Использование сельдерея в качестве канала управления для витых приложений

Я пытаюсь использовать сельдерей в качестве канала управления для витой программы. My Twisted application - это уровень абстракции, который обеспечивает стандартный интерфейс для различных локально запущенных процессов (через ProcessProtocol). Я бы хотел использовать Celery для управления этим дистанционно - AMQP кажется идеальным методом управления многими Twisted приложениями из центрального местоположения, и я хотел бы воспользоваться функциями на основе задач Celery, например. задачи, подзадачи и т.д.

Это не работает так, как я планировал, и я надеюсь, что кто-то может помочь мне указать мне в правильном направлении, чтобы это работало.

Поведение, которое я пытаюсь достичь, когда я запускаю свой script, это:

  • Начните слегка модифицированный сельдерей (см. ниже)
  • Ожидание задач Сельдерея
  • Когда получена задача "начать процесс", создайте ProcessProtocol
  • Когда выполняются другие задачи, запустите функцию в Twisted-протоколе и верните результат, используя команду Отложенные

"слегка модифицированный сельдерей" celeryd с небольшой модификацией, которая позволяет задачам обращаться к реактору Twisted через self.app.twisted, и порожденный процесс через self.app.process. Чтобы все было в порядке, я использую интегрирование Cycle "solo" для пула процессов, которое не разворачивает новый процесс для рабочих.

Моя проблема возникает, когда я пытаюсь использовать задачу Celery для инициализации ProcessProtocol (т.е. запускающего внешний процесс). Процесс запускается правильно, но дочерний процесс ProcessProtocol childDataReceived никогда не вызывается. Я думаю, что это связано с тем, что дескрипторы файлов не наследуются/устанавливаются правильно.

Ниже приведен пример кода примера, основанного на примере "wc" в документации ProcessProtocol. Он включает в себя две задачи Celery - один для запуска процесса wc, а другой - подсчет слов в некотором тексте (с использованием ранее начатого процесса wc).

Этот пример довольно надуман, но если я смогу выполнить эту работу, он послужит хорошей отправной точкой для реализации моих ProcessProtocols, которые являются длительными процессами, которые будут реагировать на команды, написанные на stdin.

Я тестирую это, сначала запуская демона Celery:

python2.6 mycelery.py -l info -P solo

Затем в другом окне выполняется script, который отправляет две задачи:

python2.6 command_test.py

Ожидаемое поведение command_test.py выполняется для двух команд: один запускает процесс wc, а другой отправляет некоторый текст в CountWordsTask. Что на самом деле происходит:

  • StartProcTask запускает процесс и получает "процесс запущен" в качестве ответа через Deffered
  • CountWordsTask никогда не получает результат, потому что childDataReceived никогда не вызывается

Может кто-нибудь пролить свет на это или предложить некоторые советы о том, как лучше использовать Сельдерей в качестве канала управления для Twisted ProcessProtocols?

Было бы лучше написать реалистичную версию ProcessPool для Celery? Является ли мой метод вызова WorkerCommand.execute_from_commandline через reactor.callLater правильным подходом для обеспечения того, чтобы все произошло в Twisted thread?

Я читал об AMPoule, который, я думаю, мог бы предоставить некоторые из этих функций, но хотел бы придерживаться Сельдерея, если это возможно, поскольку я использую его в других частях моего приложения.

Любая помощь или помощь будут с благодарностью оценены!

myceleryd.py

from functools import partial
from celery.app import App
from celery.bin.celeryd import WorkerCommand
from twisted.internet import reactor


class MyCeleryApp(App):
    def __init__(self, twisted, *args, **kwargs):
        self.twisted = twisted
        super(MyCeleryApp, self).__init__(*args, **kwargs)

def main():
    get_my_app = partial(MyCeleryApp, reactor)
    worker = WorkerCommand(get_app=get_my_app)
    reactor.callLater(1, worker.execute_from_commandline)
    reactor.run()

if __name__ == '__main__':
    main()

protocol.py

from twisted.internet import protocol
from twisted.internet.defer import Deferred

class WCProcessProtocol(protocol.ProcessProtocol):

    def __init__(self, text):
        self.text = text
        self._waiting = {} # Dict to contain deferreds, keyed by command name

    def connectionMade(self):
        if 'startup' in self._waiting:
            self._waiting['startup'].callback('process started')

    def outReceived(self, data):
        fieldLength = len(data) / 3
        lines = int(data[:fieldLength])
        words = int(data[fieldLength:fieldLength*2])
        chars = int(data[fieldLength*2:])
        self.transport.loseConnection()
        self.receiveCounts(lines, words, chars)

        if 'countWords' in self._waiting:
            self._waiting['countWords'].callback(words)

    def processExited(self, status):
        print 'exiting'


    def receiveCounts(self, lines, words, chars):
        print >> sys.stderr, 'Received counts from wc.'
        print >> sys.stderr, 'Lines:', lines
        print >> sys.stderr, 'Words:', words
        print >> sys.stderr, 'Characters:', chars

    def countWords(self, text):
        self._waiting['countWords'] = Deferred()
        self.transport.write(text)
        return self._waiting['countWords']

tasks.py

from celery.task import Task
from protocol import WCProcessProtocol
from twisted.internet.defer import Deferred
from twisted.internet import reactor

class StartProcTask(Task):
    def run(self):
        self.app.proc = WCProcessProtocol('testing')
        self.app.proc._waiting['startup'] = Deferred()
        self.app.twisted.spawnProcess(self.app.proc,
                                      'wc',
                                      ['wc'],
                                      usePTY=True)
        return self.app.proc._waiting['startup']

class CountWordsTask(Task):
    def run(self):
        return self.app.proc.countWords('test test')

Ответ 1

Сельдерей, вероятно, блокируется, ожидая новых сообщений из сети. Поскольку вы используете его в одном однопоточном процессе вместе с реактором Twisted, он блокирует работу реактора. Это отключит большую часть Twisted, которая требует, чтобы реактор фактически выполнялся (вы назвали reactor.run, но с блокировкой Celery, он эффективно не работает).

reactor.callLater только задерживает запуск сельдерея. Как только сельдерей начинает, он все еще блокирует реактор.

Проблема, которую необходимо избегать, заключается в блокировании реактора.

Одним из решений было бы запустить сельдерей в одной нити и в реакторе в другом потоке. Используйте reactor.callFromThread для отправки сообщений в Twisted ( "функции вызова в реакторной нити" ) из нити сельдерея. Используйте эквивалент Сельдерея, если вам нужно отправить сообщения обратно на сельдерей из витой нити.

Другим решением было бы реализовать протокол Celery (AMQP? - см. txAMQP) в качестве собственной библиотеки Twisted и использовать ее для обработки Сообщения сельдерея без блокировки.