[Python-checkins] r81458 - in python/branches/py3k-jit: Include/bg_thread.h Include/ceval.h Include/code.h Include/intrcheck.h Include/pystate.h Lib/test/test_sys.py Makefile.pre.in Misc/NEWS Modules/_posixsubprocess.c Modules/posixmodule.c Modules/signalmodule.c Objects/codeobject.c Python/bg_thread.cc Python/ceval.c Python/pystate.c Unittests/BgThreadTest.cc

jeffrey.yasskin python-checkins at python.org
Sat May 22 01:46:06 CEST 2010


Author: jeffrey.yasskin
Date: Sat May 22 01:46:06 2010
New Revision: 81458

Log:
This patch adds a background thread that starts when a function becomes hot,
but only runs dummy jobs in the background. The idea is that this will smoke
out problems with forking, etc. and will make it easier to introduce
compilation later.

The concepts all come from Reid Kleckner's background compilation thread, but
I've changed them around a bit. In particular, the background thread is now
independent of the fact that we plan to do compilation in it; it should work
for other kinds of jobs too.

### 2to3 ###
18.861179 -> 19.893243: 1.0547x slower

### slowpickle ###
Min: 0.052314 -> 0.053090: 1.0148x slower
Avg: 0.053037 -> 0.054663: 1.0306x slower
Significant (t=-2.916043)
Stddev: 0.00226 -> 0.00323: 1.4302x larger
Timeline: b'http://tinyurl.com/32uk99v'

### slowunpickle ###
Min: 0.089470 -> 0.088268: 1.0136x faster
Avg: 0.089903 -> 0.089005: 1.0101x faster
Not significant
Stddev: 0.00214 -> 0.00276: 1.2912x larger
Timeline: b'http://tinyurl.com/2urke56'

I believe the slowdown is due to allocating inside PyCondition::Wait.  I'll
send another patch to fix that.


Added:
   python/branches/py3k-jit/Include/bg_thread.h
   python/branches/py3k-jit/Python/bg_thread.cc
   python/branches/py3k-jit/Unittests/BgThreadTest.cc
Modified:
   python/branches/py3k-jit/Include/ceval.h
   python/branches/py3k-jit/Include/code.h
   python/branches/py3k-jit/Include/intrcheck.h
   python/branches/py3k-jit/Include/pystate.h
   python/branches/py3k-jit/Lib/test/test_sys.py
   python/branches/py3k-jit/Makefile.pre.in
   python/branches/py3k-jit/Misc/NEWS
   python/branches/py3k-jit/Modules/_posixsubprocess.c
   python/branches/py3k-jit/Modules/posixmodule.c
   python/branches/py3k-jit/Modules/signalmodule.c
   python/branches/py3k-jit/Objects/codeobject.c
   python/branches/py3k-jit/Python/ceval.c
   python/branches/py3k-jit/Python/pystate.c

