Commit ebdf3a63 authored by Tim Peters's avatar Tim Peters

Largely rewritten.

remove_loop_callback():  There were two definitions of this function.
Deleted one, changed the other to use enumerate().

poll():  This was trying to repair a bug in Python 2.2's asyncore.poll().
2.2 is no longer supported, so removed this poll().

_start_loop(), _stop_loop():  Removed; functionality folded into
reworked loop().

loop():  The signature of asyncore.loop() changed in 2.4, but this
still had "the old" signature.  Instead of sniffing the signature
of the Python in use, this does a saner thing:  the original
asyncore.loop is captured, and called from the body of this loop().
That way, (A) we don't care what asyncore.loop's signature is; and,
(B) we still call the original Python code, so debugging prints and
breakpoints (etc) stuffed into Python's asyncore no longer "vanish
by magic" when ZEO is used.
parent 80343fd8
......@@ -24,6 +24,18 @@ FileStorage.UndoSearch
very valuable when you want to spot strange transaction sizes via Zope's
'Undo' tab".
ThreadedAsync.LoopCallback
--------------------------
- This replaces Python's ``asyncore.loop`` function with its own, in order
to get notified when ``loop()`` is first called. The signature of
``asyncore.loop`` changed in Python 2.4, but ``LoopCallback.loop``'s
signature didn't change to match. The code here was repaired to be
compatible with both old and new signatures, and also repaired to invoke
Python's ``asyncore.loop()`` instead of replacing it entirely (so, for
example, debugging prints added to Python's ``asyncore.loop`` won't be
lost anymore).
What's new in ZODB3 3.4?
========================
......
......@@ -11,7 +11,7 @@
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""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
......@@ -27,27 +27,16 @@ socket map as its first argument.
"""
import asyncore
import select
import thread
import time
from errno import EINTR
_original_asyncore_loop = asyncore.loop
_loop_lock = thread.allocate_lock()
_looping = None
_looping = None # changes to socket map when loop() starts
_loop_callbacks = []
def remove_loop_callback(callback):
"""Remove a callback function registered earlier.
This is useful if loop() was never called.
"""
for i in range(len(_loop_callbacks)):
if _loop_callbacks[i][0] == callback:
del _loop_callbacks[i]
return
def register_loop_callback(callback, args=(), kw=None):
"""Register callback function to be called when mainloop starts
"""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
......@@ -72,15 +61,21 @@ def remove_loop_callback(callback):
This is useful if loop() was never called.
"""
for i in range(len(_loop_callbacks)):
if _loop_callbacks[i][0] == callback:
for i, value in enumerate(_loop_callbacks):
if value[0] == callback:
del _loop_callbacks[i]
return
def _start_loop(map):
# Caution: the signature of asyncore.loop changed in Python 2.4.
# That's why we use `args` and `kws` instead of spelling out the
# "intended" arguments. Since we _replace_ asyncore.loop with this
# loop(), we need to be compatible with all signatures.
def loop(*args, **kws):
global _looping
map = kws.get("map", asyncore.socket_map)
_loop_lock.acquire()
try:
global _looping
_looping = map
while _loop_callbacks:
cb, args, kw = _loop_callbacks.pop()
......@@ -88,96 +83,24 @@ def _start_loop(map):
finally:
_loop_lock.release()
def _stop_loop():
result = _original_asyncore_loop(*args, **kws)
_loop_lock.acquire()
try:
global _looping
_looping = None
finally:
_loop_lock.release()
def poll(timeout=0.0, map=None):
"""A copy of asyncore.poll() with a bug fixed (see comment).
(asyncore.poll2() and .poll3() don't have this bug.)
"""
if map is None:
map = asyncore.socket_map
if map:
r = []; w = []; e = []
for fd, obj in map.items():
if obj.readable():
r.append(fd)
if obj.writable():
w.append(fd)
if [] == r == w == e:
time.sleep(timeout)
else:
try:
r, w, e = select.select(r, w, e, timeout)
except select.error, err:
if err[0] != EINTR:
raise
else:
# This part is missing in asyncore before Python 2.3
return
return result
for fd in r:
obj = map.get(fd)
if obj is not None:
try:
obj.handle_read_event()
except asyncore.ExitNow:
raise asyncore.ExitNow
except:
obj.handle_error()
for fd in w:
obj = map.get(fd)
if obj is not None:
try:
obj.handle_write_event()
except asyncore.ExitNow:
raise asyncore.ExitNow
except:
obj.handle_error()
def loop(timeout=30.0, use_poll=0, map=None):
"""Invoke asyncore mainloop
This function functions like the regular asyncore.loop() function
except that it also triggers ThreadedAsync callback functions
before starting the loop.
"""
global exit_status
exit_status = None
if use_poll:
if hasattr(select, 'poll'):
poll_fun = asyncore.poll3
else:
poll_fun = asyncore.poll2
else:
poll_fun = poll
if map is None:
map = asyncore.socket_map
_start_loop(map)
while map and exit_status is None:
poll_fun(timeout, map)
_stop_loop()
# This module used to do something evil -- it rebound asyncore.loop to the
# above loop() function. What was evil about this is that if you added some
# debugging to asyncore.loop, you'd spend 6 hours debugging why your debugging
# code wasn't called!
# Evil: rebind asyncore.loop to the above loop() function.
#
# Code should instead explicitly call ThreadedAsync.loop() instead of
# asyncore.loop(). Most of ZODB has been fixed, but ripping this out may
# break 3rd party code. So we'll issue a warning and let it continue -- for
# now.
# Code should explicitly call ThreadedAsync.loop() instead of asyncore.loop().
# Most of ZODB has been fixed, but ripping this out may break 3rd party code.
# Maybe we should issue a warning and let it continue for a while. Or
# maybe we should get rid of this mechanism entirely, and have each ZEO
# piece that needs one run its own asyncore loop in its own thread.
##def deprecated_loop(*args, **kws):
## import warnings
......@@ -186,7 +109,7 @@ def loop(timeout=30.0, use_poll=0, map=None):
##You should change your code to call ThreadedAsync.loop() explicitly.""",
## DeprecationWarning)
## loop(*args, **kws)
##
##asyncore.loop = deprecated_loop
asyncore.loop = loop
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment