带有Docker的Python Luigi-线程/信号问题

时间:2018-07-11 23:40:33

标签: python multithreading signals dockerfile luigi

概述

我们正在使用Luigi在Docker容器内构建管道。这个 是我第一次使用Luigi,并且试图使其运行,但我陷入了Python线程/信号错误。

我们正在构建的东西
我们有一个运行setup.py脚本作为入口点的容器。该脚本导入了我的Luigi任务,但其主要功能是为Google Cloud Services打开PubSub通道。当它在该频道上收到消息时,便开始执行一系列任务。

错误
我直接从Python调用Luigi,尝试以下命令的变体:

luigi.build([GetImageSet()], workers=2, local_scheduler=True, no_lock=True)

并收到此错误:

ValueError: signal only works in main thread


Signal和Luigi的背景

来自Python Signal模块文档:
signal.signal:只能从主线程调用此函数;尝试从其他线程调用它会引发ValueError异常。

来自Luigi worker.py脚本here
Luigi提供了no_install_shutdown_handler标志(默认为false)。 “如果为true,则不会在worker上安装SIGUSR1关闭处理程序”。这也是发生错误的地方(第538行)。该脚本在运行signal.signal()之前检查no_install_shutdown_handler的配置标志是否为(默认)false。到目前为止,我未能让Luigi读取该标志设置为true的client.cfg文件,而可能是Docker造成的。

通过Luigi interface.py脚本here
如果您不想从命令行运行luigi。您可以使用此模块中定义的方法以编程方式运行luigi。在此脚本中,我可以提供一个自定义的工作日程安排工厂,但是我还无法解决这个问题。

本地与全局Luigi调度程序
Luigi提供了两个调度程序选项来运行任务。本地

Dockerfile麻烦:在此容器的Dockerfile中,我正在通过pip安装Luigi,但没有做其他事情。在回顾了thisthis在github上的docker / luigi实现之后,我开始担心我在Dockerfile中做得不够。


我认为错误正在发生的可能原因

  1. pub-sub通道订阅者是无阻塞的,因此我正在做一些可能很糟糕的事情,以防止我们在后台等待消息时退出主线程。这似乎是我的线程问题的根源。
  2. no_install_shutdown_handler标志未成功设置为True,这有望绕过错误,但不一定是我想要的
  3. 本地任务计划程序。我应该使用全局调度程序而不是本地调度程序。无论如何,我最终将不得不将其用于生产……
  4. 从Python而不是命令行运行脚本
  5. 使用luigi.build。相反,我应该使用luigi.run,但是根据Running from Python的文档页面,“如果您想从其他来源(例如数据库)获取一些动态参数,或者在开始之前提供其他逻辑,则构建很有用。任务。”听起来很适合我的用例(从pub-sub通道接收到传递运行第一个任务所需的变量的消息后触发任务)

我还是做错了吗? 如果您对实施上述系统有任何建议,请告诉我。我还将根据请求发布Dockerfile和setup.py尝试。


一些代码示例

这是Dockerfile

# ./Dockerfile
# sfm-base:test is the container with tensorflow & our python sfm-library. It installs Ubuntu, Python, pip etc.
FROM sfm-base:test
LABEL maintainer "---@---.io"

# Install luigi, google-cloud w/ pip in module mode
RUN python -m pip install luigi && \
python -m pip install --upgrade google-cloud

# for now at least, start.sh just calls setup.py and sets google credentials. Ignore that chmod bit if it's bad I don't know.
COPY start.sh /usr/local/bin
RUN chmod -R 755 "/usr/local/bin/start.sh"
ENTRYPOINT [ "start.sh" ]

WORKDIR /src
COPY . .

# this was my attempt at setting the Luigi client.cfg in the container
# all I'm having the config do for now is set [core] no_install_shutdown_handler: true
ENV LUIGI_CONFIG_PATH /src/client.cfg

这是setup.py(针对SO进行了编辑)

# setup.py
from google.cloud import pubsub_v1
from time import sleep
import luigitasks
import luigi
import logging
import json

subscriber = pubsub_v1.SubscriberClient()
subscription_path = subscriber.subscription_path(
'servicename', 'pubsubcommand')

# Example task. These are actually in luigitasks.py
class GetImageSet(luigi.Task):
     uri = luigi.Parameter(default='')

     def requires(self):
          return []

     def output(self):
          # write zip to local
          return

     def run(self):
          # use the URI to retrieve the ImageSet.zip from the bucket
          logging.info('Running getImageSet')

# Pubsub message came in
def onMessageReceived(message):
     print('Received message: {}'.format(message))

     if message.attributes:
          for key in message.attributes:
               if key == 'ImageSetUri':
                    value = message.attributes.get(key)
                    # Kick off the pipeline starting with the GetImageSet Task
                    # I've tried setting no_lock, take_lock, local_scheduler...
                    # General flags to try and prevent the thread issues
                    luigi.build([GetImageSet()], workers=3, local_scheduler=True, no_lock=False)
                    message.ack()

subscriber.subscribe(subscription_path, callback=onMessageReceived)

# The subscriber is non-blocking, so I keep the main thread from
# exiting to allow it to process messages in the background. Is this
# the cause of my woes?
print('Listening for messages on {}'.format(subscription_path))
while True:
    sleep(60)

1 个答案:

答案 0 :(得分:0)

发生这种情况是因为subscriber.subscribe启动了后台线程。当该线程调用luigi.build时,将引发异常。

这里最好的解决方案是使用subscriber.pull从主线程读取pub-sub消息。参见示例in the docs