Added: python/branches/py3k-jit/Include/bg_thread.h
==============================================================================
--- (empty file)
+++ python/branches/py3k-jit/Include/bg_thread.h	Sat May 22 01:46:06 2010
@@ -0,0 +1,283 @@
+/* -*- C++ -*- */
+#ifndef Py_BG_THREAD_H
+#define Py_BG_THREAD_H
+
+/* This header defines a class used to run jobs in a background
+   thread, with the GIL unlocked.  The thread is accessible from
+   either C or C++, but it is implemented in C++.  The interface and
+   implementation assume a single background thread (and, of course,
+   one thread at a time on the GIL end). */
+
+typedef struct PyBackgroundThread PyBackgroundThread;
+typedef struct PyBackgroundJob PyBackgroundJob;
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Whether or not we should block until a background job is finished.  */
+typedef enum {
+    Py_NO_BLOCK,
+    Py_BLOCK
+} Py_ShouldBlock;
+
+/* Creates and starts a background thread if none was running and
+   saves it into the PyInterpreterState.  Then hands it a job to
+   run. */
+PyAPI_FUNC(int) PyBackgroundThread_RunJob(
+    PyInterpreterState*, PyBackgroundJob*);
+
+/* Returns true if the background thread has been disabled after
+ * forking.  This function must be false to call the other functions
+ * here. */
+#define PyBackgroundThread_Disabled(thread) ((uintptr_t)(thread) & 1)
+
+/* Simple extern "C" wrappers for the compile thread methods.  */
+PyAPI_FUNC(PyBackgroundThread*) PyBackgroundThread_New(void);
+PyAPI_FUNC(void) PyBackgroundThread_Free(PyBackgroundThread*);
+PyAPI_FUNC(void) PyBackgroundThread_Pause(PyBackgroundThread*);
+PyAPI_FUNC(void) PyBackgroundThread_Unpause(PyBackgroundThread*);
+PyAPI_FUNC(void) PyBackgroundThread_DisableAfterFork(PyInterpreterState*);
+PyAPI_FUNC(int) PyBackgroundThread_ApplyFinishedJobs(
+    PyBackgroundThread*, Py_ShouldBlock);
+
+/* Returns a PyBackgroundJob that does nothing. */
+PyAPI_FUNC(PyBackgroundJob*) PyBackgroundThread_NewDummyJob(void);
+
+#ifdef __cplusplus
+}
+
+#include "pythread.h"
+#include <deque>
+
+// Acquires its lock argument within a scope.
+struct PyLockGuard {
+    PyLockGuard(PyThread_type_lock lock) : lock_(lock)
+    {
+        PyThread_acquire_lock(this->lock_, WAIT_LOCK);
+    }
+    ~PyLockGuard()
+    {
+        PyThread_release_lock(this->lock_);
+    }
+private:
+    const PyThread_type_lock lock_;
+};
+
+// Releases its lock argument within a scope.  The lock must be held when the
+// scope is entered, of course.
+struct PyUnlockGuard {
+    PyUnlockGuard(PyThread_type_lock lock) : lock_(lock)
+    {
+        PyThread_release_lock(this->lock_);
+    }
+    ~PyUnlockGuard()
+    {
+        PyThread_acquire_lock(this->lock_, WAIT_LOCK);
+    }
+private:
+    const PyThread_type_lock lock_;
+};
+
+// Acts roughly like a posix condition variable.  This class takes the lock as a
+// constructor argument and requires that it be held on entry to Wait(),
+// Notify(), and NotifyAll().
+struct PyCondition {
+    PyCondition(PyThread_type_lock lock);
+    // All threads blocked in this->Wait() must have been notified by the time
+    // 'this' is destroyed.
+    ~PyCondition();
+
+    // Blocks until another thread calls Notify or NotifyAll.  The associated
+    // lock must be held on entry and will be released and reacquired.
+    void Wait();
+
+    // Wakes up 'to_notify' threads blocked in this->Wait().  If less than that
+    // many threads are blocked, wakes all blocked threads.  Unlike Posix
+    // condition variables, the associated lock must be held.
+    void Notify(size_t to_notify = 1);
+
+    // Wakes up all threads blocked in this->Wait().  Unlike Posix condition
+    // variables, the associated lock must be held.
+    void NotifyAll();
+
+private:
+    const PyThread_type_lock lock_;
+    std::deque<PyThread_type_lock> waiters_;
+};
+
+// Provides a boolean variable 'was_set' that can transition from false to true
+// at most one time, and a way to wait until it turns true.  There is no way to
+// change 'was_set' back to false because most uses would require extra locking
+// around the reset to make sure notifications aren't lost.
+struct PyMonotonicEvent {
+    PyMonotonicEvent();
+    ~PyMonotonicEvent();
+
+    // Waits for 'was_set' to become true.
+    void Wait();
+    // Sets 'was_set' to true.
+    void Set();
+
+private:
+    const PyThread_type_lock lock_;
+    PyCondition cond_;
+    bool was_set_;
+};
+
+// This is the interface for background jobs.  Each job has two virtual methods:
+// a Run method, and an Apply method.
+//
+// The Run method runs on the background thread, with the GIL unlocked.  It may
+// acquire the GIL, but that kinda defeats the point.  All Run methods execute
+// serially, in the same order they were submitted to the background thread.
+//
+// The Apply method will called from some foreground thread that holds the GIL.
+// The eval loop applies jobs periodically, and Python code waiting for a
+// particular job to finish may apply other jobs too.  When a Job's Apply method
+// returns, the system deletes the job.
+struct PyBackgroundJob {
+    PyBackgroundJob() : ready_to_apply_(NULL) {}
+    virtual ~PyBackgroundJob() {}
+
+    // This method is called in the background thread.  It must be careful not
+    // to access Python data structures since it doesn't hold the GIL, and to
+    // lock any data it needs to share.  'shutting_down' is true if Terminate
+    // has been called.
+    virtual void Run(bool shutting_down) = 0;
+
+    // This method applies the result of the background job in a foreground
+    // thread.  It's called while holding the GIL.  If any Python exceptions are
+    // set when Apply returns, they'll be printed as if they escaped a Python
+    // thread.
+    virtual void Apply() = 0;
+
+private:
+    friend struct PyBackgroundThread;
+    // If ready_to_apply_ is not null, the background thread will Set it when a
+    // subsequent ApplyFinishedJobs will apply it.  This is only set by
+    // PyBackgroundThread::RunJobAndWait(); users don't have access.
+    PyMonotonicEvent *ready_to_apply_;
+};
+
+// This class encapsulates all state required by the background thread.
+// Instances must be created and all public methods must be called while holding
+// the GIL.
+struct PyBackgroundThread {
+public:
+    PyBackgroundThread();
+    ~PyBackgroundThread();
+
+    // This method starts the background thread.  Calling it multiple times has
+    // no effect.
+    void Start();
+
+    // Terminate the background thread.  This will block until it terminates,
+    // which may be after it finishes a single compile job.  After the thread
+    // has has stopped, all of the public methods below will run the job in the
+    // foreground or do nothing.  This method must be called before destroying
+    // the thread object or the PyGlobalLlvmData object.  Calling this method
+    // when the thread is already stopped has no effect.
+    void Terminate();
+
+    // Pause the background thread.  Puts a job on the queue that will cause the
+    // background thread to acquire the unpause event lock twice.  This call
+    // must be paired with an Unpause call from the same thread.
+    //
+    // While the background thread is paused, it will run new jobs in the
+    // foreground, as if it had been terminated.  This means that if we fork
+    // while paused, the child process won't send jobs off to nowhere allows us
+    // to fork while paused and avoid creating a new thread in the child process
+    // or depending on state
+    void Pause();
+
+    // Unpause the background thread.  This releases the unpause_event_ lock,
+    // allowing the background thread to proceed.  This call must be paired with
+    // a Pause call from the same thread.
+    void Unpause();
+
+    // Helper method that puts a job on the queue.  If the queue is closed, it
+    // calls the RunAfterShutdown method of the job and returns -1.  This
+    // method takes ownership of the job.
+    int RunJob(PyBackgroundJob *job);
+
+    // Helper method that puts a job on the queue and waits for it to be
+    // finished.  This method takes ownership of the job.
+    void RunJobAndWait(PyBackgroundJob *job);
+
+    // Helper method that calls RunJobAndWait and then applies all finished
+    // jobs.  This method takes ownership of the job.
+    void RunJobAndApply(PyBackgroundJob *job);
+
+    // Apply the results of any JIT compilation done in the background thread.
+    // If block is PY_NO_BLOCK, then this function returns non-zero immediately
+    // if it cannot acquire the lock on the output job queue.  Otherwise it
+    // returns 0.  The thread that calls this must hold the GIL.
+    int ApplyFinishedJobs(Py_ShouldBlock block);
+
+    PyThreadState *getThreadState() { return this->tstate_; }
+
+private:
+    // Guards member variables.
+    const PyThread_type_lock lock_;
+    // Lets the background thread wait for something to do.  Guarded by
+    // this->lock_.
+    PyCondition cond_;
+
+    // This is the queue transferring jobs that need to be Run from the
+    // foreground (GIL-holding threads) to the background thread.  Guarded by
+    // this->lock_.
+    std::deque<PyBackgroundJob*> fore2back_queue_;
+
+    // This is the queue transferring finished jobs that need to be Applied from
+    // the background thread to the foreground.  It is read periodically when
+    // the Python ticker expires, or when a thread waits for a job to complete.
+    // Guarded by this->lock_.
+    std::deque<PyBackgroundJob*> back2fore_queue_;
+
+    // Indicates whether the thread is running.  Guarded by this->lock_.
+    bool running_;
+
+    // True if a thread has called Terminate().  Guarded by this->lock_.
+    bool exiting_;
+
+    // The thread state for the background thread.  We don't use any of this
+    // information, but we have to have an interpreter state to swap in when we
+    // acquire the GIL.  Many Python calls assume that there is always a valid
+    // thread state from which to get the interpreter state, so we must provide
+    // one.  Set once at the beginning of Run
+    PyThreadState *tstate_;
+
+    // The interpreter state corresponding to this background thread.  We cache
+    // this value because it is not safe to call PyThreadState_GET() from a
+    // background thread.
+    PyInterpreterState *const interpreter_state_;
+
+    // A lock that allows us to pause and unpause the background thread without
+    // destroying and recreating the underlying OS-level thread.
+    PyThread_type_lock unpause_event_;
+
+
+    // -- Functions called by the background thread --
+
+    // This static method is the background compilation thread entry point.  It
+    // simply wraps calling ((PyBackgroundThread*)thread)->Run() so we can pass
+    // it as a function pointer to PyThread_start_new_thread.
+    static void Bootstrap(void *thread);
+
+    // This method reads jobs from the queue and runs them in a loop.  It
+    // terminates when it reads a JIT_EXIT job from the queue.  This method
+    // must be called from the background thread, and the caller should not
+    // hold the GIL.
+    void Run();
+
+    // Puts a finished job on the output queue and notifies anyone waiting for
+    // it that it is now on the output queue.  This method takes ownership of
+    // the job.  This method must be called from the background thread, and the
+    // caller should not hold the GIL.
+    void OutputFinishedJob(PyBackgroundJob *job);
+};
+
+#endif /* __cplusplus */
+
+#endif /* Py_BG_THREAD_H */

