Skip to content

Commit

Permalink
Forget abou some timers (specially, about timeouts)
Browse files Browse the repository at this point in the history
Some docs
  • Loading branch information
Alvaro committed Nov 12, 2012
1 parent fcd38cc commit 7aa94eb
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 77 deletions.
2 changes: 1 addition & 1 deletion .idea/evy.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 32 additions & 15 deletions evy/hubs/hub.py
Expand Up @@ -204,6 +204,9 @@ def add (self, evtype, fileno, cb):
The *cb* argument is the callback which will be called when the file
is ready for reading/writing.
"""
if fileno < 0:
raise ValueError('invalid file descriptor: %d' % (fileno))

listener = self.lclass(evtype, fileno, cb)
bucket = self.listeners[evtype]
if fileno in bucket:
Expand Down Expand Up @@ -317,7 +320,7 @@ def wait (self, seconds = None):
:param seconds: the amount of seconds to wait
:type seconds: integer
"""
timer = Timer(self, seconds)
timer = Timer(self, seconds * 1000)
timer.start(lambda x: None)

try:
Expand Down Expand Up @@ -448,11 +451,18 @@ def squelch_timer_exception (self, timer, exc_info):
##

def add_timer (self, timer):
"""
Add a timer in the hub
:param timer:
:param unreferenced: if True, we unreference the timer, so the loop does not wait until it is triggered
:return:
"""
# scheduled_time = self.clock() + timer.seconds
# self.next_timers.append((scheduled_time, timer))
# return scheduled_time
# store the pyevent timer object so that we can cancel later
eventtimer = Timer(self, timer.seconds)
eventtimer = Timer(self, timer.seconds * 1000)
timer.impltimer = eventtimer
eventtimer.start(self.timer_finished, timer)

Expand All @@ -465,19 +475,26 @@ def timer_canceled (self, timer):
# self.next_timers = [t for t in self.next_timers if not t[1].called]
# heapq.heapify(self.timers)

#timer.impltimer.stop()
del timer.impltimer
#try:
# timer.impltimer.stop()
# del timer.impltimer
#except (AttributeError, TypeError):
# pass
#finally:
# super(Hub, self).timer_canceled(timer)
try:
#timer.impltimer.stop()
del timer.impltimer
except (AttributeError, TypeError):
pass

def timer_finished (self, timer):
pass

def forget_timer(self, timer):
"""
Let the hub forget about a timer, so we do not keep the loop running forever until
this timer triggers...
"""
try:
self.unref(timer.impltimer.handle)
except (AttributeError, TypeError):
pass


def prepare_timers (self):
heappush = heapq.heappush
t = self.timers
Expand Down Expand Up @@ -629,7 +646,7 @@ def default_loop(self):
## references
##

def ref(self):
def ref(self, handle):
"""
The event loop only runs as long as there are active watchers. This system works by having
every watcher increase the reference count of the event loop when it is started and decreasing
Expand All @@ -638,9 +655,9 @@ def ref(self):
:return: None
"""
libuv.uv_ref(self._uv_ptr)
libuv.uv_ref(handle)

def unref(self):
def unref(self, handle):
"""
This method can be used with interval timers. You might have a garbage collector which runs
every X seconds, or your network service might send a heartbeat to others periodically, but
Expand All @@ -651,7 +668,7 @@ def unref(self):
:return: None
"""
libuv.uv_unref(self._uv_ptr)
libuv.uv_unref(handle)


def now(self):
Expand Down
33 changes: 21 additions & 12 deletions evy/hubs/timer.py
Expand Up @@ -27,6 +27,8 @@
# THE SOFTWARE.
#

from functools import partial

from evy.support import greenlets as greenlet
from evy.hubs import get_hub

