Twisted Mixing – lvh – _@lvh.io



Twisted Mixing – lvh – _@lvh.io

1 3


TwistedMixing


On Github lvh / TwistedMixing

Twisted Mixing

lvh

_@lvh.io

Introduction

Rackspace

Crypto 101

Twisted

About this talk

Twisted code ↔ other code

  • combining twisted code with not-explicitly-twisted code
  • “Twisted infects your entire codebase”: myth
  • Show that you probably can use Twisted
    • (Not so much that you should use Twisted)
  • Overview

Why Twisted?

“The only reason anyone cares about Twisted is X”

  • IRC, SMTP, DNS, SSH, WebSockets…
  • Running multiple things in one process
  • Lots of simultaneous connections
  • Protocol and transport abstractions
  • Cooperation with existing event
  • Existing event loops: GUIs, gevent

Prerequisites

Python, some Twisted probably helps

  • For this talk I’m going to assume I don’t need to explain Python
  • Some Twisted experience will undoubtedly help, but I will explain basics
  • Show of hands: “how many people know how a reactor works?”
    • It’s really easy: unicorns all the way down
    • except for IOCP which is made of Russian-grade sadness

Introducing Twisted

Reactor

An object that reacts to events

  • Register some events
  • Reactor waits for them to happen
  • Dispatches to subscribers

Internally: event loop, except for test reactors e.g. Clock

IReactor{}

  • Time: callLater…
  • Process: spawnProcess
  • Threads: call(In|From)Thread, …
  • (TCP|UDP|SSL|Multicast)
  • (UNIX|UNIXDatagram|Socket)
  • FDSet: (add|remove)(Reader|Writer), …

Examples of reactor interfaces to give you an idea what a reactor does Usually you call higher-level APIs!

Deferred

An object you get now,

gets you result or failure later

Why?

  • Many operations take time
  • Can’t get a result right now

Blocking API

try:
    result = blocking_read()
except SomeError as e:
    on_failure(e)
else:
    on_result(result)
  • Evaluate or raise some point in the future
  • Thread can’t do anything else until then

Deferred API

d = async_read()
d.addCallbacks(on_result, on_failure)
  • Get an object representing the future result now
  • Get result (or failure) when it’s available
  • Thread is free to do something else

Inline callbacks

try:
    result = yield async_read()
except SomeError as e:
    on_failure(e)
else:
    on_result(result)

Twisted and your app

SOA

Service Oriented Architecture

  • Perhaps your workplace has been infected with SOA!
  • If someone is giggling around you: fun fact: “SOA” means “STD” in Dutch
  • In programming: “Loosely coupled things that talk to each other”
  • Written in Gevent? Twisted? COBOL? Who cares!?
  • Work with Twisted, without touching existing code

WSGI

Web Server Gateway Interface

twistd web --wsgi=wsgi.app

Show of hands: how many of you:

  • have a WSGI application?
  • knew Twisted has a production quality WSGI server? (reveal point)

Demo

  • Flask app, served by t.w.wsgi
  • Real-time chat, with txsockjs

Test run, demo

Blocking code in Twisted

Almost everything I’m about to say applies to pretty much any event-driven single-threaded thing

Can’t block reactor

Production reactors are just event loops Twisted is single-threaded by default One thing at a time, all in the same thread Concurrency through asynchronous IO Blocking the reactor thread means nothing else happens

Blocking IO

def _getDataAtURL(url):
    return requests.get(url) # BLOCKS!

Blocking computation

def _compute(n):
    x = 2
    for _ in xrange(n): # BLOCKS!
        x *= x
    send_somewhere(x)

Can’t block reactor

Alternatives:

Don’t block Block another thread

Don’t block

IO bound? Asynchronous IO!

CPU bound? Cooperate!

Asynchronous I/O!

treq: requests-like, but asynchronous

def _getDataAtURL(url):
    return treq.get(url)

Cooperation!

t.internet.task.coiterate & friends

def _compute(n):
    x = 2
    for _ in xrange(n):
        x *= x
        yield # Yields to the reactor :)
    send_somewhere(x)

coiterate(_compute(n))

Don’t block?

Avoiding blocking isn’t always possible

  • Blocking API: DBAPI2, WSGI…
  • Opaque code: scrypt, Pillow…
  • Kernel/syscall level: file IO, …

Sometimes all of the above!

Block somewhere else

Can’t block reactor thread

→ block a different one!

  • … in the same process: deferToThread
  • … in a child process: spawnProcess…
  • … in a remote process: Ampoule, RPC…

deferToThread is used by a lot of wrappers: adbapi, txscrypt

deferToThread

  • Easy automagic deferreds!
  • Shared mutable state :-(
  • Many projects full of shared mutable state; Python module system!
  • Consequence of threads, not deferToThread

Twisted in blocking code

itamarst/crochet

setup()

  • Spawns a thread, runs the reactor in it
  • Idempotent

@run_in_reactor

  • Run in reactor thread
  • Return EventualResult

EventualResult?

  • Synchronous analog of Deferred
  • wait(timeout=None)

Example #1

from twisted.web.client import getPage
from crochet import setup, run_in_reactor
setup()

@run_in_reactor
def download_page(url):
    return getPage(url)

url = "http://tm.tl/5000"
result = download_page(url)
print result.wait()

Example #2

  • Twisted queries exchange rate every 30s
  • Flask app serves the latest exchange rate

Demo

Twisted part

class ExchangeRate(object):
    # ...

    @run_in_reactor
    def start(self):
        self._lc = LoopingCall(self._download)
        self._lc.start(30, now=True)

    def _download(self):
        d = getPage(url)
        # ...

most twisted apis can only be called from the reactor thread _start method in reactor thread because of @runinreactor decorator _download method in reactor thread because of LoopingCall

important part here: Twisted code looks like regular Twisted code! (But remember the @run_in_reactor)

Flask part

@app.route('/')
def index():
    rate = EURUSD.latest_value()
    if rate is None:
        rate = "unavailable"
    return "EUR/USD rate: {0}.".format(rate)

app.run()

Flask code looks like regular Flask code!

index() will be called in whatever thread the wsgi server runs it

Twisted in gevent

jyio/geventreactor

install()

“Blocking” code

gevent-style automagic suspending

  • Actually blocking the thread: bad (like in gevent)
  • Suspending the reactor greenlet: bad
  • Earlier requests example: probably okay

Deferreds ↔ greenlets

r = waitForDeferred(d)

d = waitForGreenlet(g)

  • Bidirectional bridge
  • waitForDeferred = “wait” for deferred (suspend greenlet)
  • waitForGreenlet = produce deferred from greenlet

Demo

TODO

Recap

Twisted plays well with others

  • Many supported protocols
  • Cooperates with blocking code
  • Cooperates with other event loops

Conclusion

If you want to use Twisted, you probably can.

That doesn’t necessarily mean it’s a good idea.

Although it obviously is ;-)

Questions?

0