我在我的应用程序中创建了一个单独的模块,通过手动缩放和1个实例,循环遍历Pull队列,租用并完成一个任务。为了实现这一点,我有一个RequestHandler映射到' / _ ah / start'注册一个关闭钩子并启动后台线程。后台线程遍历队列,租用任务,并使用它来解析CSV文件。到目前为止,一切正常,除了关闭钩子。我认为它不会运行。这是我的代码:
class FileParserWrapper(object):
def __init__(self):
version = os.getenv('CURRENT_VERSION_ID').split('.')
if version[0] == 'test':
self.instance = 'development'
else:
self.instance = 'production'
if (os.getenv('SERVER_SOFTWARE') and
os.getenv('SERVER_SOFTWARE').startswith('Google App Engine/')):
if self.instance == 'development':
self.staging_db = MySQLdb.connect(unix_socket='/cloudsql/' + _INSTANCE_NAME, db='xxxxxx', user='xxxxxx')
else:
self.staging_db = MySQLdb.connect(unix_socket='/cloudsql/' + _INSTANCE_NAME, db=STAGING_DB_NAME, user='xxxxxx')
else:
self.staging_db = MySQLdb.connect(host='xxxxxx', db='xxxxxx', user='xxxxxx')
self.staging_cursor = self.staging_db.cursor()
self.current_task = None
self.current_state = 'inactive'
self.current_task_complete = False
self.last_task_complete = False
self.retries = 0
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
logging.info('FileParserWrapper.__exit__()')
self.staging_cursor.close()
if self.staging_db.open:
self.staging_db.close()
def shutdown(self):
logging.info('FileParserWrapper.shutdown()')
apiproxy_stub_map.apiproxy.CancelApiCalls()
# save_state()
self.__exit__(None, None, None)
# May want to raise an exception
def generate_payload_dict(self, p):
...
def reset_tables(self, upload_id, format):
...
def run_parser(self):
if self.instance == 'development':
q = taskqueue.Queue('test-queue')
else:
q = taskqueue.Queue('ir-upload-queue')
t = q.lease_tasks(3600, 1)
while t:
self.current_state = 'active'
self.current_task = t[0].name
payload = self.generate_payload_dict(t[0].payload)
logging.info('*** Processing task ' + self.current_task + ' ***')
logging.debug(repr(payload))
with FileParser() as fp:
try:
logging.info('Beginning parse...')
result = fp.ParseFile(payload['new_file_name_full'], payload['format'], payload['upload_id'], payload)
if payload['name_file_temp'] != None and result == True:
fp.ParseFile(payload['new_csu_name_file_name_full'], "name", payload['upload_id'], payload)
except:
logging.error('Unknown error occured!')
self.last_task_complete = False
# Release the task lease
q.modify_task_lease(t[0], 0)
self.reset_tables(payload['upload_id'], payload['format'])
raise
else:
self.last_task_complete = True
q.delete_tasks_by_name(self.current_task)
if self.last_task_complete == True:
# Sleep for 5 seconds, then check for new tasks
time.sleep(5)
t = q.lease_tasks(3600, 1)
logging.info('Loop complete, shutting down')
# Shutdown the instance
modules.stop_version()
class BatchProcessorHandler(webapp2.RequestHandler):
def get(self):
proc = FileParserWrapper()
def run(arg):
proc.run_parser()
def shutdown():
logging.info('BatchProcessorHandler().get().shutdown()')
self.response.set_status(200)
proc.shutdown()
# Register shutdown hook
hook = runtime.set_shutdown_hook(shutdown)
# Start background thread
tid = background_thread.start_new_background_thread(run, [True])
application = webapp2.WSGIApplication([
('/_ah/start', BatchProcessorHandler),
], debug=True)
正如您所看到的,我在处理关闭挂钩时应该调用的每个方法中都放置了日志消息,但我从未在日志中看到它们。我要么看到" / _啊/ start"和" / _啊/背景",或者我看到请求" / _啊/ start"," / _ ah / background"," / _ ah /停止&#34 ;.在后一版本中," / _ ah / stop"接收HTTP 500代码,日志中的最后一条消息是"进程终止,因为后端已停止"。此行为似乎是随机发生的,Google上的帖子很少提及此特定错误消息。
为什么会出现这种情况?我在本地梳理了Google App Engine代码,但找不到此消息。该代码还声明不推荐使用Runtime API和后台线程,即使替换Backends的Modules文档具有使用此相同代码的代码示例。 runtime.set_shutdown_hook
函数可以被窃听吗?
**为了便于阅读,省略了一些代码
答案 0 :(得分:1)
所以这不是一个直接的答案,但这是我解决这个问题的方法:
我从手动缩放切换到基本缩放。
一旦我切换到基本缩放,关闭钩子运行完美,但你不能直接调用它。通过基本扩展,服务器自己处理关闭,您只需在模块的YAML文件中设置一个变量(我认为空闲超时)。我将我的设置为5分钟,这适用于我的项目。我不得不转移一些RequestHandler以确保所有内容都被正确调用。这是我目前的代码:
YAML文件
application: xxxxxxxxx
version: 1
runtime: python27
api_version: 1
threadsafe: yes
module: batch-processor
instance_class: B4_1G
basic_scaling:
max_instances: 1
idle_timeout: 5m
Python(删节)
class FileParserWrapper(object):
...
def run_parser(self):
self.current_state = 'active'
if self.instance == 'development':
q = taskqueue.Queue('test-queue')
else:
q = taskqueue.Queue('ir-upload-queue')
# q.lease_tasks will return an empty list if no tasks.
# Empty lists evaluate to False
t = q.lease_tasks(3600, 1)
while t:
self.last_task_complete = self.current_task_complete
self.current_task_complete = False
self.current_task = t[0].name
payload = self.generate_payload_dict(t[0].payload)
logging.info('*** Processing task ' + self.current_task + ' ***')
logging.debug(repr(payload))
with FileParser() as fp:
try:
logging.info('Beginning parse...')
result = fp.ParseFile(payload['new_file_name_full'], payload['format'], payload['upload_id'], payload)
if payload['name_file_temp'] != None and result == True:
fp.ParseFile(payload['new_csu_name_file_name_full'], "name", payload['upload_id'], payload)
except:
logging.error('Unknown error occured!')
self.current_task_complete = False
# Release the task lease
q.modify_task_lease(t[0], 0)
self.reset_tables(payload['upload_id'], payload['format'])
self.shutdown()
raise
else:
self.current_task_complete = True
q.delete_tasks_by_name(self.current_task)
if self.current_task_complete == True:
# Sleep for 5 seconds, then check for new tasks
time.sleep(5)
t = q.lease_tasks(3600, 1)
self.shutdown()
class StartupHandler(webapp2.RequestHandler):
def get(self):
logging.info('Starting instance of module batch-processor')
class BatchProcessorHandler(webapp2.RequestHandler):
def post(self):
logging.info('Initial Memory:')
logging.info('CPU Usage: ' + str(quota.megacycles_to_cpu_seconds(runtime.cpu_usage().total())))
logging.info('Memory Usage: ' + str(runtime.memory_usage().current()))
proc = FileParserWrapper()
def run(arg):
proc.run_parser()
def shutdown():
logging.info('BatchProcessorHandler().get().shutdown()')
self.response.set_status(200)
proc.shutdown()
# Register shutdown hook
hook = runtime.set_shutdown_hook(shutdown)
# Start background thread
tid = background_thread.start_new_background_thread(run, [True])
application = webapp2.WSGIApplication([
('/_ah/start', StartupHandler),
('/_ah/queue/default', BatchProcessorHandler),
], debug=True)
我的大部分代码都是一样的。但是,我所做的是将BatchProcessorHandler
映射到/_ah/queue/default
,映射到默认的任务队列。然后,在我的其他一个脚本中,在将任务添加到Pull队列之后,我创建了一个" dummy"任务,为其提供target
版本/模块对,并将其分配给默认的推送队列。这最终会调用BatchProcessorHandler
。以下是代码的一部分:
...
other code here
...
# Start up a batch-processor instance
logging.info('Starting instance of batch-processor')
try:
the_retry_options = taskqueue.TaskRetryOptions(task_retry_limit=1)
taskqueue.add(target='1.batch-processor', retry_options=the_retry_options)
except modules.UnexpectedStateError:
logging.warning('Instance already started!')
except:
logging.error('Could not start module!')
raise
1.batch-processor
是我正在讨论的版本/模块对。我将一个空任务添加到队列中,其目的基本上是" ping"端点/_ah/queue/default
,启动BatchProcessorHandler
。