Code review comment for lp:~oubiwann/ubuntu-accomplishments-system/946850-twisted-app

Revision history for this message
Duncan McGreggor (oubiwann) wrote :

On Mon, Mar 26, 2012 at 2:01 AM, Jono Bacon <email address hidden> wrote:
> On 22 March 2012 15:22, Duncan McGreggor <email address hidden> wrote:
>>
>>>  * It looks like each of the classes in service.py overload a set of methods in Twisted (startService, stopService etc)
>>
>> It doesn't overload. It simply implements the method defined by the
>> interface. Each service can potentially do lots of difference things
>> in its startService and stopService methods -- all different from each
>> other, since each service is of a different type and has different
>> responsibilities.
>>
>> This would be a good place to do any DBus main loop tweaks, if needed,
>> for instance.
>>
>> (Btw, Python can't really do classic overloading, since a
>> method/function can only be defined once in the same scope. However,
>> you can achieve a mostly similar-seeming result with positional and
>> named args and a dispatch mechanism. You could also override and then
>> call the superclass's method, that also gets you something similar to
>> overloading.)
>
> So it looks like each of the different services are set up and each of
> these are then used throughout the rest of the API (e.g. when we
> run_scripts() this uses the ScriptRunner service). I guess the
> startService/stopService pieces are then used to help them start and
> stop in a twistd way (incorporating logging etc), right?

Yup.

>>> and then applicationFactory in app.py gets everything up and running. Is this right?
>>
>> Almost. applicationFactory creates an application object (with lots of
>> child components) and assigns that to the variable name "application"
>> in the .tac file. twistd loads the .tac file, looks for the
>> "application" variable name, loads the object stored there, and fires
>> of the chain of startService methods on each child object.
>
> I saw mention of a .tac file in the twistd docs, but I didn't see it
> in the code. Is there a file somewhere I should see?

Sorry, I renamed it to not use an extension, since it will be used as
a daemon in a long-running process. It's still a .tac file, though.
(it's in the bin dir)

>> So it's twistd that gets everything up and running. applicationFactory
>> just applies the configuration to various instantiations and then
>> passes the application object back.
>
> Gotcha. Btw, you showed me how to start a twistd service, how do I stop it?

So when run in non-daemonized mode (for development), it's just ^C.

When run in daemon mode, it writes std out to a log file
(configurable; see twistd --help) and the PID of the process to a .pid
file (configurable; see twistd --help). Sys V init scripts typically
generate a file or shell variable themselves then later cat the pid
file when running a kill against it. You'll do the same thing here,
except the pid file has already been created for you.

> Also, do you know if there is a good way to map twistd start/stop to
> upstart start/stop events?

I've never written upstart scripts, but twistd is used in all sorts of
init/rc scripts, similarly to how I outlined above.

Jereb (another core hacker/long-time Twisted guy) did a great write-up
here for plugins:
  https://bitbucket.org/jerub/twisted-plugin-example

In particular, he recommends the following usage for start scripts
with that example (you can pick-and-choose your preferred options):

/usr/bin/twistd --reactor=epoll --nodaemon \
                --syslog --prefix=example \
                --pidfile=/var/run/example.pid \
                --uid=nobody --gid=nobody \
                example --strport tcp:81

>>>  * In the notes you say "all blocking calls that are going to be made in functions/methods that return deferreds should either be converted to async code that utilizes Twisted to accomplish the same thing, or be run in a thread with deferToThread" - I thought that by using inline callbacks (as my code does already) this was doing this the Twisted way and not blocking?
>>
>> Nope. This is one of the many pitfalls of using inlineCallbacks.
>>
>> inlineCallbacks is basically a wrapper for deferreds. It doesn't
>> magically make code non-blocking any more than using deferreds
>> directly does. There's a famous saying in the Twisted community:
>>  http://www.flickr.com/photos/oubiwann/131113883/
>>
>> Remember:
>>  * all file I/O is blocking
>>  * claims of async filesystem operations are, when you get down to it,
>> pretty lies
>>  * as such, all (file) I/O needs to be analyzed on how it impacts a
>> given program and what gets blocked and for how long
>>
>> This is another reason why you want to break up your long methods into
>> smaller methods, especially if you're using inlineCallbacks (which you
>> shouldn't be!): the logic will be easier to track, gremlins will be
>> easier to see, and you can isolate async-unfriendly code more
>> effectively and deal with it appropriately.
>
> Ahhhh I see. So inlineCallbacks just magically generates the deferreds

Well, it's not magic, but yeah :-) It does some nifty wrapping so that
yields get converted to deferreds. So, under the covers, you're still
using deferreds.

> and then continues processing. I just did some reading while I was
> over here and it looks like I instead need to create a deferred for
> each blocking method, and then specify the callback that processes the
> deferred when it is complete.

Bingo!

> I guess I need to be careful with how I
> do this as with three different services and multiple deferreds
> completing at different times, I am likely to get different types of
> race condition,

Well, the services shouldn't matter, really. All you need to make sure
of is that you're adding your callbacks in the correct order. If you
do that, you won't have any race conditions.

If there are a bunch of things you want to have run in parallel, but
all of them need to complete before the next task is done (whatever
that may be), you can create those deferreds, append them to a regular
Python list, and then instantiate DeferredList with the list of
deferreds you just created. When you use a callback from that
DeferredList instance, that callback will only fire once all the
deferreds in that list have finished (the results will be a little
different, though: a list of tuples).

If you're going to be creating lots of deferreds (on the order of
100s), you need to watch out for running out of file descriptors,
since deferreds are created all at once. If you run into situations
like that, you probably want to refer to this blog post:
  http://oubiwann.blogspot.com/2008/06/async-batching-with-twisted-walkthrough.html

Welcome to Twisted!

« Back to merge proposal