Modified: python/branches/py3k-jit/Include/ceval.h
==============================================================================
--- python/branches/py3k-jit/Include/ceval.h	(original)
+++ python/branches/py3k-jit/Include/ceval.h	Sat May 22 01:46:06 2010
@@ -170,6 +170,16 @@
 PyAPI_FUNC(void) _PyEval_SetSwitchInterval(unsigned long microseconds);
 PyAPI_FUNC(unsigned long) _PyEval_GetSwitchInterval(void);
 
+PyAPI_FUNC(void) _PyEval_AssertLockHeld(void);
+
+// Hide this assertion function behind a macro to avoid extra calls in release
+// builds.
+#ifndef NDEBUG
+#define PyEval_AssertLockHeld() _PyEval_AssertLockHeld()
+#else
+#define PyEval_AssertLockHeld()
+#endif /* NDEBUG */
+
 #define Py_BEGIN_ALLOW_THREADS { \
                         PyThreadState *_save; \
                         _save = PyEval_SaveThread();
@@ -189,7 +199,7 @@
 
 PyAPI_FUNC(int) _PyEval_SliceIndex(PyObject *, Py_ssize_t *);
 PyAPI_FUNC(void) _PyEval_SignalAsyncExc(void);
-
+PyAPI_FUNC(void) _PyEval_SetBackgroundJobAvailable(int);
 
 #ifdef __cplusplus
 }

Modified: python/branches/py3k-jit/Include/code.h
==============================================================================
--- python/branches/py3k-jit/Include/code.h	(original)
+++ python/branches/py3k-jit/Include/code.h	Sat May 22 01:46:06 2010
@@ -32,9 +32,14 @@
     /* Measure of how hot this code object is. This will be used to
        decide which code objects are worth sending through LLVM. */
     long co_hotness;
+    /* True if the code object is being compiled by the background thread. */
+    char co_being_compiled;
 #endif  /* WITH_LLVM */
 } PyCodeObject;
 
+/* The threshold for co_hotness before the code object is considered "hot". */
+#define PY_HOTNESS_THRESHOLD 100000
+
 /* Masks for co_flags above */
 #define CO_OPTIMIZED	0x0001
 #define CO_NEWLOCALS	0x0002

Modified: python/branches/py3k-jit/Include/intrcheck.h
==============================================================================
--- python/branches/py3k-jit/Include/intrcheck.h	(original)
+++ python/branches/py3k-jit/Include/intrcheck.h	Sat May 22 01:46:06 2010
@@ -7,7 +7,8 @@
 
 PyAPI_FUNC(int) PyOS_InterruptOccurred(void);
 PyAPI_FUNC(void) PyOS_InitInterrupts(void);
-PyAPI_FUNC(void) PyOS_AfterFork(void);
+PyAPI_FUNC(void) PyOS_BeforeFork(void);
+PyAPI_FUNC(int) PyOS_AfterFork(int);  /* Preserves errno. */
 
 #ifdef __cplusplus
 }

Modified: python/branches/py3k-jit/Include/pystate.h
==============================================================================
--- python/branches/py3k-jit/Include/pystate.h	(original)
+++ python/branches/py3k-jit/Include/pystate.h	Sat May 22 01:46:06 2010
@@ -27,6 +27,13 @@
     PyObject *codec_search_path;
     PyObject *codec_search_cache;
     PyObject *codec_error_registry;
+#ifdef WITH_LLVM
+    /* NULL if no thread has been started yet.  The low bit is set to
+       1 after a fork to mark the thread as disabled until the user
+       explicitly re-enables it with
+       PyBackgroundThread_ReenableAfterFork(). */
+    struct PyBackgroundThread *background_thread;
+#endif
     int codecs_initialized;
 
 #ifdef HAVE_DLOPEN

Modified: python/branches/py3k-jit/Lib/test/test_sys.py
==============================================================================
--- python/branches/py3k-jit/Lib/test/test_sys.py	(original)
+++ python/branches/py3k-jit/Lib/test/test_sys.py	Sat May 22 01:46:06 2010
@@ -633,7 +633,7 @@
         check(get_cell().__closure__[0], size(h + 'P'))
         # code
         if WITH_LLVM:
-            check(get_cell().__code__, size(h + '5i8Pi3Pl'))
+            check(get_cell().__code__, size(h + '5i8Pi3Plc'))
         else:
             check(get_cell().__code__, size(h + '5i8Pi3P'))
         # complex

Modified: python/branches/py3k-jit/Makefile.pre.in
==============================================================================
--- python/branches/py3k-jit/Makefile.pre.in	(original)
+++ python/branches/py3k-jit/Makefile.pre.in	Sat May 22 01:46:06 2010
@@ -292,6 +292,7 @@
 		Python/Python-ast.o \
 		Python/asdl.o \
 		Python/ast.o \
+		Python/bg_thread.o \
 		Python/bltinmodule.o \
 		Python/ceval.o \
 		Python/compile.o \
@@ -654,11 +655,12 @@
 		Include/abstract.h \
 		Include/asdl.h \
 		Include/ast.h \
-                Include/bltinmodule.h \
+		Include/bg_thread.h \
 		Include/bitset.h \
+		Include/bltinmodule.h \
 		Include/boolobject.h \
-		Include/bytes_methods.h \
 		Include/bytearrayobject.h \
+		Include/bytes_methods.h \
 		Include/bytesobject.h \
 		Include/cellobject.h \
 		Include/ceval.h \
@@ -746,6 +748,7 @@
 		$(srcdir)/Unittests/StatsTest.cc
 
 UNITTEST_SRCS := $(LIBRARY) \
+		$(srcdir)/Unittests/BgThreadTest.cc \
 		$(srcdir)/Unittests/DatetimeTest.cc \
 		$(srcdir)/Unittests/DictTest.cc \
 		$(srcdir)/Unittests/GetArgsTest.cc \

Modified: python/branches/py3k-jit/Misc/NEWS
==============================================================================
--- python/branches/py3k-jit/Misc/NEWS	(original)
+++ python/branches/py3k-jit/Misc/NEWS	Sat May 22 01:46:06 2010
@@ -4,6 +4,25 @@
 
 (editors: check NEWS.help for information about editing NEWS using ReST.)
 
