GAE Python:由于后端被停止,进程终止

时间:2015-02-26 19:12:16

标签: python google-app-engine

我在我的应用程序中创建了一个单独的模块,通过手动缩放和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函数可以被窃听吗?

**为了便于阅读,省略了一些代码

1 个答案:

答案 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