искривленный вызов клиента из библиотечной функции

Я пытаюсь реализовать функцию, которая будет действовать как клиент Twisted. Он вызывается из кода, который я не могу контролировать. Я попробовал что-то вроде (это взято из кода примера pbsimpleclient.py):

# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.


from twisted.spread import pb
from twisted.internet import reactor
from twisted.python import util

def remcall(**kw):
    factory = pb.PBClientFactory()
    reactor.connectTCP("localhost", 8789, factory)
    d = factory.getRootObject()
    # kw here is what's passed in via remcall
    d.addCallback(lambda object: object.callRemote("echo", kw))
    d.addCallback(lambda echo: 'server echoed: '+repr(echo))
    d.addErrback(lambda reason: 'error: '+str(reason.value))
    d.addCallback(util.println)
    d.addCallback(lambda _: reactor.stop())
    reactor.run()

И вызывающий абонент будет звонить так:

remcall(hello=1, world=2)
remcall(hi=3, there=4)

Но, как вы уже догадались, выдает ошибку "twisted.internet.error.ReactorNotRestartable".

Как лучше всего это сделать? Я не так беспокоюсь о получении ответа от удаленного конца, но я должен знать, если он не работает и почему.


person mlv    schedule 01.07.2016    source источник
comment
Вы не запускаете/останавливаете, реактор в работе. Вы запускаете его в конце своей основной функции и останавливаете, когда хотите выйти из программы. Один раз.   -  person Klaus D.    schedule 02.07.2016
comment
Но есть ли способ запустить его (возможно, в потоке) и позволить основному потоку вернуться к вызывающей стороне? Или это то, что нельзя сделать с Twisted?   -  person mlv    schedule 02.07.2016


Ответы (2)


Удалите reactor.run() из функции remcall и добавьте его в конец. Также удалите d.addCallback(lambda _: reactor.stop())

def remcall(**kw):
    factory = pb.PBClientFactory()
    reactor.connectTCP("localhost", 8789, factory)
    d = factory.getRootObject()
    # kw here is what's passed in via remcall
    d.addCallback(lambda object: object.callRemote("echo", kw))
    d.addCallback(lambda echo: 'server echoed: '+repr(echo))
    d.addErrback(lambda reason: 'error: '+str(reason.value))
    d.addCallback(util.println)


remcall(hello=1, world=2)
remcall(hi=3, there=3)
reactor.run()    # this should be the last thing to run

Реактор можно запустить только один раз. reactor.stop() выполняется, и она не должна выполняться, если только вашему приложению не нужно полностью прекратить работу. Вот почему вы получаете исключение ReactorNotRestartable.

person notorious.no    schedule 02.07.2016
comment
Вы неправильно поняли вопрос. Другой код, который я не могу контролировать, будет вызывать remcall много раз, и каждый раз необходимо устанавливать соединение с удаленной стороной. - person mlv; 02.07.2016
comment
Ответ показывает несколько вызовов remcall, как вам кажется. Пусть вас не смущает тот факт, что в этом примере оба вызова происходят до вызова реактора.run(). Они бы работали так же хорошо, если бы были сделаны после реактора.run(). Однако то, что вы не можете сделать, это реактор.run(); remcall(...), потому что реактор.run() не возвращается до тех пор, пока реактор не остановится. Вместо этого вам нужен другой поток, чтобы сделать что-то вроде реактора.callFromThread(lambda: remcall(...)) или поместить remcall(...) в обработчик событий Twisted. - person Jean-Paul Calderone; 02.07.2016
comment
Итак, @mlv, вы говорите, что код, над которым у вас нет контроля, будет импортировать remcall и выполнять его? Другой код написан с использованием Twisted? Если это не так, вам придется запустить реактор и функцию в потоке, и самый простой способ сделать это — использовать crochet github.com/itamarst/crochet. Извините за недоразумение, надеюсь, теперь понял -__- - person notorious.no; 02.07.2016
comment
Да, @notorious, крючком именно то, что мне было нужно. Спасибо! - person mlv; 04.07.2016

Ответ заключается в использовании вязания крючком.

# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.

## Add these two lines
from crochet import setup, wait_for
setup()

from twisted.spread import pb
from twisted.internet import reactor
from twisted.python import util

## Add a wait_for decorator
@wait_for(timeout=5.0)
def remcall(**kw):
    factory = pb.PBClientFactory()
    reactor.connectTCP("localhost", 8789, factory)
    d = factory.getRootObject()
    # kw here is what's passed in via remcall
    d.addCallback(lambda object: object.callRemote("echo", kw))
    d.addCallback(lambda echo: 'server echoed: '+repr(echo))
    d.addErrback(lambda reason: 'error: '+str(reason.value))
    d.addCallback(util.println)
## Get rid of the reactor calls, and return d
#    d.addCallback(lambda _: reactor.stop())
#    reactor.run()
    return d

Затем вызывающий абонент просто звонит

remcall(hello=1, world=2)
remcall(hi=3, there=4)

и crochet @wait_for обрабатывает запуск remcall внутри потока реактора.

person mlv    schedule 04.07.2016