+What's New in Python 3.3 Alpha 1?
+=================================
+
+*Release date: XX-XXX-XXX*
+
+Core and Builtins
+-----------------
+
+C-API
+-----
+
+- Previously, if extension modules forked, they were supposed to call
+  PyOS_AfterFork from the child process.  Now they are required to call
+  PyOS_BeforeFork and PyOS_AfterFork in both the child and the parent and pass a
+  new boolean parameter to PyOS_AfterFork specifying whether the current process
+  is the child or the parent.  To be specific, you should change "if (pid == 0)
+  PyOS_AfterFork()" to "PyOS_AfterFork(pid == 0)".
+
+
 What's New in Python 3.2 Alpha 1?
 =================================
 

Modified: python/branches/py3k-jit/Modules/_posixsubprocess.c
==============================================================================
--- python/branches/py3k-jit/Modules/_posixsubprocess.c	(original)
+++ python/branches/py3k-jit/Modules/_posixsubprocess.c	Sat May 22 01:46:06 2010
@@ -265,7 +265,7 @@
         preexec_fn_args_tuple = PyTuple_New(0);
         if (!preexec_fn_args_tuple)
             goto cleanup;
-        _PyImport_AcquireLock();
+        PyOS_BeforeFork();
     }
 
     if (cwd_obj != Py_None) {
@@ -291,7 +291,7 @@
              * This call may not be async-signal-safe but neither is calling
              * back into Python.  The user asked us to use hope as a strategy
              * to avoid deadlock... */
-            PyOS_AfterFork();
+            PyOS_AfterFork(/*is_child=*/1);
         }
 
         child_exec(exec_array, argv, envp, cwd,
@@ -308,11 +308,9 @@
         /* Capture the errno exception before errno can be clobbered. */
         PyErr_SetFromErrno(PyExc_OSError);
     }
-    if (preexec_fn != Py_None &&
-        _PyImport_ReleaseLock() < 0 && !PyErr_Occurred()) {
-        PyErr_SetString(PyExc_RuntimeError,
-                        "not holding the import lock");
-    }
+
+    if (preexec_fn != Py_None)
+        PyOS_AfterFork(/*is_child=*/0);
 
     /* Parent process */
     if (envp)

Modified: python/branches/py3k-jit/Modules/posixmodule.c
==============================================================================
--- python/branches/py3k-jit/Modules/posixmodule.c	(original)
+++ python/branches/py3k-jit/Modules/posixmodule.c	Sat May 22 01:46:06 2010
@@ -3590,21 +3590,12 @@
 {
     pid_t pid;
     int result = 0;
-    _PyImport_AcquireLock();
+    PyOS_BeforeFork();
     pid = fork1();
-    if (pid == 0) {
-        /* child: this clobbers and resets the import lock. */
-        PyOS_AfterFork();
-    } else {
-        /* parent: release the import lock. */
-        result = _PyImport_ReleaseLock();
-    }
+    result = PyOS_AfterFork(pid == 0);
     if (pid == -1)
         return posix_error();
     if (result < 0) {
-        /* Don't clobber the OSError if the fork failed. */
-        PyErr_SetString(PyExc_RuntimeError,
-                        "not holding the import lock");
         return NULL;
     }
     return PyLong_FromPid(pid);
@@ -3623,21 +3614,12 @@
 {
     pid_t pid;
     int result = 0;
-    _PyImport_AcquireLock();
+    PyOS_BeforeFork();
     pid = fork();
-    if (pid == 0) {
-        /* child: this clobbers and resets the import lock. */
-        PyOS_AfterFork();
-    } else {
-        /* parent: release the import lock. */
-        result = _PyImport_ReleaseLock();
-    }
+    result = PyOS_AfterFork(pid == 0);
     if (pid == -1)
         return posix_error();
     if (result < 0) {
-        /* Don't clobber the OSError if the fork failed. */
-        PyErr_SetString(PyExc_RuntimeError,
-                        "not holding the import lock");
         return NULL;
     }
     return PyLong_FromPid(pid);
@@ -3749,21 +3731,12 @@
     int master_fd = -1, result = 0;
     pid_t pid;
 
-    _PyImport_AcquireLock();
+    PyOS_BeforeFork();
     pid = forkpty(&master_fd, NULL, NULL, NULL);
-    if (pid == 0) {
-        /* child: this clobbers and resets the import lock. */
-        PyOS_AfterFork();
-    } else {
-        /* parent: release the import lock. */
-        result = _PyImport_ReleaseLock();
-    }
+    result = PyOS_AfterFork(pid == 0);
     if (pid == -1)
         return posix_error();
     if (result < 0) {
-        /* Don't clobber the OSError if the fork failed. */
-        PyErr_SetString(PyExc_RuntimeError,
-                        "not holding the import lock");
         return NULL;
     }
     return Py_BuildValue("(Ni)", PyLong_FromPid(pid), master_fd);

Modified: python/branches/py3k-jit/Modules/signalmodule.c
==============================================================================
--- python/branches/py3k-jit/Modules/signalmodule.c	(original)
+++ python/branches/py3k-jit/Modules/signalmodule.c	Sat May 22 01:46:06 2010
@@ -5,6 +5,7 @@
 
 #include "Python.h"
 #include "intrcheck.h"
+#include "bg_thread.h"
 
 #ifdef MS_WINDOWS
 #include <Windows.h>
@@ -957,14 +958,51 @@
     return 0;
 }
 
+/* TODO: Move these other PyOS functions to a better place.  */
+
 void
-PyOS_AfterFork(void)
+PyOS_BeforeFork(void)
 {
+    _PyImport_AcquireLock();
 #ifdef WITH_THREAD
-    PyEval_ReInitThreads();
-    main_thread = PyThread_get_thread_ident();
-    main_pid = getpid();
-    _PyImport_ReInitLock();
-    PyThread_ReInitTLS();
+#ifdef WITH_LLVM
+    PyBackgroundThread_Pause(PyThreadState_GET()->interp->background_thread);
+#endif
+#endif
+}
+
+/* Returns -1 if there was an error. Preserves errno. */
+int
+PyOS_AfterFork(int is_child)
+{
+    int result = 0;
+    int saved_errno = errno;
+#ifdef WITH_THREAD
+    PyInterpreterState *interp = PyThreadState_GET()->interp;
+    if (is_child) {
+#ifdef WITH_LLVM
+        PyBackgroundThread_DisableAfterFork(interp);
+#endif
+        PyEval_ReInitThreads();
+        main_thread = PyThread_get_thread_ident();
+        main_pid = getpid();
+        _PyImport_ReInitLock();
+        PyThread_ReInitTLS();
+    } else {
+#ifdef WITH_LLVM
+        PyBackgroundThread_Unpause(interp->background_thread);
 #endif
+    }
+#endif
+    if (!is_child) {
+        /* parent-only: release the import lock. */
+        result = _PyImport_ReleaseLock();
+        if (result < 0 && !PyErr_Occurred()) {
+            /* Don't clobber any other errors someone set. */
+            PyErr_SetString(PyExc_RuntimeError,
+                            "not holding the import lock");
+        }
+    }
+    errno = saved_errno;
+    return result;
 }

Modified: python/branches/py3k-jit/Objects/codeobject.c
==============================================================================
--- python/branches/py3k-jit/Objects/codeobject.c	(original)
+++ python/branches/py3k-jit/Objects/codeobject.c	Sat May 22 01:46:06 2010
@@ -111,6 +111,7 @@
         co->co_weakreflist = NULL;
 #ifdef WITH_LLVM
         co->co_hotness = 0;
+        co->co_being_compiled = 0;
 #endif  /* WITH_LLVM */
     }
     return co;

