ZeroMQ: How to react on different signal types on EINTR
注意:这个问题与我的问题相当接近,但我至少可以对提供的解决方案使用一些工作示例,也许
目前我对阻塞
1 2 3 4 5 6 7 8 | try { zmq::poll(&items, number, timeout); } catch (zmq::error_t &ex) { if (ex.num() != EINTR) { throw; } } ... |
我的意图是:重新抛出所有捕获的异常,但那些由中断的系统调用触发的异常,我通常可以忽略(例如
如果是
目前我最好的选择是安装一个监听
如果我正确阅读了原始发帖者的问题,@frans 会询问是否有办法重新抛出某些异常,其中 C 异常包含
提出了两个不同的问题,关于 POSIX
- POSIX 信号与 C 异常处理无关。
-
zmq::error_t 是由zmq::poll() 作为返回EINTR 的系统调用的结果生成的 C 异常。
TL;DR 回答:不,没有更清洁的方法。
POSIX/Unix/Linux/BSD
其他警告:不要从 POSIX/Unix/Linux/BSD 信号处理程序中抛出 C 异常。这在此 SO 主题中之前已讨论过。
我遇到了一个类似的问题,并且盯着这个问题及其答案看了很长时间,希望如果我足够努力的话,一个解决方案会神奇地出现。
最后,我采用了本文中详述的方法。
底线是我们需要一个基于
这实际上是在轮询器中添加一个"信号outlets"。轮询器将返回(使用
现在,在您的 poll 调用之后,当您通常通过现在可读/可写的套接字时,您首先检查您从信号处理程序中设置的标志。信号在
一些注意事项:为此,您不能使用任何其他阻塞调用,因此您所有的
另一件事是您已经提到的:在库的情况下,用户应该可以自由安装自己的信号处理程序。因此,如果他们想以稳健的方式使用您的库,您可能需要指导用户如何以这种方式正确处理信号。我认为如果您向用户公开标志(或翻转标志的函数)以便他们可以从自己的信号处理程序中调用它应该是可行的。
好的,所以有三种方法,一种简单,两种麻烦
干净
简单的方法是这样运行的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | zmq::socket_t inSock; zmq::pollitem_t pollOnThese[2]; int quitLoop = 0; <code to connect inSock to something> pollOnThese[0].socket = NULL; // This item is not polling a ZMQ socket pollOnThese[0].fd = 0; // Poll on stdin. 0 is the fd for stdin pollOnThese[0].event = ZMQ_POLLIN; pollOnThese[1].socket = &inSock; // This item polls inSock pollOnThese[1].fd = 0; // This field is ignored because socket isn't NULL pollOnThese[1].event = ZMQ_POLLIN; while (!quitLoop) { zmq::poll(pollOnThese,2); if (pollOnThese[0].revents == ZMQ_POLLIN) { // A key has been pressed, read it char c; read(0, &c, 1); if (c == 'c') { quitloop = 1; } } if (pollOnThese[1].revent == ZMQ_POLLIN) { // Handle inSock as required } } |
当然这意味着你的"abort"不再是用户按下 CTRL-C,他们只是按下键 \\'c\\',或者实际上任何可以写入这个标准输入的程序都可以发送一个 \\'c \\'。或者,您也可以添加另一个 ZMQ 套接字作为命令通道。这里根本没有对信号的容忍度,但我一直发现将信号的使用与 Actor 模型编程混合使用是一件非常尴尬的事情。见下文。
凌乱
使用信号的混乱方式看起来像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | zmq::socket_t inSock; zmq::pollitem_t pollOnThis; int quitLoop = 0; <code to connect inSock to something> <install a signal handler that handles SIGINT by setting quitLoop to 1> pollOnThis.socket = &inSock; // This item polls inSock pollOnThis.fd = 0; // This field is ignored because socket isn't NULL pollOnThis.event = ZMQ_POLLIN; while (!quitLoop) { try { zmq::poll(&pollOnThis, 1); } catch (zmq::error_t &ex) { if (ex.num() != EINTR) { throw; } } if (pollOnThis.revent == ZMQ_POLLIN && !quitLoop) { // Handle inSock as required bool keepReading = true; do { try { inSock.recv(&message) keepReading = false; } catch (zmq::error_t &ex) { if (ex.num() != EINTR) { throw; } else { // We also may want to test quitFlag here because the signal, // being asynchronous, may have been delivered mid recv() // and the handler would have set quitFlag. if (quitFlag) { // Abort keepReading = false; } else { // Some other signal interrupted things // What to do? Poll has said a message can be read without blocking. // But does EINTR mean that that meassage has been partially read? // Has one of the myriad of system calls that underpin recv() aborted? // Is the recv() restartable? Documentation doesn't say. // Have a go anyway. keepReading = true; } } // if } // catch } while (keepReading); <repeat this loop for every single zmq:: recv and zmq::send> } } |
混合(也很混乱)
在此处的 ZeroMQ 指南文档中,他们通过让信号处理程序写入管道并将管道包含在 zmq_poll 中,就像我在上面使用标准输入所做的那样,它们有点融合了这两种想法。
这是将异步信号转换为同步事件的常用技巧。
但是根本没有提示是否可以重新启动任何 zmq 例程。他们只是将其用作启动干净关闭的一种方式,已放弃任何正在进行的 recv() 或 send()。如果为了彻底中止,您需要完成一系列 recv() 和 send() 例程,那么如果正在使用信号,则无法保证这是可能的。
如果我错了,请原谅我,但感觉就像你正在重新利用 SIGINT 的意义,而不是"立即终止整个程序"。如果是这样,您可能希望稍后恢复您的通信循环。在这种情况下,谁知道在您的信号到达后是否有任何 recv() 或 send() 调用可以恢复。 zmq 将使用一大堆系统调用。它们中的许多在某些情况下是不可重新启动的,并且不知道 ZMQ 是如何使用这些调用的(缺少阅读它们的源代码)。
结论
基本上我会完全避免使用信号,尤其是当您不想安装自己的处理程序时。通过使用标准输入或管道或另一个 ZMQ 套接字(或实际上所有三个)作为包含在 zmq_poll() 中的"中止"通道,您将提供一种简单有效的方法来中止循环,并且不会产生任何并发症从它的使用。