├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── NOTICE ├── README.rst ├── THANKS ├── UNLICENSE ├── examples ├── actor_example.py ├── echo_client.py ├── echo_server.py ├── echo_sockserver.py ├── hello.py ├── stackless_scheduler_thread.py └── stackless_threadsafe_channels.py ├── flower ├── __init__.py ├── actor.py ├── core │ ├── __init__.py │ ├── channel.py │ ├── sched.py │ ├── sync.c │ ├── timer.py │ ├── util.py │ └── uv.py ├── io.py ├── local.py ├── net │ ├── __init__.py │ ├── base.py │ ├── pipe.py │ ├── sock.py │ ├── tcp.py │ ├── udp.py │ └── util.py ├── registry.py ├── time.py └── util.py ├── requirements.txt ├── requirements_dev.txt ├── setup.py ├── test ├── test_actor.py ├── test_channel.py ├── test_io.py ├── test_local.py ├── test_registry.py ├── test_scheduler.py ├── test_sync.py ├── test_time.py └── test_timer.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.swp 3 | *.pyc 4 | *#* 5 | build 6 | dist 7 | setuptools-* 8 | .svn/* 9 | .DS_Store 10 | *.so 11 | .Python 12 | distribute-0.6.8-py2.6.egg 13 | distribute-0.6.8.tar.gz 14 | *.egg-info 15 | nohup.out 16 | .coverage 17 | doc/.sass-cache 18 | bin/ 19 | lib/ 20 | man/ 21 | include/ 22 | html/ 23 | .tox 24 | htmlcov 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | - "2.7" 5 | - "3.1" 6 | - "3.2" 7 | install: 8 | - pip install -r requirements_dev.txt --use-mirrors 9 | - python setup.py install 10 | script: py.test 11 | branches: 12 | only: 13 | - master 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2012 (c) Benoît Chesneau 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include NOTICE 2 | include LICENSE 3 | include README.rst 4 | include THANKS 5 | include UNLICENSE 6 | recursive-include examples * 7 | recursive-include tests * 8 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | flower 2 | ------ 3 | 4 | 2012 (c) Benoît Chesneau 5 | 6 | flower is available in the public domain (see UNLICENSE). flower 7 | is also optionally available under the MIT License (see LICENSE), meant 8 | especially for jurisdictions that do not recognize public domain works. 9 | 10 | 11 | flower.stackless 12 | ---------------- 13 | 14 | based on pypy stackless implementation under MIT License. 15 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | ========================================================================================== 4 | 5 | 6 | 7 | 8 | **DEPPRECATED** Flower is now superseded by `Offset `_ . 9 | 10 | 11 | 12 | ========================================================================================== 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Flower 22 | ====== 23 | 24 | .. image:: https://secure.travis-ci.org/benoitc/flower.png?branch=master 25 | :target: http://travis-ci.org/benoitc/flower 26 | 27 | collection of modules to build distributed and reliable concurrent 28 | systems in Python. 29 | 30 | :: 31 | 32 | a = “” 33 | 34 | def f(): 35 | print(a) 36 | 37 | def hello(): 38 | a = "hello, world" 39 | tasklet(f)() 40 | 41 | Requirements 42 | ------------ 43 | 44 | - Python from 2.6 to 3.x 45 | - A platform supported by `libuv ` 46 | 47 | Simple example 48 | -------------- 49 | 50 | A simple example showing how to create a simple echo server. 51 | 52 | .. code-block:: python 53 | 54 | # Echo server program 55 | from flower import tasklet, run 56 | from flower.net import Listen 57 | 58 | 59 | # handle the connection. It return data to the sender. 60 | def handle_connection(conn): 61 | while True: 62 | data = conn.read() 63 | if not data: 64 | break 65 | 66 | conn.write(data) 67 | 68 | 69 | # Listen on tcp port 8000 on localhost 70 | l = Listen(('127.0.0.1', 8000), "tcp") 71 | try: 72 | while True: 73 | try: 74 | 75 | # wait for new connections (it doesn't block other tasks) 76 | conn, err = l.accept() 77 | 78 | # Handle the connection in a new task. 79 | # The loop then returns to accepting, so that 80 | # multiple connections may be served concurrently. 81 | 82 | t = tasklet(handle_connection)(conn) 83 | except KeyboardInterrupt: 84 | break 85 | finally: 86 | l.close() 87 | 88 | run() 89 | 90 | 91 | And the echo client:: 92 | 93 | from flower import tasklet, run, schedule 94 | from flower.net import Dial 95 | 96 | 97 | # connect to the remote server 98 | conn = Dial("tcp", ('127.0.0.1', 8000)) 99 | 100 | # start to handle the connection 101 | # we send a string to the server and fetch the 102 | # response back 103 | 104 | for i in range(3): 105 | msg = "hello" 106 | print("sent %s" % msg) 107 | resp = conn.write(msg) 108 | ret = conn.read() 109 | print("echo: %s" % ret) 110 | 111 | conn.close() 112 | run() 113 | 114 | 115 | Installation 116 | ------------ 117 | 118 | Flower requires Python superior to 2.6 (yes Python 3 is supported) 119 | 120 | To install flower using pip you must make sure you have a 121 | recent version of distribute installed:: 122 | 123 | $ curl -O http://python-distribute.org/distribute_setup.py 124 | $ sudo python distribute_setup.py 125 | $ easy_install pip 126 | 127 | 128 | For now flower can only be installed from sources. To install or upgrade to the latest released version of flower:: 129 | 130 | $ git clone https://github.com/benoitc/flower.git 131 | $ cd flower && pip install -r requirements.txt 132 | 133 | License 134 | ------- 135 | 136 | flower is available in the public domain (see UNLICENSE). flower is also 137 | optionally available under the MIT License (see LICENSE), meant 138 | especially for jurisdictions that do not recognize public domain 139 | works. 140 | -------------------------------------------------------------------------------- /THANKS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/flower/fca53d83bc067e669215cf37a94ea1269108499d/THANKS -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /examples/actor_example.py: -------------------------------------------------------------------------------- 1 | from flower import spawn, receive, send, run 2 | 3 | messages = [] 4 | sources = [] 5 | def consumer(): 6 | # wait for coming message in the current actor 7 | while True: 8 | source, msg = receive() 9 | if not msg: 10 | break 11 | print("got message from %s: %s" % (source.ref, msg)) 12 | 13 | def publisher1(ref): 14 | # an actor sending messages to the consumer 15 | msg = ['hello', 'world'] 16 | for s in msg: 17 | send(ref, s) 18 | 19 | def publisher2(ref): 20 | msg = ['brave', 'new', 'world', ''] 21 | for s in msg: 22 | send(ref, s) 23 | 24 | ref_consumer = spawn(consumer) 25 | spawn(publisher1, ref_consumer) 26 | spawn(publisher2, ref_consumer) 27 | 28 | run() 29 | -------------------------------------------------------------------------------- /examples/echo_client.py: -------------------------------------------------------------------------------- 1 | from flower import tasklet, run, schedule 2 | from flower.net import Dial 3 | 4 | 5 | # connect to the remote server 6 | conn = Dial("tcp", ('127.0.0.1', 8000)) 7 | 8 | # start to handle the connection 9 | # we send a string to the server and fetch the 10 | # response back 11 | 12 | for i in range(3): 13 | msg = "hello" 14 | print("sent %s" % msg) 15 | resp = conn.write(msg) 16 | ret = conn.read() 17 | print("echo: %s" % ret) 18 | 19 | conn.close() 20 | run() 21 | -------------------------------------------------------------------------------- /examples/echo_server.py: -------------------------------------------------------------------------------- 1 | # Echo server program 2 | from flower import tasklet, run 3 | from flower.net import Listen 4 | 5 | 6 | # handle the connection. It return data to the sender. 7 | def handle_connection(conn): 8 | while True: 9 | data = conn.read() 10 | if not data: 11 | break 12 | 13 | conn.write(data) 14 | 15 | 16 | # Listen on tcp port 8000 on localhost 17 | l = Listen(('127.0.0.1', 8000), "tcp") 18 | try: 19 | while True: 20 | try: 21 | 22 | # wait for new connections (it doesn't block other tasks) 23 | conn, err = l.accept() 24 | 25 | # Handle the connection in a new task. 26 | # The loop then returns to accepting, so that 27 | # multiple connections may be served concurrently. 28 | 29 | t = tasklet(handle_connection)(conn) 30 | except KeyboardInterrupt: 31 | break 32 | finally: 33 | l.close() 34 | 35 | run() 36 | -------------------------------------------------------------------------------- /examples/echo_sockserver.py: -------------------------------------------------------------------------------- 1 | # Echo server program 2 | from flower import tasklet, run 3 | from flower.net import Listen 4 | 5 | 6 | # handle the connection. It return data to the sender. 7 | def handle_connection(conn): 8 | while True: 9 | data = conn.read() 10 | if not data: 11 | break 12 | 13 | conn.write(data) 14 | 15 | 16 | # Listen on tcp port 8000 on localhost using the python socket module. 17 | l = Listen(('127.0.0.1', 8000), "socktcp") 18 | try: 19 | while True: 20 | try: 21 | 22 | # wait for new connections (it doesn't block other tasks) 23 | conn, err = l.accept() 24 | 25 | # Handle the connection in a new task. 26 | # The loop then returns to accepting, so that 27 | # multiple connections may be served concurrently. 28 | 29 | t = tasklet(handle_connection)(conn) 30 | except KeyboardInterrupt: 31 | break 32 | finally: 33 | l.close() 34 | 35 | run() 36 | -------------------------------------------------------------------------------- /examples/hello.py: -------------------------------------------------------------------------------- 1 | from flower import run, schedule, tasklet 2 | 3 | def say(s): 4 | for i in range(5): 5 | schedule() 6 | print(s) 7 | 8 | def main(): 9 | tasklet(say)("world") 10 | say("hello") 11 | 12 | run() 13 | 14 | if __name__ == '__main__': 15 | main() 16 | -------------------------------------------------------------------------------- /examples/stackless_scheduler_thread.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from flower import schedule, run, getruncount, schedule, tasklet 3 | 4 | try: 5 | from thread import get_ident 6 | except ImportError: #python 3 7 | from _thread import get_ident 8 | 9 | def secondary_thread_func(): 10 | runcount = getruncount() 11 | print("THREAD(2): Has", runcount, "tasklets in its scheduler") 12 | 13 | def main_thread_func(): 14 | print("THREAD(1): Waiting for death of THREAD(2)") 15 | while thread.is_alive(): 16 | schedule() 17 | print("THREAD(1): Death of THREAD(2) detected") 18 | 19 | mainThreadTasklet = tasklet(main_thread_func)() 20 | 21 | thread = threading.Thread(target=secondary_thread_func) 22 | thread.start() 23 | 24 | print("we are in %s" % get_ident()) 25 | run() 26 | -------------------------------------------------------------------------------- /examples/stackless_threadsafe_channels.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import sys 3 | if sys.version_info[0] <= 2: 4 | import thread 5 | else: 6 | import _thread as thread 7 | 8 | 9 | import flower 10 | 11 | commandChannel = flower.channel() 12 | 13 | def master_func(): 14 | commandChannel.send("ECHO 1") 15 | commandChannel.send("ECHO 2") 16 | commandChannel.send("ECHO 3") 17 | commandChannel.send("QUIT") 18 | 19 | def slave_func(): 20 | print("SLAVE STARTING") 21 | while 1: 22 | command = commandChannel.receive() 23 | print("SLAVE:", command) 24 | if command == "QUIT": 25 | break 26 | print("SLAVE ENDING") 27 | 28 | def scheduler_run(tasklet_func): 29 | t = flower.tasklet(tasklet_func)() 30 | while t.alive: 31 | flower.run() 32 | 33 | th = threading.Thread(target=scheduler_run, args=(master_func,)) 34 | th.start() 35 | 36 | scheduler_run(slave_func) 37 | -------------------------------------------------------------------------------- /flower/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | 6 | from flower.core import ( 7 | # tasks functions 8 | tasklet, get_scheduler, getruncount, getcurrent, getmain, 9 | set_schedule_callback, schedule, schedule_remove, run, 10 | 11 | # channel functions 12 | channel, bomb, set_channel_callback) 13 | 14 | 15 | from flower.actor import (spawn, spawn_link, spawn_after, send, 16 | send_after, receive, flush) 17 | -------------------------------------------------------------------------------- /flower/actor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | from collections import deque 6 | import inspect 7 | import operator 8 | import sys 9 | import threading 10 | import weakref 11 | 12 | import six 13 | 14 | from flower import core 15 | from flower.registry import registry 16 | from flower.time import sleep, after_func 17 | 18 | 19 | def self(): 20 | return core.getcurrent() 21 | 22 | 23 | class ActorRef(object): 24 | 25 | __slots__ = ['ref', '_actor_ref', 'is_alive', '__dict__', 'actor'] 26 | 27 | __shared_state__ = dict( 28 | _ref_count = 0 29 | ) 30 | 31 | def __init__(self, actor): 32 | self.__dict__ = self.__shared_state__ 33 | self._actor_ref = weakref.ref(actor) 34 | 35 | # increment the ref counter 36 | with threading.RLock(): 37 | self.ref = self._ref_count 38 | self._ref_count += 1 39 | 40 | def __str__(self): 41 | return "" % self.ref 42 | 43 | @property 44 | def actor(self): 45 | return self._actor_ref() 46 | 47 | @property 48 | def is_alive(self): 49 | return self.actor is not None 50 | 51 | class Message(object): 52 | 53 | def __init__(self, source, dest, msg): 54 | self.source = source 55 | self.dest = dest 56 | self.msg = msg 57 | 58 | def send(self): 59 | target = self.dest.actor 60 | if not target: 61 | registry.unregister(self.dest) 62 | return 63 | 64 | target.send((self.source.ref, self.msg)) 65 | 66 | def send_after(self, seconds): 67 | after_func(seconds, self.send) 68 | 69 | 70 | 71 | class Mailbox(object): 72 | """ a mailbox can accept any message from other actors. 73 | This is different from a channel since it doesn't block the sender. 74 | 75 | Each actors have an attached mailbox used to send him some any 76 | messages. 77 | """ 78 | 79 | def __init__(self): 80 | self.messages = deque() 81 | self.channel = core.channel() 82 | self._lock = threading.RLock() 83 | 84 | def send(self, msg): 85 | """ append a message to the queue or if the actor is accepting 86 | messages send it directly to it """ 87 | 88 | if self.channel is not None and self.channel.balance < 0: 89 | self.channel.send(msg) 90 | else: 91 | # no waiters append to the queue and return 92 | self.messages.append(msg) 93 | return 94 | 95 | def receive(self): 96 | """ fetch a message from the queue or wait for a new one """ 97 | try: 98 | return self.messages.popleft() 99 | except IndexError: 100 | pass 101 | return self.channel.receive() 102 | 103 | def flush(self): 104 | with self._lock: 105 | while True: 106 | try: 107 | yield self.messages.popleft() 108 | except IndexError: 109 | raise StopIteration 110 | 111 | def clear(self): 112 | self.messages.clear() 113 | 114 | class Actor(core.tasklet): 115 | 116 | """ An actor is like a tasklet but with a mailbox. """ 117 | 118 | def __init__(self): 119 | core.tasklet.__init__(self) 120 | self.ref = ActorRef(self) 121 | self.links = [] 122 | self.mailbox = Mailbox() 123 | 124 | @classmethod 125 | def spawn(cls, func, *args, **kwargs): 126 | instance = cls() 127 | 128 | # wrap func to be scheduled immediately 129 | def _func(): 130 | func(*args, **kwargs) 131 | sleep(0.0) 132 | instance.bind(_func) 133 | instance.setup() 134 | return instance.ref 135 | 136 | @classmethod 137 | def spawn_link(cls, func, *args, **kwargs): 138 | curr = core.getcurrent() 139 | if not hasattr(curr, 'mailbox'): 140 | curr = cls.wrap(curr) 141 | 142 | if operator.indexOf(self.links, curr.ref) < 0: 143 | self.links.append(curr.ref) 144 | 145 | return cls.spawn(func, *args, **kwargs) 146 | 147 | @classmethod 148 | def spawn_after(cls, seconds, func, *args, **kwargs): 149 | instance = cls() 150 | 151 | # wrap func to be scheduled immediately 152 | def _func(): 153 | func(*args, **kwargs) 154 | sleep(0.0) 155 | 156 | def _deferred_spawn(): 157 | instance.bind(_func) 158 | instance.setup() 159 | 160 | after_func(seconds, _deferred_spawn) 161 | return instance.ref 162 | 163 | def unlink(self, ref): 164 | idx = operator.indexOf(self.links, ref) 165 | if idx < 0: 166 | return 167 | with self._lock: 168 | del self.links[idx] 169 | 170 | @classmethod 171 | def wrap(cls, task): 172 | """ method to wrap a task to an actor """ 173 | 174 | if hasattr(task, 'mailbox'): 175 | return 176 | 177 | actor = cls() 178 | task.__class__ = Actor 179 | for n, m in inspect.getmembers(actor): 180 | if not hasattr(task, n): 181 | setattr(task, n, m) 182 | 183 | setattr(task, 'mailbox', actor.mailbox) 184 | setattr(task, 'ref', actor.ref) 185 | setattr(task, 'links', actor.links) 186 | return task 187 | 188 | def send(self, msg): 189 | self.mailbox.send(msg) 190 | 191 | def receive(self): 192 | return self.mailbox.receive() 193 | 194 | def flush(self): 195 | return self.mailbox.flush() 196 | 197 | 198 | 199 | spawn = Actor.spawn 200 | spawn_link = Actor.spawn_link 201 | spawn_after = Actor.spawn_after 202 | wrap = Actor.wrap 203 | 204 | def maybe_wrap(actor): 205 | if not hasattr(actor, 'mailbox'): 206 | return wrap(actor) 207 | return actor 208 | 209 | def send(dest, msg): 210 | """ send a message to the destination """ 211 | source = maybe_wrap(core.getcurrent()) 212 | 213 | if isinstance(dest, six.string_types): 214 | dest = registry[dest] 215 | elif isinstance(dest, core.tasklet): 216 | dest = maybe_wrap(dest) 217 | 218 | mail = Message(source, dest, msg) 219 | mail.send() 220 | 221 | def send_after(seconds, dest, msg): 222 | """ send a message after n seconds """ 223 | 224 | if not seconds: 225 | return send(dest, msg) 226 | 227 | source = maybe_wrap(core.getcurrent()) 228 | if isinstance(dest, six.string_types): 229 | dest = registry[dest] 230 | elif isinstance(dest, core.tasklet): 231 | dest = maybe_wrap(dest) 232 | 233 | mail = Message(source, dest, msg) 234 | mail.send_after(seconds) 235 | 236 | def receive(): 237 | curr = maybe_wrap(core.getcurrent()) 238 | return curr.receive() 239 | 240 | def flush(): 241 | curr = maybe_wrap(core.getcurrent()) 242 | curr.flush() 243 | -------------------------------------------------------------------------------- /flower/core/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | from flower.core.sched import (tasklet, get_scheduler, getruncount, 6 | getcurrent, getmain, set_schedule_callback, schedule, 7 | schedule_remove, run, taskwakeup) 8 | 9 | from flower.core.channel import (bomb, channel, set_channel_callback) 10 | 11 | 12 | def defer(func): 13 | """ A "defer" function invokes a function whose execution is 14 | deferred to the moment the surrounding function returns. """ 15 | return lambda *args, **kwargs: func(*args, **kwargs) 16 | -------------------------------------------------------------------------------- /flower/core/channel.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | from collections import deque 6 | import sys 7 | import threading 8 | 9 | import six 10 | 11 | from flower.core.sched import ( 12 | schedule, getcurrent, get_scheduler, 13 | schedrem, thread_ident 14 | ) 15 | 16 | class bomb(object): 17 | def __init__(self, exp_type=None, exp_value=None, exp_traceback=None): 18 | self.type = exp_type 19 | self.value = exp_value 20 | self.traceback = exp_traceback 21 | 22 | def raise_(self): 23 | six.reraise(self.type, self.value, self.traceback) 24 | 25 | class ChannelWaiter(object): 26 | 27 | __slots__ = ['scheduler', 'task', 'thread_id', 'arg'] 28 | 29 | def __init__(self, task, scheduler, arg): 30 | self.task = task 31 | self.scheduler = scheduler 32 | self.arg = arg 33 | self.thread_id = thread_ident() 34 | 35 | def __str__(self): 36 | "waiter: %s" % str(self.task) 37 | 38 | class channel(object): 39 | """ 40 | A channel provides a mechanism for two concurrently executing 41 | functions to synchronize execution and communicate by passing a 42 | value of a specified element type. A channel is the only thread-safe 43 | operation. 44 | 45 | The capacity, in number of elements, sets the size of the buffer in 46 | the channel. If the capacity is greater than zero, the channel is 47 | asynchronous: communication operations succeed without blocking if 48 | the buffer is not full (sends) or not empty (receives), and elements 49 | are received in the order they are sent. If the capacity is zero or 50 | absent, the communication succeeds only when both a sender and 51 | receiver are ready. 52 | """ 53 | 54 | 55 | def __init__(self, capacity=None, label=''): 56 | self.capacity = capacity 57 | self.closing = False 58 | self.recvq = deque() 59 | self.sendq = deque() 60 | self._lock = threading.Lock() 61 | self.label = label 62 | self.preference = -1 63 | self.schedule_all = False 64 | 65 | def __str__(self): 66 | return 'channel[%s](%s,%s)' % (self.label, self.balance, self.queue) 67 | 68 | def close(self): 69 | """ 70 | channel.close() -- stops the channel from enlarging its queue. 71 | 72 | If the channel is not empty, the flag 'closing' becomes true. 73 | If the channel is empty, the flag 'closed' becomes true. 74 | """ 75 | self.closing = True 76 | 77 | @property 78 | def balance(self): 79 | return len(self.sendq) - len(self.recvq) 80 | 81 | @property 82 | def closed(self): 83 | return self.closing and not self.queue 84 | 85 | def open(self): 86 | """ 87 | channel.open() -- reopen a channel. See channel.close. 88 | """ 89 | self.closing = False 90 | 91 | def enqueue(self, d, waiter): 92 | if d > 0: 93 | return self.sendq.append(waiter) 94 | else: 95 | return self.recvq.append(waiter) 96 | 97 | def dequeue(self, d): 98 | if d > 0: 99 | return self.recvq.popleft() 100 | else: 101 | return self.sendq.popleft() 102 | 103 | def _channel_action(self, arg, d): 104 | """ 105 | d == -1 : receive 106 | d == 1 : send 107 | 108 | the original CStackless has an argument 'stackl' which is not used 109 | here. 110 | 111 | 'target' is the peer tasklet to the current one 112 | """ 113 | 114 | assert abs(d) == 1 115 | 116 | do_schedule = False 117 | curr = getcurrent() 118 | source = ChannelWaiter(curr, get_scheduler(), arg) 119 | 120 | if d > 0: 121 | if not self.capacity: 122 | cando = self.balance < 0 123 | else: 124 | cando = len(self.recvq) <= self.capacity 125 | dir = d 126 | else: 127 | if not self.capacity: 128 | cando = self.balance > 0 129 | else: 130 | cando = len(self.sendq) <= self.capacity 131 | dir = 0 132 | 133 | if _channel_callback is not None: 134 | with self._lock: 135 | _channel_callback(self, getcurrent(), dir, not cando) 136 | 137 | if cando: 138 | # there is somebody waiting 139 | try: 140 | target = self.dequeue(d) 141 | except IndexError: 142 | # capacity is not None but nobody is waiting 143 | if d > 0: 144 | self.enqueue(dir, ChannelWaiter(None, None, arg)) 145 | return None 146 | 147 | source.arg, target.arg = target.arg, source.arg 148 | if target.task is not None: 149 | if self.schedule_all: 150 | target.scheduler.unblock(target.task) 151 | do_schedule = True 152 | elif self.preference == -d: 153 | target.scheduler.unblock(target.task, False) 154 | do_schedule = True 155 | else: 156 | target.scheduler.unblock(target.task) 157 | else: 158 | # nobody is waiting 159 | source.task.blocked == 1 160 | self.enqueue(dir, source) 161 | schedrem(source.task) 162 | do_schedule = True 163 | 164 | if do_schedule: 165 | schedule() 166 | 167 | 168 | if isinstance(source.arg, bomb): 169 | source.arg.raise_() 170 | return source.arg 171 | 172 | def receive(self): 173 | """ 174 | channel.receive() -- receive a value over the channel. 175 | If no other tasklet is already sending on the channel, 176 | the receiver will be blocked. Otherwise, the receiver will 177 | continue immediately, and the sender is put at the end of 178 | the runnables list. 179 | The above policy can be changed by setting channel flags. 180 | """ 181 | return self._channel_action(None, -1) 182 | 183 | def send_exception(self, exp_type, msg): 184 | self.send(bomb(exp_type, exp_type(msg))) 185 | 186 | def send_sequence(self, iterable): 187 | for item in iterable: 188 | self.send(item) 189 | 190 | def send(self, msg): 191 | """ 192 | channel.send(value) -- send a value over the channel. 193 | If no other tasklet is already receiving on the channel, 194 | the sender will be blocked. Otherwise, the receiver will 195 | be activated immediately, and the sender is put at the end of 196 | the runnables list. 197 | """ 198 | return self._channel_action(msg, 1) 199 | 200 | 201 | _channel_callback = None 202 | 203 | def set_channel_callback(channel_cb): 204 | global _channel_callback 205 | _channel_callback = channel_cb 206 | -------------------------------------------------------------------------------- /flower/core/sched.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | 6 | from collections import deque 7 | import operator 8 | import threading 9 | import time 10 | 11 | _tls = threading.local() 12 | 13 | import greenlet 14 | import six 15 | 16 | from .util import thread_ident 17 | 18 | 19 | class TaskletExit(Exception): 20 | pass 21 | 22 | try: 23 | import __builtin__ 24 | __builtin__.TaskletExit = TaskletExit 25 | except ImportError: 26 | import builtins 27 | setattr(builtins, 'TaskletExit', TaskletExit) 28 | 29 | CoroutineExit = TaskletExit 30 | _global_task_id = 0 31 | 32 | def _coroutine_getcurrent(): 33 | try: 34 | return _tls.current_coroutine 35 | except AttributeError: 36 | return _coroutine_getmain() 37 | 38 | def _coroutine_getmain(): 39 | try: 40 | return _tls.main_coroutine 41 | except AttributeError: 42 | main = coroutine() 43 | main._is_started = -1 44 | main._greenlet = greenlet.getcurrent() 45 | _tls.main_coroutine = main 46 | return _tls.main_coroutine 47 | 48 | class coroutine(object): 49 | """ simple wrapper to bind lazily a greenlet to a function """ 50 | 51 | _is_started = 0 52 | 53 | def __init__(self): 54 | self._greenlet = greenlet 55 | 56 | def bind(self, func, *args, **kwargs): 57 | def _run(): 58 | _tls.current_coroutine = self 59 | self._is_started = 1 60 | func(*args, **kwargs) 61 | self._is_started = 0 62 | self._greenlet = greenlet.greenlet(_run) 63 | 64 | def switch(self): 65 | current = _coroutine_getcurrent() 66 | try: 67 | self._greenlet.switch() 68 | finally: 69 | _tls.current_coroutine = current 70 | 71 | def kill(self): 72 | current = _coroutine_getcurrent() 73 | if self is current: 74 | raise CoroutineExit 75 | self.throw(CoroutineExit) 76 | 77 | def throw(self, *args): 78 | current = _coroutine_getcurrent() 79 | try: 80 | self._greenlet.throw(*args) 81 | finally: 82 | _tls.current_coroutine = current 83 | 84 | @property 85 | def is_alive(self): 86 | return self._is_started < 0 or bool(self._greenlet) 87 | 88 | @property 89 | def is_zombie(self): 90 | return self._is_started > 0 and bool(self._greenlet.dead) 91 | 92 | getcurrent = staticmethod(_coroutine_getcurrent) 93 | 94 | 95 | 96 | def _scheduler_contains(value): 97 | scheduler = get_scheduler() 98 | return value in scheduler 99 | 100 | def _scheduler_switch(current, next): 101 | scheduler = get_scheduler() 102 | return scheduler.switch(current, next) 103 | 104 | class tasklet(coroutine): 105 | """ 106 | A tasklet object represents a tiny task in a Python thread. 107 | At program start, there is always one running main tasklet. 108 | New tasklets can be created with methods from the stackless 109 | module. 110 | """ 111 | tempval = None 112 | 113 | def __new__(cls, func=None, label=''): 114 | res = coroutine.__new__(cls) 115 | res.label = label 116 | res._task_id = None 117 | return res 118 | 119 | 120 | def __init__(self, func=None, label=''): 121 | coroutine.__init__(self) 122 | self._init(func, label) 123 | 124 | def _init(self, func=None, label=''): 125 | global _global_task_id 126 | self.func = func 127 | self.label = label 128 | self.alive = False 129 | self.blocked = False 130 | self.sched = None 131 | self.thread_id = thread_ident() 132 | self._task_id = _global_task_id 133 | _global_task_id += 1 134 | 135 | def __str__(self): 136 | return '' % (self.label,self._task_id) 137 | 138 | __repr__ = __str__ 139 | 140 | def __call__(self, *argl, **argd): 141 | return self.setup(*argl, **argd) 142 | 143 | def bind(self, func): 144 | """ 145 | Binding a tasklet to a callable object. 146 | The callable is usually passed in to the constructor. 147 | In some cases, it makes sense to be able to re-bind a tasklet, 148 | after it has been run, in order to keep its identity. 149 | Note that a tasklet can only be bound when it doesn't have a frame. 150 | """ 151 | if not six.callable(func): 152 | raise TypeError('tasklet function must be a callable') 153 | self.func = func 154 | 155 | 156 | def setup(self, *argl, **argd): 157 | """ 158 | supply the parameters for the callable 159 | """ 160 | if self.func is None: 161 | raise TypeError('tasklet function must be callable') 162 | func = self.func 163 | sched = self.sched = get_scheduler() 164 | 165 | def _func(): 166 | 167 | try: 168 | try: 169 | func(*argl, **argd) 170 | except TaskletExit: 171 | pass 172 | finally: 173 | sched.remove(self) 174 | self.alive = False 175 | 176 | self.func = None 177 | coroutine.bind(self, _func) 178 | self.alive = True 179 | sched.append(self) 180 | return self 181 | 182 | def run(self): 183 | self.insert() 184 | _scheduler_switch(getcurrent(), self) 185 | 186 | def kill(self): 187 | if self.is_alive: 188 | # Killing the tasklet by throwing TaskletExit exception. 189 | coroutine.kill(self) 190 | 191 | schedrem(self) 192 | self.alive = False 193 | 194 | def raise_exception(self, exc, *args): 195 | if not self.is_alive: 196 | return 197 | schedrem(self) 198 | coroutine.throw(self, exc, *args) 199 | 200 | 201 | def insert(self): 202 | if self.blocked: 203 | raise RuntimeError("You cannot run a blocked tasklet") 204 | if not self.alive: 205 | raise RuntimeError("You cannot run an unbound(dead) tasklet") 206 | schedpush(self) 207 | 208 | def remove(self): 209 | if self.blocked: 210 | raise RuntimeError("You cannot remove a blocked tasklet.") 211 | if self is getcurrent(): 212 | raise RuntimeError("The current tasklet cannot be removed.") 213 | schedrem(self) 214 | 215 | class Scheduler(object): 216 | 217 | def __init__(self): 218 | # define the main tasklet 219 | self._main_coroutine = _coroutine_getmain() 220 | self._main_tasklet = _coroutine_getcurrent() 221 | self._main_tasklet.__class__ = tasklet 222 | six.get_method_function(self._main_tasklet._init)(self._main_tasklet, 223 | label='main') 224 | self._last_task = self._main_tasklet 225 | 226 | self.thread_id = thread_ident() # the scheduler thread id 227 | self._lock = threading.Lock() # global scheduler lock 228 | 229 | self._callback = None # scheduler callback 230 | self._run_calls = [] # runcalls. (tasks where run apply 231 | self.runnable = deque() # runnable tasks 232 | self.blocked = 0 # number of blocked/sleeping tasks 233 | self.append(self._main_tasklet) 234 | 235 | def send(self): 236 | self._async.send() 237 | 238 | def wakeup(self, handle): 239 | self.schedule() 240 | 241 | def set_callback(self, cb): 242 | self._callback = cb 243 | 244 | def append(self, value, normal=True): 245 | if normal: 246 | self.runnable.append(value) 247 | else: 248 | self.runnable.rotate(-1) 249 | self.runnable.appendleft(value) 250 | self.runnable.rotate(1) 251 | 252 | def appendleft(self, task): 253 | self.runnable.appendleft(task) 254 | 255 | def remove(self, task): 256 | """ remove a task from the runnable """ 257 | 258 | # the scheduler need to be locked here 259 | with self._lock: 260 | try: 261 | self.runnable.remove(task) 262 | # if the task is blocked increment their number 263 | if task.blocked: 264 | self.blocked += 1 265 | except ValueError: 266 | pass 267 | 268 | def unblock(self, task, normal=True): 269 | """ unblock a task (put back from sleep)""" 270 | with self._lock: 271 | task.blocked = 0 272 | self.blocked -= 1 273 | self.append(task, normal) 274 | 275 | def taskwakeup(self, task): 276 | if task is None: 277 | return 278 | 279 | # the scheduler need to be locked here 280 | with self._lock: 281 | try: 282 | self.runnable.remove(task) 283 | except ValueError: 284 | pass 285 | 286 | # eventually unblock the tasj 287 | self.unblock(task) 288 | 289 | def switch(self, current, next): 290 | prev = self._last_task 291 | if (self._callback is not None and prev is not next): 292 | self._callback(prev, next) 293 | self._last_task = next 294 | 295 | 296 | assert not next.blocked 297 | 298 | if next is not current: 299 | next.switch() 300 | 301 | return current 302 | 303 | def schedule(self, retval=None): 304 | curr = self.getcurrent() 305 | main = self.getmain() 306 | 307 | if retval is None: 308 | retval = curr 309 | 310 | while True: 311 | if self.runnable: 312 | if self.runnable[0] is curr: 313 | self.runnable.rotate(-1) 314 | task = self.runnable[0] 315 | elif self._run_calls: 316 | task = self._run_calls.pop() 317 | else: 318 | raise RuntimeError("no more tasks are sleeping") 319 | 320 | 321 | # switch to next task 322 | self.switch(curr, task) 323 | 324 | # exit the loop if there are no more tasks 325 | if curr is self._last_task: 326 | return retval 327 | 328 | def run(self): 329 | curr = self.getcurrent() 330 | self._run_calls.append(curr) 331 | self.remove(curr) 332 | try: 333 | while True: 334 | self.schedule() 335 | if not curr.blocked: 336 | break 337 | time.sleep(0.0001) 338 | finally: 339 | self.append(curr) 340 | 341 | def runcount(self): 342 | return len(self.runnable) 343 | 344 | def getmain(self): 345 | return self._main_tasklet 346 | 347 | def getcurrent(self): 348 | curr = _coroutine_getcurrent() 349 | if curr == self._main_coroutine: 350 | return self._main_tasklet 351 | else: 352 | return curr 353 | 354 | def __contains__(self, value): 355 | try: 356 | operator.indexOf(self.runnable, value) 357 | return True 358 | except ValueError: 359 | return False 360 | 361 | _channel_callback = None 362 | 363 | def set_channel_callback(channel_cb): 364 | global _channel_callback 365 | _channel_callback = channel_cb 366 | 367 | 368 | def get_scheduler(): 369 | global _tls 370 | try: 371 | return _tls.scheduler 372 | except AttributeError: 373 | scheduler = _tls.scheduler = Scheduler() 374 | return scheduler 375 | 376 | 377 | def taskwakeup(task): 378 | sched = get_scheduler() 379 | sched.taskwakeup(task) 380 | 381 | def getruncount(): 382 | sched = get_scheduler() 383 | return sched.runcount() 384 | 385 | def getcurrent(): 386 | return get_scheduler().getcurrent() 387 | 388 | def getmain(): 389 | return get_scheduler().getmain() 390 | 391 | def set_schedule_callback(scheduler_cb): 392 | sched = get_scheduler() 393 | sched.set_callback(scheduler_cb) 394 | 395 | def schedule(retval=None): 396 | scheduler = get_scheduler() 397 | return scheduler.schedule(retval=retval) 398 | 399 | def schedule_remove(retval=None): 400 | scheduler = get_scheduler() 401 | scheduler.remove(scheduler.getcurrent()) 402 | return scheduler.schedule(retval=retval) 403 | 404 | def schedpush(task): 405 | scheduler = get_scheduler() 406 | scheduler.append(task) 407 | 408 | def schedrem(task): 409 | scheduler = get_scheduler() 410 | scheduler.remove(task) 411 | 412 | def run(): 413 | sched = get_scheduler() 414 | sched.run() 415 | 416 | # bootstrap the scheduler 417 | def _bootstrap(): 418 | get_scheduler() 419 | _bootstrap() 420 | -------------------------------------------------------------------------------- /flower/core/sync.c: -------------------------------------------------------------------------------- 1 | /* -*- coding: utf-8 - 2 | * 3 | * This file is part of flower. See the NOTICE for more information. */ 4 | 5 | #include 6 | #if defined( __ia64__ ) && defined( __INTEL_COMPILER ) 7 | # include 8 | #endif 9 | #include 10 | 11 | #include "Python.h" 12 | 13 | struct module_state { 14 | PyObject *error; 15 | }; 16 | 17 | #if PY_MAJOR_VERSION >= 3 18 | #define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) 19 | #else 20 | #define GETSTATE(m) (&_state) 21 | static struct module_state _state; 22 | #endif 23 | 24 | static 25 | PyObject* compare_and_swap(PyObject *self, PyObject *args) 26 | { 27 | int oldval, newval, r; 28 | if (!PyArg_ParseTuple(args, "ii", &oldval, &newval)) 29 | return NULL; 30 | r = __sync_bool_compare_and_swap(&oldval, oldval, newval); 31 | 32 | return Py_BuildValue("i", oldval); 33 | } 34 | 35 | static 36 | PyObject* increment(PyObject *self, PyObject *args) 37 | { 38 | int val; 39 | if (!PyArg_ParseTuple(args, "i", &val)) 40 | return NULL; 41 | 42 | return Py_BuildValue("i", __sync_add_and_fetch(&val, 1)); 43 | } 44 | 45 | static 46 | PyObject* decrement(PyObject *self, PyObject *args) 47 | { 48 | int val; 49 | if (!PyArg_ParseTuple(args, "i", &val)) 50 | return NULL; 51 | 52 | return Py_BuildValue("i", __sync_sub_and_fetch(&val, 1)); 53 | } 54 | 55 | static 56 | PyObject* atomic_read(PyObject *self, PyObject *args) 57 | { 58 | int val; 59 | if (!PyArg_ParseTuple(args, "i", &val)) 60 | return NULL; 61 | 62 | return Py_BuildValue("i", __sync_add_and_fetch(&val, 0)); 63 | } 64 | 65 | 66 | static PyMethodDef 67 | sync_methods[] = { 68 | {"compare_and_swap", compare_and_swap, METH_VARARGS, 69 | "Atomically compare and swap 2 integers"}, 70 | {"increment", increment, METH_VARARGS, "Atomically increment an integer"}, 71 | {"decrement", decrement, METH_VARARGS, "Atomically decrement an integer"}, 72 | {"atomic_read", atomic_read, METH_VARARGS, "Atomically read an integer"}, 73 | {NULL, NULL, 0, NULL} 74 | }; 75 | 76 | #if PY_MAJOR_VERSION >= 3 77 | 78 | static int sync_traverse(PyObject *m, visitproc visit, void *arg) { 79 | Py_VISIT(GETSTATE(m)->error); 80 | return 0; 81 | } 82 | 83 | static int sync_clear(PyObject *m) { 84 | Py_CLEAR(GETSTATE(m)->error); 85 | return 0; 86 | } 87 | 88 | 89 | static struct PyModuleDef moduledef = { 90 | PyModuleDef_HEAD_INIT, 91 | "sync", 92 | NULL, 93 | sizeof(struct module_state), 94 | sync_methods, 95 | NULL, 96 | sync_traverse, 97 | sync_clear, 98 | NULL 99 | }; 100 | 101 | #define INITERROR return NULL 102 | 103 | PyObject * 104 | PyInit_sync(void) 105 | 106 | #else 107 | #define INITERROR return 108 | 109 | void 110 | initsync(void) 111 | #endif 112 | { 113 | #if PY_MAJOR_VERSION >= 3 114 | PyObject *module = PyModule_Create(&moduledef); 115 | #else 116 | PyObject *module = Py_InitModule("sync", sync_methods); 117 | #endif 118 | 119 | if (module == NULL) 120 | INITERROR; 121 | struct module_state *st = GETSTATE(module); 122 | 123 | st->error = PyErr_NewException("sync.Error", NULL, NULL); 124 | if (st->error == NULL) { 125 | Py_DECREF(module); 126 | INITERROR; 127 | } 128 | 129 | #if PY_MAJOR_VERSION >= 3 130 | return module; 131 | #endif 132 | } 133 | 134 | 135 | -------------------------------------------------------------------------------- /flower/core/timer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | import heapq 6 | import operator 7 | import threading 8 | 9 | import six 10 | 11 | from .util import nanotime 12 | from .sched import (tasklet, schedule, schedule_remove, get_scheduler, 13 | getcurrent, taskwakeup, getmain) 14 | from .channel import channel 15 | 16 | class Timers(object): 17 | 18 | __slots__ = ['__dict__', '_lock', 'sleeping'] 19 | 20 | __shared_state__ = dict( 21 | _timers = {}, 22 | _heap = [], 23 | _timerproc = None 24 | ) 25 | 26 | def __init__(self): 27 | self.__dict__ = self.__shared_state__ 28 | self._lock = threading.RLock() 29 | self.sleeping = False 30 | 31 | 32 | def add(self, t): 33 | with self._lock: 34 | self._add_timer(t) 35 | 36 | if self.sleeping: 37 | self.sleeping = False 38 | taskwakeup(self._timerproc) 39 | 40 | if self._timerproc is None or not self._timerproc.alive: 41 | self._timerproc = tasklet(self.timerproc)() 42 | 43 | def _add_timer(self, t): 44 | if not t.interval: 45 | return 46 | heapq.heappush(self._heap, t) 47 | 48 | 49 | def remove(self, t): 50 | with self._lock: 51 | try: 52 | del self._heap[operator.indexOf(self._heap, t)] 53 | except (KeyError, IndexError): 54 | pass 55 | 56 | def timerproc(self): 57 | while True: 58 | self._lock.acquire() 59 | 60 | while True: 61 | if not len(self._heap): 62 | delta = -1 63 | break 64 | 65 | t = heapq.heappop(self._heap) 66 | now = nanotime() 67 | delta = t.when - now 68 | if delta > 0: 69 | heapq.heappush(self._heap, t) 70 | break 71 | else: 72 | # repeat ? reinsert the timer 73 | if t.period is not None and t.period > 0: 74 | np = nanotime(t.period) 75 | t.when += np * (1 - delta/np) 76 | heapq.heappush(self._heap, t) 77 | 78 | # run 79 | self._lock.release() 80 | t.callback(now, t, *t.args, **t.kwargs) 81 | self._lock.acquire() 82 | 83 | 84 | if delta < 0: 85 | self.sleeping = True 86 | self._lock.release() 87 | schedule_remove() 88 | else: 89 | self._lock.release() 90 | schedule() 91 | 92 | timers = Timers() 93 | add_timer = timers.add 94 | remove_timer = timers.remove 95 | 96 | 97 | class Timer(object): 98 | 99 | def __init__(self, callback, interval=None, period=None, args=None, 100 | kwargs=None): 101 | if not six.callable(callback): 102 | raise ValueError("callback must be a callable") 103 | 104 | self.callback = callback 105 | self.interval = interval 106 | self.period = period 107 | self.args = args or [] 108 | self.kwargs = kwargs or {} 109 | self.when = 0 110 | self.active = False 111 | 112 | def start(self): 113 | global timers 114 | self.active = True 115 | self.when = nanotime() + nanotime(self.interval) 116 | add_timer(self) 117 | 118 | def stop(self): 119 | remove_timer(self) 120 | self.active = False 121 | 122 | def __lt__(self, other): 123 | return self.when < other.when 124 | 125 | __cmp__ = __lt__ 126 | 127 | def sleep(seconds=0): 128 | if not seconds: 129 | return 130 | 131 | sched = get_scheduler() 132 | curr = getcurrent() 133 | 134 | c = channel() 135 | def ready(now, t): 136 | c.send(None) 137 | 138 | t = Timer(ready, seconds) 139 | t.start() 140 | c.receive() 141 | -------------------------------------------------------------------------------- /flower/core/util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | import time 6 | 7 | try: 8 | from thread import get_ident as thread_ident 9 | except ImportError: 10 | from _thread import get_ident as thread_ident 11 | 12 | 13 | def nanotime(s=None): 14 | """ convert seconds to nanoseconds. If s is None, current time is 15 | returned """ 16 | if s is not None: 17 | return s * 1000000000 18 | return time.time() * 1000000000 19 | 20 | def from_nanotime(n): 21 | """ convert from nanotime to seconds """ 22 | return n / 1.0e9 23 | -------------------------------------------------------------------------------- /flower/core/uv.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | import sys 6 | import threading 7 | 8 | _tls = threading.local() 9 | 10 | import pyuv 11 | 12 | from flower.core.channel import channel 13 | from flower.core.sched import tasklet, getcurrent, schedule 14 | 15 | def get_fd(io): 16 | if not isinstance(io, int): 17 | if hasattr(io, 'fileno'): 18 | if callable(io.fileno): 19 | fd = io.fileno() 20 | else: 21 | fd = io.fileno 22 | else: 23 | raise ValueError("invalid file descriptor number") 24 | else: 25 | fd = io 26 | return fd 27 | 28 | 29 | def uv_mode(m): 30 | if m == 0: 31 | return pyuv.UV_READABLE 32 | elif m == 1: 33 | return pyuv.UV_WRITABLE 34 | else: 35 | return pyuv.UV_READABLE | pyuv.UV_WRITABLE 36 | 37 | class UVExit(Exception): 38 | pass 39 | 40 | 41 | class UV(object): 42 | 43 | def __init__(self): 44 | self.loop = pyuv.Loop() 45 | self._async = pyuv.Async(self.loop, self._wakeloop) 46 | self._async.unref() 47 | self.fds = {} 48 | self._lock = threading.RLock() 49 | self.running = False 50 | 51 | # start the server task 52 | self._runtask = tasklet(self.run, "uv_server")() 53 | 54 | def _wakeloop(self, handle): 55 | self.loop.update_time() 56 | 57 | def wakeup(self): 58 | self._async.send() 59 | 60 | def switch(self): 61 | if not self.running: 62 | self._runtask = tasklet(self.run)() 63 | 64 | getcurrent().remove() 65 | self._runtask.switch() 66 | 67 | def idle(self, handle): 68 | if getcurrent() is self._runtask: 69 | schedule() 70 | 71 | def run(self): 72 | t = pyuv.Timer(self.loop) 73 | t.start(self.idle, 0.0001, 0.0001) 74 | t.unref() 75 | self.running = True 76 | try: 77 | self.loop.run() 78 | finally: 79 | self.running = False 80 | 81 | def uv_server(): 82 | global _tls 83 | 84 | try: 85 | return _tls.uv_server 86 | except AttributeError: 87 | uv_server = _tls.uv_server = UV() 88 | return uv_server 89 | 90 | def uv_sleep(seconds, ref=True): 91 | """ use the event loop for sleep. This an alternative to our own 92 | time events scheduler """ 93 | 94 | uv = uv_server() 95 | c = channel() 96 | def _sleep_cb(handle): 97 | handle.stop() 98 | c.send(None) 99 | 100 | sleep = pyuv.Timer(uv.loop) 101 | sleep.start(_sleep_cb, seconds, seconds) 102 | if not ref: 103 | sleep.unref() 104 | 105 | c.receive() 106 | 107 | def uv_idle(ref=True): 108 | """ use the event loop for idling. This an alternative to our own 109 | time events scheduler """ 110 | 111 | uv = uv_server() 112 | c = channel() 113 | def _sleep_cb(handle): 114 | handle.stop() 115 | c.send(True) 116 | 117 | 118 | idle = pyuv.Idle(uv.loop) 119 | idle.start(_sleep_cb) 120 | if not ref: 121 | idle.unref() 122 | 123 | return c.receive() 124 | -------------------------------------------------------------------------------- /flower/io.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | import pyuv 6 | from flower.core import channel 7 | from flower.core.uv import get_fd, uv_mode, uv_server 8 | 9 | from pyuv import errno 10 | 11 | class IOChannel(channel): 12 | """ channel to wait on IO events for a specific fd. It now use the UV server 13 | facility. 14 | 15 | mode: 16 | 0: read 17 | 1: write 18 | 2: read & write""" 19 | 20 | def __init__(self, io, mode=0, label=''): 21 | super(IOChannel, self).__init__(label=label) 22 | 23 | fno = get_fd(io) 24 | self.io = io 25 | uv = uv_server() 26 | self._poller = pyuv.Poll(uv.loop, fno) 27 | self._poller.start(uv_mode(mode), self._tick) 28 | 29 | def _tick(self, handle, events, error): 30 | if error: 31 | if error == errno.UV_EBADF: 32 | self.handle.close() 33 | self.send(events) 34 | else: 35 | self.send_exception(IOError, "uv error: %s" % errno) 36 | else: 37 | self.send(events) 38 | 39 | def stop(self): 40 | self._poller.stop() 41 | self.close() 42 | 43 | def wait_read(io): 44 | """ wrapper around IOChannel to only wait when a device become 45 | readable """ 46 | c = IOChannel(io) 47 | try: 48 | return c.receive() 49 | finally: 50 | c.close() 51 | 52 | def wait_write(io): 53 | """ wrapper around IOChannel to only wait when a device become 54 | writable """ 55 | c = IOChannel(io, 1) 56 | try: 57 | return c.receive() 58 | finally: 59 | c.close() 60 | -------------------------------------------------------------------------------- /flower/local.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | import weakref 6 | from flower.core import getcurrent 7 | 8 | 9 | class local(object): 10 | """ a local class working like a thread.local class to keep local 11 | attributes for a given tasklet """ 12 | 13 | class _local_attr(object): pass 14 | 15 | def __init__(self): 16 | self._d = weakref.WeakKeyDictionary() 17 | 18 | def __getattr__(self, key): 19 | d = self._d 20 | curr = getcurrent() 21 | if not curr in d or not hasattr(d[curr], key): 22 | raise AttributeError(key) 23 | return getattr(d[curr], key) 24 | 25 | def __setattr__(self, key, value): 26 | if key == '_d': 27 | self.__dict__[key] = value 28 | object.__setattr__(self, key, value) 29 | else: 30 | d = self._d 31 | curr = getcurrent() 32 | if not curr in d: 33 | d[curr] = self._local_attr() 34 | setattr(d[curr], key, value) 35 | 36 | def __delattr__(self, key): 37 | d = self._d 38 | curr = getcurrent() 39 | if not curr in d or not hasattr(d[curr], key): 40 | raise AttributeError(key) 41 | delattr(d[curr], key) 42 | -------------------------------------------------------------------------------- /flower/net/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | from flower.net.tcp import TCPListen, dial_tcp 6 | from flower.net.udp import UDPListen, dial_udp 7 | from flower.net.pipe import PipeListen, dial_pipe 8 | from flower.net.sock import TCPSockListen, PipeSockListen 9 | 10 | LISTEN_HANDLERS = dict( 11 | tcp = TCPListen, 12 | udp = UDPListen, 13 | pipe = PipeListen, 14 | socktcp = TCPSockListen, 15 | sockpipe = PipeSockListen) 16 | 17 | DIAL_HANDLERS = dict( 18 | tcp = dial_tcp, 19 | udp = dial_udp, 20 | pipe = dial_pipe) 21 | 22 | 23 | def Dial(proto, *args): 24 | """ A Dial is a generic client for stream-oriented protocols. 25 | 26 | Example:: 27 | 28 | conn, err = Dial("tcp", ('127.0.0.1', 8000)) 29 | conn.write("hello") 30 | print(conn.read()) 31 | """ 32 | 33 | try: 34 | dial_func = DIAL_HANDLERS[proto] 35 | except KeyError: 36 | raise ValueError("type should be tcp, udp or unix") 37 | return dial_func(*args) 38 | 39 | dial = Dial # for pep8 lovers 40 | 41 | def Listen(addr=('0.0.0.0', 0), proto="tcp", *args): 42 | """A Listener is a generic network listener for stream-oriented protocols. 43 | Multiple tasks may invoke methods on a Listener simultaneously. 44 | 45 | Example:: 46 | 47 | def handle_connection(conn): 48 | while True: 49 | data = conn.read() 50 | if not data: 51 | break 52 | conn.write(data) 53 | 54 | l = Listen(('127.0.0.1', 8000)) 55 | 56 | try: 57 | while True: 58 | try: 59 | conn, err = l.accept() 60 | t = tasklet(handle_connection)(conn) 61 | except KeyboardInterrupt: 62 | break 63 | finally: 64 | l.close() 65 | 66 | run() 67 | """ 68 | 69 | try: 70 | listen_class = LISTEN_HANDLERS[proto] 71 | except KeyError: 72 | raise ValueError("type should be tcp, udp or unix") 73 | 74 | return listen_class(addr, *args) 75 | 76 | listen = Listen # for pep8 lovers 77 | -------------------------------------------------------------------------------- /flower/net/base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | from flower.core import channel, getcurrent, get_scheduler 6 | from flower.core.uv import uv_server 7 | 8 | class NoMoreListener(Exception): 9 | pass 10 | 11 | class Listener(object): 12 | 13 | def __init__(self): 14 | self.task = getcurrent() 15 | self.uv = uv_server() 16 | self.sched = get_scheduler() 17 | self.c = channel() 18 | 19 | 20 | @property 21 | def loop(self): 22 | return self.uv.loop 23 | 24 | 25 | class IConn(object): 26 | """ connection interface """ 27 | 28 | def read(self): 29 | """ return data """ 30 | 31 | def write(self, data): 32 | """ send data to the remote connection """ 33 | 34 | def writelines(self, seq): 35 | """ send data using a list or an iterator to the remote 36 | connection """ 37 | 38 | def local_addr(self): 39 | """ return the local address """ 40 | 41 | def remote_addr(self): 42 | """ return the remote address """ 43 | 44 | @property 45 | def status(self): 46 | """ return current status """ 47 | if self.client.closed: 48 | return "closed" 49 | elif self.client.readable and self.client.writable: 50 | return "open" 51 | elif self.client.readable and not self.client.writable: 52 | return "readonly" 53 | elif not self.client.readable and self.client.writable: 54 | return "writeonly" 55 | else: 56 | return "closed" 57 | 58 | 59 | class IListen(object): 60 | 61 | def accept(self): 62 | """ accept a connection. Return a Conn instance. It always 63 | block the current task """ 64 | 65 | def close(self): 66 | """ stop listening """ 67 | 68 | def addr(self): 69 | " return the bound address """ 70 | 71 | class IDial(object): 72 | 73 | """ base interface for Dial class """ 74 | -------------------------------------------------------------------------------- /flower/net/pipe.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | import pyuv 6 | 7 | from flower.core import channel 8 | from flower.core.uv import uv_server 9 | from flower.net.tcp import TCPListen, TCPConn 10 | 11 | class PipeConn(TCPConn): 12 | """ A Pipe connection """ 13 | 14 | 15 | class PipeListen(TCPListen): 16 | """ A Pipe listener """ 17 | 18 | CONN_CLASS = PipeConn 19 | HANDLER_CLASS = pyuv.Pipe 20 | 21 | def dial_pipe(addr): 22 | uv = uv_server() 23 | h = pyuv.Pipe(uv.loop) 24 | 25 | c = channel() 26 | def _on_connect(handle, error): 27 | c.send((handle, error)) 28 | 29 | h.connect(addr, _on_connect) 30 | h1, error = c.receive() 31 | return (PipeConn(h1), error) 32 | -------------------------------------------------------------------------------- /flower/net/sock.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | from collections import deque 6 | from io import DEFAULT_BUFFER_SIZE 7 | import os 8 | import threading 9 | 10 | import socket 11 | import sys 12 | 13 | import pyuv 14 | 15 | from flower.core import (channel, schedule, getcurrent, bomb) 16 | from flower.core.uv import uv_server 17 | from flower.net.base import IConn, Listener, IListen, NoMoreListener 18 | from flower.net.util import parse_address, is_ipv6 19 | 20 | IS_WINDOW = sys.platform == 'win32' 21 | 22 | if IS_WINDOW: 23 | from errno import WSAEWOULDBLOCK as EWOULDBLOCK 24 | EAGAIN = EWOULDBLOCK 25 | else: 26 | from errno import EWOULDBLOCK 27 | 28 | try: 29 | from errno import EBADF 30 | except ImportError: 31 | EBADF = 9 32 | 33 | # sys.exc_clear was removed in Python3 as the except block of a try/except 34 | # performs the same task. Add it as a no-op method. 35 | try: 36 | sys.exc_clear 37 | except AttributeError: 38 | def exc_clear(): 39 | return 40 | sys.exc_clear = exc_clear 41 | 42 | if sys.version_info < (2, 7, 0, 'final'): 43 | # in python 2.6 socket.recv_into doesn't support bytesarray 44 | def recv_into(sock, b): 45 | l = max(len(b), DEFAULT_BUFFER_SIZE) 46 | buf = sock.recv(l) 47 | recved = len(buf) 48 | b[0:recved] = buf 49 | return recved 50 | else: 51 | def recv_into(sock, b): 52 | return sock.recv_into(b) 53 | 54 | # from gevent code 55 | if sys.version_info[:2] < (2, 7): 56 | _get_memory = buffer 57 | elif sys.version_info[:2] < (3, 0): 58 | def _get_memory(string, offset): 59 | try: 60 | return memoryview(string)[offset:] 61 | except TypeError: 62 | return buffer(string, offset) 63 | else: 64 | def _get_memory(string, offset): 65 | return memoryview(string)[offset:] 66 | 67 | 68 | class SockConn(IConn): 69 | 70 | def __init__(self, client, laddr, addr): 71 | # set connection info 72 | self.client = client 73 | self.client.setblocking(0) 74 | self.timeout = socket.getdefaulttimeout() 75 | self.laddr = laddr 76 | self.addr = addr 77 | 78 | # utilies used to fetch & send ata 79 | self.cr = channel() # channel keeping readers waiters 80 | self.cw = channel() # channel keeping writers waiters 81 | self.queue = deque() # queue of readable data 82 | self.uv = uv_server() 83 | self.rpoller = None 84 | self.wpoller = None 85 | self._lock = threading.RLock() 86 | self.ncr = 0 # reader refcount 87 | self.ncw = 0 # writer refcount 88 | 89 | self.closing = False 90 | 91 | 92 | def read(self): 93 | if self.closing: 94 | return "" 95 | 96 | while True: 97 | try: 98 | retval = self.queue.popleft() 99 | if self.cr.balance < 0: 100 | self.cr.send(retval) 101 | 102 | if isinstance(retval, bomb): 103 | retval.raise_() 104 | 105 | return retval 106 | except IndexError: 107 | pass 108 | 109 | msg = None 110 | buf = bytearray(DEFAULT_BUFFER_SIZE) 111 | try: 112 | recvd = recv_into(self.client, buf) 113 | msg = bytes(buf[0:recvd]) 114 | except socket.error: 115 | ex = sys.exc_info()[1] 116 | if ex.args[0] == EBADF: 117 | msg = "" 118 | self.closing = True 119 | if ex.args[0] != EWOULDBLOCK or self.timeout == 0.0: 120 | msg = bomb(ex, sys.exc_info()[2]) 121 | self.closing = True 122 | exc_clear() 123 | 124 | if msg is None: 125 | res = self._watch_read() 126 | if res is not None: 127 | self.queue.append(res) 128 | 129 | else: 130 | self.queue.append(msg) 131 | 132 | def write(self, data): 133 | data_sent = 0 134 | while data_sent < len(data): 135 | data_sent += self._send(_get_memory(data, data_sent)) 136 | 137 | def writelines(self, seq): 138 | for data in seq: 139 | self.write(data) 140 | 141 | def local_addr(self): 142 | return self.laddr 143 | 144 | def remote_addr(self): 145 | return self.addr 146 | 147 | def close(self): 148 | self.client.close() 149 | 150 | # stop polling 151 | if self.wpoller is not None: 152 | self.wpoller.stop() 153 | self.wpoller = None 154 | 155 | if self.rpoller is not None: 156 | self.rpoller.stop() 157 | self.rpoller = None 158 | 159 | def _watch_read(self): 160 | self._lock.acquire() 161 | if not self.rpoller: 162 | self.rpoller = pyuv.Poll(self.uv.loop, self.client.fileno()) 163 | self.rpoller.start(pyuv.UV_READABLE, self._on_read) 164 | 165 | # increase the reader refcount 166 | self.ncr += 1 167 | self._lock.release() 168 | try: 169 | self.cr.receive() 170 | finally: 171 | self._lock.acquire() 172 | # decrease the refcount 173 | self.ncr -= 1 174 | # if no more waiters, close the poller 175 | if self.ncr <= 0: 176 | self.rpoller.stop() 177 | self.rpoller = None 178 | self._lock.release() 179 | 180 | def _on_read(self, handle, events, error): 181 | if error and error is not None: 182 | self.readable = False 183 | if error == 1: 184 | self.closing = True 185 | msg = "" 186 | else: 187 | msg = bomb(IOError, IOError("uv error: %s" % error)) 188 | else: 189 | self.readable = True 190 | msg = None 191 | self.cr.send(msg) 192 | 193 | def _send(self, data): 194 | while True: 195 | try: 196 | return self.client.send(data) 197 | except socket.error: 198 | ex = sys.exc_info()[1] 199 | if ex.args[0] == EBADF: 200 | self.closing = True 201 | return 202 | if ex.args[0] != EWOULDBLOCK or self.timeout == 0.0: 203 | raise 204 | exc_clear() 205 | 206 | # wait for newt write 207 | self._watch_write() 208 | 209 | def _watch_write(self): 210 | self._lock.acquire() 211 | 212 | # create a new poller 213 | if not self.wpoller: 214 | self.wpoller = pyuv.Poll(self.uv.loop, self.client.fileno()) 215 | self.wpoller.start(pyuv.UV_WRITABLE, self._on_write) 216 | 217 | # increase the writer refcount 218 | self.ncw += 1 219 | 220 | self._lock.release() 221 | 222 | try: 223 | self.cw.receive() 224 | finally: 225 | self._lock.acquire() 226 | self.ncw -= 1 227 | if self.ncw <= 0: 228 | self.wpoller.stop() 229 | self.wpoller = None 230 | self._lock.release() 231 | 232 | 233 | def _on_write(self, handle, events, errors): 234 | if not errors: 235 | self.cw.send() 236 | 237 | def _read(self): 238 | buf = bytearray(DEFAULT_BUFFER_SIZE) 239 | try: 240 | recvd = recv_into(self.client, buf) 241 | msg = bytes(buf[0:recvd]) 242 | except socket.error: 243 | ex = sys.exc_info()[1] 244 | if ex.args[0] == EBADF: 245 | msg = "" 246 | if ex.args[0] != EWOULDBLOCK or self.timeout == 0.0: 247 | msg = bomb(ex, sys.exc_info()[2]) 248 | exc_clear() 249 | return msg 250 | 251 | 252 | class TCPSockListen(IListen): 253 | 254 | def __init__(self, addr, *args, **kwargs): 255 | 256 | sock = None 257 | fd = None 258 | if "sock" in kwargs: 259 | # we passed a socket in the kwargs, just use it 260 | sock = kwargs['sock'] 261 | fd = sock.fileno() 262 | elif isinstance(addr, int): 263 | # we are reusing a socket here 264 | fd = addr 265 | if "family" not in kwargs: 266 | family = socket.AF_INET 267 | else: 268 | family = kwargs['family'] 269 | sock = socket.fromfd(fd, family, socket.SOCK_STREAM) 270 | else: 271 | # new socket 272 | addr = parse_address(addr) 273 | if is_ipv6(addr[0]): 274 | family = socket.AF_INET6 275 | else: 276 | family = socket.AF_INET 277 | 278 | sock = socket.socket(family, socket.SOCK_STREAM) 279 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 280 | 281 | nodelay = kwargs.get('nodelay', True) 282 | if family == socket.AF_INET and nodelay: 283 | sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 284 | 285 | sock.bind(addr) 286 | sock.setblocking(0) 287 | fd = sock.fileno() 288 | 289 | self.sock = sock 290 | self.fd = fd 291 | self.addr = addr 292 | self.backlog = kwargs.get('backlog', 128) 293 | self.timeout = socket.getdefaulttimeout() 294 | self.uv = uv_server() 295 | self.poller = None 296 | self.listeners = deque() 297 | self.task = getcurrent() 298 | 299 | # start to listen 300 | self.sock.listen(self.backlog) 301 | 302 | def accept(self): 303 | """ start the accept loop. Let the OS handle accepting balancing 304 | between listeners """ 305 | 306 | if self.poller is None: 307 | self.poller = pyuv.Poll(self.uv.loop, self.fd) 308 | self.poller.start(pyuv.UV_READABLE, self._on_read) 309 | 310 | listener = Listener() 311 | self.listeners.append(listener) 312 | return listener.c.receive() 313 | 314 | def addr(self): 315 | return self.addr 316 | 317 | def close(self): 318 | if self.poller is not None: 319 | self.poller.stop() 320 | self.sock.close() 321 | 322 | def _on_read(self, handle, events, error): 323 | if error: 324 | handle.stop() 325 | self.poller = None 326 | else: 327 | res = None 328 | try: 329 | res = self.sock.accept() 330 | except socket.error: 331 | exc_info = sys.exc_info() 332 | ex = exc_info[1] 333 | if ex.args[0] != EWOULDBLOCK or self.timeout == 0.0: 334 | self.task.throw(*exc_info) 335 | exc_clear() 336 | 337 | if res is not None: 338 | client, addr = res 339 | self._on_connection(client, addr) 340 | 341 | def _on_connection(self, client, addr): 342 | if len(self.listeners): 343 | listener = self.listeners.popleft() 344 | 345 | self.uv.wakeup() 346 | 347 | # return a new connection object to the listener 348 | conn = SockConn(client, self.addr, addr) 349 | listener.c.send((conn, None)) 350 | schedule() 351 | else: 352 | # we should probably do something there to drop connections 353 | self.task.throw(NoMoreListener) 354 | 355 | class PipeSockListen(TCPSockListen): 356 | 357 | def __init__(self, addr, *args, **kwargs): 358 | fd = kwargs.get('fd') 359 | if fd is None: 360 | try: 361 | os.remove(addr) 362 | except OSError: 363 | pass 364 | 365 | sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 366 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 367 | 368 | if fd is None: 369 | sock.bind(addr) 370 | sock.setblocking(0) 371 | 372 | self.sock = sock 373 | self.fd = fd 374 | self.addr = addr 375 | self.backlog = kwargs.get('backlog', 128) 376 | self.timeout = socket.getdefaulttimeout() 377 | 378 | # start to listen 379 | self.sock.listen(self.backlog) 380 | -------------------------------------------------------------------------------- /flower/net/tcp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | from collections import deque 6 | import pyuv 7 | 8 | 9 | from flower.core.uv import uv_server 10 | from flower.core import (channel, schedule, getcurrent, get_scheduler, 11 | bomb) 12 | from flower.net.base import IConn, Listener, IListen, NoMoreListener 13 | 14 | class TCPConn(IConn): 15 | 16 | def __init__(self, client): 17 | self.client = client 18 | self.reading = False 19 | self.cr = channel() 20 | self.queue = deque() 21 | 22 | def read(self): 23 | if not self.reading: 24 | self.reading = True 25 | self.client.start_read(self._on_read) 26 | 27 | self.client.loop.update_time() 28 | try: 29 | retval = self.queue.popleft() 30 | if self.cr.balance < 0: 31 | self.cr.send(retval) 32 | 33 | if isinstance(retval, bomb): 34 | retval.raise_() 35 | return retval 36 | except IndexError: 37 | pass 38 | 39 | return self.cr.receive() 40 | 41 | def write(self, data): 42 | return self.client.write(data) 43 | 44 | def writelines(self, seq): 45 | return self.client.writelines(seq) 46 | 47 | def _wait_write(self, func, data): 48 | c = channel() 49 | def _wait_cb(handle, err): 50 | c.send(True) 51 | 52 | func(data, _wait_cb) 53 | c.receive() 54 | 55 | 56 | def local_address(self): 57 | return self.client.getsockame() 58 | 59 | def remote_address(self): 60 | return self.client.getpeername() 61 | 62 | def close(self): 63 | self.client.close() 64 | 65 | def _on_read(self, handle, data, error): 66 | if error: 67 | if error == 1: # EOF 68 | msg = "" 69 | else: 70 | msg = bomb(IOError, IOError("uv error: %s" % error)) 71 | else: 72 | msg = data 73 | 74 | # append the message to the queue 75 | self.queue.append(msg) 76 | 77 | if self.cr.balance < 0: 78 | # someone is waiting, return last message 79 | self.cr.send(self.queue.popleft()) 80 | 81 | class TCPListen(IListen): 82 | """ A TCP listener """ 83 | 84 | CONN_CLASS = TCPConn # connection object returned 85 | HANDLER_CLASS = pyuv.TCP # pyuv class used to handle the conenction 86 | 87 | def __init__(self, addr=('0.0.0.0', 0)): 88 | # listeners are all couroutines waiting for a connections 89 | self.listeners = deque() 90 | self.uv = uv_server() 91 | self.sched = get_scheduler() 92 | self.task = getcurrent() 93 | self.listening = False 94 | 95 | self.handler = self.HANDLER_CLASS(self.uv.loop) 96 | self.handler.bind(addr) 97 | 98 | def accept(self): 99 | listener = Listener() 100 | self.listeners.append(listener) 101 | 102 | if not self.listening: 103 | self.handler.listen(self.on_connection) 104 | return listener.c.receive() 105 | 106 | def close(self): 107 | self.handler.close() 108 | 109 | def on_connection(self, server, error): 110 | if len(self.listeners): 111 | listener = self.listeners.popleft() 112 | 113 | # accept the connection 114 | client = pyuv.TCP(server.loop) 115 | server.accept(client) 116 | 117 | self.uv.wakeup() 118 | # return a new connection object to the listener 119 | conn = self.CONN_CLASS(client) 120 | listener.c.send((conn, error)) 121 | schedule() 122 | else: 123 | # we should probably do something there to drop connections 124 | self.task.throw(NoMoreListener) 125 | 126 | 127 | def dial_tcp(addr): 128 | uv = uv_server() 129 | h = pyuv.TCP(uv.loop) 130 | 131 | c = channel() 132 | def _on_connect(handle, error): 133 | if error: 134 | c.send_exception(IOError, "uv error: %s" % error) 135 | else: 136 | c.send(handle) 137 | 138 | h.connect(addr, _on_connect) 139 | h1 = c.receive() 140 | return TCPConn(h1) 141 | -------------------------------------------------------------------------------- /flower/net/udp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | from collections import deque 6 | 7 | from flower.core import channel, schedule, getcurrent, bomb 8 | from flower.core.uv import uv_server 9 | from flower.net.base import Listener, IConn, IListen, NoMoreListener 10 | 11 | import pyuv 12 | 13 | class UDPConn(IConn): 14 | 15 | def __init__(self, addr, raddr, client=None): 16 | self.uv = uv_server() 17 | if client is None: 18 | self.client = pyuv.UDP(self.uv.loop) 19 | self.client.bind(raddr) 20 | else: 21 | self.client = client 22 | self.reading = True 23 | self.queue = deque() 24 | self.cr = channel 25 | self._raddr = raddr 26 | self.addr = addr 27 | 28 | def read(self): 29 | try: 30 | retval = self.queue.popleft() 31 | if self.cr.balance < 0: 32 | self.cr.send(retval) 33 | 34 | if isinstance(retval, bomb): 35 | retval.raise_() 36 | return retval 37 | except IndexError: 38 | pass 39 | 40 | return self.cr.receive() 41 | 42 | def write(self, data): 43 | self.client.send(self._remote_addr, data) 44 | 45 | def writelines(self, seq): 46 | self.client.sendlines(self._remote_addr, seq) 47 | 48 | def local_addr(self): 49 | return self.client.getsockame() 50 | 51 | def remote_addr(self): 52 | return self.remote_addr 53 | 54 | class UDPListen(IListen): 55 | 56 | def __init__(self, addr=('0.0.0.0', 0)): 57 | # listeners are all couroutines waiting for a connections 58 | self.listeners = deque() 59 | 60 | self.conns = {} 61 | self.uv = uv_server() 62 | self.task = getcurrent() 63 | self.listening = False 64 | self.handler = pyuv.UDP(self.uv.loop) 65 | self.handler.bind(addr) 66 | 67 | def accept(self): 68 | listener = Listener() 69 | self.listeners.append(listener) 70 | 71 | if not self.listening: 72 | self.handler.start_recv(self.on_recv) 73 | 74 | return listener.c.receive() 75 | 76 | def on_recv(self, handler, addr, data, error): 77 | with self._lock: 78 | if addr in self.conns: 79 | conn = self.conns[addr] 80 | 81 | if error: 82 | if error == 1: 83 | msg = "" 84 | else: 85 | msg = bomb(IOError, IOError("uv error: %s" % error)) 86 | else: 87 | msg = data 88 | 89 | # emit last message 90 | conn.queue.append(msg) 91 | if conn.cr.balance < 0: 92 | # someone is waiting, return last message 93 | conn.cr.send(self.queue.popleft()) 94 | 95 | elif len(self.listeners): 96 | listener = self.listeners.popleft() 97 | if error: 98 | listener.c.send_exception(IOError, "uv error: %s" % error) 99 | else: 100 | conn = UDPConn(addr) 101 | conn.queue.append(data) 102 | self.conns[addr] = conn 103 | listener.c.send(conn, error) 104 | else: 105 | # we should probably do something there to drop connections 106 | self.task.throw(NoMoreListener) 107 | 108 | 109 | schedule() 110 | 111 | def addr(self): 112 | return self.handler.getsockname() 113 | 114 | def dial_udp(laddr, raddr): 115 | uv = uv_server() 116 | h = pyuv.UDP(uv.loop) 117 | h.bind(laddr) 118 | 119 | return (UDPConn(laddr, raddr, h), None) 120 | -------------------------------------------------------------------------------- /flower/net/util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | import socket 6 | 7 | def is_ipv6(addr): 8 | try: 9 | socket.inet_pton(socket.AF_INET6, addr) 10 | except socket.error: # not a valid address 11 | return False 12 | return True 13 | 14 | def parse_address(netloc, default_port=0): 15 | if isinstance(netloc, tuple): 16 | return netloc 17 | 18 | # get host 19 | if '[' in netloc and ']' in netloc: 20 | host = netloc.split(']')[0][1:].lower() 21 | elif ':' in netloc: 22 | host = netloc.split(':')[0].lower() 23 | elif netloc == "": 24 | host = "0.0.0.0" 25 | else: 26 | host = netloc.lower() 27 | 28 | #get port 29 | netloc = netloc.split(']')[-1] 30 | if ":" in netloc: 31 | port = netloc.split(':', 1)[1] 32 | if not port.isdigit(): 33 | raise RuntimeError("%r is not a valid port number." % port) 34 | port = int(port) 35 | else: 36 | port = default_port 37 | return (host, port) 38 | 39 | -------------------------------------------------------------------------------- /flower/registry.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | import operator 6 | import threading 7 | 8 | import six 9 | 10 | from flower import core 11 | from flower.local import local 12 | 13 | _local = local() 14 | 15 | class Registry(object): 16 | """ actors registry. This rgistry is used to keep a trace of created 17 | actors """ 18 | 19 | __slots__ = ['__dict__', '_lock'] 20 | 21 | # share state between instances 22 | __shared_state__ = dict( 23 | _registered_names = {}, 24 | _by_ref = {} 25 | ) 26 | 27 | def __init__(self): 28 | self.__dict__ = self.__shared_state__ 29 | self._lock = threading.RLock() 30 | 31 | 32 | def register(self, name, ref): 33 | """ register an actor ref with a name in the registry """ 34 | with self._lock: 35 | 36 | if name in self._registered_names: 37 | if self._registered_names[name] == ref: 38 | return 39 | raise KeyError("An actor is already registered for this name") 40 | 41 | self._registered_names[name] = ref 42 | if not ref in self._by_ref: 43 | self._by_ref[ref] = [name] 44 | else: 45 | self._by_ref[ref].append(name) 46 | 47 | def unregister(self, ref_or_name): 48 | """ unregister a name in the registery. If the name doesn't 49 | exist we safely ignore it. """ 50 | try: 51 | if isinstance(ref_or_name, six.string_types): 52 | with self._lock: 53 | ref = self._registered_names[ref_or_name] 54 | names = self._by_ref[ref] 55 | del names[operator.indexOf(names, ref_or_name)] 56 | del self._registered_names[ref_or_name] 57 | else: 58 | with self._lock: 59 | names = self._by_ref[ref_or_name] 60 | for name in names: 61 | del self._registered_names[name] 62 | except (KeyError, IndexError): 63 | pass 64 | 65 | def registered(self, ref=None): 66 | """ get an actor by it's ref """ 67 | print(type(core.getcurrent())) 68 | if ref is None: 69 | try: 70 | ref = core.getcurrent().ref 71 | except AttributeError: 72 | return [] 73 | 74 | 75 | print(ref) 76 | print(self._by_ref) 77 | 78 | if ref not in self._by_ref: 79 | return [] 80 | print(self._by_ref[ref]) 81 | return sorted(self._by_ref[ref]) 82 | 83 | def by_name(self, name): 84 | """ get an actor by name """ 85 | try: 86 | return self._registered_names[name] 87 | except KeyError: 88 | return None 89 | 90 | def __getitem__(self, ref_or_name): 91 | if isinstance(ref_or_name, six.string_types): 92 | return self.by_name(ref_or_name) 93 | else: 94 | return self.registered(ref_or_name) 95 | 96 | def __delitem__(self, ref_or_name): 97 | self.unregister(ref_or_name) 98 | 99 | def __contains__(self, ref_or_name): 100 | with self._lock: 101 | if isinstance(ref_or_name, six.string_types): 102 | return ref_or_name in self._registered_names 103 | else: 104 | return ref_or_name in self._by_ref 105 | 106 | def __iter__(self): 107 | return iter(self._registered_name.items()) 108 | 109 | 110 | registry = Registry() 111 | register = registry.register 112 | unregister = registry.unregister 113 | registered = registry.registered 114 | -------------------------------------------------------------------------------- /flower/time.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information 4 | 5 | 6 | time = __import__('time').time 7 | 8 | from flower import core 9 | from flower.core import timer 10 | from flower.core.util import from_nanotime 11 | 12 | class Ticker(core.channel): 13 | """A Ticker holds a synchronous channel that delivers `ticks' of a 14 | clock at intervals.""" 15 | 16 | def __init__(self, interval, label=''): 17 | super(Ticker, self).__init__(label=label) 18 | self._interval = interval 19 | self._timer = timer.Timer(self._tick, interval, interval) 20 | self._timer.start() 21 | 22 | def _tick(self, now, h): 23 | self.send(from_nanotime(now)) 24 | 25 | def stop(self): 26 | self._timer.stop() 27 | 28 | def idle(): 29 | """ By using this function the current tasklet will be scheduled asap""" 30 | 31 | sched = core.get_scheduler() 32 | curr = core.getcurrent() 33 | def ready(now, h): 34 | curr.blocked = False 35 | sched.append(curr) 36 | core.schedule() 37 | 38 | t = timer.Timer(ready, 0.0001) 39 | t.start() 40 | 41 | curr.blocked = True 42 | core.schedule_remove() 43 | 44 | def sleep(seconds=0): 45 | """ sleep the current tasklet for a while""" 46 | if not seconds: 47 | idle() 48 | else: 49 | timer.sleep(seconds) 50 | 51 | def after_func(d, f, *args, **kwargs): 52 | """ AfterFunc waits for the duration to elapse and then calls f in 53 | its own coroutine. It returns a Timer that can be used to cancel the 54 | call using its stop method. """ 55 | 56 | def _func(now, handle): 57 | core.tasklet(f)(*args, **kwargs) 58 | core.schedule() 59 | 60 | t = timer.Timer(_func, d) 61 | t.start() 62 | return t 63 | -------------------------------------------------------------------------------- /flower/util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | import os 6 | import sys 7 | 8 | 9 | def cpu_count(): 10 | ''' 11 | Returns the number of CPUs in the system 12 | ''' 13 | if sys.platform == 'win32': 14 | try: 15 | num = int(os.environ['NUMBER_OF_PROCESSORS']) 16 | except (ValueError, KeyError): 17 | num = 0 18 | elif 'bsd' in sys.platform or sys.platform == 'darwin': 19 | comm = '/sbin/sysctl -n hw.ncpu' 20 | if sys.platform == 'darwin': 21 | comm = '/usr' + comm 22 | try: 23 | with os.popen(comm) as p: 24 | num = int(p.read()) 25 | except ValueError: 26 | num = 0 27 | else: 28 | try: 29 | num = os.sysconf('SC_NPROCESSORS_ONLN') 30 | except (ValueError, OSError, AttributeError): 31 | num = 0 32 | 33 | if num >= 1: 34 | return num 35 | else: 36 | raise NotImplementedError('cannot determine number of cpus') 37 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | greenlet 2 | pyuv 3 | six 4 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | greenlet 2 | pyuv 3 | six 4 | pytest 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup, find_packages, Extension 3 | 4 | import sys 5 | 6 | py_version = sys.version_info[:2] 7 | 8 | if py_version < (2, 6): 9 | raise RuntimeError('On Python 2, Flower requires Python 2.6 or better') 10 | 11 | 12 | CLASSIFIERS = [ 13 | 'Development Status :: 4 - Beta', 14 | 'Environment :: Web Environment', 15 | 'Intended Audience :: Developers', 16 | 'License :: OSI Approved :: MIT License', 17 | 'Operating System :: OS Independent', 18 | 'Programming Language :: Python', 19 | 'Programming Language :: Python :: 2', 20 | 'Programming Language :: Python :: 2.6', 21 | 'Programming Language :: Python :: 2.7', 22 | 'Programming Language :: Python :: 3', 23 | 'Programming Language :: Python :: 3.0', 24 | 'Programming Language :: Python :: 3.1', 25 | 'Programming Language :: Python :: 3.2', 26 | 'Topic :: Software Development :: Libraries'] 27 | 28 | 29 | # read long description 30 | with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as f: 31 | long_description = f.read() 32 | 33 | DATA_FILES = [ 34 | ('flower', ["LICENSE", "MANIFEST.in", "NOTICE", "README.rst", 35 | "THANKS", "UNLICENSE"]) 36 | ] 37 | 38 | 39 | setup(name='tulip', 40 | version='0.1.0', 41 | description = 'collection of modules to build distributed and reliable concurrent systems', 42 | long_description = long_description, 43 | classifiers = CLASSIFIERS, 44 | license = 'BSD', 45 | url = 'http://github.com/benoitc/flower', 46 | author = 'Benoit Chesneau', 47 | author_email = 'benoitc@e-engura.org', 48 | packages=find_packages(), 49 | ext_modules = [ 50 | Extension("flower.core.sync", ["flower/core/sync.c"]) 51 | ], 52 | 53 | install_requires = ['pyuv', 'greenlet', 'six'], 54 | data_files = DATA_FILES) 55 | -------------------------------------------------------------------------------- /test/test_actor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | from flower.actor import (receive, send, spawn, spawn_after, ActorRef, 6 | send_after, wrap, Actor) 7 | from flower import core 8 | from flower.time import sleep 9 | import time 10 | 11 | class Test_Actor: 12 | 13 | def test_simple(self): 14 | r_list = [] 15 | def f(): 16 | r_list.append(True) 17 | 18 | pid = spawn(f) 19 | assert isinstance(pid, ActorRef) 20 | assert pid.ref == 0 21 | assert hasattr(pid.actor, 'mailbox') 22 | 23 | sleep(0.1) 24 | core.run() 25 | 26 | assert r_list == [True] 27 | assert pid.actor is None 28 | assert pid.is_alive is False 29 | 30 | def test_wrap(self): 31 | r_list = [] 32 | def f(): 33 | r_list.append(True) 34 | 35 | t = core.tasklet(f)() 36 | assert not hasattr(t, 'mailbox') 37 | wrap(t) 38 | assert isinstance(t, Actor) 39 | assert hasattr(t, 'mailbox') 40 | assert hasattr(t, 'ref') 41 | 42 | pid = t.ref 43 | assert isinstance(pid, ActorRef) 44 | assert pid.ref == 1 45 | 46 | core.run() 47 | assert r_list == [True] 48 | assert pid.actor is None 49 | assert pid.is_alive is False 50 | 51 | 52 | def test_mailbox(self): 53 | messages = [] 54 | sources = [] 55 | def f(): 56 | while True: 57 | source, msg = receive() 58 | if not msg: 59 | break 60 | if source.ref not in sources: 61 | sources.append(source.ref) 62 | messages.append(msg) 63 | 64 | def f1(ref): 65 | msg = ['hello', ' ', 'world'] 66 | for s in msg: 67 | send(ref, s) 68 | 69 | pid0 = spawn(f) 70 | pid1 = spawn(f1, pid0) 71 | 72 | core.run() 73 | 74 | assert messages == ['hello', ' ', 'world'] 75 | assert sources == [3] 76 | 77 | def test_multiple_producers(self): 78 | messages = [] 79 | sources = [] 80 | def f(): 81 | while True: 82 | source, msg = receive() 83 | if not msg: 84 | break 85 | if source.ref not in sources: 86 | sources.append(source.ref) 87 | messages.append(msg) 88 | 89 | def f1(ref): 90 | msg = ['hello', 'world'] 91 | for s in msg: 92 | send(ref, s) 93 | 94 | def f2(ref): 95 | msg = ['brave', 'new', 'world', ''] 96 | for s in msg: 97 | send(ref, s) 98 | 99 | pid0 = spawn(f) 100 | pid1 = spawn(f1, pid0) 101 | pid2 = spawn(f2, pid0) 102 | 103 | core.run() 104 | 105 | assert len(messages) == 5 106 | assert sources == [5, 6] 107 | 108 | def test_spawn_after(self): 109 | r_list = [] 110 | def f(): 111 | r_list.append(time.time()) 112 | 113 | start = time.time() 114 | spawn_after(0.3, f) 115 | 116 | core.run() 117 | 118 | end = r_list[0] 119 | diff = end - start 120 | assert 0.29 <= diff <= 0.31 121 | 122 | def test_send_after(self): 123 | r_list = [] 124 | def f(): 125 | receive() 126 | r_list.append(time.time()) 127 | 128 | ref = spawn(f) 129 | start = time.time() 130 | send_after(0.3, ref, None) 131 | 132 | core.run() 133 | 134 | end = r_list[0] 135 | diff = end - start 136 | assert 0.29 <= diff <= 0.31 137 | -------------------------------------------------------------------------------- /test/test_channel.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | from __future__ import absolute_import 6 | 7 | import time 8 | from py.test import skip 9 | from flower import core 10 | 11 | SHOW_STRANGE = False 12 | 13 | 14 | import six 15 | from six.moves import xrange 16 | 17 | def dprint(txt): 18 | if SHOW_STRANGE: 19 | print(txt) 20 | 21 | class Test_Channel: 22 | 23 | def test_simple_channel(self): 24 | output = [] 25 | def print_(*args): 26 | output.append(args) 27 | 28 | def Sending(channel): 29 | print_("sending") 30 | channel.send("foo") 31 | 32 | def Receiving(channel): 33 | print_("receiving") 34 | print_(channel.receive()) 35 | 36 | ch=core.channel() 37 | 38 | task=core.tasklet(Sending)(ch) 39 | 40 | # Note: the argument, schedule is taking is the value, 41 | # schedule returns, not the task that runs next 42 | 43 | #core.schedule(task) 44 | core.schedule() 45 | task2=core.tasklet(Receiving)(ch) 46 | #core.schedule(task2) 47 | core.schedule() 48 | 49 | core.run() 50 | 51 | assert output == [('sending',), ('receiving',), ('foo',)] 52 | 53 | def test_task_with_channel(self): 54 | pref = {} 55 | pref[-1] = ['s0', 'r0', 's1', 'r1', 's2', 'r2', 56 | 's3', 'r3', 's4', 'r4', 's5', 'r5', 57 | 's6', 'r6', 's7', 'r7', 's8', 'r8', 58 | 's9', 'r9'] 59 | pref[0] = ['s0', 'r0', 's1', 's2', 'r1', 'r2', 60 | 's3', 's4', 'r3', 'r4', 's5', 's6', 61 | 'r5', 'r6', 's7', 's8', 'r7', 'r8', 62 | 's9', 'r9'] 63 | pref[1] = ['s0', 's1', 'r0', 's2', 'r1', 's3', 64 | 'r2', 's4', 'r3', 's5', 'r4', 's6', 65 | 'r5', 's7', 'r6', 's8', 'r7', 's9', 66 | 'r8', 'r9'] 67 | rlist = [] 68 | 69 | def f(outchan): 70 | for i in range(10): 71 | rlist.append('s%s' % i) 72 | outchan.send(i) 73 | outchan.send(-1) 74 | 75 | def g(inchan): 76 | while 1: 77 | val = inchan.receive() 78 | if val == -1: 79 | break 80 | rlist.append('r%s' % val) 81 | 82 | for preference in [-1, 0, 1]: 83 | rlist = [] 84 | ch = core.channel() 85 | ch.preference = preference 86 | t1 = core.tasklet(f)(ch) 87 | t2 = core.tasklet(g)(ch) 88 | 89 | core.run() 90 | 91 | assert len(rlist) == 20 92 | assert rlist == pref[preference] 93 | 94 | def test_send_counter(self): 95 | import random 96 | 97 | numbers = list(range(20)) 98 | random.shuffle(numbers) 99 | 100 | def counter(n, ch): 101 | for i in xrange(n): 102 | core.schedule() 103 | ch.send(n) 104 | 105 | ch = core.channel() 106 | for each in numbers: 107 | core.tasklet(counter)(each, ch) 108 | 109 | core.run() 110 | 111 | rlist = [] 112 | while ch.balance: 113 | rlist.append(ch.receive()) 114 | 115 | numbers.sort() 116 | assert rlist == numbers 117 | 118 | def test_receive_counter(self): 119 | import random 120 | 121 | numbers = list(range(20)) 122 | random.shuffle(numbers) 123 | 124 | rlist = [] 125 | def counter(n, ch): 126 | for i in xrange(n): 127 | core.schedule() 128 | ch.receive() 129 | rlist.append(n) 130 | 131 | ch = core.channel() 132 | for each in numbers: 133 | core.tasklet(counter)(each, ch) 134 | 135 | core.run() 136 | 137 | while ch.balance: 138 | ch.send(None) 139 | 140 | numbers.sort() 141 | assert rlist == numbers 142 | 143 | 144 | 145 | def test_balance_zero(self): 146 | ch=core.channel() 147 | assert ch.balance == 0 148 | 149 | def test_balance_send(self): 150 | def Sending(channel): 151 | channel.send("foo") 152 | 153 | ch=core.channel() 154 | 155 | task=core.tasklet(Sending)(ch) 156 | core.run() 157 | 158 | assert ch.balance == 1 159 | 160 | def test_balance_recv(self): 161 | def Receiving(channel): 162 | channel.receive() 163 | 164 | ch=core.channel() 165 | 166 | task=core.tasklet(Receiving)(ch) 167 | core.run() 168 | 169 | assert ch.balance == -1 170 | 171 | def test_channel_callback(self): 172 | res = [] 173 | cb = [] 174 | def callback_function(chan, task, sending, willblock): 175 | cb.append((chan, task, sending, willblock)) 176 | core.set_channel_callback(callback_function) 177 | def f(chan): 178 | chan.send('hello') 179 | val = chan.receive() 180 | res.append(val) 181 | 182 | chan = core.channel() 183 | task = core.tasklet(f)(chan) 184 | val = chan.receive() 185 | res.append(val) 186 | chan.send('world') 187 | assert res == ['hello','world'] 188 | maintask = core.getmain() 189 | assert cb == [ 190 | (chan, maintask, 0, 1), 191 | (chan, task, 1, 0), 192 | (chan, maintask, 1, 1), 193 | (chan, task, 0, 0) 194 | ] 195 | 196 | def test_bomb(self): 197 | try: 198 | 1/0 199 | except: 200 | import sys 201 | b = core.bomb(*sys.exc_info()) 202 | assert b.type is ZeroDivisionError 203 | if six.PY3: 204 | assert (str(b.value).startswith('division by zero') or 205 | str(b.value).startswith('int division')) 206 | else: 207 | assert str(b.value).startswith('integer division') 208 | assert b.traceback is not None 209 | 210 | def test_send_exception(self): 211 | def exp_sender(chan): 212 | chan.send_exception(Exception, 'test') 213 | 214 | def exp_recv(chan): 215 | try: 216 | val = chan.receive() 217 | except Exception as exp: 218 | assert exp.__class__ is Exception 219 | assert str(exp) == 'test' 220 | 221 | chan = core.channel() 222 | t1 = core.tasklet(exp_recv)(chan) 223 | t2 = core.tasklet(exp_sender)(chan) 224 | core.run() 225 | 226 | def test_send_sequence(self): 227 | res = [] 228 | lst = [1,2,3,4,5,6,None] 229 | iterable = iter(lst) 230 | chan = core.channel() 231 | def f(chan): 232 | r = chan.receive() 233 | while r: 234 | res.append(r) 235 | r = chan.receive() 236 | 237 | t = core.tasklet(f)(chan) 238 | chan.send_sequence(iterable) 239 | assert res == [1,2,3,4,5,6] 240 | 241 | def test_getruncount(self): 242 | assert core.getruncount() == 1 243 | def with_schedule(): 244 | assert core.getruncount() == 2 245 | 246 | t1 = core.tasklet(with_schedule)() 247 | assert core.getruncount() == 2 248 | core.schedule() 249 | def with_run(): 250 | assert core.getruncount() == 1 251 | 252 | t2 = core.tasklet(with_run)() 253 | core.run() 254 | 255 | def test_simple_pipe(self): 256 | def pipe(X_in, X_out): 257 | foo = X_in.receive() 258 | X_out.send(foo) 259 | 260 | X, Y = core.channel(), core.channel() 261 | t = core.tasklet(pipe)(X, Y) 262 | core.run() 263 | X.send(42) 264 | assert Y.receive() == 42 265 | 266 | def test_nested_pipe(self): 267 | dprint('tnp ==== 1') 268 | def pipe(X, Y): 269 | dprint('tnp_P ==== 1') 270 | foo = X.receive() 271 | dprint('tnp_P ==== 2') 272 | Y.send(foo) 273 | dprint('tnp_P ==== 3') 274 | 275 | def nest(X, Y): 276 | X2, Y2 = core.channel(), core.channel() 277 | t = core.tasklet(pipe)(X2, Y2) 278 | dprint('tnp_N ==== 1') 279 | X_Val = X.receive() 280 | dprint('tnp_N ==== 2') 281 | X2.send(X_Val) 282 | dprint('tnp_N ==== 3') 283 | Y2_Val = Y2.receive() 284 | dprint('tnp_N ==== 4') 285 | Y.send(Y2_Val) 286 | dprint('tnp_N ==== 5') 287 | 288 | X, Y = core.channel(), core.channel() 289 | t1 = core.tasklet(nest)(X, Y) 290 | X.send(13) 291 | dprint('tnp ==== 2') 292 | res = Y.receive() 293 | dprint('tnp ==== 3') 294 | assert res == 13 295 | if SHOW_STRANGE: 296 | raise Exception('force prints') 297 | 298 | def test_wait_two(self): 299 | """ 300 | A tasklets/channels adaptation of the test_wait_two from the 301 | logic object space 302 | """ 303 | def sleep(X, Y): 304 | dprint('twt_S ==== 1') 305 | value = X.receive() 306 | dprint('twt_S ==== 2') 307 | Y.send((X, value)) 308 | dprint('twt_S ==== 3') 309 | 310 | def wait_two(X, Y, Ret_chan): 311 | Barrier = core.channel() 312 | core.tasklet(sleep)(X, Barrier) 313 | core.tasklet(sleep)(Y, Barrier) 314 | dprint('twt_W ==== 1') 315 | ret = Barrier.receive() 316 | dprint('twt_W ==== 2') 317 | if ret[0] == X: 318 | Ret_chan.send((1, ret[1])) 319 | else: 320 | Ret_chan.send((2, ret[1])) 321 | dprint('twt_W ==== 3') 322 | 323 | X = core.channel() 324 | Y = core.channel() 325 | Ret_chan = core.channel() 326 | 327 | core.tasklet(wait_two)(X, Y, Ret_chan) 328 | 329 | dprint('twt ==== 1') 330 | Y.send(42) 331 | 332 | dprint('twt ==== 2') 333 | X.send(42) 334 | dprint('twt ==== 3') 335 | value = Ret_chan.receive() 336 | dprint('twt ==== 4') 337 | assert value == (2, 42) 338 | 339 | 340 | def test_schedule_return_value(self): 341 | 342 | def task(val): 343 | value = core.schedule(val) 344 | assert value == val 345 | 346 | core.tasklet(task)(10) 347 | core.tasklet(task)(5) 348 | 349 | core.run() 350 | 351 | def test_nonblocking_channel(self): 352 | c = core.channel(100) 353 | r1 = c.receive() 354 | r2 = c.send(True) 355 | r3 = c.receive() 356 | r4 = c.receive() 357 | 358 | assert r1 is None 359 | assert r2 is None 360 | assert r3 == True 361 | assert r4 is None 362 | 363 | def test_async_channel(self): 364 | c = core.channel(100) 365 | 366 | unblocked_sent = 0 367 | for i in range(100): 368 | c.send(True) 369 | unblocked_sent += 1 370 | 371 | assert unblocked_sent == 100 372 | assert c.balance == 100 373 | 374 | unblocked_recv = [] 375 | for i in range(100): 376 | unblocked_recv.append(c.receive()) 377 | 378 | assert len(unblocked_recv) == 100 379 | 380 | def test_async_with_blocking_channel(self): 381 | 382 | c = core.channel(10) 383 | 384 | unblocked_sent = 0 385 | for i in range(10): 386 | c.send(True) 387 | unblocked_sent += 1 388 | 389 | assert unblocked_sent == 10 390 | assert c.balance == 10 391 | 392 | r_list = [] 393 | def f(): 394 | start = time.time() 395 | c.send(True) 396 | r_list.append(start) 397 | 398 | core.tasklet(f)() 399 | 400 | 401 | unblocked_recv = [] 402 | for i in range(11): 403 | time.sleep(0.01) 404 | unblocked_recv.append(c.receive()) 405 | core.schedule() 406 | 407 | 408 | core.run() 409 | 410 | diff = time.time() - r_list[0] 411 | 412 | assert len(unblocked_recv) == 11 413 | assert diff > 0.1 414 | -------------------------------------------------------------------------------- /test/test_io.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | import os 6 | import tempfile 7 | 8 | from py.test import skip 9 | from flower import core 10 | from flower.io import IOChannel 11 | 12 | class Test_IO: 13 | 14 | def test_readable(self): 15 | (r, w) = os.pipe() 16 | 17 | ret = [] 18 | def _read(fd): 19 | c = IOChannel(r, mode=0) 20 | c.receive() 21 | ret.append(os.read(fd, 10)) 22 | c.stop() 23 | 24 | def _write(fd): 25 | os.write(fd, b"TEST") 26 | 27 | core.tasklet(_read)(r) 28 | core.tasklet(_write)(w) 29 | core.run() 30 | 31 | assert ret == [b"TEST"] 32 | -------------------------------------------------------------------------------- /test/test_local.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | import pytest 6 | from py.test import skip 7 | from flower.local import local 8 | from flower import core 9 | 10 | class Test_Local: 11 | 12 | def test_simple(self): 13 | d = local() 14 | d.a = 1 15 | assert d.a == 1 16 | d.a = 2 17 | assert d.a == 2 18 | 19 | def test_simple_delete(self): 20 | d = local() 21 | d.a = 1 22 | assert d.a == 1 23 | del d.a 24 | def f(): return d.a 25 | with pytest.raises(AttributeError): 26 | f() 27 | 28 | def test_simple_delete2(self): 29 | d = local() 30 | d.a = 1 31 | d.b = 2 32 | assert d.a == 1 33 | assert d.b == 2 34 | del d.a 35 | def f(): return d.a 36 | with pytest.raises(AttributeError): 37 | f() 38 | assert d.b == 2 39 | 40 | def test_local(self): 41 | d = local() 42 | d.a = 1 43 | 44 | r_list = [] 45 | def f(): 46 | try: 47 | d.a 48 | except AttributeError: 49 | r_list.append(True) 50 | 51 | core.tasklet(f)() 52 | core.schedule() 53 | 54 | assert r_list == [True] 55 | 56 | def test_local2(self): 57 | d = local() 58 | d.a = 1 59 | 60 | r_list = [] 61 | def f(): 62 | try: 63 | d.a 64 | except AttributeError: 65 | r_list.append(True) 66 | d.a = 2 67 | if d.a == 2: 68 | r_list.append(True) 69 | 70 | core.tasklet(f)() 71 | core.schedule() 72 | 73 | assert r_list == [True, True] 74 | assert d.a == 1 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /test/test_registry.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | import pytest 6 | 7 | from flower.actor import spawn, ActorRef 8 | from flower.registry import (registry, register, unregister, 9 | registered, Registry) 10 | from flower import core 11 | 12 | 13 | 14 | class Test_Registry: 15 | 16 | def test_simple(self): 17 | def f(): return 18 | 19 | pid = spawn(f) 20 | register("test", pid) 21 | 22 | assert pid in registry 23 | assert "test" in registry 24 | assert registry["test"] == pid 25 | assert registry[pid] == ["test"] 26 | 27 | del registry[pid] 28 | assert registry["test"] is None 29 | 30 | core.run() 31 | 32 | def test_registered(self): 33 | r_list = [] 34 | def f(): 35 | print("ici %s" % registered()) 36 | print(registry._by_ref) 37 | [r_list.append(r) for r in registered()] 38 | 39 | pid = spawn(f) 40 | register("b", pid) 41 | register("a", pid) 42 | 43 | 44 | 45 | assert 'a' in registry 46 | assert 'b' in registry 47 | assert registered(pid) == ['a', 'b'] 48 | 49 | pid.actor.switch() 50 | assert r_list == ['a', 'b'] 51 | 52 | 53 | def test_share_registry(self): 54 | r = Registry() 55 | 56 | def f(): return 57 | pid = spawn(f) 58 | register("test1", pid) 59 | 60 | assert "test1" in registry 61 | assert registry["test1"] is pid 62 | assert "test1" in r 63 | assert r["test1"] == registry["test1"] 64 | -------------------------------------------------------------------------------- /test/test_scheduler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | from __future__ import absolute_import 6 | 7 | import time 8 | from py.test import skip 9 | from flower import core 10 | 11 | SHOW_STRANGE = False 12 | 13 | 14 | import six 15 | from six.moves import xrange 16 | 17 | def dprint(txt): 18 | if SHOW_STRANGE: 19 | print(txt) 20 | 21 | class Test_Stackless: 22 | 23 | def test_simple(self): 24 | rlist = [] 25 | 26 | def f(): 27 | rlist.append('f') 28 | 29 | def g(): 30 | rlist.append('g') 31 | core.schedule() 32 | 33 | def main(): 34 | rlist.append('m') 35 | cg = core.tasklet(g)() 36 | cf = core.tasklet(f)() 37 | core.run() 38 | rlist.append('m') 39 | 40 | main() 41 | 42 | assert core.getcurrent() is core.getmain() 43 | assert rlist == 'm g f m'.split() 44 | 45 | def test_run(self): 46 | output = [] 47 | def print_(*args): 48 | output.append(args) 49 | 50 | def f(i): 51 | print_(i) 52 | 53 | core.tasklet(f)(1) 54 | core.tasklet(f)(2) 55 | core.run() 56 | 57 | assert output == [(1,), (2,)] 58 | 59 | def test_scheduling_cleanup(self): 60 | rlist = [] 61 | def f(): 62 | rlist.append('fb') 63 | core.schedule() 64 | rlist.append('fa') 65 | 66 | def g(): 67 | rlist.append('gb') 68 | core.schedule() 69 | rlist.append('ga') 70 | 71 | def h(): 72 | rlist.append('hb') 73 | core.schedule() 74 | rlist.append('ha') 75 | 76 | tf = core.tasklet(f)() 77 | tg = core.tasklet(g)() 78 | th = core.tasklet(h)() 79 | 80 | rlist.append('mb') 81 | core.run() 82 | rlist.append('ma') 83 | 84 | assert rlist == 'mb fb gb hb fa ga ha ma'.split() 85 | 86 | def test_except(self): 87 | rlist = [] 88 | def f(): 89 | rlist.append('f') 90 | return 1/0 91 | 92 | def g(): 93 | rlist.append('bg') 94 | core.schedule() 95 | rlist.append('ag') 96 | 97 | def h(): 98 | rlist.append('bh') 99 | core.schedule() 100 | rlist.append('ah') 101 | 102 | tg = core.tasklet(g)() 103 | tf = core.tasklet(f)() 104 | th = core.tasklet(h)() 105 | 106 | try: 107 | core.run() 108 | # cheating, can't test for ZeroDivisionError 109 | except ZeroDivisionError: 110 | rlist.append('E') 111 | core.schedule() 112 | core.schedule() 113 | 114 | assert rlist == "bg f E bh ag ah".split() 115 | 116 | def test_except_full(self): 117 | rlist = [] 118 | def f(): 119 | rlist.append('f') 120 | return 1/0 121 | 122 | def g(): 123 | rlist.append('bg') 124 | core.schedule() 125 | rlist.append('ag') 126 | 127 | def h(): 128 | rlist.append('bh') 129 | core.schedule() 130 | rlist.append('ah') 131 | 132 | tg = core.tasklet(g)() 133 | tf = core.tasklet(f)() 134 | th = core.tasklet(h)() 135 | 136 | try: 137 | core.run() 138 | except ZeroDivisionError: 139 | rlist.append('E') 140 | core.schedule() 141 | core.schedule() 142 | 143 | assert rlist == "bg f E bh ag ah".split() 144 | 145 | def test_kill(self): 146 | def f():pass 147 | t = core.tasklet(f)() 148 | t.kill() 149 | assert not t.alive 150 | 151 | def test_catch_taskletexit(self): 152 | # Tests if TaskletExit can be caught in the tasklet being killed. 153 | global taskletexit 154 | taskletexit = False 155 | 156 | def f(): 157 | try: 158 | core.schedule() 159 | except TaskletExit: 160 | global TaskletExit 161 | taskletexit = True 162 | raise 163 | 164 | t = core.tasklet(f)() 165 | t.run() 166 | assert t.alive 167 | t.kill() 168 | assert not t.alive 169 | assert taskletexit 170 | 171 | def test_autocatch_taskletexit(self): 172 | # Tests if TaskletExit is caught correctly in core.tasklet.setup(). 173 | def f(): 174 | core.schedule() 175 | 176 | t = core.tasklet(f)() 177 | t.run() 178 | t.kill() 179 | 180 | 181 | # tests inspired from simple core.com examples 182 | 183 | def test_construction(self): 184 | output = [] 185 | def print_(*args): 186 | output.append(args) 187 | 188 | def aCallable(value): 189 | print_("aCallable:", value) 190 | 191 | task = core.tasklet(aCallable) 192 | task.setup('Inline using setup') 193 | 194 | core.run() 195 | assert output == [("aCallable:", 'Inline using setup')] 196 | 197 | 198 | del output[:] 199 | task = core.tasklet(aCallable) 200 | task('Inline using ()') 201 | 202 | core.run() 203 | assert output == [("aCallable:", 'Inline using ()')] 204 | 205 | del output[:] 206 | task = core.tasklet() 207 | task.bind(aCallable) 208 | task('Bind using ()') 209 | 210 | core.run() 211 | assert output == [("aCallable:", 'Bind using ()')] 212 | 213 | def test_run(self): 214 | output = [] 215 | def print_(*args): 216 | output.append(args) 217 | 218 | def f(i): 219 | print_(i) 220 | 221 | core.tasklet(f)(1) 222 | core.tasklet(f)(2) 223 | core.run() 224 | 225 | assert output == [(1,), (2,)] 226 | 227 | def test_schedule(self): 228 | output = [] 229 | def print_(*args): 230 | output.append(args) 231 | 232 | def f(i): 233 | print_(i) 234 | 235 | core.tasklet(f)(1) 236 | core.tasklet(f)(2) 237 | core.schedule() 238 | 239 | assert output == [(1,), (2,)] 240 | 241 | 242 | def test_cooperative(self): 243 | output = [] 244 | def print_(*args): 245 | output.append(args) 246 | 247 | def Loop(i): 248 | for x in range(3): 249 | core.schedule() 250 | print_("schedule", i) 251 | 252 | core.tasklet(Loop)(1) 253 | core.tasklet(Loop)(2) 254 | core.run() 255 | 256 | assert output == [('schedule', 1), ('schedule', 2), 257 | ('schedule', 1), ('schedule', 2), 258 | ('schedule', 1), ('schedule', 2),] 259 | 260 | 261 | def test_schedule_callback(self): 262 | res = [] 263 | cb = [] 264 | def schedule_cb(prev, next): 265 | cb.append((prev, next)) 266 | 267 | core.set_schedule_callback(schedule_cb) 268 | def f(i): 269 | res.append('A_%s' % i) 270 | core.schedule() 271 | res.append('B_%s' % i) 272 | 273 | t1 = core.tasklet(f)(1) 274 | t2 = core.tasklet(f)(2) 275 | maintask = core.getmain() 276 | core.run() 277 | assert res == ['A_1', 'A_2', 'B_1', 'B_2'] 278 | assert len(cb) == 5 279 | assert cb[0] == (maintask, t1) 280 | assert cb[1] == (t1, t2) 281 | assert cb[2] == (t2, t1) 282 | assert cb[3] == (t1, t2) 283 | assert cb[4] == (t2, maintask) 284 | 285 | def test_getruncount(self): 286 | assert core.getruncount() == 1 287 | def with_schedule(): 288 | assert core.getruncount() == 2 289 | 290 | t1 = core.tasklet(with_schedule)() 291 | assert core.getruncount() == 2 292 | core.schedule() 293 | def with_run(): 294 | assert core.getruncount() == 1 295 | 296 | t2 = core.tasklet(with_run)() 297 | core.run() 298 | 299 | def test_schedule_return(self): 300 | def f():pass 301 | t1= core.tasklet(f)() 302 | r = core.schedule() 303 | assert r is core.getmain() 304 | t2 = core.tasklet(f)() 305 | r = core.schedule('test') 306 | assert r == 'test' 307 | 308 | def test_schedule_return_value(self): 309 | 310 | def task(val): 311 | value = core.schedule(val) 312 | assert value == val 313 | 314 | core.tasklet(task)(10) 315 | core.tasklet(task)(5) 316 | 317 | core.run() 318 | -------------------------------------------------------------------------------- /test/test_sync.py: -------------------------------------------------------------------------------- 1 | 2 | from flower.core.sync import * 3 | 4 | def test_increment(): 5 | i = 0 6 | i = increment(i) 7 | assert i == 1 8 | 9 | i = increment(i) 10 | assert i == 2 11 | 12 | 13 | def test_decrement(): 14 | i = 0 15 | i = decrement(i) 16 | assert i == -1 17 | 18 | i = decrement(i) 19 | assert i == -2 20 | 21 | def test_combine(): 22 | i = 1 23 | i = decrement(i) 24 | assert i == 0 25 | i = increment(i) 26 | assert i == 1 27 | 28 | 29 | def test_read(): 30 | i = 10 31 | v = atomic_read(i) 32 | assert i == v 33 | 34 | def test_compare_and_swap(): 35 | a, b = 1, 2 36 | a = compare_and_swap(a, b) 37 | 38 | assert a == b 39 | 40 | a, b = 1, 1 41 | r = compare_and_swap(a, b) 42 | assert a == 1 43 | -------------------------------------------------------------------------------- /test/test_time.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | import os 6 | import time 7 | 8 | import pytest 9 | from py.test import skip 10 | 11 | from flower import core 12 | from flower.time import Ticker, sleep 13 | 14 | IS_TRAVIS = False 15 | 16 | if os.environ.get('TRAVIS') and os.environ.get('TRAVIS') is not None: 17 | IS_TRAVIS = True 18 | 19 | class Test_Time: 20 | 21 | def test_ticker(self): 22 | rlist = [] 23 | 24 | def f(): 25 | ticker = Ticker(0.1) 26 | i = 0 27 | while True: 28 | if i == 3: break 29 | t = ticker.receive() 30 | rlist.append(t) 31 | i += 1 32 | ticker.stop() 33 | 34 | tf = core.tasklet(f)() 35 | core.run() 36 | 37 | assert len(rlist) == 3 38 | 39 | 40 | def test_simple_sleep(self): 41 | if IS_TRAVIS: 42 | skip() 43 | start = time.time() 44 | sleep(0.02) 45 | delay = time.time() - start 46 | assert 0.02 - 0.004 <= delay < 0.02 + 0.02, delay 47 | 48 | 49 | def test_sleep(self): 50 | rlist = [] 51 | 52 | def f(): 53 | sleep(0.2) 54 | rlist.append('a') 55 | 56 | def f1(): 57 | rlist.append('b') 58 | 59 | core.tasklet(f)() 60 | core.tasklet(f1)() 61 | core.run() 62 | 63 | assert rlist == ['b', 'a'] 64 | 65 | 66 | def test_sleep2(self): 67 | rlist = [] 68 | 69 | def f(): 70 | sleep() 71 | rlist.append('a') 72 | 73 | def f1(): 74 | rlist.append('b') 75 | 76 | core.tasklet(f)() 77 | core.tasklet(f1)() 78 | core.run() 79 | 80 | assert rlist == ['b', 'a'] 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /test/test_timer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of flower. See the NOTICE for more information. 4 | 5 | import time 6 | 7 | from flower.core.util import from_nanotime 8 | from flower.core import run, tasklet 9 | from flower.core.timer import Timer, sleep 10 | 11 | def _wait(): 12 | time.sleep(0.01) 13 | 14 | 15 | def test_simple_timer(): 16 | r_list = [] 17 | def _func(now, t): 18 | r_list.append(from_nanotime(now)) 19 | 20 | now = time.time() 21 | t = Timer(_func, 0.1) 22 | t.start() 23 | run() 24 | delay = r_list[0] 25 | assert (now + 0.09) <= delay <= (now + 0.11), delay 26 | 27 | 28 | def test_multiple_timer(): 29 | r1 = [] 30 | def f(now, t): 31 | r1.append(from_nanotime(now)) 32 | 33 | r2 = [] 34 | def f1(now, t): 35 | r2.append(from_nanotime(now)) 36 | 37 | now = time.time() 38 | 39 | t = Timer(f, 0.4) 40 | t.start() 41 | 42 | t1 = Timer(f1, 0.1) 43 | t1.start() 44 | 45 | run() 46 | assert r1[0] > r2[0] 47 | assert (now + 0.39) <= r1[0] <= (now + 0.41), r1[0] 48 | assert (now + 0.09) <= r2[0] <= (now + 0.11), r2[0] 49 | 50 | 51 | def test_repeat(): 52 | r = [] 53 | def f(now, t): 54 | if len(r) == 3: 55 | t.stop() 56 | return 57 | r.append(now) 58 | 59 | t = Timer(f, 0.01, 0.01) 60 | t.start() 61 | run() 62 | assert len(r) == 3 63 | assert r[2] > r[1] 64 | assert r[1] > r[0] 65 | 66 | def test_sleep(): 67 | start = time.time() 68 | sleep(0.1) 69 | diff = time.time() - start 70 | assert 0.09 <= diff <= 0.11 71 | 72 | 73 | def test_multiple_sleep(): 74 | r1 = [] 75 | def f(): 76 | sleep(0.4) 77 | r1.append(time.time()) 78 | 79 | r2 = [] 80 | def f1(): 81 | sleep(0.1) 82 | r2.append(time.time()) 83 | 84 | tasklet(f)() 85 | tasklet(f1)() 86 | 87 | now = time.time() 88 | run() 89 | assert r1[0] > r2[0] 90 | assert (now + 0.39) <= r1[0] <= (now + 0.41), r1[0] 91 | assert (now + 0.09) <= r2[0] <= (now + 0.11), r2[0] 92 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py26, py27, py31, py32 3 | 4 | [testenv] 5 | deps=pytest 6 | commands = py.test [] 7 | --------------------------------------------------------------------------------