Added: python/branches/py3k-jit/Python/bg_thread.cc
==============================================================================
--- (empty file)
+++ python/branches/py3k-jit/Python/bg_thread.cc	Sat May 22 01:46:06 2010
@@ -0,0 +1,438 @@
+#include "Python.h"
+
+#include "bg_thread.h"
+#include "pythread.h"
+
+#ifdef WITH_LLVM
+
+// Support structs:
+
+PyCondition::PyCondition(PyThread_type_lock lock)
+    : lock_(lock)
+{}
+PyCondition::~PyCondition()
+{
+    assert(this->waiters_.empty() &&
+           "Destroyed condition variable with waiting threads");
+}
+
+void PyCondition::Wait()
+{
+    // TODO: Allocating a new python-lock on every wait is moderately
+    // expensive.  We should add a real condition variable to
+    // pythreads or create a freelist for these python-locks.
+    const PyThread_type_lock semaphore = PyThread_allocate_lock();
+    PyThread_acquire_lock(semaphore, WAIT_LOCK);
+    this->waiters_.push_back(semaphore);
+    const PyThread_type_lock this_lock = this->lock_;
+    PyThread_release_lock(this_lock);
+    // After here, 'this' may have been destroyed.
+    // Block until another thread notifies us.
+    PyThread_acquire_lock(semaphore, WAIT_LOCK);
+    PyThread_free_lock(semaphore);
+    PyThread_acquire_lock(this_lock, WAIT_LOCK);
+}
+
+void PyCondition::Notify(size_t to_notify)
+{
+    to_notify = std::min<size_t>(to_notify, waiters_.size());
+    for (size_t i = 0; i < to_notify; ++i) {
+        PyThread_type_lock waiter_semaphore = waiters_.front();
+        waiters_.pop_front();
+        PyThread_release_lock(waiter_semaphore);
+    }
+}
+
+void PyCondition::NotifyAll()
+{
+    Notify(waiters_.size());
+}
+
+PyMonotonicEvent::PyMonotonicEvent()
+    : lock_(PyThread_allocate_lock()),
+      cond_(lock_),
+      was_set_(false)
+{}
+PyMonotonicEvent::~PyMonotonicEvent()
+{
+    PyThread_free_lock(lock_);
+}
+
+void PyMonotonicEvent::Wait()
+{
+    PyLockGuard g(this->lock_);
+    while (!this->was_set_) {
+        this->cond_.Wait();
+    }
+}
+
+void PyMonotonicEvent::Set()
+{
+    PyLockGuard g(this->lock_);
+    this->was_set_ = true;
+    this->cond_.NotifyAll();
+}
+
+
+// Actual background thread code:
+
+PyBackgroundThread::PyBackgroundThread()
+    : lock_(PyThread_allocate_lock()), cond_(lock_),
+      running_(false), exiting_(false), tstate_(NULL),
+      interpreter_state_(PyThreadState_GET()->interp),
+      unpause_event_(PyThread_allocate_lock())
+{
+}
+
+PyBackgroundThread::~PyBackgroundThread()
+{
+    this->Terminate();
+    PyThread_free_lock(this->unpause_event_);
+#ifdef Py_WITH_INSTRUMENTATION
+    fprintf(stderr, "Compilation thread statistics:\n");
+    fprintf(stderr, "compile jobs completed: %d\n", CompileJob::compile_count);
+    fprintf(stderr, "compile jobs skipped at thread termination: %d\n",
+            CompileJob::skipped_compile_count);
+#endif
+}
+
+// Assumes this->lock_ is held.
+void
+PyBackgroundThread::OutputFinishedJob(PyBackgroundJob *job)
+{
+    // Put the result of the action on the output queue.  This takes
+    // ownership of the job.
+    this->back2fore_queue_.push_back(job);
+
+    // Alert anybody who might be blocked waiting for this job to finish.  This
+    // is done under the same lock as the output queue, so that anyone waiting
+    // can call this->ApplyFinishedJobs(Py_BLOCK) to make the job take effect.
+    if (job->ready_to_apply_) {
+        job->ready_to_apply_->Set();
+    }
+
+    // Tell the eval loop too.
+    _PyEval_SetBackgroundJobAvailable(true);
+}
+
+void
+PyBackgroundThread::Run()
+{
+    // Create a new thread state.
+    assert(this->tstate_ == NULL);
+    this->tstate_ = PyThreadState_New(this->interpreter_state_);
+
+    PyLockGuard lg(this->lock_);
+    // Consume and run jobs from the input queue until Terminate is called.
+    // Then consume jobs until there aren't any left and exit.
+    while (true) {
+        while (!this->exiting_ && this->fore2back_queue_.empty()) {
+            this->cond_.Wait();
+        }
+        const bool exiting = this->exiting_;
+        if (exiting && this->fore2back_queue_.empty()) {
+            break;
+        }
+        PyBackgroundJob *job = this->fore2back_queue_.front();
+        this->fore2back_queue_.pop_front();
+
+        {
+            PyUnlockGuard ug(this->lock_);
+            // Don't hold the lock while running the job, so other jobs can be
+            // submitted without blocking.
+            job->Run(exiting);
+        }
+        this->OutputFinishedJob(job);
+    }
+    PyThreadState_Clear(this->tstate_);
+    PyThreadState_Delete(this->tstate_);
+    this->tstate_ = NULL;
+    this->running_ = false;
+    this->cond_.NotifyAll();
+}
+
+int
+PyBackgroundThread::RunJob(PyBackgroundJob *job)
+{
+    PyEval_AssertLockHeld();
+
+    {
+        PyLockGuard g(this->lock_);
+        if (this->running_) {
+            this->fore2back_queue_.push_back(job);
+            this->cond_.NotifyAll();
+            return 0;
+        }
+    }
+
+    // If the background thread has terminated, it can't race with the code in
+    // any job that expects to run there.  So we just do exactly what the
+    // background thread would have done.
+    job->Run(/*shutting_down=*/true);
+
+    // Rather than pushing it on to the output queue, since we already hold
+    // the GIL, we apply the job and delete it here.
+    job->Apply();
+    delete job;
+    return -1;
+}
+
+void
+PyBackgroundThread::RunJobAndWait(PyBackgroundJob *job)
+{
+    // Create a lock for the job, acquire it, and put the job on the queue.
+    assert(job->ready_to_apply_ == NULL && "Job is already being waited for?");
+    PyMonotonicEvent ready;
+    job->ready_to_apply_ = &ready;
+    if (this->RunJob(job) < 0) {
+        // If we couldn't put it on the queue, don't wait for it, because it's
+        // already done.
+        return;
+    }
+
+    // Try to acquire the lock again to wait until the lock is released by the
+    // background thread.  This may take awhile, so we release the GIL while we
+    // wait.
+    Py_BEGIN_ALLOW_THREADS;
+    ready.Wait();
+    job->ready_to_apply_ = NULL;
+    // When we get control back, reacquire the GIL.
+    Py_END_ALLOW_THREADS;
+}
+
+void
+PyBackgroundThread::RunJobAndApply(PyBackgroundJob *job)
+{
+    this->RunJobAndWait(job);
+    // Call this to make sure all compilations take effect before we return.
+    this->ApplyFinishedJobs(Py_BLOCK);
+}
+
+int
+PyBackgroundThread::ApplyFinishedJobs(Py_ShouldBlock block)
+{
+    if (!PyThread_acquire_lock(this->lock_, block == Py_BLOCK)) {
+        assert(block == Py_NO_BLOCK &&
+               "We should only fail to acquire the lock "
+               "in a non-blocking application attempt.");
+        return -1;  // We couldn't acquire the lock, so we'll try again later.
+    }
+    std::deque<PyBackgroundJob*> queue;
+    // Take all of the elements out of the background->foreground queue.
+    queue.swap(this->back2fore_queue_);
+    // Tell the eval loop that the jobs are taken care of. We have to
+    // do this under the lock so we don't clobber any new jobs that
+    // finish after we release the lock.
+    _PyEval_SetBackgroundJobAvailable(false);
+    // Then release the lock so Apply doesn't deadlock, and the background
+    // thread can keep running jobs.
+    PyThread_release_lock(this->lock_);
+
+    for (std::deque<PyBackgroundJob*>::const_iterator
+             it = queue.begin(), end = queue.end(); it != end; ++it) {
+        PyBackgroundJob *job = *it;
+        job->Apply();
+        delete job;
+    }
+    return 0;
+}
+
+void
+PyBackgroundThread::Terminate()
+{
+    Py_BEGIN_ALLOW_THREADS;
+    {
+        PyLockGuard g(this->lock_);
+        this->exiting_ = true;
+        this->cond_.NotifyAll();
+        while (this->running_) {
+            this->cond_.Wait();
+        }
+    }
+    Py_END_ALLOW_THREADS;
+    this->ApplyFinishedJobs(Py_BLOCK);
+}
+
+void
+PyBackgroundThread::Bootstrap(void *thread)
+{
+    ((PyBackgroundThread*)thread)->Run();
+}
+
+void
+PyBackgroundThread::Start()
+{
+    if (this->running_) {
+        return;
+    }
+    this->running_ = true;
+
+    PyEval_InitThreads(); /* Start the interpreter's thread-awareness */
+    PyThread_start_new_thread(Bootstrap, this);
+}
+
+// A PyBackgroundJob that pauses the background thread in preparation for
+// forking.  This works by acquiring the PyBackgroundThread::unpause_event_ lock
+// twice.
+struct PauseJob : public PyBackgroundJob {
+    PauseJob(PyThread_type_lock bg_thread_lock,
+             PyThread_type_lock unpause_event,
+             PyMonotonicEvent &paused)
+        : bg_thread_lock_(bg_thread_lock),
+          unpause_event_(unpause_event),
+          paused_(&paused)
+    {}
+
+    // Acquire the unpause event lock so that we wait for it.
+    virtual void Run(bool shut_down) {
+        if (shut_down) {
+            // If we try to pause the thread after its stopped, acquire the lock once,
+            // so that it can be properly released later.
+            PyThread_acquire_lock(this->unpause_event_, WAIT_LOCK);
+            return;
+        }
+
+        // Prepare to block by acquiring the unpause event lock twice.
+        assert(PyThread_acquire_lock(this->unpause_event_, NOWAIT_LOCK) &&
+               "Unpause event lock was already acquired!");
+
+        // Acquire the lock on the whole background thread so that no one can
+        // use it while we're paused.
+        PyThread_acquire_lock(this->bg_thread_lock_, WAIT_LOCK);
+
+        // We need to notify whoever is waiting on this job before we block.
+        this->paused_->Set();
+        // The foreground thread may fork at any point after this.
+        this->paused_ = NULL;
+
+        // At this point, there is sort of a race between the thread that paused
+        // the compilation thread and this next acquire.  In either case, after
+        // the acquire we will hold the lock because only one pause job can be
+        // processed at a time.
+
+        // Block until we are unpaused.
+        PyThread_acquire_lock(this->unpause_event_, WAIT_LOCK);
+
+        // Leave the unpause event lock in the released state.
+        PyThread_release_lock(this->unpause_event_);
+
+        // Release the locks on the background thread.
+        PyThread_release_lock(this->bg_thread_lock_);
+    }
+
+    virtual void Apply() {}
+
+private:
+    const PyThread_type_lock bg_thread_lock_;
+    const PyThread_type_lock unpause_event_;
+    PyMonotonicEvent *paused_;
+};
+
+void
+PyBackgroundThread::Pause()
+{
+    PyMonotonicEvent paused;
+    {
+        PyLockGuard g(this->lock_);
+        if (!this->running_)
+            return;
+
+        // Put the pause job at the front of the queue so we don't wait for the
+        // other jobs to finish before pausing.
+        this->fore2back_queue_.push_front(
+            new PauseJob(this->lock_, this->unpause_event_, paused));
+        this->cond_.NotifyAll();
+    }
+
+    // Wait for the pause job to actually start and acquire the locks it needs
+    // to acquire.
+    paused.Wait();
+
+    // Note that we leak the pause job in the child process during a fork.  This
+    // is OK because we leak all kinds of other things, like locks and objects
+    // owned by other threads.
+}
+
+void
+PyBackgroundThread::Unpause()
+{
+    PyThread_release_lock(this->unpause_event_);
+}
+
+extern "C" {
+    void
+    PyBackgroundThread_DisableAfterFork(PyInterpreterState *interp)
+    {
+        interp->background_thread =
+            reinterpret_cast<PyBackgroundThread*>(
+                reinterpret_cast<uintptr_t>(interp->background_thread) | 1);
+    }
+
+    void
+    PyBackgroundThread_ReenableAfterFork(PyInterpreterState *interp)
+    {
+        // After forking, most of the data in the background thread is
+        // in an unknown state.  We do, however, know that the thread
+        // isn't actually running.  So we leak any memory owned by the
+        // background thread, and reset the interpreter's pointer to
+        // NULL, so anyone trying to use it will restart the thread.
+        interp->background_thread = NULL;
+    }
+
+    PyBackgroundThread *
+    PyBackgroundThread_New()
+    {
+        return new PyBackgroundThread;
+    }
+
+    void
+    PyBackgroundThread_Free(PyBackgroundThread *bg_thread)
+    {
+        delete bg_thread;
+    }
+
+    void
+    PyBackgroundThread_Pause(PyBackgroundThread *thread)
+    {
+        if (thread != NULL && !PyBackgroundThread_Disabled(thread))
+            thread->Pause();
+    }
+
+    void
+    PyBackgroundThread_Unpause(PyBackgroundThread *thread)
+    {
+        if (thread != NULL && !PyBackgroundThread_Disabled(thread))
+            thread->Unpause();
+    }
+
+    int PyBackgroundThread_RunJob(PyInterpreterState *interp,
+                                  PyBackgroundJob *job)
+    {
+        if (interp->background_thread == NULL) {
+            interp->background_thread = new PyBackgroundThread;
+            interp->background_thread->Start();
+        }
+        return interp->background_thread->RunJob(job);
+    }
+
+    int
+    PyBackgroundThread_ApplyFinishedJobs(
+        PyBackgroundThread *thread, Py_ShouldBlock block)
+    {
+        return thread->ApplyFinishedJobs(block);
+    }
+
+    PyBackgroundJob*
+    PyBackgroundThread_NewDummyJob()
+    {
+        struct DummyJob : PyBackgroundJob {
+            virtual void Run(bool shutting_down)
+            {}
+            virtual void Apply()
+            {}
+        };
+        return new DummyJob;
+    }
+}
+
+#endif // WITH_LLVM

