├── .gitignore ├── AUTHORS ├── LICENSE ├── MANIFEST.in ├── README.rst ├── examples ├── poll.py ├── reqrep.py └── simple.py ├── gevent_zeromq ├── __init__.py ├── core.py ├── core.pyx ├── poll.py └── tests.py ├── requirements.txt ├── setup.py └── setupegg.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.lst 3 | *.o 4 | *.so 5 | /build/ 6 | /dist/ 7 | MANIFEST -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Travis Cline 2 | Ryan Kelly 3 | Zachary Voase 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Travis Cline All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | Neither the name of Travis Cline nor the names of its contributors 12 | may be used to endorse or promote products derived from this software 13 | without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | recursive-include examples *py 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | COMPLETE! 2 | =========== 3 | This work has been merged upstream into `pyzmq `_ 4 | ---------------------------------------------------------------------------------- 5 | 6 | ============= 7 | gevent-zeromq 8 | ============= 9 | 10 | This library wraps pyzmq to make it compatible with gevent. ØMQ socket 11 | operations that would normally block the current thread will only block the 12 | current greenlet instead. 13 | 14 | Requirements 15 | ------------ 16 | 17 | * pyzmq==2.2.0 18 | * gevent (compatible with 1.0 pre-releases as well) 19 | 20 | 21 | Usage 22 | ----- 23 | 24 | Instead of importing zmq directly, do so in the following manner: 25 | 26 | .. 27 | 28 | from gevent_zeromq import zmq 29 | 30 | 31 | Any calls that would have blocked the current thread will now only block the 32 | current green thread. 33 | 34 | 35 | About 36 | ----- 37 | 38 | This compatibility is accomplished by ensuring the nonblocking flag is set 39 | before any blocking operation and the ØMQ file descriptor is polled internally 40 | to trigger needed events. 41 | 42 | Will build with cython if available, decreasing overhead. 43 | 44 | License 45 | ------- 46 | See LICENSE (New BSD) 47 | -------------------------------------------------------------------------------- /examples/poll.py: -------------------------------------------------------------------------------- 1 | import gevent 2 | from gevent_zeromq import zmq 3 | 4 | # Connect to both receiving sockets and send 10 messages 5 | def sender(): 6 | 7 | sender = context.socket(zmq.PUSH) 8 | sender.connect('inproc://polltest1') 9 | sender.connect('inproc://polltest2') 10 | 11 | for i in xrange(10): 12 | sender.send('test %d' % i) 13 | gevent.sleep(1) 14 | 15 | 16 | # create zmq context, and bind to pull sockets 17 | context = zmq.Context() 18 | receiver1 = context.socket(zmq.PULL) 19 | receiver1.bind('inproc://polltest1') 20 | receiver2 = context.socket(zmq.PULL) 21 | receiver2.bind('inproc://polltest2') 22 | 23 | gevent.spawn(sender) 24 | 25 | # Create poller and register both reciever sockets 26 | poller = zmq.Poller() 27 | poller.register(receiver1, zmq.POLLIN) 28 | poller.register(receiver2, zmq.POLLIN) 29 | 30 | # Read 10 messages from both reciever sockets 31 | msgcnt = 0 32 | while msgcnt < 10: 33 | socks = dict(poller.poll()) 34 | if receiver1 in socks and socks[receiver1] == zmq.POLLIN: 35 | print "Message from receiver1: %s" % receiver1.recv() 36 | msgcnt += 1 37 | 38 | if receiver2 in socks and socks[receiver2] == zmq.POLLIN: 39 | print "Message from receiver2: %s" % receiver2.recv() 40 | msgcnt += 1 41 | 42 | print "%d messages received" % msgcnt 43 | 44 | 45 | -------------------------------------------------------------------------------- /examples/reqrep.py: -------------------------------------------------------------------------------- 1 | """ 2 | Complex example which is a combination of the rr* examples from the zguide. 3 | """ 4 | from gevent import spawn 5 | from gevent_zeromq import zmq 6 | 7 | # server 8 | context = zmq.Context() 9 | socket = context.socket(zmq.REP) 10 | socket.connect("tcp://localhost:5560") 11 | 12 | def serve(socket): 13 | while True: 14 | message = socket.recv() 15 | print "Received request: ", message 16 | socket.send("World") 17 | server = spawn(serve, socket) 18 | 19 | 20 | # client 21 | context = zmq.Context() 22 | socket = context.socket(zmq.REQ) 23 | socket.connect("tcp://localhost:5559") 24 | 25 | # Do 10 requests, waiting each time for a response 26 | def client(): 27 | for request in range(1,10): 28 | socket.send("Hello") 29 | message = socket.recv() 30 | print "Received reply ", request, "[", message, "]" 31 | 32 | 33 | # broker 34 | frontend = context.socket(zmq.XREP) 35 | backend = context.socket(zmq.XREQ); 36 | frontend.bind("tcp://*:5559") 37 | backend.bind("tcp://*:5560") 38 | 39 | def proxy(socket_from, socket_to): 40 | while True: 41 | m = socket_from.recv_multipart() 42 | socket_to.send_multipart(m) 43 | 44 | a = spawn(proxy, frontend, backend) 45 | b = spawn(proxy, backend, frontend) 46 | 47 | spawn(client).join() 48 | -------------------------------------------------------------------------------- /examples/simple.py: -------------------------------------------------------------------------------- 1 | from gevent import spawn, spawn_later 2 | from gevent_zeromq import zmq 3 | 4 | # server 5 | ctx = zmq.Context() 6 | sock = ctx.socket(zmq.PUSH) 7 | sock.bind('ipc:///tmp/zmqtest') 8 | 9 | spawn(sock.send_pyobj, ('this', 'is', 'a', 'python', 'tuple')) 10 | spawn_later(1, sock.send_pyobj, {'hi': 1234}) 11 | spawn_later(2, sock.send_pyobj, ({'this': ['is a more complicated object', ':)']}, 42, 42, 42)) 12 | spawn_later(3, sock.send_pyobj, 'foobar') 13 | spawn_later(4, sock.send_pyobj, 'quit') 14 | 15 | 16 | # client 17 | ctx = zmq.Context() # create a new context to kick the wheels 18 | sock = ctx.socket(zmq.PULL) 19 | sock.connect('ipc:///tmp/zmqtest') 20 | 21 | def get_objs(sock): 22 | while True: 23 | o = sock.recv_pyobj() 24 | print 'received python object:', o 25 | if o == 'quit': 26 | print 'exiting.' 27 | break 28 | 29 | def print_every(s, t=None): 30 | print s 31 | if t: 32 | spawn_later(t, print_every, s, t) 33 | 34 | print_every('printing every half second', 0.5) 35 | spawn(get_objs, sock).join() 36 | 37 | -------------------------------------------------------------------------------- /gevent_zeromq/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """gevent_zmq - gevent compatibility with zeromq. 3 | 4 | Usage 5 | ----- 6 | 7 | Instead of importing zmq directly, do so in the following manner: 8 | 9 | .. 10 | 11 | from gevent_zeromq import zmq 12 | 13 | 14 | Any calls that would have blocked the current thread will now only block the 15 | current green thread. 16 | 17 | This compatibility is accomplished by ensuring the nonblocking flag is set 18 | before any blocking operation and the ØMQ file descriptor is polled internally 19 | to trigger needed events. 20 | """ 21 | 22 | from zmq import * 23 | from zmq import devices 24 | import gevent_zeromq.core as zmq 25 | from gevent_zeromq.poll import GreenPoller 26 | 27 | zmq.Socket = zmq.GreenSocket 28 | zmq.Context = zmq.GreenContext 29 | zmq.Poller = GreenPoller 30 | Socket = zmq.GreenSocket 31 | Context = zmq.GreenContext 32 | Poller = GreenPoller 33 | 34 | def monkey_patch(): 35 | """ 36 | Monkey patches `zmq.Context` and `zmq.Socket` 37 | 38 | If test_suite is True, the pyzmq test suite will be patched for 39 | compatibility as well. 40 | """ 41 | ozmq = __import__('zmq') 42 | ozmq.Socket = zmq.Socket 43 | ozmq.Context = zmq.Context 44 | ozmq.Poller = zmq.Poller 45 | ioloop = __import__('zmq.eventloop.ioloop') 46 | ioloop.Poller = zmq.Poller 47 | 48 | __all__ = zmq.__all__ + ['monkey_patch'] 49 | -------------------------------------------------------------------------------- /gevent_zeromq/core.py: -------------------------------------------------------------------------------- 1 | """This module wraps the :class:`Socket` and :class:`Context` found in :mod:`pyzmq ` to be non blocking 2 | """ 3 | import zmq 4 | from zmq import * 5 | from zmq import devices 6 | __all__ = zmq.__all__ 7 | 8 | import gevent 9 | from gevent import select 10 | from gevent.event import AsyncResult 11 | from gevent.hub import get_hub 12 | 13 | 14 | class GreenSocket(Socket): 15 | """Green version of :class:`zmq.core.socket.Socket` 16 | 17 | The following methods are overridden: 18 | 19 | * send 20 | * send_multipart 21 | * recv 22 | * recv_multipart 23 | 24 | To ensure that the ``zmq.NOBLOCK`` flag is set and that sending or recieving 25 | is deferred to the hub if a ``zmq.EAGAIN`` (retry) error is raised. 26 | 27 | The `__state_changed` method is triggered when the zmq.FD for the socket is 28 | marked as readable and triggers the necessary read and write events (which 29 | are waited for in the recv and send methods). 30 | 31 | Some double underscore prefixes are used to minimize pollution of 32 | :class:`zmq.core.socket.Socket`'s namespace. 33 | """ 34 | 35 | def __init__(self, context, socket_type): 36 | self.__in_send_multipart = False 37 | self.__in_recv_multipart = False 38 | self.__setup_events() 39 | 40 | def __del__(self): 41 | self.close() 42 | 43 | def close(self, linger=None): 44 | super(GreenSocket, self).close(linger) 45 | self.__cleanup_events() 46 | 47 | def __cleanup_events(self): 48 | # close the _state_event event, keeps the number of active file descriptors down 49 | if getattr(self, '_state_event', None): 50 | try: 51 | self._state_event.stop() 52 | except AttributeError, e: 53 | # gevent<1.0 compat 54 | self._state_event.cancel() 55 | 56 | # if the socket has entered a close state resume any waiting greenlets 57 | if hasattr(self, '__writable'): 58 | self.__writable.set() 59 | self.__readable.set() 60 | 61 | def __setup_events(self): 62 | self.__readable = AsyncResult() 63 | self.__writable = AsyncResult() 64 | try: 65 | self._state_event = get_hub().loop.io(self.getsockopt(FD), 1) # read state watcher 66 | self._state_event.start(self.__state_changed) 67 | except AttributeError: 68 | # for gevent<1.0 compatibility 69 | from gevent.core import read_event 70 | self._state_event = read_event(self.getsockopt(FD), self.__state_changed, persist=True) 71 | 72 | def __state_changed(self, event=None, _evtype=None): 73 | if self.closed: 74 | self.__cleanup_events() 75 | return 76 | try: 77 | events = super(GreenSocket, self).getsockopt(zmq.EVENTS) 78 | except ZMQError, exc: 79 | self.__writable.set_exception(exc) 80 | self.__readable.set_exception(exc) 81 | else: 82 | if events & zmq.POLLOUT: 83 | self.__writable.set() 84 | if events & zmq.POLLIN: 85 | self.__readable.set() 86 | 87 | def _wait_write(self): 88 | self.__writable = AsyncResult() 89 | try: 90 | self.__writable.get(timeout=1) 91 | except gevent.Timeout: 92 | self.__writable.set() 93 | 94 | def _wait_read(self): 95 | self.__readable = AsyncResult() 96 | try: 97 | self.__readable.get(timeout=1) 98 | except gevent.Timeout: 99 | self.__readable.set() 100 | 101 | def send(self, data, flags=0, copy=True, track=False): 102 | # if we're given the NOBLOCK flag act as normal and let the EAGAIN get raised 103 | if flags & zmq.NOBLOCK: 104 | try: 105 | msg = super(GreenSocket, self).send(data, flags, copy, track) 106 | finally: 107 | if not self.__in_send_multipart: 108 | self.__state_changed() 109 | return msg 110 | 111 | # ensure the zmq.NOBLOCK flag is part of flags 112 | flags |= zmq.NOBLOCK 113 | while True: # Attempt to complete this operation indefinitely, blocking the current greenlet 114 | try: 115 | # attempt the actual call 116 | return super(GreenSocket, self).send(data, flags, copy, track) 117 | except zmq.ZMQError, e: 118 | # if the raised ZMQError is not EAGAIN, reraise 119 | if e.errno != zmq.EAGAIN: 120 | raise 121 | # defer to the event loop until we're notified the socket is writable 122 | self._wait_write() 123 | 124 | def recv(self, flags=0, copy=True, track=False): 125 | if flags & zmq.NOBLOCK: 126 | try: 127 | msg = super(GreenSocket, self).recv(flags, copy, track) 128 | finally: 129 | if not self.__in_recv_multipart: 130 | self.__state_changed() 131 | return msg 132 | 133 | flags |= zmq.NOBLOCK 134 | while True: 135 | try: 136 | return super(GreenSocket, self).recv(flags, copy, track) 137 | except zmq.ZMQError, e: 138 | if e.errno != zmq.EAGAIN: 139 | if not self.__in_recv_multipart: 140 | self.__state_changed() 141 | raise 142 | else: 143 | if not self.__in_recv_multipart: 144 | self.__state_changed() 145 | return msg 146 | self._wait_read() 147 | 148 | def send_multipart(self, *args, **kwargs): 149 | """wrap send_multipart to prevent state_changed on each partial send""" 150 | self.__in_send_multipart = True 151 | try: 152 | msg = super(GreenSocket, self).send_multipart(*args, **kwargs) 153 | finally: 154 | self.__in_send_multipart = False 155 | self.__state_changed() 156 | return msg 157 | 158 | def recv_multipart(self, *args, **kwargs): 159 | """wrap recv_multipart to prevent state_changed on each partial recv""" 160 | self.__in_recv_multipart = True 161 | try: 162 | msg = super(GreenSocket, self).recv_multipart(*args, **kwargs) 163 | finally: 164 | self.__in_recv_multipart = False 165 | self.__state_changed() 166 | return msg 167 | 168 | 169 | class GreenContext(Context): 170 | """Replacement for :class:`zmq.core.context.Context` 171 | 172 | Ensures that the greened Socket above is used in calls to `socket`. 173 | """ 174 | _socket_class = GreenSocket 175 | 176 | -------------------------------------------------------------------------------- /gevent_zeromq/core.pyx: -------------------------------------------------------------------------------- 1 | """This module wraps the :class:`Socket` and :class:`Context` found in :mod:`pyzmq ` to be non blocking 2 | """ 3 | import zmq 4 | from zmq import * 5 | from zmq import devices 6 | __all__ = zmq.__all__ 7 | 8 | import gevent 9 | from gevent import select 10 | from gevent.event import AsyncResult 11 | from gevent.hub import get_hub 12 | 13 | from zmq.core.context cimport Context as _Context 14 | from zmq.core.socket cimport Socket as _Socket 15 | 16 | 17 | cdef class GreenSocket(_Socket): 18 | """Green version of :class:`zmq.core.socket.Socket` 19 | 20 | The following methods are overridden: 21 | 22 | * send 23 | * send_multipart 24 | * recv 25 | * recv_multipart 26 | 27 | To ensure that the ``zmq.NOBLOCK`` flag is set and that sending or recieving 28 | is deferred to the hub if a ``zmq.EAGAIN`` (retry) error is raised. 29 | 30 | The `__state_changed` method is triggered when the zmq.FD for the socket is 31 | marked as readable and triggers the necessary read and write events (which 32 | are waited for in the recv and send methods). 33 | 34 | Some double underscore prefixes are used to minimize pollution of 35 | :class:`zmq.core.socket.Socket`'s namespace. 36 | """ 37 | cdef object __readable 38 | cdef object __writable 39 | cdef object __in_send_mulitpart 40 | cdef object __in_recv_mulitpart 41 | cdef public object _state_event 42 | 43 | def __init__(self, context, int socket_type): 44 | self.__in_send_multipart = False 45 | self.__in_recv_multipart = False 46 | self.__setup_events() 47 | 48 | def __del__(self): 49 | self.close() 50 | 51 | def close(self, linger=None): 52 | super(GreenSocket, self).close(linger) 53 | self.__cleanup_events() 54 | 55 | def __cleanup_events(self): 56 | # close the _state_event event, keeps the number of active file descriptors down 57 | if getattr(self, '_state_event', None): 58 | try: 59 | self._state_event.stop() 60 | except AttributeError, e: 61 | # gevent<1.0 compat 62 | self._state_event.cancel() 63 | 64 | # if the socket has entered a close state resume any waiting greenlets 65 | if hasattr(self, '__writable'): 66 | self.__writable.set() 67 | self.__readable.set() 68 | 69 | def __setup_events(self): 70 | self.__readable = AsyncResult() 71 | self.__writable = AsyncResult() 72 | try: 73 | self._state_event = get_hub().loop.io(self.getsockopt(FD), 1) # read state watcher 74 | self._state_event.start(self.__state_changed) 75 | except AttributeError: 76 | # for gevent<1.0 compatibility 77 | from gevent.core import read_event 78 | self._state_event = read_event(self.getsockopt(FD), self.__state_changed, persist=True) 79 | 80 | def __state_changed(self, event=None, _evtype=None): 81 | cdef int events 82 | if self.closed: 83 | self.__cleanup_events() 84 | return 85 | try: 86 | events = super(GreenSocket, self).getsockopt(EVENTS) 87 | except ZMQError, exc: 88 | self.__writable.set_exception(exc) 89 | self.__readable.set_exception(exc) 90 | else: 91 | if events & POLLOUT: 92 | self.__writable.set() 93 | if events & POLLIN: 94 | self.__readable.set() 95 | 96 | cdef _wait_write(self) with gil: 97 | self.__writable = AsyncResult() 98 | try: 99 | self.__writable.get(timeout=1) 100 | except gevent.Timeout: 101 | self.__writable.set() 102 | 103 | cdef _wait_read(self) with gil: 104 | self.__readable = AsyncResult() 105 | try: 106 | self.__readable.get(timeout=1) 107 | except gevent.Timeout: 108 | self.__readable.set() 109 | 110 | cpdef object send(self, object data, int flags=0, copy=True, track=False): 111 | # if we're given the NOBLOCK flag act as normal and let the EAGAIN get raised 112 | if flags & NOBLOCK: 113 | return _Socket.send(self, data, flags, copy, track) 114 | # ensure the zmq.NOBLOCK flag is part of flags 115 | flags = flags | NOBLOCK 116 | while True: # Attempt to complete this operation indefinitely, blocking the current greenlet 117 | try: 118 | # attempt the actual call 119 | return _Socket.send(self, data, flags, copy, track) 120 | except ZMQError, e: 121 | # if the raised ZMQError is not EAGAIN, reraise 122 | if e.errno != EAGAIN: 123 | raise 124 | # defer to the event loop until we're notified the socket is writable 125 | self._wait_write() 126 | 127 | cpdef object recv(self, int flags=0, copy=True, track=False): 128 | if flags & NOBLOCK: 129 | return _Socket.recv(self, flags, copy, track) 130 | flags = flags | NOBLOCK 131 | while True: 132 | try: 133 | return _Socket.recv(self, flags, copy, track) 134 | except ZMQError, e: 135 | if e.errno != EAGAIN: 136 | raise 137 | self._wait_read() 138 | 139 | def send_multipart(self, *args, **kwargs): 140 | """wrap send_multipart to prevent state_changed on each partial send""" 141 | self.__in_send_multipart = True 142 | try: 143 | msg = super(GreenSocket, self).send_multipart(*args, **kwargs) 144 | finally: 145 | self.__in_send_multipart = False 146 | self.__state_changed() 147 | return msg 148 | 149 | def recv_multipart(self, *args, **kwargs): 150 | """wrap recv_multipart to prevent state_changed on each partial recv""" 151 | self.__in_recv_multipart = True 152 | try: 153 | msg = super(GreenSocket, self).recv_multipart(*args, **kwargs) 154 | finally: 155 | self.__in_recv_multipart = False 156 | self.__state_changed() 157 | return msg 158 | 159 | 160 | class GreenContext(_Context): 161 | """Replacement for :class:`zmq.core.context.Context` 162 | 163 | Ensures that the greened Socket below is used in calls to `socket`. 164 | """ 165 | _socket_class = GreenSocket 166 | -------------------------------------------------------------------------------- /gevent_zeromq/poll.py: -------------------------------------------------------------------------------- 1 | import zmq 2 | from zmq import Poller 3 | import gevent 4 | from gevent import select 5 | 6 | 7 | class GreenPoller(Poller): 8 | """Replacement for :class:`zmq.core.Poller` 9 | 10 | Ensures that the greened Poller below is used in calls to 11 | :meth:`zmq.core.Poller.poll`. 12 | """ 13 | 14 | def _get_descriptors(self): 15 | """Returns three elements tuple with socket descriptors ready 16 | for gevent.select.select 17 | """ 18 | rlist = [] 19 | wlist = [] 20 | xlist = [] 21 | 22 | for socket, flags in self.sockets.items(): 23 | if isinstance(socket, zmq.Socket): 24 | rlist.append(socket.getsockopt(zmq.FD)) 25 | continue 26 | elif isinstance(socket, int): 27 | fd = socket 28 | elif hasattr(socket, 'fileno'): 29 | try: 30 | fd = int(socket.fileno()) 31 | except: 32 | raise ValueError('fileno() must return an valid integer fd') 33 | else: 34 | raise TypeError('Socket must be a 0MQ socket, an integer fd ' 35 | 'or have a fileno() method: %r' % socket) 36 | 37 | if flags & zmq.POLLIN: 38 | rlist.append(fd) 39 | if flags & zmq.POLLOUT: 40 | wlist.append(fd) 41 | if flags & zmq.POLLERR: 42 | xlist.append(fd) 43 | 44 | return (rlist, wlist, xlist) 45 | 46 | def poll(self, timeout=-1): 47 | """Overridden method to ensure that the green version of 48 | Poller is used. 49 | 50 | Behaves the same as :meth:`zmq.core.Poller.poll` 51 | """ 52 | 53 | if timeout is None: 54 | timeout = -1 55 | 56 | if timeout < 0: 57 | timeout = -1 58 | 59 | rlist = None 60 | wlist = None 61 | xlist = None 62 | 63 | if timeout > 0: 64 | tout = gevent.Timeout.start_new(timeout/1000.0) 65 | 66 | try: 67 | # Loop until timeout or events available 68 | rlist, wlist, xlist = self._get_descriptors() 69 | while True: 70 | events = super(GreenPoller, self).poll(0) 71 | if events or timeout == 0: 72 | return events 73 | 74 | # wait for activity on sockets in a green way 75 | select.select(rlist, wlist, xlist) 76 | 77 | except gevent.Timeout, t: 78 | if t is not tout: 79 | raise 80 | return [] 81 | finally: 82 | if timeout > 0: 83 | tout.cancel() 84 | -------------------------------------------------------------------------------- /gevent_zeromq/tests.py: -------------------------------------------------------------------------------- 1 | import gevent 2 | from gevent_zeromq import zmq 3 | 4 | try: 5 | from gevent_utils import BlockingDetector 6 | gevent.spawn(BlockingDetector(25)) 7 | except ImportError: 8 | print 'If you encounter hangs consider installing gevent_utils' 9 | 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyzmq==2.2.0 2 | gevent 3 | cython 4 | nose 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from distutils.core import Command, setup 5 | from distutils.command.build_ext import build_ext 6 | from traceback import print_exc 7 | 8 | cython_available = False 9 | try: 10 | from Cython.Distutils import build_ext 11 | from Cython.Distutils.extension import Extension 12 | cython_available = True 13 | except ImportError, e: 14 | pass 15 | 16 | try: 17 | import nose 18 | except ImportError: 19 | nose = None 20 | 21 | def get_ext_modules(): 22 | if not cython_available: 23 | print 'WARNING: cython not available, proceeding with pure python implementation.' 24 | return [] 25 | try: 26 | import gevent 27 | except ImportError, e: 28 | print 'WARNING: gevent must be installed to build cython version of gevent-zeromq (%s).' % e 29 | return [] 30 | try: 31 | import zmq 32 | except ImportError, e: 33 | print 'WARNING: pyzmq(==2.2.0) must be installed to build cython version of gevent-zeromq (%s).' % e 34 | return [] 35 | 36 | return [Extension('gevent_zeromq.core', ['gevent_zeromq/core.pyx'], include_dirs=zmq.get_includes())] 37 | 38 | class TestCommand(Command): 39 | """Custom distutils command to run the test suite.""" 40 | 41 | user_options = [] 42 | 43 | def initialize_options(self): 44 | pass 45 | 46 | def finalize_options(self): 47 | pass 48 | 49 | def run(self): 50 | # crude check for inplace build: 51 | try: 52 | import gevent_zeromq 53 | except ImportError: 54 | print_exc() 55 | print ("Could not import gevent_zeromq!") 56 | print ("You must build gevent_zeromq with 'python setup.py build_ext --inplace' for 'python setup.py test' to work.") 57 | print ("If you did build gevent_zeromq in-place, then this is a real error.") 58 | sys.exit(1) 59 | 60 | import zmq 61 | zmq_tests = os.path.join(os.path.dirname(zmq.__file__), 'tests') 62 | tests = os.path.join(os.path.dirname(gevent_zeromq.__file__), 'tests.py') 63 | import zmq.tests 64 | zmq.tests.have_gevent = True 65 | zmq.tests.gzmq = gevent_zeromq 66 | 67 | if nose is None: 68 | print ("nose unavailable, skipping tests.") 69 | else: 70 | return nose.core.TestProgram(argv=["", '-vvs', tests, zmq_tests]) 71 | 72 | __version__ = (0, 2, 5) 73 | 74 | setup( 75 | name = 'gevent_zeromq', 76 | version = '.'.join([str(x) for x in __version__]), 77 | packages = ['gevent_zeromq'], 78 | cmdclass = {'build_ext': build_ext, 'test': TestCommand}, 79 | ext_modules = get_ext_modules(), 80 | author = 'Travis Cline', 81 | author_email = 'travis.cline@gmail.com', 82 | url = 'http://github.com/traviscline/gevent-zeromq', 83 | description = 'gevent compatibility layer for pyzmq', 84 | long_description=open('README.rst').read(), 85 | install_requires = ['pyzmq==2.2.0', 'gevent'], 86 | license = 'New BSD', 87 | ) 88 | -------------------------------------------------------------------------------- /setupegg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Wrapper to run setup.py using setuptools.""" 3 | 4 | import os, sys 5 | 6 | # now, import setuptools and call the actual setup 7 | import setuptools 8 | try: 9 | execfile('setup.py') 10 | except NameError: 11 | exec( open('setup.py','rb').read() ) 12 | 13 | --------------------------------------------------------------------------------