Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .venv/lib/python3.11/site-packages/zmq/backend/__init__.py +34 -0
- .venv/lib/python3.11/site-packages/zmq/backend/__init__.pyi +122 -0
- .venv/lib/python3.11/site-packages/zmq/backend/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/backend/__pycache__/select.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cffi/README.md +1 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cffi/__init__.py +35 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/_poll.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/context.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/devices.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/error.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/message.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/socket.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/utils.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cffi/_cdefs.h +91 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cffi/_cffi_src.c +50 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cffi/_poll.py +92 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cffi/context.py +77 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cffi/devices.py +63 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cffi/error.py +20 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cffi/message.py +222 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cffi/socket.py +368 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cffi/utils.py +78 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cython/__init__.pxd +1 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cython/__init__.py +15 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cython/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cython/__pycache__/_zmq.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cython/_externs.pxd +13 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cython/_zmq.pxd +50 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cython/_zmq.py +1958 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cython/constant_enums.pxi +250 -0
- .venv/lib/python3.11/site-packages/zmq/backend/cython/libzmq.pxd +122 -0
- .venv/lib/python3.11/site-packages/zmq/backend/select.py +41 -0
- .venv/lib/python3.11/site-packages/zmq/eventloop/__init__.py +5 -0
- .venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/_deprecated.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/future.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/ioloop.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/zmqstream.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/eventloop/_deprecated.py +211 -0
- .venv/lib/python3.11/site-packages/zmq/eventloop/future.py +104 -0
- .venv/lib/python3.11/site-packages/zmq/eventloop/ioloop.py +37 -0
- .venv/lib/python3.11/site-packages/zmq/eventloop/zmqstream.py +689 -0
- .venv/lib/python3.11/site-packages/zmq/green/__init__.py +48 -0
- .venv/lib/python3.11/site-packages/zmq/green/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/green/__pycache__/core.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/green/__pycache__/device.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/green/__pycache__/poll.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/zmq/green/core.py +317 -0
- .venv/lib/python3.11/site-packages/zmq/green/device.py +34 -0
.venv/lib/python3.11/site-packages/zmq/backend/__init__.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Import basic exposure of libzmq C API as a backend"""
|
| 2 |
+
|
| 3 |
+
# Copyright (C) PyZMQ Developers
|
| 4 |
+
# Distributed under the terms of the Modified BSD License.
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import platform
|
| 8 |
+
|
| 9 |
+
from .select import public_api, select_backend
|
| 10 |
+
|
| 11 |
+
if 'PYZMQ_BACKEND' in os.environ:
|
| 12 |
+
backend = os.environ['PYZMQ_BACKEND']
|
| 13 |
+
if backend in ('cython', 'cffi'):
|
| 14 |
+
backend = f'zmq.backend.{backend}'
|
| 15 |
+
_ns = select_backend(backend)
|
| 16 |
+
else:
|
| 17 |
+
# default to cython, fallback to cffi
|
| 18 |
+
# (reverse on PyPy)
|
| 19 |
+
if platform.python_implementation() == 'PyPy':
|
| 20 |
+
first, second = ('zmq.backend.cffi', 'zmq.backend.cython')
|
| 21 |
+
else:
|
| 22 |
+
first, second = ('zmq.backend.cython', 'zmq.backend.cffi')
|
| 23 |
+
|
| 24 |
+
try:
|
| 25 |
+
_ns = select_backend(first)
|
| 26 |
+
except Exception as original_error:
|
| 27 |
+
try:
|
| 28 |
+
_ns = select_backend(second)
|
| 29 |
+
except ImportError:
|
| 30 |
+
raise original_error from None
|
| 31 |
+
|
| 32 |
+
globals().update(_ns)
|
| 33 |
+
|
| 34 |
+
__all__ = public_api
|
.venv/lib/python3.11/site-packages/zmq/backend/__init__.pyi
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Any, Callable, List, Optional, Set, Tuple, TypeVar, Union, overload
|
| 2 |
+
|
| 3 |
+
from typing_extensions import Literal
|
| 4 |
+
|
| 5 |
+
import zmq
|
| 6 |
+
|
| 7 |
+
from .select import select_backend
|
| 8 |
+
|
| 9 |
+
# avoid collision in Frame.bytes
|
| 10 |
+
_bytestr = bytes
|
| 11 |
+
|
| 12 |
+
T = TypeVar("T")
|
| 13 |
+
|
| 14 |
+
class Frame:
|
| 15 |
+
buffer: Any
|
| 16 |
+
bytes: bytes
|
| 17 |
+
more: bool
|
| 18 |
+
tracker: Any
|
| 19 |
+
def __init__(
|
| 20 |
+
self,
|
| 21 |
+
data: Any = None,
|
| 22 |
+
track: bool = False,
|
| 23 |
+
copy: bool | None = None,
|
| 24 |
+
copy_threshold: int | None = None,
|
| 25 |
+
): ...
|
| 26 |
+
def copy_fast(self: T) -> T: ...
|
| 27 |
+
def get(self, option: int) -> int | _bytestr | str: ...
|
| 28 |
+
def set(self, option: int, value: int | _bytestr | str) -> None: ...
|
| 29 |
+
|
| 30 |
+
class Socket:
|
| 31 |
+
underlying: int
|
| 32 |
+
context: zmq.Context
|
| 33 |
+
copy_threshold: int
|
| 34 |
+
|
| 35 |
+
# specific option types
|
| 36 |
+
FD: int
|
| 37 |
+
|
| 38 |
+
def __init__(
|
| 39 |
+
self,
|
| 40 |
+
context: Context | None = None,
|
| 41 |
+
socket_type: int = 0,
|
| 42 |
+
shadow: int = 0,
|
| 43 |
+
copy_threshold: int | None = zmq.COPY_THRESHOLD,
|
| 44 |
+
) -> None: ...
|
| 45 |
+
def close(self, linger: int | None = ...) -> None: ...
|
| 46 |
+
def get(self, option: int) -> int | bytes | str: ...
|
| 47 |
+
def set(self, option: int, value: int | bytes | str) -> None: ...
|
| 48 |
+
def connect(self, url: str): ...
|
| 49 |
+
def disconnect(self, url: str) -> None: ...
|
| 50 |
+
def bind(self, url: str): ...
|
| 51 |
+
def unbind(self, url: str) -> None: ...
|
| 52 |
+
def send(
|
| 53 |
+
self,
|
| 54 |
+
data: Any,
|
| 55 |
+
flags: int = ...,
|
| 56 |
+
copy: bool = ...,
|
| 57 |
+
track: bool = ...,
|
| 58 |
+
) -> zmq.MessageTracker | None: ...
|
| 59 |
+
@overload
|
| 60 |
+
def recv(
|
| 61 |
+
self,
|
| 62 |
+
flags: int = ...,
|
| 63 |
+
*,
|
| 64 |
+
copy: Literal[False],
|
| 65 |
+
track: bool = ...,
|
| 66 |
+
) -> zmq.Frame: ...
|
| 67 |
+
@overload
|
| 68 |
+
def recv(
|
| 69 |
+
self,
|
| 70 |
+
flags: int = ...,
|
| 71 |
+
*,
|
| 72 |
+
copy: Literal[True],
|
| 73 |
+
track: bool = ...,
|
| 74 |
+
) -> bytes: ...
|
| 75 |
+
@overload
|
| 76 |
+
def recv(
|
| 77 |
+
self,
|
| 78 |
+
flags: int = ...,
|
| 79 |
+
track: bool = False,
|
| 80 |
+
) -> bytes: ...
|
| 81 |
+
@overload
|
| 82 |
+
def recv(
|
| 83 |
+
self,
|
| 84 |
+
flags: int | None = ...,
|
| 85 |
+
copy: bool = ...,
|
| 86 |
+
track: bool | None = False,
|
| 87 |
+
) -> zmq.Frame | bytes: ...
|
| 88 |
+
def monitor(self, addr: str | None, events: int) -> None: ...
|
| 89 |
+
# draft methods
|
| 90 |
+
def join(self, group: str) -> None: ...
|
| 91 |
+
def leave(self, group: str) -> None: ...
|
| 92 |
+
|
| 93 |
+
class Context:
|
| 94 |
+
underlying: int
|
| 95 |
+
def __init__(self, io_threads: int = 1, shadow: int = 0): ...
|
| 96 |
+
def get(self, option: int) -> int | bytes | str: ...
|
| 97 |
+
def set(self, option: int, value: int | bytes | str) -> None: ...
|
| 98 |
+
def socket(self, socket_type: int) -> Socket: ...
|
| 99 |
+
def term(self) -> None: ...
|
| 100 |
+
|
| 101 |
+
IPC_PATH_MAX_LEN: int
|
| 102 |
+
|
| 103 |
+
def has(capability: str) -> bool: ...
|
| 104 |
+
def curve_keypair() -> tuple[bytes, bytes]: ...
|
| 105 |
+
def curve_public(secret_key: bytes) -> bytes: ...
|
| 106 |
+
def strerror(errno: int | None = ...) -> str: ...
|
| 107 |
+
def zmq_errno() -> int: ...
|
| 108 |
+
def zmq_version() -> str: ...
|
| 109 |
+
def zmq_version_info() -> tuple[int, int, int]: ...
|
| 110 |
+
def zmq_poll(
|
| 111 |
+
sockets: list[Any], timeout: int | None = ...
|
| 112 |
+
) -> list[tuple[Socket, int]]: ...
|
| 113 |
+
def device(device_type: int, frontend: Socket, backend: Socket | None = ...) -> int: ...
|
| 114 |
+
def proxy(frontend: Socket, backend: Socket, capture: Socket | None = None) -> int: ...
|
| 115 |
+
def proxy_steerable(
|
| 116 |
+
frontend: Socket,
|
| 117 |
+
backend: Socket,
|
| 118 |
+
capture: Socket | None = ...,
|
| 119 |
+
control: Socket | None = ...,
|
| 120 |
+
) -> int: ...
|
| 121 |
+
|
| 122 |
+
monitored_queue = Callable | None
|
.venv/lib/python3.11/site-packages/zmq/backend/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (1.25 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/backend/__pycache__/select.cpython-311.pyc
ADDED
|
Binary file (1.52 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/backend/cffi/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
PyZMQ's CFFI support is designed only for (Unix) systems conforming to `have_sys_un_h = True`.
|
.venv/lib/python3.11/site-packages/zmq/backend/cffi/__init__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""CFFI backend (for PyPy)"""
|
| 2 |
+
|
| 3 |
+
# Copyright (C) PyZMQ Developers
|
| 4 |
+
# Distributed under the terms of the Modified BSD License.
|
| 5 |
+
|
| 6 |
+
from zmq.backend.cffi import _poll, context, devices, error, message, socket, utils
|
| 7 |
+
|
| 8 |
+
from ._cffi import ffi
|
| 9 |
+
from ._cffi import lib as C
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def zmq_version_info():
|
| 13 |
+
"""Get libzmq version as tuple of ints"""
|
| 14 |
+
major = ffi.new('int*')
|
| 15 |
+
minor = ffi.new('int*')
|
| 16 |
+
patch = ffi.new('int*')
|
| 17 |
+
|
| 18 |
+
C.zmq_version(major, minor, patch)
|
| 19 |
+
|
| 20 |
+
return (int(major[0]), int(minor[0]), int(patch[0]))
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
__all__ = ["zmq_version_info"]
|
| 24 |
+
for submod in (error, message, context, socket, _poll, devices, utils):
|
| 25 |
+
__all__.extend(submod.__all__)
|
| 26 |
+
|
| 27 |
+
from ._poll import *
|
| 28 |
+
from .context import *
|
| 29 |
+
from .devices import *
|
| 30 |
+
from .error import *
|
| 31 |
+
from .message import *
|
| 32 |
+
from .socket import *
|
| 33 |
+
from .utils import *
|
| 34 |
+
|
| 35 |
+
monitored_queue = None
|
.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (1.48 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/_poll.cpython-311.pyc
ADDED
|
Binary file (3.82 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/context.cpython-311.pyc
ADDED
|
Binary file (3.33 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/devices.cpython-311.pyc
ADDED
|
Binary file (2.19 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/error.cpython-311.pyc
ADDED
|
Binary file (783 Bytes). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/message.cpython-311.pyc
ADDED
|
Binary file (9.03 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/socket.cpython-311.pyc
ADDED
|
Binary file (17.9 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/utils.cpython-311.pyc
ADDED
|
Binary file (3.35 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/backend/cffi/_cdefs.h
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
void zmq_version(int *major, int *minor, int *patch);
|
| 2 |
+
|
| 3 |
+
void* zmq_socket(void *context, int type);
|
| 4 |
+
int zmq_close(void *socket);
|
| 5 |
+
|
| 6 |
+
int zmq_bind(void *socket, const char *endpoint);
|
| 7 |
+
int zmq_connect(void *socket, const char *endpoint);
|
| 8 |
+
|
| 9 |
+
int zmq_errno(void);
|
| 10 |
+
const char * zmq_strerror(int errnum);
|
| 11 |
+
|
| 12 |
+
int zmq_device(int device, void *frontend, void *backend);
|
| 13 |
+
|
| 14 |
+
int zmq_unbind(void *socket, const char *endpoint);
|
| 15 |
+
int zmq_disconnect(void *socket, const char *endpoint);
|
| 16 |
+
void* zmq_ctx_new();
|
| 17 |
+
int zmq_ctx_destroy(void *context);
|
| 18 |
+
int zmq_ctx_get(void *context, int opt);
|
| 19 |
+
int zmq_ctx_set(void *context, int opt, int optval);
|
| 20 |
+
int zmq_proxy(void *frontend, void *backend, void *capture);
|
| 21 |
+
int zmq_proxy_steerable(void *frontend,
|
| 22 |
+
void *backend,
|
| 23 |
+
void *capture,
|
| 24 |
+
void *control);
|
| 25 |
+
int zmq_socket_monitor(void *socket, const char *addr, int events);
|
| 26 |
+
|
| 27 |
+
int zmq_curve_keypair (char *z85_public_key, char *z85_secret_key);
|
| 28 |
+
int zmq_curve_public (char *z85_public_key, char *z85_secret_key);
|
| 29 |
+
int zmq_has (const char *capability);
|
| 30 |
+
|
| 31 |
+
typedef struct { ...; } zmq_msg_t;
|
| 32 |
+
typedef ... zmq_free_fn;
|
| 33 |
+
|
| 34 |
+
int zmq_msg_init(zmq_msg_t *msg);
|
| 35 |
+
int zmq_msg_init_size(zmq_msg_t *msg, size_t size);
|
| 36 |
+
int zmq_msg_init_data(zmq_msg_t *msg,
|
| 37 |
+
void *data,
|
| 38 |
+
size_t size,
|
| 39 |
+
zmq_free_fn *ffn,
|
| 40 |
+
void *hint);
|
| 41 |
+
|
| 42 |
+
size_t zmq_msg_size(zmq_msg_t *msg);
|
| 43 |
+
void *zmq_msg_data(zmq_msg_t *msg);
|
| 44 |
+
int zmq_msg_close(zmq_msg_t *msg);
|
| 45 |
+
|
| 46 |
+
int zmq_msg_copy(zmq_msg_t *dst, zmq_msg_t *src);
|
| 47 |
+
int zmq_msg_send(zmq_msg_t *msg, void *socket, int flags);
|
| 48 |
+
int zmq_msg_recv(zmq_msg_t *msg, void *socket, int flags);
|
| 49 |
+
|
| 50 |
+
int zmq_getsockopt(void *socket,
|
| 51 |
+
int option_name,
|
| 52 |
+
void *option_value,
|
| 53 |
+
size_t *option_len);
|
| 54 |
+
|
| 55 |
+
int zmq_setsockopt(void *socket,
|
| 56 |
+
int option_name,
|
| 57 |
+
const void *option_value,
|
| 58 |
+
size_t option_len);
|
| 59 |
+
|
| 60 |
+
typedef int... ZMQ_FD_T;
|
| 61 |
+
|
| 62 |
+
typedef struct
|
| 63 |
+
{
|
| 64 |
+
void *socket;
|
| 65 |
+
ZMQ_FD_T fd;
|
| 66 |
+
short events;
|
| 67 |
+
short revents;
|
| 68 |
+
} zmq_pollitem_t;
|
| 69 |
+
|
| 70 |
+
int zmq_poll(zmq_pollitem_t *items, int nitems, long timeout);
|
| 71 |
+
|
| 72 |
+
// miscellany
|
| 73 |
+
void * memcpy(void *restrict s1, const void *restrict s2, size_t n);
|
| 74 |
+
void * malloc(size_t sz);
|
| 75 |
+
void free(void *p);
|
| 76 |
+
int get_ipc_path_max_len(void);
|
| 77 |
+
|
| 78 |
+
typedef struct { ...; } mutex_t;
|
| 79 |
+
|
| 80 |
+
typedef struct _zhint {
|
| 81 |
+
void *sock;
|
| 82 |
+
mutex_t *mutex;
|
| 83 |
+
size_t id;
|
| 84 |
+
} zhint;
|
| 85 |
+
|
| 86 |
+
mutex_t* mutex_allocate();
|
| 87 |
+
|
| 88 |
+
int zmq_wrap_msg_init_data(zmq_msg_t *msg,
|
| 89 |
+
void *data,
|
| 90 |
+
size_t size,
|
| 91 |
+
void *hint);
|
.venv/lib/python3.11/site-packages/zmq/backend/cffi/_cffi_src.c
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <stdio.h>
|
| 2 |
+
#include <string.h>
|
| 3 |
+
|
| 4 |
+
#include "pyversion_compat.h"
|
| 5 |
+
#include "mutex.h"
|
| 6 |
+
#include "ipcmaxlen.h"
|
| 7 |
+
#include "zmq_compat.h"
|
| 8 |
+
#include <zmq.h>
|
| 9 |
+
|
| 10 |
+
typedef struct _zhint {
|
| 11 |
+
void *sock;
|
| 12 |
+
mutex_t *mutex;
|
| 13 |
+
size_t id;
|
| 14 |
+
} zhint;
|
| 15 |
+
|
| 16 |
+
void free_python_msg(void *data, void *vhint) {
|
| 17 |
+
zmq_msg_t msg;
|
| 18 |
+
zhint *hint = (zhint *)vhint;
|
| 19 |
+
int rc;
|
| 20 |
+
if (hint != NULL) {
|
| 21 |
+
zmq_msg_init_size(&msg, sizeof(size_t));
|
| 22 |
+
memcpy(zmq_msg_data(&msg), &hint->id, sizeof(size_t));
|
| 23 |
+
rc = mutex_lock(hint->mutex);
|
| 24 |
+
if (rc != 0) {
|
| 25 |
+
fprintf(stderr, "pyzmq-gc mutex lock failed rc=%d\n", rc);
|
| 26 |
+
}
|
| 27 |
+
rc = zmq_msg_send(&msg, hint->sock, 0);
|
| 28 |
+
if (rc < 0) {
|
| 29 |
+
/*
|
| 30 |
+
* gc socket could have been closed, e.g. during process teardown.
|
| 31 |
+
* If so, ignore the failure because there's nothing to do.
|
| 32 |
+
*/
|
| 33 |
+
if (zmq_errno() != ENOTSOCK) {
|
| 34 |
+
fprintf(stderr, "pyzmq-gc send failed: %s\n",
|
| 35 |
+
zmq_strerror(zmq_errno()));
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
rc = mutex_unlock(hint->mutex);
|
| 39 |
+
if (rc != 0) {
|
| 40 |
+
fprintf(stderr, "pyzmq-gc mutex unlock failed rc=%d\n", rc);
|
| 41 |
+
}
|
| 42 |
+
zmq_msg_close(&msg);
|
| 43 |
+
free(hint);
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
int zmq_wrap_msg_init_data(zmq_msg_t *msg, void *data, size_t size,
|
| 48 |
+
void *hint) {
|
| 49 |
+
return zmq_msg_init_data(msg, data, size, free_python_msg, hint);
|
| 50 |
+
}
|
.venv/lib/python3.11/site-packages/zmq/backend/cffi/_poll.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""zmq poll function"""
|
| 2 |
+
|
| 3 |
+
# Copyright (C) PyZMQ Developers
|
| 4 |
+
# Distributed under the terms of the Modified BSD License.
|
| 5 |
+
|
| 6 |
+
try:
|
| 7 |
+
from time import monotonic
|
| 8 |
+
except ImportError:
|
| 9 |
+
from time import clock as monotonic
|
| 10 |
+
|
| 11 |
+
import warnings
|
| 12 |
+
|
| 13 |
+
from zmq.error import InterruptedSystemCall, _check_rc
|
| 14 |
+
|
| 15 |
+
from ._cffi import ffi
|
| 16 |
+
from ._cffi import lib as C
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def _make_zmq_pollitem(socket, flags):
|
| 20 |
+
zmq_socket = socket._zmq_socket
|
| 21 |
+
zmq_pollitem = ffi.new('zmq_pollitem_t*')
|
| 22 |
+
zmq_pollitem.socket = zmq_socket
|
| 23 |
+
zmq_pollitem.fd = 0
|
| 24 |
+
zmq_pollitem.events = flags
|
| 25 |
+
zmq_pollitem.revents = 0
|
| 26 |
+
return zmq_pollitem[0]
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def _make_zmq_pollitem_fromfd(socket_fd, flags):
|
| 30 |
+
zmq_pollitem = ffi.new('zmq_pollitem_t*')
|
| 31 |
+
zmq_pollitem.socket = ffi.NULL
|
| 32 |
+
zmq_pollitem.fd = socket_fd
|
| 33 |
+
zmq_pollitem.events = flags
|
| 34 |
+
zmq_pollitem.revents = 0
|
| 35 |
+
return zmq_pollitem[0]
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def zmq_poll(sockets, timeout):
|
| 39 |
+
cffi_pollitem_list = []
|
| 40 |
+
low_level_to_socket_obj = {}
|
| 41 |
+
from zmq import Socket
|
| 42 |
+
|
| 43 |
+
for item in sockets:
|
| 44 |
+
if isinstance(item[0], Socket):
|
| 45 |
+
low_level_to_socket_obj[item[0]._zmq_socket] = item
|
| 46 |
+
cffi_pollitem_list.append(_make_zmq_pollitem(item[0], item[1]))
|
| 47 |
+
else:
|
| 48 |
+
if not isinstance(item[0], int):
|
| 49 |
+
# not an FD, get it from fileno()
|
| 50 |
+
item = (item[0].fileno(), item[1])
|
| 51 |
+
low_level_to_socket_obj[item[0]] = item
|
| 52 |
+
cffi_pollitem_list.append(_make_zmq_pollitem_fromfd(item[0], item[1]))
|
| 53 |
+
items = ffi.new('zmq_pollitem_t[]', cffi_pollitem_list)
|
| 54 |
+
list_length = ffi.cast('int', len(cffi_pollitem_list))
|
| 55 |
+
while True:
|
| 56 |
+
c_timeout = ffi.cast('long', timeout)
|
| 57 |
+
start = monotonic()
|
| 58 |
+
rc = C.zmq_poll(items, list_length, c_timeout)
|
| 59 |
+
try:
|
| 60 |
+
_check_rc(rc)
|
| 61 |
+
except InterruptedSystemCall:
|
| 62 |
+
if timeout > 0:
|
| 63 |
+
ms_passed = int(1000 * (monotonic() - start))
|
| 64 |
+
if ms_passed < 0:
|
| 65 |
+
# don't allow negative ms_passed,
|
| 66 |
+
# which can happen on old Python versions without time.monotonic.
|
| 67 |
+
warnings.warn(
|
| 68 |
+
f"Negative elapsed time for interrupted poll: {ms_passed}."
|
| 69 |
+
" Did the clock change?",
|
| 70 |
+
RuntimeWarning,
|
| 71 |
+
)
|
| 72 |
+
ms_passed = 0
|
| 73 |
+
timeout = max(0, timeout - ms_passed)
|
| 74 |
+
continue
|
| 75 |
+
else:
|
| 76 |
+
break
|
| 77 |
+
result = []
|
| 78 |
+
for item in items:
|
| 79 |
+
if item.revents > 0:
|
| 80 |
+
if item.socket != ffi.NULL:
|
| 81 |
+
result.append(
|
| 82 |
+
(
|
| 83 |
+
low_level_to_socket_obj[item.socket][0],
|
| 84 |
+
item.revents,
|
| 85 |
+
)
|
| 86 |
+
)
|
| 87 |
+
else:
|
| 88 |
+
result.append((item.fd, item.revents))
|
| 89 |
+
return result
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
__all__ = ['zmq_poll']
|
.venv/lib/python3.11/site-packages/zmq/backend/cffi/context.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""zmq Context class"""
|
| 2 |
+
|
| 3 |
+
# Copyright (C) PyZMQ Developers
|
| 4 |
+
# Distributed under the terms of the Modified BSD License.
|
| 5 |
+
|
| 6 |
+
from zmq.constants import EINVAL, IO_THREADS
|
| 7 |
+
from zmq.error import InterruptedSystemCall, ZMQError, _check_rc
|
| 8 |
+
|
| 9 |
+
from ._cffi import ffi
|
| 10 |
+
from ._cffi import lib as C
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class Context:
|
| 14 |
+
_zmq_ctx = None
|
| 15 |
+
_iothreads = None
|
| 16 |
+
_closed = True
|
| 17 |
+
_shadow = False
|
| 18 |
+
|
| 19 |
+
def __init__(self, io_threads=1, shadow=None):
|
| 20 |
+
if shadow:
|
| 21 |
+
self._zmq_ctx = ffi.cast("void *", shadow)
|
| 22 |
+
self._shadow = True
|
| 23 |
+
else:
|
| 24 |
+
self._shadow = False
|
| 25 |
+
if not io_threads >= 0:
|
| 26 |
+
raise ZMQError(EINVAL)
|
| 27 |
+
|
| 28 |
+
self._zmq_ctx = C.zmq_ctx_new()
|
| 29 |
+
if self._zmq_ctx == ffi.NULL:
|
| 30 |
+
raise ZMQError(C.zmq_errno())
|
| 31 |
+
if not shadow:
|
| 32 |
+
C.zmq_ctx_set(self._zmq_ctx, IO_THREADS, io_threads)
|
| 33 |
+
self._closed = False
|
| 34 |
+
|
| 35 |
+
@property
|
| 36 |
+
def underlying(self):
|
| 37 |
+
"""The address of the underlying libzmq context"""
|
| 38 |
+
return int(ffi.cast('size_t', self._zmq_ctx))
|
| 39 |
+
|
| 40 |
+
@property
|
| 41 |
+
def closed(self):
|
| 42 |
+
return self._closed
|
| 43 |
+
|
| 44 |
+
def set(self, option, value):
|
| 45 |
+
"""set a context option
|
| 46 |
+
|
| 47 |
+
see zmq_ctx_set
|
| 48 |
+
"""
|
| 49 |
+
rc = C.zmq_ctx_set(self._zmq_ctx, option, value)
|
| 50 |
+
_check_rc(rc)
|
| 51 |
+
|
| 52 |
+
def get(self, option):
|
| 53 |
+
"""get context option
|
| 54 |
+
|
| 55 |
+
see zmq_ctx_get
|
| 56 |
+
"""
|
| 57 |
+
rc = C.zmq_ctx_get(self._zmq_ctx, option)
|
| 58 |
+
_check_rc(rc, error_without_errno=False)
|
| 59 |
+
return rc
|
| 60 |
+
|
| 61 |
+
def term(self):
|
| 62 |
+
if self.closed:
|
| 63 |
+
return
|
| 64 |
+
|
| 65 |
+
rc = C.zmq_ctx_destroy(self._zmq_ctx)
|
| 66 |
+
try:
|
| 67 |
+
_check_rc(rc)
|
| 68 |
+
except InterruptedSystemCall:
|
| 69 |
+
# ignore interrupted term
|
| 70 |
+
# see PEP 475 notes about close & EINTR for why
|
| 71 |
+
pass
|
| 72 |
+
|
| 73 |
+
self._zmq_ctx = None
|
| 74 |
+
self._closed = True
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
__all__ = ['Context']
|
.venv/lib/python3.11/site-packages/zmq/backend/cffi/devices.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""zmq device functions"""
|
| 2 |
+
|
| 3 |
+
# Copyright (C) PyZMQ Developers
|
| 4 |
+
# Distributed under the terms of the Modified BSD License.
|
| 5 |
+
|
| 6 |
+
from ._cffi import ffi
|
| 7 |
+
from ._cffi import lib as C
|
| 8 |
+
from .socket import Socket
|
| 9 |
+
from .utils import _retry_sys_call
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def device(device_type, frontend, backend):
|
| 13 |
+
return proxy(frontend, backend)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def proxy(frontend, backend, capture=None):
|
| 17 |
+
if isinstance(capture, Socket):
|
| 18 |
+
capture = capture._zmq_socket
|
| 19 |
+
else:
|
| 20 |
+
capture = ffi.NULL
|
| 21 |
+
|
| 22 |
+
_retry_sys_call(C.zmq_proxy, frontend._zmq_socket, backend._zmq_socket, capture)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def proxy_steerable(frontend, backend, capture=None, control=None):
|
| 26 |
+
"""proxy_steerable(frontend, backend, capture, control)
|
| 27 |
+
|
| 28 |
+
Start a zeromq proxy with control flow.
|
| 29 |
+
|
| 30 |
+
.. versionadded:: libzmq-4.1
|
| 31 |
+
.. versionadded:: 18.0
|
| 32 |
+
|
| 33 |
+
Parameters
|
| 34 |
+
----------
|
| 35 |
+
frontend : Socket
|
| 36 |
+
The Socket instance for the incoming traffic.
|
| 37 |
+
backend : Socket
|
| 38 |
+
The Socket instance for the outbound traffic.
|
| 39 |
+
capture : Socket (optional)
|
| 40 |
+
The Socket instance for capturing traffic.
|
| 41 |
+
control : Socket (optional)
|
| 42 |
+
The Socket instance for control flow.
|
| 43 |
+
"""
|
| 44 |
+
if isinstance(capture, Socket):
|
| 45 |
+
capture = capture._zmq_socket
|
| 46 |
+
else:
|
| 47 |
+
capture = ffi.NULL
|
| 48 |
+
|
| 49 |
+
if isinstance(control, Socket):
|
| 50 |
+
control = control._zmq_socket
|
| 51 |
+
else:
|
| 52 |
+
control = ffi.NULL
|
| 53 |
+
|
| 54 |
+
_retry_sys_call(
|
| 55 |
+
C.zmq_proxy_steerable,
|
| 56 |
+
frontend._zmq_socket,
|
| 57 |
+
backend._zmq_socket,
|
| 58 |
+
capture,
|
| 59 |
+
control,
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
__all__ = ['device', 'proxy', 'proxy_steerable']
|
.venv/lib/python3.11/site-packages/zmq/backend/cffi/error.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""zmq error functions"""
|
| 2 |
+
|
| 3 |
+
# Copyright (C) PyZMQ Developers
|
| 4 |
+
# Distributed under the terms of the Modified BSD License.
|
| 5 |
+
|
| 6 |
+
from ._cffi import ffi
|
| 7 |
+
from ._cffi import lib as C
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def strerror(errno):
|
| 11 |
+
s = ffi.string(C.zmq_strerror(errno))
|
| 12 |
+
if not isinstance(s, str):
|
| 13 |
+
# py3
|
| 14 |
+
s = s.decode()
|
| 15 |
+
return s
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
zmq_errno = C.zmq_errno
|
| 19 |
+
|
| 20 |
+
__all__ = ['strerror', 'zmq_errno']
|
.venv/lib/python3.11/site-packages/zmq/backend/cffi/message.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Dummy Frame object"""
|
| 2 |
+
|
| 3 |
+
# Copyright (C) PyZMQ Developers
|
| 4 |
+
# Distributed under the terms of the Modified BSD License.
|
| 5 |
+
|
| 6 |
+
import errno
|
| 7 |
+
from threading import Event
|
| 8 |
+
|
| 9 |
+
import zmq
|
| 10 |
+
import zmq.error
|
| 11 |
+
from zmq.constants import ETERM
|
| 12 |
+
|
| 13 |
+
from ._cffi import ffi
|
| 14 |
+
from ._cffi import lib as C
|
| 15 |
+
|
| 16 |
+
zmq_gc = None
|
| 17 |
+
|
| 18 |
+
try:
|
| 19 |
+
from __pypy__.bufferable import bufferable as maybe_bufferable
|
| 20 |
+
except ImportError:
|
| 21 |
+
maybe_bufferable = object
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def _content(obj):
|
| 25 |
+
"""Return content of obj as bytes"""
|
| 26 |
+
if type(obj) is bytes:
|
| 27 |
+
return obj
|
| 28 |
+
if not isinstance(obj, memoryview):
|
| 29 |
+
obj = memoryview(obj)
|
| 30 |
+
return obj.tobytes()
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _check_rc(rc):
|
| 34 |
+
err = C.zmq_errno()
|
| 35 |
+
if rc == -1:
|
| 36 |
+
if err == errno.EINTR:
|
| 37 |
+
raise zmq.error.InterrruptedSystemCall(err)
|
| 38 |
+
elif err == errno.EAGAIN:
|
| 39 |
+
raise zmq.error.Again(errno)
|
| 40 |
+
elif err == ETERM:
|
| 41 |
+
raise zmq.error.ContextTerminated(err)
|
| 42 |
+
else:
|
| 43 |
+
raise zmq.error.ZMQError(err)
|
| 44 |
+
return 0
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class Frame(maybe_bufferable):
|
| 48 |
+
_data = None
|
| 49 |
+
tracker = None
|
| 50 |
+
closed = False
|
| 51 |
+
more = False
|
| 52 |
+
_buffer = None
|
| 53 |
+
_bytes = None
|
| 54 |
+
_failed_init = False
|
| 55 |
+
tracker_event = None
|
| 56 |
+
zmq_msg = None
|
| 57 |
+
|
| 58 |
+
def __init__(self, data=None, track=False, copy=None, copy_threshold=None):
|
| 59 |
+
self._failed_init = True
|
| 60 |
+
|
| 61 |
+
self.zmq_msg = ffi.cast('zmq_msg_t[1]', C.malloc(ffi.sizeof("zmq_msg_t")))
|
| 62 |
+
|
| 63 |
+
# self.tracker should start finished
|
| 64 |
+
# except in the case where we are sharing memory with libzmq
|
| 65 |
+
if track:
|
| 66 |
+
self.tracker = zmq._FINISHED_TRACKER
|
| 67 |
+
|
| 68 |
+
if isinstance(data, str):
|
| 69 |
+
raise TypeError(
|
| 70 |
+
"Unicode strings are not allowed. Only: bytes, buffer interfaces."
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
if data is None:
|
| 74 |
+
rc = C.zmq_msg_init(self.zmq_msg)
|
| 75 |
+
_check_rc(rc)
|
| 76 |
+
self._failed_init = False
|
| 77 |
+
return
|
| 78 |
+
|
| 79 |
+
self._data = data
|
| 80 |
+
if type(data) is bytes:
|
| 81 |
+
# avoid unnecessary copy on .bytes access
|
| 82 |
+
self._bytes = data
|
| 83 |
+
|
| 84 |
+
self._buffer = memoryview(data)
|
| 85 |
+
c_data = ffi.from_buffer(self._buffer)
|
| 86 |
+
data_len_c = self._buffer.nbytes
|
| 87 |
+
|
| 88 |
+
if copy is None:
|
| 89 |
+
if copy_threshold and data_len_c < copy_threshold:
|
| 90 |
+
copy = True
|
| 91 |
+
else:
|
| 92 |
+
copy = False
|
| 93 |
+
|
| 94 |
+
if copy:
|
| 95 |
+
# copy message data instead of sharing memory
|
| 96 |
+
rc = C.zmq_msg_init_size(self.zmq_msg, data_len_c)
|
| 97 |
+
_check_rc(rc)
|
| 98 |
+
ffi.buffer(C.zmq_msg_data(self.zmq_msg), data_len_c)[:] = self._buffer
|
| 99 |
+
self._failed_init = False
|
| 100 |
+
return
|
| 101 |
+
|
| 102 |
+
# Getting here means that we are doing a true zero-copy Frame,
|
| 103 |
+
# where libzmq and Python are sharing memory.
|
| 104 |
+
# Hook up garbage collection with MessageTracker and zmq_free_fn
|
| 105 |
+
|
| 106 |
+
# Event and MessageTracker for monitoring when zmq is done with data:
|
| 107 |
+
if track:
|
| 108 |
+
evt = Event()
|
| 109 |
+
self.tracker_event = evt
|
| 110 |
+
self.tracker = zmq.MessageTracker(evt)
|
| 111 |
+
# create the hint for zmq_free_fn
|
| 112 |
+
# two pointers: the zmq_gc context and a message to be sent to the zmq_gc PULL socket
|
| 113 |
+
# allows libzmq to signal to Python when it is done with Python-owned memory.
|
| 114 |
+
global zmq_gc
|
| 115 |
+
if zmq_gc is None:
|
| 116 |
+
from zmq.utils.garbage import gc as zmq_gc
|
| 117 |
+
# can't use ffi.new because it will be freed at the wrong time!
|
| 118 |
+
hint = ffi.cast("zhint[1]", C.malloc(ffi.sizeof("zhint")))
|
| 119 |
+
hint[0].id = zmq_gc.store(data, self.tracker_event)
|
| 120 |
+
if not zmq_gc._push_mutex:
|
| 121 |
+
zmq_gc._push_mutex = C.mutex_allocate()
|
| 122 |
+
|
| 123 |
+
hint[0].mutex = ffi.cast("mutex_t*", zmq_gc._push_mutex)
|
| 124 |
+
hint[0].sock = ffi.cast("void*", zmq_gc._push_socket.underlying)
|
| 125 |
+
|
| 126 |
+
# calls zmq_wrap_msg_init_data with the C.free_python_msg callback
|
| 127 |
+
rc = C.zmq_wrap_msg_init_data(
|
| 128 |
+
self.zmq_msg,
|
| 129 |
+
c_data,
|
| 130 |
+
data_len_c,
|
| 131 |
+
hint,
|
| 132 |
+
)
|
| 133 |
+
if rc != 0:
|
| 134 |
+
C.free(hint)
|
| 135 |
+
C.free(self.zmq_msg)
|
| 136 |
+
_check_rc(rc)
|
| 137 |
+
self._failed_init = False
|
| 138 |
+
|
| 139 |
+
def __del__(self):
|
| 140 |
+
if not self.closed and not self._failed_init:
|
| 141 |
+
self.close()
|
| 142 |
+
|
| 143 |
+
def close(self):
|
| 144 |
+
if self.closed or self._failed_init or self.zmq_msg is None:
|
| 145 |
+
return
|
| 146 |
+
self.closed = True
|
| 147 |
+
rc = C.zmq_msg_close(self.zmq_msg)
|
| 148 |
+
C.free(self.zmq_msg)
|
| 149 |
+
self.zmq_msg = None
|
| 150 |
+
if rc != 0:
|
| 151 |
+
_check_rc(rc)
|
| 152 |
+
|
| 153 |
+
def _buffer_from_zmq_msg(self):
|
| 154 |
+
"""one-time extract buffer from zmq_msg
|
| 155 |
+
|
| 156 |
+
for Frames created by recv
|
| 157 |
+
"""
|
| 158 |
+
if self._data is None:
|
| 159 |
+
self._data = ffi.buffer(
|
| 160 |
+
C.zmq_msg_data(self.zmq_msg), C.zmq_msg_size(self.zmq_msg)
|
| 161 |
+
)
|
| 162 |
+
if self._buffer is None:
|
| 163 |
+
self._buffer = memoryview(self._data)
|
| 164 |
+
|
| 165 |
+
@property
|
| 166 |
+
def buffer(self):
|
| 167 |
+
if self._buffer is None:
|
| 168 |
+
self._buffer_from_zmq_msg()
|
| 169 |
+
return self._buffer
|
| 170 |
+
|
| 171 |
+
@property
|
| 172 |
+
def bytes(self):
|
| 173 |
+
if self._bytes is None:
|
| 174 |
+
self._bytes = self.buffer.tobytes()
|
| 175 |
+
return self._bytes
|
| 176 |
+
|
| 177 |
+
def __len__(self):
|
| 178 |
+
return self.buffer.nbytes
|
| 179 |
+
|
| 180 |
+
def __eq__(self, other):
|
| 181 |
+
return self.bytes == _content(other)
|
| 182 |
+
|
| 183 |
+
@property
|
| 184 |
+
def done(self):
|
| 185 |
+
return self.tracker.done()
|
| 186 |
+
|
| 187 |
+
def __buffer__(self, flags):
|
| 188 |
+
return self.buffer
|
| 189 |
+
|
| 190 |
+
def __copy__(self):
|
| 191 |
+
"""Create a shallow copy of the message.
|
| 192 |
+
|
| 193 |
+
This does not copy the contents of the Frame, just the pointer.
|
| 194 |
+
This will increment the 0MQ ref count of the message, but not
|
| 195 |
+
the ref count of the Python object. That is only done once when
|
| 196 |
+
the Python is first turned into a 0MQ message.
|
| 197 |
+
"""
|
| 198 |
+
return self.fast_copy()
|
| 199 |
+
|
| 200 |
+
def fast_copy(self):
|
| 201 |
+
"""Fast shallow copy of the Frame.
|
| 202 |
+
|
| 203 |
+
Does not copy underlying data.
|
| 204 |
+
"""
|
| 205 |
+
new_msg = Frame()
|
| 206 |
+
# This does not copy the contents, but just increases the ref-count
|
| 207 |
+
# of the zmq_msg by one.
|
| 208 |
+
C.zmq_msg_copy(new_msg.zmq_msg, self.zmq_msg)
|
| 209 |
+
# Copy the ref to underlying data
|
| 210 |
+
new_msg._data = self._data
|
| 211 |
+
new_msg._buffer = self._buffer
|
| 212 |
+
|
| 213 |
+
# Frame copies share the tracker and tracker_event
|
| 214 |
+
new_msg.tracker_event = self.tracker_event
|
| 215 |
+
new_msg.tracker = self.tracker
|
| 216 |
+
|
| 217 |
+
return new_msg
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
Message = Frame
|
| 221 |
+
|
| 222 |
+
__all__ = ['Frame', 'Message']
|
.venv/lib/python3.11/site-packages/zmq/backend/cffi/socket.py
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""zmq Socket class"""
|
| 2 |
+
|
| 3 |
+
# Copyright (C) PyZMQ Developers
|
| 4 |
+
# Distributed under the terms of the Modified BSD License.
|
| 5 |
+
|
| 6 |
+
import errno as errno_mod
|
| 7 |
+
|
| 8 |
+
import zmq
|
| 9 |
+
from zmq.constants import SocketOption, _OptType
|
| 10 |
+
from zmq.error import ZMQError, _check_rc, _check_version
|
| 11 |
+
|
| 12 |
+
from ._cffi import ffi
|
| 13 |
+
from ._cffi import lib as C
|
| 14 |
+
from .message import Frame
|
| 15 |
+
from .utils import _retry_sys_call
|
| 16 |
+
|
| 17 |
+
nsp = new_sizet_pointer = lambda length: ffi.new('size_t*', length)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def new_uint64_pointer():
|
| 21 |
+
return ffi.new('uint64_t*'), nsp(ffi.sizeof('uint64_t'))
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def new_int64_pointer():
|
| 25 |
+
return ffi.new('int64_t*'), nsp(ffi.sizeof('int64_t'))
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def new_int_pointer():
|
| 29 |
+
return ffi.new('int*'), nsp(ffi.sizeof('int'))
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def new_binary_data(length):
|
| 33 |
+
return ffi.new(f'char[{length:d}]'), nsp(ffi.sizeof('char') * length)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def value_uint64_pointer(val):
|
| 37 |
+
return ffi.new('uint64_t*', val), ffi.sizeof('uint64_t')
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def value_int64_pointer(val):
|
| 41 |
+
return ffi.new('int64_t*', val), ffi.sizeof('int64_t')
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def value_int_pointer(val):
|
| 45 |
+
return ffi.new('int*', val), ffi.sizeof('int')
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def value_binary_data(val, length):
|
| 49 |
+
return ffi.new(f'char[{length + 1:d}]', val), ffi.sizeof('char') * length
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
ZMQ_FD_64BIT = ffi.sizeof('ZMQ_FD_T') == 8
|
| 53 |
+
|
| 54 |
+
IPC_PATH_MAX_LEN = C.get_ipc_path_max_len()
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def new_pointer_from_opt(option, length=0):
|
| 58 |
+
opt_type = getattr(option, "_opt_type", _OptType.int)
|
| 59 |
+
|
| 60 |
+
if opt_type == _OptType.int64 or (ZMQ_FD_64BIT and opt_type == _OptType.fd):
|
| 61 |
+
return new_int64_pointer()
|
| 62 |
+
elif opt_type == _OptType.bytes:
|
| 63 |
+
return new_binary_data(length)
|
| 64 |
+
else:
|
| 65 |
+
# default
|
| 66 |
+
return new_int_pointer()
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def value_from_opt_pointer(option, opt_pointer, length=0):
|
| 70 |
+
try:
|
| 71 |
+
option = SocketOption(option)
|
| 72 |
+
except ValueError:
|
| 73 |
+
# unrecognized option,
|
| 74 |
+
# assume from the future,
|
| 75 |
+
# let EINVAL raise
|
| 76 |
+
opt_type = _OptType.int
|
| 77 |
+
else:
|
| 78 |
+
opt_type = option._opt_type
|
| 79 |
+
|
| 80 |
+
if opt_type == _OptType.bytes:
|
| 81 |
+
return ffi.buffer(opt_pointer, length)[:]
|
| 82 |
+
else:
|
| 83 |
+
return int(opt_pointer[0])
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def initialize_opt_pointer(option, value, length=0):
|
| 87 |
+
opt_type = getattr(option, "_opt_type", _OptType.int)
|
| 88 |
+
if opt_type == _OptType.int64 or (ZMQ_FD_64BIT and opt_type == _OptType.fd):
|
| 89 |
+
return value_int64_pointer(value)
|
| 90 |
+
elif opt_type == _OptType.bytes:
|
| 91 |
+
return value_binary_data(value, length)
|
| 92 |
+
else:
|
| 93 |
+
return value_int_pointer(value)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class Socket:
|
| 97 |
+
context = None
|
| 98 |
+
socket_type = None
|
| 99 |
+
_zmq_socket = None
|
| 100 |
+
_closed = None
|
| 101 |
+
_ref = None
|
| 102 |
+
_shadow = False
|
| 103 |
+
copy_threshold = 0
|
| 104 |
+
|
| 105 |
+
def __init__(self, context=None, socket_type=None, shadow=0, copy_threshold=None):
|
| 106 |
+
if copy_threshold is None:
|
| 107 |
+
copy_threshold = zmq.COPY_THRESHOLD
|
| 108 |
+
self.copy_threshold = copy_threshold
|
| 109 |
+
|
| 110 |
+
self.context = context
|
| 111 |
+
if shadow:
|
| 112 |
+
self._zmq_socket = ffi.cast("void *", shadow)
|
| 113 |
+
self._shadow = True
|
| 114 |
+
else:
|
| 115 |
+
self._shadow = False
|
| 116 |
+
self._zmq_socket = C.zmq_socket(context._zmq_ctx, socket_type)
|
| 117 |
+
if self._zmq_socket == ffi.NULL:
|
| 118 |
+
raise ZMQError()
|
| 119 |
+
self._closed = False
|
| 120 |
+
|
| 121 |
+
@property
|
| 122 |
+
def underlying(self):
|
| 123 |
+
"""The address of the underlying libzmq socket"""
|
| 124 |
+
return int(ffi.cast('size_t', self._zmq_socket))
|
| 125 |
+
|
| 126 |
+
def _check_closed_deep(self):
|
| 127 |
+
"""thorough check of whether the socket has been closed,
|
| 128 |
+
even if by another entity (e.g. ctx.destroy).
|
| 129 |
+
|
| 130 |
+
Only used by the `closed` property.
|
| 131 |
+
|
| 132 |
+
returns True if closed, False otherwise
|
| 133 |
+
"""
|
| 134 |
+
if self._closed:
|
| 135 |
+
return True
|
| 136 |
+
try:
|
| 137 |
+
self.get(zmq.TYPE)
|
| 138 |
+
except ZMQError as e:
|
| 139 |
+
if e.errno == zmq.ENOTSOCK:
|
| 140 |
+
self._closed = True
|
| 141 |
+
return True
|
| 142 |
+
elif e.errno == zmq.ETERM:
|
| 143 |
+
pass
|
| 144 |
+
else:
|
| 145 |
+
raise
|
| 146 |
+
return False
|
| 147 |
+
|
| 148 |
+
@property
|
| 149 |
+
def closed(self):
|
| 150 |
+
return self._check_closed_deep()
|
| 151 |
+
|
| 152 |
+
def close(self, linger=None):
|
| 153 |
+
rc = 0
|
| 154 |
+
if not self._closed and hasattr(self, '_zmq_socket'):
|
| 155 |
+
if self._zmq_socket is not None:
|
| 156 |
+
if linger is not None:
|
| 157 |
+
self.set(zmq.LINGER, linger)
|
| 158 |
+
rc = C.zmq_close(self._zmq_socket)
|
| 159 |
+
self._closed = True
|
| 160 |
+
if rc < 0:
|
| 161 |
+
_check_rc(rc)
|
| 162 |
+
|
| 163 |
+
def bind(self, address):
|
| 164 |
+
if isinstance(address, str):
|
| 165 |
+
address_b = address.encode('utf8')
|
| 166 |
+
else:
|
| 167 |
+
address_b = address
|
| 168 |
+
if isinstance(address, bytes):
|
| 169 |
+
address = address_b.decode('utf8')
|
| 170 |
+
rc = C.zmq_bind(self._zmq_socket, address_b)
|
| 171 |
+
if rc < 0:
|
| 172 |
+
if IPC_PATH_MAX_LEN and C.zmq_errno() == errno_mod.ENAMETOOLONG:
|
| 173 |
+
path = address.split('://', 1)[-1]
|
| 174 |
+
msg = (
|
| 175 |
+
f'ipc path "{path}" is longer than {IPC_PATH_MAX_LEN} '
|
| 176 |
+
'characters (sizeof(sockaddr_un.sun_path)).'
|
| 177 |
+
)
|
| 178 |
+
raise ZMQError(C.zmq_errno(), msg=msg)
|
| 179 |
+
elif C.zmq_errno() == errno_mod.ENOENT:
|
| 180 |
+
path = address.split('://', 1)[-1]
|
| 181 |
+
msg = f'No such file or directory for ipc path "{path}".'
|
| 182 |
+
raise ZMQError(C.zmq_errno(), msg=msg)
|
| 183 |
+
else:
|
| 184 |
+
_check_rc(rc)
|
| 185 |
+
|
| 186 |
+
def unbind(self, address):
|
| 187 |
+
_check_version((3, 2), "unbind")
|
| 188 |
+
if isinstance(address, str):
|
| 189 |
+
address = address.encode('utf8')
|
| 190 |
+
rc = C.zmq_unbind(self._zmq_socket, address)
|
| 191 |
+
_check_rc(rc)
|
| 192 |
+
|
| 193 |
+
def connect(self, address):
|
| 194 |
+
if isinstance(address, str):
|
| 195 |
+
address = address.encode('utf8')
|
| 196 |
+
rc = C.zmq_connect(self._zmq_socket, address)
|
| 197 |
+
_check_rc(rc)
|
| 198 |
+
|
| 199 |
+
def disconnect(self, address):
|
| 200 |
+
_check_version((3, 2), "disconnect")
|
| 201 |
+
if isinstance(address, str):
|
| 202 |
+
address = address.encode('utf8')
|
| 203 |
+
rc = C.zmq_disconnect(self._zmq_socket, address)
|
| 204 |
+
_check_rc(rc)
|
| 205 |
+
|
| 206 |
+
def set(self, option, value):
|
| 207 |
+
length = None
|
| 208 |
+
if isinstance(value, str):
|
| 209 |
+
raise TypeError("unicode not allowed, use bytes")
|
| 210 |
+
|
| 211 |
+
try:
|
| 212 |
+
option = SocketOption(option)
|
| 213 |
+
except ValueError:
|
| 214 |
+
# unrecognized option,
|
| 215 |
+
# assume from the future,
|
| 216 |
+
# let EINVAL raise
|
| 217 |
+
opt_type = _OptType.int
|
| 218 |
+
else:
|
| 219 |
+
opt_type = option._opt_type
|
| 220 |
+
|
| 221 |
+
if isinstance(value, bytes):
|
| 222 |
+
if opt_type != _OptType.bytes:
|
| 223 |
+
raise TypeError(f"not a bytes sockopt: {option}")
|
| 224 |
+
length = len(value)
|
| 225 |
+
|
| 226 |
+
c_value_pointer, c_sizet = initialize_opt_pointer(option, value, length)
|
| 227 |
+
|
| 228 |
+
_retry_sys_call(
|
| 229 |
+
C.zmq_setsockopt,
|
| 230 |
+
self._zmq_socket,
|
| 231 |
+
option,
|
| 232 |
+
ffi.cast('void*', c_value_pointer),
|
| 233 |
+
c_sizet,
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
def get(self, option):
|
| 237 |
+
try:
|
| 238 |
+
option = SocketOption(option)
|
| 239 |
+
except ValueError:
|
| 240 |
+
# unrecognized option,
|
| 241 |
+
# assume from the future,
|
| 242 |
+
# let EINVAL raise
|
| 243 |
+
opt_type = _OptType.int
|
| 244 |
+
else:
|
| 245 |
+
opt_type = option._opt_type
|
| 246 |
+
|
| 247 |
+
c_value_pointer, c_sizet_pointer = new_pointer_from_opt(option, length=255)
|
| 248 |
+
|
| 249 |
+
_retry_sys_call(
|
| 250 |
+
C.zmq_getsockopt, self._zmq_socket, option, c_value_pointer, c_sizet_pointer
|
| 251 |
+
)
|
| 252 |
+
|
| 253 |
+
sz = c_sizet_pointer[0]
|
| 254 |
+
v = value_from_opt_pointer(option, c_value_pointer, sz)
|
| 255 |
+
if (
|
| 256 |
+
option != zmq.SocketOption.ROUTING_ID
|
| 257 |
+
and opt_type == _OptType.bytes
|
| 258 |
+
and v.endswith(b'\0')
|
| 259 |
+
):
|
| 260 |
+
v = v[:-1]
|
| 261 |
+
return v
|
| 262 |
+
|
| 263 |
+
def _send_copy(self, buf, flags):
|
| 264 |
+
"""Send a copy of a bufferable"""
|
| 265 |
+
zmq_msg = ffi.new('zmq_msg_t*')
|
| 266 |
+
if not isinstance(buf, bytes):
|
| 267 |
+
# cast any bufferable data to bytes via memoryview
|
| 268 |
+
buf = memoryview(buf).tobytes()
|
| 269 |
+
|
| 270 |
+
c_message = ffi.new('char[]', buf)
|
| 271 |
+
rc = C.zmq_msg_init_size(zmq_msg, len(buf))
|
| 272 |
+
_check_rc(rc)
|
| 273 |
+
C.memcpy(C.zmq_msg_data(zmq_msg), c_message, len(buf))
|
| 274 |
+
_retry_sys_call(C.zmq_msg_send, zmq_msg, self._zmq_socket, flags)
|
| 275 |
+
rc2 = C.zmq_msg_close(zmq_msg)
|
| 276 |
+
_check_rc(rc2)
|
| 277 |
+
|
| 278 |
+
def _send_frame(self, frame, flags):
|
| 279 |
+
"""Send a Frame on this socket in a non-copy manner."""
|
| 280 |
+
# Always copy the Frame so the original message isn't garbage collected.
|
| 281 |
+
# This doesn't do a real copy, just a reference.
|
| 282 |
+
frame_copy = frame.fast_copy()
|
| 283 |
+
zmq_msg = frame_copy.zmq_msg
|
| 284 |
+
_retry_sys_call(C.zmq_msg_send, zmq_msg, self._zmq_socket, flags)
|
| 285 |
+
tracker = frame_copy.tracker
|
| 286 |
+
frame_copy.close()
|
| 287 |
+
return tracker
|
| 288 |
+
|
| 289 |
+
def send(self, data, flags=0, copy=False, track=False):
|
| 290 |
+
if isinstance(data, str):
|
| 291 |
+
raise TypeError("Message must be in bytes, not a unicode object")
|
| 292 |
+
|
| 293 |
+
if copy and not isinstance(data, Frame):
|
| 294 |
+
return self._send_copy(data, flags)
|
| 295 |
+
else:
|
| 296 |
+
close_frame = False
|
| 297 |
+
if isinstance(data, Frame):
|
| 298 |
+
if track and not data.tracker:
|
| 299 |
+
raise ValueError('Not a tracked message')
|
| 300 |
+
frame = data
|
| 301 |
+
else:
|
| 302 |
+
if self.copy_threshold:
|
| 303 |
+
buf = memoryview(data)
|
| 304 |
+
# always copy messages smaller than copy_threshold
|
| 305 |
+
if buf.nbytes < self.copy_threshold:
|
| 306 |
+
self._send_copy(buf, flags)
|
| 307 |
+
return zmq._FINISHED_TRACKER
|
| 308 |
+
frame = Frame(data, track=track, copy_threshold=self.copy_threshold)
|
| 309 |
+
close_frame = True
|
| 310 |
+
|
| 311 |
+
tracker = self._send_frame(frame, flags)
|
| 312 |
+
if close_frame:
|
| 313 |
+
frame.close()
|
| 314 |
+
return tracker
|
| 315 |
+
|
| 316 |
+
def recv(self, flags=0, copy=True, track=False):
|
| 317 |
+
if copy:
|
| 318 |
+
zmq_msg = ffi.new('zmq_msg_t*')
|
| 319 |
+
C.zmq_msg_init(zmq_msg)
|
| 320 |
+
else:
|
| 321 |
+
frame = zmq.Frame(track=track)
|
| 322 |
+
zmq_msg = frame.zmq_msg
|
| 323 |
+
|
| 324 |
+
try:
|
| 325 |
+
_retry_sys_call(C.zmq_msg_recv, zmq_msg, self._zmq_socket, flags)
|
| 326 |
+
except Exception:
|
| 327 |
+
if copy:
|
| 328 |
+
C.zmq_msg_close(zmq_msg)
|
| 329 |
+
raise
|
| 330 |
+
|
| 331 |
+
if not copy:
|
| 332 |
+
return frame
|
| 333 |
+
|
| 334 |
+
_buffer = ffi.buffer(C.zmq_msg_data(zmq_msg), C.zmq_msg_size(zmq_msg))
|
| 335 |
+
_bytes = _buffer[:]
|
| 336 |
+
rc = C.zmq_msg_close(zmq_msg)
|
| 337 |
+
_check_rc(rc)
|
| 338 |
+
return _bytes
|
| 339 |
+
|
| 340 |
+
def monitor(self, addr, events=-1):
|
| 341 |
+
"""s.monitor(addr, flags)
|
| 342 |
+
|
| 343 |
+
Start publishing socket events on inproc.
|
| 344 |
+
See libzmq docs for zmq_monitor for details.
|
| 345 |
+
|
| 346 |
+
Note: requires libzmq >= 3.2
|
| 347 |
+
|
| 348 |
+
Parameters
|
| 349 |
+
----------
|
| 350 |
+
addr : str
|
| 351 |
+
The inproc url used for monitoring. Passing None as
|
| 352 |
+
the addr will cause an existing socket monitor to be
|
| 353 |
+
deregistered.
|
| 354 |
+
events : int [default: zmq.EVENT_ALL]
|
| 355 |
+
The zmq event bitmask for which events will be sent to the monitor.
|
| 356 |
+
"""
|
| 357 |
+
|
| 358 |
+
_check_version((3, 2), "monitor")
|
| 359 |
+
if events < 0:
|
| 360 |
+
events = zmq.EVENT_ALL
|
| 361 |
+
if addr is None:
|
| 362 |
+
addr = ffi.NULL
|
| 363 |
+
if isinstance(addr, str):
|
| 364 |
+
addr = addr.encode('utf8')
|
| 365 |
+
C.zmq_socket_monitor(self._zmq_socket, addr, events)
|
| 366 |
+
|
| 367 |
+
|
| 368 |
+
__all__ = ['Socket', 'IPC_PATH_MAX_LEN']
|
.venv/lib/python3.11/site-packages/zmq/backend/cffi/utils.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""miscellaneous zmq_utils wrapping"""
|
| 2 |
+
|
| 3 |
+
# Copyright (C) PyZMQ Developers
|
| 4 |
+
# Distributed under the terms of the Modified BSD License.
|
| 5 |
+
|
| 6 |
+
from zmq.error import InterruptedSystemCall, _check_rc, _check_version
|
| 7 |
+
|
| 8 |
+
from ._cffi import ffi
|
| 9 |
+
from ._cffi import lib as C
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def has(capability):
|
| 13 |
+
"""Check for zmq capability by name (e.g. 'ipc', 'curve')
|
| 14 |
+
|
| 15 |
+
.. versionadded:: libzmq-4.1
|
| 16 |
+
.. versionadded:: 14.1
|
| 17 |
+
"""
|
| 18 |
+
_check_version((4, 1), 'zmq.has')
|
| 19 |
+
if isinstance(capability, str):
|
| 20 |
+
capability = capability.encode('utf8')
|
| 21 |
+
return bool(C.zmq_has(capability))
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def curve_keypair():
|
| 25 |
+
"""generate a Z85 key pair for use with zmq.CURVE security
|
| 26 |
+
|
| 27 |
+
Requires libzmq (≥ 4.0) to have been built with CURVE support.
|
| 28 |
+
|
| 29 |
+
Returns
|
| 30 |
+
-------
|
| 31 |
+
(public, secret) : two bytestrings
|
| 32 |
+
The public and private key pair as 40 byte z85-encoded bytestrings.
|
| 33 |
+
"""
|
| 34 |
+
_check_version((3, 2), "curve_keypair")
|
| 35 |
+
public = ffi.new('char[64]')
|
| 36 |
+
private = ffi.new('char[64]')
|
| 37 |
+
rc = C.zmq_curve_keypair(public, private)
|
| 38 |
+
_check_rc(rc)
|
| 39 |
+
return ffi.buffer(public)[:40], ffi.buffer(private)[:40]
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def curve_public(private):
|
| 43 |
+
"""Compute the public key corresponding to a private key for use
|
| 44 |
+
with zmq.CURVE security
|
| 45 |
+
|
| 46 |
+
Requires libzmq (≥ 4.2) to have been built with CURVE support.
|
| 47 |
+
|
| 48 |
+
Parameters
|
| 49 |
+
----------
|
| 50 |
+
private
|
| 51 |
+
The private key as a 40 byte z85-encoded bytestring
|
| 52 |
+
Returns
|
| 53 |
+
-------
|
| 54 |
+
bytestring
|
| 55 |
+
The public key as a 40 byte z85-encoded bytestring.
|
| 56 |
+
"""
|
| 57 |
+
if isinstance(private, str):
|
| 58 |
+
private = private.encode('utf8')
|
| 59 |
+
_check_version((4, 2), "curve_public")
|
| 60 |
+
public = ffi.new('char[64]')
|
| 61 |
+
rc = C.zmq_curve_public(public, private)
|
| 62 |
+
_check_rc(rc)
|
| 63 |
+
return ffi.buffer(public)[:40]
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def _retry_sys_call(f, *args, **kwargs):
|
| 67 |
+
"""make a call, retrying if interrupted with EINTR"""
|
| 68 |
+
while True:
|
| 69 |
+
rc = f(*args)
|
| 70 |
+
try:
|
| 71 |
+
_check_rc(rc)
|
| 72 |
+
except InterruptedSystemCall:
|
| 73 |
+
continue
|
| 74 |
+
else:
|
| 75 |
+
break
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
__all__ = ['has', 'curve_keypair', 'curve_public']
|
.venv/lib/python3.11/site-packages/zmq/backend/cython/__init__.pxd
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from zmq.backend.cython._zmq cimport Context, Frame, Socket
|
.venv/lib/python3.11/site-packages/zmq/backend/cython/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Python bindings for core 0MQ objects."""
|
| 2 |
+
|
| 3 |
+
# Copyright (C) PyZMQ Developers
|
| 4 |
+
# Distributed under the terms of the Modified BSD License.
|
| 5 |
+
|
| 6 |
+
from . import _zmq
|
| 7 |
+
|
| 8 |
+
# mq not in __all__
|
| 9 |
+
from ._zmq import * # noqa
|
| 10 |
+
from ._zmq import monitored_queue # noqa
|
| 11 |
+
|
| 12 |
+
Message = _zmq.Frame
|
| 13 |
+
|
| 14 |
+
__all__ = ["Message"]
|
| 15 |
+
__all__.extend(_zmq.__all__)
|
.venv/lib/python3.11/site-packages/zmq/backend/cython/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (519 Bytes). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/backend/cython/__pycache__/_zmq.cpython-311.pyc
ADDED
|
Binary file (72.1 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/backend/cython/_externs.pxd
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
cdef extern from "mutex.h" nogil:
|
| 2 |
+
ctypedef struct mutex_t:
|
| 3 |
+
pass
|
| 4 |
+
cdef mutex_t* mutex_allocate()
|
| 5 |
+
cdef void mutex_dallocate(mutex_t*)
|
| 6 |
+
cdef int mutex_lock(mutex_t*)
|
| 7 |
+
cdef int mutex_unlock(mutex_t*)
|
| 8 |
+
|
| 9 |
+
cdef extern from "getpid_compat.h":
|
| 10 |
+
int getpid()
|
| 11 |
+
|
| 12 |
+
cdef extern from "ipcmaxlen.h":
|
| 13 |
+
int get_ipc_path_max_len()
|
.venv/lib/python3.11/site-packages/zmq/backend/cython/_zmq.pxd
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""zmq Cython backend augmented declarations"""
|
| 2 |
+
|
| 3 |
+
# Copyright (C) PyZMQ Developers
|
| 4 |
+
# Distributed under the terms of the Modified BSD License.
|
| 5 |
+
|
| 6 |
+
from zmq.backend.cython.libzmq cimport zmq_msg_t
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
cdef class Context:
|
| 10 |
+
|
| 11 |
+
cdef object __weakref__ # enable weakref
|
| 12 |
+
cdef void *handle # The C handle for the underlying zmq object.
|
| 13 |
+
cdef bint _shadow # whether the Context is a shadow wrapper of another
|
| 14 |
+
cdef int _pid # the pid of the process which created me (for fork safety)
|
| 15 |
+
|
| 16 |
+
cdef public bint closed # bool property for a closed context.
|
| 17 |
+
cdef inline int _term(self)
|
| 18 |
+
|
| 19 |
+
cdef class MessageTracker(object):
|
| 20 |
+
cdef set events # Message Event objects to track.
|
| 21 |
+
cdef set peers # Other Message or MessageTracker objects.
|
| 22 |
+
|
| 23 |
+
cdef class Frame:
|
| 24 |
+
|
| 25 |
+
cdef zmq_msg_t zmq_msg
|
| 26 |
+
cdef object _data # The actual message data as a Python object.
|
| 27 |
+
cdef object _buffer # A Python memoryview of the message contents
|
| 28 |
+
cdef object _bytes # A bytes copy of the message.
|
| 29 |
+
cdef bint _failed_init # flag to hold failed init
|
| 30 |
+
cdef public object tracker_event # Event for use with zmq_free_fn.
|
| 31 |
+
cdef public object tracker # MessageTracker object.
|
| 32 |
+
cdef public bint more # whether RCVMORE was set
|
| 33 |
+
|
| 34 |
+
cdef Frame fast_copy(self) # Create shallow copy of Message object.
|
| 35 |
+
|
| 36 |
+
cdef class Socket:
|
| 37 |
+
|
| 38 |
+
cdef object __weakref__ # enable weakref
|
| 39 |
+
cdef void *handle # The C handle for the underlying zmq object.
|
| 40 |
+
cdef bint _shadow # whether the Socket is a shadow wrapper of another
|
| 41 |
+
# Hold on to a reference to the context to make sure it is not garbage
|
| 42 |
+
# collected until the socket it done with it.
|
| 43 |
+
cdef public Context context # The zmq Context object that owns this.
|
| 44 |
+
cdef public bint _closed # bool property for a closed socket.
|
| 45 |
+
cdef public int copy_threshold # threshold below which pyzmq will always copy messages
|
| 46 |
+
cdef int _pid # the pid of the process which created me (for fork safety)
|
| 47 |
+
|
| 48 |
+
# cpdef methods for direct-cython access:
|
| 49 |
+
cpdef object send(self, data, int flags=*, bint copy=*, bint track=*)
|
| 50 |
+
cpdef object recv(self, int flags=*, bint copy=*, bint track=*)
|
.venv/lib/python3.11/site-packages/zmq/backend/cython/_zmq.py
ADDED
|
@@ -0,0 +1,1958 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# cython: freethreading_compatible = True
|
| 2 |
+
"""Cython backend for pyzmq"""
|
| 3 |
+
|
| 4 |
+
# Copyright (C) PyZMQ Developers
|
| 5 |
+
# Distributed under the terms of the Modified BSD License.
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
try:
|
| 10 |
+
import cython
|
| 11 |
+
|
| 12 |
+
if not cython.compiled:
|
| 13 |
+
raise ImportError()
|
| 14 |
+
except ImportError:
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
|
| 17 |
+
zmq_root = Path(__file__).parents[3]
|
| 18 |
+
msg = f"""
|
| 19 |
+
Attempting to import zmq Cython backend, which has not been compiled.
|
| 20 |
+
|
| 21 |
+
This probably means you are importing zmq from its source tree.
|
| 22 |
+
if this is what you want, make sure to do an in-place build first:
|
| 23 |
+
|
| 24 |
+
pip install -e '{zmq_root}'
|
| 25 |
+
|
| 26 |
+
If it is not, then '{zmq_root}' is probably on your sys.path,
|
| 27 |
+
when it shouldn't be. Is that your current working directory?
|
| 28 |
+
|
| 29 |
+
If neither of those is true and this file is actually installed,
|
| 30 |
+
something seems to have gone wrong with the install!
|
| 31 |
+
Please report at https://github.com/zeromq/pyzmq/issues
|
| 32 |
+
"""
|
| 33 |
+
raise ImportError(msg)
|
| 34 |
+
|
| 35 |
+
import time
|
| 36 |
+
import warnings
|
| 37 |
+
from threading import Event
|
| 38 |
+
from weakref import ref
|
| 39 |
+
|
| 40 |
+
import cython as C
|
| 41 |
+
from cython import (
|
| 42 |
+
NULL,
|
| 43 |
+
Py_ssize_t,
|
| 44 |
+
address,
|
| 45 |
+
bint,
|
| 46 |
+
cast,
|
| 47 |
+
cclass,
|
| 48 |
+
cfunc,
|
| 49 |
+
char,
|
| 50 |
+
declare,
|
| 51 |
+
inline,
|
| 52 |
+
nogil,
|
| 53 |
+
p_char,
|
| 54 |
+
p_void,
|
| 55 |
+
pointer,
|
| 56 |
+
size_t,
|
| 57 |
+
sizeof,
|
| 58 |
+
)
|
| 59 |
+
from cython.cimports.cpython import (
|
| 60 |
+
PyBytes_AsString,
|
| 61 |
+
PyBytes_FromStringAndSize,
|
| 62 |
+
PyBytes_Size,
|
| 63 |
+
PyErr_CheckSignals,
|
| 64 |
+
)
|
| 65 |
+
from cython.cimports.libc.errno import EAGAIN, EINTR, ENAMETOOLONG, ENOENT, ENOTSOCK
|
| 66 |
+
|
| 67 |
+
# cimports require Cython 3
|
| 68 |
+
from cython.cimports.libc.stdint import uint32_t
|
| 69 |
+
from cython.cimports.libc.stdio import fprintf
|
| 70 |
+
from cython.cimports.libc.stdio import stderr as cstderr
|
| 71 |
+
from cython.cimports.libc.stdlib import free, malloc
|
| 72 |
+
from cython.cimports.libc.string import memcpy
|
| 73 |
+
from cython.cimports.zmq.backend.cython._externs import (
|
| 74 |
+
get_ipc_path_max_len,
|
| 75 |
+
getpid,
|
| 76 |
+
mutex_allocate,
|
| 77 |
+
mutex_lock,
|
| 78 |
+
mutex_t,
|
| 79 |
+
mutex_unlock,
|
| 80 |
+
)
|
| 81 |
+
from cython.cimports.zmq.backend.cython.libzmq import (
|
| 82 |
+
ZMQ_ENOTSOCK,
|
| 83 |
+
ZMQ_ETERM,
|
| 84 |
+
ZMQ_EVENT_ALL,
|
| 85 |
+
ZMQ_IDENTITY,
|
| 86 |
+
ZMQ_IO_THREADS,
|
| 87 |
+
ZMQ_LINGER,
|
| 88 |
+
ZMQ_POLLIN,
|
| 89 |
+
ZMQ_RCVMORE,
|
| 90 |
+
ZMQ_ROUTER,
|
| 91 |
+
ZMQ_SNDMORE,
|
| 92 |
+
ZMQ_TYPE,
|
| 93 |
+
ZMQ_VERSION_MAJOR,
|
| 94 |
+
_zmq_version,
|
| 95 |
+
fd_t,
|
| 96 |
+
int64_t,
|
| 97 |
+
zmq_bind,
|
| 98 |
+
zmq_close,
|
| 99 |
+
zmq_connect,
|
| 100 |
+
zmq_ctx_destroy,
|
| 101 |
+
zmq_ctx_get,
|
| 102 |
+
zmq_ctx_new,
|
| 103 |
+
zmq_ctx_set,
|
| 104 |
+
zmq_curve_keypair,
|
| 105 |
+
zmq_curve_public,
|
| 106 |
+
zmq_device,
|
| 107 |
+
zmq_disconnect,
|
| 108 |
+
zmq_free_fn,
|
| 109 |
+
zmq_getsockopt,
|
| 110 |
+
zmq_has,
|
| 111 |
+
zmq_init,
|
| 112 |
+
zmq_join,
|
| 113 |
+
zmq_leave,
|
| 114 |
+
zmq_msg_close,
|
| 115 |
+
zmq_msg_copy,
|
| 116 |
+
zmq_msg_data,
|
| 117 |
+
zmq_msg_get,
|
| 118 |
+
zmq_msg_gets,
|
| 119 |
+
zmq_msg_group,
|
| 120 |
+
zmq_msg_init,
|
| 121 |
+
zmq_msg_init_data,
|
| 122 |
+
zmq_msg_init_size,
|
| 123 |
+
zmq_msg_recv,
|
| 124 |
+
zmq_msg_routing_id,
|
| 125 |
+
zmq_msg_send,
|
| 126 |
+
zmq_msg_set,
|
| 127 |
+
zmq_msg_set_group,
|
| 128 |
+
zmq_msg_set_routing_id,
|
| 129 |
+
zmq_msg_size,
|
| 130 |
+
zmq_msg_t,
|
| 131 |
+
zmq_pollitem_t,
|
| 132 |
+
zmq_proxy,
|
| 133 |
+
zmq_proxy_steerable,
|
| 134 |
+
zmq_setsockopt,
|
| 135 |
+
zmq_socket,
|
| 136 |
+
zmq_socket_monitor,
|
| 137 |
+
zmq_strerror,
|
| 138 |
+
zmq_unbind,
|
| 139 |
+
)
|
| 140 |
+
from cython.cimports.zmq.backend.cython.libzmq import zmq_errno as _zmq_errno
|
| 141 |
+
from cython.cimports.zmq.backend.cython.libzmq import zmq_poll as zmq_poll_c
|
| 142 |
+
from cython.cimports.zmq.utils.buffers import asbuffer_r
|
| 143 |
+
|
| 144 |
+
import zmq
|
| 145 |
+
from zmq.constants import SocketOption, _OptType
|
| 146 |
+
from zmq.error import InterruptedSystemCall, ZMQError, _check_version
|
| 147 |
+
|
| 148 |
+
IPC_PATH_MAX_LEN = get_ipc_path_max_len()
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
@cfunc
|
| 152 |
+
@inline
|
| 153 |
+
@C.exceptval(-1)
|
| 154 |
+
def _check_rc(rc: C.int, error_without_errno: bint = False) -> C.int:
|
| 155 |
+
"""internal utility for checking zmq return condition
|
| 156 |
+
|
| 157 |
+
and raising the appropriate Exception class
|
| 158 |
+
"""
|
| 159 |
+
errno: C.int = _zmq_errno()
|
| 160 |
+
PyErr_CheckSignals()
|
| 161 |
+
if errno == 0 and not error_without_errno:
|
| 162 |
+
return 0
|
| 163 |
+
if rc == -1: # if rc < -1, it's a bug in libzmq. Should we warn?
|
| 164 |
+
if errno == EINTR:
|
| 165 |
+
from zmq.error import InterruptedSystemCall
|
| 166 |
+
|
| 167 |
+
raise InterruptedSystemCall(errno)
|
| 168 |
+
elif errno == EAGAIN:
|
| 169 |
+
from zmq.error import Again
|
| 170 |
+
|
| 171 |
+
raise Again(errno)
|
| 172 |
+
elif errno == ZMQ_ETERM:
|
| 173 |
+
from zmq.error import ContextTerminated
|
| 174 |
+
|
| 175 |
+
raise ContextTerminated(errno)
|
| 176 |
+
else:
|
| 177 |
+
from zmq.error import ZMQError
|
| 178 |
+
|
| 179 |
+
raise ZMQError(errno)
|
| 180 |
+
return 0
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
# message Frame class
|
| 184 |
+
|
| 185 |
+
_zhint = C.struct(
|
| 186 |
+
sock=p_void,
|
| 187 |
+
mutex=pointer(mutex_t),
|
| 188 |
+
id=size_t,
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
@cfunc
|
| 193 |
+
@nogil
|
| 194 |
+
def free_python_msg(data: p_void, vhint: p_void) -> C.int:
|
| 195 |
+
"""A pure-C function for DECREF'ing Python-owned message data.
|
| 196 |
+
|
| 197 |
+
Sends a message on a PUSH socket
|
| 198 |
+
|
| 199 |
+
The hint is a `zhint` struct with two values:
|
| 200 |
+
|
| 201 |
+
sock (void *): pointer to the Garbage Collector's PUSH socket
|
| 202 |
+
id (size_t): the id to be used to construct a zmq_msg_t that should be sent on a PUSH socket,
|
| 203 |
+
signaling the Garbage Collector to remove its reference to the object.
|
| 204 |
+
|
| 205 |
+
When the Garbage Collector's PULL socket receives the message,
|
| 206 |
+
it deletes its reference to the object,
|
| 207 |
+
allowing Python to free the memory.
|
| 208 |
+
"""
|
| 209 |
+
msg = declare(zmq_msg_t)
|
| 210 |
+
msg_ptr: pointer(zmq_msg_t) = address(msg)
|
| 211 |
+
hint: pointer(_zhint) = cast(pointer(_zhint), vhint)
|
| 212 |
+
rc: C.int
|
| 213 |
+
|
| 214 |
+
if hint != NULL:
|
| 215 |
+
zmq_msg_init_size(msg_ptr, sizeof(size_t))
|
| 216 |
+
memcpy(zmq_msg_data(msg_ptr), address(hint.id), sizeof(size_t))
|
| 217 |
+
rc = mutex_lock(hint.mutex)
|
| 218 |
+
if rc != 0:
|
| 219 |
+
fprintf(cstderr, "pyzmq-gc mutex lock failed rc=%d\n", rc)
|
| 220 |
+
rc = zmq_msg_send(msg_ptr, hint.sock, 0)
|
| 221 |
+
if rc < 0:
|
| 222 |
+
# gc socket could have been closed, e.g. during process teardown.
|
| 223 |
+
# If so, ignore the failure because there's nothing to do.
|
| 224 |
+
if _zmq_errno() != ZMQ_ENOTSOCK:
|
| 225 |
+
fprintf(
|
| 226 |
+
cstderr, "pyzmq-gc send failed: %s\n", zmq_strerror(_zmq_errno())
|
| 227 |
+
)
|
| 228 |
+
rc = mutex_unlock(hint.mutex)
|
| 229 |
+
if rc != 0:
|
| 230 |
+
fprintf(cstderr, "pyzmq-gc mutex unlock failed rc=%d\n", rc)
|
| 231 |
+
|
| 232 |
+
zmq_msg_close(msg_ptr)
|
| 233 |
+
free(hint)
|
| 234 |
+
return 0
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
@cfunc
|
| 238 |
+
@inline
|
| 239 |
+
def _copy_zmq_msg_bytes(zmq_msg: pointer(zmq_msg_t)) -> bytes:
|
| 240 |
+
"""Copy the data from a zmq_msg_t"""
|
| 241 |
+
data_c: p_char = NULL
|
| 242 |
+
data_len_c: Py_ssize_t
|
| 243 |
+
data_c = cast(p_char, zmq_msg_data(zmq_msg))
|
| 244 |
+
data_len_c = zmq_msg_size(zmq_msg)
|
| 245 |
+
return PyBytes_FromStringAndSize(data_c, data_len_c)
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
_gc = None
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
@cclass
|
| 252 |
+
class Frame:
|
| 253 |
+
def __init__(
|
| 254 |
+
self, data=None, track=False, copy=None, copy_threshold=None, **kwargs
|
| 255 |
+
):
|
| 256 |
+
rc: C.int
|
| 257 |
+
data_c: p_char = NULL
|
| 258 |
+
data_len_c: Py_ssize_t = 0
|
| 259 |
+
hint: pointer(_zhint)
|
| 260 |
+
if copy_threshold is None:
|
| 261 |
+
copy_threshold = zmq.COPY_THRESHOLD
|
| 262 |
+
|
| 263 |
+
zmq_msg_ptr: pointer(zmq_msg_t) = address(self.zmq_msg)
|
| 264 |
+
# init more as False
|
| 265 |
+
self.more = False
|
| 266 |
+
|
| 267 |
+
# Save the data object in case the user wants the the data as a str.
|
| 268 |
+
self._data = data
|
| 269 |
+
self._failed_init = True # bool switch for dealloc
|
| 270 |
+
self._buffer = None # buffer view of data
|
| 271 |
+
self._bytes = None # bytes copy of data
|
| 272 |
+
|
| 273 |
+
self.tracker_event = None
|
| 274 |
+
self.tracker = None
|
| 275 |
+
# self.tracker should start finished
|
| 276 |
+
# except in the case where we are sharing memory with libzmq
|
| 277 |
+
if track:
|
| 278 |
+
self.tracker = zmq._FINISHED_TRACKER
|
| 279 |
+
|
| 280 |
+
if isinstance(data, str):
|
| 281 |
+
raise TypeError("Str objects not allowed. Only: bytes, buffer interfaces.")
|
| 282 |
+
|
| 283 |
+
if data is None:
|
| 284 |
+
rc = zmq_msg_init(zmq_msg_ptr)
|
| 285 |
+
_check_rc(rc)
|
| 286 |
+
self._failed_init = False
|
| 287 |
+
return
|
| 288 |
+
|
| 289 |
+
asbuffer_r(data, cast(pointer(p_void), address(data_c)), address(data_len_c))
|
| 290 |
+
|
| 291 |
+
# copy unspecified, apply copy_threshold
|
| 292 |
+
if copy is None:
|
| 293 |
+
if copy_threshold and data_len_c < copy_threshold:
|
| 294 |
+
copy = True
|
| 295 |
+
else:
|
| 296 |
+
copy = False
|
| 297 |
+
|
| 298 |
+
if copy:
|
| 299 |
+
# copy message data instead of sharing memory
|
| 300 |
+
rc = zmq_msg_init_size(zmq_msg_ptr, data_len_c)
|
| 301 |
+
_check_rc(rc)
|
| 302 |
+
memcpy(zmq_msg_data(zmq_msg_ptr), data_c, data_len_c)
|
| 303 |
+
self._failed_init = False
|
| 304 |
+
return
|
| 305 |
+
|
| 306 |
+
# Getting here means that we are doing a true zero-copy Frame,
|
| 307 |
+
# where libzmq and Python are sharing memory.
|
| 308 |
+
# Hook up garbage collection with MessageTracker and zmq_free_fn
|
| 309 |
+
|
| 310 |
+
# Event and MessageTracker for monitoring when zmq is done with data:
|
| 311 |
+
if track:
|
| 312 |
+
evt = Event()
|
| 313 |
+
self.tracker_event = evt
|
| 314 |
+
self.tracker = zmq.MessageTracker(evt)
|
| 315 |
+
# create the hint for zmq_free_fn
|
| 316 |
+
# two pointers: the gc context and a message to be sent to the gc PULL socket
|
| 317 |
+
# allows libzmq to signal to Python when it is done with Python-owned memory.
|
| 318 |
+
global _gc
|
| 319 |
+
if _gc is None:
|
| 320 |
+
from zmq.utils.garbage import gc as _gc
|
| 321 |
+
|
| 322 |
+
hint: pointer(_zhint) = cast(pointer(_zhint), malloc(sizeof(_zhint)))
|
| 323 |
+
hint.id = _gc.store(data, self.tracker_event)
|
| 324 |
+
if not _gc._push_mutex:
|
| 325 |
+
hint.mutex = mutex_allocate()
|
| 326 |
+
_gc._push_mutex = cast(size_t, hint.mutex)
|
| 327 |
+
else:
|
| 328 |
+
hint.mutex = cast(pointer(mutex_t), cast(size_t, _gc._push_mutex))
|
| 329 |
+
hint.sock = cast(p_void, cast(size_t, _gc._push_socket.underlying))
|
| 330 |
+
|
| 331 |
+
rc = zmq_msg_init_data(
|
| 332 |
+
zmq_msg_ptr,
|
| 333 |
+
cast(p_void, data_c),
|
| 334 |
+
data_len_c,
|
| 335 |
+
cast(pointer(zmq_free_fn), free_python_msg),
|
| 336 |
+
cast(p_void, hint),
|
| 337 |
+
)
|
| 338 |
+
if rc != 0:
|
| 339 |
+
free(hint)
|
| 340 |
+
_check_rc(rc)
|
| 341 |
+
self._failed_init = False
|
| 342 |
+
|
| 343 |
+
def __del__(self):
|
| 344 |
+
if self._failed_init:
|
| 345 |
+
return
|
| 346 |
+
# This simply decreases the 0MQ ref-count of zmq_msg.
|
| 347 |
+
with nogil:
|
| 348 |
+
rc: C.int = zmq_msg_close(address(self.zmq_msg))
|
| 349 |
+
_check_rc(rc)
|
| 350 |
+
|
| 351 |
+
def __copy__(self):
|
| 352 |
+
return self.fast_copy()
|
| 353 |
+
|
| 354 |
+
def fast_copy(self) -> Frame:
|
| 355 |
+
new_msg: Frame = Frame()
|
| 356 |
+
# This does not copy the contents, but just increases the ref-count
|
| 357 |
+
# of the zmq_msg by one.
|
| 358 |
+
zmq_msg_copy(address(new_msg.zmq_msg), address(self.zmq_msg))
|
| 359 |
+
# Copy the ref to data so the copy won't create a copy when str is
|
| 360 |
+
# called.
|
| 361 |
+
if self._data is not None:
|
| 362 |
+
new_msg._data = self._data
|
| 363 |
+
if self._buffer is not None:
|
| 364 |
+
new_msg._buffer = self._buffer
|
| 365 |
+
if self._bytes is not None:
|
| 366 |
+
new_msg._bytes = self._bytes
|
| 367 |
+
|
| 368 |
+
# Frame copies share the tracker and tracker_event
|
| 369 |
+
new_msg.tracker_event = self.tracker_event
|
| 370 |
+
new_msg.tracker = self.tracker
|
| 371 |
+
|
| 372 |
+
return new_msg
|
| 373 |
+
|
| 374 |
+
# buffer interface code adapted from petsc4py by Lisandro Dalcin, a BSD project
|
| 375 |
+
|
| 376 |
+
def __getbuffer__(self, buffer: pointer(Py_buffer), flags: C.int): # noqa: F821
|
| 377 |
+
# new-style (memoryview) buffer interface
|
| 378 |
+
buffer.buf = zmq_msg_data(address(self.zmq_msg))
|
| 379 |
+
buffer.len = zmq_msg_size(address(self.zmq_msg))
|
| 380 |
+
|
| 381 |
+
buffer.obj = self
|
| 382 |
+
buffer.readonly = 0
|
| 383 |
+
buffer.format = "B"
|
| 384 |
+
buffer.ndim = 1
|
| 385 |
+
buffer.shape = address(buffer.len)
|
| 386 |
+
buffer.strides = NULL
|
| 387 |
+
buffer.suboffsets = NULL
|
| 388 |
+
buffer.itemsize = 1
|
| 389 |
+
buffer.internal = NULL
|
| 390 |
+
|
| 391 |
+
def __len__(self) -> size_t:
|
| 392 |
+
"""Return the length of the message in bytes."""
|
| 393 |
+
sz: size_t = zmq_msg_size(address(self.zmq_msg))
|
| 394 |
+
return sz
|
| 395 |
+
|
| 396 |
+
@property
|
| 397 |
+
def buffer(self):
|
| 398 |
+
"""A memoryview of the message contents."""
|
| 399 |
+
_buffer = self._buffer and self._buffer()
|
| 400 |
+
if _buffer is not None:
|
| 401 |
+
return _buffer
|
| 402 |
+
_buffer = memoryview(self)
|
| 403 |
+
self._buffer = ref(_buffer)
|
| 404 |
+
return _buffer
|
| 405 |
+
|
| 406 |
+
@property
|
| 407 |
+
def bytes(self):
|
| 408 |
+
"""The message content as a Python bytes object.
|
| 409 |
+
|
| 410 |
+
The first time this property is accessed, a copy of the message
|
| 411 |
+
contents is made. From then on that same copy of the message is
|
| 412 |
+
returned.
|
| 413 |
+
"""
|
| 414 |
+
if self._bytes is None:
|
| 415 |
+
self._bytes = _copy_zmq_msg_bytes(address(self.zmq_msg))
|
| 416 |
+
return self._bytes
|
| 417 |
+
|
| 418 |
+
def get(self, option):
|
| 419 |
+
"""
|
| 420 |
+
Get a Frame option or property.
|
| 421 |
+
|
| 422 |
+
See the 0MQ API documentation for zmq_msg_get and zmq_msg_gets
|
| 423 |
+
for details on specific options.
|
| 424 |
+
|
| 425 |
+
.. versionadded:: libzmq-3.2
|
| 426 |
+
.. versionadded:: 13.0
|
| 427 |
+
|
| 428 |
+
.. versionchanged:: 14.3
|
| 429 |
+
add support for zmq_msg_gets (requires libzmq-4.1)
|
| 430 |
+
All message properties are strings.
|
| 431 |
+
|
| 432 |
+
.. versionchanged:: 17.0
|
| 433 |
+
Added support for `routing_id` and `group`.
|
| 434 |
+
Only available if draft API is enabled
|
| 435 |
+
with libzmq >= 4.2.
|
| 436 |
+
"""
|
| 437 |
+
rc: C.int = 0
|
| 438 |
+
property_c: p_char = NULL
|
| 439 |
+
|
| 440 |
+
# zmq_msg_get
|
| 441 |
+
if isinstance(option, int):
|
| 442 |
+
rc = zmq_msg_get(address(self.zmq_msg), option)
|
| 443 |
+
_check_rc(rc)
|
| 444 |
+
return rc
|
| 445 |
+
|
| 446 |
+
if option == 'routing_id':
|
| 447 |
+
routing_id: uint32_t = zmq_msg_routing_id(address(self.zmq_msg))
|
| 448 |
+
if routing_id == 0:
|
| 449 |
+
_check_rc(-1)
|
| 450 |
+
return routing_id
|
| 451 |
+
elif option == 'group':
|
| 452 |
+
buf = zmq_msg_group(address(self.zmq_msg))
|
| 453 |
+
if buf == NULL:
|
| 454 |
+
_check_rc(-1)
|
| 455 |
+
return buf.decode('utf8')
|
| 456 |
+
|
| 457 |
+
# zmq_msg_gets
|
| 458 |
+
_check_version((4, 1), "get string properties")
|
| 459 |
+
if isinstance(option, str):
|
| 460 |
+
option = option.encode('utf8')
|
| 461 |
+
|
| 462 |
+
if not isinstance(option, bytes):
|
| 463 |
+
raise TypeError(f"expected str, got: {option!r}")
|
| 464 |
+
|
| 465 |
+
property_c = option
|
| 466 |
+
|
| 467 |
+
result: p_char = cast(p_char, zmq_msg_gets(address(self.zmq_msg), property_c))
|
| 468 |
+
if result == NULL:
|
| 469 |
+
_check_rc(-1)
|
| 470 |
+
return result.decode('utf8')
|
| 471 |
+
|
| 472 |
+
def set(self, option, value):
|
| 473 |
+
"""Set a Frame option.
|
| 474 |
+
|
| 475 |
+
See the 0MQ API documentation for zmq_msg_set
|
| 476 |
+
for details on specific options.
|
| 477 |
+
|
| 478 |
+
.. versionadded:: libzmq-3.2
|
| 479 |
+
.. versionadded:: 13.0
|
| 480 |
+
.. versionchanged:: 17.0
|
| 481 |
+
Added support for `routing_id` and `group`.
|
| 482 |
+
Only available if draft API is enabled
|
| 483 |
+
with libzmq >= 4.2.
|
| 484 |
+
"""
|
| 485 |
+
rc: C.int
|
| 486 |
+
|
| 487 |
+
if option == 'routing_id':
|
| 488 |
+
routing_id: uint32_t = value
|
| 489 |
+
rc = zmq_msg_set_routing_id(address(self.zmq_msg), routing_id)
|
| 490 |
+
_check_rc(rc)
|
| 491 |
+
return
|
| 492 |
+
elif option == 'group':
|
| 493 |
+
if isinstance(value, str):
|
| 494 |
+
value = value.encode('utf8')
|
| 495 |
+
rc = zmq_msg_set_group(address(self.zmq_msg), value)
|
| 496 |
+
_check_rc(rc)
|
| 497 |
+
return
|
| 498 |
+
|
| 499 |
+
rc = zmq_msg_set(address(self.zmq_msg), option, value)
|
| 500 |
+
_check_rc(rc)
|
| 501 |
+
|
| 502 |
+
|
| 503 |
+
@cclass
|
| 504 |
+
class Context:
|
| 505 |
+
"""
|
| 506 |
+
Manage the lifecycle of a 0MQ context.
|
| 507 |
+
|
| 508 |
+
Parameters
|
| 509 |
+
----------
|
| 510 |
+
io_threads : int
|
| 511 |
+
The number of IO threads.
|
| 512 |
+
"""
|
| 513 |
+
|
| 514 |
+
def __init__(self, io_threads: C.int = 1, shadow: size_t = 0):
|
| 515 |
+
self.handle = NULL
|
| 516 |
+
self._pid = 0
|
| 517 |
+
self._shadow = False
|
| 518 |
+
|
| 519 |
+
if shadow:
|
| 520 |
+
self.handle = cast(p_void, shadow)
|
| 521 |
+
self._shadow = True
|
| 522 |
+
else:
|
| 523 |
+
self._shadow = False
|
| 524 |
+
if ZMQ_VERSION_MAJOR >= 3:
|
| 525 |
+
self.handle = zmq_ctx_new()
|
| 526 |
+
else:
|
| 527 |
+
self.handle = zmq_init(io_threads)
|
| 528 |
+
|
| 529 |
+
if self.handle == NULL:
|
| 530 |
+
raise ZMQError()
|
| 531 |
+
|
| 532 |
+
rc: C.int = 0
|
| 533 |
+
if ZMQ_VERSION_MAJOR >= 3 and not self._shadow:
|
| 534 |
+
rc = zmq_ctx_set(self.handle, ZMQ_IO_THREADS, io_threads)
|
| 535 |
+
_check_rc(rc)
|
| 536 |
+
|
| 537 |
+
self.closed = False
|
| 538 |
+
self._pid = getpid()
|
| 539 |
+
|
| 540 |
+
@property
|
| 541 |
+
def underlying(self):
|
| 542 |
+
"""The address of the underlying libzmq context"""
|
| 543 |
+
return cast(size_t, self.handle)
|
| 544 |
+
|
| 545 |
+
@cfunc
|
| 546 |
+
@inline
|
| 547 |
+
def _term(self) -> C.int:
|
| 548 |
+
rc: C.int = 0
|
| 549 |
+
if self.handle != NULL and not self.closed and getpid() == self._pid:
|
| 550 |
+
with nogil:
|
| 551 |
+
rc = zmq_ctx_destroy(self.handle)
|
| 552 |
+
self.handle = NULL
|
| 553 |
+
return rc
|
| 554 |
+
|
| 555 |
+
def term(self):
|
| 556 |
+
"""
|
| 557 |
+
Close or terminate the context.
|
| 558 |
+
|
| 559 |
+
This can be called to close the context by hand. If this is not called,
|
| 560 |
+
the context will automatically be closed when it is garbage collected.
|
| 561 |
+
"""
|
| 562 |
+
rc: C.int = self._term()
|
| 563 |
+
try:
|
| 564 |
+
_check_rc(rc)
|
| 565 |
+
except InterruptedSystemCall:
|
| 566 |
+
# ignore interrupted term
|
| 567 |
+
# see PEP 475 notes about close & EINTR for why
|
| 568 |
+
pass
|
| 569 |
+
|
| 570 |
+
self.closed = True
|
| 571 |
+
|
| 572 |
+
def set(self, option: C.int, optval):
|
| 573 |
+
"""
|
| 574 |
+
Set a context option.
|
| 575 |
+
|
| 576 |
+
See the 0MQ API documentation for zmq_ctx_set
|
| 577 |
+
for details on specific options.
|
| 578 |
+
|
| 579 |
+
.. versionadded:: libzmq-3.2
|
| 580 |
+
.. versionadded:: 13.0
|
| 581 |
+
|
| 582 |
+
Parameters
|
| 583 |
+
----------
|
| 584 |
+
option : int
|
| 585 |
+
The option to set. Available values will depend on your
|
| 586 |
+
version of libzmq. Examples include::
|
| 587 |
+
|
| 588 |
+
zmq.IO_THREADS, zmq.MAX_SOCKETS
|
| 589 |
+
|
| 590 |
+
optval : int
|
| 591 |
+
The value of the option to set.
|
| 592 |
+
"""
|
| 593 |
+
optval_int_c: C.int
|
| 594 |
+
rc: C.int
|
| 595 |
+
|
| 596 |
+
if self.closed:
|
| 597 |
+
raise RuntimeError("Context has been destroyed")
|
| 598 |
+
|
| 599 |
+
if not isinstance(optval, int):
|
| 600 |
+
raise TypeError(f'expected int, got: {optval!r}')
|
| 601 |
+
optval_int_c = optval
|
| 602 |
+
rc = zmq_ctx_set(self.handle, option, optval_int_c)
|
| 603 |
+
_check_rc(rc)
|
| 604 |
+
|
| 605 |
+
def get(self, option: C.int):
|
| 606 |
+
"""
|
| 607 |
+
Get the value of a context option.
|
| 608 |
+
|
| 609 |
+
See the 0MQ API documentation for zmq_ctx_get
|
| 610 |
+
for details on specific options.
|
| 611 |
+
|
| 612 |
+
.. versionadded:: libzmq-3.2
|
| 613 |
+
.. versionadded:: 13.0
|
| 614 |
+
|
| 615 |
+
Parameters
|
| 616 |
+
----------
|
| 617 |
+
option : int
|
| 618 |
+
The option to get. Available values will depend on your
|
| 619 |
+
version of libzmq. Examples include::
|
| 620 |
+
|
| 621 |
+
zmq.IO_THREADS, zmq.MAX_SOCKETS
|
| 622 |
+
|
| 623 |
+
Returns
|
| 624 |
+
-------
|
| 625 |
+
optval : int
|
| 626 |
+
The value of the option as an integer.
|
| 627 |
+
"""
|
| 628 |
+
rc: C.int
|
| 629 |
+
|
| 630 |
+
if self.closed:
|
| 631 |
+
raise RuntimeError("Context has been destroyed")
|
| 632 |
+
|
| 633 |
+
rc = zmq_ctx_get(self.handle, option)
|
| 634 |
+
_check_rc(rc, error_without_errno=False)
|
| 635 |
+
return rc
|
| 636 |
+
|
| 637 |
+
|
| 638 |
+
@cclass
|
| 639 |
+
class Socket:
|
| 640 |
+
"""
|
| 641 |
+
A 0MQ socket.
|
| 642 |
+
|
| 643 |
+
These objects will generally be constructed via the socket() method of a Context object.
|
| 644 |
+
|
| 645 |
+
Note: 0MQ Sockets are *not* threadsafe. **DO NOT** share them across threads.
|
| 646 |
+
|
| 647 |
+
Parameters
|
| 648 |
+
----------
|
| 649 |
+
context : Context
|
| 650 |
+
The 0MQ Context this Socket belongs to.
|
| 651 |
+
socket_type : int
|
| 652 |
+
The socket type, which can be any of the 0MQ socket types:
|
| 653 |
+
REQ, REP, PUB, SUB, PAIR, DEALER, ROUTER, PULL, PUSH, XPUB, XSUB.
|
| 654 |
+
|
| 655 |
+
See Also
|
| 656 |
+
--------
|
| 657 |
+
.Context.socket : method for creating a socket bound to a Context.
|
| 658 |
+
"""
|
| 659 |
+
|
| 660 |
+
def __init__(
|
| 661 |
+
self,
|
| 662 |
+
context=None,
|
| 663 |
+
socket_type: C.int = -1,
|
| 664 |
+
shadow: size_t = 0,
|
| 665 |
+
copy_threshold=None,
|
| 666 |
+
):
|
| 667 |
+
# pre-init
|
| 668 |
+
self.handle = NULL
|
| 669 |
+
self._pid = 0
|
| 670 |
+
self._shadow = False
|
| 671 |
+
self.context = None
|
| 672 |
+
|
| 673 |
+
if copy_threshold is None:
|
| 674 |
+
copy_threshold = zmq.COPY_THRESHOLD
|
| 675 |
+
self.copy_threshold = copy_threshold
|
| 676 |
+
|
| 677 |
+
self.handle = NULL
|
| 678 |
+
self.context = context
|
| 679 |
+
if shadow:
|
| 680 |
+
self._shadow = True
|
| 681 |
+
self.handle = cast(p_void, shadow)
|
| 682 |
+
else:
|
| 683 |
+
if context is None:
|
| 684 |
+
raise TypeError("context must be specified")
|
| 685 |
+
if socket_type < 0:
|
| 686 |
+
raise TypeError("socket_type must be specified")
|
| 687 |
+
self._shadow = False
|
| 688 |
+
self.handle = zmq_socket(self.context.handle, socket_type)
|
| 689 |
+
if self.handle == NULL:
|
| 690 |
+
raise ZMQError()
|
| 691 |
+
self._closed = False
|
| 692 |
+
self._pid = getpid()
|
| 693 |
+
|
| 694 |
+
@property
|
| 695 |
+
def underlying(self):
|
| 696 |
+
"""The address of the underlying libzmq socket"""
|
| 697 |
+
return cast(size_t, self.handle)
|
| 698 |
+
|
| 699 |
+
@property
|
| 700 |
+
def closed(self):
|
| 701 |
+
"""Whether the socket is closed"""
|
| 702 |
+
return _check_closed_deep(self)
|
| 703 |
+
|
| 704 |
+
def close(self, linger=None):
|
| 705 |
+
"""
|
| 706 |
+
Close the socket.
|
| 707 |
+
|
| 708 |
+
If linger is specified, LINGER sockopt will be set prior to closing.
|
| 709 |
+
|
| 710 |
+
This can be called to close the socket by hand. If this is not
|
| 711 |
+
called, the socket will automatically be closed when it is
|
| 712 |
+
garbage collected.
|
| 713 |
+
"""
|
| 714 |
+
rc: C.int = 0
|
| 715 |
+
linger_c: C.int
|
| 716 |
+
setlinger: bint = False
|
| 717 |
+
|
| 718 |
+
if linger is not None:
|
| 719 |
+
linger_c = linger
|
| 720 |
+
setlinger = True
|
| 721 |
+
|
| 722 |
+
if self.handle != NULL and not self._closed and getpid() == self._pid:
|
| 723 |
+
if setlinger:
|
| 724 |
+
zmq_setsockopt(self.handle, ZMQ_LINGER, address(linger_c), sizeof(int))
|
| 725 |
+
rc = zmq_close(self.handle)
|
| 726 |
+
if rc < 0 and zmq_errno() != ENOTSOCK:
|
| 727 |
+
# ignore ENOTSOCK (closed by Context)
|
| 728 |
+
_check_rc(rc)
|
| 729 |
+
self._closed = True
|
| 730 |
+
self.handle = NULL
|
| 731 |
+
|
| 732 |
+
def set(self, option: C.int, optval):
|
| 733 |
+
"""
|
| 734 |
+
Set socket options.
|
| 735 |
+
|
| 736 |
+
See the 0MQ API documentation for details on specific options.
|
| 737 |
+
|
| 738 |
+
Parameters
|
| 739 |
+
----------
|
| 740 |
+
option : int
|
| 741 |
+
The option to set. Available values will depend on your
|
| 742 |
+
version of libzmq. Examples include::
|
| 743 |
+
|
| 744 |
+
zmq.SUBSCRIBE, UNSUBSCRIBE, IDENTITY, HWM, LINGER, FD
|
| 745 |
+
|
| 746 |
+
optval : int or bytes
|
| 747 |
+
The value of the option to set.
|
| 748 |
+
|
| 749 |
+
Notes
|
| 750 |
+
-----
|
| 751 |
+
.. warning::
|
| 752 |
+
|
| 753 |
+
All options other than zmq.SUBSCRIBE, zmq.UNSUBSCRIBE and
|
| 754 |
+
zmq.LINGER only take effect for subsequent socket bind/connects.
|
| 755 |
+
"""
|
| 756 |
+
optval_int64_c: int64_t
|
| 757 |
+
optval_int_c: C.int
|
| 758 |
+
optval_c: p_char
|
| 759 |
+
sz: Py_ssize_t
|
| 760 |
+
|
| 761 |
+
_check_closed(self)
|
| 762 |
+
if isinstance(optval, str):
|
| 763 |
+
raise TypeError("unicode not allowed, use setsockopt_string")
|
| 764 |
+
|
| 765 |
+
try:
|
| 766 |
+
sopt = SocketOption(option)
|
| 767 |
+
except ValueError:
|
| 768 |
+
# unrecognized option,
|
| 769 |
+
# assume from the future,
|
| 770 |
+
# let EINVAL raise
|
| 771 |
+
opt_type = _OptType.int
|
| 772 |
+
else:
|
| 773 |
+
opt_type = sopt._opt_type
|
| 774 |
+
|
| 775 |
+
if opt_type == _OptType.bytes:
|
| 776 |
+
if not isinstance(optval, bytes):
|
| 777 |
+
raise TypeError(f'expected bytes, got: {optval!r}')
|
| 778 |
+
optval_c = PyBytes_AsString(optval)
|
| 779 |
+
sz = PyBytes_Size(optval)
|
| 780 |
+
_setsockopt(self.handle, option, optval_c, sz)
|
| 781 |
+
elif opt_type == _OptType.int64:
|
| 782 |
+
if not isinstance(optval, int):
|
| 783 |
+
raise TypeError(f'expected int, got: {optval!r}')
|
| 784 |
+
optval_int64_c = optval
|
| 785 |
+
_setsockopt(self.handle, option, address(optval_int64_c), sizeof(int64_t))
|
| 786 |
+
else:
|
| 787 |
+
# default is to assume int, which is what most new sockopts will be
|
| 788 |
+
# this lets pyzmq work with newer libzmq which may add constants
|
| 789 |
+
# pyzmq has not yet added, rather than artificially raising. Invalid
|
| 790 |
+
# sockopts will still raise just the same, but it will be libzmq doing
|
| 791 |
+
# the raising.
|
| 792 |
+
if not isinstance(optval, int):
|
| 793 |
+
raise TypeError(f'expected int, got: {optval!r}')
|
| 794 |
+
optval_int_c = optval
|
| 795 |
+
_setsockopt(self.handle, option, address(optval_int_c), sizeof(int))
|
| 796 |
+
|
| 797 |
+
def get(self, option: C.int):
|
| 798 |
+
"""
|
| 799 |
+
Get the value of a socket option.
|
| 800 |
+
|
| 801 |
+
See the 0MQ API documentation for details on specific options.
|
| 802 |
+
|
| 803 |
+
Parameters
|
| 804 |
+
----------
|
| 805 |
+
option : int
|
| 806 |
+
The option to get. Available values will depend on your
|
| 807 |
+
version of libzmq. Examples include::
|
| 808 |
+
|
| 809 |
+
zmq.IDENTITY, HWM, LINGER, FD, EVENTS
|
| 810 |
+
|
| 811 |
+
Returns
|
| 812 |
+
-------
|
| 813 |
+
optval : int or bytes
|
| 814 |
+
The value of the option as a bytestring or int.
|
| 815 |
+
"""
|
| 816 |
+
optval_int64_c = declare(int64_t)
|
| 817 |
+
optval_int_c = declare(C.int)
|
| 818 |
+
optval_fd_c = declare(fd_t)
|
| 819 |
+
identity_str_c = declare(char[255])
|
| 820 |
+
sz: size_t
|
| 821 |
+
|
| 822 |
+
_check_closed(self)
|
| 823 |
+
|
| 824 |
+
try:
|
| 825 |
+
sopt = SocketOption(option)
|
| 826 |
+
except ValueError:
|
| 827 |
+
# unrecognized option,
|
| 828 |
+
# assume from the future,
|
| 829 |
+
# let EINVAL raise
|
| 830 |
+
opt_type = _OptType.int
|
| 831 |
+
else:
|
| 832 |
+
opt_type = sopt._opt_type
|
| 833 |
+
|
| 834 |
+
if opt_type == _OptType.bytes:
|
| 835 |
+
sz = 255
|
| 836 |
+
_getsockopt(self.handle, option, cast(p_void, identity_str_c), address(sz))
|
| 837 |
+
# strip null-terminated strings *except* identity
|
| 838 |
+
if (
|
| 839 |
+
option != ZMQ_IDENTITY
|
| 840 |
+
and sz > 0
|
| 841 |
+
and (cast(p_char, identity_str_c))[sz - 1] == b'\0'
|
| 842 |
+
):
|
| 843 |
+
sz -= 1
|
| 844 |
+
result = PyBytes_FromStringAndSize(cast(p_char, identity_str_c), sz)
|
| 845 |
+
elif opt_type == _OptType.int64:
|
| 846 |
+
sz = sizeof(int64_t)
|
| 847 |
+
_getsockopt(
|
| 848 |
+
self.handle, option, cast(p_void, address(optval_int64_c)), address(sz)
|
| 849 |
+
)
|
| 850 |
+
result = optval_int64_c
|
| 851 |
+
elif opt_type == _OptType.fd:
|
| 852 |
+
sz = sizeof(fd_t)
|
| 853 |
+
_getsockopt(
|
| 854 |
+
self.handle, option, cast(p_void, address(optval_fd_c)), address(sz)
|
| 855 |
+
)
|
| 856 |
+
result = optval_fd_c
|
| 857 |
+
else:
|
| 858 |
+
# default is to assume int, which is what most new sockopts will be
|
| 859 |
+
# this lets pyzmq work with newer libzmq which may add constants
|
| 860 |
+
# pyzmq has not yet added, rather than artificially raising. Invalid
|
| 861 |
+
# sockopts will still raise just the same, but it will be libzmq doing
|
| 862 |
+
# the raising.
|
| 863 |
+
sz = sizeof(int)
|
| 864 |
+
_getsockopt(
|
| 865 |
+
self.handle, option, cast(p_void, address(optval_int_c)), address(sz)
|
| 866 |
+
)
|
| 867 |
+
result = optval_int_c
|
| 868 |
+
|
| 869 |
+
return result
|
| 870 |
+
|
| 871 |
+
def bind(self, addr):
|
| 872 |
+
"""
|
| 873 |
+
Bind the socket to an address.
|
| 874 |
+
|
| 875 |
+
This causes the socket to listen on a network port. Sockets on the
|
| 876 |
+
other side of this connection will use ``Socket.connect(addr)`` to
|
| 877 |
+
connect to this socket.
|
| 878 |
+
|
| 879 |
+
Parameters
|
| 880 |
+
----------
|
| 881 |
+
addr : str
|
| 882 |
+
The address string. This has the form 'protocol://interface:port',
|
| 883 |
+
for example 'tcp://127.0.0.1:5555'. Protocols supported include
|
| 884 |
+
tcp, udp, pgm, epgm, inproc and ipc. If the address is unicode, it is
|
| 885 |
+
encoded to utf-8 first.
|
| 886 |
+
"""
|
| 887 |
+
rc: C.int
|
| 888 |
+
c_addr: p_char
|
| 889 |
+
|
| 890 |
+
_check_closed(self)
|
| 891 |
+
addr_b = addr
|
| 892 |
+
if isinstance(addr, str):
|
| 893 |
+
addr_b = addr.encode('utf-8')
|
| 894 |
+
elif isinstance(addr_b, bytes):
|
| 895 |
+
addr = addr_b.decode('utf-8')
|
| 896 |
+
|
| 897 |
+
if not isinstance(addr_b, bytes):
|
| 898 |
+
raise TypeError(f'expected str, got: {addr!r}')
|
| 899 |
+
c_addr = addr_b
|
| 900 |
+
rc = zmq_bind(self.handle, c_addr)
|
| 901 |
+
if rc != 0:
|
| 902 |
+
if IPC_PATH_MAX_LEN and zmq_errno() == ENAMETOOLONG:
|
| 903 |
+
path = addr.split('://', 1)[-1]
|
| 904 |
+
msg = (
|
| 905 |
+
f'ipc path "{path}" is longer than {IPC_PATH_MAX_LEN} '
|
| 906 |
+
'characters (sizeof(sockaddr_un.sun_path)). '
|
| 907 |
+
'zmq.IPC_PATH_MAX_LEN constant can be used '
|
| 908 |
+
'to check addr length (if it is defined).'
|
| 909 |
+
)
|
| 910 |
+
raise ZMQError(msg=msg)
|
| 911 |
+
elif zmq_errno() == ENOENT:
|
| 912 |
+
path = addr.split('://', 1)[-1]
|
| 913 |
+
msg = f'No such file or directory for ipc path "{path}".'
|
| 914 |
+
raise ZMQError(msg=msg)
|
| 915 |
+
while True:
|
| 916 |
+
try:
|
| 917 |
+
_check_rc(rc)
|
| 918 |
+
except InterruptedSystemCall:
|
| 919 |
+
rc = zmq_bind(self.handle, c_addr)
|
| 920 |
+
continue
|
| 921 |
+
else:
|
| 922 |
+
break
|
| 923 |
+
|
| 924 |
+
def connect(self, addr):
|
| 925 |
+
"""
|
| 926 |
+
Connect to a remote 0MQ socket.
|
| 927 |
+
|
| 928 |
+
Parameters
|
| 929 |
+
----------
|
| 930 |
+
addr : str
|
| 931 |
+
The address string. This has the form 'protocol://interface:port',
|
| 932 |
+
for example 'tcp://127.0.0.1:5555'. Protocols supported are
|
| 933 |
+
tcp, udp, pgm, inproc and ipc. If the address is unicode, it is
|
| 934 |
+
encoded to utf-8 first.
|
| 935 |
+
"""
|
| 936 |
+
rc: C.int
|
| 937 |
+
c_addr: p_char
|
| 938 |
+
|
| 939 |
+
_check_closed(self)
|
| 940 |
+
if isinstance(addr, str):
|
| 941 |
+
addr = addr.encode('utf-8')
|
| 942 |
+
if not isinstance(addr, bytes):
|
| 943 |
+
raise TypeError(f'expected str, got: {addr!r}')
|
| 944 |
+
c_addr = addr
|
| 945 |
+
|
| 946 |
+
while True:
|
| 947 |
+
try:
|
| 948 |
+
rc = zmq_connect(self.handle, c_addr)
|
| 949 |
+
_check_rc(rc)
|
| 950 |
+
except InterruptedSystemCall:
|
| 951 |
+
# retry syscall
|
| 952 |
+
continue
|
| 953 |
+
else:
|
| 954 |
+
break
|
| 955 |
+
|
| 956 |
+
def unbind(self, addr):
|
| 957 |
+
"""
|
| 958 |
+
Unbind from an address (undoes a call to bind).
|
| 959 |
+
|
| 960 |
+
.. versionadded:: libzmq-3.2
|
| 961 |
+
.. versionadded:: 13.0
|
| 962 |
+
|
| 963 |
+
Parameters
|
| 964 |
+
----------
|
| 965 |
+
addr : str
|
| 966 |
+
The address string. This has the form 'protocol://interface:port',
|
| 967 |
+
for example 'tcp://127.0.0.1:5555'. Protocols supported are
|
| 968 |
+
tcp, udp, pgm, inproc and ipc. If the address is unicode, it is
|
| 969 |
+
encoded to utf-8 first.
|
| 970 |
+
"""
|
| 971 |
+
rc: C.int
|
| 972 |
+
c_addr: p_char
|
| 973 |
+
|
| 974 |
+
_check_version((3, 2), "unbind")
|
| 975 |
+
_check_closed(self)
|
| 976 |
+
if isinstance(addr, str):
|
| 977 |
+
addr = addr.encode('utf-8')
|
| 978 |
+
if not isinstance(addr, bytes):
|
| 979 |
+
raise TypeError(f'expected str, got: {addr!r}')
|
| 980 |
+
c_addr = addr
|
| 981 |
+
|
| 982 |
+
rc = zmq_unbind(self.handle, c_addr)
|
| 983 |
+
if rc != 0:
|
| 984 |
+
raise ZMQError()
|
| 985 |
+
|
| 986 |
+
def disconnect(self, addr):
|
| 987 |
+
"""
|
| 988 |
+
Disconnect from a remote 0MQ socket (undoes a call to connect).
|
| 989 |
+
|
| 990 |
+
.. versionadded:: libzmq-3.2
|
| 991 |
+
.. versionadded:: 13.0
|
| 992 |
+
|
| 993 |
+
Parameters
|
| 994 |
+
----------
|
| 995 |
+
addr : str
|
| 996 |
+
The address string. This has the form 'protocol://interface:port',
|
| 997 |
+
for example 'tcp://127.0.0.1:5555'. Protocols supported are
|
| 998 |
+
tcp, udp, pgm, inproc and ipc. If the address is unicode, it is
|
| 999 |
+
encoded to utf-8 first.
|
| 1000 |
+
"""
|
| 1001 |
+
rc: C.int
|
| 1002 |
+
c_addr: p_char
|
| 1003 |
+
|
| 1004 |
+
_check_version((3, 2), "disconnect")
|
| 1005 |
+
_check_closed(self)
|
| 1006 |
+
if isinstance(addr, str):
|
| 1007 |
+
addr = addr.encode('utf-8')
|
| 1008 |
+
if not isinstance(addr, bytes):
|
| 1009 |
+
raise TypeError(f'expected str, got: {addr!r}')
|
| 1010 |
+
c_addr = addr
|
| 1011 |
+
|
| 1012 |
+
rc = zmq_disconnect(self.handle, c_addr)
|
| 1013 |
+
if rc != 0:
|
| 1014 |
+
raise ZMQError()
|
| 1015 |
+
|
| 1016 |
+
def monitor(self, addr, events: C.int = ZMQ_EVENT_ALL):
|
| 1017 |
+
"""
|
| 1018 |
+
Start publishing socket events on inproc.
|
| 1019 |
+
See libzmq docs for zmq_monitor for details.
|
| 1020 |
+
|
| 1021 |
+
While this function is available from libzmq 3.2,
|
| 1022 |
+
pyzmq cannot parse monitor messages from libzmq prior to 4.0.
|
| 1023 |
+
|
| 1024 |
+
.. versionadded: libzmq-3.2
|
| 1025 |
+
.. versionadded: 14.0
|
| 1026 |
+
|
| 1027 |
+
Parameters
|
| 1028 |
+
----------
|
| 1029 |
+
addr : str
|
| 1030 |
+
The inproc url used for monitoring. Passing None as
|
| 1031 |
+
the addr will cause an existing socket monitor to be
|
| 1032 |
+
deregistered.
|
| 1033 |
+
events : int
|
| 1034 |
+
default: zmq.EVENT_ALL
|
| 1035 |
+
The zmq event bitmask for which events will be sent to the monitor.
|
| 1036 |
+
"""
|
| 1037 |
+
_check_version((3, 2), "monitor")
|
| 1038 |
+
|
| 1039 |
+
if isinstance(addr, str):
|
| 1040 |
+
# cast str to utf8 bytes
|
| 1041 |
+
addr = addr.encode("utf-8")
|
| 1042 |
+
|
| 1043 |
+
# cast bytes to char*
|
| 1044 |
+
c_addr: p_char
|
| 1045 |
+
|
| 1046 |
+
if addr is None:
|
| 1047 |
+
c_addr = NULL
|
| 1048 |
+
else:
|
| 1049 |
+
# let Cython do the casting,
|
| 1050 |
+
# but return a nicer error message if it fails
|
| 1051 |
+
try:
|
| 1052 |
+
c_addr = addr
|
| 1053 |
+
except TypeError:
|
| 1054 |
+
raise TypeError(f"Monitor addr must be str, got {addr!r}") from None
|
| 1055 |
+
|
| 1056 |
+
_check_rc(zmq_socket_monitor(self.handle, c_addr, events))
|
| 1057 |
+
|
| 1058 |
+
def join(self, group):
|
| 1059 |
+
"""
|
| 1060 |
+
Join a RADIO-DISH group
|
| 1061 |
+
|
| 1062 |
+
Only for DISH sockets.
|
| 1063 |
+
|
| 1064 |
+
libzmq and pyzmq must have been built with ZMQ_BUILD_DRAFT_API
|
| 1065 |
+
|
| 1066 |
+
.. versionadded:: 17
|
| 1067 |
+
"""
|
| 1068 |
+
_check_version((4, 2), "RADIO-DISH")
|
| 1069 |
+
if not zmq.has('draft'):
|
| 1070 |
+
raise RuntimeError("libzmq must be built with draft support")
|
| 1071 |
+
if isinstance(group, str):
|
| 1072 |
+
group = group.encode('utf8')
|
| 1073 |
+
rc: C.int = zmq_join(self.handle, group)
|
| 1074 |
+
_check_rc(rc)
|
| 1075 |
+
|
| 1076 |
+
def leave(self, group):
|
| 1077 |
+
"""
|
| 1078 |
+
Leave a RADIO-DISH group
|
| 1079 |
+
|
| 1080 |
+
Only for DISH sockets.
|
| 1081 |
+
|
| 1082 |
+
libzmq and pyzmq must have been built with ZMQ_BUILD_DRAFT_API
|
| 1083 |
+
|
| 1084 |
+
.. versionadded:: 17
|
| 1085 |
+
"""
|
| 1086 |
+
_check_version((4, 2), "RADIO-DISH")
|
| 1087 |
+
if not zmq.has('draft'):
|
| 1088 |
+
raise RuntimeError("libzmq must be built with draft support")
|
| 1089 |
+
rc: C.int = zmq_leave(self.handle, group)
|
| 1090 |
+
_check_rc(rc)
|
| 1091 |
+
|
| 1092 |
+
def send(self, data, flags=0, copy: bint = True, track: bint = False):
|
| 1093 |
+
"""
|
| 1094 |
+
Send a single zmq message frame on this socket.
|
| 1095 |
+
|
| 1096 |
+
This queues the message to be sent by the IO thread at a later time.
|
| 1097 |
+
|
| 1098 |
+
With flags=NOBLOCK, this raises :class:`ZMQError` if the queue is full;
|
| 1099 |
+
otherwise, this waits until space is available.
|
| 1100 |
+
See :class:`Poller` for more general non-blocking I/O.
|
| 1101 |
+
|
| 1102 |
+
Parameters
|
| 1103 |
+
----------
|
| 1104 |
+
data : bytes, Frame, memoryview
|
| 1105 |
+
The content of the message. This can be any object that provides
|
| 1106 |
+
the Python buffer API (`memoryview(data)` can be called).
|
| 1107 |
+
flags : int
|
| 1108 |
+
0, NOBLOCK, SNDMORE, or NOBLOCK|SNDMORE.
|
| 1109 |
+
copy : bool
|
| 1110 |
+
Should the message be sent in a copying or non-copying manner.
|
| 1111 |
+
track : bool
|
| 1112 |
+
Should the message be tracked for notification that ZMQ has
|
| 1113 |
+
finished with it? (ignored if copy=True)
|
| 1114 |
+
|
| 1115 |
+
Returns
|
| 1116 |
+
-------
|
| 1117 |
+
None : if `copy` or not track
|
| 1118 |
+
None if message was sent, raises an exception otherwise.
|
| 1119 |
+
MessageTracker : if track and not copy
|
| 1120 |
+
a MessageTracker object, whose `done` property will
|
| 1121 |
+
be False until the send is completed.
|
| 1122 |
+
|
| 1123 |
+
Raises
|
| 1124 |
+
------
|
| 1125 |
+
TypeError
|
| 1126 |
+
If a unicode object is passed
|
| 1127 |
+
ValueError
|
| 1128 |
+
If `track=True`, but an untracked Frame is passed.
|
| 1129 |
+
ZMQError
|
| 1130 |
+
for any of the reasons zmq_msg_send might fail (including
|
| 1131 |
+
if NOBLOCK is set and the outgoing queue is full).
|
| 1132 |
+
|
| 1133 |
+
"""
|
| 1134 |
+
_check_closed(self)
|
| 1135 |
+
|
| 1136 |
+
if isinstance(data, str):
|
| 1137 |
+
raise TypeError("unicode not allowed, use send_string")
|
| 1138 |
+
|
| 1139 |
+
if copy and not isinstance(data, Frame):
|
| 1140 |
+
return _send_copy(self.handle, data, flags)
|
| 1141 |
+
else:
|
| 1142 |
+
if isinstance(data, Frame):
|
| 1143 |
+
if track and not data.tracker:
|
| 1144 |
+
raise ValueError('Not a tracked message')
|
| 1145 |
+
msg = data
|
| 1146 |
+
else:
|
| 1147 |
+
if self.copy_threshold:
|
| 1148 |
+
buf = memoryview(data)
|
| 1149 |
+
# always copy messages smaller than copy_threshold
|
| 1150 |
+
if buf.nbytes < self.copy_threshold:
|
| 1151 |
+
_send_copy(self.handle, buf, flags)
|
| 1152 |
+
return zmq._FINISHED_TRACKER
|
| 1153 |
+
msg = Frame(data, track=track, copy_threshold=self.copy_threshold)
|
| 1154 |
+
return _send_frame(self.handle, msg, flags)
|
| 1155 |
+
|
| 1156 |
+
def recv(self, flags=0, copy: bint = True, track: bint = False):
|
| 1157 |
+
"""
|
| 1158 |
+
Receive a message.
|
| 1159 |
+
|
| 1160 |
+
With flags=NOBLOCK, this raises :class:`ZMQError` if no messages have
|
| 1161 |
+
arrived; otherwise, this waits until a message arrives.
|
| 1162 |
+
See :class:`Poller` for more general non-blocking I/O.
|
| 1163 |
+
|
| 1164 |
+
Parameters
|
| 1165 |
+
----------
|
| 1166 |
+
flags : int
|
| 1167 |
+
0 or NOBLOCK.
|
| 1168 |
+
copy : bool
|
| 1169 |
+
Should the message be received in a copying or non-copying manner?
|
| 1170 |
+
If False a Frame object is returned, if True a string copy of
|
| 1171 |
+
message is returned.
|
| 1172 |
+
track : bool
|
| 1173 |
+
Should the message be tracked for notification that ZMQ has
|
| 1174 |
+
finished with it? (ignored if copy=True)
|
| 1175 |
+
|
| 1176 |
+
Returns
|
| 1177 |
+
-------
|
| 1178 |
+
msg : bytes or Frame
|
| 1179 |
+
The received message frame. If `copy` is False, then it will be a Frame,
|
| 1180 |
+
otherwise it will be bytes.
|
| 1181 |
+
|
| 1182 |
+
Raises
|
| 1183 |
+
------
|
| 1184 |
+
ZMQError
|
| 1185 |
+
for any of the reasons zmq_msg_recv might fail (including if
|
| 1186 |
+
NOBLOCK is set and no new messages have arrived).
|
| 1187 |
+
"""
|
| 1188 |
+
_check_closed(self)
|
| 1189 |
+
|
| 1190 |
+
if copy:
|
| 1191 |
+
return _recv_copy(self.handle, flags)
|
| 1192 |
+
else:
|
| 1193 |
+
frame = _recv_frame(self.handle, flags, track)
|
| 1194 |
+
frame.more = self.get(zmq.RCVMORE)
|
| 1195 |
+
return frame
|
| 1196 |
+
|
| 1197 |
+
|
| 1198 |
+
# inline socket methods
|
| 1199 |
+
|
| 1200 |
+
|
| 1201 |
+
@inline
|
| 1202 |
+
@cfunc
|
| 1203 |
+
def _check_closed(s: Socket):
|
| 1204 |
+
"""raise ENOTSUP if socket is closed
|
| 1205 |
+
|
| 1206 |
+
Does not do a deep check
|
| 1207 |
+
"""
|
| 1208 |
+
if s._closed:
|
| 1209 |
+
raise ZMQError(ENOTSOCK)
|
| 1210 |
+
|
| 1211 |
+
|
| 1212 |
+
@inline
|
| 1213 |
+
@cfunc
|
| 1214 |
+
def _check_closed_deep(s: Socket) -> bint:
|
| 1215 |
+
"""thorough check of whether the socket has been closed,
|
| 1216 |
+
even if by another entity (e.g. ctx.destroy).
|
| 1217 |
+
|
| 1218 |
+
Only used by the `closed` property.
|
| 1219 |
+
|
| 1220 |
+
returns True if closed, False otherwise
|
| 1221 |
+
"""
|
| 1222 |
+
rc: C.int
|
| 1223 |
+
errno: C.int
|
| 1224 |
+
stype = declare(C.int)
|
| 1225 |
+
sz: size_t = sizeof(int)
|
| 1226 |
+
|
| 1227 |
+
if s._closed:
|
| 1228 |
+
return True
|
| 1229 |
+
else:
|
| 1230 |
+
rc = zmq_getsockopt(
|
| 1231 |
+
s.handle, ZMQ_TYPE, cast(p_void, address(stype)), address(sz)
|
| 1232 |
+
)
|
| 1233 |
+
if rc < 0:
|
| 1234 |
+
errno = zmq_errno()
|
| 1235 |
+
if errno == ENOTSOCK:
|
| 1236 |
+
s._closed = True
|
| 1237 |
+
return True
|
| 1238 |
+
elif errno == ZMQ_ETERM:
|
| 1239 |
+
# don't raise ETERM when checking if we're closed
|
| 1240 |
+
return False
|
| 1241 |
+
else:
|
| 1242 |
+
_check_rc(rc)
|
| 1243 |
+
return False
|
| 1244 |
+
|
| 1245 |
+
|
| 1246 |
+
@cfunc
|
| 1247 |
+
@inline
|
| 1248 |
+
def _recv_frame(handle: p_void, flags: C.int = 0, track: bint = False) -> Frame:
|
| 1249 |
+
"""Receive a message in a non-copying manner and return a Frame."""
|
| 1250 |
+
rc: C.int
|
| 1251 |
+
msg = zmq.Frame(track=track)
|
| 1252 |
+
cmsg: Frame = msg
|
| 1253 |
+
|
| 1254 |
+
while True:
|
| 1255 |
+
with nogil:
|
| 1256 |
+
rc = zmq_msg_recv(address(cmsg.zmq_msg), handle, flags)
|
| 1257 |
+
try:
|
| 1258 |
+
_check_rc(rc)
|
| 1259 |
+
except InterruptedSystemCall:
|
| 1260 |
+
continue
|
| 1261 |
+
else:
|
| 1262 |
+
break
|
| 1263 |
+
return msg
|
| 1264 |
+
|
| 1265 |
+
|
| 1266 |
+
@cfunc
|
| 1267 |
+
@inline
|
| 1268 |
+
def _recv_copy(handle: p_void, flags: C.int = 0):
|
| 1269 |
+
"""Receive a message and return a copy"""
|
| 1270 |
+
zmq_msg = declare(zmq_msg_t)
|
| 1271 |
+
zmq_msg_p: pointer(zmq_msg_t) = address(zmq_msg)
|
| 1272 |
+
rc: C.int = zmq_msg_init(zmq_msg_p)
|
| 1273 |
+
_check_rc(rc)
|
| 1274 |
+
while True:
|
| 1275 |
+
with nogil:
|
| 1276 |
+
rc = zmq_msg_recv(zmq_msg_p, handle, flags)
|
| 1277 |
+
try:
|
| 1278 |
+
_check_rc(rc)
|
| 1279 |
+
except InterruptedSystemCall:
|
| 1280 |
+
continue
|
| 1281 |
+
except Exception:
|
| 1282 |
+
zmq_msg_close(zmq_msg_p) # ensure msg is closed on failure
|
| 1283 |
+
raise
|
| 1284 |
+
else:
|
| 1285 |
+
break
|
| 1286 |
+
|
| 1287 |
+
msg_bytes = _copy_zmq_msg_bytes(zmq_msg_p)
|
| 1288 |
+
zmq_msg_close(zmq_msg_p)
|
| 1289 |
+
return msg_bytes
|
| 1290 |
+
|
| 1291 |
+
|
| 1292 |
+
@cfunc
|
| 1293 |
+
@inline
|
| 1294 |
+
def _send_frame(handle: p_void, msg: Frame, flags: C.int = 0):
|
| 1295 |
+
"""Send a Frame on this socket in a non-copy manner."""
|
| 1296 |
+
rc: C.int
|
| 1297 |
+
msg_copy: Frame
|
| 1298 |
+
|
| 1299 |
+
# Always copy so the original message isn't garbage collected.
|
| 1300 |
+
# This doesn't do a real copy, just a reference.
|
| 1301 |
+
msg_copy = msg.fast_copy()
|
| 1302 |
+
|
| 1303 |
+
while True:
|
| 1304 |
+
with nogil:
|
| 1305 |
+
rc = zmq_msg_send(address(msg_copy.zmq_msg), handle, flags)
|
| 1306 |
+
try:
|
| 1307 |
+
_check_rc(rc)
|
| 1308 |
+
except InterruptedSystemCall:
|
| 1309 |
+
continue
|
| 1310 |
+
else:
|
| 1311 |
+
break
|
| 1312 |
+
|
| 1313 |
+
return msg.tracker
|
| 1314 |
+
|
| 1315 |
+
|
| 1316 |
+
@cfunc
|
| 1317 |
+
@inline
|
| 1318 |
+
def _send_copy(handle: p_void, buf, flags: C.int = 0):
|
| 1319 |
+
"""Send a message on this socket by copying its content."""
|
| 1320 |
+
rc: C.int
|
| 1321 |
+
msg = declare(zmq_msg_t)
|
| 1322 |
+
c_bytes = declare(p_char)
|
| 1323 |
+
c_bytes_len: Py_ssize_t = 0
|
| 1324 |
+
|
| 1325 |
+
# copy to c array:
|
| 1326 |
+
asbuffer_r(buf, cast(pointer(p_void), address(c_bytes)), address(c_bytes_len))
|
| 1327 |
+
|
| 1328 |
+
# Copy the msg before sending. This avoids any complications with
|
| 1329 |
+
# the GIL, etc.
|
| 1330 |
+
# If zmq_msg_init_* fails we must not call zmq_msg_close (Bus Error)
|
| 1331 |
+
rc = zmq_msg_init_size(address(msg), c_bytes_len)
|
| 1332 |
+
_check_rc(rc)
|
| 1333 |
+
|
| 1334 |
+
while True:
|
| 1335 |
+
with nogil:
|
| 1336 |
+
memcpy(zmq_msg_data(address(msg)), c_bytes, zmq_msg_size(address(msg)))
|
| 1337 |
+
rc = zmq_msg_send(address(msg), handle, flags)
|
| 1338 |
+
try:
|
| 1339 |
+
_check_rc(rc)
|
| 1340 |
+
except InterruptedSystemCall:
|
| 1341 |
+
continue
|
| 1342 |
+
except Exception:
|
| 1343 |
+
zmq_msg_close(address(msg)) # close the unused msg
|
| 1344 |
+
raise # raise original exception
|
| 1345 |
+
else:
|
| 1346 |
+
rc = zmq_msg_close(address(msg))
|
| 1347 |
+
_check_rc(rc)
|
| 1348 |
+
break
|
| 1349 |
+
|
| 1350 |
+
|
| 1351 |
+
@cfunc
|
| 1352 |
+
@inline
|
| 1353 |
+
def _getsockopt(handle: p_void, option: C.int, optval: p_void, sz: pointer(size_t)):
|
| 1354 |
+
"""getsockopt, retrying interrupted calls
|
| 1355 |
+
|
| 1356 |
+
checks rc, raising ZMQError on failure.
|
| 1357 |
+
"""
|
| 1358 |
+
rc: C.int = 0
|
| 1359 |
+
while True:
|
| 1360 |
+
rc = zmq_getsockopt(handle, option, optval, sz)
|
| 1361 |
+
try:
|
| 1362 |
+
_check_rc(rc)
|
| 1363 |
+
except InterruptedSystemCall:
|
| 1364 |
+
continue
|
| 1365 |
+
else:
|
| 1366 |
+
break
|
| 1367 |
+
|
| 1368 |
+
|
| 1369 |
+
@cfunc
|
| 1370 |
+
@inline
|
| 1371 |
+
def _setsockopt(handle: p_void, option: C.int, optval: p_void, sz: size_t):
|
| 1372 |
+
"""setsockopt, retrying interrupted calls
|
| 1373 |
+
|
| 1374 |
+
checks rc, raising ZMQError on failure.
|
| 1375 |
+
"""
|
| 1376 |
+
rc: C.int = 0
|
| 1377 |
+
while True:
|
| 1378 |
+
rc = zmq_setsockopt(handle, option, optval, sz)
|
| 1379 |
+
try:
|
| 1380 |
+
_check_rc(rc)
|
| 1381 |
+
except InterruptedSystemCall:
|
| 1382 |
+
continue
|
| 1383 |
+
else:
|
| 1384 |
+
break
|
| 1385 |
+
|
| 1386 |
+
|
| 1387 |
+
# General utility functions
|
| 1388 |
+
|
| 1389 |
+
|
| 1390 |
+
def zmq_errno():
|
| 1391 |
+
"""Return the integer errno of the most recent zmq error."""
|
| 1392 |
+
return _zmq_errno()
|
| 1393 |
+
|
| 1394 |
+
|
| 1395 |
+
def strerror(errno: C.int) -> str:
|
| 1396 |
+
"""
|
| 1397 |
+
Return the error string given the error number.
|
| 1398 |
+
"""
|
| 1399 |
+
str_e: bytes = zmq_strerror(errno)
|
| 1400 |
+
return str_e.decode("utf8", "replace")
|
| 1401 |
+
|
| 1402 |
+
|
| 1403 |
+
def zmq_version_info() -> tuple[int, int, int]:
|
| 1404 |
+
"""Return the version of ZeroMQ itself as a 3-tuple of ints."""
|
| 1405 |
+
major: C.int = 0
|
| 1406 |
+
minor: C.int = 0
|
| 1407 |
+
patch: C.int = 0
|
| 1408 |
+
_zmq_version(address(major), address(minor), address(patch))
|
| 1409 |
+
return (major, minor, patch)
|
| 1410 |
+
|
| 1411 |
+
|
| 1412 |
+
def has(capability) -> bool:
|
| 1413 |
+
"""Check for zmq capability by name (e.g. 'ipc', 'curve')
|
| 1414 |
+
|
| 1415 |
+
.. versionadded:: libzmq-4.1
|
| 1416 |
+
.. versionadded:: 14.1
|
| 1417 |
+
"""
|
| 1418 |
+
_check_version((4, 1), 'zmq.has')
|
| 1419 |
+
ccap: bytes
|
| 1420 |
+
if isinstance(capability, str):
|
| 1421 |
+
capability = capability.encode('utf8')
|
| 1422 |
+
ccap = capability
|
| 1423 |
+
return bool(zmq_has(ccap))
|
| 1424 |
+
|
| 1425 |
+
|
| 1426 |
+
def curve_keypair() -> tuple[bytes, bytes]:
|
| 1427 |
+
"""generate a Z85 key pair for use with zmq.CURVE security
|
| 1428 |
+
|
| 1429 |
+
Requires libzmq (≥ 4.0) to have been built with CURVE support.
|
| 1430 |
+
|
| 1431 |
+
.. versionadded:: libzmq-4.0
|
| 1432 |
+
.. versionadded:: 14.0
|
| 1433 |
+
|
| 1434 |
+
Returns
|
| 1435 |
+
-------
|
| 1436 |
+
public: bytes
|
| 1437 |
+
The public key as 40 byte z85-encoded bytestring.
|
| 1438 |
+
private: bytes
|
| 1439 |
+
The private key as 40 byte z85-encoded bytestring.
|
| 1440 |
+
"""
|
| 1441 |
+
rc: C.int
|
| 1442 |
+
public_key = declare(char[64])
|
| 1443 |
+
secret_key = declare(char[64])
|
| 1444 |
+
_check_version((4, 0), "curve_keypair")
|
| 1445 |
+
# see huge comment in libzmq/src/random.cpp
|
| 1446 |
+
# about threadsafety of random initialization
|
| 1447 |
+
rc = zmq_curve_keypair(public_key, secret_key)
|
| 1448 |
+
_check_rc(rc)
|
| 1449 |
+
return public_key, secret_key
|
| 1450 |
+
|
| 1451 |
+
|
| 1452 |
+
def curve_public(secret_key) -> bytes:
|
| 1453 |
+
"""Compute the public key corresponding to a secret key for use
|
| 1454 |
+
with zmq.CURVE security
|
| 1455 |
+
|
| 1456 |
+
Requires libzmq (≥ 4.2) to have been built with CURVE support.
|
| 1457 |
+
|
| 1458 |
+
Parameters
|
| 1459 |
+
----------
|
| 1460 |
+
private
|
| 1461 |
+
The private key as a 40 byte z85-encoded bytestring
|
| 1462 |
+
|
| 1463 |
+
Returns
|
| 1464 |
+
-------
|
| 1465 |
+
bytes
|
| 1466 |
+
The public key as a 40 byte z85-encoded bytestring
|
| 1467 |
+
"""
|
| 1468 |
+
if isinstance(secret_key, str):
|
| 1469 |
+
secret_key = secret_key.encode('utf8')
|
| 1470 |
+
if not len(secret_key) == 40:
|
| 1471 |
+
raise ValueError('secret key must be a 40 byte z85 encoded string')
|
| 1472 |
+
|
| 1473 |
+
rc: C.int
|
| 1474 |
+
public_key = declare(char[64])
|
| 1475 |
+
c_secret_key: pointer(char) = secret_key
|
| 1476 |
+
_check_version((4, 2), "curve_public")
|
| 1477 |
+
# see huge comment in libzmq/src/random.cpp
|
| 1478 |
+
# about threadsafety of random initialization
|
| 1479 |
+
rc = zmq_curve_public(public_key, c_secret_key)
|
| 1480 |
+
_check_rc(rc)
|
| 1481 |
+
return public_key[:40]
|
| 1482 |
+
|
| 1483 |
+
|
| 1484 |
+
# polling
|
| 1485 |
+
def zmq_poll(sockets, timeout: C.int = -1):
|
| 1486 |
+
"""zmq_poll(sockets, timeout=-1)
|
| 1487 |
+
|
| 1488 |
+
Poll a set of 0MQ sockets, native file descs. or sockets.
|
| 1489 |
+
|
| 1490 |
+
Parameters
|
| 1491 |
+
----------
|
| 1492 |
+
sockets : list of tuples of (socket, flags)
|
| 1493 |
+
Each element of this list is a two-tuple containing a socket
|
| 1494 |
+
and a flags. The socket may be a 0MQ socket or any object with
|
| 1495 |
+
a ``fileno()`` method. The flags can be zmq.POLLIN (for detecting
|
| 1496 |
+
for incoming messages), zmq.POLLOUT (for detecting that send is OK)
|
| 1497 |
+
or zmq.POLLIN|zmq.POLLOUT for detecting both.
|
| 1498 |
+
timeout : int
|
| 1499 |
+
The number of milliseconds to poll for. Negative means no timeout.
|
| 1500 |
+
"""
|
| 1501 |
+
rc: C.int
|
| 1502 |
+
i: C.int
|
| 1503 |
+
pollitems: pointer(zmq_pollitem_t) = NULL
|
| 1504 |
+
nsockets: C.int = len(sockets)
|
| 1505 |
+
|
| 1506 |
+
if nsockets == 0:
|
| 1507 |
+
return []
|
| 1508 |
+
|
| 1509 |
+
pollitems = cast(pointer(zmq_pollitem_t), malloc(nsockets * sizeof(zmq_pollitem_t)))
|
| 1510 |
+
if pollitems == NULL:
|
| 1511 |
+
raise MemoryError("Could not allocate poll items")
|
| 1512 |
+
|
| 1513 |
+
if ZMQ_VERSION_MAJOR < 3:
|
| 1514 |
+
# timeout is us in 2.x, ms in 3.x
|
| 1515 |
+
# expected input is ms (matches 3.x)
|
| 1516 |
+
timeout = 1000 * timeout
|
| 1517 |
+
|
| 1518 |
+
for i in range(nsockets):
|
| 1519 |
+
s, events = sockets[i]
|
| 1520 |
+
if isinstance(s, Socket):
|
| 1521 |
+
pollitems[i].socket = cast(Socket, s).handle
|
| 1522 |
+
pollitems[i].fd = 0
|
| 1523 |
+
pollitems[i].events = events
|
| 1524 |
+
pollitems[i].revents = 0
|
| 1525 |
+
elif isinstance(s, int):
|
| 1526 |
+
pollitems[i].socket = NULL
|
| 1527 |
+
pollitems[i].fd = s
|
| 1528 |
+
pollitems[i].events = events
|
| 1529 |
+
pollitems[i].revents = 0
|
| 1530 |
+
elif hasattr(s, 'fileno'):
|
| 1531 |
+
try:
|
| 1532 |
+
fileno = int(s.fileno())
|
| 1533 |
+
except Exception:
|
| 1534 |
+
free(pollitems)
|
| 1535 |
+
raise ValueError('fileno() must return a valid integer fd')
|
| 1536 |
+
else:
|
| 1537 |
+
pollitems[i].socket = NULL
|
| 1538 |
+
pollitems[i].fd = fileno
|
| 1539 |
+
pollitems[i].events = events
|
| 1540 |
+
pollitems[i].revents = 0
|
| 1541 |
+
else:
|
| 1542 |
+
free(pollitems)
|
| 1543 |
+
raise TypeError(
|
| 1544 |
+
"Socket must be a 0MQ socket, an integer fd or have "
|
| 1545 |
+
f"a fileno() method: {s!r}"
|
| 1546 |
+
)
|
| 1547 |
+
|
| 1548 |
+
ms_passed: int = 0
|
| 1549 |
+
try:
|
| 1550 |
+
while True:
|
| 1551 |
+
start = time.monotonic()
|
| 1552 |
+
with nogil:
|
| 1553 |
+
rc = zmq_poll_c(pollitems, nsockets, timeout)
|
| 1554 |
+
try:
|
| 1555 |
+
_check_rc(rc)
|
| 1556 |
+
except InterruptedSystemCall:
|
| 1557 |
+
if timeout > 0:
|
| 1558 |
+
ms_passed = int(1000 * (time.monotonic() - start))
|
| 1559 |
+
if ms_passed < 0:
|
| 1560 |
+
# don't allow negative ms_passed,
|
| 1561 |
+
# which can happen on old Python versions without time.monotonic.
|
| 1562 |
+
warnings.warn(
|
| 1563 |
+
f"Negative elapsed time for interrupted poll: {ms_passed}."
|
| 1564 |
+
" Did the clock change?",
|
| 1565 |
+
RuntimeWarning,
|
| 1566 |
+
)
|
| 1567 |
+
# treat this case the same as no time passing,
|
| 1568 |
+
# since it should be rare and not happen twice in a row.
|
| 1569 |
+
ms_passed = 0
|
| 1570 |
+
timeout = max(0, timeout - ms_passed)
|
| 1571 |
+
continue
|
| 1572 |
+
else:
|
| 1573 |
+
break
|
| 1574 |
+
except Exception:
|
| 1575 |
+
free(pollitems)
|
| 1576 |
+
raise
|
| 1577 |
+
|
| 1578 |
+
results = []
|
| 1579 |
+
for i in range(nsockets):
|
| 1580 |
+
revents = pollitems[i].revents
|
| 1581 |
+
# for compatibility with select.poll:
|
| 1582 |
+
# - only return sockets with non-zero status
|
| 1583 |
+
# - return the fd for plain sockets
|
| 1584 |
+
if revents > 0:
|
| 1585 |
+
if pollitems[i].socket != NULL:
|
| 1586 |
+
s = sockets[i][0]
|
| 1587 |
+
else:
|
| 1588 |
+
s = pollitems[i].fd
|
| 1589 |
+
results.append((s, revents))
|
| 1590 |
+
|
| 1591 |
+
free(pollitems)
|
| 1592 |
+
return results
|
| 1593 |
+
|
| 1594 |
+
|
| 1595 |
+
# device functions
|
| 1596 |
+
|
| 1597 |
+
|
| 1598 |
+
def device(device_type: C.int, frontend: Socket, backend: Socket = None):
|
| 1599 |
+
"""
|
| 1600 |
+
Start a zeromq device.
|
| 1601 |
+
|
| 1602 |
+
.. deprecated:: libzmq-3.2
|
| 1603 |
+
Use zmq.proxy
|
| 1604 |
+
|
| 1605 |
+
Parameters
|
| 1606 |
+
----------
|
| 1607 |
+
device_type : int
|
| 1608 |
+
one of: QUEUE, FORWARDER, STREAMER
|
| 1609 |
+
The type of device to start.
|
| 1610 |
+
frontend : Socket
|
| 1611 |
+
The Socket instance for the incoming traffic.
|
| 1612 |
+
backend : Socket
|
| 1613 |
+
The Socket instance for the outbound traffic.
|
| 1614 |
+
"""
|
| 1615 |
+
if ZMQ_VERSION_MAJOR >= 3:
|
| 1616 |
+
return proxy(frontend, backend)
|
| 1617 |
+
|
| 1618 |
+
rc: C.int = 0
|
| 1619 |
+
while True:
|
| 1620 |
+
with nogil:
|
| 1621 |
+
rc = zmq_device(device_type, frontend.handle, backend.handle)
|
| 1622 |
+
try:
|
| 1623 |
+
_check_rc(rc)
|
| 1624 |
+
except InterruptedSystemCall:
|
| 1625 |
+
continue
|
| 1626 |
+
else:
|
| 1627 |
+
break
|
| 1628 |
+
return rc
|
| 1629 |
+
|
| 1630 |
+
|
| 1631 |
+
def proxy(frontend: Socket, backend: Socket, capture: Socket = None):
|
| 1632 |
+
"""
|
| 1633 |
+
Start a zeromq proxy (replacement for device).
|
| 1634 |
+
|
| 1635 |
+
.. versionadded:: libzmq-3.2
|
| 1636 |
+
.. versionadded:: 13.0
|
| 1637 |
+
|
| 1638 |
+
Parameters
|
| 1639 |
+
----------
|
| 1640 |
+
frontend : Socket
|
| 1641 |
+
The Socket instance for the incoming traffic.
|
| 1642 |
+
backend : Socket
|
| 1643 |
+
The Socket instance for the outbound traffic.
|
| 1644 |
+
capture : Socket (optional)
|
| 1645 |
+
The Socket instance for capturing traffic.
|
| 1646 |
+
"""
|
| 1647 |
+
rc: C.int = 0
|
| 1648 |
+
capture_handle: p_void
|
| 1649 |
+
if isinstance(capture, Socket):
|
| 1650 |
+
capture_handle = capture.handle
|
| 1651 |
+
else:
|
| 1652 |
+
capture_handle = NULL
|
| 1653 |
+
while True:
|
| 1654 |
+
with nogil:
|
| 1655 |
+
rc = zmq_proxy(frontend.handle, backend.handle, capture_handle)
|
| 1656 |
+
try:
|
| 1657 |
+
_check_rc(rc)
|
| 1658 |
+
except InterruptedSystemCall:
|
| 1659 |
+
continue
|
| 1660 |
+
else:
|
| 1661 |
+
break
|
| 1662 |
+
return rc
|
| 1663 |
+
|
| 1664 |
+
|
| 1665 |
+
def proxy_steerable(
|
| 1666 |
+
frontend: Socket,
|
| 1667 |
+
backend: Socket,
|
| 1668 |
+
capture: Socket = None,
|
| 1669 |
+
control: Socket = None,
|
| 1670 |
+
):
|
| 1671 |
+
"""
|
| 1672 |
+
Start a zeromq proxy with control flow.
|
| 1673 |
+
|
| 1674 |
+
.. versionadded:: libzmq-4.1
|
| 1675 |
+
.. versionadded:: 18.0
|
| 1676 |
+
|
| 1677 |
+
Parameters
|
| 1678 |
+
----------
|
| 1679 |
+
frontend : Socket
|
| 1680 |
+
The Socket instance for the incoming traffic.
|
| 1681 |
+
backend : Socket
|
| 1682 |
+
The Socket instance for the outbound traffic.
|
| 1683 |
+
capture : Socket (optional)
|
| 1684 |
+
The Socket instance for capturing traffic.
|
| 1685 |
+
control : Socket (optional)
|
| 1686 |
+
The Socket instance for control flow.
|
| 1687 |
+
"""
|
| 1688 |
+
rc: C.int = 0
|
| 1689 |
+
capture_handle: p_void
|
| 1690 |
+
if isinstance(capture, Socket):
|
| 1691 |
+
capture_handle = capture.handle
|
| 1692 |
+
else:
|
| 1693 |
+
capture_handle = NULL
|
| 1694 |
+
if isinstance(control, Socket):
|
| 1695 |
+
control_handle = control.handle
|
| 1696 |
+
else:
|
| 1697 |
+
control_handle = NULL
|
| 1698 |
+
while True:
|
| 1699 |
+
with nogil:
|
| 1700 |
+
rc = zmq_proxy_steerable(
|
| 1701 |
+
frontend.handle, backend.handle, capture_handle, control_handle
|
| 1702 |
+
)
|
| 1703 |
+
try:
|
| 1704 |
+
_check_rc(rc)
|
| 1705 |
+
except InterruptedSystemCall:
|
| 1706 |
+
continue
|
| 1707 |
+
else:
|
| 1708 |
+
break
|
| 1709 |
+
return rc
|
| 1710 |
+
|
| 1711 |
+
|
| 1712 |
+
# monitored queue - like proxy (predates libzmq proxy)
|
| 1713 |
+
# but supports ROUTER-ROUTER devices
|
| 1714 |
+
@cfunc
|
| 1715 |
+
@inline
|
| 1716 |
+
@nogil
|
| 1717 |
+
def _mq_relay(
|
| 1718 |
+
in_socket: p_void,
|
| 1719 |
+
out_socket: p_void,
|
| 1720 |
+
side_socket: p_void,
|
| 1721 |
+
msg: zmq_msg_t,
|
| 1722 |
+
side_msg: zmq_msg_t,
|
| 1723 |
+
id_msg: zmq_msg_t,
|
| 1724 |
+
swap_ids: bint,
|
| 1725 |
+
) -> C.int:
|
| 1726 |
+
rc: C.int
|
| 1727 |
+
flags: C.int
|
| 1728 |
+
flagsz = declare(size_t)
|
| 1729 |
+
more = declare(int)
|
| 1730 |
+
flagsz = sizeof(int)
|
| 1731 |
+
|
| 1732 |
+
if swap_ids: # both router, must send second identity first
|
| 1733 |
+
# recv two ids into msg, id_msg
|
| 1734 |
+
rc = zmq_msg_recv(address(msg), in_socket, 0)
|
| 1735 |
+
if rc < 0:
|
| 1736 |
+
return rc
|
| 1737 |
+
|
| 1738 |
+
rc = zmq_msg_recv(address(id_msg), in_socket, 0)
|
| 1739 |
+
if rc < 0:
|
| 1740 |
+
return rc
|
| 1741 |
+
|
| 1742 |
+
# send second id (id_msg) first
|
| 1743 |
+
# !!!! always send a copy before the original !!!!
|
| 1744 |
+
rc = zmq_msg_copy(address(side_msg), address(id_msg))
|
| 1745 |
+
if rc < 0:
|
| 1746 |
+
return rc
|
| 1747 |
+
rc = zmq_msg_send(address(side_msg), out_socket, ZMQ_SNDMORE)
|
| 1748 |
+
if rc < 0:
|
| 1749 |
+
return rc
|
| 1750 |
+
rc = zmq_msg_send(address(id_msg), side_socket, ZMQ_SNDMORE)
|
| 1751 |
+
if rc < 0:
|
| 1752 |
+
return rc
|
| 1753 |
+
# send first id (msg) second
|
| 1754 |
+
rc = zmq_msg_copy(address(side_msg), address(msg))
|
| 1755 |
+
if rc < 0:
|
| 1756 |
+
return rc
|
| 1757 |
+
rc = zmq_msg_send(address(side_msg), out_socket, ZMQ_SNDMORE)
|
| 1758 |
+
if rc < 0:
|
| 1759 |
+
return rc
|
| 1760 |
+
rc = zmq_msg_send(address(msg), side_socket, ZMQ_SNDMORE)
|
| 1761 |
+
if rc < 0:
|
| 1762 |
+
return rc
|
| 1763 |
+
while True:
|
| 1764 |
+
rc = zmq_msg_recv(address(msg), in_socket, 0)
|
| 1765 |
+
if rc < 0:
|
| 1766 |
+
return rc
|
| 1767 |
+
# assert (rc == 0)
|
| 1768 |
+
rc = zmq_getsockopt(in_socket, ZMQ_RCVMORE, address(more), address(flagsz))
|
| 1769 |
+
if rc < 0:
|
| 1770 |
+
return rc
|
| 1771 |
+
flags = 0
|
| 1772 |
+
if more:
|
| 1773 |
+
flags |= ZMQ_SNDMORE
|
| 1774 |
+
|
| 1775 |
+
rc = zmq_msg_copy(address(side_msg), address(msg))
|
| 1776 |
+
if rc < 0:
|
| 1777 |
+
return rc
|
| 1778 |
+
if flags:
|
| 1779 |
+
rc = zmq_msg_send(address(side_msg), out_socket, flags)
|
| 1780 |
+
if rc < 0:
|
| 1781 |
+
return rc
|
| 1782 |
+
# only SNDMORE for side-socket
|
| 1783 |
+
rc = zmq_msg_send(address(msg), side_socket, ZMQ_SNDMORE)
|
| 1784 |
+
if rc < 0:
|
| 1785 |
+
return rc
|
| 1786 |
+
else:
|
| 1787 |
+
rc = zmq_msg_send(address(side_msg), out_socket, 0)
|
| 1788 |
+
if rc < 0:
|
| 1789 |
+
return rc
|
| 1790 |
+
rc = zmq_msg_send(address(msg), side_socket, 0)
|
| 1791 |
+
if rc < 0:
|
| 1792 |
+
return rc
|
| 1793 |
+
break
|
| 1794 |
+
return rc
|
| 1795 |
+
|
| 1796 |
+
|
| 1797 |
+
@cfunc
|
| 1798 |
+
@inline
|
| 1799 |
+
@nogil
|
| 1800 |
+
def _mq_inline(
|
| 1801 |
+
in_socket: p_void,
|
| 1802 |
+
out_socket: p_void,
|
| 1803 |
+
side_socket: p_void,
|
| 1804 |
+
in_msg_ptr: pointer(zmq_msg_t),
|
| 1805 |
+
out_msg_ptr: pointer(zmq_msg_t),
|
| 1806 |
+
swap_ids: bint,
|
| 1807 |
+
) -> C.int:
|
| 1808 |
+
"""
|
| 1809 |
+
inner C function for monitored_queue
|
| 1810 |
+
"""
|
| 1811 |
+
|
| 1812 |
+
msg: zmq_msg_t = declare(zmq_msg_t)
|
| 1813 |
+
rc: C.int = zmq_msg_init(address(msg))
|
| 1814 |
+
id_msg = declare(zmq_msg_t)
|
| 1815 |
+
rc = zmq_msg_init(address(id_msg))
|
| 1816 |
+
if rc < 0:
|
| 1817 |
+
return rc
|
| 1818 |
+
side_msg = declare(zmq_msg_t)
|
| 1819 |
+
rc = zmq_msg_init(address(side_msg))
|
| 1820 |
+
if rc < 0:
|
| 1821 |
+
return rc
|
| 1822 |
+
|
| 1823 |
+
items = declare(zmq_pollitem_t[2])
|
| 1824 |
+
items[0].socket = in_socket
|
| 1825 |
+
items[0].events = ZMQ_POLLIN
|
| 1826 |
+
items[0].fd = items[0].revents = 0
|
| 1827 |
+
items[1].socket = out_socket
|
| 1828 |
+
items[1].events = ZMQ_POLLIN
|
| 1829 |
+
items[1].fd = items[1].revents = 0
|
| 1830 |
+
|
| 1831 |
+
while True:
|
| 1832 |
+
# wait for the next message to process
|
| 1833 |
+
rc = zmq_poll_c(address(items[0]), 2, -1)
|
| 1834 |
+
if rc < 0:
|
| 1835 |
+
return rc
|
| 1836 |
+
if items[0].revents & ZMQ_POLLIN:
|
| 1837 |
+
# send in_prefix to side socket
|
| 1838 |
+
rc = zmq_msg_copy(address(side_msg), in_msg_ptr)
|
| 1839 |
+
if rc < 0:
|
| 1840 |
+
return rc
|
| 1841 |
+
rc = zmq_msg_send(address(side_msg), side_socket, ZMQ_SNDMORE)
|
| 1842 |
+
if rc < 0:
|
| 1843 |
+
return rc
|
| 1844 |
+
# relay the rest of the message
|
| 1845 |
+
rc = _mq_relay(
|
| 1846 |
+
in_socket, out_socket, side_socket, msg, side_msg, id_msg, swap_ids
|
| 1847 |
+
)
|
| 1848 |
+
if rc < 0:
|
| 1849 |
+
return rc
|
| 1850 |
+
if items[1].revents & ZMQ_POLLIN:
|
| 1851 |
+
# send out_prefix to side socket
|
| 1852 |
+
rc = zmq_msg_copy(address(side_msg), out_msg_ptr)
|
| 1853 |
+
if rc < 0:
|
| 1854 |
+
return rc
|
| 1855 |
+
rc = zmq_msg_send(address(side_msg), side_socket, ZMQ_SNDMORE)
|
| 1856 |
+
if rc < 0:
|
| 1857 |
+
return rc
|
| 1858 |
+
# relay the rest of the message
|
| 1859 |
+
rc = _mq_relay(
|
| 1860 |
+
out_socket, in_socket, side_socket, msg, side_msg, id_msg, swap_ids
|
| 1861 |
+
)
|
| 1862 |
+
if rc < 0:
|
| 1863 |
+
return rc
|
| 1864 |
+
return rc
|
| 1865 |
+
|
| 1866 |
+
|
| 1867 |
+
def monitored_queue(
|
| 1868 |
+
in_socket: Socket,
|
| 1869 |
+
out_socket: Socket,
|
| 1870 |
+
mon_socket: Socket,
|
| 1871 |
+
in_prefix: bytes = b'in',
|
| 1872 |
+
out_prefix: bytes = b'out',
|
| 1873 |
+
):
|
| 1874 |
+
"""
|
| 1875 |
+
Start a monitored queue device.
|
| 1876 |
+
|
| 1877 |
+
A monitored queue is very similar to the zmq.proxy device (monitored queue came first).
|
| 1878 |
+
|
| 1879 |
+
Differences from zmq.proxy:
|
| 1880 |
+
|
| 1881 |
+
- monitored_queue supports both in and out being ROUTER sockets
|
| 1882 |
+
(via swapping IDENTITY prefixes).
|
| 1883 |
+
- monitor messages are prefixed, making in and out messages distinguishable.
|
| 1884 |
+
|
| 1885 |
+
Parameters
|
| 1886 |
+
----------
|
| 1887 |
+
in_socket : zmq.Socket
|
| 1888 |
+
One of the sockets to the Queue. Its messages will be prefixed with
|
| 1889 |
+
'in'.
|
| 1890 |
+
out_socket : zmq.Socket
|
| 1891 |
+
One of the sockets to the Queue. Its messages will be prefixed with
|
| 1892 |
+
'out'. The only difference between in/out socket is this prefix.
|
| 1893 |
+
mon_socket : zmq.Socket
|
| 1894 |
+
This socket sends out every message received by each of the others
|
| 1895 |
+
with an in/out prefix specifying which one it was.
|
| 1896 |
+
in_prefix : str
|
| 1897 |
+
Prefix added to broadcast messages from in_socket.
|
| 1898 |
+
out_prefix : str
|
| 1899 |
+
Prefix added to broadcast messages from out_socket.
|
| 1900 |
+
"""
|
| 1901 |
+
ins: p_void = in_socket.handle
|
| 1902 |
+
outs: p_void = out_socket.handle
|
| 1903 |
+
mons: p_void = mon_socket.handle
|
| 1904 |
+
in_msg = declare(zmq_msg_t)
|
| 1905 |
+
out_msg = declare(zmq_msg_t)
|
| 1906 |
+
swap_ids: bint
|
| 1907 |
+
msg_c: p_char = NULL
|
| 1908 |
+
msg_c_len = declare(Py_ssize_t)
|
| 1909 |
+
rc: C.int
|
| 1910 |
+
|
| 1911 |
+
# force swap_ids if both ROUTERs
|
| 1912 |
+
swap_ids = in_socket.type == ZMQ_ROUTER and out_socket.type == ZMQ_ROUTER
|
| 1913 |
+
|
| 1914 |
+
# build zmq_msg objects from str prefixes
|
| 1915 |
+
asbuffer_r(in_prefix, cast(pointer(p_void), address(msg_c)), address(msg_c_len))
|
| 1916 |
+
rc = zmq_msg_init_size(address(in_msg), msg_c_len)
|
| 1917 |
+
_check_rc(rc)
|
| 1918 |
+
|
| 1919 |
+
memcpy(zmq_msg_data(address(in_msg)), msg_c, zmq_msg_size(address(in_msg)))
|
| 1920 |
+
|
| 1921 |
+
asbuffer_r(out_prefix, cast(pointer(p_void), address(msg_c)), address(msg_c_len))
|
| 1922 |
+
|
| 1923 |
+
rc = zmq_msg_init_size(address(out_msg), msg_c_len)
|
| 1924 |
+
_check_rc(rc)
|
| 1925 |
+
|
| 1926 |
+
while True:
|
| 1927 |
+
with nogil:
|
| 1928 |
+
memcpy(
|
| 1929 |
+
zmq_msg_data(address(out_msg)), msg_c, zmq_msg_size(address(out_msg))
|
| 1930 |
+
)
|
| 1931 |
+
rc = _mq_inline(
|
| 1932 |
+
ins, outs, mons, address(in_msg), address(out_msg), swap_ids
|
| 1933 |
+
)
|
| 1934 |
+
try:
|
| 1935 |
+
_check_rc(rc)
|
| 1936 |
+
except InterruptedSystemCall:
|
| 1937 |
+
continue
|
| 1938 |
+
else:
|
| 1939 |
+
break
|
| 1940 |
+
return rc
|
| 1941 |
+
|
| 1942 |
+
|
| 1943 |
+
__all__ = [
|
| 1944 |
+
'IPC_PATH_MAX_LEN',
|
| 1945 |
+
'Context',
|
| 1946 |
+
'Socket',
|
| 1947 |
+
'Frame',
|
| 1948 |
+
'has',
|
| 1949 |
+
'curve_keypair',
|
| 1950 |
+
'curve_public',
|
| 1951 |
+
'zmq_version_info',
|
| 1952 |
+
'zmq_errno',
|
| 1953 |
+
'zmq_poll',
|
| 1954 |
+
'strerror',
|
| 1955 |
+
'device',
|
| 1956 |
+
'proxy',
|
| 1957 |
+
'proxy_steerable',
|
| 1958 |
+
]
|
.venv/lib/python3.11/site-packages/zmq/backend/cython/constant_enums.pxi
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
cdef extern from "zmq.h" nogil:
|
| 2 |
+
enum: PYZMQ_DRAFT_API
|
| 3 |
+
enum: ZMQ_VERSION
|
| 4 |
+
enum: ZMQ_VERSION_MAJOR
|
| 5 |
+
enum: ZMQ_VERSION_MINOR
|
| 6 |
+
enum: ZMQ_VERSION_PATCH
|
| 7 |
+
enum: ZMQ_IO_THREADS
|
| 8 |
+
enum: ZMQ_MAX_SOCKETS
|
| 9 |
+
enum: ZMQ_SOCKET_LIMIT
|
| 10 |
+
enum: ZMQ_THREAD_PRIORITY
|
| 11 |
+
enum: ZMQ_THREAD_SCHED_POLICY
|
| 12 |
+
enum: ZMQ_MAX_MSGSZ
|
| 13 |
+
enum: ZMQ_MSG_T_SIZE
|
| 14 |
+
enum: ZMQ_THREAD_AFFINITY_CPU_ADD
|
| 15 |
+
enum: ZMQ_THREAD_AFFINITY_CPU_REMOVE
|
| 16 |
+
enum: ZMQ_THREAD_NAME_PREFIX
|
| 17 |
+
enum: ZMQ_STREAMER
|
| 18 |
+
enum: ZMQ_FORWARDER
|
| 19 |
+
enum: ZMQ_QUEUE
|
| 20 |
+
enum: ZMQ_EAGAIN "EAGAIN"
|
| 21 |
+
enum: ZMQ_EFAULT "EFAULT"
|
| 22 |
+
enum: ZMQ_EINVAL "EINVAL"
|
| 23 |
+
enum: ZMQ_ENOTSUP "ENOTSUP"
|
| 24 |
+
enum: ZMQ_EPROTONOSUPPORT "EPROTONOSUPPORT"
|
| 25 |
+
enum: ZMQ_ENOBUFS "ENOBUFS"
|
| 26 |
+
enum: ZMQ_ENETDOWN "ENETDOWN"
|
| 27 |
+
enum: ZMQ_EADDRINUSE "EADDRINUSE"
|
| 28 |
+
enum: ZMQ_EADDRNOTAVAIL "EADDRNOTAVAIL"
|
| 29 |
+
enum: ZMQ_ECONNREFUSED "ECONNREFUSED"
|
| 30 |
+
enum: ZMQ_EINPROGRESS "EINPROGRESS"
|
| 31 |
+
enum: ZMQ_ENOTSOCK "ENOTSOCK"
|
| 32 |
+
enum: ZMQ_EMSGSIZE "EMSGSIZE"
|
| 33 |
+
enum: ZMQ_EAFNOSUPPORT "EAFNOSUPPORT"
|
| 34 |
+
enum: ZMQ_ENETUNREACH "ENETUNREACH"
|
| 35 |
+
enum: ZMQ_ECONNABORTED "ECONNABORTED"
|
| 36 |
+
enum: ZMQ_ECONNRESET "ECONNRESET"
|
| 37 |
+
enum: ZMQ_ENOTCONN "ENOTCONN"
|
| 38 |
+
enum: ZMQ_ETIMEDOUT "ETIMEDOUT"
|
| 39 |
+
enum: ZMQ_EHOSTUNREACH "EHOSTUNREACH"
|
| 40 |
+
enum: ZMQ_ENETRESET "ENETRESET"
|
| 41 |
+
enum: ZMQ_EFSM "EFSM"
|
| 42 |
+
enum: ZMQ_ENOCOMPATPROTO "ENOCOMPATPROTO"
|
| 43 |
+
enum: ZMQ_ETERM "ETERM"
|
| 44 |
+
enum: ZMQ_EMTHREAD "EMTHREAD"
|
| 45 |
+
enum: ZMQ_PROTOCOL_ERROR_WS_UNSPECIFIED
|
| 46 |
+
enum: ZMQ_PROTOCOL_ERROR_ZMTP_UNSPECIFIED
|
| 47 |
+
enum: ZMQ_PROTOCOL_ERROR_ZMTP_UNEXPECTED_COMMAND
|
| 48 |
+
enum: ZMQ_PROTOCOL_ERROR_ZMTP_INVALID_SEQUENCE
|
| 49 |
+
enum: ZMQ_PROTOCOL_ERROR_ZMTP_KEY_EXCHANGE
|
| 50 |
+
enum: ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_UNSPECIFIED
|
| 51 |
+
enum: ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_MESSAGE
|
| 52 |
+
enum: ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_HELLO
|
| 53 |
+
enum: ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_INITIATE
|
| 54 |
+
enum: ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_ERROR
|
| 55 |
+
enum: ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_READY
|
| 56 |
+
enum: ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_WELCOME
|
| 57 |
+
enum: ZMQ_PROTOCOL_ERROR_ZMTP_INVALID_METADATA
|
| 58 |
+
enum: ZMQ_PROTOCOL_ERROR_ZMTP_CRYPTOGRAPHIC
|
| 59 |
+
enum: ZMQ_PROTOCOL_ERROR_ZMTP_MECHANISM_MISMATCH
|
| 60 |
+
enum: ZMQ_PROTOCOL_ERROR_ZAP_UNSPECIFIED
|
| 61 |
+
enum: ZMQ_PROTOCOL_ERROR_ZAP_MALFORMED_REPLY
|
| 62 |
+
enum: ZMQ_PROTOCOL_ERROR_ZAP_BAD_REQUEST_ID
|
| 63 |
+
enum: ZMQ_PROTOCOL_ERROR_ZAP_BAD_VERSION
|
| 64 |
+
enum: ZMQ_PROTOCOL_ERROR_ZAP_INVALID_STATUS_CODE
|
| 65 |
+
enum: ZMQ_PROTOCOL_ERROR_ZAP_INVALID_METADATA
|
| 66 |
+
enum: ZMQ_EVENT_CONNECTED
|
| 67 |
+
enum: ZMQ_EVENT_CONNECT_DELAYED
|
| 68 |
+
enum: ZMQ_EVENT_CONNECT_RETRIED
|
| 69 |
+
enum: ZMQ_EVENT_LISTENING
|
| 70 |
+
enum: ZMQ_EVENT_BIND_FAILED
|
| 71 |
+
enum: ZMQ_EVENT_ACCEPTED
|
| 72 |
+
enum: ZMQ_EVENT_ACCEPT_FAILED
|
| 73 |
+
enum: ZMQ_EVENT_CLOSED
|
| 74 |
+
enum: ZMQ_EVENT_CLOSE_FAILED
|
| 75 |
+
enum: ZMQ_EVENT_DISCONNECTED
|
| 76 |
+
enum: ZMQ_EVENT_MONITOR_STOPPED
|
| 77 |
+
enum: ZMQ_EVENT_HANDSHAKE_FAILED_NO_DETAIL
|
| 78 |
+
enum: ZMQ_EVENT_HANDSHAKE_SUCCEEDED
|
| 79 |
+
enum: ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL
|
| 80 |
+
enum: ZMQ_EVENT_HANDSHAKE_FAILED_AUTH
|
| 81 |
+
enum: ZMQ_EVENT_ALL_V1
|
| 82 |
+
enum: ZMQ_EVENT_ALL
|
| 83 |
+
enum: ZMQ_EVENT_PIPES_STATS
|
| 84 |
+
enum: ZMQ_EVENT_ALL_V2
|
| 85 |
+
enum: ZMQ_DONTWAIT
|
| 86 |
+
enum: ZMQ_SNDMORE
|
| 87 |
+
enum: ZMQ_NOBLOCK
|
| 88 |
+
enum: ZMQ_MORE
|
| 89 |
+
enum: ZMQ_SHARED
|
| 90 |
+
enum: ZMQ_SRCFD
|
| 91 |
+
enum: ZMQ_NORM_FIXED
|
| 92 |
+
enum: ZMQ_NORM_CC
|
| 93 |
+
enum: ZMQ_NORM_CCL
|
| 94 |
+
enum: ZMQ_NORM_CCE
|
| 95 |
+
enum: ZMQ_NORM_CCE_ECNONLY
|
| 96 |
+
enum: ZMQ_POLLIN
|
| 97 |
+
enum: ZMQ_POLLOUT
|
| 98 |
+
enum: ZMQ_POLLERR
|
| 99 |
+
enum: ZMQ_POLLPRI
|
| 100 |
+
enum: ZMQ_RECONNECT_STOP_CONN_REFUSED
|
| 101 |
+
enum: ZMQ_RECONNECT_STOP_HANDSHAKE_FAILED
|
| 102 |
+
enum: ZMQ_RECONNECT_STOP_AFTER_DISCONNECT
|
| 103 |
+
enum: ZMQ_NOTIFY_CONNECT
|
| 104 |
+
enum: ZMQ_NOTIFY_DISCONNECT
|
| 105 |
+
enum: ZMQ_NULL
|
| 106 |
+
enum: ZMQ_PLAIN
|
| 107 |
+
enum: ZMQ_CURVE
|
| 108 |
+
enum: ZMQ_GSSAPI
|
| 109 |
+
enum: ZMQ_HWM
|
| 110 |
+
enum: ZMQ_AFFINITY
|
| 111 |
+
enum: ZMQ_ROUTING_ID
|
| 112 |
+
enum: ZMQ_SUBSCRIBE
|
| 113 |
+
enum: ZMQ_UNSUBSCRIBE
|
| 114 |
+
enum: ZMQ_RATE
|
| 115 |
+
enum: ZMQ_RECOVERY_IVL
|
| 116 |
+
enum: ZMQ_SNDBUF
|
| 117 |
+
enum: ZMQ_RCVBUF
|
| 118 |
+
enum: ZMQ_RCVMORE
|
| 119 |
+
enum: ZMQ_FD
|
| 120 |
+
enum: ZMQ_EVENTS
|
| 121 |
+
enum: ZMQ_TYPE
|
| 122 |
+
enum: ZMQ_LINGER
|
| 123 |
+
enum: ZMQ_RECONNECT_IVL
|
| 124 |
+
enum: ZMQ_BACKLOG
|
| 125 |
+
enum: ZMQ_RECONNECT_IVL_MAX
|
| 126 |
+
enum: ZMQ_MAXMSGSIZE
|
| 127 |
+
enum: ZMQ_SNDHWM
|
| 128 |
+
enum: ZMQ_RCVHWM
|
| 129 |
+
enum: ZMQ_MULTICAST_HOPS
|
| 130 |
+
enum: ZMQ_RCVTIMEO
|
| 131 |
+
enum: ZMQ_SNDTIMEO
|
| 132 |
+
enum: ZMQ_LAST_ENDPOINT
|
| 133 |
+
enum: ZMQ_ROUTER_MANDATORY
|
| 134 |
+
enum: ZMQ_TCP_KEEPALIVE
|
| 135 |
+
enum: ZMQ_TCP_KEEPALIVE_CNT
|
| 136 |
+
enum: ZMQ_TCP_KEEPALIVE_IDLE
|
| 137 |
+
enum: ZMQ_TCP_KEEPALIVE_INTVL
|
| 138 |
+
enum: ZMQ_IMMEDIATE
|
| 139 |
+
enum: ZMQ_XPUB_VERBOSE
|
| 140 |
+
enum: ZMQ_ROUTER_RAW
|
| 141 |
+
enum: ZMQ_IPV6
|
| 142 |
+
enum: ZMQ_MECHANISM
|
| 143 |
+
enum: ZMQ_PLAIN_SERVER
|
| 144 |
+
enum: ZMQ_PLAIN_USERNAME
|
| 145 |
+
enum: ZMQ_PLAIN_PASSWORD
|
| 146 |
+
enum: ZMQ_CURVE_SERVER
|
| 147 |
+
enum: ZMQ_CURVE_PUBLICKEY
|
| 148 |
+
enum: ZMQ_CURVE_SECRETKEY
|
| 149 |
+
enum: ZMQ_CURVE_SERVERKEY
|
| 150 |
+
enum: ZMQ_PROBE_ROUTER
|
| 151 |
+
enum: ZMQ_REQ_CORRELATE
|
| 152 |
+
enum: ZMQ_REQ_RELAXED
|
| 153 |
+
enum: ZMQ_CONFLATE
|
| 154 |
+
enum: ZMQ_ZAP_DOMAIN
|
| 155 |
+
enum: ZMQ_ROUTER_HANDOVER
|
| 156 |
+
enum: ZMQ_TOS
|
| 157 |
+
enum: ZMQ_CONNECT_ROUTING_ID
|
| 158 |
+
enum: ZMQ_GSSAPI_SERVER
|
| 159 |
+
enum: ZMQ_GSSAPI_PRINCIPAL
|
| 160 |
+
enum: ZMQ_GSSAPI_SERVICE_PRINCIPAL
|
| 161 |
+
enum: ZMQ_GSSAPI_PLAINTEXT
|
| 162 |
+
enum: ZMQ_HANDSHAKE_IVL
|
| 163 |
+
enum: ZMQ_SOCKS_PROXY
|
| 164 |
+
enum: ZMQ_XPUB_NODROP
|
| 165 |
+
enum: ZMQ_BLOCKY
|
| 166 |
+
enum: ZMQ_XPUB_MANUAL
|
| 167 |
+
enum: ZMQ_XPUB_WELCOME_MSG
|
| 168 |
+
enum: ZMQ_STREAM_NOTIFY
|
| 169 |
+
enum: ZMQ_INVERT_MATCHING
|
| 170 |
+
enum: ZMQ_HEARTBEAT_IVL
|
| 171 |
+
enum: ZMQ_HEARTBEAT_TTL
|
| 172 |
+
enum: ZMQ_HEARTBEAT_TIMEOUT
|
| 173 |
+
enum: ZMQ_XPUB_VERBOSER
|
| 174 |
+
enum: ZMQ_CONNECT_TIMEOUT
|
| 175 |
+
enum: ZMQ_TCP_MAXRT
|
| 176 |
+
enum: ZMQ_THREAD_SAFE
|
| 177 |
+
enum: ZMQ_MULTICAST_MAXTPDU
|
| 178 |
+
enum: ZMQ_VMCI_BUFFER_SIZE
|
| 179 |
+
enum: ZMQ_VMCI_BUFFER_MIN_SIZE
|
| 180 |
+
enum: ZMQ_VMCI_BUFFER_MAX_SIZE
|
| 181 |
+
enum: ZMQ_VMCI_CONNECT_TIMEOUT
|
| 182 |
+
enum: ZMQ_USE_FD
|
| 183 |
+
enum: ZMQ_GSSAPI_PRINCIPAL_NAMETYPE
|
| 184 |
+
enum: ZMQ_GSSAPI_SERVICE_PRINCIPAL_NAMETYPE
|
| 185 |
+
enum: ZMQ_BINDTODEVICE
|
| 186 |
+
enum: ZMQ_IDENTITY
|
| 187 |
+
enum: ZMQ_CONNECT_RID
|
| 188 |
+
enum: ZMQ_TCP_ACCEPT_FILTER
|
| 189 |
+
enum: ZMQ_IPC_FILTER_PID
|
| 190 |
+
enum: ZMQ_IPC_FILTER_UID
|
| 191 |
+
enum: ZMQ_IPC_FILTER_GID
|
| 192 |
+
enum: ZMQ_IPV4ONLY
|
| 193 |
+
enum: ZMQ_DELAY_ATTACH_ON_CONNECT
|
| 194 |
+
enum: ZMQ_FAIL_UNROUTABLE
|
| 195 |
+
enum: ZMQ_ROUTER_BEHAVIOR
|
| 196 |
+
enum: ZMQ_ZAP_ENFORCE_DOMAIN
|
| 197 |
+
enum: ZMQ_LOOPBACK_FASTPATH
|
| 198 |
+
enum: ZMQ_METADATA
|
| 199 |
+
enum: ZMQ_MULTICAST_LOOP
|
| 200 |
+
enum: ZMQ_ROUTER_NOTIFY
|
| 201 |
+
enum: ZMQ_XPUB_MANUAL_LAST_VALUE
|
| 202 |
+
enum: ZMQ_SOCKS_USERNAME
|
| 203 |
+
enum: ZMQ_SOCKS_PASSWORD
|
| 204 |
+
enum: ZMQ_IN_BATCH_SIZE
|
| 205 |
+
enum: ZMQ_OUT_BATCH_SIZE
|
| 206 |
+
enum: ZMQ_WSS_KEY_PEM
|
| 207 |
+
enum: ZMQ_WSS_CERT_PEM
|
| 208 |
+
enum: ZMQ_WSS_TRUST_PEM
|
| 209 |
+
enum: ZMQ_WSS_HOSTNAME
|
| 210 |
+
enum: ZMQ_WSS_TRUST_SYSTEM
|
| 211 |
+
enum: ZMQ_ONLY_FIRST_SUBSCRIBE
|
| 212 |
+
enum: ZMQ_RECONNECT_STOP
|
| 213 |
+
enum: ZMQ_HELLO_MSG
|
| 214 |
+
enum: ZMQ_DISCONNECT_MSG
|
| 215 |
+
enum: ZMQ_PRIORITY
|
| 216 |
+
enum: ZMQ_BUSY_POLL
|
| 217 |
+
enum: ZMQ_HICCUP_MSG
|
| 218 |
+
enum: ZMQ_XSUB_VERBOSE_UNSUBSCRIBE
|
| 219 |
+
enum: ZMQ_TOPICS_COUNT
|
| 220 |
+
enum: ZMQ_NORM_MODE
|
| 221 |
+
enum: ZMQ_NORM_UNICAST_NACK
|
| 222 |
+
enum: ZMQ_NORM_BUFFER_SIZE
|
| 223 |
+
enum: ZMQ_NORM_SEGMENT_SIZE
|
| 224 |
+
enum: ZMQ_NORM_BLOCK_SIZE
|
| 225 |
+
enum: ZMQ_NORM_NUM_PARITY
|
| 226 |
+
enum: ZMQ_NORM_NUM_AUTOPARITY
|
| 227 |
+
enum: ZMQ_NORM_PUSH
|
| 228 |
+
enum: ZMQ_PAIR
|
| 229 |
+
enum: ZMQ_PUB
|
| 230 |
+
enum: ZMQ_SUB
|
| 231 |
+
enum: ZMQ_REQ
|
| 232 |
+
enum: ZMQ_REP
|
| 233 |
+
enum: ZMQ_DEALER
|
| 234 |
+
enum: ZMQ_ROUTER
|
| 235 |
+
enum: ZMQ_PULL
|
| 236 |
+
enum: ZMQ_PUSH
|
| 237 |
+
enum: ZMQ_XPUB
|
| 238 |
+
enum: ZMQ_XSUB
|
| 239 |
+
enum: ZMQ_STREAM
|
| 240 |
+
enum: ZMQ_XREQ
|
| 241 |
+
enum: ZMQ_XREP
|
| 242 |
+
enum: ZMQ_SERVER
|
| 243 |
+
enum: ZMQ_CLIENT
|
| 244 |
+
enum: ZMQ_RADIO
|
| 245 |
+
enum: ZMQ_DISH
|
| 246 |
+
enum: ZMQ_GATHER
|
| 247 |
+
enum: ZMQ_SCATTER
|
| 248 |
+
enum: ZMQ_DGRAM
|
| 249 |
+
enum: ZMQ_PEER
|
| 250 |
+
enum: ZMQ_CHANNEL
|
.venv/lib/python3.11/site-packages/zmq/backend/cython/libzmq.pxd
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""All the C imports for 0MQ"""
|
| 2 |
+
|
| 3 |
+
#
|
| 4 |
+
# Copyright (c) 2010 Brian E. Granger & Min Ragan-Kelley
|
| 5 |
+
#
|
| 6 |
+
# This file is part of pyzmq.
|
| 7 |
+
#
|
| 8 |
+
# pyzmq is free software; you can redistribute it and/or modify it under
|
| 9 |
+
# the terms of the Lesser GNU General Public License as published by
|
| 10 |
+
# the Free Software Foundation; either version 3 of the License, or
|
| 11 |
+
# (at your option) any later version.
|
| 12 |
+
#
|
| 13 |
+
# pyzmq is distributed in the hope that it will be useful,
|
| 14 |
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 15 |
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 16 |
+
# Lesser GNU General Public License for more details.
|
| 17 |
+
#
|
| 18 |
+
# You should have received a copy of the Lesser GNU General Public License
|
| 19 |
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
| 20 |
+
#
|
| 21 |
+
|
| 22 |
+
#-----------------------------------------------------------------------------
|
| 23 |
+
# Imports
|
| 24 |
+
#-----------------------------------------------------------------------------
|
| 25 |
+
|
| 26 |
+
#-----------------------------------------------------------------------------
|
| 27 |
+
# Import the C header files
|
| 28 |
+
#-----------------------------------------------------------------------------
|
| 29 |
+
|
| 30 |
+
# common includes, such as zmq compat, pyversion_compat
|
| 31 |
+
# make sure we load pyversion compat in every Cython module
|
| 32 |
+
cdef extern from "pyversion_compat.h":
|
| 33 |
+
pass
|
| 34 |
+
|
| 35 |
+
# were it not for Windows,
|
| 36 |
+
# we could cimport these from libc.stdint
|
| 37 |
+
cdef extern from "zmq_compat.h":
|
| 38 |
+
ctypedef signed long long int64_t "pyzmq_int64_t"
|
| 39 |
+
ctypedef unsigned int uint32_t "pyzmq_uint32_t"
|
| 40 |
+
|
| 41 |
+
include "constant_enums.pxi"
|
| 42 |
+
|
| 43 |
+
cdef extern from "zmq.h" nogil:
|
| 44 |
+
|
| 45 |
+
void _zmq_version "zmq_version"(int *major, int *minor, int *patch)
|
| 46 |
+
|
| 47 |
+
ctypedef int fd_t "ZMQ_FD_T"
|
| 48 |
+
|
| 49 |
+
enum: errno
|
| 50 |
+
const char *zmq_strerror (int errnum)
|
| 51 |
+
int zmq_errno()
|
| 52 |
+
|
| 53 |
+
void *zmq_ctx_new ()
|
| 54 |
+
int zmq_ctx_destroy (void *context)
|
| 55 |
+
int zmq_ctx_set (void *context, int option, int optval)
|
| 56 |
+
int zmq_ctx_get (void *context, int option)
|
| 57 |
+
void *zmq_init (int io_threads)
|
| 58 |
+
int zmq_term (void *context)
|
| 59 |
+
|
| 60 |
+
# blackbox def for zmq_msg_t
|
| 61 |
+
ctypedef void * zmq_msg_t "zmq_msg_t"
|
| 62 |
+
|
| 63 |
+
ctypedef void zmq_free_fn(void *data, void *hint)
|
| 64 |
+
|
| 65 |
+
int zmq_msg_init (zmq_msg_t *msg)
|
| 66 |
+
int zmq_msg_init_size (zmq_msg_t *msg, size_t size)
|
| 67 |
+
int zmq_msg_init_data (zmq_msg_t *msg, void *data,
|
| 68 |
+
size_t size, zmq_free_fn *ffn, void *hint)
|
| 69 |
+
int zmq_msg_send (zmq_msg_t *msg, void *s, int flags)
|
| 70 |
+
int zmq_msg_recv (zmq_msg_t *msg, void *s, int flags)
|
| 71 |
+
int zmq_msg_close (zmq_msg_t *msg)
|
| 72 |
+
int zmq_msg_move (zmq_msg_t *dest, zmq_msg_t *src)
|
| 73 |
+
int zmq_msg_copy (zmq_msg_t *dest, zmq_msg_t *src)
|
| 74 |
+
void *zmq_msg_data (zmq_msg_t *msg)
|
| 75 |
+
size_t zmq_msg_size (zmq_msg_t *msg)
|
| 76 |
+
int zmq_msg_more (zmq_msg_t *msg)
|
| 77 |
+
int zmq_msg_get (zmq_msg_t *msg, int option)
|
| 78 |
+
int zmq_msg_set (zmq_msg_t *msg, int option, int optval)
|
| 79 |
+
const char *zmq_msg_gets (zmq_msg_t *msg, const char *property)
|
| 80 |
+
int zmq_has (const char *capability)
|
| 81 |
+
|
| 82 |
+
void *zmq_socket (void *context, int type)
|
| 83 |
+
int zmq_close (void *s)
|
| 84 |
+
int zmq_setsockopt (void *s, int option, void *optval, size_t optvallen)
|
| 85 |
+
int zmq_getsockopt (void *s, int option, void *optval, size_t *optvallen)
|
| 86 |
+
int zmq_bind (void *s, char *addr)
|
| 87 |
+
int zmq_connect (void *s, char *addr)
|
| 88 |
+
int zmq_unbind (void *s, char *addr)
|
| 89 |
+
int zmq_disconnect (void *s, char *addr)
|
| 90 |
+
|
| 91 |
+
int zmq_socket_monitor (void *s, char *addr, int flags)
|
| 92 |
+
|
| 93 |
+
# send/recv
|
| 94 |
+
int zmq_sendbuf (void *s, const void *buf, size_t n, int flags)
|
| 95 |
+
int zmq_recvbuf (void *s, void *buf, size_t n, int flags)
|
| 96 |
+
|
| 97 |
+
ctypedef struct zmq_pollitem_t:
|
| 98 |
+
void *socket
|
| 99 |
+
fd_t fd
|
| 100 |
+
short events
|
| 101 |
+
short revents
|
| 102 |
+
|
| 103 |
+
int zmq_poll (zmq_pollitem_t *items, int nitems, long timeout)
|
| 104 |
+
|
| 105 |
+
int zmq_device (int device_, void *insocket_, void *outsocket_)
|
| 106 |
+
int zmq_proxy (void *frontend, void *backend, void *capture)
|
| 107 |
+
int zmq_proxy_steerable (void *frontend,
|
| 108 |
+
void *backend,
|
| 109 |
+
void *capture,
|
| 110 |
+
void *control)
|
| 111 |
+
|
| 112 |
+
int zmq_curve_keypair (char *z85_public_key, char *z85_secret_key)
|
| 113 |
+
int zmq_curve_public (char *z85_public_key, char *z85_secret_key)
|
| 114 |
+
|
| 115 |
+
# 4.2 draft
|
| 116 |
+
int zmq_join (void *s, const char *group)
|
| 117 |
+
int zmq_leave (void *s, const char *group)
|
| 118 |
+
|
| 119 |
+
int zmq_msg_set_routing_id(zmq_msg_t *msg, uint32_t routing_id)
|
| 120 |
+
uint32_t zmq_msg_routing_id(zmq_msg_t *msg)
|
| 121 |
+
int zmq_msg_set_group(zmq_msg_t *msg, const char *group)
|
| 122 |
+
const char *zmq_msg_group(zmq_msg_t *msg)
|
.venv/lib/python3.11/site-packages/zmq/backend/select.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Import basic exposure of libzmq C API as a backend"""
|
| 2 |
+
|
| 3 |
+
# Copyright (C) PyZMQ Developers
|
| 4 |
+
# Distributed under the terms of the Modified BSD License.
|
| 5 |
+
|
| 6 |
+
from importlib import import_module
|
| 7 |
+
from typing import Dict
|
| 8 |
+
|
| 9 |
+
public_api = [
|
| 10 |
+
'Context',
|
| 11 |
+
'Socket',
|
| 12 |
+
'Frame',
|
| 13 |
+
'Message',
|
| 14 |
+
'device',
|
| 15 |
+
'proxy',
|
| 16 |
+
'proxy_steerable',
|
| 17 |
+
'zmq_poll',
|
| 18 |
+
'strerror',
|
| 19 |
+
'zmq_errno',
|
| 20 |
+
'has',
|
| 21 |
+
'curve_keypair',
|
| 22 |
+
'curve_public',
|
| 23 |
+
'zmq_version_info',
|
| 24 |
+
'IPC_PATH_MAX_LEN',
|
| 25 |
+
]
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def select_backend(name: str) -> Dict:
|
| 29 |
+
"""Select the pyzmq backend"""
|
| 30 |
+
try:
|
| 31 |
+
mod = import_module(name)
|
| 32 |
+
except ImportError:
|
| 33 |
+
raise
|
| 34 |
+
except Exception as e:
|
| 35 |
+
raise ImportError(f"Importing {name} failed with {e}") from e
|
| 36 |
+
ns = {
|
| 37 |
+
# private API
|
| 38 |
+
'monitored_queue': mod.monitored_queue,
|
| 39 |
+
}
|
| 40 |
+
ns.update({key: getattr(mod, key) for key in public_api})
|
| 41 |
+
return ns
|
.venv/lib/python3.11/site-packages/zmq/eventloop/__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tornado eventloop integration for pyzmq"""
|
| 2 |
+
|
| 3 |
+
from tornado.ioloop import IOLoop
|
| 4 |
+
|
| 5 |
+
__all__ = ['IOLoop']
|
.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (330 Bytes). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/_deprecated.cpython-311.pyc
ADDED
|
Binary file (10.3 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/future.cpython-311.pyc
ADDED
|
Binary file (6.08 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/ioloop.cpython-311.pyc
ADDED
|
Binary file (1.18 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/zmqstream.cpython-311.pyc
ADDED
|
Binary file (29.3 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/eventloop/_deprecated.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""tornado IOLoop API with zmq compatibility
|
| 2 |
+
|
| 3 |
+
If you have tornado ≥ 3.0, this is a subclass of tornado's IOLoop,
|
| 4 |
+
otherwise we ship a minimal subset of tornado in zmq.eventloop.minitornado.
|
| 5 |
+
|
| 6 |
+
The minimal shipped version of tornado's IOLoop does not include
|
| 7 |
+
support for concurrent futures - this will only be available if you
|
| 8 |
+
have tornado ≥ 3.0.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
# Copyright (C) PyZMQ Developers
|
| 12 |
+
# Distributed under the terms of the Modified BSD License.
|
| 13 |
+
|
| 14 |
+
import time
|
| 15 |
+
import warnings
|
| 16 |
+
from typing import Tuple
|
| 17 |
+
|
| 18 |
+
from zmq import ETERM, POLLERR, POLLIN, POLLOUT, Poller, ZMQError
|
| 19 |
+
|
| 20 |
+
tornado_version: Tuple = ()
|
| 21 |
+
try:
|
| 22 |
+
import tornado
|
| 23 |
+
|
| 24 |
+
tornado_version = tornado.version_info
|
| 25 |
+
except (ImportError, AttributeError):
|
| 26 |
+
pass
|
| 27 |
+
|
| 28 |
+
from .minitornado.ioloop import PeriodicCallback, PollIOLoop
|
| 29 |
+
from .minitornado.log import gen_log
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class DelayedCallback(PeriodicCallback):
|
| 33 |
+
"""Schedules the given callback to be called once.
|
| 34 |
+
|
| 35 |
+
The callback is called once, after callback_time milliseconds.
|
| 36 |
+
|
| 37 |
+
`start` must be called after the DelayedCallback is created.
|
| 38 |
+
|
| 39 |
+
The timeout is calculated from when `start` is called.
|
| 40 |
+
"""
|
| 41 |
+
|
| 42 |
+
def __init__(self, callback, callback_time, io_loop=None):
|
| 43 |
+
# PeriodicCallback require callback_time to be positive
|
| 44 |
+
warnings.warn(
|
| 45 |
+
"""DelayedCallback is deprecated.
|
| 46 |
+
Use loop.add_timeout instead.""",
|
| 47 |
+
DeprecationWarning,
|
| 48 |
+
)
|
| 49 |
+
callback_time = max(callback_time, 1e-3)
|
| 50 |
+
super().__init__(callback, callback_time, io_loop)
|
| 51 |
+
|
| 52 |
+
def start(self):
|
| 53 |
+
"""Starts the timer."""
|
| 54 |
+
self._running = True
|
| 55 |
+
self._firstrun = True
|
| 56 |
+
self._next_timeout = time.time() + self.callback_time / 1000.0
|
| 57 |
+
self.io_loop.add_timeout(self._next_timeout, self._run)
|
| 58 |
+
|
| 59 |
+
def _run(self):
|
| 60 |
+
if not self._running:
|
| 61 |
+
return
|
| 62 |
+
self._running = False
|
| 63 |
+
try:
|
| 64 |
+
self.callback()
|
| 65 |
+
except Exception:
|
| 66 |
+
gen_log.error("Error in delayed callback", exc_info=True)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class ZMQPoller:
|
| 70 |
+
"""A poller that can be used in the tornado IOLoop.
|
| 71 |
+
|
| 72 |
+
This simply wraps a regular zmq.Poller, scaling the timeout
|
| 73 |
+
by 1000, so that it is in seconds rather than milliseconds.
|
| 74 |
+
"""
|
| 75 |
+
|
| 76 |
+
def __init__(self):
|
| 77 |
+
self._poller = Poller()
|
| 78 |
+
|
| 79 |
+
@staticmethod
|
| 80 |
+
def _map_events(events):
|
| 81 |
+
"""translate IOLoop.READ/WRITE/ERROR event masks into zmq.POLLIN/OUT/ERR"""
|
| 82 |
+
z_events = 0
|
| 83 |
+
if events & IOLoop.READ:
|
| 84 |
+
z_events |= POLLIN
|
| 85 |
+
if events & IOLoop.WRITE:
|
| 86 |
+
z_events |= POLLOUT
|
| 87 |
+
if events & IOLoop.ERROR:
|
| 88 |
+
z_events |= POLLERR
|
| 89 |
+
return z_events
|
| 90 |
+
|
| 91 |
+
@staticmethod
|
| 92 |
+
def _remap_events(z_events):
|
| 93 |
+
"""translate zmq.POLLIN/OUT/ERR event masks into IOLoop.READ/WRITE/ERROR"""
|
| 94 |
+
events = 0
|
| 95 |
+
if z_events & POLLIN:
|
| 96 |
+
events |= IOLoop.READ
|
| 97 |
+
if z_events & POLLOUT:
|
| 98 |
+
events |= IOLoop.WRITE
|
| 99 |
+
if z_events & POLLERR:
|
| 100 |
+
events |= IOLoop.ERROR
|
| 101 |
+
return events
|
| 102 |
+
|
| 103 |
+
def register(self, fd, events):
|
| 104 |
+
return self._poller.register(fd, self._map_events(events))
|
| 105 |
+
|
| 106 |
+
def modify(self, fd, events):
|
| 107 |
+
return self._poller.modify(fd, self._map_events(events))
|
| 108 |
+
|
| 109 |
+
def unregister(self, fd):
|
| 110 |
+
return self._poller.unregister(fd)
|
| 111 |
+
|
| 112 |
+
def poll(self, timeout):
|
| 113 |
+
"""poll in seconds rather than milliseconds.
|
| 114 |
+
|
| 115 |
+
Event masks will be IOLoop.READ/WRITE/ERROR
|
| 116 |
+
"""
|
| 117 |
+
z_events = self._poller.poll(1000 * timeout)
|
| 118 |
+
return [(fd, self._remap_events(evt)) for (fd, evt) in z_events]
|
| 119 |
+
|
| 120 |
+
def close(self):
|
| 121 |
+
pass
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
class ZMQIOLoop(PollIOLoop):
|
| 125 |
+
"""ZMQ subclass of tornado's IOLoop
|
| 126 |
+
|
| 127 |
+
Minor modifications, so that .current/.instance return self
|
| 128 |
+
"""
|
| 129 |
+
|
| 130 |
+
_zmq_impl = ZMQPoller
|
| 131 |
+
|
| 132 |
+
def initialize(self, impl=None, **kwargs):
|
| 133 |
+
impl = self._zmq_impl() if impl is None else impl
|
| 134 |
+
super().initialize(impl=impl, **kwargs)
|
| 135 |
+
|
| 136 |
+
@classmethod
|
| 137 |
+
def instance(cls, *args, **kwargs):
|
| 138 |
+
"""Returns a global `IOLoop` instance.
|
| 139 |
+
|
| 140 |
+
Most applications have a single, global `IOLoop` running on the
|
| 141 |
+
main thread. Use this method to get this instance from
|
| 142 |
+
another thread. To get the current thread's `IOLoop`, use `current()`.
|
| 143 |
+
"""
|
| 144 |
+
# install ZMQIOLoop as the active IOLoop implementation
|
| 145 |
+
# when using tornado 3
|
| 146 |
+
if tornado_version >= (3,):
|
| 147 |
+
PollIOLoop.configure(cls)
|
| 148 |
+
loop = PollIOLoop.instance(*args, **kwargs)
|
| 149 |
+
if not isinstance(loop, cls):
|
| 150 |
+
warnings.warn(
|
| 151 |
+
f"IOLoop.current expected instance of {cls!r}, got {loop!r}",
|
| 152 |
+
RuntimeWarning,
|
| 153 |
+
stacklevel=2,
|
| 154 |
+
)
|
| 155 |
+
return loop
|
| 156 |
+
|
| 157 |
+
@classmethod
|
| 158 |
+
def current(cls, *args, **kwargs):
|
| 159 |
+
"""Returns the current thread’s IOLoop."""
|
| 160 |
+
# install ZMQIOLoop as the active IOLoop implementation
|
| 161 |
+
# when using tornado 3
|
| 162 |
+
if tornado_version >= (3,):
|
| 163 |
+
PollIOLoop.configure(cls)
|
| 164 |
+
loop = PollIOLoop.current(*args, **kwargs)
|
| 165 |
+
if not isinstance(loop, cls):
|
| 166 |
+
warnings.warn(
|
| 167 |
+
f"IOLoop.current expected instance of {cls!r}, got {loop!r}",
|
| 168 |
+
RuntimeWarning,
|
| 169 |
+
stacklevel=2,
|
| 170 |
+
)
|
| 171 |
+
return loop
|
| 172 |
+
|
| 173 |
+
def start(self):
|
| 174 |
+
try:
|
| 175 |
+
super().start()
|
| 176 |
+
except ZMQError as e:
|
| 177 |
+
if e.errno == ETERM:
|
| 178 |
+
# quietly return on ETERM
|
| 179 |
+
pass
|
| 180 |
+
else:
|
| 181 |
+
raise
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
# public API name
|
| 185 |
+
IOLoop = ZMQIOLoop
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def install():
|
| 189 |
+
"""set the tornado IOLoop instance with the pyzmq IOLoop.
|
| 190 |
+
|
| 191 |
+
After calling this function, tornado's IOLoop.instance() and pyzmq's
|
| 192 |
+
IOLoop.instance() will return the same object.
|
| 193 |
+
|
| 194 |
+
An assertion error will be raised if tornado's IOLoop has been initialized
|
| 195 |
+
prior to calling this function.
|
| 196 |
+
"""
|
| 197 |
+
from tornado import ioloop
|
| 198 |
+
|
| 199 |
+
# check if tornado's IOLoop is already initialized to something other
|
| 200 |
+
# than the pyzmq IOLoop instance:
|
| 201 |
+
assert (
|
| 202 |
+
(not ioloop.IOLoop.initialized())
|
| 203 |
+
or ioloop.IOLoop.instance() is IOLoop.instance()
|
| 204 |
+
), "tornado IOLoop already initialized"
|
| 205 |
+
|
| 206 |
+
if tornado_version >= (3,):
|
| 207 |
+
# tornado 3 has an official API for registering new defaults, yay!
|
| 208 |
+
ioloop.IOLoop.configure(ZMQIOLoop)
|
| 209 |
+
else:
|
| 210 |
+
# we have to set the global instance explicitly
|
| 211 |
+
ioloop.IOLoop._instance = IOLoop.instance()
|
.venv/lib/python3.11/site-packages/zmq/eventloop/future.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Future-returning APIs for tornado coroutines.
|
| 2 |
+
|
| 3 |
+
.. seealso::
|
| 4 |
+
|
| 5 |
+
:mod:`zmq.asyncio`
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
# Copyright (c) PyZMQ Developers.
|
| 10 |
+
# Distributed under the terms of the Modified BSD License.
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import asyncio
|
| 14 |
+
import warnings
|
| 15 |
+
from typing import Any
|
| 16 |
+
|
| 17 |
+
from tornado.concurrent import Future
|
| 18 |
+
from tornado.ioloop import IOLoop
|
| 19 |
+
|
| 20 |
+
import zmq as _zmq
|
| 21 |
+
from zmq._future import _AsyncPoller, _AsyncSocket
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class CancelledError(Exception):
|
| 25 |
+
pass
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class _TornadoFuture(Future):
|
| 29 |
+
"""Subclass Tornado Future, reinstating cancellation."""
|
| 30 |
+
|
| 31 |
+
def cancel(self):
|
| 32 |
+
if self.done():
|
| 33 |
+
return False
|
| 34 |
+
self.set_exception(CancelledError())
|
| 35 |
+
return True
|
| 36 |
+
|
| 37 |
+
def cancelled(self):
|
| 38 |
+
return self.done() and isinstance(self.exception(), CancelledError)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class _CancellableTornadoTimeout:
|
| 42 |
+
def __init__(self, loop, timeout):
|
| 43 |
+
self.loop = loop
|
| 44 |
+
self.timeout = timeout
|
| 45 |
+
|
| 46 |
+
def cancel(self):
|
| 47 |
+
self.loop.remove_timeout(self.timeout)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
# mixin for tornado/asyncio compatibility
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class _AsyncTornado:
|
| 54 |
+
_Future: type[asyncio.Future] = _TornadoFuture
|
| 55 |
+
_READ = IOLoop.READ
|
| 56 |
+
_WRITE = IOLoop.WRITE
|
| 57 |
+
|
| 58 |
+
def _default_loop(self):
|
| 59 |
+
return IOLoop.current()
|
| 60 |
+
|
| 61 |
+
def _call_later(self, delay, callback):
|
| 62 |
+
io_loop = self._get_loop()
|
| 63 |
+
timeout = io_loop.call_later(delay, callback)
|
| 64 |
+
return _CancellableTornadoTimeout(io_loop, timeout)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class Poller(_AsyncTornado, _AsyncPoller):
|
| 68 |
+
def _watch_raw_socket(self, loop, socket, evt, f):
|
| 69 |
+
"""Schedule callback for a raw socket"""
|
| 70 |
+
loop.add_handler(socket, lambda *args: f(), evt)
|
| 71 |
+
|
| 72 |
+
def _unwatch_raw_sockets(self, loop, *sockets):
|
| 73 |
+
"""Unschedule callback for a raw socket"""
|
| 74 |
+
for socket in sockets:
|
| 75 |
+
loop.remove_handler(socket)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class Socket(_AsyncTornado, _AsyncSocket):
|
| 79 |
+
_poller_class = Poller
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
Poller._socket_class = Socket
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
class Context(_zmq.Context[Socket]):
|
| 86 |
+
# avoid sharing instance with base Context class
|
| 87 |
+
_instance = None
|
| 88 |
+
|
| 89 |
+
io_loop = None
|
| 90 |
+
|
| 91 |
+
@staticmethod
|
| 92 |
+
def _socket_class(self, socket_type):
|
| 93 |
+
return Socket(self, socket_type)
|
| 94 |
+
|
| 95 |
+
def __init__(self: Context, *args: Any, **kwargs: Any) -> None:
|
| 96 |
+
io_loop = kwargs.pop('io_loop', None)
|
| 97 |
+
if io_loop is not None:
|
| 98 |
+
warnings.warn(
|
| 99 |
+
f"{self.__class__.__name__}(io_loop) argument is deprecated in pyzmq 22.2."
|
| 100 |
+
" The currently active loop will always be used.",
|
| 101 |
+
DeprecationWarning,
|
| 102 |
+
stacklevel=2,
|
| 103 |
+
)
|
| 104 |
+
super().__init__(*args, **kwargs) # type: ignore
|
.venv/lib/python3.11/site-packages/zmq/eventloop/ioloop.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""tornado IOLoop API with zmq compatibility
|
| 2 |
+
|
| 3 |
+
This module is deprecated in pyzmq 17.
|
| 4 |
+
To use zmq with tornado,
|
| 5 |
+
eventloop integration is no longer required
|
| 6 |
+
and tornado itself should be used.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
# Copyright (C) PyZMQ Developers
|
| 10 |
+
# Distributed under the terms of the Modified BSD License.
|
| 11 |
+
|
| 12 |
+
import warnings
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def _deprecated():
|
| 16 |
+
warnings.warn(
|
| 17 |
+
"zmq.eventloop.ioloop is deprecated in pyzmq 17."
|
| 18 |
+
" pyzmq now works with default tornado and asyncio eventloops.",
|
| 19 |
+
DeprecationWarning,
|
| 20 |
+
stacklevel=3,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
_deprecated()
|
| 25 |
+
|
| 26 |
+
from tornado.ioloop import * # noqa
|
| 27 |
+
from tornado.ioloop import IOLoop
|
| 28 |
+
|
| 29 |
+
ZMQIOLoop = IOLoop
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def install():
|
| 33 |
+
"""DEPRECATED
|
| 34 |
+
|
| 35 |
+
pyzmq 17 no longer needs any special integration for tornado.
|
| 36 |
+
"""
|
| 37 |
+
_deprecated()
|
.venv/lib/python3.11/site-packages/zmq/eventloop/zmqstream.py
ADDED
|
@@ -0,0 +1,689 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Derived from iostream.py from tornado 1.0, Copyright 2009 Facebook
|
| 2 |
+
# Used under Apache License Version 2.0
|
| 3 |
+
#
|
| 4 |
+
# Modifications are Copyright (C) PyZMQ Developers
|
| 5 |
+
# Distributed under the terms of the Modified BSD License.
|
| 6 |
+
"""A utility class for event-based messaging on a zmq socket using tornado.
|
| 7 |
+
|
| 8 |
+
.. seealso::
|
| 9 |
+
|
| 10 |
+
- :mod:`zmq.asyncio`
|
| 11 |
+
- :mod:`zmq.eventloop.future`
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
import asyncio
|
| 17 |
+
import pickle
|
| 18 |
+
import warnings
|
| 19 |
+
from queue import Queue
|
| 20 |
+
from typing import Any, Awaitable, Callable, Sequence, cast, overload
|
| 21 |
+
|
| 22 |
+
from tornado.ioloop import IOLoop
|
| 23 |
+
from tornado.log import gen_log
|
| 24 |
+
|
| 25 |
+
import zmq
|
| 26 |
+
import zmq._future
|
| 27 |
+
from zmq import POLLIN, POLLOUT
|
| 28 |
+
from zmq._typing import Literal
|
| 29 |
+
from zmq.utils import jsonapi
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class ZMQStream:
|
| 33 |
+
"""A utility class to register callbacks when a zmq socket sends and receives
|
| 34 |
+
|
| 35 |
+
For use with tornado IOLoop.
|
| 36 |
+
|
| 37 |
+
There are three main methods
|
| 38 |
+
|
| 39 |
+
Methods:
|
| 40 |
+
|
| 41 |
+
* **on_recv(callback, copy=True):**
|
| 42 |
+
register a callback to be run every time the socket has something to receive
|
| 43 |
+
* **on_send(callback):**
|
| 44 |
+
register a callback to be run every time you call send
|
| 45 |
+
* **send_multipart(self, msg, flags=0, copy=False, callback=None):**
|
| 46 |
+
perform a send that will trigger the callback
|
| 47 |
+
if callback is passed, on_send is also called.
|
| 48 |
+
|
| 49 |
+
There are also send_multipart(), send_json(), send_pyobj()
|
| 50 |
+
|
| 51 |
+
Three other methods for deactivating the callbacks:
|
| 52 |
+
|
| 53 |
+
* **stop_on_recv():**
|
| 54 |
+
turn off the recv callback
|
| 55 |
+
* **stop_on_send():**
|
| 56 |
+
turn off the send callback
|
| 57 |
+
|
| 58 |
+
which simply call ``on_<evt>(None)``.
|
| 59 |
+
|
| 60 |
+
The entire socket interface, excluding direct recv methods, is also
|
| 61 |
+
provided, primarily through direct-linking the methods.
|
| 62 |
+
e.g.
|
| 63 |
+
|
| 64 |
+
>>> stream.bind is stream.socket.bind
|
| 65 |
+
True
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
.. versionadded:: 25
|
| 69 |
+
|
| 70 |
+
send/recv callbacks can be coroutines.
|
| 71 |
+
|
| 72 |
+
.. versionchanged:: 25
|
| 73 |
+
|
| 74 |
+
ZMQStreams only support base zmq.Socket classes (this has always been true, but not enforced).
|
| 75 |
+
If ZMQStreams are created with e.g. async Socket subclasses,
|
| 76 |
+
a RuntimeWarning will be shown,
|
| 77 |
+
and the socket cast back to the default zmq.Socket
|
| 78 |
+
before connecting events.
|
| 79 |
+
|
| 80 |
+
Previously, using async sockets (or any zmq.Socket subclass) would result in undefined behavior for the
|
| 81 |
+
arguments passed to callback functions.
|
| 82 |
+
Now, the callback functions reliably get the return value of the base `zmq.Socket` send/recv_multipart methods
|
| 83 |
+
(the list of message frames).
|
| 84 |
+
"""
|
| 85 |
+
|
| 86 |
+
socket: zmq.Socket
|
| 87 |
+
io_loop: IOLoop
|
| 88 |
+
poller: zmq.Poller
|
| 89 |
+
_send_queue: Queue
|
| 90 |
+
_recv_callback: Callable | None
|
| 91 |
+
_send_callback: Callable | None
|
| 92 |
+
_close_callback: Callable | None
|
| 93 |
+
_state: int = 0
|
| 94 |
+
_flushed: bool = False
|
| 95 |
+
_recv_copy: bool = False
|
| 96 |
+
_fd: int
|
| 97 |
+
|
| 98 |
+
def __init__(self, socket: zmq.Socket, io_loop: IOLoop | None = None):
|
| 99 |
+
if isinstance(socket, zmq._future._AsyncSocket):
|
| 100 |
+
warnings.warn(
|
| 101 |
+
f"""ZMQStream only supports the base zmq.Socket class.
|
| 102 |
+
|
| 103 |
+
Use zmq.Socket(shadow=other_socket)
|
| 104 |
+
or `ctx.socket(zmq.{socket._type_name}, socket_class=zmq.Socket)`
|
| 105 |
+
to create a base zmq.Socket object,
|
| 106 |
+
no matter what other kind of socket your Context creates.
|
| 107 |
+
""",
|
| 108 |
+
RuntimeWarning,
|
| 109 |
+
stacklevel=2,
|
| 110 |
+
)
|
| 111 |
+
# shadow back to base zmq.Socket,
|
| 112 |
+
# otherwise callbacks like `on_recv` will get the wrong types.
|
| 113 |
+
socket = zmq.Socket(shadow=socket)
|
| 114 |
+
self.socket = socket
|
| 115 |
+
|
| 116 |
+
# IOLoop.current() is deprecated if called outside the event loop
|
| 117 |
+
# that means
|
| 118 |
+
self.io_loop = io_loop or IOLoop.current()
|
| 119 |
+
self.poller = zmq.Poller()
|
| 120 |
+
self._fd = cast(int, self.socket.FD)
|
| 121 |
+
|
| 122 |
+
self._send_queue = Queue()
|
| 123 |
+
self._recv_callback = None
|
| 124 |
+
self._send_callback = None
|
| 125 |
+
self._close_callback = None
|
| 126 |
+
self._recv_copy = False
|
| 127 |
+
self._flushed = False
|
| 128 |
+
|
| 129 |
+
self._state = 0
|
| 130 |
+
self._init_io_state()
|
| 131 |
+
|
| 132 |
+
# shortcircuit some socket methods
|
| 133 |
+
self.bind = self.socket.bind
|
| 134 |
+
self.bind_to_random_port = self.socket.bind_to_random_port
|
| 135 |
+
self.connect = self.socket.connect
|
| 136 |
+
self.setsockopt = self.socket.setsockopt
|
| 137 |
+
self.getsockopt = self.socket.getsockopt
|
| 138 |
+
self.setsockopt_string = self.socket.setsockopt_string
|
| 139 |
+
self.getsockopt_string = self.socket.getsockopt_string
|
| 140 |
+
self.setsockopt_unicode = self.socket.setsockopt_unicode
|
| 141 |
+
self.getsockopt_unicode = self.socket.getsockopt_unicode
|
| 142 |
+
|
| 143 |
+
def stop_on_recv(self):
|
| 144 |
+
"""Disable callback and automatic receiving."""
|
| 145 |
+
return self.on_recv(None)
|
| 146 |
+
|
| 147 |
+
def stop_on_send(self):
|
| 148 |
+
"""Disable callback on sending."""
|
| 149 |
+
return self.on_send(None)
|
| 150 |
+
|
| 151 |
+
def stop_on_err(self):
|
| 152 |
+
"""DEPRECATED, does nothing"""
|
| 153 |
+
gen_log.warn("on_err does nothing, and will be removed")
|
| 154 |
+
|
| 155 |
+
def on_err(self, callback: Callable):
|
| 156 |
+
"""DEPRECATED, does nothing"""
|
| 157 |
+
gen_log.warn("on_err does nothing, and will be removed")
|
| 158 |
+
|
| 159 |
+
@overload
|
| 160 |
+
def on_recv(
|
| 161 |
+
self,
|
| 162 |
+
callback: Callable[[list[bytes]], Any],
|
| 163 |
+
) -> None: ...
|
| 164 |
+
|
| 165 |
+
@overload
|
| 166 |
+
def on_recv(
|
| 167 |
+
self,
|
| 168 |
+
callback: Callable[[list[bytes]], Any],
|
| 169 |
+
copy: Literal[True],
|
| 170 |
+
) -> None: ...
|
| 171 |
+
|
| 172 |
+
@overload
|
| 173 |
+
def on_recv(
|
| 174 |
+
self,
|
| 175 |
+
callback: Callable[[list[zmq.Frame]], Any],
|
| 176 |
+
copy: Literal[False],
|
| 177 |
+
) -> None: ...
|
| 178 |
+
|
| 179 |
+
@overload
|
| 180 |
+
def on_recv(
|
| 181 |
+
self,
|
| 182 |
+
callback: Callable[[list[zmq.Frame]], Any] | Callable[[list[bytes]], Any],
|
| 183 |
+
copy: bool = ...,
|
| 184 |
+
): ...
|
| 185 |
+
|
| 186 |
+
def on_recv(
|
| 187 |
+
self,
|
| 188 |
+
callback: Callable[[list[zmq.Frame]], Any] | Callable[[list[bytes]], Any],
|
| 189 |
+
copy: bool = True,
|
| 190 |
+
) -> None:
|
| 191 |
+
"""Register a callback for when a message is ready to recv.
|
| 192 |
+
|
| 193 |
+
There can be only one callback registered at a time, so each
|
| 194 |
+
call to `on_recv` replaces previously registered callbacks.
|
| 195 |
+
|
| 196 |
+
on_recv(None) disables recv event polling.
|
| 197 |
+
|
| 198 |
+
Use on_recv_stream(callback) instead, to register a callback that will receive
|
| 199 |
+
both this ZMQStream and the message, instead of just the message.
|
| 200 |
+
|
| 201 |
+
Parameters
|
| 202 |
+
----------
|
| 203 |
+
|
| 204 |
+
callback : callable
|
| 205 |
+
callback must take exactly one argument, which will be a
|
| 206 |
+
list, as returned by socket.recv_multipart()
|
| 207 |
+
if callback is None, recv callbacks are disabled.
|
| 208 |
+
copy : bool
|
| 209 |
+
copy is passed directly to recv, so if copy is False,
|
| 210 |
+
callback will receive Message objects. If copy is True,
|
| 211 |
+
then callback will receive bytes/str objects.
|
| 212 |
+
|
| 213 |
+
Returns : None
|
| 214 |
+
"""
|
| 215 |
+
|
| 216 |
+
self._check_closed()
|
| 217 |
+
assert callback is None or callable(callback)
|
| 218 |
+
self._recv_callback = callback
|
| 219 |
+
self._recv_copy = copy
|
| 220 |
+
if callback is None:
|
| 221 |
+
self._drop_io_state(zmq.POLLIN)
|
| 222 |
+
else:
|
| 223 |
+
self._add_io_state(zmq.POLLIN)
|
| 224 |
+
|
| 225 |
+
@overload
|
| 226 |
+
def on_recv_stream(
|
| 227 |
+
self,
|
| 228 |
+
callback: Callable[[ZMQStream, list[bytes]], Any],
|
| 229 |
+
) -> None: ...
|
| 230 |
+
|
| 231 |
+
@overload
|
| 232 |
+
def on_recv_stream(
|
| 233 |
+
self,
|
| 234 |
+
callback: Callable[[ZMQStream, list[bytes]], Any],
|
| 235 |
+
copy: Literal[True],
|
| 236 |
+
) -> None: ...
|
| 237 |
+
|
| 238 |
+
@overload
|
| 239 |
+
def on_recv_stream(
|
| 240 |
+
self,
|
| 241 |
+
callback: Callable[[ZMQStream, list[zmq.Frame]], Any],
|
| 242 |
+
copy: Literal[False],
|
| 243 |
+
) -> None: ...
|
| 244 |
+
|
| 245 |
+
@overload
|
| 246 |
+
def on_recv_stream(
|
| 247 |
+
self,
|
| 248 |
+
callback: (
|
| 249 |
+
Callable[[ZMQStream, list[zmq.Frame]], Any]
|
| 250 |
+
| Callable[[ZMQStream, list[bytes]], Any]
|
| 251 |
+
),
|
| 252 |
+
copy: bool = ...,
|
| 253 |
+
): ...
|
| 254 |
+
|
| 255 |
+
def on_recv_stream(
|
| 256 |
+
self,
|
| 257 |
+
callback: (
|
| 258 |
+
Callable[[ZMQStream, list[zmq.Frame]], Any]
|
| 259 |
+
| Callable[[ZMQStream, list[bytes]], Any]
|
| 260 |
+
),
|
| 261 |
+
copy: bool = True,
|
| 262 |
+
):
|
| 263 |
+
"""Same as on_recv, but callback will get this stream as first argument
|
| 264 |
+
|
| 265 |
+
callback must take exactly two arguments, as it will be called as::
|
| 266 |
+
|
| 267 |
+
callback(stream, msg)
|
| 268 |
+
|
| 269 |
+
Useful when a single callback should be used with multiple streams.
|
| 270 |
+
"""
|
| 271 |
+
if callback is None:
|
| 272 |
+
self.stop_on_recv()
|
| 273 |
+
else:
|
| 274 |
+
|
| 275 |
+
def stream_callback(msg):
|
| 276 |
+
return callback(self, msg)
|
| 277 |
+
|
| 278 |
+
self.on_recv(stream_callback, copy=copy)
|
| 279 |
+
|
| 280 |
+
def on_send(
|
| 281 |
+
self, callback: Callable[[Sequence[Any], zmq.MessageTracker | None], Any]
|
| 282 |
+
):
|
| 283 |
+
"""Register a callback to be called on each send
|
| 284 |
+
|
| 285 |
+
There will be two arguments::
|
| 286 |
+
|
| 287 |
+
callback(msg, status)
|
| 288 |
+
|
| 289 |
+
* `msg` will be the list of sendable objects that was just sent
|
| 290 |
+
* `status` will be the return result of socket.send_multipart(msg) -
|
| 291 |
+
MessageTracker or None.
|
| 292 |
+
|
| 293 |
+
Non-copying sends return a MessageTracker object whose
|
| 294 |
+
`done` attribute will be True when the send is complete.
|
| 295 |
+
This allows users to track when an object is safe to write to
|
| 296 |
+
again.
|
| 297 |
+
|
| 298 |
+
The second argument will always be None if copy=True
|
| 299 |
+
on the send.
|
| 300 |
+
|
| 301 |
+
Use on_send_stream(callback) to register a callback that will be passed
|
| 302 |
+
this ZMQStream as the first argument, in addition to the other two.
|
| 303 |
+
|
| 304 |
+
on_send(None) disables recv event polling.
|
| 305 |
+
|
| 306 |
+
Parameters
|
| 307 |
+
----------
|
| 308 |
+
|
| 309 |
+
callback : callable
|
| 310 |
+
callback must take exactly two arguments, which will be
|
| 311 |
+
the message being sent (always a list),
|
| 312 |
+
and the return result of socket.send_multipart(msg) -
|
| 313 |
+
MessageTracker or None.
|
| 314 |
+
|
| 315 |
+
if callback is None, send callbacks are disabled.
|
| 316 |
+
"""
|
| 317 |
+
|
| 318 |
+
self._check_closed()
|
| 319 |
+
assert callback is None or callable(callback)
|
| 320 |
+
self._send_callback = callback
|
| 321 |
+
|
| 322 |
+
def on_send_stream(
|
| 323 |
+
self,
|
| 324 |
+
callback: Callable[[ZMQStream, Sequence[Any], zmq.MessageTracker | None], Any],
|
| 325 |
+
):
|
| 326 |
+
"""Same as on_send, but callback will get this stream as first argument
|
| 327 |
+
|
| 328 |
+
Callback will be passed three arguments::
|
| 329 |
+
|
| 330 |
+
callback(stream, msg, status)
|
| 331 |
+
|
| 332 |
+
Useful when a single callback should be used with multiple streams.
|
| 333 |
+
"""
|
| 334 |
+
if callback is None:
|
| 335 |
+
self.stop_on_send()
|
| 336 |
+
else:
|
| 337 |
+
self.on_send(lambda msg, status: callback(self, msg, status))
|
| 338 |
+
|
| 339 |
+
def send(self, msg, flags=0, copy=True, track=False, callback=None, **kwargs):
|
| 340 |
+
"""Send a message, optionally also register a new callback for sends.
|
| 341 |
+
See zmq.socket.send for details.
|
| 342 |
+
"""
|
| 343 |
+
return self.send_multipart(
|
| 344 |
+
[msg], flags=flags, copy=copy, track=track, callback=callback, **kwargs
|
| 345 |
+
)
|
| 346 |
+
|
| 347 |
+
def send_multipart(
|
| 348 |
+
self,
|
| 349 |
+
msg: Sequence[Any],
|
| 350 |
+
flags: int = 0,
|
| 351 |
+
copy: bool = True,
|
| 352 |
+
track: bool = False,
|
| 353 |
+
callback: Callable | None = None,
|
| 354 |
+
**kwargs: Any,
|
| 355 |
+
) -> None:
|
| 356 |
+
"""Send a multipart message, optionally also register a new callback for sends.
|
| 357 |
+
See zmq.socket.send_multipart for details.
|
| 358 |
+
"""
|
| 359 |
+
kwargs.update(dict(flags=flags, copy=copy, track=track))
|
| 360 |
+
self._send_queue.put((msg, kwargs))
|
| 361 |
+
callback = callback or self._send_callback
|
| 362 |
+
if callback is not None:
|
| 363 |
+
self.on_send(callback)
|
| 364 |
+
else:
|
| 365 |
+
# noop callback
|
| 366 |
+
self.on_send(lambda *args: None)
|
| 367 |
+
self._add_io_state(zmq.POLLOUT)
|
| 368 |
+
|
| 369 |
+
def send_string(
|
| 370 |
+
self,
|
| 371 |
+
u: str,
|
| 372 |
+
flags: int = 0,
|
| 373 |
+
encoding: str = 'utf-8',
|
| 374 |
+
callback: Callable | None = None,
|
| 375 |
+
**kwargs: Any,
|
| 376 |
+
):
|
| 377 |
+
"""Send a unicode message with an encoding.
|
| 378 |
+
See zmq.socket.send_unicode for details.
|
| 379 |
+
"""
|
| 380 |
+
if not isinstance(u, str):
|
| 381 |
+
raise TypeError("unicode/str objects only")
|
| 382 |
+
return self.send(u.encode(encoding), flags=flags, callback=callback, **kwargs)
|
| 383 |
+
|
| 384 |
+
send_unicode = send_string
|
| 385 |
+
|
| 386 |
+
def send_json(
|
| 387 |
+
self,
|
| 388 |
+
obj: Any,
|
| 389 |
+
flags: int = 0,
|
| 390 |
+
callback: Callable | None = None,
|
| 391 |
+
**kwargs: Any,
|
| 392 |
+
):
|
| 393 |
+
"""Send json-serialized version of an object.
|
| 394 |
+
See zmq.socket.send_json for details.
|
| 395 |
+
"""
|
| 396 |
+
msg = jsonapi.dumps(obj)
|
| 397 |
+
return self.send(msg, flags=flags, callback=callback, **kwargs)
|
| 398 |
+
|
| 399 |
+
def send_pyobj(
|
| 400 |
+
self,
|
| 401 |
+
obj: Any,
|
| 402 |
+
flags: int = 0,
|
| 403 |
+
protocol: int = -1,
|
| 404 |
+
callback: Callable | None = None,
|
| 405 |
+
**kwargs: Any,
|
| 406 |
+
):
|
| 407 |
+
"""Send a Python object as a message using pickle to serialize.
|
| 408 |
+
|
| 409 |
+
See zmq.socket.send_json for details.
|
| 410 |
+
"""
|
| 411 |
+
msg = pickle.dumps(obj, protocol)
|
| 412 |
+
return self.send(msg, flags, callback=callback, **kwargs)
|
| 413 |
+
|
| 414 |
+
def _finish_flush(self):
|
| 415 |
+
"""callback for unsetting _flushed flag."""
|
| 416 |
+
self._flushed = False
|
| 417 |
+
|
| 418 |
+
def flush(self, flag: int = zmq.POLLIN | zmq.POLLOUT, limit: int | None = None):
|
| 419 |
+
"""Flush pending messages.
|
| 420 |
+
|
| 421 |
+
This method safely handles all pending incoming and/or outgoing messages,
|
| 422 |
+
bypassing the inner loop, passing them to the registered callbacks.
|
| 423 |
+
|
| 424 |
+
A limit can be specified, to prevent blocking under high load.
|
| 425 |
+
|
| 426 |
+
flush will return the first time ANY of these conditions are met:
|
| 427 |
+
* No more events matching the flag are pending.
|
| 428 |
+
* the total number of events handled reaches the limit.
|
| 429 |
+
|
| 430 |
+
Note that if ``flag|POLLIN != 0``, recv events will be flushed even if no callback
|
| 431 |
+
is registered, unlike normal IOLoop operation. This allows flush to be
|
| 432 |
+
used to remove *and ignore* incoming messages.
|
| 433 |
+
|
| 434 |
+
Parameters
|
| 435 |
+
----------
|
| 436 |
+
flag : int
|
| 437 |
+
default=POLLIN|POLLOUT
|
| 438 |
+
0MQ poll flags.
|
| 439 |
+
If flag|POLLIN, recv events will be flushed.
|
| 440 |
+
If flag|POLLOUT, send events will be flushed.
|
| 441 |
+
Both flags can be set at once, which is the default.
|
| 442 |
+
limit : None or int, optional
|
| 443 |
+
The maximum number of messages to send or receive.
|
| 444 |
+
Both send and recv count against this limit.
|
| 445 |
+
|
| 446 |
+
Returns
|
| 447 |
+
-------
|
| 448 |
+
int :
|
| 449 |
+
count of events handled (both send and recv)
|
| 450 |
+
"""
|
| 451 |
+
self._check_closed()
|
| 452 |
+
# unset self._flushed, so callbacks will execute, in case flush has
|
| 453 |
+
# already been called this iteration
|
| 454 |
+
already_flushed = self._flushed
|
| 455 |
+
self._flushed = False
|
| 456 |
+
# initialize counters
|
| 457 |
+
count = 0
|
| 458 |
+
|
| 459 |
+
def update_flag():
|
| 460 |
+
"""Update the poll flag, to prevent registering POLLOUT events
|
| 461 |
+
if we don't have pending sends."""
|
| 462 |
+
return flag & zmq.POLLIN | (self.sending() and flag & zmq.POLLOUT)
|
| 463 |
+
|
| 464 |
+
flag = update_flag()
|
| 465 |
+
if not flag:
|
| 466 |
+
# nothing to do
|
| 467 |
+
return 0
|
| 468 |
+
self.poller.register(self.socket, flag)
|
| 469 |
+
events = self.poller.poll(0)
|
| 470 |
+
while events and (not limit or count < limit):
|
| 471 |
+
s, event = events[0]
|
| 472 |
+
if event & POLLIN: # receiving
|
| 473 |
+
self._handle_recv()
|
| 474 |
+
count += 1
|
| 475 |
+
if self.socket is None:
|
| 476 |
+
# break if socket was closed during callback
|
| 477 |
+
break
|
| 478 |
+
if event & POLLOUT and self.sending():
|
| 479 |
+
self._handle_send()
|
| 480 |
+
count += 1
|
| 481 |
+
if self.socket is None:
|
| 482 |
+
# break if socket was closed during callback
|
| 483 |
+
break
|
| 484 |
+
|
| 485 |
+
flag = update_flag()
|
| 486 |
+
if flag:
|
| 487 |
+
self.poller.register(self.socket, flag)
|
| 488 |
+
events = self.poller.poll(0)
|
| 489 |
+
else:
|
| 490 |
+
events = []
|
| 491 |
+
if count: # only bypass loop if we actually flushed something
|
| 492 |
+
# skip send/recv callbacks this iteration
|
| 493 |
+
self._flushed = True
|
| 494 |
+
# reregister them at the end of the loop
|
| 495 |
+
if not already_flushed: # don't need to do it again
|
| 496 |
+
self.io_loop.add_callback(self._finish_flush)
|
| 497 |
+
elif already_flushed:
|
| 498 |
+
self._flushed = True
|
| 499 |
+
|
| 500 |
+
# update ioloop poll state, which may have changed
|
| 501 |
+
self._rebuild_io_state()
|
| 502 |
+
return count
|
| 503 |
+
|
| 504 |
+
def set_close_callback(self, callback: Callable | None):
|
| 505 |
+
"""Call the given callback when the stream is closed."""
|
| 506 |
+
self._close_callback = callback
|
| 507 |
+
|
| 508 |
+
def close(self, linger: int | None = None) -> None:
|
| 509 |
+
"""Close this stream."""
|
| 510 |
+
if self.socket is not None:
|
| 511 |
+
if self.socket.closed:
|
| 512 |
+
# fallback on raw fd for closed sockets
|
| 513 |
+
# hopefully this happened promptly after close,
|
| 514 |
+
# otherwise somebody else may have the FD
|
| 515 |
+
warnings.warn(
|
| 516 |
+
f"Unregistering FD {self._fd} after closing socket. "
|
| 517 |
+
"This could result in unregistering handlers for the wrong socket. "
|
| 518 |
+
"Please use stream.close() instead of closing the socket directly.",
|
| 519 |
+
stacklevel=2,
|
| 520 |
+
)
|
| 521 |
+
self.io_loop.remove_handler(self._fd)
|
| 522 |
+
else:
|
| 523 |
+
self.io_loop.remove_handler(self.socket)
|
| 524 |
+
self.socket.close(linger)
|
| 525 |
+
self.socket = None # type: ignore
|
| 526 |
+
if self._close_callback:
|
| 527 |
+
self._run_callback(self._close_callback)
|
| 528 |
+
|
| 529 |
+
def receiving(self) -> bool:
|
| 530 |
+
"""Returns True if we are currently receiving from the stream."""
|
| 531 |
+
return self._recv_callback is not None
|
| 532 |
+
|
| 533 |
+
def sending(self) -> bool:
|
| 534 |
+
"""Returns True if we are currently sending to the stream."""
|
| 535 |
+
return not self._send_queue.empty()
|
| 536 |
+
|
| 537 |
+
def closed(self) -> bool:
|
| 538 |
+
if self.socket is None:
|
| 539 |
+
return True
|
| 540 |
+
if self.socket.closed:
|
| 541 |
+
# underlying socket has been closed, but not by us!
|
| 542 |
+
# trigger our cleanup
|
| 543 |
+
self.close()
|
| 544 |
+
return True
|
| 545 |
+
return False
|
| 546 |
+
|
| 547 |
+
def _run_callback(self, callback, *args, **kwargs):
|
| 548 |
+
"""Wrap running callbacks in try/except to allow us to
|
| 549 |
+
close our socket."""
|
| 550 |
+
try:
|
| 551 |
+
f = callback(*args, **kwargs)
|
| 552 |
+
if isinstance(f, Awaitable):
|
| 553 |
+
f = asyncio.ensure_future(f)
|
| 554 |
+
else:
|
| 555 |
+
f = None
|
| 556 |
+
except Exception:
|
| 557 |
+
gen_log.error("Uncaught exception in ZMQStream callback", exc_info=True)
|
| 558 |
+
# Re-raise the exception so that IOLoop.handle_callback_exception
|
| 559 |
+
# can see it and log the error
|
| 560 |
+
raise
|
| 561 |
+
|
| 562 |
+
if f is not None:
|
| 563 |
+
# handle async callbacks
|
| 564 |
+
def _log_error(f):
|
| 565 |
+
try:
|
| 566 |
+
f.result()
|
| 567 |
+
except Exception:
|
| 568 |
+
gen_log.error(
|
| 569 |
+
"Uncaught exception in ZMQStream callback", exc_info=True
|
| 570 |
+
)
|
| 571 |
+
|
| 572 |
+
f.add_done_callback(_log_error)
|
| 573 |
+
|
| 574 |
+
def _handle_events(self, fd, events):
|
| 575 |
+
"""This method is the actual handler for IOLoop, that gets called whenever
|
| 576 |
+
an event on my socket is posted. It dispatches to _handle_recv, etc."""
|
| 577 |
+
if not self.socket:
|
| 578 |
+
gen_log.warning("Got events for closed stream %s", self)
|
| 579 |
+
return
|
| 580 |
+
try:
|
| 581 |
+
zmq_events = self.socket.EVENTS
|
| 582 |
+
except zmq.ContextTerminated:
|
| 583 |
+
gen_log.warning("Got events for stream %s after terminating context", self)
|
| 584 |
+
# trigger close check, this will unregister callbacks
|
| 585 |
+
self.closed()
|
| 586 |
+
return
|
| 587 |
+
except zmq.ZMQError as e:
|
| 588 |
+
# run close check
|
| 589 |
+
# shadow sockets may have been closed elsewhere,
|
| 590 |
+
# which should show up as ENOTSOCK here
|
| 591 |
+
if self.closed():
|
| 592 |
+
gen_log.warning(
|
| 593 |
+
"Got events for stream %s attached to closed socket: %s", self, e
|
| 594 |
+
)
|
| 595 |
+
else:
|
| 596 |
+
gen_log.error("Error getting events for %s: %s", self, e)
|
| 597 |
+
return
|
| 598 |
+
try:
|
| 599 |
+
# dispatch events:
|
| 600 |
+
if zmq_events & zmq.POLLIN and self.receiving():
|
| 601 |
+
self._handle_recv()
|
| 602 |
+
if not self.socket:
|
| 603 |
+
return
|
| 604 |
+
if zmq_events & zmq.POLLOUT and self.sending():
|
| 605 |
+
self._handle_send()
|
| 606 |
+
if not self.socket:
|
| 607 |
+
return
|
| 608 |
+
|
| 609 |
+
# rebuild the poll state
|
| 610 |
+
self._rebuild_io_state()
|
| 611 |
+
except Exception:
|
| 612 |
+
gen_log.error("Uncaught exception in zmqstream callback", exc_info=True)
|
| 613 |
+
raise
|
| 614 |
+
|
| 615 |
+
def _handle_recv(self):
|
| 616 |
+
"""Handle a recv event."""
|
| 617 |
+
if self._flushed:
|
| 618 |
+
return
|
| 619 |
+
try:
|
| 620 |
+
msg = self.socket.recv_multipart(zmq.NOBLOCK, copy=self._recv_copy)
|
| 621 |
+
except zmq.ZMQError as e:
|
| 622 |
+
if e.errno == zmq.EAGAIN:
|
| 623 |
+
# state changed since poll event
|
| 624 |
+
pass
|
| 625 |
+
else:
|
| 626 |
+
raise
|
| 627 |
+
else:
|
| 628 |
+
if self._recv_callback:
|
| 629 |
+
callback = self._recv_callback
|
| 630 |
+
self._run_callback(callback, msg)
|
| 631 |
+
|
| 632 |
+
def _handle_send(self):
|
| 633 |
+
"""Handle a send event."""
|
| 634 |
+
if self._flushed:
|
| 635 |
+
return
|
| 636 |
+
if not self.sending():
|
| 637 |
+
gen_log.error("Shouldn't have handled a send event")
|
| 638 |
+
return
|
| 639 |
+
|
| 640 |
+
msg, kwargs = self._send_queue.get()
|
| 641 |
+
try:
|
| 642 |
+
status = self.socket.send_multipart(msg, **kwargs)
|
| 643 |
+
except zmq.ZMQError as e:
|
| 644 |
+
gen_log.error("SEND Error: %s", e)
|
| 645 |
+
status = e
|
| 646 |
+
if self._send_callback:
|
| 647 |
+
callback = self._send_callback
|
| 648 |
+
self._run_callback(callback, msg, status)
|
| 649 |
+
|
| 650 |
+
def _check_closed(self):
|
| 651 |
+
if not self.socket:
|
| 652 |
+
raise OSError("Stream is closed")
|
| 653 |
+
|
| 654 |
+
def _rebuild_io_state(self):
|
| 655 |
+
"""rebuild io state based on self.sending() and receiving()"""
|
| 656 |
+
if self.socket is None:
|
| 657 |
+
return
|
| 658 |
+
state = 0
|
| 659 |
+
if self.receiving():
|
| 660 |
+
state |= zmq.POLLIN
|
| 661 |
+
if self.sending():
|
| 662 |
+
state |= zmq.POLLOUT
|
| 663 |
+
|
| 664 |
+
self._state = state
|
| 665 |
+
self._update_handler(state)
|
| 666 |
+
|
| 667 |
+
def _add_io_state(self, state):
|
| 668 |
+
"""Add io_state to poller."""
|
| 669 |
+
self._state = self._state | state
|
| 670 |
+
self._update_handler(self._state)
|
| 671 |
+
|
| 672 |
+
def _drop_io_state(self, state):
|
| 673 |
+
"""Stop poller from watching an io_state."""
|
| 674 |
+
self._state = self._state & (~state)
|
| 675 |
+
self._update_handler(self._state)
|
| 676 |
+
|
| 677 |
+
def _update_handler(self, state):
|
| 678 |
+
"""Update IOLoop handler with state."""
|
| 679 |
+
if self.socket is None:
|
| 680 |
+
return
|
| 681 |
+
|
| 682 |
+
if state & self.socket.events:
|
| 683 |
+
# events still exist that haven't been processed
|
| 684 |
+
# explicitly schedule handling to avoid missing events due to edge-triggered FDs
|
| 685 |
+
self.io_loop.add_callback(lambda: self._handle_events(self.socket, 0))
|
| 686 |
+
|
| 687 |
+
def _init_io_state(self):
|
| 688 |
+
"""initialize the ioloop event handler"""
|
| 689 |
+
self.io_loop.add_handler(self.socket, self._handle_events, self.io_loop.READ)
|
.venv/lib/python3.11/site-packages/zmq/green/__init__.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -----------------------------------------------------------------------------
|
| 2 |
+
# Copyright (C) 2011-2012 Travis Cline
|
| 3 |
+
#
|
| 4 |
+
# This file is part of pyzmq
|
| 5 |
+
# It is adapted from upstream project zeromq_gevent under the New BSD License
|
| 6 |
+
#
|
| 7 |
+
# Distributed under the terms of the New BSD License. The full license is in
|
| 8 |
+
# the file LICENSE.BSD, distributed as part of this software.
|
| 9 |
+
# -----------------------------------------------------------------------------
|
| 10 |
+
|
| 11 |
+
"""zmq.green - gevent compatibility with zeromq.
|
| 12 |
+
|
| 13 |
+
Usage
|
| 14 |
+
-----
|
| 15 |
+
|
| 16 |
+
Instead of importing zmq directly, do so in the following manner:
|
| 17 |
+
|
| 18 |
+
..
|
| 19 |
+
|
| 20 |
+
import zmq.green as zmq
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
Any calls that would have blocked the current thread will now only block the
|
| 24 |
+
current green thread.
|
| 25 |
+
|
| 26 |
+
This compatibility is accomplished by ensuring the nonblocking flag is set
|
| 27 |
+
before any blocking operation and the ØMQ file descriptor is polled internally
|
| 28 |
+
to trigger needed events.
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
from __future__ import annotations
|
| 32 |
+
|
| 33 |
+
from typing import List
|
| 34 |
+
|
| 35 |
+
import zmq as _zmq
|
| 36 |
+
from zmq import *
|
| 37 |
+
from zmq.green.core import _Context, _Socket
|
| 38 |
+
from zmq.green.poll import _Poller
|
| 39 |
+
|
| 40 |
+
Context = _Context # type: ignore
|
| 41 |
+
Socket = _Socket # type: ignore
|
| 42 |
+
Poller = _Poller # type: ignore
|
| 43 |
+
|
| 44 |
+
from zmq.green.device import device # type: ignore
|
| 45 |
+
|
| 46 |
+
__all__: list[str] = []
|
| 47 |
+
# adding `__all__` to __init__.pyi gets mypy all confused
|
| 48 |
+
__all__.extend(_zmq.__all__) # type: ignore
|
.venv/lib/python3.11/site-packages/zmq/green/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (1.25 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/green/__pycache__/core.cpython-311.pyc
ADDED
|
Binary file (14.5 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/green/__pycache__/device.cpython-311.pyc
ADDED
|
Binary file (1.54 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/green/__pycache__/poll.cpython-311.pyc
ADDED
|
Binary file (4.29 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/zmq/green/core.py
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -----------------------------------------------------------------------------
|
| 2 |
+
# Copyright (C) 2011-2012 Travis Cline
|
| 3 |
+
#
|
| 4 |
+
# This file is part of pyzmq
|
| 5 |
+
# It is adapted from upstream project zeromq_gevent under the New BSD License
|
| 6 |
+
#
|
| 7 |
+
# Distributed under the terms of the New BSD License. The full license is in
|
| 8 |
+
# the file LICENSE.BSD, distributed as part of this software.
|
| 9 |
+
# -----------------------------------------------------------------------------
|
| 10 |
+
|
| 11 |
+
"""This module wraps the :class:`Socket` and :class:`Context` found in :mod:`pyzmq <zmq>` to be non blocking"""
|
| 12 |
+
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
|
| 15 |
+
import sys
|
| 16 |
+
import time
|
| 17 |
+
import warnings
|
| 18 |
+
|
| 19 |
+
import gevent
|
| 20 |
+
from gevent.event import AsyncResult
|
| 21 |
+
from gevent.hub import get_hub
|
| 22 |
+
|
| 23 |
+
import zmq
|
| 24 |
+
from zmq import Context as _original_Context
|
| 25 |
+
from zmq import Socket as _original_Socket
|
| 26 |
+
|
| 27 |
+
from .poll import _Poller
|
| 28 |
+
|
| 29 |
+
if hasattr(zmq, 'RCVTIMEO'):
|
| 30 |
+
TIMEOS: tuple = (zmq.RCVTIMEO, zmq.SNDTIMEO)
|
| 31 |
+
else:
|
| 32 |
+
TIMEOS = ()
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def _stop(evt):
|
| 36 |
+
"""simple wrapper for stopping an Event, allowing for method rename in gevent 1.0"""
|
| 37 |
+
try:
|
| 38 |
+
evt.stop()
|
| 39 |
+
except AttributeError:
|
| 40 |
+
# gevent<1.0 compat
|
| 41 |
+
evt.cancel()
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class _Socket(_original_Socket):
|
| 45 |
+
"""Green version of :class:`zmq.Socket`
|
| 46 |
+
|
| 47 |
+
The following methods are overridden:
|
| 48 |
+
|
| 49 |
+
* send
|
| 50 |
+
* recv
|
| 51 |
+
|
| 52 |
+
To ensure that the ``zmq.NOBLOCK`` flag is set and that sending or receiving
|
| 53 |
+
is deferred to the hub if a ``zmq.EAGAIN`` (retry) error is raised.
|
| 54 |
+
|
| 55 |
+
The `__state_changed` method is triggered when the zmq.FD for the socket is
|
| 56 |
+
marked as readable and triggers the necessary read and write events (which
|
| 57 |
+
are waited for in the recv and send methods).
|
| 58 |
+
|
| 59 |
+
Some double underscore prefixes are used to minimize pollution of
|
| 60 |
+
:class:`zmq.Socket`'s namespace.
|
| 61 |
+
"""
|
| 62 |
+
|
| 63 |
+
__in_send_multipart = False
|
| 64 |
+
__in_recv_multipart = False
|
| 65 |
+
__writable = None
|
| 66 |
+
__readable = None
|
| 67 |
+
_state_event = None
|
| 68 |
+
_gevent_bug_timeout = 11.6 # timeout for not trusting gevent
|
| 69 |
+
_debug_gevent = False # turn on if you think gevent is missing events
|
| 70 |
+
_poller_class = _Poller
|
| 71 |
+
_repr_cls = "zmq.green.Socket"
|
| 72 |
+
|
| 73 |
+
def __init__(self, *a, **kw):
|
| 74 |
+
super().__init__(*a, **kw)
|
| 75 |
+
self.__in_send_multipart = False
|
| 76 |
+
self.__in_recv_multipart = False
|
| 77 |
+
self.__setup_events()
|
| 78 |
+
|
| 79 |
+
def __del__(self):
|
| 80 |
+
self.close()
|
| 81 |
+
|
| 82 |
+
def close(self, linger=None):
|
| 83 |
+
super().close(linger)
|
| 84 |
+
self.__cleanup_events()
|
| 85 |
+
|
| 86 |
+
def __cleanup_events(self):
|
| 87 |
+
# close the _state_event event, keeps the number of active file descriptors down
|
| 88 |
+
if getattr(self, '_state_event', None):
|
| 89 |
+
_stop(self._state_event)
|
| 90 |
+
self._state_event = None
|
| 91 |
+
# if the socket has entered a close state resume any waiting greenlets
|
| 92 |
+
self.__writable.set()
|
| 93 |
+
self.__readable.set()
|
| 94 |
+
|
| 95 |
+
def __setup_events(self):
|
| 96 |
+
self.__readable = AsyncResult()
|
| 97 |
+
self.__writable = AsyncResult()
|
| 98 |
+
self.__readable.set()
|
| 99 |
+
self.__writable.set()
|
| 100 |
+
|
| 101 |
+
try:
|
| 102 |
+
self._state_event = get_hub().loop.io(
|
| 103 |
+
self.getsockopt(zmq.FD), 1
|
| 104 |
+
) # read state watcher
|
| 105 |
+
self._state_event.start(self.__state_changed)
|
| 106 |
+
except AttributeError:
|
| 107 |
+
# for gevent<1.0 compatibility
|
| 108 |
+
from gevent.core import read_event
|
| 109 |
+
|
| 110 |
+
self._state_event = read_event(
|
| 111 |
+
self.getsockopt(zmq.FD), self.__state_changed, persist=True
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
def __state_changed(self, event=None, _evtype=None):
|
| 115 |
+
if self.closed:
|
| 116 |
+
self.__cleanup_events()
|
| 117 |
+
return
|
| 118 |
+
try:
|
| 119 |
+
# avoid triggering __state_changed from inside __state_changed
|
| 120 |
+
events = super().getsockopt(zmq.EVENTS)
|
| 121 |
+
except zmq.ZMQError as exc:
|
| 122 |
+
self.__writable.set_exception(exc)
|
| 123 |
+
self.__readable.set_exception(exc)
|
| 124 |
+
else:
|
| 125 |
+
if events & zmq.POLLOUT:
|
| 126 |
+
self.__writable.set()
|
| 127 |
+
if events & zmq.POLLIN:
|
| 128 |
+
self.__readable.set()
|
| 129 |
+
|
| 130 |
+
def _wait_write(self):
|
| 131 |
+
assert self.__writable.ready(), "Only one greenlet can be waiting on this event"
|
| 132 |
+
self.__writable = AsyncResult()
|
| 133 |
+
# timeout is because libzmq cannot be trusted to properly signal a new send event:
|
| 134 |
+
# this is effectively a maximum poll interval of 1s
|
| 135 |
+
tic = time.time()
|
| 136 |
+
dt = self._gevent_bug_timeout
|
| 137 |
+
if dt:
|
| 138 |
+
timeout = gevent.Timeout(seconds=dt)
|
| 139 |
+
else:
|
| 140 |
+
timeout = None
|
| 141 |
+
try:
|
| 142 |
+
if timeout:
|
| 143 |
+
timeout.start()
|
| 144 |
+
self.__writable.get(block=True)
|
| 145 |
+
except gevent.Timeout as t:
|
| 146 |
+
if t is not timeout:
|
| 147 |
+
raise
|
| 148 |
+
toc = time.time()
|
| 149 |
+
# gevent bug: get can raise timeout even on clean return
|
| 150 |
+
# don't display zmq bug warning for gevent bug (this is getting ridiculous)
|
| 151 |
+
if (
|
| 152 |
+
self._debug_gevent
|
| 153 |
+
and timeout
|
| 154 |
+
and toc - tic > dt
|
| 155 |
+
and self.getsockopt(zmq.EVENTS) & zmq.POLLOUT
|
| 156 |
+
):
|
| 157 |
+
print(
|
| 158 |
+
f"BUG: gevent may have missed a libzmq send event on {self.FD}!",
|
| 159 |
+
file=sys.stderr,
|
| 160 |
+
)
|
| 161 |
+
finally:
|
| 162 |
+
if timeout:
|
| 163 |
+
timeout.close()
|
| 164 |
+
self.__writable.set()
|
| 165 |
+
|
| 166 |
+
def _wait_read(self):
|
| 167 |
+
assert self.__readable.ready(), "Only one greenlet can be waiting on this event"
|
| 168 |
+
self.__readable = AsyncResult()
|
| 169 |
+
# timeout is because libzmq cannot always be trusted to play nice with libevent.
|
| 170 |
+
# I can only confirm that this actually happens for send, but lets be symmetrical
|
| 171 |
+
# with our dirty hacks.
|
| 172 |
+
# this is effectively a maximum poll interval of 1s
|
| 173 |
+
tic = time.time()
|
| 174 |
+
dt = self._gevent_bug_timeout
|
| 175 |
+
if dt:
|
| 176 |
+
timeout = gevent.Timeout(seconds=dt)
|
| 177 |
+
else:
|
| 178 |
+
timeout = None
|
| 179 |
+
try:
|
| 180 |
+
if timeout:
|
| 181 |
+
timeout.start()
|
| 182 |
+
self.__readable.get(block=True)
|
| 183 |
+
except gevent.Timeout as t:
|
| 184 |
+
if t is not timeout:
|
| 185 |
+
raise
|
| 186 |
+
toc = time.time()
|
| 187 |
+
# gevent bug: get can raise timeout even on clean return
|
| 188 |
+
# don't display zmq bug warning for gevent bug (this is getting ridiculous)
|
| 189 |
+
if (
|
| 190 |
+
self._debug_gevent
|
| 191 |
+
and timeout
|
| 192 |
+
and toc - tic > dt
|
| 193 |
+
and self.getsockopt(zmq.EVENTS) & zmq.POLLIN
|
| 194 |
+
):
|
| 195 |
+
print(
|
| 196 |
+
f"BUG: gevent may have missed a libzmq recv event on {self.FD}!",
|
| 197 |
+
file=sys.stderr,
|
| 198 |
+
)
|
| 199 |
+
finally:
|
| 200 |
+
if timeout:
|
| 201 |
+
timeout.close()
|
| 202 |
+
self.__readable.set()
|
| 203 |
+
|
| 204 |
+
def send(self, data, flags=0, copy=True, track=False, **kwargs):
|
| 205 |
+
"""send, which will only block current greenlet
|
| 206 |
+
|
| 207 |
+
state_changed always fires exactly once (success or fail) at the
|
| 208 |
+
end of this method.
|
| 209 |
+
"""
|
| 210 |
+
|
| 211 |
+
# if we're given the NOBLOCK flag act as normal and let the EAGAIN get raised
|
| 212 |
+
if flags & zmq.NOBLOCK:
|
| 213 |
+
try:
|
| 214 |
+
msg = super().send(data, flags, copy, track, **kwargs)
|
| 215 |
+
finally:
|
| 216 |
+
if not self.__in_send_multipart:
|
| 217 |
+
self.__state_changed()
|
| 218 |
+
return msg
|
| 219 |
+
# ensure the zmq.NOBLOCK flag is part of flags
|
| 220 |
+
flags |= zmq.NOBLOCK
|
| 221 |
+
while True: # Attempt to complete this operation indefinitely, blocking the current greenlet
|
| 222 |
+
try:
|
| 223 |
+
# attempt the actual call
|
| 224 |
+
msg = super().send(data, flags, copy, track)
|
| 225 |
+
except zmq.ZMQError as e:
|
| 226 |
+
# if the raised ZMQError is not EAGAIN, reraise
|
| 227 |
+
if e.errno != zmq.EAGAIN:
|
| 228 |
+
if not self.__in_send_multipart:
|
| 229 |
+
self.__state_changed()
|
| 230 |
+
raise
|
| 231 |
+
else:
|
| 232 |
+
if not self.__in_send_multipart:
|
| 233 |
+
self.__state_changed()
|
| 234 |
+
return msg
|
| 235 |
+
# defer to the event loop until we're notified the socket is writable
|
| 236 |
+
self._wait_write()
|
| 237 |
+
|
| 238 |
+
def recv(self, flags=0, copy=True, track=False):
|
| 239 |
+
"""recv, which will only block current greenlet
|
| 240 |
+
|
| 241 |
+
state_changed always fires exactly once (success or fail) at the
|
| 242 |
+
end of this method.
|
| 243 |
+
"""
|
| 244 |
+
if flags & zmq.NOBLOCK:
|
| 245 |
+
try:
|
| 246 |
+
msg = super().recv(flags, copy, track)
|
| 247 |
+
finally:
|
| 248 |
+
if not self.__in_recv_multipart:
|
| 249 |
+
self.__state_changed()
|
| 250 |
+
return msg
|
| 251 |
+
|
| 252 |
+
flags |= zmq.NOBLOCK
|
| 253 |
+
while True:
|
| 254 |
+
try:
|
| 255 |
+
msg = super().recv(flags, copy, track)
|
| 256 |
+
except zmq.ZMQError as e:
|
| 257 |
+
if e.errno != zmq.EAGAIN:
|
| 258 |
+
if not self.__in_recv_multipart:
|
| 259 |
+
self.__state_changed()
|
| 260 |
+
raise
|
| 261 |
+
else:
|
| 262 |
+
if not self.__in_recv_multipart:
|
| 263 |
+
self.__state_changed()
|
| 264 |
+
return msg
|
| 265 |
+
self._wait_read()
|
| 266 |
+
|
| 267 |
+
def send_multipart(self, *args, **kwargs):
|
| 268 |
+
"""wrap send_multipart to prevent state_changed on each partial send"""
|
| 269 |
+
self.__in_send_multipart = True
|
| 270 |
+
try:
|
| 271 |
+
msg = super().send_multipart(*args, **kwargs)
|
| 272 |
+
finally:
|
| 273 |
+
self.__in_send_multipart = False
|
| 274 |
+
self.__state_changed()
|
| 275 |
+
return msg
|
| 276 |
+
|
| 277 |
+
def recv_multipart(self, *args, **kwargs):
|
| 278 |
+
"""wrap recv_multipart to prevent state_changed on each partial recv"""
|
| 279 |
+
self.__in_recv_multipart = True
|
| 280 |
+
try:
|
| 281 |
+
msg = super().recv_multipart(*args, **kwargs)
|
| 282 |
+
finally:
|
| 283 |
+
self.__in_recv_multipart = False
|
| 284 |
+
self.__state_changed()
|
| 285 |
+
return msg
|
| 286 |
+
|
| 287 |
+
def get(self, opt):
|
| 288 |
+
"""trigger state_changed on getsockopt(EVENTS)"""
|
| 289 |
+
if opt in TIMEOS:
|
| 290 |
+
warnings.warn(
|
| 291 |
+
"TIMEO socket options have no effect in zmq.green", UserWarning
|
| 292 |
+
)
|
| 293 |
+
optval = super().get(opt)
|
| 294 |
+
if opt == zmq.EVENTS:
|
| 295 |
+
self.__state_changed()
|
| 296 |
+
return optval
|
| 297 |
+
|
| 298 |
+
def set(self, opt, val):
|
| 299 |
+
"""set socket option"""
|
| 300 |
+
if opt in TIMEOS:
|
| 301 |
+
warnings.warn(
|
| 302 |
+
"TIMEO socket options have no effect in zmq.green", UserWarning
|
| 303 |
+
)
|
| 304 |
+
return super().set(opt, val)
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
class _Context(_original_Context[_Socket]):
|
| 308 |
+
"""Replacement for :class:`zmq.Context`
|
| 309 |
+
|
| 310 |
+
Ensures that the greened Socket above is used in calls to `socket`.
|
| 311 |
+
"""
|
| 312 |
+
|
| 313 |
+
_socket_class = _Socket
|
| 314 |
+
_repr_cls = "zmq.green.Context"
|
| 315 |
+
|
| 316 |
+
# avoid sharing instance with base Context class
|
| 317 |
+
_instance = None
|
.venv/lib/python3.11/site-packages/zmq/green/device.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (C) PyZMQ Developers
|
| 2 |
+
# Distributed under the terms of the Modified BSD License.
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import zmq
|
| 6 |
+
from zmq.green import Poller
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def device(device_type, isocket, osocket):
|
| 10 |
+
"""Start a zeromq device (gevent-compatible).
|
| 11 |
+
|
| 12 |
+
Unlike the true zmq.device, this does not release the GIL.
|
| 13 |
+
|
| 14 |
+
Parameters
|
| 15 |
+
----------
|
| 16 |
+
device_type : (QUEUE, FORWARDER, STREAMER)
|
| 17 |
+
The type of device to start (ignored).
|
| 18 |
+
isocket : Socket
|
| 19 |
+
The Socket instance for the incoming traffic.
|
| 20 |
+
osocket : Socket
|
| 21 |
+
The Socket instance for the outbound traffic.
|
| 22 |
+
"""
|
| 23 |
+
p = Poller()
|
| 24 |
+
if osocket == -1:
|
| 25 |
+
osocket = isocket
|
| 26 |
+
p.register(isocket, zmq.POLLIN)
|
| 27 |
+
p.register(osocket, zmq.POLLIN)
|
| 28 |
+
|
| 29 |
+
while True:
|
| 30 |
+
events = dict(p.poll())
|
| 31 |
+
if isocket in events:
|
| 32 |
+
osocket.send_multipart(isocket.recv_multipart())
|
| 33 |
+
if osocket in events:
|
| 34 |
+
isocket.send_multipart(osocket.recv_multipart())
|