Modified: python/branches/py3k-jit/Python/ceval.c
==============================================================================
--- python/branches/py3k-jit/Python/ceval.c	(original)
+++ python/branches/py3k-jit/Python/ceval.c	Sat May 22 01:46:06 2010
@@ -11,6 +11,7 @@
 
 #include "Python.h"
 
+#include "bg_thread.h"
 #include "code.h"
 #include "frameobject.h"
 #include "eval.h"
@@ -119,6 +120,8 @@
 
 #ifdef WITH_LLVM
 static inline void mark_called(PyCodeObject *co);
+static inline void maybe_compile(PyInterpreterState *interp,
+                                 PyCodeObject *co, PyFrameObject *f);
 #endif  /* WITH_LLVM */
 
 #define CALL_FLAG_VAR 1
@@ -229,6 +232,7 @@
         &eval_breaker, \
         _Py_atomic_load_relaxed(&gil_drop_request) | \
         _Py_atomic_load_relaxed(&pendingcalls_to_do) | \
+        _Py_atomic_load_relaxed(&background_job_ready) | \
         pending_async_exc)
 
 #define SET_GIL_DROP_REQUEST() \
@@ -265,6 +269,18 @@
 #define UNSIGNAL_ASYNC_EXC() \
     do { pending_async_exc = 0; COMPUTE_EVAL_BREAKER(); } while (0)
 
