luv icon indicating copy to clipboard operation
luv copied to clipboard

Allow Coroutine Continuations

Open truemedian opened this issue 3 years ago • 5 comments

Allows passing a coroutine in place of a callback in all methods using uv_req_t. The coroutine will be yielded and then resumed using the synchronous argument list [not the (err, value) callbacks recieve]. I have not yet completely verified the behaviour when passing a coroutine that is not the running thread, however it should work.

Disabled by default, any blocking function call inside of the not-main coroutine will be a yield + resume to allow the libuv event loop to continue running. This behavior can be changed with the LUV_FORCE_COROUTINE_CONTINUATION flag to cmake.

Tap has been modified to also run tests inside of a coroutine to provide a testing suite for these changes.

I have not run into any issues so far, but the luv test suite definitely does not cover all bases.

truemedian avatar Sep 26 '22 18:09 truemedian

Just to help me wrap my head around this, could you provide some example code showing a use-case this change allows for (e.g. before, code with callbacks would be written like this, but now it could be written like this).

squeek502 avatar Sep 27 '22 01:09 squeek502

Should add some tests to cover with coroutine or not.

zhaozg avatar Sep 27 '22 01:09 zhaozg

Just to help me wrap my head around this, could you provide some example code showing a use-case this change allows for (e.g. before, code with callbacks would be written like this, but now it could be written like this).

A quick and simple comparison would be using uv_fs_ (since its the largest consumer of uv_req_t).

completely async:

uv.fs_open(path, flags, mode, function(err, fd)
  assert(fd, err)
  -- do something with fd
end

currently completely blocking:

local fd, errstr, errname = uv.fs_open(path, flags, mode)
assert(fd, errstr)
-- do something with fd

note: this would be a coroutine continuation if inside a non-main coroutine with LUV_FORCE_COROUTINE_CONTINUATION defined

coroutine continuation (yield + resume):

local fd, errstr, errname = uv.fs_open(path, flags, mode, coroutine.running())
assert(fd, errstr)
-- do something with fd

I attempted to make the resume of the coroutine match a blocking function call so that LUV_FORCE_COROUTINE_CONTINUATION can work seamlessly.

Should add some tests to cover with coroutine or not.

I definitely agree that this needs a lot more tests. Thats what adding coroutine.wrap()'d tests to tap was intended to accomplish; but it doesn't test the new surfaces that I introduced.

truemedian avatar Sep 27 '22 02:09 truemedian

Seems like the added test is failing on LuaJIT

Bilal2453 avatar Sep 27 '22 12:09 Bilal2453

I can't quite understand why exactly the appveyor build is failing. The failing tcp test passes on my machine on windows and there seems to be garbage at the bottom of the log?

truemedian avatar Sep 29 '22 13:09 truemedian

Closed because it makes continuations much more complicated. Similar behavior can be reproduced via:

local thread = coroutine.running()
local req, err = uv.write(stream, function(...)
  assert(coroutine.resume(thread, ...)) -- resume waiting coroutine, beware this will eat your stack trace
end
if not req then
  return req, err -- write failed for some reason
end
return coroutine.yield() -- wait for write to finish

truemedian avatar Jun 17 '24 20:06 truemedian