I’ve got a connection generated by a C library (LibPQ).
It returns an FD (which is either a socket or pipe).
I need to see if that fd is writable via the event loop, and then call a function in the C library to let it process data from/to the connection.
With libevent, the below script works, but with epol it fails. I wonder if there’s something I need to be calling to “clear” the writeable status?
Also, I know this method is kind of internal, so I’m open to other suggestions.
require "socket"
spawn do
while 1
puts "ping"
sleep 5.seconds
end
end
puts Crystal::EventLoop.current.class.name
io = TCPSocket.new("localhost", 80)
Crystal::EventLoop.current.wait_writable(io)
puts "waited"
Crystal::EventLoop.current.wait_writable(io)
puts "waited 2"
sleep
$ CRYSTAL_OPTS=-Devloop=libevent crystal build a.cr && ./a
Crystal::EventLoop::LibEvent
ping
waited
waited 2
ping
^C
$ CRYSTAL_OPTS= crystal build a.cr && ./a
Crystal::EventLoop::Epoll
ping
ping
^C
I do not have any good answer to your question about the difference in behavior. However, regarding
I need to ask - are you certain it is wait_writable
and not wait_readable
that you are supposed to be using? wait_readable
will wake when there is something new from the other side, but wait_writable
will not. On the other hand, if you are sending and it stops being writable, then the regular methods that write will loop in wait_writable
as it should.
Oh, and have you seen GitHub - will/crystal-pg: a postgres driver for crystal ?
I’m quite sure it’s wait_writable that fails. In this example, I’ve got nginx listening, and libevent does exactly what I’d expect here, letting me know the connection is ready for writing twice.
For my full PQ example, that also fails when checking for writability the second time via epol.
(PostgreSQL: Documentation: 17: 32.1. Database Connection Control Functions says to assume writability when starting the connection as well fwiw.)
I also have seen wil-pg, thanks. If this continues I’ll be going with that, but there are a couple other places I find this pattern, so it behooves me to look for a solution.
I appreciate your help.
The ssh2 shard hit the same issue with the new epoll event loop. They were doing too many waits that succeeded for some reason. I now guess it’s because libevent always adds then removes the fd
to/from epoll, while we now keep it for as long as it lives; thanks to the add on every attempt, maybe the kernel would tell that the buffer is empty or not, but the new epoll event loop only gets a notification when the state changes, so you must empty the buffer (read) or fill the buffer (write) before waiting.
BTW: if PQ states that you must assuming writability then you mustn’t wait before writing: just write. By default, an opened socket is writable (the kernel buffer is empty).
Then you shall react on what PQ tells you: shall you wait on read or on write, or both.
Sweet, this fixed things! Indeed, epol only responds to is_writeable or is_readable when the previous state of the fd changes. Otherwise it hangs forever waiting for this. Thank you so much!
Well, epoll has multiple operating modes, but we chose “edge-trigerred” notifications, this requires to exhaust the read buffer or to fill the write buffer, then wait on read and/or write when told to.
This is for performance reasons, it’s faster to read/write then wait only when needed, than always wait then read/write.