+#define SIGNAL_BACKGROUND_JOB_READY() \
+    do { \
+        _Py_atomic_store_relaxed(&background_job_ready, 1); \
+        _Py_atomic_store_relaxed(&eval_breaker, 1); \
+    } while (0)
+
+#define UNSIGNAL_BACKGROUND_JOB_READY() \
+    do { \
+        _Py_atomic_store_relaxed(&background_job_ready, 0); \
+        COMPUTE_EVAL_BREAKER(); \
+    } while (0)
+
 
 #ifdef WITH_THREAD
 
@@ -285,6 +301,8 @@
 /* Request for looking at the `async_exc` field of the current thread state.
    Guarded by the GIL. */
 static int pending_async_exc = 0;
+/* Request for applying background jobs. */
+static _Py_atomic_int background_job_ready = {0};
 
 #include "ceval_gil.h"
 
@@ -326,6 +344,18 @@
         &_PyThreadState_Current));
 }
 
+#ifndef NDEBUG
+// Assert that the calling thread holds the GIL.  To do this we ask Python what
+// thread it thinks is currently running and compare its identity with our own.
+void
+_PyEval_AssertLockHeld()
+{
+    PyThreadState *tstate = PyThreadState_GET();
+    assert(tstate && tstate->thread_id == PyThread_get_thread_ident() &&
+           "Caller must hold the GIL!");
+}
+#endif // NDEBUG
+
 void
 PyEval_AcquireThread(PyThreadState *tstate)
 {
@@ -403,6 +433,7 @@
 static _Py_atomic_int eval_breaker = {0};
 static _Py_atomic_int gil_drop_request = {0};
 static int pending_async_exc = 0;
+static _Py_atomic_int background_job_ready = {0};
 #endif /* WITH_THREAD */
 
 /* This function is used to signal that async exceptions are waiting to be
@@ -414,6 +445,17 @@
     SIGNAL_ASYNC_EXC();
 }
 
+/* Lets the eval loop know that a background job is or isn't ready to
+   apply. */
+void
+_PyEval_SetBackgroundJobAvailable(int available)
+{
+    if (available)
+        SIGNAL_BACKGROUND_JOB_READY();
+    else
+        UNSIGNAL_BACKGROUND_JOB_READY();
+}
+
 /* Functions save_thread and restore_thread are always defined so
    dynamically loaded modules needn't be compiled separately for use
    with and without threads: */
@@ -1144,6 +1186,11 @@
         return NULL;
 
     tstate->frame = f;
+    co = f->f_code;
+
+#ifdef WITH_LLVM
+    maybe_compile(tstate->interp, co, f);
+#endif  /* WITH_LLVM */
 
     if (tstate->use_tracing) {
         if (tstate->c_tracefunc != NULL) {
@@ -1179,7 +1226,6 @@
         }
     }
 
-    co = f->f_code;
     names = co->co_names;
     consts = co->co_consts;
     fastlocals = f->f_localsplus;
@@ -1281,6 +1327,28 @@
                     goto on_error;
                 }
             }
