Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Execute Twisted reactor.run() in a thread?

I'm trying to make a Python library to use the Ubutu One API from Objective-C. Here is my source code: https://github.com/JoseExposito/U1-Finder-Plugin/blob/master/U1FinderLib/U1FinderLib.py

I need to make many calls to the API, for this reason I need to have my reactor running once instead of run it and stop it, like in the example of the Ubuntu One documentation: https://one.ubuntu.com/developer/files/store_files/syncdaemontool

Because it is not possible to run twice the reactor... And I need to execute reactor.run() in a thread because I can not block the application that uses that library!

It is possible to do that? I'm not able to run the reactor in a thread and call the Ubuntu One API synchronous.

EDIT:

I'm using this simple source code to test the idea:

#!/usr/bin/env python
import objc
import thread
import os
import time
from   twisted.internet import reactor, defer
from   ubuntuone.platform.tools import (SyncDaemonTool, is_already_running)
from   threading import Thread
NSObject = objc.lookUpClass('NSObject')

##
# Variable to get the result of the calls to the Sync Daemon.
# The result is a JSON string stored in returned_value[0].
returned_value = ['']

##
# Objective-C facade to the methods of the U1FinderLib.
class U1FinderLib(NSObject):

    def init(self):
        self = super(U1FinderLib, self).init()
        self.sync_daemon_tool = SyncDaemonTool(None)
        Thread(target=reactor.run, args=(False,)).start()
        return self

    @objc.typedSelector('@@:')
    def volumeList(self):
        print "Begin volumeList"
        reactor.callLater(0, run_command, "volume_list", [], self.sync_daemon_tool)
        print "End volumeList"
        return returned_value[0]

##
# Auxiliar functions to call to the sync daemon.
@defer.inlineCallbacks
def run_command(action, params, sync_daemon_tool):
    print "run_command"
    running = yield is_already_running()
    print "After is_already_running"
    try:
        if not running:
            returned_value[0] = '{ type:"error" reason:"Sync Daemon is not running" }'
        else:
            print "Before run_action"
            yield run_action(action, params, sync_daemon_tool)
            print "After run_action"
    except Exception, e:
        returned_value[0] = '{ type:"error" reason:"Exception: %s" }' % e

@defer.inlineCallbacks
def run_action(action, params, sync_daemon_tool):
    if action == "volume_list":
        d = sync_daemon_tool.get_folders()
        returned_value[0] = yield d.addCallback(lambda r: volume_list(r))

# Volume List
def volume_list(folders):
    volumes_json = '{ type:"volume_list" volumes: { \n\t{ volume:"' + os.path.expanduser('~/Ubuntu One') + '" subscribed:"YES" }'
    for folder in folders:
        volumes_json += ',\n\t{ volume:"' + folder['path'] + '" subscribed:"' + ('YES' if bool(folder['subscribed']) else 'NO') + '" }'
    volumes_json += '\n} }'
    return volumes_json

if __name__ == '__main__':
    py = U1FinderLib.alloc().init()
    print py.volumeList()
    print "EXIT"

And this is the output of the program:

Begin volumeList
End volumeList

EXIT

The problem is that the "run_command" function is never called

like image 547
user1204395 Avatar asked Jan 11 '13 09:01

user1204395


1 Answers

Rather than running the reactor in a thread, you should use a reactor which integrates with the application you want to use it in.

For example, perhaps you want to use the CoreFoundation reactor (since your application uses Obj-C and has "Finder" in the name).

If you really can't do that (for example, if Ubuntu One requires a different reactor - I don't know if this is the case), then you probably can run that reactor in a thread. Most reactors do support this, although if Ubuntu One requires a particular reactor, that reactor may not support threaded usage.

You didn't actually explain what problem you have when trying to run the reactor in a thread, so I can't help you understand why it that didn't work. However, it should be easy to do so. Just:

from twisted.internet import reactor
from threading import Thread

Thread(target=reactor.run, args=(False,)).start()

Keep in mind that once you choose to run the reactor in a thread, you may only use Twisted APIs in that thread.

If this doesn't work, provide more details about how it doesn't work in your question.

like image 75
Jean-Paul Calderone Avatar answered Nov 10 '22 07:11

Jean-Paul Calderone