Commit 4b082179 authored by SVN-Git Migration's avatar SVN-Git Migration

Imported Upstream version 2.1.10

parent 1b30b35a
*.pyc
*.c
build
dist
conf
*.egg-info
*.so
*.pyd
*.dll
docs/source/api/generated
docs/gh-pages
setup.cfg
MANIFEST
include COPYING.LESSER
include README.rst
include setup.cfg.template
include setup.py
include setupegg.py
include zmqversion.py
include buildutils.py
graft docs
prune docs/build
prune docs/gh-pages
graft examples
graft zmq
graft perf
exclude setup.cfg
exclude zmq/libzmq*
# exclude docs/_static
# exclude docs/_templates
global-exclude *.so
global-exclude *.pyd
global-exclude *.pyc
global-exclude .git*
global-exclude .DS_Store
Metadata-Version: 1.0
Name: pyzmq
Version: 2.1.10
Summary: Python bindings for 0MQ.
Home-page: http://github.com/zeromq/pyzmq
Author: Brian E. Granger, Min Ragan-Kelley
Author-email: zeromq-dev@lists.zeromq.org
License: LGPL
Download-URL: http://github.com/zeromq/pyzmq/downloads
Description:
PyZMQ is a lightweight and super-fast messaging library built on top of
the ZeroMQ library (http://www.zeromq.org).
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX
Classifier: Topic :: System :: Networking
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.5
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.0
Classifier: Programming Language :: Python :: 3.1
Classifier: Programming Language :: Python :: 3.2
......@@ -8,7 +8,7 @@ This package contains Python bindings for `ØMQ <http://www.zeromq.org>`_.
Versioning
==========
Current release of pyzmq is 2.1.9, and targets libzmq-2.1.9. For libzmq
Current release of pyzmq is 2.1.10, and targets libzmq-2.1.10. For libzmq
2.0.x, use pyzmq release 2.0.10.1 or the 2.0.x development branch.
PyZMQ versioning follows libzmq versioning. In general, your pyzmq version should be the same
......@@ -20,10 +20,12 @@ intend to support libzmq >= 2.1.4 (the first 'stable' 2.1 release) for pyzmq 2.1
---------------
As of 2.1.7, we have experimental support for the 3.0 API of libzmq,
developed at https://github.com/zeromq/libzmq. No code to change, no flags to pass, just
build pyzmq against libzmq 3 and it should work. The pyzmq API has not changed.
developed at https://github.com/zeromq/zeromq3-0. No code to change, no flags to pass, just
build pyzmq against libzmq 3 and it should work. The pyzmq API has not changed, though
some syntax has been added to support new features, such as the LABEL routing prefix in 3.0.
2.1.9 adds support for the changes in ØMQ-4.0 dev, at time of release.
2.1.9 adds support for the changes in the experimental 4.0 development branch at
https://github.com/zeromq/libzmq.
Documentation
......@@ -51,7 +53,7 @@ Eggs and MSIs
-------------
We have binary installers for various Pythons on OSX and Windows, so you should be able to
just ``easy_install pyzmq`` in many situations. These eggs *include libzmq-2.1.9*, so they should
just ``easy_install pyzmq`` in many situations. These eggs *include matching libzmq*, so they should
be the only thing you need to start using pyzmq, but we simply don't have the experience to know
when and where these installers will not work.
......@@ -59,9 +61,14 @@ If a binary installer fails for you, please `tell us <https://github.com/zeromq/
about your system and the failure, so that we can try to fix it in later releases, and fall back
on building from source.
Eggs are on PyPI, and we have them for 'current' Pythons, which are for OSX 10.6:
Eggs are on `PyPI <http://pypi.python.org/pypi/pyzmq>`_, and we have them for 'current' Pythons,
which are for OSX 10.7:
* Python 2.6, 2.7, 3.2 (32b and 64b intel)
* Python 2.7, 3.2 (32b+64b intel)
and OSX 10.6:
* Python 2.6 (32b+64b intel)
and Windows (x86 and x64):
......@@ -76,9 +83,12 @@ Our build scripts are much improved as of 2.1.4, so if you would like to contrib
Windows installers, or have any improvements on existing releases, they would be much
appreciated. Simply ``python setup.py bdist_msi`` or ``python setupegg.py bdist_egg`` *should*
work, once you have a libzmq and Python. We simply don't have the VMs or time in which to cover
all the bases ourselves. Sometimes libzmq.so/dll/dylib doesn't get included unless ``build``
is specified *also*, e.g. ``python setupegg.py build bdist_egg``, but this doesn't always
seem to be true.
all the bases ourselves.
.. note::
Sometimes libzmq.so/dll/dylib doesn't get included unless ``build`` is
specified *also*, e.g. ``python setupegg.py build bdist_egg``, but this
doesn't always seem to be true.
General
-------
......@@ -94,11 +104,11 @@ or the zmq install directory on OSX/Linux:
$ python setup.py configure --zmq=/usr/local
The argument should be a directory containing a ``lib`` and a ``include`` directory, containing
The argument should be a directory containing ``lib`` and ``include`` directories, with
``libzmq`` and ``zmq.h`` respectively. For instance (on Windows), if you have downloaded pyzmq
and current libzmq into the same parent directory, this would be:
$ python setup.py configure --zmq=../zeromq-2.1.9
$ python setup.py configure --zmq=../zeromq-2.1.10
Second, run this command::
......@@ -229,6 +239,7 @@ The following people have contributed to the project:
* Eugene Chernyshov (chernyshov DOT eugene AT gmail DOT com)
* Douglas Creager (dcreager AT dcreager DOT net)
* Craig Austin (craig DOT austin AT gmail DOT com)
* Andrew Gwozdziewycz (git AT apgwoz DOT com)
* Baptiste Lepilleur (baptiste DOT lepilleur AT gmail DOT com)
......
......@@ -9,9 +9,32 @@ Changes in PyZMQ
This is a coarse summary of changes in pyzmq versions. For a real changelog, consult the
`git log <https://github.com/zeromq/pyzmq/commits>`_
2.1.10
======
2.1.9 (dev)
===========
* Add support for libzmq-3.0 LABEL prefixes:
* send a message with label-prefix with:
.. sourcecode:: python
send_multipart([b'msg', b'parts'], prefix=[b'label', b'prefix'])
* :meth:`recv_multipart` returns a tuple of ``(prefix,msg)`` if a label prefix is detected
* ZMQStreams and devices also respect the LABEL prefix
* add czmq-style close&term as :meth:`ctx.destroy`, so that :meth:`ctx.term`
remains threadsafe and 1:1 with libzmq.
* :meth:`Socket.close` takes optional linger option, for setting linger prior
to closing.
* add :func:`~zmq.core.version.zmq_version_info` and
:func:`~zmq.core.version.pyzmq_version_info` for getting libzmq and pyzmq versions as
tuples of numbers. This helps with the fact that version string comparison breaks down
once versions get into double-digits.
* ioloop changes merged from upstream `Tornado <http://www.tornadoweb.org>`_ 2.1
2.1.9
=====
* added zmq.ssh tools for tunneling socket connections, copied from IPython
* Expanded sockopt support to cover changes in libzmq-4.0 dev.
......
......@@ -13,8 +13,9 @@ PyZMQ Documentation
.. Note::
PyZMQ versioning follows zeromq, so your pyzmq version should match that of your
zeromq. The only changes at the pyzmq user's level are the addition of a few socket
types and socket options.
zeromq. Building the same pyzmq against various versions of zeromq should only result
in the addition/removal of a few socket types and socket options, depending on
the active zeromq's support.
PyZMQ is the Python bindings for ØMQ_, written almost entirely in Cython_. This
......@@ -26,6 +27,7 @@ an overview of what the ØMQ API looks like in Python. For information on how to
As of PyZMQ 2.1.7, PyZMQ has experimental support for the libzmq-3.0 development version,
and has dropped support for the zeromq-2.0 series.
Please don't hesitate to report pyzmq issues to our tracker_ on GitHub.
......
import re,time
import sys
from subprocess import Popen,PIPE
from local_lat import main as local_lat
from local_thr import main as local_thr
from remote_lat import main as remote_lat
from remote_thr import main as remote_thr
def run_thr(args):
local = Popen('python local_thr.py'.split()+args,stdout=PIPE,stdin=PIPE)
remote = Popen('python remote_thr.py'.split()+args,stdout=PIPE,stdin=PIPE)
remote.wait()
local.wait()
out,_ = local.communicate()
lines = out.splitlines()
result = lines[-1]
throughput = re.findall(r'[0-9\.]+',result)[0]
return float(throughput)
def run_lat(args):
local = Popen('python local_lat.py'.split()+args,stdout=PIPE,stdin=PIPE)
remote = Popen('python remote_lat.py'.split()+args,stdout=PIPE,stdin=PIPE)
line = ''
while 'latency' not in line:
line = remote.stdout.readline()
if 'latency' in line:
latency = re.findall(r'[0-9\.]+',line)[0]
remote.wait()
local.wait()
return float(latency)
def multi_lat(args, n=3):
lats = [ run_lat(args) for i in xrange(3) ]
avg = sum(lats) / len(lats)
return avg,min(lats),max(lats)
def multi_thr(args, n=3):
thrs = [ run_thr(args) for i in xrange(3) ]
avg = sum(thrs) / len(thrs)
return avg,min(thrs),max(thrs)
def generate_vs_msg_size(nmsgs, msg_sizes=[2**p for p in range(6,21)], samples=5):
print nmsgs
x = msg_sizes
thr = []
lat = []
for msg_size in msg_sizes:
args = ('tcp://127.0.0.1:12345 %i %i'%(msg_size, nmsgs)).split()
thr.append(multi_thr(args,samples))
print 'thr: %i %s'%(msg_size, thr[-1])
lat.append(multi_lat(args,samples))
print 'lat: %i %s'%(msg_size, lat[-1])
return x,thr,lat
def generate_vs_nmsgs(msg_size, nmsgs_s=[2**p for p in range(4,14)], samples=5):
print msg_size
x = nmsgs_s
thr = []
lat = []
for nmsgs in nmsgs_s:
args = ('tcp://127.0.0.1:12345 %i %i'%(msg_size, nmsgs)).split()
thr.append(multi_thr(args,samples))
print 'thr: %i %s'%(nmsgs, thr[-1])
lat.append(multi_lat(args,samples))
print 'lat: %i %s'%(nmsgs, lat[-1])
return x,thr,lat
def do_plot(x,thr,lat, ref=1024, vs='nmsgs'):
import pylab
pylab.figure()
if vs == 'msg_size':
title = "%i msgs"%ref
xlabel = "msg size (B)"
else:
title = "msg size = %i B"%ref
xlabel = "nmsgs"
pylab.grid(True, which='major')
# pylab.grid(True, which='minor')
pylab.title(title)
pylab.xlabel(xlabel)
t_a,t_min,t_max = zip(*thr)
pylab.loglog(x,t_a,'b',label='throughput')
pylab.loglog(x,t_min,'b:')
pylab.loglog(x,t_max,'b:')
pylab.ylabel("Mb/s")
pylab.legend(loc='upper left')
ax2 = pylab.twinx()
l_a,l_min,l_max = zip(*lat)
pylab.loglog(x,l_a,'g',label='latency')
pylab.loglog(x,l_min,'g:')
pylab.loglog(x,l_max,'g:')
pylab.ylabel("msec")
pylab.legend(loc='upper right')
......@@ -501,7 +501,7 @@ monqueue = pxd('devices', 'monitoredqueue')
submodules = dict(
core = {'constants': [libzmq],
'error':[libzmq],
'poll':[libzmq, socket],
'poll':[libzmq, socket, context],
'stopwatch':[libzmq, pxd('core','stopwatch')],
'context':[context, libzmq],
'message':[libzmq, buffers, message],
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -167,12 +167,13 @@ EADDRINUSE = ZMQ_EADDRINUSE
EADDRNOTAVAIL = ZMQ_EADDRNOTAVAIL
ECONNREFUSED = ZMQ_ECONNREFUSED
EINPROGRESS = ZMQ_EINPROGRESS
ENOTSOCK = ZMQ_ENOTSOCK
# 0MQ Native
EMTHREAD = ZMQ_EMTHREAD
EFSM = ZMQ_EFSM
ENOCOMPATPROTO = ZMQ_ENOCOMPATPROTO
ETERM = ZMQ_ETERM
EMTHREAD = ZMQ_EMTHREAD
if ZMQ_VERSION >= 400000:
ECANTROUTE = ZMQ_ECANTROUTE
_optionals.append('ECANTROUTE')
......@@ -229,10 +230,11 @@ __all__ = [
'EADDRNOTAVAIL',
'ECONNREFUSED',
'EINPROGRESS',
'EMTHREAD',
'ENOTSOCK',
'EFSM',
'ENOCOMPATPROTO',
'ETERM',
'EMTHREAD',
'EFAULT',
'ENOMEM',
'ENODEV',
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -24,7 +24,6 @@
#-----------------------------------------------------------------------------
from libc.stdlib cimport free, malloc, realloc
from errno import ENOTSOCK
from libzmq cimport *
......@@ -66,12 +65,16 @@ cdef class Context:
raise MemoryError("Could not allocate _sockets array")
def __del__(self):
"""deleting a Context should terminate it, without trying non-threadsafe destroy"""
self.term()
def __dealloc__(self):
"""don't touch members in dealloc, just cleanup allocations"""
cdef int rc
if self.handle != NULL:
self.term()
if self._sockets != NULL:
free(self._sockets)
self.term()
cdef inline void _add_socket(self, void* handle):
"""Add a socket handle to be closed when Context terminates.
......@@ -133,22 +136,15 @@ cdef class Context:
def term(self):
"""ctx.term()
Close or terminate the context and all its sockets.
This can be called to close the context by hand. If this is not
called, the context will automatically be closed when it is
garbage collected.
Close or terminate the context.
This can be called to close the context by hand. If this is not called,
the context will automatically be closed when it is garbage collected.
"""
cdef int rc
cdef int i=-1
if self.handle != NULL and not self.closed:
for i in range(self.n_sockets):
# print 'closing: ', <size_t>self._sockets[i]
with nogil:
rc = zmq_close(self._sockets[i])
if rc != 0 and zmq_errno() != ENOTSOCK:
raise ZMQError()
# print i
with nogil:
rc = zmq_term(self.handle)
if rc != 0:
......@@ -156,6 +152,37 @@ cdef class Context:
self.handle = NULL
self.closed = True
def destroy(self, linger=None):
"""ctx.destroy(linger=None)
Close all sockets associated with this context, and then terminate
the context. If linger is specified,
the LINGER sockopt of the sockets will be set prior to closing.
WARNING:
destroy involves calling zmq_close(), which is *NOT* threadsafe.
If there are active sockets in other threads, this must not be called.
"""
cdef int linger_c
cdef bint setlinger=False
if linger is not None:
linger_c = linger
setlinger=True
if self.handle != NULL and not self.closed and self.n_sockets:
while self.n_sockets:
with nogil:
if setlinger:
zmq_setsockopt(self._sockets[0], ZMQ_LINGER, &linger_c, sizeof(int))
rc = zmq_close(self._sockets[0])
if rc != 0 and zmq_errno() != ENOTSOCK:
raise ZMQError()
self.n_sockets -= 1
self._sockets[0] = self._sockets[self.n_sockets]
self.term()
def socket(self, int socket_type):
"""ctx.socket(socket_type)
......
This diff is collapsed.
......@@ -55,6 +55,42 @@ def device(int device_type, cSocket isocket, cSocket osocket):
raise ZMQError()
return rc
# inner loop inlined, to prevent duplication
cdef inline int _relay(void * insocket, void *outsocket, zmq_msg_t msg) nogil:
cdef int more=0
cdef int label=0
cdef int flags=0
cdef size_t flagsz
flagsz = sizeof (more)
while (True):
rc = zmq_recvmsg(insocket, &msg, 0)
if (rc < 0):
return -1
rc = zmq_getsockopt(insocket, ZMQ_RCVMORE, &more, &flagsz)
if (rc < 0):
return -1
rc = zmq_getsockopt(insocket, ZMQ_RCVLABEL, &label, &flagsz)
if (rc < 0):
return -1
flags = 0
if more:
flags = flags | ZMQ_SNDMORE
if label:
flags = flags | ZMQ_SNDLABEL
rc = zmq_sendmsg(outsocket, &msg, flags)
if (rc < 0):
return -1
if not (flags):
break
return 0
# c_device copied (and cythonized) from zmq_device in zeromq release-2.1.6
# used under LGPL
cdef inline int c_device (void * insocket, void *outsocket) nogil:
......@@ -67,10 +103,6 @@ cdef inline int c_device (void * insocket, void *outsocket) nogil:
if (rc != 0):
return -1
cdef int more
cdef size_t moresz
moresz = sizeof (more)
cdef zmq_pollitem_t items [2]
items [0].socket = insocket
items [0].fd = 0
......@@ -96,49 +128,11 @@ cdef inline int c_device (void * insocket, void *outsocket) nogil:
# Process a request.
if (items [0].revents & ZMQ_POLLIN):
while (True):
rc = zmq_recvmsg(insocket, &msg, 0)
if (rc < 0):
return -1
rc = zmq_getsockopt(insocket, ZMQ_RCVMORE, &more, &moresz)
if (rc < 0):
return -1
if more:
rc = zmq_sendmsg(outsocket, &msg,ZMQ_SNDMORE)
else:
rc = zmq_sendmsg(outsocket, &msg,0)
if (rc < 0):
return -1
if (not more):
break
rc = _relay(insocket, outsocket, msg)
# Process a reply.
if (items [1].revents & ZMQ_POLLIN):
while (True):
rc = zmq_recvmsg(outsocket, &msg, 0)
if (rc < 0):
return -1
rc = zmq_getsockopt(outsocket, ZMQ_RCVMORE, &more, &moresz)
if (rc < 0):
return -1
if more:
rc = zmq_sendmsg(insocket, &msg,ZMQ_SNDMORE)
else:
rc = zmq_sendmsg(insocket, &msg,0)
if (rc < 0):
return -1
if (not more):
break
rc = _relay(outsocket, insocket, msg)
return 0
......
This diff is collapsed.
......@@ -33,7 +33,7 @@ from libzmq cimport zmq_strerror, zmq_errno
from zmq.utils.strtypes import bytes
def strerror(errnum):
def strerror(int errnum):
"""strerror(errnum)
Return the error string given the error number.
......
......@@ -67,11 +67,12 @@ cdef extern from "zmq.h" nogil:
enum: ZMQ_EADDRNOTAVAIL "EADDRNOTAVAIL"
enum: ZMQ_ECONNREFUSED "ECONNREFUSED"
enum: ZMQ_EINPROGRESS "EINPROGRESS"
enum: ZMQ_EMTHREAD "EMTHREAD"
enum: ZMQ_ENOTSOCK "ENOTSOCK"
enum: ZMQ_EFSM "EFSM"
enum: ZMQ_ENOCOMPATPROTO "ENOCOMPATPROTO"
enum: ZMQ_ETERM "ETERM"
enum: ZMQ_ECANTROUTE "ECANTROUTE"
enum: ZMQ_EMTHREAD "EMTHREAD"
enum: errno
char *zmq_strerror (int errnum)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -52,8 +52,6 @@ import random
import struct
import codecs
from errno import ENOTSOCK
from zmq.utils import jsonapi
try:
......@@ -93,7 +91,9 @@ cdef inline _check_closed(Socket s, bint raise_notsup):
raise ZMQError(ENOTSUP)
else:
return True
# return False
elif rc:
raise ZMQError()
return False
cdef inline Message _recv_message(void *handle, int flags=0, track=False):
"""Receive a message in a non-copying manner and return a Message."""
......@@ -204,9 +204,21 @@ cdef class Socket:
self._attrs = {}
context._add_socket(self.handle)
def __dealloc__(self):
def __del__(self):
"""close *and* remove from context's list"""
self.close()
def __dealloc__(self):
"""don't touch the Context during dealloc, since it might have been cleaned up already.
This method will likely do nothing unless init has failed."""
if self.handle != NULL:
with nogil:
rc = zmq_close(self.handle)
if rc != 0 and zmq_errno() != ENOTSOCK:
# ignore ENOTSOCK (closed by Context)
raise ZMQError()
def __init__(self, context, socket_type):
pass
......@@ -214,19 +226,29 @@ cdef class Socket:
def closed(self):
return _check_closed(self, False)
def close(self):
"""s.close()
def close(self, linger=None):
"""s.close(linger=None)
Close the socket.
If linger is specified, LINGER sockopt will be set prior to closing.
This can be called to close the socket by hand. If this is not
called, the socket will automatically be closed when it is
garbage collected.
"""
cdef int rc
cdef int rc=0
cdef int linger_c
cdef bint setlinger=False
if linger is not None:
linger_c = linger
setlinger=True
if self.handle != NULL and not self._closed:
with nogil:
if setlinger:
zmq_setsockopt(self.handle, ZMQ_LINGER, &linger_c, sizeof(int))
rc = zmq_close(self.handle)
if rc != 0 and zmq_errno() != ENOTSOCK:
# ignore ENOTSOCK (closed by Context)
......@@ -447,14 +469,16 @@ cdef class Socket:
encoded to utf-8 first.
"""
cdef int rc
cdef char* c_addr
_check_closed(self, True)
if isinstance(addr, unicode):
addr = addr.encode('utf-8')
if not isinstance(addr, bytes):
raise TypeError('expected str, got: %r' % addr)
c_addr = addr
with nogil:
rc = zmq_bind(self.handle, addr)
rc = zmq_bind(self.handle, c_addr)
if rc != 0:
raise ZMQError()
......@@ -508,14 +532,17 @@ cdef class Socket:
encoded to utf-8 first.
"""
cdef int rc
cdef char* c_addr
_check_closed(self, True)
if isinstance(addr, unicode):
addr = addr.encode('utf-8')
if not isinstance(addr, bytes):
raise TypeError('expected str, got: %r' % addr)
c_addr = addr
with nogil:
rc = zmq_connect(self.handle, addr)
rc = zmq_connect(self.handle, c_addr)
if rc != 0:
raise ZMQError()
......@@ -618,8 +645,8 @@ cdef class Socket:
else:
return _recv_message(self.handle, flags, track)
def send_multipart(self, msg_parts, int flags=0, copy=True, track=False):
"""s.send_multipart(msg_parts, flags=0, copy=True, track=False)
def send_multipart(self, msg_parts, int flags=0, copy=True, track=False, prefix=None):
"""s.send_multipart(msg_parts, flags=0, copy=True, track=False, prefix=None)
Send a sequence of messages as a multipart message.
......@@ -636,6 +663,9 @@ cdef class Socket:
track : bool, optional
Should the message(s) be tracked for notification that ZMQ has
finished with it (ignored if copy=True).
prefix : iterable
A sequence of messages to send as a 0MQ label prefix (0MQ >= 3.0 only).
Each element can be any sendable object (Message, bytes, buffer-providers)
Returns
-------
......@@ -644,6 +674,15 @@ cdef class Socket:
a MessageTracker object, whose `pending` property will
be True until the last send is completed.
"""
cdef int SNDLABEL = ZMQ_SNDLABEL
if prefix:
if isinstance(prefix, bytes):
prefix = [prefix]
if SNDLABEL == -1:
# ignore SNDLABEL on early libzmq, as SNDMORE is fine
SNDLABEL = SNDMORE
for msg in prefix:
self.send(msg, SNDLABEL|flags)
for msg in msg_parts[:-1]:
self.send(msg, SNDMORE|flags, copy=copy, track=track)
# Send the last part without the extra SNDMORE flag.
......@@ -668,35 +707,46 @@ cdef class Socket:
track : bool, optional
Should the message(s) be tracked for notification that ZMQ has
finished with it? (ignored if copy=True)
Returns
-------
msg_parts : list
A list of messages in the multipart message; either Messages or strs,
depending on `copy`.
0MQ-3.0:
prefix, msg_parts : two lists
`prefix` will be the prefix list of message labels at the front of the
message. If prefix would be empty, only a single msg_parts list
will be returned.
"""
parts = []
while True:
prefix = []
if ZMQ_VERSION_MAJOR >= 3:
while True:
# receive the label prefix, if any
part = self.recv(flags, copy=copy, track=track)
if self.getsockopt(ZMQ_RCVLABEL):
prefix.append(part)
else: