zmq连接到使用子进程打开的脚本

时间:2016-05-16 06:31:33

标签: python python-3.x subprocess zeromq pyzmq

我有一个相当复杂的socket(ZeroMQ - REQ/REP )基于python程序,我想通过在同一台机器上运行一个简单的套接字脚本来验证它。

测试脚本就是这样的。

import subprocess
import zmq
import json

# ...

for call, response in zip(test_calls, expected_responses):
    p = subprocess.Popen(['python', 'main.py'], stdout=subprocess.PIPE)

    context = zmq.Context()
    socket = context.socket(zmq.REQ)
    socket.setsockopt(zmq.RCVTIMEO, 1000)
    socket.connect("tcp://localhost:8084")

    socket.send_string(json.dumps(call))
    r = json.loads(socket.recv_string())

    assert r == response

    p.terminate()
    socket.close()

(可能值得注意的是,它实际上是在nose2中使用这样的测试实现的,但我觉得这超出了这个问题的范围并且会使样本复杂化。这几乎总结了测试中发生的事情)。

85%的时间,这将工作,一切都会通过。 Whooho!其他15%的时间,我在zmq.error.Again: Resource temporarily unavailable行上r = json.loads(socket.recv_string())(如果我没有zmq.RCVTIMEO设置它就会挂起)。

想知道这是否是一个计时过程(子流程无法及时启动/停止)我在这个地方放了几个time.sleep()电话,但它似乎没有做任何事情。

我在套接字部分和pdb之后插入一个catch,检查python子进程的stdout。我在应用程序中有几个print语句,通过套接字打印到stdout每个调用和响应,但它没有收到任何输入,因此当然recv正在进行超时。

我之前从未遇到过zmq的这类问题,所以我认为这可能与像这样的子进程的使用有关。有谁知道问题可能是什么以及如何解决它?

感谢。

更新:所以看起来这些进程没有终止(尽管在主应用程序中使用了signal.signal(signal.SIGTERM, close_app)信号)。这是否会导致与通过zmq进行通信的活动进程混淆?最初调用p.kill()而不是p.terminate()似乎可以解决问题,尽管它仍以相同的方式失败一次或两次。

更新2:似乎有效的方法是直接调用命令kill

subprocess.call(['kill', str(p.pid)])
counter = 0
while p.poll() is None:
    time.sleep(0.1)
    counter += 1
    if counter > 20:
        p.kill()

在大多数情况下,似乎优雅地将其关闭。

1 个答案:

答案 0 :(得分:2)

问题可能是什么

可能与所述子流程中未发布的代码有关,这导致在子流程强制终止期间发起的观察到的行为(包括其他资源,在智能和非常特征中管理 - 丰富的多线程 zmq.Context( n_IO_threads = 1 ) 实例,您只能通过有限的先验编码/执行控件来实现。

考虑{SIGTERM|SIGKILL|...}而不是紧急刹车,恐慌按钮,这不是分布式系统设计的明智解决方案

一旦进入分布式系统设计,我们宁可忘记使用无上下文工具SIGTERM等,但最好还是将自己的软信号控制平面纳入其中。新设计的分布式系统基础设施。

这有助于"远程" -agent的行为符合这种软信号的实际环境,并允许执行(在您的完整算法控制下)所有必要的安全保护,资源清理和终止前的职责,以便最后优雅地清理退出。

我可能听起来很老套,但在您的代码最终指示您的所有.close()个实例之前,请始终明确指示 zmq.Context() 的套接字<强> .term() 即可。据报道这不是必要的,但在资源处理方面做得干净和公平是恕我直言,这是分布式系统设计/实施的公平责任。

没有例外,没有任何借口。

ZMQ_LINGER

中被遗忘的零

值得一提的一个例子是 ZeroMQ API 参数的 ZMQ_LINGER 默认值,如果没有设置,则默认值为默认值 0 的值,这意味着,一旦此类ZeroMQ - 套接字实例(明确或隐式地)被指示 .close() 并发生仍然有一个ZMQ_LINGER == 0,套接字端点将 BLOCK ,直到来自交易对手缓冲区的所有消息都被传递,这可能导致您的分布式处理挂机而没有任何机会解决这样的死锁事后,如果没有正确预设,不要永远等待待处理的消息。

较新的pyzmq文档明确警告不要.destroy() zmq.Context个实例(并且通过权威颁发的.close()盲目地让套接字.destroy() - d,这是一个自己的代码控制)

  

<强> ctx.destroy( linger = None )
  关闭与此上下文关联的所有套接字,然后终止上下文。如果指定linger,则在关闭之前将设置套接字的LINGER sockopt。

  警告

  .destroy涉及调用zmq_close(),这不是线程安全的。如果其他线程中有活动套接字,这不能被称为 (这个建议很可能是SIGTERM&amp; al会忽略的,不是吗? )

所以甚至还有一些理由不依赖SIGTERM恶魔&#39;服务。

正在使用的端口

另外,释放占用的传输类资源需要一些时间。因此,拥有刚刚发布 IP:port 的代码并不意味着另一个实例/进程/线程可能会直接跳入并捕获相同的端口,而没有一些与O / S相关的延迟。而是在这方面检查您的资源重用/发布策略(我敢在此区域冒任何阻塞风险,并使用一些端口地址池来轮换和 FILO - 排队,以便至少推迟任何潜在的重用情况,直到合理的O / S相关延迟已经过期 - 恕我直言,防止阻塞状态比在阻塞状态上事后处理异常要好得多)

.bind() 之前

.connect()

是另一个这样的问题。一旦 subprocess.Popen(...) 启动,O / S服务启动前需要一些时间,并使子流程开始呼吸它自己。

如果您的第一个进程已经处于活动状态且正在执行已达到.connect(),则在生成的子进程实例进入.bind()之前,分布式系统将会阻塞。

设置/拆除往返次数不能简化为零。资源不是一次性用品。有一些系统相关的维护&amp;分担与使用相关的开销。

最后.recv_string()可能并确实引发了ZMQError EAGAIN

在某些情况下,本地节点中还没有任何消息准备好通过任何.recv*()方法检索它,无论是{.recv|.recv_string|.recv_json|&al}模式下的flags = zmq.NOBLOCK