0

I am exploring Klein and Deferred. In the following example I am trying to increment a number using a child process and return it via Future. I am able to receive the Future call back.

The problem is that deferred object never calls the cb() function and the request made to endpoint never returns. Please help me identify the problem.

Following is my server.py code

from klein import Klein
from twisted.internet.defer import inlineCallbacks, returnValue
import Process4

if __name__ == '__main__':
    app = Klein()

    @app.route('/visit')
    @inlineCallbacks
    def get_num_visit(request):        
        try:
            resp = yield Process4.get_visitor_num()
            req.setResponseCode(200)
            returnValue('Visited = {}'.format(resp))
        except Exception as e:
            req.setResponseCode(500)
            returnValue('error {}'.format(e))

    print('starting server')
    app.run('0.0.0.0', 5005)

Following is Process4.py code

from multiprocessing import Process
from concurrent.futures import Future
from time import sleep
from twisted.internet.defer import Deferred

def foo(x):
    result = x+1
    sleep(3)
    return result


class MyProcess(Process):

    def __init__(self, target, args):
        super().__init__()
        self.target = target
        self.args = args
        self.f = Future()
        self.visit = 0

    def run(self):
        r = foo(self.visit)
        self.f.set_result(result=r)

def cb(result):
    print('visitor number {}'.format(result))
    return result

def eb(err):
    print('error occurred {}'.format(err))
    return err


def future_to_deferred(future):
    d = Deferred()

    def callback(f):
        e = f.exception()
        if e:
            d.errback(e)
        else:
            d.callback(f.result())

    future.add_done_callback(callback)
    return d

def get_visitor_num():
    p1 = MyProcess(target=foo, args=None)
    d = future_to_deferred(p1.f)
    p1.start()
    d.addCallback(cb)
    d.addErrback(eb)
    sleep(1)
    return d

Edit 1

Adding callbacks before starting the process p1 solves the problem of calling cb() function. But still the http request made to the endpoint does not return.

Majeed Khan
  • 505
  • 7
  • 16
  • Twisted and the stdlib multiprocessing module are a poor fit. Consider Ampoule instead. See https://stackoverflow.com/questions/5715217/mix-python-twisted-with-multiprocessing and https://stackoverflow.com/questions/1470850/twisted-network-client-with-multiprocessing-workers and other similar questions on SO. – Jean-Paul Calderone Dec 19 '17 at 13:28
  • I'm sure `reactor.callFromThread` has to be called so that results are set in the main thread. Take a look at [this answer I gave a while back](https://stackoverflow.com/questions/45930518/how-to-make-twisted-defer-get-function-result/45969032#45969032) and see if it makes sense. You should be able to apply something similar. – notorious.no Dec 19 '17 at 20:21
  • Thanks for reply. Please have a look at my answer below. @notorious.no, Jean-Paul Calderone – Majeed Khan Dec 24 '17 at 14:33

1 Answers1

0

It turns out that setting future result self.f.set_result(result=r) in the run() method triggers the callback() method in the child process, where no thread is waiting for the result to be returned!

So to get the callback() function triggered in the MainProcess I had to get the result from the child-process using a multiprocess Queue using a worker thread in the MainProcess and then set the future result.

@notorious.no Thanks for reply. One thing which I noticed is that reactor.callFromThread does switches result from worker thread to MainThread in my modified code however d.callback(f.result()) works just fine but returns result from worker thread.

Following is the modified working code

server.py

from klein import Klein
from twisted.internet.defer import inlineCallbacks, returnValue


import Process4

if __name__ == '__main__':
    app = Klein()
    visit_count = 0

    @app.route('/visit')
    @inlineCallbacks
    def get_num_visit(req):
        global visit_count
        try:
            resp = yield Process4.get_visitor_num(visit_count)
            req.setResponseCode(200)
            visit_count = resp
            returnValue('Visited = {}'.format(resp))
        except Exception as e:
            req.setResponseCode(500)
            returnValue('error {}'.format(e))

    print('starting server')
    app.run('0.0.0.0', 5005)

Process4.py

from multiprocessing import Process, Queue
from concurrent.futures import Future
from time import sleep
from twisted.internet.defer import Deferred
import threading
from twisted.internet import reactor


def foo(x, q):
    result = x+1
    sleep(3)
    print('setting result, {}'.format(result))
    q.put(result)


class MyProcess(Process):

    def __init__(self, target, args):
        super().__init__()
        self.target = target
        self.args = args
        self.visit = 0

    def run(self):
        self.target(*self.args)


def future_to_deferred(future):
    d = Deferred()

    def callback(f):
        e = f.exception()
        print('inside callback {}'.format(threading.current_thread().name))
        if e:
            print('calling errback')
            d.errback(e)
            # reactor.callFromThread(d.errback, e)
        else:
            print('calling callback with result {}'.format(f.result()))
            # d.callback(f.result())
            reactor.callFromThread(d.callback, f.result())
    future.add_done_callback(callback)
    return d


def wait(q,f):
    r = q.get(block=True)
    f.set_result(r)


def get_visitor_num(x):

    def cb(result):
        print('inside cb visitor number {} {}'.format(result, threading.current_thread().name))
        return result

    def eb(err):
        print('inside eb error occurred {}'.format(err))
        return err

    f = Future()
    q = Queue()
    p1 = MyProcess(target=foo, args=(x,q,))

    wait_thread = threading.Thread(target=wait, args=(q,f,))
    wait_thread.start()

    defr = future_to_deferred(f)
    defr.addCallback(cb)
    defr.addErrback(eb)
    p1.start()
    print('returning deferred')
    return defr
Majeed Khan
  • 505
  • 7
  • 16