Expand Down Expand Up @@ -54,8 +56,11 @@ def __init__(self, seconds, cb, *args, **kw):
calling timer.schedule() or runloop.add_timer(timer).
"""
self.seconds = seconds
self.tpl = cb, args, kw

self.callback = kw.pop('callback', partial(cb, *args, **kw))

self.called = False

if _g_debug:
import traceback, cStringIO
self.traceback = cStringIO.StringIO()
Expand All @@ -67,33 +72,38 @@ def pending(self):

def __repr__(self):
secs = getattr(self, 'seconds', None)
cb, args, kw = getattr(self, 'tpl', (None, None, None))
retval = "Timer(%s, %s, *%s, **%s)" % (secs, cb, args, kw)
cb = getattr(self, 'callback', None)
retval = "Timer(%s, %s)" % (secs, cb)
if _g_debug and hasattr(self, 'traceback'):
retval += '\n' + self.traceback.getvalue()
return retval

def copy(self):
cb, args, kw = self.tpl
return self.__class__(self.seconds, cb, *args, **kw)
return self.__class__(self.seconds, callback = self.callback)

def schedule(self):
"""
Schedule this timer to run in the current runloop.
Schedule this timer to run in the current loop.
"""
self.called = False
self.scheduled_time = get_hub().add_timer(self)
return self

def forget(self):
"""
Let the hub forget about this timer, so we do not keep the loop running forever until
the timer triggers.
"""
get_hub().forget_timer(self)

def __call__(self, *args):
if not self.called:
self.called = True
cb, args, kw = self.tpl
try:
cb(*args, **kw)
self.callback()
finally:
try:
del self.tpl
del self.callback
except AttributeError:
pass

Expand All @@ -106,7 +116,7 @@ def cancel(self):
self.called = True
get_hub().timer_canceled(self)
try:
del self.tpl
del self.callback
except AttributeError:
pass

Expand Down Expand Up @@ -134,8 +144,7 @@ def __call__(self, *args):
self.called = True
if self.greenlet is not None and self.greenlet.dead:
return
cb, args, kw = self.tpl
cb(*args, **kw)
self.callback()

def cancel(self):
"""
Expand Down
27 changes: 16 additions & 11 deletions evy/timeout.py
Expand Up @@ -58,19 +58,24 @@ def __init__ (self, seconds = None, exception = None):
self.start()

def start (self):
"""Schedule the timeout. This is called on construction, so
"""
Schedule the timeout. This is called on construction, so
it should not be called explicitly, unless the timer has been
canceled."""
assert not self.pending,\
'%r is already started; to restart it, cancel it first' % self
if self.seconds is None: # "fake" timeout (never expires)
canceled.
"""
assert not self.pending, '%r is already started; to restart it, cancel it first' % self

if self.seconds is None:
# "fake" timeout (never expires)
self.timer = None
elif self.exception is None or isinstance(self.exception, bool): # timeout that raises self
self.timer = get_hub().schedule_call_global(
self.seconds, greenlet.getcurrent().throw, self)
else: # regular timeout with user-provided exception
self.timer = get_hub().schedule_call_global(
self.seconds, greenlet.getcurrent().throw, self.exception)
else:
hub = get_hub()
if self.exception is None or isinstance(self.exception, bool): # timeout that raises self
self.timer = hub.schedule_call_global(self.seconds, greenlet.getcurrent().throw, self)
else: # regular timeout with user-provided exception
self.timer = hub.schedule_call_global(self.seconds, greenlet.getcurrent().throw, self.exception)
self.timer.forget()

return self

@property
Expand Down
15 changes: 10 additions & 5 deletions evy/uv/watchers.py
Expand Up @@ -32,7 +32,7 @@

from functools import partial

from evy.uv.interface import libuv, ffi, handle_is_active
from evy.uv.interface import libuv, ffi, handle_is_active, cast_to_handle



Expand Down Expand Up @@ -68,7 +68,6 @@ def __init__(self, _hub, ref = True):

## .. and another one for stopping it
if self.libuv_stop_this_watcher:
assert self._uv_handle
self._stop_func = partial(self.libuv_stop_this_watcher, self._uv_handle)

def _run_callback(self, handle, *args):
Expand Down Expand Up @@ -177,9 +176,16 @@ def active(self):
return handle_is_active(self._uv_handle)

##
## handles (internal)
## handles
##

@property
def handle(self):
"""
Return the uv_handle for this watcher
"""
return cast_to_handle(self._uv_handle)

def _new_libuv_handle(self):
"""
Return a new libuv C handle for this watcher
Expand Down Expand Up @@ -343,8 +349,7 @@ def start(self, callback, *args, **kwargs):

self._libuv_unref()

if update:
libuv.uv_update_time(self.hub._uv_ptr)
if update: libuv.uv_update_time(self.hub._uv_ptr)

libuv.uv_timer_start(self._uv_handle, self._cb, self._after, self._repeat)

Expand Down
14 changes: 0 additions & 14 deletions tests/__init__.py
Expand Up @@ -83,20 +83,6 @@ def wrapped (*a, **kw):
return skipped_wrapper


def requires_twisted (func):
""" Decorator that skips a test if Twisted is not present."""

def requirement (_f):
from evy.hubs import get_hub

try:
return 'Twisted' in type(get_hub()).__name__
except Exception:
return False

return skip_unless(requirement)(func)


def using_pyevent (_f):
from evy.hubs import get_hub

Expand Down

0 comments on commit 7aa94eb

Please sign in to comment.