Skip to content

Commit

Permalink
fix(yielding) error if yield is unexpected/user error (#124)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tieske committed Jul 28, 2022
1 parent 6fe25d6 commit 25c4d51
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 7 deletions.
3 changes: 3 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ <h2><a name="history"></a>History</h2>
<dl class="history">
<dt><strong>Copas x.x.x</strong> [unreleased]</dt>
<dd><ul>
<li>Fixed: yielding to the Copas scheduler from user-code now throws a proper error and
no longer breaks the loop. Breaking the loop could also happen if a thread terminated with
at leaast 2 return values.</li>
<li>Added: added <code>sempahore:destroy()</code></li>
<li>Added: <code>copas.settimeouts</code>, to set separate timeouts for connect, send, receive</li>
<li>Added: queue class, see module "copas.queue"</li>
Expand Down
30 changes: 23 additions & 7 deletions src/copas.lua
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ local coroutine_create = coroutine.create
local coroutine_running = coroutine.running
local coroutine_yield = coroutine.yield
local coroutine_resume = coroutine.resume
local coroutine_status = coroutine.status


local copas = {}
Expand Down Expand Up @@ -988,18 +989,33 @@ local function _doTick (co, skt, ...)
return
end

-- res: the socket (being read/write on) or the time to sleep
-- new_q: either _writing, _reading, or _sleeping
local ok, res, new_q = coroutine_resume(co, skt, ...)

if ok and res and new_q then
if new_q == _reading or new_q == _writing or new_q == _sleeping then
-- we're yielding to a new queue
new_q:insert (res)
new_q:push (res, co)
else
if not ok then pcall (_errhandlers [co] or _deferror, res, co, skt) end
if skt and copas.autoclose and isTCP(skt) then
skt:close() -- do not auto-close UDP sockets, as the handler socket is also the server socket
end
_errhandlers [co] = nil
return
end

-- coroutine is terminating

if ok and coroutine_status(co) ~= "dead" then
-- it called coroutine.yield from a non-Copas function which is unexpected
ok = false
res = "coroutine.yield was called without a resume first, user-code cannot yield to Copas"
end

if not ok then
pcall(_errhandlers[co] or _deferror, res, co, skt)
end

if skt and copas.autoclose and isTCP(skt) then
skt:close() -- do not auto-close UDP sockets, as the handler socket is also the server socket
end
_errhandlers[co] = nil
end


Expand Down
19 changes: 19 additions & 0 deletions tests/errhandlers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,25 @@ if _VERSION ~= "Lua 5.1" then
end


tests.yielding_from_user_code_fails = function()
local old_print = print
local msg
print = function(errmsg) --luacheck: ignore
msg = errmsg
--old_print(msg)
end

copas.loop(function()
copas.sleep(1)
coroutine.yield() -- directly yield to Copas
end)

print = old_print --luacheck: ignore

assert(msg:find("coroutine.yield was called without a resume first, user-code cannot yield to Copas", 1, true), "got:\n"..msg)
end


tests.handler_gets_called_if_set = function()
local call_count = 0
copas.loop(function()
Expand Down

0 comments on commit 25c4d51

Please sign in to comment.