定期执行人#

PyMongo实现了 PeriodicExecutor 有两个目的:作为 Monitor ,并定期检查 OP_KILL_CURSORS 必须发送到服务器的消息。

杀死光标#

不完全迭代 Cursor 在客户机上表示服务器上打开的游标对象。在这样的代码中,在完成迭代之前,我们会丢失对游标的引用:

for doc in collection.find():
    raise Exception()

我们试图发送一个 OP_KILL_CURSORS to the server to tell it to clean up the server-side cursor. But we must not take any locks directly from the cursor's destructor (see PYTHON-799 ),因此我们无法安全地使用发送消息所需的PyMongo数据结构。解决方案是将光标的id添加到 MongoClient 不用锁。

每个客户端都有一个 PeriodicExecutor 专门检查数组中的游标ID。它看到的任何结果都是在服务器端游标仍处于打开状态时释放的游标的结果。执行器可以安全地获取所需的锁,以便发送 OP_KILL_CURSORS 消息。

停止执行器#

正如 Cursor 不能从它的析构函数中取任何锁,也不能 MongoClientTopology . 因此,尽管客户端调用 close() 在它的kill cursors线程上,拓扑调用 close() 在所有的监视器线程上 close() 方法无法实际调用 wake() 在遗嘱执行人身上,因为 wake() 拿了把锁。

相反,执行器会定期唤醒以检查 self.close 已设置,如果设置,则退出。

如果线程在Python解释器的关闭序列中延迟唤醒,那么它可能会记录错误,因此我们尝试在此之前连接线程。每个周期执行器(监视器或kill cursors线程)都会将weakref添加到名为 _EXECUTORS ,在 periodic_executor 模块。

exit handler 在关闭时运行,并通知所有执行器停止,然后尝试(短暂超时)联接所有执行器线程。

监测#

对于拓扑中的每个服务器, Topology 使用定期执行器启动监视线程。这个线程一定不能阻止拓扑被释放,所以它会破坏拓扑。此外,它使用weakref回调在拓扑释放后不久终止自身。

实线表示强参照,虚线表示弱参照:

../_images/periodic-executor-refs.png

Stopping Executors 以上是对 _EXECUTORS 集合。

这是 Server Discovery And Monitoring Spec 睡觉的监视器可以早点被唤醒。除了偶尔醒来做他们指定的家务,以及偶尔的中断,定期的执行者也会定期醒来检查是否应该终止。

这个想法的第一个实现是显而易见的:使用Python标准库的线程.条件.等待暂停一下。另一个线程通过向条件变量发送信号提前唤醒执行器。

拓扑不能用信号通知条件变量来通知执行器终止,因为这样会有在垃圾回收器中出现死锁的风险:析构函数或weakref回调都不能使用锁来通知条件变量(请参见 PYTHON-863 );因此,对于濒死对象来说,终止周期执行器的唯一方法是设置其“stopped”标志,并让执行器在下次唤醒时看到该标志。

我们在快速清理方面犯了错误,并将检查间隔设置为100ms,我们认为在现代机器上,检查一个标志并每秒返回睡眠10次是很便宜的。

从Python3.2开始锁定.获取接受一个timeout参数,因此python3.2+条件变量只需调用锁定.获取;它们的实施与预期一样有效。

但是在python2中,锁定.获取没有超时。为了等待超时,Python2条件变量休眠一毫秒,尝试获取锁,休眠两倍,然后重试。这种指数衰减达到最大睡眠时间50ms。

如果PyMongo在短时间内调用条件变量的“wait”方法,则会频繁地重新启动指数回退。总的来说,条件变量不是每秒唤醒几次,而是几百次。(参见 PYTHON-983

因此,当前周期执行器的设计出奇地简单:它们执行一个简单的 time.sleep 在半秒钟内,检查是该起床还是该终止,然后再睡觉。