hreinhardt/amqp

Loosing connection but no exception thrown

Closed this issue · 13 comments

nd2s commented

I'm writing a worker consuming RabbitMQ messages. For that I'm establishing a connection and subscribe to a queue with consumeMsgs - like in the example in the Network.AMQP docs.

If if then run the program and shut down RabbitMQ I do not see any exception getting thrown. Shouldn't I get an ConnectionClosedException? I'd need to detect this case so I can try to reconnect.

You can use addConnectionClosedHandler to detect this.

nd2s commented

But this also triggers when I close the connection manually. I do that on SIGINT/SIGTERM and in that case I do not want to reconnect.

A workaround might be to use an IORef Bool that tracks whether you want to reconnect. It would initially be True and then be set to False in your SIGINT-handler.

Alternatively we could extend addConnectionClosedHandler so that it passes the exception to the user. But that would require some internal changes and might lead to unexpected problems.

nd2s commented

I just started using this module but addConnectionClosedHandler looks like a way of handling normal control flow. The current implementation that calls handler on any connection close without knowing what caused it seems fine to me.

But server closing the connection is an exceptional case that should be handled by throwing ConnectionClosedException. Is there a reason why this isn't done in the current implementation? It only seems to be used when connecting failed ("Could not connect to any of the provided brokers").

The critical question is: Where should we throw the ConnectionClosed exception to?

In case of a blocking function call like publishMsg, we send the exception to the thread that calls the function.

But in case of a non-blocking call like consumeMsgs, it doesn't feel right to send the exception to the thread that called consumeMsgs, because that thread might now be doing something completely unrelated and probably doesn't expect an exception.

nd2s commented

Not sure if that makes sense but could there be a blocking consumeMsgs' that then also receives exceptions?

Edit: In consumer-only applications there isn't need for non-blocking anyway. I'm currently using an MVar to keep the main thread from terminating.

Should be possible, but I'm not sure if that function would allow you to do anything you can't do right now (using addConnectionClosedHandler).

nd2s commented

Maybe I'm misunderstanding you (or the module), but how can one use addConnectionClosedHandler to detect whether the connection close was normal control flow vs. exceptional without using messy external constructs like IORef?

Sure I can use that as a workaround now, but I think the module should offer some way to handle all the exceptions when consuming. But maybe addConnectionClosedHandler' :: Exception e => (Maybe e -> IO ()) -> IO () or similar that acts like a catch would be a better idea.. Kind of what you suggested earlier.

I wouldn't say that using an IORef is messy. It seems like a straight-forward solution to me.

I just remembered that we also have addChannelExceptionHandler. I think this should allow you to detect erroneous connection closes.

I don't disagree that there should be a more natural way to handle these situations but I'm not sure how exactly that should look. So unless there's a situation that can't be handled right now (using simple workarounds), I'm reluctant to make any big changes.

nd2s commented

Ah, addChannelExceptionHandler sounds useful. Wasn't aware of that.

Why does that take SomeException?

It gives you the exception that was internally thrown. It's not really elegant but somebody needed that function and was happy with this implementation.

nd2s commented

Hmm ok. I hope I can work with SomeException.

In any case: That exception handler solves the problem. Thank you very much for your help!
Closing this ticket now.

Alright, let me know if it doesn't work out.