Skip to content

Commit

Permalink
Merge pull request #3 from mah0x211/change-the-timeout-handling
Browse files Browse the repository at this point in the history
Change the timeout handling
  • Loading branch information
mah0x211 authored Mar 26, 2024
2 parents 4b8030a + 30af1b6 commit 829d120
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 829d120

Please sign in to comment.