Index: Doc/c-api/init.rst =================================================================== --- Doc/c-api/init.rst (revision 68340) +++ Doc/c-api/init.rst (working copy) @@ -780,6 +780,43 @@ .. versionadded:: 2.3 + +Asynchronous Notifications +========================== + +A mechanism is provided to make asynchronous notifications to the the main +interpreter thread. These notifications take the form of a function +pointer and a void argument. + +.. index:: single: setcheckinterval() (in module sys) + +Every check interval, when the interpreter lock is released and reacquired, +python will also call any such provided functions. This can be used for +example by asynchronous IO handlers. The notification can be scheduled +from a worker thread and the actuall call than made at the earliest +convenience by the main thread where it has possession of the global +interpreter lock and can perform any Python API calls. + +.. cfunction:: void Py_AddPendingCall( int (*func)(void *), void *arg) ) + + .. index:: single: Py_AddPendingCall() + + Post a notification to the Python main thread. If successful, + \*:attr`func` will be called with the argument :attr:`arg` at the earliest + convenience. \*:attr:`func` must can use the Python API and can take any + action such as setting object attributes to signal IO completion. + It must return 0 on success, or -1 signalling an exception. + + This function returns 0 on success in which case the notification has been + scheduled. Otherwise, for example if the notification buffer is full, + it returns -1 without setting any exception. + + This function can be called on any thread. + + .. versionadded:: 2.7 + + + .. _profiling: Profiling and Tracing Index: Lib/test/test_capi.py =================================================================== --- Lib/test/test_capi.py (revision 68340) +++ Lib/test/test_capi.py (working copy) @@ -35,6 +35,49 @@ raise test_support.TestFailed, \ "Couldn't find main thread correctly in the list" + def TestPendingCalls_Submit(l, n): + def callback(): + l[0] += 1 + + _testcapi._pending_threadfunc(callback, n); + + def TestPendingCalls_Wait(l, n): + #now, stick around until l[0] has grown to 10 + count = 0; + while l[0] != n: + #this busy loop is where we expect to be interrupted to + #run our callbacks. Note that callbacks are only run on the + #main thread + if test_support.verbose: + print "(%i)"%(l[0],), + for i in xrange(1000): + a = i*i + count += 1 + if count > 10000: + raise test_support.TestFailed, \ + "timeout waiting for %i callbacks, got %i"%(n, count) + if test_support.verbose: + print "(%i)"%(l[0],) + + def TestPendingCalls(): + if test_support.verbose: + print "pending-calls" + l = [0] + n = 5 + import threading + t = threading.Thread(target=TestPendingCalls_Submit, args = (l, n)) + t.start() + TestPendingCalls_Wait(l, n) + t.join() + + #again, just using the main thread, likely they will all be dispathced at + #once. We must be careful not to ask for too many. The queue can handle + #at most 32 and since we are on a single thread, we would wait forever + #for the queue to become free + l[0] = 0 + TestPendingCalls_Submit(l, n) + TestPendingCalls_Wait(l, n) + try: _testcapi._test_thread_state have_thread_state = True Index: Modules/_testcapimodule.c =================================================================== --- Modules/_testcapimodule.c (revision 68340) +++ Modules/_testcapimodule.c (working copy) @@ -837,6 +837,50 @@ return NULL; Py_RETURN_NONE; } + +/* test Py_AddPendingCalls using threads */ +static int _pending_callback(void *arg) +{ + /* we assume the argument is callable object to which we own a reference */ + PyObject *callable = (PyObject *)arg; + PyObject *r = PyObject_CallObject(callable, NULL); + Py_DECREF(callable); + Py_DECREF(r); + return r != NULL ? 0 : -1; +} + +/* The following requests n callbacks to _pending_callback. It can be + * run from any python thread. + */ +PyObject *pending_threadfunc(PyObject *self, PyObject *arg) +{ + PyObject *callable; + int n; + int i, j, k; + if (PyArg_ParseTuple(arg, "Oi", &callable, &n) == 0) + return NULL; + + /* create references upfront, while we hold the lock */ + for(i = 0; i