-
Notifications
You must be signed in to change notification settings - Fork 473
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
connection.close causes process to die and error is uncatchable #334
Comments
Any ideas on this? |
Hi @mateodelnorte, I haven't used the promise based api, but am wondering what happens if you listen for |
It catches the errors, for instance allowing me to log the error, but the process still dies immediately after. https://github.com/mateodelnorte/servicebus/blob/master/bus/rabbitmq/bus.js#L50-L51 |
Are you sure that it's the same error event? Maybe the first one is being logged then swallowed, but a second error event being emitted from something other than the connection? Does |
Scratch my previous two comments. Did some testing and they're wrong. This seems to be happening because nothing is handling socket events, after connect. Adding sock.on('error', (err) => {
console.log('yup, socket error: ', err)
}) to line https://github.com/squaremo/amqp.node/blob/master/lib/connect.js#L185 will cause the following behavior:
So, I think this is a combination of 1) the socket error is not being handled and rethrown as something recoverable from within amqplib and 2) it's being passed to openCallback which ends up returning to a previously resolved promise, which makes bluebird in turn do a |
Seems this issue is the same as #338. |
Did this issue ever get a solution, i am facing it as of 8th March 2019?
|
Any updates on this issue please? ` Error: Heartbeat timeout at Heart. (/opt/services/dev/builds/order-service/v3.890.7/node_modules/amqplib/lib/connection.js:426:19) at emitNone (events.js:86:13) | prod-svc-31 at Heart.emit (events.js:185:7) | prod-svc-31 | July 6th 2019, 08:32:14.791 | at Heart.runHeartbeat (/opt/services/dev/builds/order-service/v3.890.7/node_modules/amqplib/lib/heartbeat.js:88:17) | prod-svc-31 | July 6th 2019, 08:32:14.791 | at tryOnTimeout (timers.js:250:5) | prod-svc-31 | July 6th 2019, 08:32:14.791 | at Timer.listOnTimeout (timers.js:214:5) ` |
I can reproduce the originally reported error with the following script. I'm not sure whether the subsequent reports of timeouts are related though. const amqplib = require('amqplib');
(async () => {
// Heartbeat effectively disabled while due to use of debugger
const connection = await amqplib.connect('amqp://localhost:5672?heartbeat=100000')
connection.on('close', () => {
throw new Error('Oh Noes!')
});
})(); If I allow the script to connect, then restart the docker container hosting the RabbitMQ server, the connection emits a close event. If the handler throws an error, this is caught in amqplib's accept loop and emitted as a frameError, the event listener for which delegates to the onSocketError handler, however because toClosed was called when the server sent the Close command, expectSocketClose is true and the onSocketHandler falls through without emitting an error. |
A workaround is to put any code inside the close event handler within a try catch
or with a setImmediate timer, e.g. const amqplib = require('amqplib');
(async () => {
const connection = await amqplib.connect('amqp://localhost:5672?heartbeat=100000')
connection.on('close', (err) => {
setImmediate(() => {
console.log('Connection error', err)
throw new Error('Oh Noes!')
})
});
})(); or to use the const amqplib = require('amqplib');
(async () => {
const connection = await amqplib.connect('amqp://localhost:5672?heartbeat=100000')
connection.on('close', async (err) => {
console.log('Connection error', err)
throw new Error('Oh Noes!')
});
})(); The former can be caught by listening for the process 'uncaughtException' event and the latter by the process 'unhandledRejection' event |
Labelling as a bug as amqplib should not be swallowing exceptions. |
I can only think of two (unsatisfactory) ways to address this
These would only be catchable via the process.on('uncaughtException') event, and not very useful for automated recovery, but would at least flag the error to the user rather than in the above case, causing it to be completely swallowed A weaker alternative is to update the documentation to clearly state that any event handler code must not emit synchronous errors. Any thoughts @kibertoad? |
@cressie176 Maybe we can allow registering some kind of an error handler that we would use for publishing these errors? |
Yes, that's possible. It would mean wrapping each emit call within a try/catch block, e.g. try {
this.connection.emit('close');
} catch (error) {
this.connection.emit('handler_error', error);
} but does beg the question, what would we do if the Also it might cause confusion, because try/catch would only work for synchronous errors. |
handler_error would be handled purely in userspace, right? In that case I would argue that it's on a user to handle errors correctly. What would happen in case of asynchronous errors? Can you give an example code of how that would look? |
Yes, but so is the connection close event handler. I agree it would be reasonable to require that userspace handlers do not throw exceptions from synchronous code; there is not much amqplib can do to handle them, and throwing them may interfere with its internal workings. If we take this approach then I suggest updating the docs to make the requirement explicit and close the issue issue. My only concern with the above is that some users won't adhere to this even if documented, and it's not great that amqplib swallows errors.
Synchronousconnection.on('close', () => {
reconnect();
}); Asynchronousconnection.on('close', () => {
setTimeout(() => reconnect(), 1000);
}); In the synchronous example, if the reconnect function throws an error before becoming asynchronous, the error will be caught in amqplib's accept loop, misinterpreted as a frame error and swallowed. In the asynchronous example, if the reconnect function throws an error, the error will bypass amqplib entirely because the handler immediately went asynchronous through the setTimeout. As a result the error will likely crash the application but at least it would be reported. We can enforce this behaviour by emitting events from a setImmediate(() => this.connection.emit('close') However, emitting errors asynchronously will mean that the handlers execute slightly later than they do now, which may result in some unexpected behaviour. I also haven't checked whether amqplib ever listens to its own connection close/error events. If it does, then we may also find subtle behaviour changes. |
Hi there. This is in response to a reported error in servicebus, which uses amqplib (mateodelnorte/servicebus#93).
Issue: Exiting RabbitMQ causes client code to exit without error. connection.close does fire, but subsequently throwing or emitting an error is somehow swallowed.
This behavior is showing up in servicebus, and it looks like it may be related to the promise library in amqplib. For both of the following examples (throwing uncaught error and emitting unhandled error), both applications will crash with no uncaught exception or unhandled rejection. I'd expect one of the two.
Is there a particular way these errors need to be thrown/emitted/captured and re-throw/emitted?
The text was updated successfully, but these errors were encountered: