[Zope-Checkins] CVS: Zope2 - ThreadedAsync.py:1.4

jeremy@digicool.com jeremy@digicool.com
Mon, 26 Mar 2001 16:49:35 -0500 (EST)


Update of /cvs-repository/Zope2/lib/python
In directory korak:/tmp/cvs-serv1544

Modified Files:
	ThreadedAsync.py 
Log Message:
Fix bug in register_loop_callback().  If the mainloop had already
started, calls to it would fail with a NameError (map).  The fix is to
using the global _looping to hold the socket map used by the current
loop() call.

Also reformat the code and add some doc strings.



--- Updated File ThreadedAsync.py in package Zope2 --
--- ThreadedAsync.py	2000/05/16 16:59:50	1.3
+++ ThreadedAsync.py	2001/03/26 21:49:35	1.4
@@ -82,51 +82,95 @@
 # attributions are listed in the accompanying credits file.
 # 
 ##############################################################################
-"""Utility module to help manage the asyncore mainloop in a multi-threaded app
+"""Manage the asyncore mainloop in a multi-threaded app
+
+In a multi-threaded application, only a single thread runs the
+asyncore mainloop.  This thread (the "mainloop thread") may not start
+the mainloop before another thread needs to perform an async action
+that requires it.  As a result, other threads need to coordinate with
+the mainloop thread to find out whether the mainloop is running.
+
+This module implements a callback mechanism that allows other threads
+to be notified when the mainloop starts.  A thread calls
+register_loop_callback() to register interest.  When the mainloop
+thread calls loop(), each registered callback will be called with the
+socket map as its first argument.
+
+This module rebinds loop() in the asyncore module; i.e. once this
+module is imported, any client of the asyncore module will get
+ThreadedAsync.loop() when it calls asyncore.loop().
 """
 __version__='$Revision$'[11:-2]
 
-import thread
 import asyncore
+import select
+import thread
 
-_loop_lock=thread.allocate_lock()
-_looping=0
-_loop_callbacks=[]
+_loop_lock = thread.allocate_lock()
+_looping = None
+_loop_callbacks = []
 
 def register_loop_callback(callback, args=(), kw=None):
+    """Register callback function to be called when mainloop starts
+
+    The callable object callback will be invokved when the mainloop
+    starts.  If the mainloop is currently running, the callback will
+    be invoked immediately.
+
+    The callback will be called with a single argument, the mainloop
+    socket map, unless the optional args or kw arguments are used.
+    args defines a tuple of extra arguments to pass after the socket
+    map.  kw defines a dictionary of keyword arguments.
+    """
     _loop_lock.acquire()
     try:
-        if _looping: apply(callback, (map,)+args, kw or {})
-        else: _loop_callbacks.append((callback, args, kw))
-    finally: _loop_lock.release()
+        if _looping is not None:
+            apply(callback, (_looping,) + args, kw or {})
+        else:
+            _loop_callbacks.append((callback, args, kw))
+    finally:
+        _loop_lock.release()
 
 def _start_loop(map):
     _loop_lock.acquire()
     try:
         global _looping
-        _looping=1
+        _looping = map
         while _loop_callbacks:
             cb, args, kw = _loop_callbacks.pop()
-            apply(cb, (map,)+args, kw or {})
-
-    finally: _loop_lock.release()
+            apply(cb, (map,) + args, kw or {})
+    finally:
+        _loop_lock.release()
 
 def _stop_loop():
     _loop_lock.acquire()
     try:
         global _looping
-        _looping=0
-    finally: _loop_lock.release()
+        _looping = None
+    finally:
+        _loop_lock.release()
 
 def loop (timeout=30.0, use_poll=0, map=None):
+    """Invoke asyncore mainloop
 
-    if use_poll: poll_fun = asyncore.poll2
-    else:        poll_fun = asyncore.poll
+    This function functions like the regular asyncore.loop() function
+    except that it also triggers ThreadedAsync callback functions
+    before starting the loop.
+    """
+    if use_poll:
+        if hasattr(select, 'poll'):
+            poll_fun = asyncore.poll3
+        else:
+            poll_fun = asyncore.poll2
+    else:
+        poll_fun = asyncore.poll
 
-    if map is None: map=asyncore.socket_map
+    if map is None:
+        map = asyncore.socket_map
 
     _start_loop(map)
-    while map: poll_fun (timeout, map)
+    while map:
+        poll_fun(timeout, map)
     _stop_loop()
 
 # Woo hoo!