我有一个用于CPython的C扩展模块。我需要多个uWSGI工作线程来共享此模块中对象的单个实例。我正在使用自定义的multiprocessing.BaseManager
子类来实现此目的,基于此答案,该答案描述了一个非常相似的解决方案。
下面的第一个脚本是wifi.manager
(wifi.controller.IFace
是要共享的对象)。我用python3 wifi/manager.py
运行它,然后启动 Web 服务器,它运行第二个代码段中的代码以获取共享对象实例。
无线网络/管理器.py:
#!/usr/bin/env python3
from multiprocessing.managers import BaseManager
# .register() changes the class itself. We don't want to do that to BaseManager.
class WifiManager(BaseManager):
pass
if __name__ == '__main__':
# If we are executed as a script (python3 manager.py), start the server
import atexit
from multiprocessing import Lock
import wifi.controller
ifaces_lock = Lock()
ifaces = dict()
def get_iface(iface_path):
with ifaces_lock:
if iface_path not in ifaces:
# Control interface isn't open. Open it.
iface = wifi.controller.IFace(iface_path)
ifaces[iface_path] = iface
return ifaces[iface_path]
def close_ifaces():
for iface in ifaces.values():
iface.close()
WifiManager.register('get_iface', get_iface)
atexit.register(close_ifaces)
manager = WifiManager(address=('127.0.0.1', 2437), authkey=b'wifimanager')
server = manager.get_server()
server.serve_forever()
else:
# If we are imported, provide the WifiManager class ready for clients to use
WifiManager.register('get_iface')
来自 Web 应用程序的代码段:
from wifi.manager import WifiManager
...
wmanager = WifiManager(address=('127.0.0.1', 2437), authkey=b'wifimanager')
wmanager.connect()
iface = wmanager.get_iface(iface_path)
iface.scan() # And other code using the iface object
wifi.controller.IFace
对象有时会引发异常,无论是内置的异常(主要是OSError
),还是它自己的wifi.controller.WifiError
异常。有时,我希望能够在 Web 应用程序中捕获这些内容,以便向客户端呈现有意义的错误页面。但是,我注意到的是,有时这些异常会被捕获并且相同的异常(例如WifiError
) 在 Web 应用程序中引发。其他时候,Web 应用会获取一个multiprocessing.managers.RemoteError
,其中来自管理器的回溯存储为字符串。
问题是,我怎么知道它何时会引发原始异常以及何时会引发RemoteError
,以便我知道要捕获哪一个?所有 Python 文档都说:
如果调用引发异常,则由 _callmethod() 重新引发。如果在管理器的进程中引发了其他异常,则将其转换为 RemoteError 异常,并由 _callmethod() 引发。
这对我来说不是很清楚,我无法弄清楚它如何与我观察到的行为保持一致。
我相信我已经想通了。我仍然不是100%确定,但是看到这个问题没有得到很多关注,我想我会继续添加一个答案,以防未来的读者偶然发现这一点。
在原始远程调用期间WifiManager
中发生的异常将作为RemoteError
引发。在这种情况下,这意味着在 Web 应用程序中的这一行中,远程端会出现异常:
iface = wmanager.get_iface(iface_path)
之后,Web 应用程序不再直接与WifiManager
交互。它只与代理对象iface
交互。如果代理对象(或更准确地说,其引用对象)在远程端引发异常,则会在 Web 应用程序中引发相同的异常,而不是RemoteError
。因此,这意味着如果此行导致例如WifiError
或OSError
,这些是您将捕获的异常:
iface.scan() # And other code using the iface object
因此,总而言之,当远程端出现异常时,对BaseManager
(或子类)的调用会引发RemoteError
。对代理对象的调用会引发原始异常的副本。
请记住,这个答案是基于我的观察,而不是我对情况的完整和完全理解,所以这里可能有一些我不知道的陷阱。如果有人知道得更好,请纠正我。但这描述了我观察到的行为,并且似乎与问题中引用的文档一致。