#summary The µThreads API
#labels Featured

= Introduction =

µThreads implements the StandardMicrothreadingPattern within [http://twistedmatrix.com/ Twisted Python].  You should be familiar with both before reading this document.

<wiki:toc max_depth="3" />

= General Guidelines =

Any application written with `uthreads` is likely to mix normal twisted python with microthreaded python.  Authors will avoid much confusion by keeping clear about which style applies at each line of code.  The `@uthreaded` decorator (explained below) is helpful as a visual reminder that the decorated function is microthreaded.

= µThreads =
The `uthreads.uThread` class is designed to act like Python's `threading.  Its constructor takes keyword arguments `target`, `args`, and `kwargs`, and `name`, just like `Threads`'s constructor.  Note that the `group` argument is not supported.  A thread object `uth` has methods
 * `uth.getName`
 * `uth.setName`
 * `uth.isAlive`
that do exactly what you expect.  The `start` method begins execution of the thread, but does not return anything.  The `join` method blocks until the thread exits, and either returns its final value or raises an exception that was not caught in the thread (note: this might change - see issue 2).  Since it might block, `join` should be yielded:
{{{
	yield worker_thd.join()
}}}

Like `threading.Thread`, `uthreads.uThread` has a `run` method which can be overridden by subclasses, as an alternative to specifying a callable to the constructor.  Also parallel to `threading`, `uthreads,current_thread()` will return the current `uThread`.

From twisted code, `uthreads.run` provides a convenient way to spawn a new microthread.  It takes a callable and arguments (much like `reactor.callLater`), and returns a deferred that will callback when the thread finishes.  For example:
{{{
from uthreads import run

	def remote_crawl(self, url):
		return run(follow_links, url, 3)
}}}

Note that `run` creates a new `uThreads` object, but hides it from the caller.  Conversely, `spawn` takes a generator, embeds it in a `uThread`, starts the thread, and returns it:
{{{
from uthreads import spawn
	worker_thread = spawn(follow_links(url, 3))
}}}
 The semantics of these two utility functions may change in an upcoming release - see issue 3

= Utilities =
== Time ==
The ubiquitous `sleep` method does exactly what you would expect:
{{{
from uthreads import sleep
def countdown(n):
	while n>0:
		print n
		yield sleep(1)
		n = n - 1
}}}

A more advanced timer is available in `uthreads.timer`.  The `Timer` class can be set with a generator to call after a delay, and subsequently cancelled.  This is helpful for implementing timeouts:
{{{
from uthreads import timer
	t = timer.Timer()
	t.set(10, abort())
	yield long_runing_calculation()
	t.clear()
}}}
 This class may be modified to take a callable (so `t.set(10, abort)` instead of `t.set(10, abort())`) - see issue 4

== Synchronization Primitives ==
Python's `threading` provides access to a number of familiar primitives.  So does `uthreads.sync`.

=== Lock ===
A microthreaded lock acts just like you expect:
{{{
from uthreads import sync

module_lock = sync.Lock()

def operation():
	# ...
	# enter critical section
	yield module_lock.acquire()
	# ...
	# leave critical section
	yield module_lock.release()
}}}

=== Queue ===
A `uthreads.sync.Queue` acts much like a `Queue.Queue`.  It can have a maximum size, specified as an optional argument to the constructor.  Its `get` and `put` methods are microthreaded, and will block until a value (for `get) or empty slot (for `put`) is available.  No-wait variants `get_nowait` and `put_nowait` raise `Empty` and `Full` exceptions where their counterparts would block.  The `qsize` method returns the number of elements currently in the queue.  Empty and full conditions are identified by the `empty` and `full` methods.

= Interacting with Twisted =
A µthreaded function may interact with Twisted in two ways: it may call functions which return `Deferred` objects, on which it wishes to block; and its caller may expect it to return a `Deferred` object.

In the first, case, a µthreaded function may simply yield such a `Deferred` object to block on it.  When the `Deferred` is called back, the yield expression will return the `Deferred` object's result.  If an error occurs, an exception will be raised, just as you would expect.  Note that this particular trick avoids the need to use `maybeDeferred`, since yielding a normal Python object just returns that object (unless it is a generator).

{{{
from twisted.web.client import getPage
def follow_links(url, depth):
    if depth == 0: return
    page = yield getPage(url)
    if not yield have_page(url, page):
        for link in extract_links(page):
            yield follow_links(link, depth-1)
}}}

In the second, case a µthreaded function should return a `Deferred` object.  This is accomplished by decorating the function with `uthreads.returns_deferred`:
{{{
from uthreads import returns_deferred
@returns_deferred
def authenticate_client(username, password):
    if not username or not password:
        raise StopIteration(False)
    raise StopIteration(yield auth_server.callRemote("check_creds", username, password)):
}}}



= Bug-prevention =
One of the easiest mistakes to make when writing µthreaded programs is to forget a `yield`:
{{{
    if not have_page(url, page): # WRONG
        # ...
}}}
The effect of this error is as if `have_page` had never been called -- no traceback is generated, and in this case it will appear to `follow_links` that `have_page` always returns true.  The problem is that `have_page(..)` returns a generator object without executing any code.  Rather than return this generator object to the µthreads scheduler, `follow_links` only tests for its truthiness, so none of the code in `have_page` is ever executed.

Detecting this is made a bit easier by decorating every µthreaded function with `@uthreaded`:
{{{
from uthreads import uthreaded
@uthreaded
def have_page(url, page):
    # ...
}}}

This decorator has no effect on the operation of the function (and in fact does nothing when compiled with -O), but adds a wrapper that will detect this "lost" generator and produce an exception:
{{{
File "/home/dustin/devel/projects/uthreads/t/trunk/uthreads/core.py", line 325, in _generator_seen
    % (last_obj_ref(), last_fn))
exceptions.RuntimeError: None, result of <function have_page at 0x6caaa0>, was not yielded to the scheduler
}}}
this feature depends on some Python trickery, and is under development.  Note that the traceback does not indicate the location of the missing `yield`, but at least hints as to which function's generator was discarded.