First stab at zeromq support. This consists of:
A new hub: This closely mirrors the poll hub with some of the internal logic changed to reflect zmq's flags. A green module for zmq: This subclasses Context and Socket to ensure calls are non blocking. A (very sparse) beginings of a test module. An example: A melding of the pyzmq chat example and the eventlet telnet chat example. TODO zmq_poll chokes if the sockets passed to it come from different contexts. As context is the entry point to everything else then it would make sense to include a check in here that each thread has only one context instance. By context being the entry point I mean: ctx = zmq.Context() socket = ctx.socket(zmq.<type-of-socket>) This call to socket is repeated for each socket you want and ctx must be the same one for each thread. Tests. I'd like to get to the point f having all zmq socket pairs tested - and perhaps a nice benchmark suite too.
This commit is contained in:
67
eventlet/green/zmq.py
Normal file
67
eventlet/green/zmq.py
Normal file
@@ -0,0 +1,67 @@
|
||||
__zmq__ = __import__('zmq')
|
||||
from eventlet.hubs import trampoline
|
||||
__patched__ = ['Context', 'Socket']
|
||||
globals().update(dict([(var, getattr(__zmq__, var))
|
||||
for var in __zmq__.__all__
|
||||
if not (var.startswith('__')
|
||||
or
|
||||
var in __patched__)
|
||||
]))
|
||||
|
||||
class Context(__zmq__.Context):
|
||||
|
||||
def socket(self, socket_type):
|
||||
return Socket(self, socket_type)
|
||||
|
||||
class Socket(__zmq__.Socket):
|
||||
|
||||
|
||||
def _send_message(self, data, flags=0, copy=True):
|
||||
# flags |= __zmq__.NOBLOCK
|
||||
print 'send'
|
||||
while True:
|
||||
try:
|
||||
return super(Socket, self)._send_message(data, flags)
|
||||
except __zmq__.ZMQError, e:
|
||||
if e.errno != EAGAIN:
|
||||
raise
|
||||
trampoline(self, read=True)
|
||||
|
||||
def _send_copy(self, data, flags=0, copy=True):
|
||||
# flags |= __zmq__.NOBLOCK
|
||||
while True:
|
||||
try:
|
||||
return super(Socket, self)._send_copy(data, flags)
|
||||
except __zmq__.ZMQError, e:
|
||||
if e.errno != EAGAIN:
|
||||
raise
|
||||
trampoline(self, write=True)
|
||||
|
||||
def _recv_message(self, flags=0):
|
||||
|
||||
flags |= __zmq__.NOBLOCK
|
||||
while True:
|
||||
try:
|
||||
m = super(Socket, self)._recv_message(flags)
|
||||
if m:
|
||||
return m
|
||||
except __zmq__.ZMQError, e:
|
||||
if e.errno != EAGAIN:
|
||||
raise
|
||||
trampoline(self, read=True)
|
||||
|
||||
def _recv_copy(self, flags=0):
|
||||
flags |= __zmq__.NOBLOCK
|
||||
while True:
|
||||
try:
|
||||
m = super(Socket, self)._recv_copy(flags)
|
||||
if m:
|
||||
return m
|
||||
except __zmq__.ZMQError, e:
|
||||
if e.errno != EAGAIN:
|
||||
raise
|
||||
trampoline(self, read=True)
|
||||
|
||||
|
||||
|
||||
|
83
eventlet/hubs/zeromq.py
Normal file
83
eventlet/hubs/zeromq.py
Normal file
@@ -0,0 +1,83 @@
|
||||
from eventlet import patcher
|
||||
from eventlet.green import zmq
|
||||
from eventlet.hubs import poll
|
||||
from eventlet.hubs.hub import BaseHub, noop
|
||||
from eventlet.hubs.poll import READ, WRITE
|
||||
from eventlet.support import clear_sys_exc_info
|
||||
import sys
|
||||
|
||||
time = patcher.original('time')
|
||||
select = patcher.original('select')
|
||||
sleep = time.sleep
|
||||
|
||||
EXC_MASK = zmq.POLLERR
|
||||
READ_MASK = zmq.POLLIN
|
||||
WRITE_MASK = zmq.POLLOUT
|
||||
|
||||
class Hub(poll.Hub):
|
||||
|
||||
|
||||
|
||||
def __init__(self, clock=time.time):
|
||||
BaseHub.__init__(self, clock)
|
||||
self.poll = zmq.Poller()
|
||||
|
||||
def register(self, fileno, new=False):
|
||||
mask = 0
|
||||
if self.listeners[READ].get(fileno):
|
||||
mask |= READ_MASK
|
||||
if self.listeners[WRITE].get(fileno):
|
||||
mask |= WRITE_MASK
|
||||
if mask:
|
||||
self.poll.register(fileno, mask)
|
||||
else:
|
||||
self.poll.unregister(fileno)
|
||||
|
||||
|
||||
def wait(self, seconds=None):
|
||||
readers = self.listeners[READ]
|
||||
writers = self.listeners[WRITE]
|
||||
|
||||
if not readers and not writers:
|
||||
if seconds:
|
||||
sleep(seconds)
|
||||
return
|
||||
try:
|
||||
presult = self.do_poll(seconds)
|
||||
except zmq.ZMQError, e:
|
||||
# In the poll hub this part exists to special case some exceptions
|
||||
# from socket. There may be some error numbers that wider use of
|
||||
# this hub will throw up as needing special treatment so leaving
|
||||
# this block and this comment as a remineder
|
||||
raise
|
||||
SYSTEM_EXCEPTIONS = self.SYSTEM_EXCEPTIONS
|
||||
|
||||
if self.debug_blocking:
|
||||
self.block_detect_pre()
|
||||
|
||||
for fileno, event in presult:
|
||||
try:
|
||||
if event & READ_MASK:
|
||||
readers.get(fileno, noop).cb(fileno)
|
||||
if event & WRITE_MASK:
|
||||
writers.get(fileno, noop).cb(fileno)
|
||||
if event & EXC_MASK:
|
||||
# zmq.POLLERR is returned for any error condition in the
|
||||
# underlying fd (as passed through to poll/epoll)
|
||||
readers.get(fileno, noop).cb(fileno)
|
||||
writers.get(fileno, noop).cb(fileno)
|
||||
except SYSTEM_EXCEPTIONS:
|
||||
raise
|
||||
except:
|
||||
self.squelch_exception(fileno, sys.exc_info())
|
||||
clear_sys_exc_info()
|
||||
|
||||
if self.debug_blocking:
|
||||
self.block_detect_post()
|
||||
|
||||
|
||||
# def do_poll(self, seconds):
|
||||
# print 'poll: ', seconds
|
||||
# if seconds < 0:
|
||||
# seconds = 500
|
||||
# return self.poll.poll(seconds)
|
64
examples/zmq_chat.py
Normal file
64
examples/zmq_chat.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import eventlet, sys
|
||||
from eventlet.green import socket, zmq
|
||||
from eventlet.hubs import use_hub
|
||||
use_hub('zeromq')
|
||||
|
||||
ADDR = 'ipc:///tmp/chat'
|
||||
|
||||
ctx = zmq.Context()
|
||||
|
||||
def publish(writer):
|
||||
|
||||
print "connected"
|
||||
socket = ctx.socket(zmq.SUB)
|
||||
|
||||
socket.setsockopt(zmq.SUBSCRIBE, "")
|
||||
socket.connect(ADDR)
|
||||
eventlet.sleep(0.1)
|
||||
|
||||
while True:
|
||||
msg = socket.recv_pyobj()
|
||||
str_msg = "%s: %s" % msg
|
||||
writer.write(str_msg)
|
||||
writer.flush()
|
||||
|
||||
|
||||
PORT=3001
|
||||
|
||||
def read_chat_forever(reader, pub_socket):
|
||||
|
||||
line = reader.readline()
|
||||
who = 'someone'
|
||||
while line:
|
||||
print "Chat:", line.strip()
|
||||
if line.startswith('name:'):
|
||||
who = line.split(':')[-1].strip()
|
||||
|
||||
try:
|
||||
pub_socket.send_pyobj((who, line))
|
||||
except socket.error, e:
|
||||
# ignore broken pipes, they just mean the participant
|
||||
# closed its connection already
|
||||
if e[0] != 32:
|
||||
raise
|
||||
line = reader.readline()
|
||||
print "Participant left chat."
|
||||
|
||||
try:
|
||||
print "ChatServer starting up on port %s" % PORT
|
||||
server = eventlet.listen(('0.0.0.0', PORT))
|
||||
pub_socket = ctx.socket(zmq.PUB)
|
||||
pub_socket.bind(ADDR)
|
||||
eventlet.spawn_n(publish,
|
||||
sys.stdout)
|
||||
while True:
|
||||
new_connection, address = server.accept()
|
||||
|
||||
print "Participant joined chat."
|
||||
eventlet.spawn_n(publish,
|
||||
new_connection.makefile('w'))
|
||||
eventlet.spawn_n(read_chat_forever,
|
||||
new_connection.makefile('r'),
|
||||
pub_socket)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
print "ChatServer exiting."
|
49
tests/zeromq_test.py
Normal file
49
tests/zeromq_test.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from eventlet import spawn, sleep, getcurrent
|
||||
from eventlet.hubs import use_hub, get_hub
|
||||
from eventlet.green import zmq
|
||||
from nose.tools import *
|
||||
from tests import mock, LimitedTestCase
|
||||
from eventlet.hubs.hub import READ, WRITE
|
||||
|
||||
class _TestZMQ(LimitedTestCase):
|
||||
|
||||
def setUp(self):
|
||||
use_hub('zeromq')
|
||||
|
||||
super(_TestZMQ, self).setUp()
|
||||
# self.timer.cancel()
|
||||
|
||||
def tearDown(self):
|
||||
super(_TestZMQ, self).tearDown()
|
||||
use_hub()
|
||||
|
||||
class TestUpstreamDownStream(_TestZMQ):
|
||||
|
||||
def _get_socket_pair(self):
|
||||
return (zmq.Context().socket(zmq.PAIR),
|
||||
zmq.Context().socket(zmq.PAIR))
|
||||
|
||||
|
||||
def test_recv_non_blocking(self):
|
||||
ipc = 'ipc:///tmp/tests'
|
||||
req, rep = self._get_socket_pair()
|
||||
req.connect(ipc)
|
||||
rep.bind(ipc)
|
||||
sleep(0.2)
|
||||
# req.send('test')
|
||||
# set_trace()
|
||||
hub = get_hub()
|
||||
# hub.add(READ, rep, getcurrent().switch)
|
||||
msg = {}
|
||||
def rx():
|
||||
msg['res'] = rep.recv()
|
||||
spawn(rx)
|
||||
|
||||
req.send('test')
|
||||
|
||||
sleep(0.2)
|
||||
|
||||
|
||||
self.assertEqual(msg['res'], 'test')
|
||||
|
||||
|
Reference in New Issue
Block a user