zeromq.js icon indicating copy to clipboard operation
zeromq.js copied to clipboard

What to do when "Socket temporarily available"

Open rgbkrk opened this issue 5 years ago • 8 comments

Context: I'm running a ZeroMQ Dealer and sending a message that the Router (written with pyzmq) can't consume. When the dealer does a receive, I get this error:

{ Error: Socket temporarily unavailable
    at Session.sendOnControl (/Users/kylek/code/src/github.com/rgbkrk/zeromq-new-for-nteract/session.js:50:49)
    at process._tickCallback (internal/process/next_tick.js:68:7) errno: 35, code: 'EAGAIN' }

My understanding is that the socket is timing out. I can code around this, but I'm curious if I should be looking at this differently.

rgbkrk avatar Dec 11 '19 04:12 rgbkrk

Good question; the error messages are maybe a bit cryptic given that they're currently just short strings based on the errno received by libzmq.

"Socket temporarily unavailable" / EAGAIN means that the socket operation send()/receive() was not possible; this can be because the high water mark was reached, a buffer is full, or if the message is unroutable. For receiving from dealers I think this would only happen if you configured a receiveTimeout. Is that possible?

rolftimmermans avatar Dec 11 '19 06:12 rolftimmermans

Oh, and another reason for EGAIN is if two calls to receive() or two calls to send() are made while the first has not resolved. That's explicitly disallowed because it breaks the queueing semantics and the possibility of the application performing back-pressure if that would be allowed.

The easiest way around that scenario is to include the send/receive in async loops, like in the examples:

for await (const [msg] of sock) {
    console.log("work: %s", msg.toString())
}
while (true) {
    await sock.send("some work")
    await new Promise(resolve => setTimeout(resolve, 500))
}

rolftimmermans avatar Dec 11 '19 06:12 rolftimmermans

I have only encountered this while sending since the receiving part tends to be in one location wrapt in a loop while there could be a lot of places in the code that might need or want to send.

When I first encountered it took me some time to figure out what was causing it since it got printed to the console as a UnhandledPromiseRejectionWarning and the error does not have a stack trace and the error could be more helpful like this where the link would provide more information and examples.

{ Error: Socket temporarily unavailable because it is busy readying. 
This could be caused by reading to rapidly from the socket 
see: https://github.com/zeromq/zeromq.js/https://github.com/zeromq/zeromq.js/{wiki, docs}/errors/EAGAIN
    at zeromq.js/src/socket.cc:512:9
    at MyObject.myfunction (/code-using-zeromq/session.js:50:49)
    at process._tickCallback (internal/process/next_tick.js:68:7) errno: 35, code: 'EAGAIN' }

My workaround at the moment is to warp the send in an async queue.

const {default: PQueue} = require('p-queue');
const queue = new PQueue({concurrency: 1});
const send = (...args) => queue.add(() => sock.send(...args));

Promise.all([
  send('first'),
  send('second'),
]);

Is it save to resend every time one encounters the an error with the code EGAIN during sending?

agirorn avatar Dec 12 '19 09:12 agirorn

The best solution is to use await send(), because it will throw an error in-place, with a correct stack.

I wish there was a way to disallow send()/receive() without using await.

The call await send() might not resolve immediately with some combinations of sockets + options. But if that is an issue and you wish to always queue immediately where possible and fail otherwise, use the socket option sendTimeout: 0.

It's usually a bad idea to implement an additional queue on top of the native ZeroMQ queue. There are some exceptions to this in advanced use cases; for example if you're implementing a broker of some kind and you want to temporarily buffer incoming requests while a worker is not yet available.

Related; the documentation and error format can definitely use some improvement. Thanks for your suggestion @agirorn!

rolftimmermans avatar Dec 12 '19 11:12 rolftimmermans

The send has an await. It's the receive that fails, due to the client erroring. I'll try to make a smaller example.

Queuing seems like a good idea regardless since we may have different sends occurring.

rgbkrk avatar Dec 12 '19 18:12 rgbkrk

I have updated the error messages in master. Another thing I realised is that a concurrent send now returns EAGAIN which is probably a bad idea because that makes it impossible to distinguish this scenario from actual EAGAIN errors from libzmq. I changed it to EBUSY with a custom error message to describe the underlying issue a bit better.

rolftimmermans avatar Dec 16 '19 15:12 rolftimmermans

Ok great, I'll look forward to the next beta release. 😄

rgbkrk avatar Dec 16 '19 18:12 rgbkrk

A new beta is out; would love to hear wat you think and if you manage to pinpoint what is causing the issue you described!

rolftimmermans avatar Dec 17 '19 14:12 rolftimmermans