Skip to content

Commit

Permalink
Change the timeout handling
Browse files Browse the repository at this point in the history
it is better to set the timeout setting only at instantiation.
  • Loading branch information
mah0x211 committed Mar 26, 2024
1 parent 4b8030a commit 30af1b6
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 13 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ jobs:
luarocks install luacov
luarocks install testcase
luarocks install assert
luarocks install os-pipe
luarocks install time-clock
-
name: Install
run: |
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ luarocks install io-reader
the following functions return the `error` object created by https://github.com/mah0x211/lua-errno module.


## r, err = io.reader.new(f)
## r, err = io.reader.new( f [, sec] )

create a new reader instance that reads data from a file or file descriptor.

**Parameters**

- `f:file*|string|integer`: file, filename or file descriptor.
- `sec:number`: timeout seconds. (default `nil` means no timeout)

**Returns**

Expand Down Expand Up @@ -63,17 +64,16 @@ print(dump({
```


## s, err, timeout = reader:read(fmt, sec)
## s, err, timeout = reader:read( [fmt] )

read data from the file or file descriptor.

**Parameters**

- `fmt:integer|string`: size of data to read, or format string as follows: (`*` prefix can be omitted)
- `*a`: reads all data.
- `*l`: reads a line. (default)
- `*L`: reads a line with the newline character.
- `sec:number`: timeout seconds.
- `*a`: reads all data.

**Returns**

Expand Down
22 changes: 13 additions & 9 deletions reader.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,20 @@ local wait_readable = require('gpoll').wait_readable
--- @class io.reader
--- @field private fd integer
--- @field private file? file*
--- @field private waitsec? number
--- @field private buf string
local Reader = {}

--- init
--- @param fd integer
--- @param f file*
--- @param sec number?
--- @return io.reader
function Reader:init(fd, f)
function Reader:init(fd, f, sec)
self.fd = fd
self.file = f
self.buf = ''
self.waitsec = sec
return self
end

Expand All @@ -68,14 +71,12 @@ end
--- read
--- wait_readable
--- @param fmt string|integer?
--- @param sec number?
--- @return string? data
--- @return any err
--- @return boolean? timeout
function Reader:read(fmt, sec)
function Reader:read(fmt)
assert(fmt == nil or type(fmt) == 'number' or type(fmt) == 'string',
'fmt must be integer, string or nil')
assert(sec == nil or type(sec) == 'number', 'sec must be number or nil')

local t = type(fmt)
if t == 'number' then
Expand All @@ -90,7 +91,7 @@ function Reader:read(fmt, sec)
local buf = self.buf
local len = #buf
if len < n then
local data, err, timeout = read(self.fd, n - len, sec)
local data, err, timeout = read(self.fd, n - len, self.waitsec)
if not data then
return nil, err, timeout
end
Expand Down Expand Up @@ -120,14 +121,14 @@ function Reader:read(fmt, sec)
return buf
end
-- read all data from the file
return read(self.fd, nil, sec)
return read(self.fd, nil, self.waitsec)
end

local buf = self.buf
local head, tail = find(buf, '\r?\n', 1)
while not head do
-- need to read more data
local data, err, timeout = read(self.fd, nil, sec)
local data, err, timeout = read(self.fd, nil, self.waitsec)
if not data then
return nil, err, timeout
end
Expand Down Expand Up @@ -155,9 +156,12 @@ Reader = require('metamodule').new(Reader)

--- new
--- @param file string|integer|file*
--- @param sec number?
--- @return io.reader? rdr
--- @return any err
local function new(file)
local function new(file, sec)
assert(sec == nil or type(sec) == 'number', 'sec must be number or nil')

local t = type(file)
local f, err
if isfile(file) then
Expand All @@ -174,7 +178,7 @@ local function new(file)
if not f then
return nil, err
end
return Reader(fileno(f), f)
return Reader(fileno(f), f, sec)
end

return {
Expand Down
53 changes: 53 additions & 0 deletions test/reader_test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ local testcase = require('testcase')
local assert = require('assert')
local fileno = require('io.fileno')
local reader = require('io.reader')
local pipe = require('os.pipe')
local gettime = require('time.clock').gettime

local TEST_TXT = 'test.txt'

Expand All @@ -25,6 +27,11 @@ function testcase.new()
assert.is_nil(err)
assert.match(r, '^io.reader: ', false)

-- test that create a new reader from file with timeout seconds
r, err = reader.new(f, 1)
assert.is_nil(err)
assert.match(r, '^io.reader: ', false)

-- test that create a new reader from filename
r, err = reader.new(TEST_TXT)
assert.is_nil(err)
Expand All @@ -40,6 +47,13 @@ function testcase.new()
assert.is_nil(err)
assert.match(r, '^io.reader: ', false)

-- test that create a new reader from pipe file descriptor
local pr, _, perr = pipe(true)
assert(perr == nil, perr)
r, err = reader.new(pr:fd())
assert.is_nil(err)
assert.match(r, '^io.reader: ', false)

-- test that return err if file descriptor is invalid
r, err = reader.new(-1)
assert.is_nil(r)
Expand All @@ -49,6 +63,10 @@ function testcase.new()
r, err = reader.new(true)
assert.is_nil(r)
assert.match(err, 'FILE*, pathname or file descriptor expected, got boolean')

-- test that throws an error if invalid sec argument
err = assert.throws(reader.new, f, true)
assert.match(err, 'sec must be number or nil')
end

function testcase.read_with_format_string()
Expand Down Expand Up @@ -112,3 +130,38 @@ function testcase.read_nbyte()
err = assert.throws(r.read, r, -1)
assert.match(err, 'negative number')
end

function testcase.read_with_timeout()
local pr, pw, perr = pipe(true)
assert(perr == nil, perr)

-- test that read timeout after 0.5 second
local r = assert(reader.new(pr:fd(), .5))
local t = gettime()
local s, err, again = r:read()
t = gettime() - t
assert.is_nil(err)
assert.is_nil(s)
assert.is_true(again)
assert.is_true(t >= .5 and t < .6)

-- test that read line from pipe
pw:write('hello\nworld!\n')
s, err, again = r:read()
assert.is_nil(err)
assert.is_nil(again)
assert.equal(s, 'hello')

-- test that read line from pipe even if peer of pipe is closed
pw:close()
s, err, again = r:read()
assert.is_nil(err)
assert.is_nil(again)
assert.equal(s, 'world!')

-- test that return nil if eof
s, err, again = r:read()
assert.is_nil(s)
assert.is_nil(err)
assert.is_nil(again)
end

0 comments on commit 30af1b6

Please sign in to comment.