It was thus said that the Great Rett Berg once stated:
> Sorry y'all, I wasn't subscribed to this thread so missed a large amount of
> the conversation
Sainan:
> > For cases like "forget about the current thread", you can just tell the
> > scheduler directly, e.g. such that the entry referencing the current
> > coroutine becomes nil.
I've found that if I want to "forget about the current thread" (assuming
that "current thread" is "coroutine that is running" is to just return. The
coroutine is now dead, the "executor" knows it dead because it does nothing
to schedule a dead coroutine. That's how I handle it. I do NOT handle the
"this coroutine wants to kill/cancel that other coroutine" because a) I
haven't had the need for that, and b) how does "this coroutine" get a
reference to "that other coroutine"?
> In LAP you just yield "forget" for this case. I used to forget on nil/false
> but as you say, I was accidentally yielding and not getting an error. Now
> coroutine.resume should only return nil if the coroutine is dead (function
> done) -- and it throws an error otherwise.
I would think you would want to avoid resuming a dead coroutine.
Me:
> > That's because the code doing the yield() has more context about what it
> > wants than the scheduler. Dealing with TCP is different from TLS is
> > different from a TTY. The scheduler doesn't need to know all the gritty
> > details, just that ths coroutine has yielded for some reason. It's up to
> > some other code that runs on the main coroutine (the one receiving all
> > the events from select()) to reschedule the appropriate coroutine based
> > upon the event.
>
> I don't understand. You want your executor to poll a LIST of values which
> are associated with coroutines -- therefore the logic of handling this
> behavior belongs in the scheduler -- not in the coroutine. The coroutine
> just says it wants the scheduler to poll on a specific fileno with a
> specific pollcode (i.e. POLLIN).
Here's the flow I have: I have the main coroutine (the one Lua starts
with before any coroutine are created). This is executed (sans some error
checking):
SOCKETS:wait(timeout);
for event in SOCKETS:events() do
event.obj(event)
end
So yes, I have a call to select() [1], and the returned events have data
associated with them---in this case, it's a function that is run in the main
coroutine, called from the "executor" when the appropriate event comes in.
For a TCP listening socket, this code is:
nfl.SOCKETS:insert(sock,'r',function()
local conn,remote,err = sock:accept()
if not conn then
syslog('error',"sock:accept() = %s",errno[err])
return
end
conn.nonblock = true
conn.nodelay = true
local ios,packet_handler = create_handler(conn,remote)
ios.__co = nfl.spawn(mainf,ios)
nfl.SOCKETS:insert(conn,'r',packet_handler)
end)
The socket is wrapped around a Lua-like file object ((it has functions
o:read(), o:write(), o:flush(), o:lines(), o:seek(), o:setvbuf() and
o:close()), and then a function is added to handle read events for this new
socket. If there's data for the connected socket, the event is handled by
the main coroutine in this code:
function(event)
assert(not (event.read and event.write))
if event.hangup then
ios._eof = true
nfl.schedule(ios.__co,"")
return
end
if event.read then
local _,packet,err = ios.__socket:recv()
if packet then
ios._eof = #packet == 0
nfl.schedule(ios.__co,packet)
else
if err ~= errno.EAGAIN then
syslog('error',"socket:recv() = %s",errno[err])
nfl.schedule(ios.__co,false,errno[err],err)
end
end
end
if event.write then
nfl.SOCKETS:update(ios.__socket,'r')
nfl.schedule(ios.__co,true)
end
end
Note: the variable ios is an upvalue and is the Lua-file like object of the
socket.
My "executor" doesn't need to know the details of how to handle the
event---it just passes the event off to some other code. So for a connect
socket, if we get a 'hangup' event (EPOLLHUP, POLLHUP, whatever), we
schedule the coroutine with no data; for a read event, we schedule the
coroutine with the packet and for write, we reset the event trigger to
readonly, and schedule the coroutine to resume. I notice one big difference
between our "executors" is that I can schedule a coroutine with data; yours
doesn't allow for that at all.
And keep in mind, all this code dealing with events from select() are
running on the main coroutine, along with the "executor". There are details
in the coroutines as well. For instance, a coroutine calls:
data = conn:read("*l") -- read a line of data
If there's no data already buffered, the conn:read() routine will eventually
get to some code that does (effectivel):
readdata = coroutine.yield()
that will pause the coroutine until a read event (see above) delivers a
packet and reschedules the coroutine to resume.
> I guess there are other cases I want to support besides poll, including
> "nice()" (yielding but wanting to be run ASAP)
Yup, I can do that in a coroutine:
nfl.schedule(coroutine.running())
coroutine.yield()
I don't have that as a function because I just didn't have a need for it,
and no one has asked for one yet.
> and sleeps don't need to use
> polls (a pure-lua binary heap is just fine IMO).
That's the one type of "event" my "executor" handles. To sleep:
nfl.timeout(3) -- wait 3 seconds
coroutine.yield()
Again, no function for that because I haven't had a need for it. I use
the timeouts mostly for dealing with functions that might block too long:
local data = {}
nfl.timeout(5) -- wait 5 seconds for a connection
local conn = tcp.connect("
example.com",'gopher')
nfl.timeout(0) -- cancel timeout
if conn:write("foobar\r\n") then
for line in conn:lines() do
table.insert(data,conn:read("*l"))
end
end
conn:close()
And I use a pure-Lua binary heap for the timeout queue.
-spc
[1] I'm using "select()" as the designator for a series of functions
that all do the same thing---wait for I/O on a file descriptor.
This could be epoll_wait(), kqueue(), poll() of select(), depending
upon the operating sytsem.