+            if (_Py_atomic_load_relaxed(&background_job_ready)) {
+#ifdef WITH_LLVM
+                if (tstate->interp->background_thread == NULL ||
+                    PyBackgroundThread_Disabled(
+                        tstate->interp->background_thread)) {
+                    /* No interpreter background thread, so it can't
+                       have a job ready, but there could be other
+                       instances that signal the interpreter
+                       spuriously. */
+                    UNSIGNAL_BACKGROUND_JOB_READY();
+                }
+                else {
+                    /* No need to block. If we can't acquire the lock,
+                       we'll try again on the next iteration. */
+                    PyBackgroundThread_ApplyFinishedJobs(
+                        tstate->interp->background_thread,
+                        Py_NO_BLOCK);
+                }
+#else  /* !WITH_LLVM */
+                UNSIGNAL_BACKGROUND_JOB_READY();
+#endif  /* WITH_LLVM */
+            }
             if (_Py_atomic_load_relaxed(&gil_drop_request)) {
 #ifdef WITH_THREAD
                 /* Give another thread a chance */
@@ -3843,6 +3911,28 @@
 {
     co->co_hotness += 10;
 }
+
+/* For now, if the code object is hot, starts the background thread if
+   necessary and sends a dummy job to it.  TODO: flesh this out to
+   actually compile. */
+static inline void
+maybe_compile(PyInterpreterState *interp, PyCodeObject *co, PyFrameObject *f)
+{
+    int is_hot = 0, should_compile;
+    if (co->co_hotness > PY_HOTNESS_THRESHOLD)
+        is_hot = 1;
+    should_compile = is_hot;
+    if (PyBackgroundThread_Disabled(interp->background_thread))
+        should_compile = 0;
+    if (co->co_being_compiled)
+        should_compile = 0;
+    if (should_compile) {
+        co->co_being_compiled = 1;
+        PyBackgroundThread_RunJob(PyThreadState_GET()->interp,
+                                  PyBackgroundThread_NewDummyJob());
+    }
+}
+
 #endif  /* WITH_LLVM */
 
 #define C_TRACE(x, call) \

Modified: python/branches/py3k-jit/Python/pystate.c
==============================================================================
--- python/branches/py3k-jit/Python/pystate.c	(original)
+++ python/branches/py3k-jit/Python/pystate.c	Sat May 22 01:46:06 2010
@@ -25,6 +25,7 @@
 
 #ifdef WITH_THREAD
 #include "pythread.h"
+#include "bg_thread.h"
 static PyThread_type_lock head_mutex = NULL; /* Protects interp->tstate_head */
 #define HEAD_INIT() (void)(head_mutex || (head_mutex = PyThread_allocate_lock()))
 #define HEAD_LOCK() PyThread_acquire_lock(head_mutex, WAIT_LOCK)
@@ -79,6 +80,9 @@
         interp->codec_search_cache = NULL;
         interp->codec_error_registry = NULL;
         interp->codecs_initialized = 0;
+#ifdef WITH_LLVM
+        interp->background_thread = NULL;
+#endif  /* WITH_LLVM */
 #ifdef HAVE_DLOPEN
 #ifdef RTLD_NOW
         interp->dlopenflags = RTLD_NOW;
@@ -116,6 +120,10 @@
     Py_CLEAR(interp->modules_reloading);
     Py_CLEAR(interp->sysdict);
     Py_CLEAR(interp->builtins);
+#ifdef WITH_LLVM
+    PyBackgroundThread_Free(interp->background_thread);
+    interp->background_thread = NULL;
+#endif  /* WITH_LLVM */
 }
 
 

Added: python/branches/py3k-jit/Unittests/BgThreadTest.cc
==============================================================================
--- (empty file)
+++ python/branches/py3k-jit/Unittests/BgThreadTest.cc	Sat May 22 01:46:06 2010
@@ -0,0 +1,112 @@
+#include "Python.h"
+#include "pythread.h"
+#include "bg_thread.h"
+
+#include "gtest/gtest.h"
+#include "pytest.h"
+
+#include <deque>
+
+namespace {
+
+class BgThreadTest : public ::testing::Test {
+    PythonSetupTeardown setup_teardown_;
+};
+
+TEST_F(BgThreadTest, StartStop)
+{
+    PyThreadState *tstate = PyThreadState_GET();
+    PyBackgroundThread thread;
+    EXPECT_EQ(tstate, PyThreadState_GET());
+    thread.Start();
+    EXPECT_EQ(tstate, PyThreadState_GET());
+    thread.Terminate();
+    EXPECT_EQ(tstate, PyThreadState_GET());
+}
+
+struct TestJob : public PyBackgroundJob {
+    TestJob(int &phases_run)
+        : phases_run_(phases_run)
+    {
+    }
+
+    virtual void Run(bool shutdown)
+    {
+        phases_run_++;
+    }
+
+    virtual void Apply()
+    {
+        phases_run_++;
+    }
+
+private:
+    int &phases_run_;
+};
+
+TEST_F(BgThreadTest, PhasesRun)
+{
+    PyBackgroundThread thread;
+    thread.Start();
+    int phases_run = 0;
+    thread.RunJobAndWait(new TestJob(phases_run));
+    EXPECT_EQ(1, phases_run);
+    thread.ApplyFinishedJobs(Py_BLOCK);
+    EXPECT_EQ(2, phases_run);
+    thread.Terminate();
+}
+
+TEST_F(BgThreadTest, EvalLoopAppliesJobs)
+{
+    PyBackgroundThread thread;
+    // Tell the eval loop which PyBackgroundThread to listen to.
+    PyThreadState_GET()->interp->background_thread = &thread;
+    thread.Start();
+    int phases_run = 0;
+    thread.RunJobAndWait(new TestJob(phases_run));
+    EXPECT_EQ(1, phases_run);
+    PyRun_SimpleString("for i in range(3): i += 1");
+    EXPECT_EQ(2, phases_run);
+    thread.Terminate();
+    // Don't free the background thread in PyInterpreterState
+    // destruction though.
+    PyThreadState_GET()->interp->background_thread = NULL;
+}
+
+TEST_F(BgThreadTest, RunAndApplyAppliesJobs)
+{
+    PyBackgroundThread thread;
+    thread.Start();
+    int phases_run = 0;
+    thread.RunJobAndApply(new TestJob(phases_run));
+    EXPECT_EQ(2, phases_run);
+    thread.Terminate();
+}
+
+TEST_F(BgThreadTest, TerminationWaitsForAndAppliesActiveJobs)
+{
+    PyBackgroundThread thread;
+    thread.Start();
+    int phases_run = 0;
+    thread.RunJob(new TestJob(phases_run));
+    thread.Terminate();
+    EXPECT_EQ(2, phases_run);
+}
+
+TEST_F(BgThreadTest, AfterTerminationJobsRunImmediately)
+{
+    PyBackgroundThread thread;
+    thread.Start();
+    thread.Terminate();
+    int phases_run = 0;
+    thread.RunJob(new TestJob(phases_run));
+    EXPECT_EQ(2, phases_run);
+}
+
+TEST_F(BgThreadTest, HotFunctionStartsThread)
+{
+    PyRun_SimpleString("def hot(): pass\nfor _ in range(10**4 + 1): hot()");
+    EXPECT_TRUE(NULL != PyThreadState_GET()->interp->background_thread);
+}
+
+}  // anonymous namespace


More information about the Python-checkins mailing list