从 CherryPy 12 开始,_TimeoutMonitor 被删除。如果您仍然倾向于尝试下面的 hack,请参阅 the pull request 获取其来源。
注意。如果您在正常执行流程中不小心杀死线程或进程,很可能会搞砸事情。如果 Python 代码 except 和 finally 无法执行,您将遇到内存和同步问题(例如死锁)、未关闭的文件句柄和套接字等等。 杀戮是解决问题的方法,就像断头台被称为治疗头皮屑的方法一样。
在 Python 中杀死一个线程
一般你做不到。只是没有这样的 API。在某些情况下您可以找到some hacks,但一般情况下您找不到。终止线程的唯一稳定方法是通过标志、事件等与之合作。
强制停止 CherryPy 进程
当您在拥有 CherryPy Python 进程的终端中按 Ctrl+C 时,解释器会收到 SIGINT 信号并引发 KeyboardInterrupt 异常。然后 CherryPy 命令其工作线程停止并告诉您等待子线程终止...。如果工作线程在用户代码中被阻塞,CherryPy 将等待直到它被释放。
要强制停止,您可以使用常见的kill -9 PID,其中PID 是您的 CherryPy 进程的进程 ID。有时您可以通过任何进程监视器找到它。或者合并cherrypy.process.plugins.PIDFile写一个进程的pidfile。
处理可能无响应的任务
总的来说,CherryPy 是一个线程服务器。如果您的任务通常需要十几秒的响应,那么您很容易用完工作线程。在这种情况下,后台任务队列可能是一个好主意(Celery、Rq)或至少使用cherrypy.process.plugins.BackgroundTask。但它显然会让您重新设计系统、进行临时结果存储、响应轮询或推送。它增加了复杂性,因此应在权衡所有利弊后做出决定。
如果您可以在执行处理或计算的最后限制执行,您最好在那里执行。无论是数据库,还是 Web 服务 API 调用,或者诸如此类,然后只处理超时异常。
CherryPy 开箱即用响应超时
CherryPy 有开箱即用的响应超时功能。它由response.timeout 配置和cherrypy._TimeoutMonitor 控制,cherrypy._TimeoutMonitor 在单独的线程中运行并查看响应是否已超时。虽然实际上监视器只设置了一个属性response.timed_out,后来在cherrypy._cprequest.Request.run 中查看了它,如果它是真的cherrypy.TimeoutError 被提出。所以事后会引发超时异常。如果您的页面处理程序阻塞 30 秒,那么您只会在 30 秒后收到异常。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
import cherrypy
config = {
'global' : {
'server.socket_host' : '127.0.0.1',
'server.socket_port' : 8080,
'server.thread_pool' : 8,
# interval in seconds at which the timeout monitor runs
'engine.timeout_monitor.frequency' : 1
},
'/' : {
# the number of seconds to allow responses to run
'response.timeout' : 2
}
}
class App:
@cherrypy.expose
def index(self):
time.sleep(8)
print('after sleep')
return '<em>Timeout test</em>'
if __name__ == '__main__':
cherrypy.quickstart(App(), '/', config)
强制响应超时
你不能杀死一个线程,但你可以杀死一个进程。如果您无法以任何其他方式控制任务,则可以将执行包装在进程中,并在超时时使用监视器将其终止。下面演示了这个想法。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import signal
import time
from multiprocessing import Process
import cherrypy
config = {
'global' : {
'server.socket_host' : '127.0.0.1',
'server.socket_port' : 8080,
'server.thread_pool' : 8,
# disable internal timeout monitor
'engine.timeout_monitor.on' : False,
# interval in seconds at which the timeout monitor runs
'engine.timeout_kill_monitor.frequency' : 1
},
'/' : {
# the number of seconds to allow responses to run
'response.timeout' : 2
}
}
class TimeoutKillMonitor(cherrypy._TimeoutMonitor):
def run(self):
cherrypy._TimeoutMonitor.run(self)
for request, response in self.servings:
if response.timed_out and hasattr(response, 'jobProcess'):
if response.jobProcess.is_alive():
os.kill(response.jobProcess.pid, signal.SIGKILL)
cherrypy.engine.timeout_kill_monitor = TimeoutKillMonitor(cherrypy.engine)
cherrypy.engine.timeout_kill_monitor.subscribe()
def externalJob():
time.sleep(8)
class App:
@cherrypy.expose
def index(self):
p = Process(target = externalJob)
p.start()
cherrypy.response.jobProcess = p
p.join()
# To determine whether your job process has been killed
# you can use ``killed = response.timed_out``. Reset it to
# ``False`` to avoid ``TimeoutError`` and response a failure
# state in other way.
return '<em>Result</em>'
if __name__ == '__main__':
cherrypy.quickstart(App(), '/', config)
请注意,您不能使用multiprocessing.Queue 或multiprocessing.Pipe 与您的工作进程通信,因为当它被杀死时,访问它们中的任何一个都会锁定您的CherryPy 线程。这是 Python 文档中对 Process.terminate 的引用。
如果在关联进程使用管道或队列时使用此方法,则管道
或者队列很容易被损坏并且可能变得无法被其他进程使用。
同样,如果进程获得了锁或信号量等,则终止它是
容易导致其他进程死锁。
所以是的,强制超时在技术上是可行的,但这是一种不鼓励且容易出错的方式。