Permalink
Browse files

Added support for Tulip mainloop

Tulip is a pure-Python mainloop for Python >= 3.3. With Tulip, there is no
need for python-gobject.

Tulip is currently experimental, but as it is only used when gobject is not
present, supporting it shouldn't do any harm.
  • Loading branch information...
1 parent 4b245f1 commit 2b13b69ace187d46746058679a347ca63b3e172f @talex5 talex5 committed Mar 9, 2013
View
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+from basetest import BaseTest
+import unittest
+import os, sys
+
+sys.path.insert(0, '..')
+from zeroinstall.support import tasks
+
+# Most of tasks.py is heavily tested by the rest of the code, but some bits aren't.
+class TestTasks(BaseTest):
+ def testInputBlocker(self):
+ r, w = os.pipe()
+ b = tasks.InputBlocker(r, "waiting for input")
+ t = tasks.TimeoutBlocker(0.01, "timeout")
+
+ @tasks.async
+ def run():
+ yield b, t
+ assert t.happened
+ assert not b.happened
+
+ os.write(w, b"!")
+
+ yield b
+ assert b.happened
+
+ os.close(r)
+ os.close(w)
+
+ tasks.wait_for_blocker(run())
+
+if __name__ == '__main__':
+ unittest.main()
View
@@ -20,10 +20,11 @@
try:
from gi.repository import GObject as gobject
except ImportError:
- import gobject
+ gobject = None
else:
import gobject
-gobject.threads_init()
+if gobject:
+ gobject.threads_init()
logger = logging.getLogger('0install')
@@ -6,7 +6,7 @@
# Copyright (C) 2009, Thomas Leonard
# See the README file for details, or visit http://0install.net.
-from zeroinstall import _, logger
+from zeroinstall import _, logger, gobject
import os, platform, re, subprocess, sys
from zeroinstall.injector import namespaces, model, arch, qdom
from zeroinstall.support import basedir, portable_rename, intern
@@ -234,9 +234,8 @@ def factory(id, only_if_missing = False, installed = True):
impl.upstream_stability = model.packaged
impl.machine = host_machine # (hopefully)
feed.implementations[impl_id] = impl
- elif master_feed.url == 'http://repo.roscidus.com/python/python-gobject' and os.name != "nt":
+ elif master_feed.url == 'http://repo.roscidus.com/python/python-gobject' and os.name != "nt" and gobject:
# Likewise, we know that there is a native python-gobject available for our Python
- from zeroinstall import gobject
impl_id = 'package:host:python-gobject:' + '.'.join(str(x) for x in gobject.pygobject_version)
assert impl_id not in feed.implementations
impl = model.DistributionImplementation(feed, impl_id, self, distro_name = 'host')
@@ -200,7 +200,6 @@ class ConsoleHandler(Handler):
original_print = None
def downloads_changed(self):
- from zeroinstall import gobject
if self.monitored_downloads and self.update is None:
if self.screen_width is None:
try:
@@ -213,20 +212,20 @@ def downloads_changed(self):
self.show_progress()
self.original_print = print
builtins.print = self.print
- self.update = gobject.timeout_add(200, self.show_progress)
+ self.update = tasks.loop.call_repeatedly(0.2, self.show_progress)
elif len(self.monitored_downloads) == 0:
if self.update:
- gobject.source_remove(self.update)
+ self.update.cancel()
self.update = None
builtins.print = self.original_print
self.original_print = None
self.clear_display()
def show_progress(self):
- if not self.monitored_downloads: return True
+ if not self.monitored_downloads: return
urls = [(dl.url, dl) for dl in self.monitored_downloads]
- if self.disable_progress: return True
+ if self.disable_progress: return
screen_width = self.screen_width - 2
item_width = max(16, screen_width // len(self.monitored_downloads))
@@ -256,7 +255,7 @@ def show_progress(self):
self.last_msg_len = len(msg)
sys.stdout.flush()
- return True
+ return
def clear_display(self):
if self.last_msg_len != None:
@@ -17,7 +17,7 @@
from collections import defaultdict
import threading
-from zeroinstall import gobject, logger
+from zeroinstall import logger
from zeroinstall.support import tasks
from zeroinstall.injector import download
@@ -107,7 +107,7 @@ def wake_up_main():
child.join()
thread_blocker.trigger(ex)
return False
- gobject.idle_add(wake_up_main)
+ tasks.loop.call_soon_threadsafe(wake_up_main)
child = threading.Thread(target = lambda: download_in_thread(step.url, step.dl.tempfile, step.dl.modification_time, notify_done))
child.daemon = True
child.start()
@@ -35,9 +35,72 @@
# Copyright (C) 2009, Thomas Leonard
# See the README file for details, or visit http://0install.net.
-from zeroinstall import _, support, gobject, logger
+from zeroinstall import _, support, logger, gobject
import sys
+if gobject:
+ class _Handler:
+ def cancel(self):
+ if self.tag is not None:
+ gobject.source_remove(self.tag)
+ self.tag = None
+
+ class loop:
+ @staticmethod
+ def call_soon_threadsafe(cb):
+ def wrapper():
+ cb()
+ return False
+ gobject.idle_add(wrapper)
+
+ call_soon = call_soon_threadsafe
+
+ @staticmethod
+ def call_repeatedly(interval, cb):
+ h = _Handler()
+ def wrapper():
+ assert h.tag is not None
+ cb()
+ return True
+ h.tag = gobject.timeout_add(int(interval * 1000), wrapper)
+ return h
+
+ @staticmethod
+ def call_later(delay, cb):
+ def wrapper():
+ cb()
+ return False
+ gobject.timeout_add(int(delay * 1000), wrapper)
+
+ @staticmethod
+ def add_reader(fd, cb, *args):
+ h = _Handler()
+ def wrapper(src, cond):
+ cb(*args)
+ return True
+ h.tag = gobject.io_add_watch(fd, gobject.IO_IN | gobject.IO_HUP, wrapper)
+ return h
+
+ @staticmethod
+ def add_writer(fd, cb, *args):
+ h = _Handler()
+ def wrapper(src, cond):
+ cb(*args)
+ return True
+ h.tag = gobject.io_add_watch(fd, gobject.IO_OUT | gobject.IO_HUP, wrapper)
+ return h
+else:
+ try:
+ import tulip
+ except ImportError:
+ # Delay the error until we actually need a mainloop
+ class Fail:
+ def __getattr__(self, x):
+ raise Exception("No mainloop available: install python3-gi or tulip")
+ tulip = loop = Fail()
+ else:
+ loop = tulip.get_event_loop()
+
# The list of Blockers whose event has happened, in the order they were
# triggered
_run_queue = []
@@ -162,14 +225,14 @@ class TimeoutBlocker(Blocker):
def __init__(self, timeout, name):
"""Trigger after 'timeout' seconds (may be a fraction)."""
Blocker.__init__(self, name)
- gobject.timeout_add(int(timeout * 1000), self._timeout)
+ loop.call_later(timeout, self._timeout)
def _timeout(self):
self.trigger()
-def _io_callback(src, cond, blocker):
+def _io_callback(blocker):
+ blocker._tag.cancel()
blocker.trigger()
- return False
class InputBlocker(Blocker):
"""Triggers when os.read(stream) would not block."""
@@ -182,15 +245,15 @@ def __init__(self, stream, name):
def add_task(self, task):
Blocker.add_task(self, task)
if self._tag is None:
- self._tag = gobject.io_add_watch(self._stream, gobject.IO_IN | gobject.IO_HUP,
- _io_callback, self)
+ self._tag = loop.add_reader(self._stream, _io_callback, self)
def remove_task(self, task):
Blocker.remove_task(self, task)
if not self._zero_lib_tasks:
- gobject.source_remove(self._tag)
+ self._tag.cancel()
self._tag = None
+# Note: this isn't used within 0install
class OutputBlocker(Blocker):
"""Triggers when os.write(stream) would not block."""
_tag = None
@@ -202,13 +265,12 @@ def __init__(self, stream, name):
def add_task(self, task):
Blocker.add_task(self, task)
if self._tag is None:
- self._tag = gobject.io_add_watch(self._stream, gobject.IO_OUT | gobject.IO_HUP,
- _io_callback, self)
+ self._tag = loop.add_writer(self._stream, _io_callback, self)
def remove_task(self, task):
Blocker.remove_task(self, task)
if not self._zero_lib_tasks:
- gobject.source_remove(self._tag)
+ self._tag.cancel()
self._tag = None
_idle_blocker = IdleBlocker("(idle)")
@@ -296,7 +358,7 @@ def __str__(self):
# Must append to _run_queue right after calling this!
def _schedule():
assert not _run_queue
- gobject.idle_add(_handle_run_queue)
+ loop.call_soon(_handle_run_queue)
def _handle_run_queue():
global _idle_blocker
@@ -325,8 +387,7 @@ def _handle_run_queue():
del _run_queue[0]
if _run_queue:
- return True
- return False
+ loop.call_soon(_handle_run_queue)
def named_async(name):
"""Decorator that turns a generator function into a function that runs the
@@ -353,22 +414,28 @@ def wait_for_blocker(blocker):
@type blocker: L{Blocker}
@since: 0.53
"""
- assert wait_for_blocker.loop is None # Avoid recursion
+ assert wait_for_blocker.x is None # Avoid recursion
if not blocker.happened:
def quitter():
yield blocker
- wait_for_blocker.loop.quit()
+ if gobject:
+ wait_for_blocker.x.quit()
+ else:
+ wait_for_blocker.x.set_result(None)
Task(quitter(), "quitter")
- wait_for_blocker.loop = gobject.MainLoop()
+ wait_for_blocker.x = gobject.MainLoop() if gobject else tulip.Future()
try:
logger.debug(_("Entering mainloop, waiting for %s"), blocker)
- wait_for_blocker.loop.run()
+ if gobject:
+ wait_for_blocker.x.run()
+ else:
+ loop.run_until_complete(wait_for_blocker.x)
finally:
- wait_for_blocker.loop = None
+ wait_for_blocker.x = None
assert blocker.happened, "Someone quit the main loop!"
check(blocker)
-wait_for_blocker.loop = None
+wait_for_blocker.x = None

0 comments on commit 2b13b69

Please sign in to comment.