diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/__init__.py b/.venv/lib/python3.11/site-packages/zmq/backend/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..15f696d9d3ad5275443eb3145b9c4087f1e6609f --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/__init__.py @@ -0,0 +1,34 @@ +"""Import basic exposure of libzmq C API as a backend""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. + +import os +import platform + +from .select import public_api, select_backend + +if 'PYZMQ_BACKEND' in os.environ: + backend = os.environ['PYZMQ_BACKEND'] + if backend in ('cython', 'cffi'): + backend = f'zmq.backend.{backend}' + _ns = select_backend(backend) +else: + # default to cython, fallback to cffi + # (reverse on PyPy) + if platform.python_implementation() == 'PyPy': + first, second = ('zmq.backend.cffi', 'zmq.backend.cython') + else: + first, second = ('zmq.backend.cython', 'zmq.backend.cffi') + + try: + _ns = select_backend(first) + except Exception as original_error: + try: + _ns = select_backend(second) + except ImportError: + raise original_error from None + +globals().update(_ns) + +__all__ = public_api diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/__init__.pyi b/.venv/lib/python3.11/site-packages/zmq/backend/__init__.pyi new file mode 100644 index 0000000000000000000000000000000000000000..5982bbf64b1acf302a3bc4df35827ad835611f66 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/__init__.pyi @@ -0,0 +1,122 @@ +from typing import Any, Callable, List, Optional, Set, Tuple, TypeVar, Union, overload + +from typing_extensions import Literal + +import zmq + +from .select import select_backend + +# avoid collision in Frame.bytes +_bytestr = bytes + +T = TypeVar("T") + +class Frame: + buffer: Any + bytes: bytes + more: bool + tracker: Any + def __init__( + self, + data: Any = None, + track: bool = False, + copy: bool | None = None, + copy_threshold: int | None = None, + ): ... + def copy_fast(self: T) -> T: ... + def get(self, option: int) -> int | _bytestr | str: ... + def set(self, option: int, value: int | _bytestr | str) -> None: ... + +class Socket: + underlying: int + context: zmq.Context + copy_threshold: int + + # specific option types + FD: int + + def __init__( + self, + context: Context | None = None, + socket_type: int = 0, + shadow: int = 0, + copy_threshold: int | None = zmq.COPY_THRESHOLD, + ) -> None: ... + def close(self, linger: int | None = ...) -> None: ... + def get(self, option: int) -> int | bytes | str: ... + def set(self, option: int, value: int | bytes | str) -> None: ... + def connect(self, url: str): ... + def disconnect(self, url: str) -> None: ... + def bind(self, url: str): ... + def unbind(self, url: str) -> None: ... + def send( + self, + data: Any, + flags: int = ..., + copy: bool = ..., + track: bool = ..., + ) -> zmq.MessageTracker | None: ... + @overload + def recv( + self, + flags: int = ..., + *, + copy: Literal[False], + track: bool = ..., + ) -> zmq.Frame: ... + @overload + def recv( + self, + flags: int = ..., + *, + copy: Literal[True], + track: bool = ..., + ) -> bytes: ... + @overload + def recv( + self, + flags: int = ..., + track: bool = False, + ) -> bytes: ... + @overload + def recv( + self, + flags: int | None = ..., + copy: bool = ..., + track: bool | None = False, + ) -> zmq.Frame | bytes: ... + def monitor(self, addr: str | None, events: int) -> None: ... + # draft methods + def join(self, group: str) -> None: ... + def leave(self, group: str) -> None: ... + +class Context: + underlying: int + def __init__(self, io_threads: int = 1, shadow: int = 0): ... + def get(self, option: int) -> int | bytes | str: ... + def set(self, option: int, value: int | bytes | str) -> None: ... + def socket(self, socket_type: int) -> Socket: ... + def term(self) -> None: ... + +IPC_PATH_MAX_LEN: int + +def has(capability: str) -> bool: ... +def curve_keypair() -> tuple[bytes, bytes]: ... +def curve_public(secret_key: bytes) -> bytes: ... +def strerror(errno: int | None = ...) -> str: ... +def zmq_errno() -> int: ... +def zmq_version() -> str: ... +def zmq_version_info() -> tuple[int, int, int]: ... +def zmq_poll( + sockets: list[Any], timeout: int | None = ... +) -> list[tuple[Socket, int]]: ... +def device(device_type: int, frontend: Socket, backend: Socket | None = ...) -> int: ... +def proxy(frontend: Socket, backend: Socket, capture: Socket | None = None) -> int: ... +def proxy_steerable( + frontend: Socket, + backend: Socket, + capture: Socket | None = ..., + control: Socket | None = ..., +) -> int: ... + +monitored_queue = Callable | None diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/backend/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf275aacb5631933a3dc7cab238aea5689011d45 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/backend/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/__pycache__/select.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/backend/__pycache__/select.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f20c0df47562c2be9d1b1afd97e307772f7dfc3 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/backend/__pycache__/select.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cffi/README.md b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/README.md new file mode 100644 index 0000000000000000000000000000000000000000..00bb32989dcfbc787760075fd6dc4801568ab0a2 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/README.md @@ -0,0 +1 @@ +PyZMQ's CFFI support is designed only for (Unix) systems conforming to `have_sys_un_h = True`. diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__init__.py b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..33e9543ccc624b74d45475993737a3e2d2ae6c30 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__init__.py @@ -0,0 +1,35 @@ +"""CFFI backend (for PyPy)""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. + +from zmq.backend.cffi import _poll, context, devices, error, message, socket, utils + +from ._cffi import ffi +from ._cffi import lib as C + + +def zmq_version_info(): + """Get libzmq version as tuple of ints""" + major = ffi.new('int*') + minor = ffi.new('int*') + patch = ffi.new('int*') + + C.zmq_version(major, minor, patch) + + return (int(major[0]), int(minor[0]), int(patch[0])) + + +__all__ = ["zmq_version_info"] +for submod in (error, message, context, socket, _poll, devices, utils): + __all__.extend(submod.__all__) + +from ._poll import * +from .context import * +from .devices import * +from .error import * +from .message import * +from .socket import * +from .utils import * + +monitored_queue = None diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c4fa4e7a60f942af71be49d0eebf063a5f3d46e7 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/_poll.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/_poll.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a83dcb827b38b8e81edb00e8519753d278f2f71f Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/_poll.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/context.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/context.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..56a0abdb140433b4380172e35e3f084df40f011c Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/context.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/devices.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/devices.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..77c46cf69c667d88e220ffe453d43d5dc45be942 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/devices.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/error.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/error.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..848d6a3ee4bcad4eeb4533d69d2075885c1b1a75 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/error.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/message.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/message.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9bac64d17ca3cac7c996296609a525c62cc4e827 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/message.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/socket.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/socket.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cb01ddb73ba17f7854c2d9c00206c40a7212aae5 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/socket.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/utils.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/utils.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a324c9b26ae0cd0f56a662f6d5d119f39f162db6 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/__pycache__/utils.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cffi/_cdefs.h b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/_cdefs.h new file mode 100644 index 0000000000000000000000000000000000000000..7833b9b266a317493db93061791f2788098b6828 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/_cdefs.h @@ -0,0 +1,91 @@ +void zmq_version(int *major, int *minor, int *patch); + +void* zmq_socket(void *context, int type); +int zmq_close(void *socket); + +int zmq_bind(void *socket, const char *endpoint); +int zmq_connect(void *socket, const char *endpoint); + +int zmq_errno(void); +const char * zmq_strerror(int errnum); + +int zmq_device(int device, void *frontend, void *backend); + +int zmq_unbind(void *socket, const char *endpoint); +int zmq_disconnect(void *socket, const char *endpoint); +void* zmq_ctx_new(); +int zmq_ctx_destroy(void *context); +int zmq_ctx_get(void *context, int opt); +int zmq_ctx_set(void *context, int opt, int optval); +int zmq_proxy(void *frontend, void *backend, void *capture); +int zmq_proxy_steerable(void *frontend, + void *backend, + void *capture, + void *control); +int zmq_socket_monitor(void *socket, const char *addr, int events); + +int zmq_curve_keypair (char *z85_public_key, char *z85_secret_key); +int zmq_curve_public (char *z85_public_key, char *z85_secret_key); +int zmq_has (const char *capability); + +typedef struct { ...; } zmq_msg_t; +typedef ... zmq_free_fn; + +int zmq_msg_init(zmq_msg_t *msg); +int zmq_msg_init_size(zmq_msg_t *msg, size_t size); +int zmq_msg_init_data(zmq_msg_t *msg, + void *data, + size_t size, + zmq_free_fn *ffn, + void *hint); + +size_t zmq_msg_size(zmq_msg_t *msg); +void *zmq_msg_data(zmq_msg_t *msg); +int zmq_msg_close(zmq_msg_t *msg); + +int zmq_msg_copy(zmq_msg_t *dst, zmq_msg_t *src); +int zmq_msg_send(zmq_msg_t *msg, void *socket, int flags); +int zmq_msg_recv(zmq_msg_t *msg, void *socket, int flags); + +int zmq_getsockopt(void *socket, + int option_name, + void *option_value, + size_t *option_len); + +int zmq_setsockopt(void *socket, + int option_name, + const void *option_value, + size_t option_len); + +typedef int... ZMQ_FD_T; + +typedef struct +{ + void *socket; + ZMQ_FD_T fd; + short events; + short revents; +} zmq_pollitem_t; + +int zmq_poll(zmq_pollitem_t *items, int nitems, long timeout); + +// miscellany +void * memcpy(void *restrict s1, const void *restrict s2, size_t n); +void * malloc(size_t sz); +void free(void *p); +int get_ipc_path_max_len(void); + +typedef struct { ...; } mutex_t; + +typedef struct _zhint { + void *sock; + mutex_t *mutex; + size_t id; +} zhint; + +mutex_t* mutex_allocate(); + +int zmq_wrap_msg_init_data(zmq_msg_t *msg, + void *data, + size_t size, + void *hint); diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cffi/_cffi_src.c b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/_cffi_src.c new file mode 100644 index 0000000000000000000000000000000000000000..691be3c782d3d8209dc67e12d241e749e08440cb --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/_cffi_src.c @@ -0,0 +1,50 @@ +#include +#include + +#include "pyversion_compat.h" +#include "mutex.h" +#include "ipcmaxlen.h" +#include "zmq_compat.h" +#include + +typedef struct _zhint { + void *sock; + mutex_t *mutex; + size_t id; +} zhint; + +void free_python_msg(void *data, void *vhint) { + zmq_msg_t msg; + zhint *hint = (zhint *)vhint; + int rc; + if (hint != NULL) { + zmq_msg_init_size(&msg, sizeof(size_t)); + memcpy(zmq_msg_data(&msg), &hint->id, sizeof(size_t)); + rc = mutex_lock(hint->mutex); + if (rc != 0) { + fprintf(stderr, "pyzmq-gc mutex lock failed rc=%d\n", rc); + } + rc = zmq_msg_send(&msg, hint->sock, 0); + if (rc < 0) { + /* + * gc socket could have been closed, e.g. during process teardown. + * If so, ignore the failure because there's nothing to do. + */ + if (zmq_errno() != ENOTSOCK) { + fprintf(stderr, "pyzmq-gc send failed: %s\n", + zmq_strerror(zmq_errno())); + } + } + rc = mutex_unlock(hint->mutex); + if (rc != 0) { + fprintf(stderr, "pyzmq-gc mutex unlock failed rc=%d\n", rc); + } + zmq_msg_close(&msg); + free(hint); + } +} + +int zmq_wrap_msg_init_data(zmq_msg_t *msg, void *data, size_t size, + void *hint) { + return zmq_msg_init_data(msg, data, size, free_python_msg, hint); +} diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cffi/_poll.py b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/_poll.py new file mode 100644 index 0000000000000000000000000000000000000000..63e2763b9c57b065b963df9b0a1f86cd9fe30af0 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/_poll.py @@ -0,0 +1,92 @@ +"""zmq poll function""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. + +try: + from time import monotonic +except ImportError: + from time import clock as monotonic + +import warnings + +from zmq.error import InterruptedSystemCall, _check_rc + +from ._cffi import ffi +from ._cffi import lib as C + + +def _make_zmq_pollitem(socket, flags): + zmq_socket = socket._zmq_socket + zmq_pollitem = ffi.new('zmq_pollitem_t*') + zmq_pollitem.socket = zmq_socket + zmq_pollitem.fd = 0 + zmq_pollitem.events = flags + zmq_pollitem.revents = 0 + return zmq_pollitem[0] + + +def _make_zmq_pollitem_fromfd(socket_fd, flags): + zmq_pollitem = ffi.new('zmq_pollitem_t*') + zmq_pollitem.socket = ffi.NULL + zmq_pollitem.fd = socket_fd + zmq_pollitem.events = flags + zmq_pollitem.revents = 0 + return zmq_pollitem[0] + + +def zmq_poll(sockets, timeout): + cffi_pollitem_list = [] + low_level_to_socket_obj = {} + from zmq import Socket + + for item in sockets: + if isinstance(item[0], Socket): + low_level_to_socket_obj[item[0]._zmq_socket] = item + cffi_pollitem_list.append(_make_zmq_pollitem(item[0], item[1])) + else: + if not isinstance(item[0], int): + # not an FD, get it from fileno() + item = (item[0].fileno(), item[1]) + low_level_to_socket_obj[item[0]] = item + cffi_pollitem_list.append(_make_zmq_pollitem_fromfd(item[0], item[1])) + items = ffi.new('zmq_pollitem_t[]', cffi_pollitem_list) + list_length = ffi.cast('int', len(cffi_pollitem_list)) + while True: + c_timeout = ffi.cast('long', timeout) + start = monotonic() + rc = C.zmq_poll(items, list_length, c_timeout) + try: + _check_rc(rc) + except InterruptedSystemCall: + if timeout > 0: + ms_passed = int(1000 * (monotonic() - start)) + if ms_passed < 0: + # don't allow negative ms_passed, + # which can happen on old Python versions without time.monotonic. + warnings.warn( + f"Negative elapsed time for interrupted poll: {ms_passed}." + " Did the clock change?", + RuntimeWarning, + ) + ms_passed = 0 + timeout = max(0, timeout - ms_passed) + continue + else: + break + result = [] + for item in items: + if item.revents > 0: + if item.socket != ffi.NULL: + result.append( + ( + low_level_to_socket_obj[item.socket][0], + item.revents, + ) + ) + else: + result.append((item.fd, item.revents)) + return result + + +__all__ = ['zmq_poll'] diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cffi/context.py b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/context.py new file mode 100644 index 0000000000000000000000000000000000000000..23a69ecf1d97c1353470ebec03601370b8a5eb73 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/context.py @@ -0,0 +1,77 @@ +"""zmq Context class""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. + +from zmq.constants import EINVAL, IO_THREADS +from zmq.error import InterruptedSystemCall, ZMQError, _check_rc + +from ._cffi import ffi +from ._cffi import lib as C + + +class Context: + _zmq_ctx = None + _iothreads = None + _closed = True + _shadow = False + + def __init__(self, io_threads=1, shadow=None): + if shadow: + self._zmq_ctx = ffi.cast("void *", shadow) + self._shadow = True + else: + self._shadow = False + if not io_threads >= 0: + raise ZMQError(EINVAL) + + self._zmq_ctx = C.zmq_ctx_new() + if self._zmq_ctx == ffi.NULL: + raise ZMQError(C.zmq_errno()) + if not shadow: + C.zmq_ctx_set(self._zmq_ctx, IO_THREADS, io_threads) + self._closed = False + + @property + def underlying(self): + """The address of the underlying libzmq context""" + return int(ffi.cast('size_t', self._zmq_ctx)) + + @property + def closed(self): + return self._closed + + def set(self, option, value): + """set a context option + + see zmq_ctx_set + """ + rc = C.zmq_ctx_set(self._zmq_ctx, option, value) + _check_rc(rc) + + def get(self, option): + """get context option + + see zmq_ctx_get + """ + rc = C.zmq_ctx_get(self._zmq_ctx, option) + _check_rc(rc, error_without_errno=False) + return rc + + def term(self): + if self.closed: + return + + rc = C.zmq_ctx_destroy(self._zmq_ctx) + try: + _check_rc(rc) + except InterruptedSystemCall: + # ignore interrupted term + # see PEP 475 notes about close & EINTR for why + pass + + self._zmq_ctx = None + self._closed = True + + +__all__ = ['Context'] diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cffi/devices.py b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/devices.py new file mode 100644 index 0000000000000000000000000000000000000000..7e1ea7610ce557ea02a331902b60458eeb2bd1da --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/devices.py @@ -0,0 +1,63 @@ +"""zmq device functions""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. + +from ._cffi import ffi +from ._cffi import lib as C +from .socket import Socket +from .utils import _retry_sys_call + + +def device(device_type, frontend, backend): + return proxy(frontend, backend) + + +def proxy(frontend, backend, capture=None): + if isinstance(capture, Socket): + capture = capture._zmq_socket + else: + capture = ffi.NULL + + _retry_sys_call(C.zmq_proxy, frontend._zmq_socket, backend._zmq_socket, capture) + + +def proxy_steerable(frontend, backend, capture=None, control=None): + """proxy_steerable(frontend, backend, capture, control) + + Start a zeromq proxy with control flow. + + .. versionadded:: libzmq-4.1 + .. versionadded:: 18.0 + + Parameters + ---------- + frontend : Socket + The Socket instance for the incoming traffic. + backend : Socket + The Socket instance for the outbound traffic. + capture : Socket (optional) + The Socket instance for capturing traffic. + control : Socket (optional) + The Socket instance for control flow. + """ + if isinstance(capture, Socket): + capture = capture._zmq_socket + else: + capture = ffi.NULL + + if isinstance(control, Socket): + control = control._zmq_socket + else: + control = ffi.NULL + + _retry_sys_call( + C.zmq_proxy_steerable, + frontend._zmq_socket, + backend._zmq_socket, + capture, + control, + ) + + +__all__ = ['device', 'proxy', 'proxy_steerable'] diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cffi/error.py b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/error.py new file mode 100644 index 0000000000000000000000000000000000000000..f457363da09716ffd72e0b05607cb8f01e884472 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/error.py @@ -0,0 +1,20 @@ +"""zmq error functions""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. + +from ._cffi import ffi +from ._cffi import lib as C + + +def strerror(errno): + s = ffi.string(C.zmq_strerror(errno)) + if not isinstance(s, str): + # py3 + s = s.decode() + return s + + +zmq_errno = C.zmq_errno + +__all__ = ['strerror', 'zmq_errno'] diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cffi/message.py b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/message.py new file mode 100644 index 0000000000000000000000000000000000000000..d2761e049800fe3db3c30be56fff67918530bcd6 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/message.py @@ -0,0 +1,222 @@ +"""Dummy Frame object""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. + +import errno +from threading import Event + +import zmq +import zmq.error +from zmq.constants import ETERM + +from ._cffi import ffi +from ._cffi import lib as C + +zmq_gc = None + +try: + from __pypy__.bufferable import bufferable as maybe_bufferable +except ImportError: + maybe_bufferable = object + + +def _content(obj): + """Return content of obj as bytes""" + if type(obj) is bytes: + return obj + if not isinstance(obj, memoryview): + obj = memoryview(obj) + return obj.tobytes() + + +def _check_rc(rc): + err = C.zmq_errno() + if rc == -1: + if err == errno.EINTR: + raise zmq.error.InterrruptedSystemCall(err) + elif err == errno.EAGAIN: + raise zmq.error.Again(errno) + elif err == ETERM: + raise zmq.error.ContextTerminated(err) + else: + raise zmq.error.ZMQError(err) + return 0 + + +class Frame(maybe_bufferable): + _data = None + tracker = None + closed = False + more = False + _buffer = None + _bytes = None + _failed_init = False + tracker_event = None + zmq_msg = None + + def __init__(self, data=None, track=False, copy=None, copy_threshold=None): + self._failed_init = True + + self.zmq_msg = ffi.cast('zmq_msg_t[1]', C.malloc(ffi.sizeof("zmq_msg_t"))) + + # self.tracker should start finished + # except in the case where we are sharing memory with libzmq + if track: + self.tracker = zmq._FINISHED_TRACKER + + if isinstance(data, str): + raise TypeError( + "Unicode strings are not allowed. Only: bytes, buffer interfaces." + ) + + if data is None: + rc = C.zmq_msg_init(self.zmq_msg) + _check_rc(rc) + self._failed_init = False + return + + self._data = data + if type(data) is bytes: + # avoid unnecessary copy on .bytes access + self._bytes = data + + self._buffer = memoryview(data) + c_data = ffi.from_buffer(self._buffer) + data_len_c = self._buffer.nbytes + + if copy is None: + if copy_threshold and data_len_c < copy_threshold: + copy = True + else: + copy = False + + if copy: + # copy message data instead of sharing memory + rc = C.zmq_msg_init_size(self.zmq_msg, data_len_c) + _check_rc(rc) + ffi.buffer(C.zmq_msg_data(self.zmq_msg), data_len_c)[:] = self._buffer + self._failed_init = False + return + + # Getting here means that we are doing a true zero-copy Frame, + # where libzmq and Python are sharing memory. + # Hook up garbage collection with MessageTracker and zmq_free_fn + + # Event and MessageTracker for monitoring when zmq is done with data: + if track: + evt = Event() + self.tracker_event = evt + self.tracker = zmq.MessageTracker(evt) + # create the hint for zmq_free_fn + # two pointers: the zmq_gc context and a message to be sent to the zmq_gc PULL socket + # allows libzmq to signal to Python when it is done with Python-owned memory. + global zmq_gc + if zmq_gc is None: + from zmq.utils.garbage import gc as zmq_gc + # can't use ffi.new because it will be freed at the wrong time! + hint = ffi.cast("zhint[1]", C.malloc(ffi.sizeof("zhint"))) + hint[0].id = zmq_gc.store(data, self.tracker_event) + if not zmq_gc._push_mutex: + zmq_gc._push_mutex = C.mutex_allocate() + + hint[0].mutex = ffi.cast("mutex_t*", zmq_gc._push_mutex) + hint[0].sock = ffi.cast("void*", zmq_gc._push_socket.underlying) + + # calls zmq_wrap_msg_init_data with the C.free_python_msg callback + rc = C.zmq_wrap_msg_init_data( + self.zmq_msg, + c_data, + data_len_c, + hint, + ) + if rc != 0: + C.free(hint) + C.free(self.zmq_msg) + _check_rc(rc) + self._failed_init = False + + def __del__(self): + if not self.closed and not self._failed_init: + self.close() + + def close(self): + if self.closed or self._failed_init or self.zmq_msg is None: + return + self.closed = True + rc = C.zmq_msg_close(self.zmq_msg) + C.free(self.zmq_msg) + self.zmq_msg = None + if rc != 0: + _check_rc(rc) + + def _buffer_from_zmq_msg(self): + """one-time extract buffer from zmq_msg + + for Frames created by recv + """ + if self._data is None: + self._data = ffi.buffer( + C.zmq_msg_data(self.zmq_msg), C.zmq_msg_size(self.zmq_msg) + ) + if self._buffer is None: + self._buffer = memoryview(self._data) + + @property + def buffer(self): + if self._buffer is None: + self._buffer_from_zmq_msg() + return self._buffer + + @property + def bytes(self): + if self._bytes is None: + self._bytes = self.buffer.tobytes() + return self._bytes + + def __len__(self): + return self.buffer.nbytes + + def __eq__(self, other): + return self.bytes == _content(other) + + @property + def done(self): + return self.tracker.done() + + def __buffer__(self, flags): + return self.buffer + + def __copy__(self): + """Create a shallow copy of the message. + + This does not copy the contents of the Frame, just the pointer. + This will increment the 0MQ ref count of the message, but not + the ref count of the Python object. That is only done once when + the Python is first turned into a 0MQ message. + """ + return self.fast_copy() + + def fast_copy(self): + """Fast shallow copy of the Frame. + + Does not copy underlying data. + """ + new_msg = Frame() + # This does not copy the contents, but just increases the ref-count + # of the zmq_msg by one. + C.zmq_msg_copy(new_msg.zmq_msg, self.zmq_msg) + # Copy the ref to underlying data + new_msg._data = self._data + new_msg._buffer = self._buffer + + # Frame copies share the tracker and tracker_event + new_msg.tracker_event = self.tracker_event + new_msg.tracker = self.tracker + + return new_msg + + +Message = Frame + +__all__ = ['Frame', 'Message'] diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cffi/socket.py b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/socket.py new file mode 100644 index 0000000000000000000000000000000000000000..ac90837e538601a733ca96f2097b229841c56afa --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/socket.py @@ -0,0 +1,368 @@ +"""zmq Socket class""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. + +import errno as errno_mod + +import zmq +from zmq.constants import SocketOption, _OptType +from zmq.error import ZMQError, _check_rc, _check_version + +from ._cffi import ffi +from ._cffi import lib as C +from .message import Frame +from .utils import _retry_sys_call + +nsp = new_sizet_pointer = lambda length: ffi.new('size_t*', length) + + +def new_uint64_pointer(): + return ffi.new('uint64_t*'), nsp(ffi.sizeof('uint64_t')) + + +def new_int64_pointer(): + return ffi.new('int64_t*'), nsp(ffi.sizeof('int64_t')) + + +def new_int_pointer(): + return ffi.new('int*'), nsp(ffi.sizeof('int')) + + +def new_binary_data(length): + return ffi.new(f'char[{length:d}]'), nsp(ffi.sizeof('char') * length) + + +def value_uint64_pointer(val): + return ffi.new('uint64_t*', val), ffi.sizeof('uint64_t') + + +def value_int64_pointer(val): + return ffi.new('int64_t*', val), ffi.sizeof('int64_t') + + +def value_int_pointer(val): + return ffi.new('int*', val), ffi.sizeof('int') + + +def value_binary_data(val, length): + return ffi.new(f'char[{length + 1:d}]', val), ffi.sizeof('char') * length + + +ZMQ_FD_64BIT = ffi.sizeof('ZMQ_FD_T') == 8 + +IPC_PATH_MAX_LEN = C.get_ipc_path_max_len() + + +def new_pointer_from_opt(option, length=0): + opt_type = getattr(option, "_opt_type", _OptType.int) + + if opt_type == _OptType.int64 or (ZMQ_FD_64BIT and opt_type == _OptType.fd): + return new_int64_pointer() + elif opt_type == _OptType.bytes: + return new_binary_data(length) + else: + # default + return new_int_pointer() + + +def value_from_opt_pointer(option, opt_pointer, length=0): + try: + option = SocketOption(option) + except ValueError: + # unrecognized option, + # assume from the future, + # let EINVAL raise + opt_type = _OptType.int + else: + opt_type = option._opt_type + + if opt_type == _OptType.bytes: + return ffi.buffer(opt_pointer, length)[:] + else: + return int(opt_pointer[0]) + + +def initialize_opt_pointer(option, value, length=0): + opt_type = getattr(option, "_opt_type", _OptType.int) + if opt_type == _OptType.int64 or (ZMQ_FD_64BIT and opt_type == _OptType.fd): + return value_int64_pointer(value) + elif opt_type == _OptType.bytes: + return value_binary_data(value, length) + else: + return value_int_pointer(value) + + +class Socket: + context = None + socket_type = None + _zmq_socket = None + _closed = None + _ref = None + _shadow = False + copy_threshold = 0 + + def __init__(self, context=None, socket_type=None, shadow=0, copy_threshold=None): + if copy_threshold is None: + copy_threshold = zmq.COPY_THRESHOLD + self.copy_threshold = copy_threshold + + self.context = context + if shadow: + self._zmq_socket = ffi.cast("void *", shadow) + self._shadow = True + else: + self._shadow = False + self._zmq_socket = C.zmq_socket(context._zmq_ctx, socket_type) + if self._zmq_socket == ffi.NULL: + raise ZMQError() + self._closed = False + + @property + def underlying(self): + """The address of the underlying libzmq socket""" + return int(ffi.cast('size_t', self._zmq_socket)) + + def _check_closed_deep(self): + """thorough check of whether the socket has been closed, + even if by another entity (e.g. ctx.destroy). + + Only used by the `closed` property. + + returns True if closed, False otherwise + """ + if self._closed: + return True + try: + self.get(zmq.TYPE) + except ZMQError as e: + if e.errno == zmq.ENOTSOCK: + self._closed = True + return True + elif e.errno == zmq.ETERM: + pass + else: + raise + return False + + @property + def closed(self): + return self._check_closed_deep() + + def close(self, linger=None): + rc = 0 + if not self._closed and hasattr(self, '_zmq_socket'): + if self._zmq_socket is not None: + if linger is not None: + self.set(zmq.LINGER, linger) + rc = C.zmq_close(self._zmq_socket) + self._closed = True + if rc < 0: + _check_rc(rc) + + def bind(self, address): + if isinstance(address, str): + address_b = address.encode('utf8') + else: + address_b = address + if isinstance(address, bytes): + address = address_b.decode('utf8') + rc = C.zmq_bind(self._zmq_socket, address_b) + if rc < 0: + if IPC_PATH_MAX_LEN and C.zmq_errno() == errno_mod.ENAMETOOLONG: + path = address.split('://', 1)[-1] + msg = ( + f'ipc path "{path}" is longer than {IPC_PATH_MAX_LEN} ' + 'characters (sizeof(sockaddr_un.sun_path)).' + ) + raise ZMQError(C.zmq_errno(), msg=msg) + elif C.zmq_errno() == errno_mod.ENOENT: + path = address.split('://', 1)[-1] + msg = f'No such file or directory for ipc path "{path}".' + raise ZMQError(C.zmq_errno(), msg=msg) + else: + _check_rc(rc) + + def unbind(self, address): + _check_version((3, 2), "unbind") + if isinstance(address, str): + address = address.encode('utf8') + rc = C.zmq_unbind(self._zmq_socket, address) + _check_rc(rc) + + def connect(self, address): + if isinstance(address, str): + address = address.encode('utf8') + rc = C.zmq_connect(self._zmq_socket, address) + _check_rc(rc) + + def disconnect(self, address): + _check_version((3, 2), "disconnect") + if isinstance(address, str): + address = address.encode('utf8') + rc = C.zmq_disconnect(self._zmq_socket, address) + _check_rc(rc) + + def set(self, option, value): + length = None + if isinstance(value, str): + raise TypeError("unicode not allowed, use bytes") + + try: + option = SocketOption(option) + except ValueError: + # unrecognized option, + # assume from the future, + # let EINVAL raise + opt_type = _OptType.int + else: + opt_type = option._opt_type + + if isinstance(value, bytes): + if opt_type != _OptType.bytes: + raise TypeError(f"not a bytes sockopt: {option}") + length = len(value) + + c_value_pointer, c_sizet = initialize_opt_pointer(option, value, length) + + _retry_sys_call( + C.zmq_setsockopt, + self._zmq_socket, + option, + ffi.cast('void*', c_value_pointer), + c_sizet, + ) + + def get(self, option): + try: + option = SocketOption(option) + except ValueError: + # unrecognized option, + # assume from the future, + # let EINVAL raise + opt_type = _OptType.int + else: + opt_type = option._opt_type + + c_value_pointer, c_sizet_pointer = new_pointer_from_opt(option, length=255) + + _retry_sys_call( + C.zmq_getsockopt, self._zmq_socket, option, c_value_pointer, c_sizet_pointer + ) + + sz = c_sizet_pointer[0] + v = value_from_opt_pointer(option, c_value_pointer, sz) + if ( + option != zmq.SocketOption.ROUTING_ID + and opt_type == _OptType.bytes + and v.endswith(b'\0') + ): + v = v[:-1] + return v + + def _send_copy(self, buf, flags): + """Send a copy of a bufferable""" + zmq_msg = ffi.new('zmq_msg_t*') + if not isinstance(buf, bytes): + # cast any bufferable data to bytes via memoryview + buf = memoryview(buf).tobytes() + + c_message = ffi.new('char[]', buf) + rc = C.zmq_msg_init_size(zmq_msg, len(buf)) + _check_rc(rc) + C.memcpy(C.zmq_msg_data(zmq_msg), c_message, len(buf)) + _retry_sys_call(C.zmq_msg_send, zmq_msg, self._zmq_socket, flags) + rc2 = C.zmq_msg_close(zmq_msg) + _check_rc(rc2) + + def _send_frame(self, frame, flags): + """Send a Frame on this socket in a non-copy manner.""" + # Always copy the Frame so the original message isn't garbage collected. + # This doesn't do a real copy, just a reference. + frame_copy = frame.fast_copy() + zmq_msg = frame_copy.zmq_msg + _retry_sys_call(C.zmq_msg_send, zmq_msg, self._zmq_socket, flags) + tracker = frame_copy.tracker + frame_copy.close() + return tracker + + def send(self, data, flags=0, copy=False, track=False): + if isinstance(data, str): + raise TypeError("Message must be in bytes, not a unicode object") + + if copy and not isinstance(data, Frame): + return self._send_copy(data, flags) + else: + close_frame = False + if isinstance(data, Frame): + if track and not data.tracker: + raise ValueError('Not a tracked message') + frame = data + else: + if self.copy_threshold: + buf = memoryview(data) + # always copy messages smaller than copy_threshold + if buf.nbytes < self.copy_threshold: + self._send_copy(buf, flags) + return zmq._FINISHED_TRACKER + frame = Frame(data, track=track, copy_threshold=self.copy_threshold) + close_frame = True + + tracker = self._send_frame(frame, flags) + if close_frame: + frame.close() + return tracker + + def recv(self, flags=0, copy=True, track=False): + if copy: + zmq_msg = ffi.new('zmq_msg_t*') + C.zmq_msg_init(zmq_msg) + else: + frame = zmq.Frame(track=track) + zmq_msg = frame.zmq_msg + + try: + _retry_sys_call(C.zmq_msg_recv, zmq_msg, self._zmq_socket, flags) + except Exception: + if copy: + C.zmq_msg_close(zmq_msg) + raise + + if not copy: + return frame + + _buffer = ffi.buffer(C.zmq_msg_data(zmq_msg), C.zmq_msg_size(zmq_msg)) + _bytes = _buffer[:] + rc = C.zmq_msg_close(zmq_msg) + _check_rc(rc) + return _bytes + + def monitor(self, addr, events=-1): + """s.monitor(addr, flags) + + Start publishing socket events on inproc. + See libzmq docs for zmq_monitor for details. + + Note: requires libzmq >= 3.2 + + Parameters + ---------- + addr : str + The inproc url used for monitoring. Passing None as + the addr will cause an existing socket monitor to be + deregistered. + events : int [default: zmq.EVENT_ALL] + The zmq event bitmask for which events will be sent to the monitor. + """ + + _check_version((3, 2), "monitor") + if events < 0: + events = zmq.EVENT_ALL + if addr is None: + addr = ffi.NULL + if isinstance(addr, str): + addr = addr.encode('utf8') + C.zmq_socket_monitor(self._zmq_socket, addr, events) + + +__all__ = ['Socket', 'IPC_PATH_MAX_LEN'] diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cffi/utils.py b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..ca9c3bcf915aab7af874b1f4d7e768a5072f1088 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/cffi/utils.py @@ -0,0 +1,78 @@ +"""miscellaneous zmq_utils wrapping""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. + +from zmq.error import InterruptedSystemCall, _check_rc, _check_version + +from ._cffi import ffi +from ._cffi import lib as C + + +def has(capability): + """Check for zmq capability by name (e.g. 'ipc', 'curve') + + .. versionadded:: libzmq-4.1 + .. versionadded:: 14.1 + """ + _check_version((4, 1), 'zmq.has') + if isinstance(capability, str): + capability = capability.encode('utf8') + return bool(C.zmq_has(capability)) + + +def curve_keypair(): + """generate a Z85 key pair for use with zmq.CURVE security + + Requires libzmq (≥ 4.0) to have been built with CURVE support. + + Returns + ------- + (public, secret) : two bytestrings + The public and private key pair as 40 byte z85-encoded bytestrings. + """ + _check_version((3, 2), "curve_keypair") + public = ffi.new('char[64]') + private = ffi.new('char[64]') + rc = C.zmq_curve_keypair(public, private) + _check_rc(rc) + return ffi.buffer(public)[:40], ffi.buffer(private)[:40] + + +def curve_public(private): + """Compute the public key corresponding to a private key for use + with zmq.CURVE security + + Requires libzmq (≥ 4.2) to have been built with CURVE support. + + Parameters + ---------- + private + The private key as a 40 byte z85-encoded bytestring + Returns + ------- + bytestring + The public key as a 40 byte z85-encoded bytestring. + """ + if isinstance(private, str): + private = private.encode('utf8') + _check_version((4, 2), "curve_public") + public = ffi.new('char[64]') + rc = C.zmq_curve_public(public, private) + _check_rc(rc) + return ffi.buffer(public)[:40] + + +def _retry_sys_call(f, *args, **kwargs): + """make a call, retrying if interrupted with EINTR""" + while True: + rc = f(*args) + try: + _check_rc(rc) + except InterruptedSystemCall: + continue + else: + break + + +__all__ = ['has', 'curve_keypair', 'curve_public'] diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cython/__init__.pxd b/.venv/lib/python3.11/site-packages/zmq/backend/cython/__init__.pxd new file mode 100644 index 0000000000000000000000000000000000000000..94c7f8a3fb3b246dec15c64768147889f6593579 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/cython/__init__.pxd @@ -0,0 +1 @@ +from zmq.backend.cython._zmq cimport Context, Frame, Socket diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cython/__init__.py b/.venv/lib/python3.11/site-packages/zmq/backend/cython/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6c4179a80a3165e15a6f01b602d0281e64254e06 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/cython/__init__.py @@ -0,0 +1,15 @@ +"""Python bindings for core 0MQ objects.""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. + +from . import _zmq + +# mq not in __all__ +from ._zmq import * # noqa +from ._zmq import monitored_queue # noqa + +Message = _zmq.Frame + +__all__ = ["Message"] +__all__.extend(_zmq.__all__) diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cython/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/backend/cython/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa8b420a2c41a58a3a111184ece07a9b41e117ac Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/backend/cython/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cython/__pycache__/_zmq.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/backend/cython/__pycache__/_zmq.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d0347e0b7b2fb8aed2254b91b03fbf52625a2e37 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/backend/cython/__pycache__/_zmq.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cython/_externs.pxd b/.venv/lib/python3.11/site-packages/zmq/backend/cython/_externs.pxd new file mode 100644 index 0000000000000000000000000000000000000000..2cf4ddd65fe2730902c8fb32b3d2730b16707526 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/cython/_externs.pxd @@ -0,0 +1,13 @@ +cdef extern from "mutex.h" nogil: + ctypedef struct mutex_t: + pass + cdef mutex_t* mutex_allocate() + cdef void mutex_dallocate(mutex_t*) + cdef int mutex_lock(mutex_t*) + cdef int mutex_unlock(mutex_t*) + +cdef extern from "getpid_compat.h": + int getpid() + +cdef extern from "ipcmaxlen.h": + int get_ipc_path_max_len() diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cython/_zmq.pxd b/.venv/lib/python3.11/site-packages/zmq/backend/cython/_zmq.pxd new file mode 100644 index 0000000000000000000000000000000000000000..ea7f05a9d5a81dff94a5ff880deb369f4ad55838 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/cython/_zmq.pxd @@ -0,0 +1,50 @@ +"""zmq Cython backend augmented declarations""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. + +from zmq.backend.cython.libzmq cimport zmq_msg_t + + +cdef class Context: + + cdef object __weakref__ # enable weakref + cdef void *handle # The C handle for the underlying zmq object. + cdef bint _shadow # whether the Context is a shadow wrapper of another + cdef int _pid # the pid of the process which created me (for fork safety) + + cdef public bint closed # bool property for a closed context. + cdef inline int _term(self) + +cdef class MessageTracker(object): + cdef set events # Message Event objects to track. + cdef set peers # Other Message or MessageTracker objects. + +cdef class Frame: + + cdef zmq_msg_t zmq_msg + cdef object _data # The actual message data as a Python object. + cdef object _buffer # A Python memoryview of the message contents + cdef object _bytes # A bytes copy of the message. + cdef bint _failed_init # flag to hold failed init + cdef public object tracker_event # Event for use with zmq_free_fn. + cdef public object tracker # MessageTracker object. + cdef public bint more # whether RCVMORE was set + + cdef Frame fast_copy(self) # Create shallow copy of Message object. + +cdef class Socket: + + cdef object __weakref__ # enable weakref + cdef void *handle # The C handle for the underlying zmq object. + cdef bint _shadow # whether the Socket is a shadow wrapper of another + # Hold on to a reference to the context to make sure it is not garbage + # collected until the socket it done with it. + cdef public Context context # The zmq Context object that owns this. + cdef public bint _closed # bool property for a closed socket. + cdef public int copy_threshold # threshold below which pyzmq will always copy messages + cdef int _pid # the pid of the process which created me (for fork safety) + + # cpdef methods for direct-cython access: + cpdef object send(self, data, int flags=*, bint copy=*, bint track=*) + cpdef object recv(self, int flags=*, bint copy=*, bint track=*) diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cython/_zmq.py b/.venv/lib/python3.11/site-packages/zmq/backend/cython/_zmq.py new file mode 100644 index 0000000000000000000000000000000000000000..8cac83bee85b8b65876550ed414e5c6a6402d05d --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/cython/_zmq.py @@ -0,0 +1,1958 @@ +# cython: freethreading_compatible = True +"""Cython backend for pyzmq""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. + +from __future__ import annotations + +try: + import cython + + if not cython.compiled: + raise ImportError() +except ImportError: + from pathlib import Path + + zmq_root = Path(__file__).parents[3] + msg = f""" + Attempting to import zmq Cython backend, which has not been compiled. + + This probably means you are importing zmq from its source tree. + if this is what you want, make sure to do an in-place build first: + + pip install -e '{zmq_root}' + + If it is not, then '{zmq_root}' is probably on your sys.path, + when it shouldn't be. Is that your current working directory? + + If neither of those is true and this file is actually installed, + something seems to have gone wrong with the install! + Please report at https://github.com/zeromq/pyzmq/issues + """ + raise ImportError(msg) + +import time +import warnings +from threading import Event +from weakref import ref + +import cython as C +from cython import ( + NULL, + Py_ssize_t, + address, + bint, + cast, + cclass, + cfunc, + char, + declare, + inline, + nogil, + p_char, + p_void, + pointer, + size_t, + sizeof, +) +from cython.cimports.cpython import ( + PyBytes_AsString, + PyBytes_FromStringAndSize, + PyBytes_Size, + PyErr_CheckSignals, +) +from cython.cimports.libc.errno import EAGAIN, EINTR, ENAMETOOLONG, ENOENT, ENOTSOCK + +# cimports require Cython 3 +from cython.cimports.libc.stdint import uint32_t +from cython.cimports.libc.stdio import fprintf +from cython.cimports.libc.stdio import stderr as cstderr +from cython.cimports.libc.stdlib import free, malloc +from cython.cimports.libc.string import memcpy +from cython.cimports.zmq.backend.cython._externs import ( + get_ipc_path_max_len, + getpid, + mutex_allocate, + mutex_lock, + mutex_t, + mutex_unlock, +) +from cython.cimports.zmq.backend.cython.libzmq import ( + ZMQ_ENOTSOCK, + ZMQ_ETERM, + ZMQ_EVENT_ALL, + ZMQ_IDENTITY, + ZMQ_IO_THREADS, + ZMQ_LINGER, + ZMQ_POLLIN, + ZMQ_RCVMORE, + ZMQ_ROUTER, + ZMQ_SNDMORE, + ZMQ_TYPE, + ZMQ_VERSION_MAJOR, + _zmq_version, + fd_t, + int64_t, + zmq_bind, + zmq_close, + zmq_connect, + zmq_ctx_destroy, + zmq_ctx_get, + zmq_ctx_new, + zmq_ctx_set, + zmq_curve_keypair, + zmq_curve_public, + zmq_device, + zmq_disconnect, + zmq_free_fn, + zmq_getsockopt, + zmq_has, + zmq_init, + zmq_join, + zmq_leave, + zmq_msg_close, + zmq_msg_copy, + zmq_msg_data, + zmq_msg_get, + zmq_msg_gets, + zmq_msg_group, + zmq_msg_init, + zmq_msg_init_data, + zmq_msg_init_size, + zmq_msg_recv, + zmq_msg_routing_id, + zmq_msg_send, + zmq_msg_set, + zmq_msg_set_group, + zmq_msg_set_routing_id, + zmq_msg_size, + zmq_msg_t, + zmq_pollitem_t, + zmq_proxy, + zmq_proxy_steerable, + zmq_setsockopt, + zmq_socket, + zmq_socket_monitor, + zmq_strerror, + zmq_unbind, +) +from cython.cimports.zmq.backend.cython.libzmq import zmq_errno as _zmq_errno +from cython.cimports.zmq.backend.cython.libzmq import zmq_poll as zmq_poll_c +from cython.cimports.zmq.utils.buffers import asbuffer_r + +import zmq +from zmq.constants import SocketOption, _OptType +from zmq.error import InterruptedSystemCall, ZMQError, _check_version + +IPC_PATH_MAX_LEN = get_ipc_path_max_len() + + +@cfunc +@inline +@C.exceptval(-1) +def _check_rc(rc: C.int, error_without_errno: bint = False) -> C.int: + """internal utility for checking zmq return condition + + and raising the appropriate Exception class + """ + errno: C.int = _zmq_errno() + PyErr_CheckSignals() + if errno == 0 and not error_without_errno: + return 0 + if rc == -1: # if rc < -1, it's a bug in libzmq. Should we warn? + if errno == EINTR: + from zmq.error import InterruptedSystemCall + + raise InterruptedSystemCall(errno) + elif errno == EAGAIN: + from zmq.error import Again + + raise Again(errno) + elif errno == ZMQ_ETERM: + from zmq.error import ContextTerminated + + raise ContextTerminated(errno) + else: + from zmq.error import ZMQError + + raise ZMQError(errno) + return 0 + + +# message Frame class + +_zhint = C.struct( + sock=p_void, + mutex=pointer(mutex_t), + id=size_t, +) + + +@cfunc +@nogil +def free_python_msg(data: p_void, vhint: p_void) -> C.int: + """A pure-C function for DECREF'ing Python-owned message data. + + Sends a message on a PUSH socket + + The hint is a `zhint` struct with two values: + + sock (void *): pointer to the Garbage Collector's PUSH socket + id (size_t): the id to be used to construct a zmq_msg_t that should be sent on a PUSH socket, + signaling the Garbage Collector to remove its reference to the object. + + When the Garbage Collector's PULL socket receives the message, + it deletes its reference to the object, + allowing Python to free the memory. + """ + msg = declare(zmq_msg_t) + msg_ptr: pointer(zmq_msg_t) = address(msg) + hint: pointer(_zhint) = cast(pointer(_zhint), vhint) + rc: C.int + + if hint != NULL: + zmq_msg_init_size(msg_ptr, sizeof(size_t)) + memcpy(zmq_msg_data(msg_ptr), address(hint.id), sizeof(size_t)) + rc = mutex_lock(hint.mutex) + if rc != 0: + fprintf(cstderr, "pyzmq-gc mutex lock failed rc=%d\n", rc) + rc = zmq_msg_send(msg_ptr, hint.sock, 0) + if rc < 0: + # gc socket could have been closed, e.g. during process teardown. + # If so, ignore the failure because there's nothing to do. + if _zmq_errno() != ZMQ_ENOTSOCK: + fprintf( + cstderr, "pyzmq-gc send failed: %s\n", zmq_strerror(_zmq_errno()) + ) + rc = mutex_unlock(hint.mutex) + if rc != 0: + fprintf(cstderr, "pyzmq-gc mutex unlock failed rc=%d\n", rc) + + zmq_msg_close(msg_ptr) + free(hint) + return 0 + + +@cfunc +@inline +def _copy_zmq_msg_bytes(zmq_msg: pointer(zmq_msg_t)) -> bytes: + """Copy the data from a zmq_msg_t""" + data_c: p_char = NULL + data_len_c: Py_ssize_t + data_c = cast(p_char, zmq_msg_data(zmq_msg)) + data_len_c = zmq_msg_size(zmq_msg) + return PyBytes_FromStringAndSize(data_c, data_len_c) + + +_gc = None + + +@cclass +class Frame: + def __init__( + self, data=None, track=False, copy=None, copy_threshold=None, **kwargs + ): + rc: C.int + data_c: p_char = NULL + data_len_c: Py_ssize_t = 0 + hint: pointer(_zhint) + if copy_threshold is None: + copy_threshold = zmq.COPY_THRESHOLD + + zmq_msg_ptr: pointer(zmq_msg_t) = address(self.zmq_msg) + # init more as False + self.more = False + + # Save the data object in case the user wants the the data as a str. + self._data = data + self._failed_init = True # bool switch for dealloc + self._buffer = None # buffer view of data + self._bytes = None # bytes copy of data + + self.tracker_event = None + self.tracker = None + # self.tracker should start finished + # except in the case where we are sharing memory with libzmq + if track: + self.tracker = zmq._FINISHED_TRACKER + + if isinstance(data, str): + raise TypeError("Str objects not allowed. Only: bytes, buffer interfaces.") + + if data is None: + rc = zmq_msg_init(zmq_msg_ptr) + _check_rc(rc) + self._failed_init = False + return + + asbuffer_r(data, cast(pointer(p_void), address(data_c)), address(data_len_c)) + + # copy unspecified, apply copy_threshold + if copy is None: + if copy_threshold and data_len_c < copy_threshold: + copy = True + else: + copy = False + + if copy: + # copy message data instead of sharing memory + rc = zmq_msg_init_size(zmq_msg_ptr, data_len_c) + _check_rc(rc) + memcpy(zmq_msg_data(zmq_msg_ptr), data_c, data_len_c) + self._failed_init = False + return + + # Getting here means that we are doing a true zero-copy Frame, + # where libzmq and Python are sharing memory. + # Hook up garbage collection with MessageTracker and zmq_free_fn + + # Event and MessageTracker for monitoring when zmq is done with data: + if track: + evt = Event() + self.tracker_event = evt + self.tracker = zmq.MessageTracker(evt) + # create the hint for zmq_free_fn + # two pointers: the gc context and a message to be sent to the gc PULL socket + # allows libzmq to signal to Python when it is done with Python-owned memory. + global _gc + if _gc is None: + from zmq.utils.garbage import gc as _gc + + hint: pointer(_zhint) = cast(pointer(_zhint), malloc(sizeof(_zhint))) + hint.id = _gc.store(data, self.tracker_event) + if not _gc._push_mutex: + hint.mutex = mutex_allocate() + _gc._push_mutex = cast(size_t, hint.mutex) + else: + hint.mutex = cast(pointer(mutex_t), cast(size_t, _gc._push_mutex)) + hint.sock = cast(p_void, cast(size_t, _gc._push_socket.underlying)) + + rc = zmq_msg_init_data( + zmq_msg_ptr, + cast(p_void, data_c), + data_len_c, + cast(pointer(zmq_free_fn), free_python_msg), + cast(p_void, hint), + ) + if rc != 0: + free(hint) + _check_rc(rc) + self._failed_init = False + + def __del__(self): + if self._failed_init: + return + # This simply decreases the 0MQ ref-count of zmq_msg. + with nogil: + rc: C.int = zmq_msg_close(address(self.zmq_msg)) + _check_rc(rc) + + def __copy__(self): + return self.fast_copy() + + def fast_copy(self) -> Frame: + new_msg: Frame = Frame() + # This does not copy the contents, but just increases the ref-count + # of the zmq_msg by one. + zmq_msg_copy(address(new_msg.zmq_msg), address(self.zmq_msg)) + # Copy the ref to data so the copy won't create a copy when str is + # called. + if self._data is not None: + new_msg._data = self._data + if self._buffer is not None: + new_msg._buffer = self._buffer + if self._bytes is not None: + new_msg._bytes = self._bytes + + # Frame copies share the tracker and tracker_event + new_msg.tracker_event = self.tracker_event + new_msg.tracker = self.tracker + + return new_msg + + # buffer interface code adapted from petsc4py by Lisandro Dalcin, a BSD project + + def __getbuffer__(self, buffer: pointer(Py_buffer), flags: C.int): # noqa: F821 + # new-style (memoryview) buffer interface + buffer.buf = zmq_msg_data(address(self.zmq_msg)) + buffer.len = zmq_msg_size(address(self.zmq_msg)) + + buffer.obj = self + buffer.readonly = 0 + buffer.format = "B" + buffer.ndim = 1 + buffer.shape = address(buffer.len) + buffer.strides = NULL + buffer.suboffsets = NULL + buffer.itemsize = 1 + buffer.internal = NULL + + def __len__(self) -> size_t: + """Return the length of the message in bytes.""" + sz: size_t = zmq_msg_size(address(self.zmq_msg)) + return sz + + @property + def buffer(self): + """A memoryview of the message contents.""" + _buffer = self._buffer and self._buffer() + if _buffer is not None: + return _buffer + _buffer = memoryview(self) + self._buffer = ref(_buffer) + return _buffer + + @property + def bytes(self): + """The message content as a Python bytes object. + + The first time this property is accessed, a copy of the message + contents is made. From then on that same copy of the message is + returned. + """ + if self._bytes is None: + self._bytes = _copy_zmq_msg_bytes(address(self.zmq_msg)) + return self._bytes + + def get(self, option): + """ + Get a Frame option or property. + + See the 0MQ API documentation for zmq_msg_get and zmq_msg_gets + for details on specific options. + + .. versionadded:: libzmq-3.2 + .. versionadded:: 13.0 + + .. versionchanged:: 14.3 + add support for zmq_msg_gets (requires libzmq-4.1) + All message properties are strings. + + .. versionchanged:: 17.0 + Added support for `routing_id` and `group`. + Only available if draft API is enabled + with libzmq >= 4.2. + """ + rc: C.int = 0 + property_c: p_char = NULL + + # zmq_msg_get + if isinstance(option, int): + rc = zmq_msg_get(address(self.zmq_msg), option) + _check_rc(rc) + return rc + + if option == 'routing_id': + routing_id: uint32_t = zmq_msg_routing_id(address(self.zmq_msg)) + if routing_id == 0: + _check_rc(-1) + return routing_id + elif option == 'group': + buf = zmq_msg_group(address(self.zmq_msg)) + if buf == NULL: + _check_rc(-1) + return buf.decode('utf8') + + # zmq_msg_gets + _check_version((4, 1), "get string properties") + if isinstance(option, str): + option = option.encode('utf8') + + if not isinstance(option, bytes): + raise TypeError(f"expected str, got: {option!r}") + + property_c = option + + result: p_char = cast(p_char, zmq_msg_gets(address(self.zmq_msg), property_c)) + if result == NULL: + _check_rc(-1) + return result.decode('utf8') + + def set(self, option, value): + """Set a Frame option. + + See the 0MQ API documentation for zmq_msg_set + for details on specific options. + + .. versionadded:: libzmq-3.2 + .. versionadded:: 13.0 + .. versionchanged:: 17.0 + Added support for `routing_id` and `group`. + Only available if draft API is enabled + with libzmq >= 4.2. + """ + rc: C.int + + if option == 'routing_id': + routing_id: uint32_t = value + rc = zmq_msg_set_routing_id(address(self.zmq_msg), routing_id) + _check_rc(rc) + return + elif option == 'group': + if isinstance(value, str): + value = value.encode('utf8') + rc = zmq_msg_set_group(address(self.zmq_msg), value) + _check_rc(rc) + return + + rc = zmq_msg_set(address(self.zmq_msg), option, value) + _check_rc(rc) + + +@cclass +class Context: + """ + Manage the lifecycle of a 0MQ context. + + Parameters + ---------- + io_threads : int + The number of IO threads. + """ + + def __init__(self, io_threads: C.int = 1, shadow: size_t = 0): + self.handle = NULL + self._pid = 0 + self._shadow = False + + if shadow: + self.handle = cast(p_void, shadow) + self._shadow = True + else: + self._shadow = False + if ZMQ_VERSION_MAJOR >= 3: + self.handle = zmq_ctx_new() + else: + self.handle = zmq_init(io_threads) + + if self.handle == NULL: + raise ZMQError() + + rc: C.int = 0 + if ZMQ_VERSION_MAJOR >= 3 and not self._shadow: + rc = zmq_ctx_set(self.handle, ZMQ_IO_THREADS, io_threads) + _check_rc(rc) + + self.closed = False + self._pid = getpid() + + @property + def underlying(self): + """The address of the underlying libzmq context""" + return cast(size_t, self.handle) + + @cfunc + @inline + def _term(self) -> C.int: + rc: C.int = 0 + if self.handle != NULL and not self.closed and getpid() == self._pid: + with nogil: + rc = zmq_ctx_destroy(self.handle) + self.handle = NULL + return rc + + def term(self): + """ + Close or terminate the context. + + This can be called to close the context by hand. If this is not called, + the context will automatically be closed when it is garbage collected. + """ + rc: C.int = self._term() + try: + _check_rc(rc) + except InterruptedSystemCall: + # ignore interrupted term + # see PEP 475 notes about close & EINTR for why + pass + + self.closed = True + + def set(self, option: C.int, optval): + """ + Set a context option. + + See the 0MQ API documentation for zmq_ctx_set + for details on specific options. + + .. versionadded:: libzmq-3.2 + .. versionadded:: 13.0 + + Parameters + ---------- + option : int + The option to set. Available values will depend on your + version of libzmq. Examples include:: + + zmq.IO_THREADS, zmq.MAX_SOCKETS + + optval : int + The value of the option to set. + """ + optval_int_c: C.int + rc: C.int + + if self.closed: + raise RuntimeError("Context has been destroyed") + + if not isinstance(optval, int): + raise TypeError(f'expected int, got: {optval!r}') + optval_int_c = optval + rc = zmq_ctx_set(self.handle, option, optval_int_c) + _check_rc(rc) + + def get(self, option: C.int): + """ + Get the value of a context option. + + See the 0MQ API documentation for zmq_ctx_get + for details on specific options. + + .. versionadded:: libzmq-3.2 + .. versionadded:: 13.0 + + Parameters + ---------- + option : int + The option to get. Available values will depend on your + version of libzmq. Examples include:: + + zmq.IO_THREADS, zmq.MAX_SOCKETS + + Returns + ------- + optval : int + The value of the option as an integer. + """ + rc: C.int + + if self.closed: + raise RuntimeError("Context has been destroyed") + + rc = zmq_ctx_get(self.handle, option) + _check_rc(rc, error_without_errno=False) + return rc + + +@cclass +class Socket: + """ + A 0MQ socket. + + These objects will generally be constructed via the socket() method of a Context object. + + Note: 0MQ Sockets are *not* threadsafe. **DO NOT** share them across threads. + + Parameters + ---------- + context : Context + The 0MQ Context this Socket belongs to. + socket_type : int + The socket type, which can be any of the 0MQ socket types: + REQ, REP, PUB, SUB, PAIR, DEALER, ROUTER, PULL, PUSH, XPUB, XSUB. + + See Also + -------- + .Context.socket : method for creating a socket bound to a Context. + """ + + def __init__( + self, + context=None, + socket_type: C.int = -1, + shadow: size_t = 0, + copy_threshold=None, + ): + # pre-init + self.handle = NULL + self._pid = 0 + self._shadow = False + self.context = None + + if copy_threshold is None: + copy_threshold = zmq.COPY_THRESHOLD + self.copy_threshold = copy_threshold + + self.handle = NULL + self.context = context + if shadow: + self._shadow = True + self.handle = cast(p_void, shadow) + else: + if context is None: + raise TypeError("context must be specified") + if socket_type < 0: + raise TypeError("socket_type must be specified") + self._shadow = False + self.handle = zmq_socket(self.context.handle, socket_type) + if self.handle == NULL: + raise ZMQError() + self._closed = False + self._pid = getpid() + + @property + def underlying(self): + """The address of the underlying libzmq socket""" + return cast(size_t, self.handle) + + @property + def closed(self): + """Whether the socket is closed""" + return _check_closed_deep(self) + + def close(self, linger=None): + """ + Close the socket. + + If linger is specified, LINGER sockopt will be set prior to closing. + + This can be called to close the socket by hand. If this is not + called, the socket will automatically be closed when it is + garbage collected. + """ + rc: C.int = 0 + linger_c: C.int + setlinger: bint = False + + if linger is not None: + linger_c = linger + setlinger = True + + if self.handle != NULL and not self._closed and getpid() == self._pid: + if setlinger: + zmq_setsockopt(self.handle, ZMQ_LINGER, address(linger_c), sizeof(int)) + rc = zmq_close(self.handle) + if rc < 0 and zmq_errno() != ENOTSOCK: + # ignore ENOTSOCK (closed by Context) + _check_rc(rc) + self._closed = True + self.handle = NULL + + def set(self, option: C.int, optval): + """ + Set socket options. + + See the 0MQ API documentation for details on specific options. + + Parameters + ---------- + option : int + The option to set. Available values will depend on your + version of libzmq. Examples include:: + + zmq.SUBSCRIBE, UNSUBSCRIBE, IDENTITY, HWM, LINGER, FD + + optval : int or bytes + The value of the option to set. + + Notes + ----- + .. warning:: + + All options other than zmq.SUBSCRIBE, zmq.UNSUBSCRIBE and + zmq.LINGER only take effect for subsequent socket bind/connects. + """ + optval_int64_c: int64_t + optval_int_c: C.int + optval_c: p_char + sz: Py_ssize_t + + _check_closed(self) + if isinstance(optval, str): + raise TypeError("unicode not allowed, use setsockopt_string") + + try: + sopt = SocketOption(option) + except ValueError: + # unrecognized option, + # assume from the future, + # let EINVAL raise + opt_type = _OptType.int + else: + opt_type = sopt._opt_type + + if opt_type == _OptType.bytes: + if not isinstance(optval, bytes): + raise TypeError(f'expected bytes, got: {optval!r}') + optval_c = PyBytes_AsString(optval) + sz = PyBytes_Size(optval) + _setsockopt(self.handle, option, optval_c, sz) + elif opt_type == _OptType.int64: + if not isinstance(optval, int): + raise TypeError(f'expected int, got: {optval!r}') + optval_int64_c = optval + _setsockopt(self.handle, option, address(optval_int64_c), sizeof(int64_t)) + else: + # default is to assume int, which is what most new sockopts will be + # this lets pyzmq work with newer libzmq which may add constants + # pyzmq has not yet added, rather than artificially raising. Invalid + # sockopts will still raise just the same, but it will be libzmq doing + # the raising. + if not isinstance(optval, int): + raise TypeError(f'expected int, got: {optval!r}') + optval_int_c = optval + _setsockopt(self.handle, option, address(optval_int_c), sizeof(int)) + + def get(self, option: C.int): + """ + Get the value of a socket option. + + See the 0MQ API documentation for details on specific options. + + Parameters + ---------- + option : int + The option to get. Available values will depend on your + version of libzmq. Examples include:: + + zmq.IDENTITY, HWM, LINGER, FD, EVENTS + + Returns + ------- + optval : int or bytes + The value of the option as a bytestring or int. + """ + optval_int64_c = declare(int64_t) + optval_int_c = declare(C.int) + optval_fd_c = declare(fd_t) + identity_str_c = declare(char[255]) + sz: size_t + + _check_closed(self) + + try: + sopt = SocketOption(option) + except ValueError: + # unrecognized option, + # assume from the future, + # let EINVAL raise + opt_type = _OptType.int + else: + opt_type = sopt._opt_type + + if opt_type == _OptType.bytes: + sz = 255 + _getsockopt(self.handle, option, cast(p_void, identity_str_c), address(sz)) + # strip null-terminated strings *except* identity + if ( + option != ZMQ_IDENTITY + and sz > 0 + and (cast(p_char, identity_str_c))[sz - 1] == b'\0' + ): + sz -= 1 + result = PyBytes_FromStringAndSize(cast(p_char, identity_str_c), sz) + elif opt_type == _OptType.int64: + sz = sizeof(int64_t) + _getsockopt( + self.handle, option, cast(p_void, address(optval_int64_c)), address(sz) + ) + result = optval_int64_c + elif opt_type == _OptType.fd: + sz = sizeof(fd_t) + _getsockopt( + self.handle, option, cast(p_void, address(optval_fd_c)), address(sz) + ) + result = optval_fd_c + else: + # default is to assume int, which is what most new sockopts will be + # this lets pyzmq work with newer libzmq which may add constants + # pyzmq has not yet added, rather than artificially raising. Invalid + # sockopts will still raise just the same, but it will be libzmq doing + # the raising. + sz = sizeof(int) + _getsockopt( + self.handle, option, cast(p_void, address(optval_int_c)), address(sz) + ) + result = optval_int_c + + return result + + def bind(self, addr): + """ + Bind the socket to an address. + + This causes the socket to listen on a network port. Sockets on the + other side of this connection will use ``Socket.connect(addr)`` to + connect to this socket. + + Parameters + ---------- + addr : str + The address string. This has the form 'protocol://interface:port', + for example 'tcp://127.0.0.1:5555'. Protocols supported include + tcp, udp, pgm, epgm, inproc and ipc. If the address is unicode, it is + encoded to utf-8 first. + """ + rc: C.int + c_addr: p_char + + _check_closed(self) + addr_b = addr + if isinstance(addr, str): + addr_b = addr.encode('utf-8') + elif isinstance(addr_b, bytes): + addr = addr_b.decode('utf-8') + + if not isinstance(addr_b, bytes): + raise TypeError(f'expected str, got: {addr!r}') + c_addr = addr_b + rc = zmq_bind(self.handle, c_addr) + if rc != 0: + if IPC_PATH_MAX_LEN and zmq_errno() == ENAMETOOLONG: + path = addr.split('://', 1)[-1] + msg = ( + f'ipc path "{path}" is longer than {IPC_PATH_MAX_LEN} ' + 'characters (sizeof(sockaddr_un.sun_path)). ' + 'zmq.IPC_PATH_MAX_LEN constant can be used ' + 'to check addr length (if it is defined).' + ) + raise ZMQError(msg=msg) + elif zmq_errno() == ENOENT: + path = addr.split('://', 1)[-1] + msg = f'No such file or directory for ipc path "{path}".' + raise ZMQError(msg=msg) + while True: + try: + _check_rc(rc) + except InterruptedSystemCall: + rc = zmq_bind(self.handle, c_addr) + continue + else: + break + + def connect(self, addr): + """ + Connect to a remote 0MQ socket. + + Parameters + ---------- + addr : str + The address string. This has the form 'protocol://interface:port', + for example 'tcp://127.0.0.1:5555'. Protocols supported are + tcp, udp, pgm, inproc and ipc. If the address is unicode, it is + encoded to utf-8 first. + """ + rc: C.int + c_addr: p_char + + _check_closed(self) + if isinstance(addr, str): + addr = addr.encode('utf-8') + if not isinstance(addr, bytes): + raise TypeError(f'expected str, got: {addr!r}') + c_addr = addr + + while True: + try: + rc = zmq_connect(self.handle, c_addr) + _check_rc(rc) + except InterruptedSystemCall: + # retry syscall + continue + else: + break + + def unbind(self, addr): + """ + Unbind from an address (undoes a call to bind). + + .. versionadded:: libzmq-3.2 + .. versionadded:: 13.0 + + Parameters + ---------- + addr : str + The address string. This has the form 'protocol://interface:port', + for example 'tcp://127.0.0.1:5555'. Protocols supported are + tcp, udp, pgm, inproc and ipc. If the address is unicode, it is + encoded to utf-8 first. + """ + rc: C.int + c_addr: p_char + + _check_version((3, 2), "unbind") + _check_closed(self) + if isinstance(addr, str): + addr = addr.encode('utf-8') + if not isinstance(addr, bytes): + raise TypeError(f'expected str, got: {addr!r}') + c_addr = addr + + rc = zmq_unbind(self.handle, c_addr) + if rc != 0: + raise ZMQError() + + def disconnect(self, addr): + """ + Disconnect from a remote 0MQ socket (undoes a call to connect). + + .. versionadded:: libzmq-3.2 + .. versionadded:: 13.0 + + Parameters + ---------- + addr : str + The address string. This has the form 'protocol://interface:port', + for example 'tcp://127.0.0.1:5555'. Protocols supported are + tcp, udp, pgm, inproc and ipc. If the address is unicode, it is + encoded to utf-8 first. + """ + rc: C.int + c_addr: p_char + + _check_version((3, 2), "disconnect") + _check_closed(self) + if isinstance(addr, str): + addr = addr.encode('utf-8') + if not isinstance(addr, bytes): + raise TypeError(f'expected str, got: {addr!r}') + c_addr = addr + + rc = zmq_disconnect(self.handle, c_addr) + if rc != 0: + raise ZMQError() + + def monitor(self, addr, events: C.int = ZMQ_EVENT_ALL): + """ + Start publishing socket events on inproc. + See libzmq docs for zmq_monitor for details. + + While this function is available from libzmq 3.2, + pyzmq cannot parse monitor messages from libzmq prior to 4.0. + + .. versionadded: libzmq-3.2 + .. versionadded: 14.0 + + Parameters + ---------- + addr : str + The inproc url used for monitoring. Passing None as + the addr will cause an existing socket monitor to be + deregistered. + events : int + default: zmq.EVENT_ALL + The zmq event bitmask for which events will be sent to the monitor. + """ + _check_version((3, 2), "monitor") + + if isinstance(addr, str): + # cast str to utf8 bytes + addr = addr.encode("utf-8") + + # cast bytes to char* + c_addr: p_char + + if addr is None: + c_addr = NULL + else: + # let Cython do the casting, + # but return a nicer error message if it fails + try: + c_addr = addr + except TypeError: + raise TypeError(f"Monitor addr must be str, got {addr!r}") from None + + _check_rc(zmq_socket_monitor(self.handle, c_addr, events)) + + def join(self, group): + """ + Join a RADIO-DISH group + + Only for DISH sockets. + + libzmq and pyzmq must have been built with ZMQ_BUILD_DRAFT_API + + .. versionadded:: 17 + """ + _check_version((4, 2), "RADIO-DISH") + if not zmq.has('draft'): + raise RuntimeError("libzmq must be built with draft support") + if isinstance(group, str): + group = group.encode('utf8') + rc: C.int = zmq_join(self.handle, group) + _check_rc(rc) + + def leave(self, group): + """ + Leave a RADIO-DISH group + + Only for DISH sockets. + + libzmq and pyzmq must have been built with ZMQ_BUILD_DRAFT_API + + .. versionadded:: 17 + """ + _check_version((4, 2), "RADIO-DISH") + if not zmq.has('draft'): + raise RuntimeError("libzmq must be built with draft support") + rc: C.int = zmq_leave(self.handle, group) + _check_rc(rc) + + def send(self, data, flags=0, copy: bint = True, track: bint = False): + """ + Send a single zmq message frame on this socket. + + This queues the message to be sent by the IO thread at a later time. + + With flags=NOBLOCK, this raises :class:`ZMQError` if the queue is full; + otherwise, this waits until space is available. + See :class:`Poller` for more general non-blocking I/O. + + Parameters + ---------- + data : bytes, Frame, memoryview + The content of the message. This can be any object that provides + the Python buffer API (`memoryview(data)` can be called). + flags : int + 0, NOBLOCK, SNDMORE, or NOBLOCK|SNDMORE. + copy : bool + Should the message be sent in a copying or non-copying manner. + track : bool + Should the message be tracked for notification that ZMQ has + finished with it? (ignored if copy=True) + + Returns + ------- + None : if `copy` or not track + None if message was sent, raises an exception otherwise. + MessageTracker : if track and not copy + a MessageTracker object, whose `done` property will + be False until the send is completed. + + Raises + ------ + TypeError + If a unicode object is passed + ValueError + If `track=True`, but an untracked Frame is passed. + ZMQError + for any of the reasons zmq_msg_send might fail (including + if NOBLOCK is set and the outgoing queue is full). + + """ + _check_closed(self) + + if isinstance(data, str): + raise TypeError("unicode not allowed, use send_string") + + if copy and not isinstance(data, Frame): + return _send_copy(self.handle, data, flags) + else: + if isinstance(data, Frame): + if track and not data.tracker: + raise ValueError('Not a tracked message') + msg = data + else: + if self.copy_threshold: + buf = memoryview(data) + # always copy messages smaller than copy_threshold + if buf.nbytes < self.copy_threshold: + _send_copy(self.handle, buf, flags) + return zmq._FINISHED_TRACKER + msg = Frame(data, track=track, copy_threshold=self.copy_threshold) + return _send_frame(self.handle, msg, flags) + + def recv(self, flags=0, copy: bint = True, track: bint = False): + """ + Receive a message. + + With flags=NOBLOCK, this raises :class:`ZMQError` if no messages have + arrived; otherwise, this waits until a message arrives. + See :class:`Poller` for more general non-blocking I/O. + + Parameters + ---------- + flags : int + 0 or NOBLOCK. + copy : bool + Should the message be received in a copying or non-copying manner? + If False a Frame object is returned, if True a string copy of + message is returned. + track : bool + Should the message be tracked for notification that ZMQ has + finished with it? (ignored if copy=True) + + Returns + ------- + msg : bytes or Frame + The received message frame. If `copy` is False, then it will be a Frame, + otherwise it will be bytes. + + Raises + ------ + ZMQError + for any of the reasons zmq_msg_recv might fail (including if + NOBLOCK is set and no new messages have arrived). + """ + _check_closed(self) + + if copy: + return _recv_copy(self.handle, flags) + else: + frame = _recv_frame(self.handle, flags, track) + frame.more = self.get(zmq.RCVMORE) + return frame + + +# inline socket methods + + +@inline +@cfunc +def _check_closed(s: Socket): + """raise ENOTSUP if socket is closed + + Does not do a deep check + """ + if s._closed: + raise ZMQError(ENOTSOCK) + + +@inline +@cfunc +def _check_closed_deep(s: Socket) -> bint: + """thorough check of whether the socket has been closed, + even if by another entity (e.g. ctx.destroy). + + Only used by the `closed` property. + + returns True if closed, False otherwise + """ + rc: C.int + errno: C.int + stype = declare(C.int) + sz: size_t = sizeof(int) + + if s._closed: + return True + else: + rc = zmq_getsockopt( + s.handle, ZMQ_TYPE, cast(p_void, address(stype)), address(sz) + ) + if rc < 0: + errno = zmq_errno() + if errno == ENOTSOCK: + s._closed = True + return True + elif errno == ZMQ_ETERM: + # don't raise ETERM when checking if we're closed + return False + else: + _check_rc(rc) + return False + + +@cfunc +@inline +def _recv_frame(handle: p_void, flags: C.int = 0, track: bint = False) -> Frame: + """Receive a message in a non-copying manner and return a Frame.""" + rc: C.int + msg = zmq.Frame(track=track) + cmsg: Frame = msg + + while True: + with nogil: + rc = zmq_msg_recv(address(cmsg.zmq_msg), handle, flags) + try: + _check_rc(rc) + except InterruptedSystemCall: + continue + else: + break + return msg + + +@cfunc +@inline +def _recv_copy(handle: p_void, flags: C.int = 0): + """Receive a message and return a copy""" + zmq_msg = declare(zmq_msg_t) + zmq_msg_p: pointer(zmq_msg_t) = address(zmq_msg) + rc: C.int = zmq_msg_init(zmq_msg_p) + _check_rc(rc) + while True: + with nogil: + rc = zmq_msg_recv(zmq_msg_p, handle, flags) + try: + _check_rc(rc) + except InterruptedSystemCall: + continue + except Exception: + zmq_msg_close(zmq_msg_p) # ensure msg is closed on failure + raise + else: + break + + msg_bytes = _copy_zmq_msg_bytes(zmq_msg_p) + zmq_msg_close(zmq_msg_p) + return msg_bytes + + +@cfunc +@inline +def _send_frame(handle: p_void, msg: Frame, flags: C.int = 0): + """Send a Frame on this socket in a non-copy manner.""" + rc: C.int + msg_copy: Frame + + # Always copy so the original message isn't garbage collected. + # This doesn't do a real copy, just a reference. + msg_copy = msg.fast_copy() + + while True: + with nogil: + rc = zmq_msg_send(address(msg_copy.zmq_msg), handle, flags) + try: + _check_rc(rc) + except InterruptedSystemCall: + continue + else: + break + + return msg.tracker + + +@cfunc +@inline +def _send_copy(handle: p_void, buf, flags: C.int = 0): + """Send a message on this socket by copying its content.""" + rc: C.int + msg = declare(zmq_msg_t) + c_bytes = declare(p_char) + c_bytes_len: Py_ssize_t = 0 + + # copy to c array: + asbuffer_r(buf, cast(pointer(p_void), address(c_bytes)), address(c_bytes_len)) + + # Copy the msg before sending. This avoids any complications with + # the GIL, etc. + # If zmq_msg_init_* fails we must not call zmq_msg_close (Bus Error) + rc = zmq_msg_init_size(address(msg), c_bytes_len) + _check_rc(rc) + + while True: + with nogil: + memcpy(zmq_msg_data(address(msg)), c_bytes, zmq_msg_size(address(msg))) + rc = zmq_msg_send(address(msg), handle, flags) + try: + _check_rc(rc) + except InterruptedSystemCall: + continue + except Exception: + zmq_msg_close(address(msg)) # close the unused msg + raise # raise original exception + else: + rc = zmq_msg_close(address(msg)) + _check_rc(rc) + break + + +@cfunc +@inline +def _getsockopt(handle: p_void, option: C.int, optval: p_void, sz: pointer(size_t)): + """getsockopt, retrying interrupted calls + + checks rc, raising ZMQError on failure. + """ + rc: C.int = 0 + while True: + rc = zmq_getsockopt(handle, option, optval, sz) + try: + _check_rc(rc) + except InterruptedSystemCall: + continue + else: + break + + +@cfunc +@inline +def _setsockopt(handle: p_void, option: C.int, optval: p_void, sz: size_t): + """setsockopt, retrying interrupted calls + + checks rc, raising ZMQError on failure. + """ + rc: C.int = 0 + while True: + rc = zmq_setsockopt(handle, option, optval, sz) + try: + _check_rc(rc) + except InterruptedSystemCall: + continue + else: + break + + +# General utility functions + + +def zmq_errno(): + """Return the integer errno of the most recent zmq error.""" + return _zmq_errno() + + +def strerror(errno: C.int) -> str: + """ + Return the error string given the error number. + """ + str_e: bytes = zmq_strerror(errno) + return str_e.decode("utf8", "replace") + + +def zmq_version_info() -> tuple[int, int, int]: + """Return the version of ZeroMQ itself as a 3-tuple of ints.""" + major: C.int = 0 + minor: C.int = 0 + patch: C.int = 0 + _zmq_version(address(major), address(minor), address(patch)) + return (major, minor, patch) + + +def has(capability) -> bool: + """Check for zmq capability by name (e.g. 'ipc', 'curve') + + .. versionadded:: libzmq-4.1 + .. versionadded:: 14.1 + """ + _check_version((4, 1), 'zmq.has') + ccap: bytes + if isinstance(capability, str): + capability = capability.encode('utf8') + ccap = capability + return bool(zmq_has(ccap)) + + +def curve_keypair() -> tuple[bytes, bytes]: + """generate a Z85 key pair for use with zmq.CURVE security + + Requires libzmq (≥ 4.0) to have been built with CURVE support. + + .. versionadded:: libzmq-4.0 + .. versionadded:: 14.0 + + Returns + ------- + public: bytes + The public key as 40 byte z85-encoded bytestring. + private: bytes + The private key as 40 byte z85-encoded bytestring. + """ + rc: C.int + public_key = declare(char[64]) + secret_key = declare(char[64]) + _check_version((4, 0), "curve_keypair") + # see huge comment in libzmq/src/random.cpp + # about threadsafety of random initialization + rc = zmq_curve_keypair(public_key, secret_key) + _check_rc(rc) + return public_key, secret_key + + +def curve_public(secret_key) -> bytes: + """Compute the public key corresponding to a secret key for use + with zmq.CURVE security + + Requires libzmq (≥ 4.2) to have been built with CURVE support. + + Parameters + ---------- + private + The private key as a 40 byte z85-encoded bytestring + + Returns + ------- + bytes + The public key as a 40 byte z85-encoded bytestring + """ + if isinstance(secret_key, str): + secret_key = secret_key.encode('utf8') + if not len(secret_key) == 40: + raise ValueError('secret key must be a 40 byte z85 encoded string') + + rc: C.int + public_key = declare(char[64]) + c_secret_key: pointer(char) = secret_key + _check_version((4, 2), "curve_public") + # see huge comment in libzmq/src/random.cpp + # about threadsafety of random initialization + rc = zmq_curve_public(public_key, c_secret_key) + _check_rc(rc) + return public_key[:40] + + +# polling +def zmq_poll(sockets, timeout: C.int = -1): + """zmq_poll(sockets, timeout=-1) + + Poll a set of 0MQ sockets, native file descs. or sockets. + + Parameters + ---------- + sockets : list of tuples of (socket, flags) + Each element of this list is a two-tuple containing a socket + and a flags. The socket may be a 0MQ socket or any object with + a ``fileno()`` method. The flags can be zmq.POLLIN (for detecting + for incoming messages), zmq.POLLOUT (for detecting that send is OK) + or zmq.POLLIN|zmq.POLLOUT for detecting both. + timeout : int + The number of milliseconds to poll for. Negative means no timeout. + """ + rc: C.int + i: C.int + pollitems: pointer(zmq_pollitem_t) = NULL + nsockets: C.int = len(sockets) + + if nsockets == 0: + return [] + + pollitems = cast(pointer(zmq_pollitem_t), malloc(nsockets * sizeof(zmq_pollitem_t))) + if pollitems == NULL: + raise MemoryError("Could not allocate poll items") + + if ZMQ_VERSION_MAJOR < 3: + # timeout is us in 2.x, ms in 3.x + # expected input is ms (matches 3.x) + timeout = 1000 * timeout + + for i in range(nsockets): + s, events = sockets[i] + if isinstance(s, Socket): + pollitems[i].socket = cast(Socket, s).handle + pollitems[i].fd = 0 + pollitems[i].events = events + pollitems[i].revents = 0 + elif isinstance(s, int): + pollitems[i].socket = NULL + pollitems[i].fd = s + pollitems[i].events = events + pollitems[i].revents = 0 + elif hasattr(s, 'fileno'): + try: + fileno = int(s.fileno()) + except Exception: + free(pollitems) + raise ValueError('fileno() must return a valid integer fd') + else: + pollitems[i].socket = NULL + pollitems[i].fd = fileno + pollitems[i].events = events + pollitems[i].revents = 0 + else: + free(pollitems) + raise TypeError( + "Socket must be a 0MQ socket, an integer fd or have " + f"a fileno() method: {s!r}" + ) + + ms_passed: int = 0 + try: + while True: + start = time.monotonic() + with nogil: + rc = zmq_poll_c(pollitems, nsockets, timeout) + try: + _check_rc(rc) + except InterruptedSystemCall: + if timeout > 0: + ms_passed = int(1000 * (time.monotonic() - start)) + if ms_passed < 0: + # don't allow negative ms_passed, + # which can happen on old Python versions without time.monotonic. + warnings.warn( + f"Negative elapsed time for interrupted poll: {ms_passed}." + " Did the clock change?", + RuntimeWarning, + ) + # treat this case the same as no time passing, + # since it should be rare and not happen twice in a row. + ms_passed = 0 + timeout = max(0, timeout - ms_passed) + continue + else: + break + except Exception: + free(pollitems) + raise + + results = [] + for i in range(nsockets): + revents = pollitems[i].revents + # for compatibility with select.poll: + # - only return sockets with non-zero status + # - return the fd for plain sockets + if revents > 0: + if pollitems[i].socket != NULL: + s = sockets[i][0] + else: + s = pollitems[i].fd + results.append((s, revents)) + + free(pollitems) + return results + + +# device functions + + +def device(device_type: C.int, frontend: Socket, backend: Socket = None): + """ + Start a zeromq device. + + .. deprecated:: libzmq-3.2 + Use zmq.proxy + + Parameters + ---------- + device_type : int + one of: QUEUE, FORWARDER, STREAMER + The type of device to start. + frontend : Socket + The Socket instance for the incoming traffic. + backend : Socket + The Socket instance for the outbound traffic. + """ + if ZMQ_VERSION_MAJOR >= 3: + return proxy(frontend, backend) + + rc: C.int = 0 + while True: + with nogil: + rc = zmq_device(device_type, frontend.handle, backend.handle) + try: + _check_rc(rc) + except InterruptedSystemCall: + continue + else: + break + return rc + + +def proxy(frontend: Socket, backend: Socket, capture: Socket = None): + """ + Start a zeromq proxy (replacement for device). + + .. versionadded:: libzmq-3.2 + .. versionadded:: 13.0 + + Parameters + ---------- + frontend : Socket + The Socket instance for the incoming traffic. + backend : Socket + The Socket instance for the outbound traffic. + capture : Socket (optional) + The Socket instance for capturing traffic. + """ + rc: C.int = 0 + capture_handle: p_void + if isinstance(capture, Socket): + capture_handle = capture.handle + else: + capture_handle = NULL + while True: + with nogil: + rc = zmq_proxy(frontend.handle, backend.handle, capture_handle) + try: + _check_rc(rc) + except InterruptedSystemCall: + continue + else: + break + return rc + + +def proxy_steerable( + frontend: Socket, + backend: Socket, + capture: Socket = None, + control: Socket = None, +): + """ + Start a zeromq proxy with control flow. + + .. versionadded:: libzmq-4.1 + .. versionadded:: 18.0 + + Parameters + ---------- + frontend : Socket + The Socket instance for the incoming traffic. + backend : Socket + The Socket instance for the outbound traffic. + capture : Socket (optional) + The Socket instance for capturing traffic. + control : Socket (optional) + The Socket instance for control flow. + """ + rc: C.int = 0 + capture_handle: p_void + if isinstance(capture, Socket): + capture_handle = capture.handle + else: + capture_handle = NULL + if isinstance(control, Socket): + control_handle = control.handle + else: + control_handle = NULL + while True: + with nogil: + rc = zmq_proxy_steerable( + frontend.handle, backend.handle, capture_handle, control_handle + ) + try: + _check_rc(rc) + except InterruptedSystemCall: + continue + else: + break + return rc + + +# monitored queue - like proxy (predates libzmq proxy) +# but supports ROUTER-ROUTER devices +@cfunc +@inline +@nogil +def _mq_relay( + in_socket: p_void, + out_socket: p_void, + side_socket: p_void, + msg: zmq_msg_t, + side_msg: zmq_msg_t, + id_msg: zmq_msg_t, + swap_ids: bint, +) -> C.int: + rc: C.int + flags: C.int + flagsz = declare(size_t) + more = declare(int) + flagsz = sizeof(int) + + if swap_ids: # both router, must send second identity first + # recv two ids into msg, id_msg + rc = zmq_msg_recv(address(msg), in_socket, 0) + if rc < 0: + return rc + + rc = zmq_msg_recv(address(id_msg), in_socket, 0) + if rc < 0: + return rc + + # send second id (id_msg) first + # !!!! always send a copy before the original !!!! + rc = zmq_msg_copy(address(side_msg), address(id_msg)) + if rc < 0: + return rc + rc = zmq_msg_send(address(side_msg), out_socket, ZMQ_SNDMORE) + if rc < 0: + return rc + rc = zmq_msg_send(address(id_msg), side_socket, ZMQ_SNDMORE) + if rc < 0: + return rc + # send first id (msg) second + rc = zmq_msg_copy(address(side_msg), address(msg)) + if rc < 0: + return rc + rc = zmq_msg_send(address(side_msg), out_socket, ZMQ_SNDMORE) + if rc < 0: + return rc + rc = zmq_msg_send(address(msg), side_socket, ZMQ_SNDMORE) + if rc < 0: + return rc + while True: + rc = zmq_msg_recv(address(msg), in_socket, 0) + if rc < 0: + return rc + # assert (rc == 0) + rc = zmq_getsockopt(in_socket, ZMQ_RCVMORE, address(more), address(flagsz)) + if rc < 0: + return rc + flags = 0 + if more: + flags |= ZMQ_SNDMORE + + rc = zmq_msg_copy(address(side_msg), address(msg)) + if rc < 0: + return rc + if flags: + rc = zmq_msg_send(address(side_msg), out_socket, flags) + if rc < 0: + return rc + # only SNDMORE for side-socket + rc = zmq_msg_send(address(msg), side_socket, ZMQ_SNDMORE) + if rc < 0: + return rc + else: + rc = zmq_msg_send(address(side_msg), out_socket, 0) + if rc < 0: + return rc + rc = zmq_msg_send(address(msg), side_socket, 0) + if rc < 0: + return rc + break + return rc + + +@cfunc +@inline +@nogil +def _mq_inline( + in_socket: p_void, + out_socket: p_void, + side_socket: p_void, + in_msg_ptr: pointer(zmq_msg_t), + out_msg_ptr: pointer(zmq_msg_t), + swap_ids: bint, +) -> C.int: + """ + inner C function for monitored_queue + """ + + msg: zmq_msg_t = declare(zmq_msg_t) + rc: C.int = zmq_msg_init(address(msg)) + id_msg = declare(zmq_msg_t) + rc = zmq_msg_init(address(id_msg)) + if rc < 0: + return rc + side_msg = declare(zmq_msg_t) + rc = zmq_msg_init(address(side_msg)) + if rc < 0: + return rc + + items = declare(zmq_pollitem_t[2]) + items[0].socket = in_socket + items[0].events = ZMQ_POLLIN + items[0].fd = items[0].revents = 0 + items[1].socket = out_socket + items[1].events = ZMQ_POLLIN + items[1].fd = items[1].revents = 0 + + while True: + # wait for the next message to process + rc = zmq_poll_c(address(items[0]), 2, -1) + if rc < 0: + return rc + if items[0].revents & ZMQ_POLLIN: + # send in_prefix to side socket + rc = zmq_msg_copy(address(side_msg), in_msg_ptr) + if rc < 0: + return rc + rc = zmq_msg_send(address(side_msg), side_socket, ZMQ_SNDMORE) + if rc < 0: + return rc + # relay the rest of the message + rc = _mq_relay( + in_socket, out_socket, side_socket, msg, side_msg, id_msg, swap_ids + ) + if rc < 0: + return rc + if items[1].revents & ZMQ_POLLIN: + # send out_prefix to side socket + rc = zmq_msg_copy(address(side_msg), out_msg_ptr) + if rc < 0: + return rc + rc = zmq_msg_send(address(side_msg), side_socket, ZMQ_SNDMORE) + if rc < 0: + return rc + # relay the rest of the message + rc = _mq_relay( + out_socket, in_socket, side_socket, msg, side_msg, id_msg, swap_ids + ) + if rc < 0: + return rc + return rc + + +def monitored_queue( + in_socket: Socket, + out_socket: Socket, + mon_socket: Socket, + in_prefix: bytes = b'in', + out_prefix: bytes = b'out', +): + """ + Start a monitored queue device. + + A monitored queue is very similar to the zmq.proxy device (monitored queue came first). + + Differences from zmq.proxy: + + - monitored_queue supports both in and out being ROUTER sockets + (via swapping IDENTITY prefixes). + - monitor messages are prefixed, making in and out messages distinguishable. + + Parameters + ---------- + in_socket : zmq.Socket + One of the sockets to the Queue. Its messages will be prefixed with + 'in'. + out_socket : zmq.Socket + One of the sockets to the Queue. Its messages will be prefixed with + 'out'. The only difference between in/out socket is this prefix. + mon_socket : zmq.Socket + This socket sends out every message received by each of the others + with an in/out prefix specifying which one it was. + in_prefix : str + Prefix added to broadcast messages from in_socket. + out_prefix : str + Prefix added to broadcast messages from out_socket. + """ + ins: p_void = in_socket.handle + outs: p_void = out_socket.handle + mons: p_void = mon_socket.handle + in_msg = declare(zmq_msg_t) + out_msg = declare(zmq_msg_t) + swap_ids: bint + msg_c: p_char = NULL + msg_c_len = declare(Py_ssize_t) + rc: C.int + + # force swap_ids if both ROUTERs + swap_ids = in_socket.type == ZMQ_ROUTER and out_socket.type == ZMQ_ROUTER + + # build zmq_msg objects from str prefixes + asbuffer_r(in_prefix, cast(pointer(p_void), address(msg_c)), address(msg_c_len)) + rc = zmq_msg_init_size(address(in_msg), msg_c_len) + _check_rc(rc) + + memcpy(zmq_msg_data(address(in_msg)), msg_c, zmq_msg_size(address(in_msg))) + + asbuffer_r(out_prefix, cast(pointer(p_void), address(msg_c)), address(msg_c_len)) + + rc = zmq_msg_init_size(address(out_msg), msg_c_len) + _check_rc(rc) + + while True: + with nogil: + memcpy( + zmq_msg_data(address(out_msg)), msg_c, zmq_msg_size(address(out_msg)) + ) + rc = _mq_inline( + ins, outs, mons, address(in_msg), address(out_msg), swap_ids + ) + try: + _check_rc(rc) + except InterruptedSystemCall: + continue + else: + break + return rc + + +__all__ = [ + 'IPC_PATH_MAX_LEN', + 'Context', + 'Socket', + 'Frame', + 'has', + 'curve_keypair', + 'curve_public', + 'zmq_version_info', + 'zmq_errno', + 'zmq_poll', + 'strerror', + 'device', + 'proxy', + 'proxy_steerable', +] diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cython/constant_enums.pxi b/.venv/lib/python3.11/site-packages/zmq/backend/cython/constant_enums.pxi new file mode 100644 index 0000000000000000000000000000000000000000..811d2a84f56f1b96d98a3f4a57cfbacf50c69a08 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/cython/constant_enums.pxi @@ -0,0 +1,250 @@ +cdef extern from "zmq.h" nogil: + enum: PYZMQ_DRAFT_API + enum: ZMQ_VERSION + enum: ZMQ_VERSION_MAJOR + enum: ZMQ_VERSION_MINOR + enum: ZMQ_VERSION_PATCH + enum: ZMQ_IO_THREADS + enum: ZMQ_MAX_SOCKETS + enum: ZMQ_SOCKET_LIMIT + enum: ZMQ_THREAD_PRIORITY + enum: ZMQ_THREAD_SCHED_POLICY + enum: ZMQ_MAX_MSGSZ + enum: ZMQ_MSG_T_SIZE + enum: ZMQ_THREAD_AFFINITY_CPU_ADD + enum: ZMQ_THREAD_AFFINITY_CPU_REMOVE + enum: ZMQ_THREAD_NAME_PREFIX + enum: ZMQ_STREAMER + enum: ZMQ_FORWARDER + enum: ZMQ_QUEUE + enum: ZMQ_EAGAIN "EAGAIN" + enum: ZMQ_EFAULT "EFAULT" + enum: ZMQ_EINVAL "EINVAL" + enum: ZMQ_ENOTSUP "ENOTSUP" + enum: ZMQ_EPROTONOSUPPORT "EPROTONOSUPPORT" + enum: ZMQ_ENOBUFS "ENOBUFS" + enum: ZMQ_ENETDOWN "ENETDOWN" + enum: ZMQ_EADDRINUSE "EADDRINUSE" + enum: ZMQ_EADDRNOTAVAIL "EADDRNOTAVAIL" + enum: ZMQ_ECONNREFUSED "ECONNREFUSED" + enum: ZMQ_EINPROGRESS "EINPROGRESS" + enum: ZMQ_ENOTSOCK "ENOTSOCK" + enum: ZMQ_EMSGSIZE "EMSGSIZE" + enum: ZMQ_EAFNOSUPPORT "EAFNOSUPPORT" + enum: ZMQ_ENETUNREACH "ENETUNREACH" + enum: ZMQ_ECONNABORTED "ECONNABORTED" + enum: ZMQ_ECONNRESET "ECONNRESET" + enum: ZMQ_ENOTCONN "ENOTCONN" + enum: ZMQ_ETIMEDOUT "ETIMEDOUT" + enum: ZMQ_EHOSTUNREACH "EHOSTUNREACH" + enum: ZMQ_ENETRESET "ENETRESET" + enum: ZMQ_EFSM "EFSM" + enum: ZMQ_ENOCOMPATPROTO "ENOCOMPATPROTO" + enum: ZMQ_ETERM "ETERM" + enum: ZMQ_EMTHREAD "EMTHREAD" + enum: ZMQ_PROTOCOL_ERROR_WS_UNSPECIFIED + enum: ZMQ_PROTOCOL_ERROR_ZMTP_UNSPECIFIED + enum: ZMQ_PROTOCOL_ERROR_ZMTP_UNEXPECTED_COMMAND + enum: ZMQ_PROTOCOL_ERROR_ZMTP_INVALID_SEQUENCE + enum: ZMQ_PROTOCOL_ERROR_ZMTP_KEY_EXCHANGE + enum: ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_UNSPECIFIED + enum: ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_MESSAGE + enum: ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_HELLO + enum: ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_INITIATE + enum: ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_ERROR + enum: ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_READY + enum: ZMQ_PROTOCOL_ERROR_ZMTP_MALFORMED_COMMAND_WELCOME + enum: ZMQ_PROTOCOL_ERROR_ZMTP_INVALID_METADATA + enum: ZMQ_PROTOCOL_ERROR_ZMTP_CRYPTOGRAPHIC + enum: ZMQ_PROTOCOL_ERROR_ZMTP_MECHANISM_MISMATCH + enum: ZMQ_PROTOCOL_ERROR_ZAP_UNSPECIFIED + enum: ZMQ_PROTOCOL_ERROR_ZAP_MALFORMED_REPLY + enum: ZMQ_PROTOCOL_ERROR_ZAP_BAD_REQUEST_ID + enum: ZMQ_PROTOCOL_ERROR_ZAP_BAD_VERSION + enum: ZMQ_PROTOCOL_ERROR_ZAP_INVALID_STATUS_CODE + enum: ZMQ_PROTOCOL_ERROR_ZAP_INVALID_METADATA + enum: ZMQ_EVENT_CONNECTED + enum: ZMQ_EVENT_CONNECT_DELAYED + enum: ZMQ_EVENT_CONNECT_RETRIED + enum: ZMQ_EVENT_LISTENING + enum: ZMQ_EVENT_BIND_FAILED + enum: ZMQ_EVENT_ACCEPTED + enum: ZMQ_EVENT_ACCEPT_FAILED + enum: ZMQ_EVENT_CLOSED + enum: ZMQ_EVENT_CLOSE_FAILED + enum: ZMQ_EVENT_DISCONNECTED + enum: ZMQ_EVENT_MONITOR_STOPPED + enum: ZMQ_EVENT_HANDSHAKE_FAILED_NO_DETAIL + enum: ZMQ_EVENT_HANDSHAKE_SUCCEEDED + enum: ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL + enum: ZMQ_EVENT_HANDSHAKE_FAILED_AUTH + enum: ZMQ_EVENT_ALL_V1 + enum: ZMQ_EVENT_ALL + enum: ZMQ_EVENT_PIPES_STATS + enum: ZMQ_EVENT_ALL_V2 + enum: ZMQ_DONTWAIT + enum: ZMQ_SNDMORE + enum: ZMQ_NOBLOCK + enum: ZMQ_MORE + enum: ZMQ_SHARED + enum: ZMQ_SRCFD + enum: ZMQ_NORM_FIXED + enum: ZMQ_NORM_CC + enum: ZMQ_NORM_CCL + enum: ZMQ_NORM_CCE + enum: ZMQ_NORM_CCE_ECNONLY + enum: ZMQ_POLLIN + enum: ZMQ_POLLOUT + enum: ZMQ_POLLERR + enum: ZMQ_POLLPRI + enum: ZMQ_RECONNECT_STOP_CONN_REFUSED + enum: ZMQ_RECONNECT_STOP_HANDSHAKE_FAILED + enum: ZMQ_RECONNECT_STOP_AFTER_DISCONNECT + enum: ZMQ_NOTIFY_CONNECT + enum: ZMQ_NOTIFY_DISCONNECT + enum: ZMQ_NULL + enum: ZMQ_PLAIN + enum: ZMQ_CURVE + enum: ZMQ_GSSAPI + enum: ZMQ_HWM + enum: ZMQ_AFFINITY + enum: ZMQ_ROUTING_ID + enum: ZMQ_SUBSCRIBE + enum: ZMQ_UNSUBSCRIBE + enum: ZMQ_RATE + enum: ZMQ_RECOVERY_IVL + enum: ZMQ_SNDBUF + enum: ZMQ_RCVBUF + enum: ZMQ_RCVMORE + enum: ZMQ_FD + enum: ZMQ_EVENTS + enum: ZMQ_TYPE + enum: ZMQ_LINGER + enum: ZMQ_RECONNECT_IVL + enum: ZMQ_BACKLOG + enum: ZMQ_RECONNECT_IVL_MAX + enum: ZMQ_MAXMSGSIZE + enum: ZMQ_SNDHWM + enum: ZMQ_RCVHWM + enum: ZMQ_MULTICAST_HOPS + enum: ZMQ_RCVTIMEO + enum: ZMQ_SNDTIMEO + enum: ZMQ_LAST_ENDPOINT + enum: ZMQ_ROUTER_MANDATORY + enum: ZMQ_TCP_KEEPALIVE + enum: ZMQ_TCP_KEEPALIVE_CNT + enum: ZMQ_TCP_KEEPALIVE_IDLE + enum: ZMQ_TCP_KEEPALIVE_INTVL + enum: ZMQ_IMMEDIATE + enum: ZMQ_XPUB_VERBOSE + enum: ZMQ_ROUTER_RAW + enum: ZMQ_IPV6 + enum: ZMQ_MECHANISM + enum: ZMQ_PLAIN_SERVER + enum: ZMQ_PLAIN_USERNAME + enum: ZMQ_PLAIN_PASSWORD + enum: ZMQ_CURVE_SERVER + enum: ZMQ_CURVE_PUBLICKEY + enum: ZMQ_CURVE_SECRETKEY + enum: ZMQ_CURVE_SERVERKEY + enum: ZMQ_PROBE_ROUTER + enum: ZMQ_REQ_CORRELATE + enum: ZMQ_REQ_RELAXED + enum: ZMQ_CONFLATE + enum: ZMQ_ZAP_DOMAIN + enum: ZMQ_ROUTER_HANDOVER + enum: ZMQ_TOS + enum: ZMQ_CONNECT_ROUTING_ID + enum: ZMQ_GSSAPI_SERVER + enum: ZMQ_GSSAPI_PRINCIPAL + enum: ZMQ_GSSAPI_SERVICE_PRINCIPAL + enum: ZMQ_GSSAPI_PLAINTEXT + enum: ZMQ_HANDSHAKE_IVL + enum: ZMQ_SOCKS_PROXY + enum: ZMQ_XPUB_NODROP + enum: ZMQ_BLOCKY + enum: ZMQ_XPUB_MANUAL + enum: ZMQ_XPUB_WELCOME_MSG + enum: ZMQ_STREAM_NOTIFY + enum: ZMQ_INVERT_MATCHING + enum: ZMQ_HEARTBEAT_IVL + enum: ZMQ_HEARTBEAT_TTL + enum: ZMQ_HEARTBEAT_TIMEOUT + enum: ZMQ_XPUB_VERBOSER + enum: ZMQ_CONNECT_TIMEOUT + enum: ZMQ_TCP_MAXRT + enum: ZMQ_THREAD_SAFE + enum: ZMQ_MULTICAST_MAXTPDU + enum: ZMQ_VMCI_BUFFER_SIZE + enum: ZMQ_VMCI_BUFFER_MIN_SIZE + enum: ZMQ_VMCI_BUFFER_MAX_SIZE + enum: ZMQ_VMCI_CONNECT_TIMEOUT + enum: ZMQ_USE_FD + enum: ZMQ_GSSAPI_PRINCIPAL_NAMETYPE + enum: ZMQ_GSSAPI_SERVICE_PRINCIPAL_NAMETYPE + enum: ZMQ_BINDTODEVICE + enum: ZMQ_IDENTITY + enum: ZMQ_CONNECT_RID + enum: ZMQ_TCP_ACCEPT_FILTER + enum: ZMQ_IPC_FILTER_PID + enum: ZMQ_IPC_FILTER_UID + enum: ZMQ_IPC_FILTER_GID + enum: ZMQ_IPV4ONLY + enum: ZMQ_DELAY_ATTACH_ON_CONNECT + enum: ZMQ_FAIL_UNROUTABLE + enum: ZMQ_ROUTER_BEHAVIOR + enum: ZMQ_ZAP_ENFORCE_DOMAIN + enum: ZMQ_LOOPBACK_FASTPATH + enum: ZMQ_METADATA + enum: ZMQ_MULTICAST_LOOP + enum: ZMQ_ROUTER_NOTIFY + enum: ZMQ_XPUB_MANUAL_LAST_VALUE + enum: ZMQ_SOCKS_USERNAME + enum: ZMQ_SOCKS_PASSWORD + enum: ZMQ_IN_BATCH_SIZE + enum: ZMQ_OUT_BATCH_SIZE + enum: ZMQ_WSS_KEY_PEM + enum: ZMQ_WSS_CERT_PEM + enum: ZMQ_WSS_TRUST_PEM + enum: ZMQ_WSS_HOSTNAME + enum: ZMQ_WSS_TRUST_SYSTEM + enum: ZMQ_ONLY_FIRST_SUBSCRIBE + enum: ZMQ_RECONNECT_STOP + enum: ZMQ_HELLO_MSG + enum: ZMQ_DISCONNECT_MSG + enum: ZMQ_PRIORITY + enum: ZMQ_BUSY_POLL + enum: ZMQ_HICCUP_MSG + enum: ZMQ_XSUB_VERBOSE_UNSUBSCRIBE + enum: ZMQ_TOPICS_COUNT + enum: ZMQ_NORM_MODE + enum: ZMQ_NORM_UNICAST_NACK + enum: ZMQ_NORM_BUFFER_SIZE + enum: ZMQ_NORM_SEGMENT_SIZE + enum: ZMQ_NORM_BLOCK_SIZE + enum: ZMQ_NORM_NUM_PARITY + enum: ZMQ_NORM_NUM_AUTOPARITY + enum: ZMQ_NORM_PUSH + enum: ZMQ_PAIR + enum: ZMQ_PUB + enum: ZMQ_SUB + enum: ZMQ_REQ + enum: ZMQ_REP + enum: ZMQ_DEALER + enum: ZMQ_ROUTER + enum: ZMQ_PULL + enum: ZMQ_PUSH + enum: ZMQ_XPUB + enum: ZMQ_XSUB + enum: ZMQ_STREAM + enum: ZMQ_XREQ + enum: ZMQ_XREP + enum: ZMQ_SERVER + enum: ZMQ_CLIENT + enum: ZMQ_RADIO + enum: ZMQ_DISH + enum: ZMQ_GATHER + enum: ZMQ_SCATTER + enum: ZMQ_DGRAM + enum: ZMQ_PEER + enum: ZMQ_CHANNEL diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/cython/libzmq.pxd b/.venv/lib/python3.11/site-packages/zmq/backend/cython/libzmq.pxd new file mode 100644 index 0000000000000000000000000000000000000000..311517373c860f9381d19d4b29ff1d90e1f19398 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/cython/libzmq.pxd @@ -0,0 +1,122 @@ +"""All the C imports for 0MQ""" + +# +# Copyright (c) 2010 Brian E. Granger & Min Ragan-Kelley +# +# This file is part of pyzmq. +# +# pyzmq is free software; you can redistribute it and/or modify it under +# the terms of the Lesser GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# pyzmq is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# Lesser GNU General Public License for more details. +# +# You should have received a copy of the Lesser GNU General Public License +# along with this program. If not, see . +# + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Import the C header files +#----------------------------------------------------------------------------- + +# common includes, such as zmq compat, pyversion_compat +# make sure we load pyversion compat in every Cython module +cdef extern from "pyversion_compat.h": + pass + +# were it not for Windows, +# we could cimport these from libc.stdint +cdef extern from "zmq_compat.h": + ctypedef signed long long int64_t "pyzmq_int64_t" + ctypedef unsigned int uint32_t "pyzmq_uint32_t" + +include "constant_enums.pxi" + +cdef extern from "zmq.h" nogil: + + void _zmq_version "zmq_version"(int *major, int *minor, int *patch) + + ctypedef int fd_t "ZMQ_FD_T" + + enum: errno + const char *zmq_strerror (int errnum) + int zmq_errno() + + void *zmq_ctx_new () + int zmq_ctx_destroy (void *context) + int zmq_ctx_set (void *context, int option, int optval) + int zmq_ctx_get (void *context, int option) + void *zmq_init (int io_threads) + int zmq_term (void *context) + + # blackbox def for zmq_msg_t + ctypedef void * zmq_msg_t "zmq_msg_t" + + ctypedef void zmq_free_fn(void *data, void *hint) + + int zmq_msg_init (zmq_msg_t *msg) + int zmq_msg_init_size (zmq_msg_t *msg, size_t size) + int zmq_msg_init_data (zmq_msg_t *msg, void *data, + size_t size, zmq_free_fn *ffn, void *hint) + int zmq_msg_send (zmq_msg_t *msg, void *s, int flags) + int zmq_msg_recv (zmq_msg_t *msg, void *s, int flags) + int zmq_msg_close (zmq_msg_t *msg) + int zmq_msg_move (zmq_msg_t *dest, zmq_msg_t *src) + int zmq_msg_copy (zmq_msg_t *dest, zmq_msg_t *src) + void *zmq_msg_data (zmq_msg_t *msg) + size_t zmq_msg_size (zmq_msg_t *msg) + int zmq_msg_more (zmq_msg_t *msg) + int zmq_msg_get (zmq_msg_t *msg, int option) + int zmq_msg_set (zmq_msg_t *msg, int option, int optval) + const char *zmq_msg_gets (zmq_msg_t *msg, const char *property) + int zmq_has (const char *capability) + + void *zmq_socket (void *context, int type) + int zmq_close (void *s) + int zmq_setsockopt (void *s, int option, void *optval, size_t optvallen) + int zmq_getsockopt (void *s, int option, void *optval, size_t *optvallen) + int zmq_bind (void *s, char *addr) + int zmq_connect (void *s, char *addr) + int zmq_unbind (void *s, char *addr) + int zmq_disconnect (void *s, char *addr) + + int zmq_socket_monitor (void *s, char *addr, int flags) + + # send/recv + int zmq_sendbuf (void *s, const void *buf, size_t n, int flags) + int zmq_recvbuf (void *s, void *buf, size_t n, int flags) + + ctypedef struct zmq_pollitem_t: + void *socket + fd_t fd + short events + short revents + + int zmq_poll (zmq_pollitem_t *items, int nitems, long timeout) + + int zmq_device (int device_, void *insocket_, void *outsocket_) + int zmq_proxy (void *frontend, void *backend, void *capture) + int zmq_proxy_steerable (void *frontend, + void *backend, + void *capture, + void *control) + + int zmq_curve_keypair (char *z85_public_key, char *z85_secret_key) + int zmq_curve_public (char *z85_public_key, char *z85_secret_key) + + # 4.2 draft + int zmq_join (void *s, const char *group) + int zmq_leave (void *s, const char *group) + + int zmq_msg_set_routing_id(zmq_msg_t *msg, uint32_t routing_id) + uint32_t zmq_msg_routing_id(zmq_msg_t *msg) + int zmq_msg_set_group(zmq_msg_t *msg, const char *group) + const char *zmq_msg_group(zmq_msg_t *msg) diff --git a/.venv/lib/python3.11/site-packages/zmq/backend/select.py b/.venv/lib/python3.11/site-packages/zmq/backend/select.py new file mode 100644 index 0000000000000000000000000000000000000000..a377321ca9cbd4aa301a291715ed4c98e671e2e2 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/backend/select.py @@ -0,0 +1,41 @@ +"""Import basic exposure of libzmq C API as a backend""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. + +from importlib import import_module +from typing import Dict + +public_api = [ + 'Context', + 'Socket', + 'Frame', + 'Message', + 'device', + 'proxy', + 'proxy_steerable', + 'zmq_poll', + 'strerror', + 'zmq_errno', + 'has', + 'curve_keypair', + 'curve_public', + 'zmq_version_info', + 'IPC_PATH_MAX_LEN', +] + + +def select_backend(name: str) -> Dict: + """Select the pyzmq backend""" + try: + mod = import_module(name) + except ImportError: + raise + except Exception as e: + raise ImportError(f"Importing {name} failed with {e}") from e + ns = { + # private API + 'monitored_queue': mod.monitored_queue, + } + ns.update({key: getattr(mod, key) for key in public_api}) + return ns diff --git a/.venv/lib/python3.11/site-packages/zmq/eventloop/__init__.py b/.venv/lib/python3.11/site-packages/zmq/eventloop/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a99e143ccf3035db55cb5a34caa13e98ba793e9a --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/eventloop/__init__.py @@ -0,0 +1,5 @@ +"""Tornado eventloop integration for pyzmq""" + +from tornado.ioloop import IOLoop + +__all__ = ['IOLoop'] diff --git a/.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b91f77b2c8c7c3ffa0674883066a17dbd77edf5 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/_deprecated.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/_deprecated.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..22fdad80951fda3807e4e7527f4460733bbf899b Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/_deprecated.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/future.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/future.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..433d498249ab3bc1cef8a2c87f705984120e3561 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/future.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/ioloop.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/ioloop.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e76a362fd2bb8070709fa9f986c1ad5fc0a51c4d Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/ioloop.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/zmqstream.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/zmqstream.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cac201cb9b3cfc6fd777e5b608d3fd7af938690c Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/eventloop/__pycache__/zmqstream.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/eventloop/_deprecated.py b/.venv/lib/python3.11/site-packages/zmq/eventloop/_deprecated.py new file mode 100644 index 0000000000000000000000000000000000000000..628900509241143c94df2ee569767856b67d4bd6 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/eventloop/_deprecated.py @@ -0,0 +1,211 @@ +"""tornado IOLoop API with zmq compatibility + +If you have tornado ≥ 3.0, this is a subclass of tornado's IOLoop, +otherwise we ship a minimal subset of tornado in zmq.eventloop.minitornado. + +The minimal shipped version of tornado's IOLoop does not include +support for concurrent futures - this will only be available if you +have tornado ≥ 3.0. +""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. + +import time +import warnings +from typing import Tuple + +from zmq import ETERM, POLLERR, POLLIN, POLLOUT, Poller, ZMQError + +tornado_version: Tuple = () +try: + import tornado + + tornado_version = tornado.version_info +except (ImportError, AttributeError): + pass + +from .minitornado.ioloop import PeriodicCallback, PollIOLoop +from .minitornado.log import gen_log + + +class DelayedCallback(PeriodicCallback): + """Schedules the given callback to be called once. + + The callback is called once, after callback_time milliseconds. + + `start` must be called after the DelayedCallback is created. + + The timeout is calculated from when `start` is called. + """ + + def __init__(self, callback, callback_time, io_loop=None): + # PeriodicCallback require callback_time to be positive + warnings.warn( + """DelayedCallback is deprecated. + Use loop.add_timeout instead.""", + DeprecationWarning, + ) + callback_time = max(callback_time, 1e-3) + super().__init__(callback, callback_time, io_loop) + + def start(self): + """Starts the timer.""" + self._running = True + self._firstrun = True + self._next_timeout = time.time() + self.callback_time / 1000.0 + self.io_loop.add_timeout(self._next_timeout, self._run) + + def _run(self): + if not self._running: + return + self._running = False + try: + self.callback() + except Exception: + gen_log.error("Error in delayed callback", exc_info=True) + + +class ZMQPoller: + """A poller that can be used in the tornado IOLoop. + + This simply wraps a regular zmq.Poller, scaling the timeout + by 1000, so that it is in seconds rather than milliseconds. + """ + + def __init__(self): + self._poller = Poller() + + @staticmethod + def _map_events(events): + """translate IOLoop.READ/WRITE/ERROR event masks into zmq.POLLIN/OUT/ERR""" + z_events = 0 + if events & IOLoop.READ: + z_events |= POLLIN + if events & IOLoop.WRITE: + z_events |= POLLOUT + if events & IOLoop.ERROR: + z_events |= POLLERR + return z_events + + @staticmethod + def _remap_events(z_events): + """translate zmq.POLLIN/OUT/ERR event masks into IOLoop.READ/WRITE/ERROR""" + events = 0 + if z_events & POLLIN: + events |= IOLoop.READ + if z_events & POLLOUT: + events |= IOLoop.WRITE + if z_events & POLLERR: + events |= IOLoop.ERROR + return events + + def register(self, fd, events): + return self._poller.register(fd, self._map_events(events)) + + def modify(self, fd, events): + return self._poller.modify(fd, self._map_events(events)) + + def unregister(self, fd): + return self._poller.unregister(fd) + + def poll(self, timeout): + """poll in seconds rather than milliseconds. + + Event masks will be IOLoop.READ/WRITE/ERROR + """ + z_events = self._poller.poll(1000 * timeout) + return [(fd, self._remap_events(evt)) for (fd, evt) in z_events] + + def close(self): + pass + + +class ZMQIOLoop(PollIOLoop): + """ZMQ subclass of tornado's IOLoop + + Minor modifications, so that .current/.instance return self + """ + + _zmq_impl = ZMQPoller + + def initialize(self, impl=None, **kwargs): + impl = self._zmq_impl() if impl is None else impl + super().initialize(impl=impl, **kwargs) + + @classmethod + def instance(cls, *args, **kwargs): + """Returns a global `IOLoop` instance. + + Most applications have a single, global `IOLoop` running on the + main thread. Use this method to get this instance from + another thread. To get the current thread's `IOLoop`, use `current()`. + """ + # install ZMQIOLoop as the active IOLoop implementation + # when using tornado 3 + if tornado_version >= (3,): + PollIOLoop.configure(cls) + loop = PollIOLoop.instance(*args, **kwargs) + if not isinstance(loop, cls): + warnings.warn( + f"IOLoop.current expected instance of {cls!r}, got {loop!r}", + RuntimeWarning, + stacklevel=2, + ) + return loop + + @classmethod + def current(cls, *args, **kwargs): + """Returns the current thread’s IOLoop.""" + # install ZMQIOLoop as the active IOLoop implementation + # when using tornado 3 + if tornado_version >= (3,): + PollIOLoop.configure(cls) + loop = PollIOLoop.current(*args, **kwargs) + if not isinstance(loop, cls): + warnings.warn( + f"IOLoop.current expected instance of {cls!r}, got {loop!r}", + RuntimeWarning, + stacklevel=2, + ) + return loop + + def start(self): + try: + super().start() + except ZMQError as e: + if e.errno == ETERM: + # quietly return on ETERM + pass + else: + raise + + +# public API name +IOLoop = ZMQIOLoop + + +def install(): + """set the tornado IOLoop instance with the pyzmq IOLoop. + + After calling this function, tornado's IOLoop.instance() and pyzmq's + IOLoop.instance() will return the same object. + + An assertion error will be raised if tornado's IOLoop has been initialized + prior to calling this function. + """ + from tornado import ioloop + + # check if tornado's IOLoop is already initialized to something other + # than the pyzmq IOLoop instance: + assert ( + (not ioloop.IOLoop.initialized()) + or ioloop.IOLoop.instance() is IOLoop.instance() + ), "tornado IOLoop already initialized" + + if tornado_version >= (3,): + # tornado 3 has an official API for registering new defaults, yay! + ioloop.IOLoop.configure(ZMQIOLoop) + else: + # we have to set the global instance explicitly + ioloop.IOLoop._instance = IOLoop.instance() diff --git a/.venv/lib/python3.11/site-packages/zmq/eventloop/future.py b/.venv/lib/python3.11/site-packages/zmq/eventloop/future.py new file mode 100644 index 0000000000000000000000000000000000000000..0f34f0ef93a8947e622b74e8ebedf79003727e0a --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/eventloop/future.py @@ -0,0 +1,104 @@ +"""Future-returning APIs for tornado coroutines. + +.. seealso:: + + :mod:`zmq.asyncio` + +""" + +# Copyright (c) PyZMQ Developers. +# Distributed under the terms of the Modified BSD License. +from __future__ import annotations + +import asyncio +import warnings +from typing import Any + +from tornado.concurrent import Future +from tornado.ioloop import IOLoop + +import zmq as _zmq +from zmq._future import _AsyncPoller, _AsyncSocket + + +class CancelledError(Exception): + pass + + +class _TornadoFuture(Future): + """Subclass Tornado Future, reinstating cancellation.""" + + def cancel(self): + if self.done(): + return False + self.set_exception(CancelledError()) + return True + + def cancelled(self): + return self.done() and isinstance(self.exception(), CancelledError) + + +class _CancellableTornadoTimeout: + def __init__(self, loop, timeout): + self.loop = loop + self.timeout = timeout + + def cancel(self): + self.loop.remove_timeout(self.timeout) + + +# mixin for tornado/asyncio compatibility + + +class _AsyncTornado: + _Future: type[asyncio.Future] = _TornadoFuture + _READ = IOLoop.READ + _WRITE = IOLoop.WRITE + + def _default_loop(self): + return IOLoop.current() + + def _call_later(self, delay, callback): + io_loop = self._get_loop() + timeout = io_loop.call_later(delay, callback) + return _CancellableTornadoTimeout(io_loop, timeout) + + +class Poller(_AsyncTornado, _AsyncPoller): + def _watch_raw_socket(self, loop, socket, evt, f): + """Schedule callback for a raw socket""" + loop.add_handler(socket, lambda *args: f(), evt) + + def _unwatch_raw_sockets(self, loop, *sockets): + """Unschedule callback for a raw socket""" + for socket in sockets: + loop.remove_handler(socket) + + +class Socket(_AsyncTornado, _AsyncSocket): + _poller_class = Poller + + +Poller._socket_class = Socket + + +class Context(_zmq.Context[Socket]): + # avoid sharing instance with base Context class + _instance = None + + io_loop = None + + @staticmethod + def _socket_class(self, socket_type): + return Socket(self, socket_type) + + def __init__(self: Context, *args: Any, **kwargs: Any) -> None: + io_loop = kwargs.pop('io_loop', None) + if io_loop is not None: + warnings.warn( + f"{self.__class__.__name__}(io_loop) argument is deprecated in pyzmq 22.2." + " The currently active loop will always be used.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore diff --git a/.venv/lib/python3.11/site-packages/zmq/eventloop/ioloop.py b/.venv/lib/python3.11/site-packages/zmq/eventloop/ioloop.py new file mode 100644 index 0000000000000000000000000000000000000000..dccb92a14eba7190c8571c0ee880ab5542e31bb7 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/eventloop/ioloop.py @@ -0,0 +1,37 @@ +"""tornado IOLoop API with zmq compatibility + +This module is deprecated in pyzmq 17. +To use zmq with tornado, +eventloop integration is no longer required +and tornado itself should be used. +""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. + +import warnings + + +def _deprecated(): + warnings.warn( + "zmq.eventloop.ioloop is deprecated in pyzmq 17." + " pyzmq now works with default tornado and asyncio eventloops.", + DeprecationWarning, + stacklevel=3, + ) + + +_deprecated() + +from tornado.ioloop import * # noqa +from tornado.ioloop import IOLoop + +ZMQIOLoop = IOLoop + + +def install(): + """DEPRECATED + + pyzmq 17 no longer needs any special integration for tornado. + """ + _deprecated() diff --git a/.venv/lib/python3.11/site-packages/zmq/eventloop/zmqstream.py b/.venv/lib/python3.11/site-packages/zmq/eventloop/zmqstream.py new file mode 100644 index 0000000000000000000000000000000000000000..3291f15793af4dfaa73b68fd235368713cc768b7 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/eventloop/zmqstream.py @@ -0,0 +1,689 @@ +# Derived from iostream.py from tornado 1.0, Copyright 2009 Facebook +# Used under Apache License Version 2.0 +# +# Modifications are Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. +"""A utility class for event-based messaging on a zmq socket using tornado. + +.. seealso:: + + - :mod:`zmq.asyncio` + - :mod:`zmq.eventloop.future` +""" + +from __future__ import annotations + +import asyncio +import pickle +import warnings +from queue import Queue +from typing import Any, Awaitable, Callable, Sequence, cast, overload + +from tornado.ioloop import IOLoop +from tornado.log import gen_log + +import zmq +import zmq._future +from zmq import POLLIN, POLLOUT +from zmq._typing import Literal +from zmq.utils import jsonapi + + +class ZMQStream: + """A utility class to register callbacks when a zmq socket sends and receives + + For use with tornado IOLoop. + + There are three main methods + + Methods: + + * **on_recv(callback, copy=True):** + register a callback to be run every time the socket has something to receive + * **on_send(callback):** + register a callback to be run every time you call send + * **send_multipart(self, msg, flags=0, copy=False, callback=None):** + perform a send that will trigger the callback + if callback is passed, on_send is also called. + + There are also send_multipart(), send_json(), send_pyobj() + + Three other methods for deactivating the callbacks: + + * **stop_on_recv():** + turn off the recv callback + * **stop_on_send():** + turn off the send callback + + which simply call ``on_(None)``. + + The entire socket interface, excluding direct recv methods, is also + provided, primarily through direct-linking the methods. + e.g. + + >>> stream.bind is stream.socket.bind + True + + + .. versionadded:: 25 + + send/recv callbacks can be coroutines. + + .. versionchanged:: 25 + + ZMQStreams only support base zmq.Socket classes (this has always been true, but not enforced). + If ZMQStreams are created with e.g. async Socket subclasses, + a RuntimeWarning will be shown, + and the socket cast back to the default zmq.Socket + before connecting events. + + Previously, using async sockets (or any zmq.Socket subclass) would result in undefined behavior for the + arguments passed to callback functions. + Now, the callback functions reliably get the return value of the base `zmq.Socket` send/recv_multipart methods + (the list of message frames). + """ + + socket: zmq.Socket + io_loop: IOLoop + poller: zmq.Poller + _send_queue: Queue + _recv_callback: Callable | None + _send_callback: Callable | None + _close_callback: Callable | None + _state: int = 0 + _flushed: bool = False + _recv_copy: bool = False + _fd: int + + def __init__(self, socket: zmq.Socket, io_loop: IOLoop | None = None): + if isinstance(socket, zmq._future._AsyncSocket): + warnings.warn( + f"""ZMQStream only supports the base zmq.Socket class. + + Use zmq.Socket(shadow=other_socket) + or `ctx.socket(zmq.{socket._type_name}, socket_class=zmq.Socket)` + to create a base zmq.Socket object, + no matter what other kind of socket your Context creates. + """, + RuntimeWarning, + stacklevel=2, + ) + # shadow back to base zmq.Socket, + # otherwise callbacks like `on_recv` will get the wrong types. + socket = zmq.Socket(shadow=socket) + self.socket = socket + + # IOLoop.current() is deprecated if called outside the event loop + # that means + self.io_loop = io_loop or IOLoop.current() + self.poller = zmq.Poller() + self._fd = cast(int, self.socket.FD) + + self._send_queue = Queue() + self._recv_callback = None + self._send_callback = None + self._close_callback = None + self._recv_copy = False + self._flushed = False + + self._state = 0 + self._init_io_state() + + # shortcircuit some socket methods + self.bind = self.socket.bind + self.bind_to_random_port = self.socket.bind_to_random_port + self.connect = self.socket.connect + self.setsockopt = self.socket.setsockopt + self.getsockopt = self.socket.getsockopt + self.setsockopt_string = self.socket.setsockopt_string + self.getsockopt_string = self.socket.getsockopt_string + self.setsockopt_unicode = self.socket.setsockopt_unicode + self.getsockopt_unicode = self.socket.getsockopt_unicode + + def stop_on_recv(self): + """Disable callback and automatic receiving.""" + return self.on_recv(None) + + def stop_on_send(self): + """Disable callback on sending.""" + return self.on_send(None) + + def stop_on_err(self): + """DEPRECATED, does nothing""" + gen_log.warn("on_err does nothing, and will be removed") + + def on_err(self, callback: Callable): + """DEPRECATED, does nothing""" + gen_log.warn("on_err does nothing, and will be removed") + + @overload + def on_recv( + self, + callback: Callable[[list[bytes]], Any], + ) -> None: ... + + @overload + def on_recv( + self, + callback: Callable[[list[bytes]], Any], + copy: Literal[True], + ) -> None: ... + + @overload + def on_recv( + self, + callback: Callable[[list[zmq.Frame]], Any], + copy: Literal[False], + ) -> None: ... + + @overload + def on_recv( + self, + callback: Callable[[list[zmq.Frame]], Any] | Callable[[list[bytes]], Any], + copy: bool = ..., + ): ... + + def on_recv( + self, + callback: Callable[[list[zmq.Frame]], Any] | Callable[[list[bytes]], Any], + copy: bool = True, + ) -> None: + """Register a callback for when a message is ready to recv. + + There can be only one callback registered at a time, so each + call to `on_recv` replaces previously registered callbacks. + + on_recv(None) disables recv event polling. + + Use on_recv_stream(callback) instead, to register a callback that will receive + both this ZMQStream and the message, instead of just the message. + + Parameters + ---------- + + callback : callable + callback must take exactly one argument, which will be a + list, as returned by socket.recv_multipart() + if callback is None, recv callbacks are disabled. + copy : bool + copy is passed directly to recv, so if copy is False, + callback will receive Message objects. If copy is True, + then callback will receive bytes/str objects. + + Returns : None + """ + + self._check_closed() + assert callback is None or callable(callback) + self._recv_callback = callback + self._recv_copy = copy + if callback is None: + self._drop_io_state(zmq.POLLIN) + else: + self._add_io_state(zmq.POLLIN) + + @overload + def on_recv_stream( + self, + callback: Callable[[ZMQStream, list[bytes]], Any], + ) -> None: ... + + @overload + def on_recv_stream( + self, + callback: Callable[[ZMQStream, list[bytes]], Any], + copy: Literal[True], + ) -> None: ... + + @overload + def on_recv_stream( + self, + callback: Callable[[ZMQStream, list[zmq.Frame]], Any], + copy: Literal[False], + ) -> None: ... + + @overload + def on_recv_stream( + self, + callback: ( + Callable[[ZMQStream, list[zmq.Frame]], Any] + | Callable[[ZMQStream, list[bytes]], Any] + ), + copy: bool = ..., + ): ... + + def on_recv_stream( + self, + callback: ( + Callable[[ZMQStream, list[zmq.Frame]], Any] + | Callable[[ZMQStream, list[bytes]], Any] + ), + copy: bool = True, + ): + """Same as on_recv, but callback will get this stream as first argument + + callback must take exactly two arguments, as it will be called as:: + + callback(stream, msg) + + Useful when a single callback should be used with multiple streams. + """ + if callback is None: + self.stop_on_recv() + else: + + def stream_callback(msg): + return callback(self, msg) + + self.on_recv(stream_callback, copy=copy) + + def on_send( + self, callback: Callable[[Sequence[Any], zmq.MessageTracker | None], Any] + ): + """Register a callback to be called on each send + + There will be two arguments:: + + callback(msg, status) + + * `msg` will be the list of sendable objects that was just sent + * `status` will be the return result of socket.send_multipart(msg) - + MessageTracker or None. + + Non-copying sends return a MessageTracker object whose + `done` attribute will be True when the send is complete. + This allows users to track when an object is safe to write to + again. + + The second argument will always be None if copy=True + on the send. + + Use on_send_stream(callback) to register a callback that will be passed + this ZMQStream as the first argument, in addition to the other two. + + on_send(None) disables recv event polling. + + Parameters + ---------- + + callback : callable + callback must take exactly two arguments, which will be + the message being sent (always a list), + and the return result of socket.send_multipart(msg) - + MessageTracker or None. + + if callback is None, send callbacks are disabled. + """ + + self._check_closed() + assert callback is None or callable(callback) + self._send_callback = callback + + def on_send_stream( + self, + callback: Callable[[ZMQStream, Sequence[Any], zmq.MessageTracker | None], Any], + ): + """Same as on_send, but callback will get this stream as first argument + + Callback will be passed three arguments:: + + callback(stream, msg, status) + + Useful when a single callback should be used with multiple streams. + """ + if callback is None: + self.stop_on_send() + else: + self.on_send(lambda msg, status: callback(self, msg, status)) + + def send(self, msg, flags=0, copy=True, track=False, callback=None, **kwargs): + """Send a message, optionally also register a new callback for sends. + See zmq.socket.send for details. + """ + return self.send_multipart( + [msg], flags=flags, copy=copy, track=track, callback=callback, **kwargs + ) + + def send_multipart( + self, + msg: Sequence[Any], + flags: int = 0, + copy: bool = True, + track: bool = False, + callback: Callable | None = None, + **kwargs: Any, + ) -> None: + """Send a multipart message, optionally also register a new callback for sends. + See zmq.socket.send_multipart for details. + """ + kwargs.update(dict(flags=flags, copy=copy, track=track)) + self._send_queue.put((msg, kwargs)) + callback = callback or self._send_callback + if callback is not None: + self.on_send(callback) + else: + # noop callback + self.on_send(lambda *args: None) + self._add_io_state(zmq.POLLOUT) + + def send_string( + self, + u: str, + flags: int = 0, + encoding: str = 'utf-8', + callback: Callable | None = None, + **kwargs: Any, + ): + """Send a unicode message with an encoding. + See zmq.socket.send_unicode for details. + """ + if not isinstance(u, str): + raise TypeError("unicode/str objects only") + return self.send(u.encode(encoding), flags=flags, callback=callback, **kwargs) + + send_unicode = send_string + + def send_json( + self, + obj: Any, + flags: int = 0, + callback: Callable | None = None, + **kwargs: Any, + ): + """Send json-serialized version of an object. + See zmq.socket.send_json for details. + """ + msg = jsonapi.dumps(obj) + return self.send(msg, flags=flags, callback=callback, **kwargs) + + def send_pyobj( + self, + obj: Any, + flags: int = 0, + protocol: int = -1, + callback: Callable | None = None, + **kwargs: Any, + ): + """Send a Python object as a message using pickle to serialize. + + See zmq.socket.send_json for details. + """ + msg = pickle.dumps(obj, protocol) + return self.send(msg, flags, callback=callback, **kwargs) + + def _finish_flush(self): + """callback for unsetting _flushed flag.""" + self._flushed = False + + def flush(self, flag: int = zmq.POLLIN | zmq.POLLOUT, limit: int | None = None): + """Flush pending messages. + + This method safely handles all pending incoming and/or outgoing messages, + bypassing the inner loop, passing them to the registered callbacks. + + A limit can be specified, to prevent blocking under high load. + + flush will return the first time ANY of these conditions are met: + * No more events matching the flag are pending. + * the total number of events handled reaches the limit. + + Note that if ``flag|POLLIN != 0``, recv events will be flushed even if no callback + is registered, unlike normal IOLoop operation. This allows flush to be + used to remove *and ignore* incoming messages. + + Parameters + ---------- + flag : int + default=POLLIN|POLLOUT + 0MQ poll flags. + If flag|POLLIN, recv events will be flushed. + If flag|POLLOUT, send events will be flushed. + Both flags can be set at once, which is the default. + limit : None or int, optional + The maximum number of messages to send or receive. + Both send and recv count against this limit. + + Returns + ------- + int : + count of events handled (both send and recv) + """ + self._check_closed() + # unset self._flushed, so callbacks will execute, in case flush has + # already been called this iteration + already_flushed = self._flushed + self._flushed = False + # initialize counters + count = 0 + + def update_flag(): + """Update the poll flag, to prevent registering POLLOUT events + if we don't have pending sends.""" + return flag & zmq.POLLIN | (self.sending() and flag & zmq.POLLOUT) + + flag = update_flag() + if not flag: + # nothing to do + return 0 + self.poller.register(self.socket, flag) + events = self.poller.poll(0) + while events and (not limit or count < limit): + s, event = events[0] + if event & POLLIN: # receiving + self._handle_recv() + count += 1 + if self.socket is None: + # break if socket was closed during callback + break + if event & POLLOUT and self.sending(): + self._handle_send() + count += 1 + if self.socket is None: + # break if socket was closed during callback + break + + flag = update_flag() + if flag: + self.poller.register(self.socket, flag) + events = self.poller.poll(0) + else: + events = [] + if count: # only bypass loop if we actually flushed something + # skip send/recv callbacks this iteration + self._flushed = True + # reregister them at the end of the loop + if not already_flushed: # don't need to do it again + self.io_loop.add_callback(self._finish_flush) + elif already_flushed: + self._flushed = True + + # update ioloop poll state, which may have changed + self._rebuild_io_state() + return count + + def set_close_callback(self, callback: Callable | None): + """Call the given callback when the stream is closed.""" + self._close_callback = callback + + def close(self, linger: int | None = None) -> None: + """Close this stream.""" + if self.socket is not None: + if self.socket.closed: + # fallback on raw fd for closed sockets + # hopefully this happened promptly after close, + # otherwise somebody else may have the FD + warnings.warn( + f"Unregistering FD {self._fd} after closing socket. " + "This could result in unregistering handlers for the wrong socket. " + "Please use stream.close() instead of closing the socket directly.", + stacklevel=2, + ) + self.io_loop.remove_handler(self._fd) + else: + self.io_loop.remove_handler(self.socket) + self.socket.close(linger) + self.socket = None # type: ignore + if self._close_callback: + self._run_callback(self._close_callback) + + def receiving(self) -> bool: + """Returns True if we are currently receiving from the stream.""" + return self._recv_callback is not None + + def sending(self) -> bool: + """Returns True if we are currently sending to the stream.""" + return not self._send_queue.empty() + + def closed(self) -> bool: + if self.socket is None: + return True + if self.socket.closed: + # underlying socket has been closed, but not by us! + # trigger our cleanup + self.close() + return True + return False + + def _run_callback(self, callback, *args, **kwargs): + """Wrap running callbacks in try/except to allow us to + close our socket.""" + try: + f = callback(*args, **kwargs) + if isinstance(f, Awaitable): + f = asyncio.ensure_future(f) + else: + f = None + except Exception: + gen_log.error("Uncaught exception in ZMQStream callback", exc_info=True) + # Re-raise the exception so that IOLoop.handle_callback_exception + # can see it and log the error + raise + + if f is not None: + # handle async callbacks + def _log_error(f): + try: + f.result() + except Exception: + gen_log.error( + "Uncaught exception in ZMQStream callback", exc_info=True + ) + + f.add_done_callback(_log_error) + + def _handle_events(self, fd, events): + """This method is the actual handler for IOLoop, that gets called whenever + an event on my socket is posted. It dispatches to _handle_recv, etc.""" + if not self.socket: + gen_log.warning("Got events for closed stream %s", self) + return + try: + zmq_events = self.socket.EVENTS + except zmq.ContextTerminated: + gen_log.warning("Got events for stream %s after terminating context", self) + # trigger close check, this will unregister callbacks + self.closed() + return + except zmq.ZMQError as e: + # run close check + # shadow sockets may have been closed elsewhere, + # which should show up as ENOTSOCK here + if self.closed(): + gen_log.warning( + "Got events for stream %s attached to closed socket: %s", self, e + ) + else: + gen_log.error("Error getting events for %s: %s", self, e) + return + try: + # dispatch events: + if zmq_events & zmq.POLLIN and self.receiving(): + self._handle_recv() + if not self.socket: + return + if zmq_events & zmq.POLLOUT and self.sending(): + self._handle_send() + if not self.socket: + return + + # rebuild the poll state + self._rebuild_io_state() + except Exception: + gen_log.error("Uncaught exception in zmqstream callback", exc_info=True) + raise + + def _handle_recv(self): + """Handle a recv event.""" + if self._flushed: + return + try: + msg = self.socket.recv_multipart(zmq.NOBLOCK, copy=self._recv_copy) + except zmq.ZMQError as e: + if e.errno == zmq.EAGAIN: + # state changed since poll event + pass + else: + raise + else: + if self._recv_callback: + callback = self._recv_callback + self._run_callback(callback, msg) + + def _handle_send(self): + """Handle a send event.""" + if self._flushed: + return + if not self.sending(): + gen_log.error("Shouldn't have handled a send event") + return + + msg, kwargs = self._send_queue.get() + try: + status = self.socket.send_multipart(msg, **kwargs) + except zmq.ZMQError as e: + gen_log.error("SEND Error: %s", e) + status = e + if self._send_callback: + callback = self._send_callback + self._run_callback(callback, msg, status) + + def _check_closed(self): + if not self.socket: + raise OSError("Stream is closed") + + def _rebuild_io_state(self): + """rebuild io state based on self.sending() and receiving()""" + if self.socket is None: + return + state = 0 + if self.receiving(): + state |= zmq.POLLIN + if self.sending(): + state |= zmq.POLLOUT + + self._state = state + self._update_handler(state) + + def _add_io_state(self, state): + """Add io_state to poller.""" + self._state = self._state | state + self._update_handler(self._state) + + def _drop_io_state(self, state): + """Stop poller from watching an io_state.""" + self._state = self._state & (~state) + self._update_handler(self._state) + + def _update_handler(self, state): + """Update IOLoop handler with state.""" + if self.socket is None: + return + + if state & self.socket.events: + # events still exist that haven't been processed + # explicitly schedule handling to avoid missing events due to edge-triggered FDs + self.io_loop.add_callback(lambda: self._handle_events(self.socket, 0)) + + def _init_io_state(self): + """initialize the ioloop event handler""" + self.io_loop.add_handler(self.socket, self._handle_events, self.io_loop.READ) diff --git a/.venv/lib/python3.11/site-packages/zmq/green/__init__.py b/.venv/lib/python3.11/site-packages/zmq/green/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8543864b00d60f236c05463f62494a483c739871 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/green/__init__.py @@ -0,0 +1,48 @@ +# ----------------------------------------------------------------------------- +# Copyright (C) 2011-2012 Travis Cline +# +# This file is part of pyzmq +# It is adapted from upstream project zeromq_gevent under the New BSD License +# +# Distributed under the terms of the New BSD License. The full license is in +# the file LICENSE.BSD, distributed as part of this software. +# ----------------------------------------------------------------------------- + +"""zmq.green - gevent compatibility with zeromq. + +Usage +----- + +Instead of importing zmq directly, do so in the following manner: + +.. + + import zmq.green as zmq + + +Any calls that would have blocked the current thread will now only block the +current green thread. + +This compatibility is accomplished by ensuring the nonblocking flag is set +before any blocking operation and the ØMQ file descriptor is polled internally +to trigger needed events. +""" + +from __future__ import annotations + +from typing import List + +import zmq as _zmq +from zmq import * +from zmq.green.core import _Context, _Socket +from zmq.green.poll import _Poller + +Context = _Context # type: ignore +Socket = _Socket # type: ignore +Poller = _Poller # type: ignore + +from zmq.green.device import device # type: ignore + +__all__: list[str] = [] +# adding `__all__` to __init__.pyi gets mypy all confused +__all__.extend(_zmq.__all__) # type: ignore diff --git a/.venv/lib/python3.11/site-packages/zmq/green/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/green/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..33dd444be33ffcfcf7cde997a7cd501567ef99e8 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/green/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/green/__pycache__/core.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/green/__pycache__/core.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53a90a1afd1430e77fe9652e65f4accf8ed89d65 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/green/__pycache__/core.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/green/__pycache__/device.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/green/__pycache__/device.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ba9234f4cbaa1d31467ba7ba3b1d1aba6c39a4e Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/green/__pycache__/device.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/green/__pycache__/poll.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/green/__pycache__/poll.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c620dd546131225a54fc3a6865b540d2652c40b8 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/green/__pycache__/poll.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/green/core.py b/.venv/lib/python3.11/site-packages/zmq/green/core.py new file mode 100644 index 0000000000000000000000000000000000000000..fb4872ebdf0436f28c2d760f25cd7362bde5689d --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/green/core.py @@ -0,0 +1,317 @@ +# ----------------------------------------------------------------------------- +# Copyright (C) 2011-2012 Travis Cline +# +# This file is part of pyzmq +# It is adapted from upstream project zeromq_gevent under the New BSD License +# +# Distributed under the terms of the New BSD License. The full license is in +# the file LICENSE.BSD, distributed as part of this software. +# ----------------------------------------------------------------------------- + +"""This module wraps the :class:`Socket` and :class:`Context` found in :mod:`pyzmq ` to be non blocking""" + +from __future__ import annotations + +import sys +import time +import warnings + +import gevent +from gevent.event import AsyncResult +from gevent.hub import get_hub + +import zmq +from zmq import Context as _original_Context +from zmq import Socket as _original_Socket + +from .poll import _Poller + +if hasattr(zmq, 'RCVTIMEO'): + TIMEOS: tuple = (zmq.RCVTIMEO, zmq.SNDTIMEO) +else: + TIMEOS = () + + +def _stop(evt): + """simple wrapper for stopping an Event, allowing for method rename in gevent 1.0""" + try: + evt.stop() + except AttributeError: + # gevent<1.0 compat + evt.cancel() + + +class _Socket(_original_Socket): + """Green version of :class:`zmq.Socket` + + The following methods are overridden: + + * send + * recv + + To ensure that the ``zmq.NOBLOCK`` flag is set and that sending or receiving + is deferred to the hub if a ``zmq.EAGAIN`` (retry) error is raised. + + The `__state_changed` method is triggered when the zmq.FD for the socket is + marked as readable and triggers the necessary read and write events (which + are waited for in the recv and send methods). + + Some double underscore prefixes are used to minimize pollution of + :class:`zmq.Socket`'s namespace. + """ + + __in_send_multipart = False + __in_recv_multipart = False + __writable = None + __readable = None + _state_event = None + _gevent_bug_timeout = 11.6 # timeout for not trusting gevent + _debug_gevent = False # turn on if you think gevent is missing events + _poller_class = _Poller + _repr_cls = "zmq.green.Socket" + + def __init__(self, *a, **kw): + super().__init__(*a, **kw) + self.__in_send_multipart = False + self.__in_recv_multipart = False + self.__setup_events() + + def __del__(self): + self.close() + + def close(self, linger=None): + super().close(linger) + self.__cleanup_events() + + def __cleanup_events(self): + # close the _state_event event, keeps the number of active file descriptors down + if getattr(self, '_state_event', None): + _stop(self._state_event) + self._state_event = None + # if the socket has entered a close state resume any waiting greenlets + self.__writable.set() + self.__readable.set() + + def __setup_events(self): + self.__readable = AsyncResult() + self.__writable = AsyncResult() + self.__readable.set() + self.__writable.set() + + try: + self._state_event = get_hub().loop.io( + self.getsockopt(zmq.FD), 1 + ) # read state watcher + self._state_event.start(self.__state_changed) + except AttributeError: + # for gevent<1.0 compatibility + from gevent.core import read_event + + self._state_event = read_event( + self.getsockopt(zmq.FD), self.__state_changed, persist=True + ) + + def __state_changed(self, event=None, _evtype=None): + if self.closed: + self.__cleanup_events() + return + try: + # avoid triggering __state_changed from inside __state_changed + events = super().getsockopt(zmq.EVENTS) + except zmq.ZMQError as exc: + self.__writable.set_exception(exc) + self.__readable.set_exception(exc) + else: + if events & zmq.POLLOUT: + self.__writable.set() + if events & zmq.POLLIN: + self.__readable.set() + + def _wait_write(self): + assert self.__writable.ready(), "Only one greenlet can be waiting on this event" + self.__writable = AsyncResult() + # timeout is because libzmq cannot be trusted to properly signal a new send event: + # this is effectively a maximum poll interval of 1s + tic = time.time() + dt = self._gevent_bug_timeout + if dt: + timeout = gevent.Timeout(seconds=dt) + else: + timeout = None + try: + if timeout: + timeout.start() + self.__writable.get(block=True) + except gevent.Timeout as t: + if t is not timeout: + raise + toc = time.time() + # gevent bug: get can raise timeout even on clean return + # don't display zmq bug warning for gevent bug (this is getting ridiculous) + if ( + self._debug_gevent + and timeout + and toc - tic > dt + and self.getsockopt(zmq.EVENTS) & zmq.POLLOUT + ): + print( + f"BUG: gevent may have missed a libzmq send event on {self.FD}!", + file=sys.stderr, + ) + finally: + if timeout: + timeout.close() + self.__writable.set() + + def _wait_read(self): + assert self.__readable.ready(), "Only one greenlet can be waiting on this event" + self.__readable = AsyncResult() + # timeout is because libzmq cannot always be trusted to play nice with libevent. + # I can only confirm that this actually happens for send, but lets be symmetrical + # with our dirty hacks. + # this is effectively a maximum poll interval of 1s + tic = time.time() + dt = self._gevent_bug_timeout + if dt: + timeout = gevent.Timeout(seconds=dt) + else: + timeout = None + try: + if timeout: + timeout.start() + self.__readable.get(block=True) + except gevent.Timeout as t: + if t is not timeout: + raise + toc = time.time() + # gevent bug: get can raise timeout even on clean return + # don't display zmq bug warning for gevent bug (this is getting ridiculous) + if ( + self._debug_gevent + and timeout + and toc - tic > dt + and self.getsockopt(zmq.EVENTS) & zmq.POLLIN + ): + print( + f"BUG: gevent may have missed a libzmq recv event on {self.FD}!", + file=sys.stderr, + ) + finally: + if timeout: + timeout.close() + self.__readable.set() + + def send(self, data, flags=0, copy=True, track=False, **kwargs): + """send, which will only block current greenlet + + state_changed always fires exactly once (success or fail) at the + end of this method. + """ + + # if we're given the NOBLOCK flag act as normal and let the EAGAIN get raised + if flags & zmq.NOBLOCK: + try: + msg = super().send(data, flags, copy, track, **kwargs) + finally: + if not self.__in_send_multipart: + self.__state_changed() + return msg + # ensure the zmq.NOBLOCK flag is part of flags + flags |= zmq.NOBLOCK + while True: # Attempt to complete this operation indefinitely, blocking the current greenlet + try: + # attempt the actual call + msg = super().send(data, flags, copy, track) + except zmq.ZMQError as e: + # if the raised ZMQError is not EAGAIN, reraise + if e.errno != zmq.EAGAIN: + if not self.__in_send_multipart: + self.__state_changed() + raise + else: + if not self.__in_send_multipart: + self.__state_changed() + return msg + # defer to the event loop until we're notified the socket is writable + self._wait_write() + + def recv(self, flags=0, copy=True, track=False): + """recv, which will only block current greenlet + + state_changed always fires exactly once (success or fail) at the + end of this method. + """ + if flags & zmq.NOBLOCK: + try: + msg = super().recv(flags, copy, track) + finally: + if not self.__in_recv_multipart: + self.__state_changed() + return msg + + flags |= zmq.NOBLOCK + while True: + try: + msg = super().recv(flags, copy, track) + except zmq.ZMQError as e: + if e.errno != zmq.EAGAIN: + if not self.__in_recv_multipart: + self.__state_changed() + raise + else: + if not self.__in_recv_multipart: + self.__state_changed() + return msg + self._wait_read() + + def send_multipart(self, *args, **kwargs): + """wrap send_multipart to prevent state_changed on each partial send""" + self.__in_send_multipart = True + try: + msg = super().send_multipart(*args, **kwargs) + finally: + self.__in_send_multipart = False + self.__state_changed() + return msg + + def recv_multipart(self, *args, **kwargs): + """wrap recv_multipart to prevent state_changed on each partial recv""" + self.__in_recv_multipart = True + try: + msg = super().recv_multipart(*args, **kwargs) + finally: + self.__in_recv_multipart = False + self.__state_changed() + return msg + + def get(self, opt): + """trigger state_changed on getsockopt(EVENTS)""" + if opt in TIMEOS: + warnings.warn( + "TIMEO socket options have no effect in zmq.green", UserWarning + ) + optval = super().get(opt) + if opt == zmq.EVENTS: + self.__state_changed() + return optval + + def set(self, opt, val): + """set socket option""" + if opt in TIMEOS: + warnings.warn( + "TIMEO socket options have no effect in zmq.green", UserWarning + ) + return super().set(opt, val) + + +class _Context(_original_Context[_Socket]): + """Replacement for :class:`zmq.Context` + + Ensures that the greened Socket above is used in calls to `socket`. + """ + + _socket_class = _Socket + _repr_cls = "zmq.green.Context" + + # avoid sharing instance with base Context class + _instance = None diff --git a/.venv/lib/python3.11/site-packages/zmq/green/device.py b/.venv/lib/python3.11/site-packages/zmq/green/device.py new file mode 100644 index 0000000000000000000000000000000000000000..f9beae39eff8f023997aa075afd2a1baf80ac196 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/green/device.py @@ -0,0 +1,34 @@ +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. +from __future__ import annotations + +import zmq +from zmq.green import Poller + + +def device(device_type, isocket, osocket): + """Start a zeromq device (gevent-compatible). + + Unlike the true zmq.device, this does not release the GIL. + + Parameters + ---------- + device_type : (QUEUE, FORWARDER, STREAMER) + The type of device to start (ignored). + isocket : Socket + The Socket instance for the incoming traffic. + osocket : Socket + The Socket instance for the outbound traffic. + """ + p = Poller() + if osocket == -1: + osocket = isocket + p.register(isocket, zmq.POLLIN) + p.register(osocket, zmq.POLLIN) + + while True: + events = dict(p.poll()) + if isocket in events: + osocket.send_multipart(isocket.recv_multipart()) + if osocket in events: + isocket.send_multipart(osocket.recv_multipart()) diff --git a/.venv/lib/python3.11/site-packages/zmq/green/eventloop/__init__.py b/.venv/lib/python3.11/site-packages/zmq/green/eventloop/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..b0ef0272c4e93a6f7a9955e9ac16f9a6ddf148e2 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/green/eventloop/__init__.py @@ -0,0 +1,3 @@ +from zmq.green.eventloop.ioloop import IOLoop + +__all__ = ['IOLoop'] diff --git a/.venv/lib/python3.11/site-packages/zmq/green/eventloop/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/green/eventloop/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82f09ca20bb5ac1a4fdb891733db4231d99bff3c Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/green/eventloop/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/green/eventloop/__pycache__/ioloop.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/green/eventloop/__pycache__/ioloop.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fafc78bc630dbee4b50a7dd34e8d1263d85608bc Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/green/eventloop/__pycache__/ioloop.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/green/eventloop/__pycache__/zmqstream.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/green/eventloop/__pycache__/zmqstream.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5845dc4d035e0536e4402a7bf844b7ee7d26cacb Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/green/eventloop/__pycache__/zmqstream.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/green/eventloop/ioloop.py b/.venv/lib/python3.11/site-packages/zmq/green/eventloop/ioloop.py new file mode 100644 index 0000000000000000000000000000000000000000..50e9151469570311c2fbc6af9fbb8d04b2d594f4 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/green/eventloop/ioloop.py @@ -0,0 +1 @@ +from zmq.eventloop.ioloop import * # noqa diff --git a/.venv/lib/python3.11/site-packages/zmq/green/eventloop/zmqstream.py b/.venv/lib/python3.11/site-packages/zmq/green/eventloop/zmqstream.py new file mode 100644 index 0000000000000000000000000000000000000000..c06c2ab50e1b7e4ef1501e78a7ac85f2fa43d6c3 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/green/eventloop/zmqstream.py @@ -0,0 +1,11 @@ +from zmq.eventloop import zmqstream +from zmq.green.eventloop.ioloop import IOLoop + + +class ZMQStream(zmqstream.ZMQStream): + def __init__(self, socket, io_loop=None): + io_loop = io_loop or IOLoop.instance() + super().__init__(socket, io_loop=io_loop) + + +__all__ = ["ZMQStream"] diff --git a/.venv/lib/python3.11/site-packages/zmq/green/poll.py b/.venv/lib/python3.11/site-packages/zmq/green/poll.py new file mode 100644 index 0000000000000000000000000000000000000000..7aa1bedd461aac26c26978b29e6e92ec0242dbf0 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/green/poll.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +import gevent +from gevent import select + +import zmq +from zmq import Poller as _original_Poller + + +class _Poller(_original_Poller): + """Replacement for :class:`zmq.Poller` + + Ensures that the greened Poller below is used in calls to + :meth:`zmq.Poller.poll`. + """ + + _gevent_bug_timeout = 1.33 # minimum poll interval, for working around gevent bug + + def _get_descriptors(self): + """Returns three elements tuple with socket descriptors ready + for gevent.select.select + """ + rlist = [] + wlist = [] + xlist = [] + + for socket, flags in self.sockets: + if isinstance(socket, zmq.Socket): + rlist.append(socket.getsockopt(zmq.FD)) + continue + elif isinstance(socket, int): + fd = socket + elif hasattr(socket, 'fileno'): + try: + fd = int(socket.fileno()) + except Exception: + raise ValueError('fileno() must return an valid integer fd') + else: + raise TypeError( + 'Socket must be a 0MQ socket, an integer fd ' + f'or have a fileno() method: {socket!r}' + ) + + if flags & zmq.POLLIN: + rlist.append(fd) + if flags & zmq.POLLOUT: + wlist.append(fd) + if flags & zmq.POLLERR: + xlist.append(fd) + + return (rlist, wlist, xlist) + + def poll(self, timeout=-1): + """Overridden method to ensure that the green version of + Poller is used. + + Behaves the same as :meth:`zmq.core.Poller.poll` + """ + + if timeout is None: + timeout = -1 + + if timeout < 0: + timeout = -1 + + rlist = None + wlist = None + xlist = None + + if timeout > 0: + tout = gevent.Timeout.start_new(timeout / 1000.0) + else: + tout = None + + try: + # Loop until timeout or events available + rlist, wlist, xlist = self._get_descriptors() + while True: + events = super().poll(0) + if events or timeout == 0: + return events + + # wait for activity on sockets in a green way + # set a minimum poll frequency, + # because gevent < 1.0 cannot be trusted to catch edge-triggered FD events + _bug_timeout = gevent.Timeout.start_new(self._gevent_bug_timeout) + try: + select.select(rlist, wlist, xlist) + except gevent.Timeout as t: + if t is not _bug_timeout: + raise + finally: + _bug_timeout.cancel() + + except gevent.Timeout as t: + if t is not tout: + raise + return [] + finally: + if timeout > 0: + tout.cancel() diff --git a/.venv/lib/python3.11/site-packages/zmq/utils/__init__.py b/.venv/lib/python3.11/site-packages/zmq/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.11/site-packages/zmq/utils/__pycache__/interop.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/utils/__pycache__/interop.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b2e6f0d2023f52c64ab24c1613b9d1ee8bfae25 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/utils/__pycache__/interop.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/utils/__pycache__/monitor.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/utils/__pycache__/monitor.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5276951d19622e9008d059cfa69ca5867014c5ca Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/utils/__pycache__/monitor.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/utils/__pycache__/win32.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/utils/__pycache__/win32.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c7344030f362733bdd8ef3b9e3edfe09de99b31 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/utils/__pycache__/win32.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/utils/__pycache__/z85.cpython-311.pyc b/.venv/lib/python3.11/site-packages/zmq/utils/__pycache__/z85.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e54b009e3e70720a48e3c6d69e94cf79e62e2a53 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/zmq/utils/__pycache__/z85.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/zmq/utils/buffers.pxd b/.venv/lib/python3.11/site-packages/zmq/utils/buffers.pxd new file mode 100644 index 0000000000000000000000000000000000000000..bb41a98ccb8008b047d7ecd38d50b9b07bb17c76 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/utils/buffers.pxd @@ -0,0 +1,234 @@ +"""Python version-independent methods for C/Python buffers. + +This file was copied and adapted from mpi4py. + +Authors +------- +* MinRK +""" + +#----------------------------------------------------------------------------- +# Copyright (c) 2010 Lisandro Dalcin +# All rights reserved. +# Used under BSD License: http://www.opensource.org/licenses/bsd-license.php +# +# Retrieval: +# Jul 23, 2010 18:00 PST (r539) +# http://code.google.com/p/mpi4py/source/browse/trunk/src/MPI/asbuffer.pxi +# +# Modifications from original: +# Copyright (c) 2010-2012 Brian Granger, Min Ragan-Kelley +# +# Distributed under the terms of the New BSD License. The full license is in +# the file LICENSE.BSD, distributed as part of this software. +#----------------------------------------------------------------------------- + + +#----------------------------------------------------------------------------- +# Python includes. +#----------------------------------------------------------------------------- + +# get version-independent aliases: +cdef extern from "pyversion_compat.h": + pass + +# Python 3 buffer interface (PEP 3118) +cdef extern from "Python.h": + int PY_MAJOR_VERSION + int PY_MINOR_VERSION + ctypedef int Py_ssize_t + ctypedef struct PyMemoryViewObject: + pass + ctypedef struct Py_buffer: + void *buf + Py_ssize_t len + int readonly + char *format + int ndim + Py_ssize_t *shape + Py_ssize_t *strides + Py_ssize_t *suboffsets + Py_ssize_t itemsize + void *internal + cdef enum: + PyBUF_SIMPLE + PyBUF_WRITABLE + PyBUF_FORMAT + PyBUF_ANY_CONTIGUOUS + int PyObject_CheckBuffer(object) + int PyObject_GetBuffer(object, Py_buffer *, int) except -1 + void PyBuffer_Release(Py_buffer *) + + int PyBuffer_FillInfo(Py_buffer *view, object obj, void *buf, + Py_ssize_t len, int readonly, int infoflags) except -1 + object PyMemoryView_FromBuffer(Py_buffer *info) + + object PyMemoryView_FromObject(object) + + +#----------------------------------------------------------------------------- +# asbuffer: C buffer from python object +#----------------------------------------------------------------------------- + + +cdef inline int check_buffer(object ob): + """Version independent check for whether an object is a buffer. + + Parameters + ---------- + object : object + Any Python object + + Returns + ------- + int : 0 if no buffer interface, 3 if newstyle buffer interface, 2 if oldstyle (removed). + """ + if PyObject_CheckBuffer(ob): + return 3 + return 0 + + +cdef inline object asbuffer(object ob, int writable, int format, + void **base, Py_ssize_t *size, + Py_ssize_t *itemsize): + """Turn an object into a C buffer in a Python version-independent way. + + Parameters + ---------- + ob : object + The object to be turned into a buffer. + Must provide a Python Buffer interface + writable : int + Whether the resulting buffer should be allowed to write + to the object. + format : int + The format of the buffer. See Python buffer docs. + base : void ** + The pointer that will be used to store the resulting C buffer. + size : Py_ssize_t * + The size of the buffer(s). + itemsize : Py_ssize_t * + The size of an item, if the buffer is non-contiguous. + + Returns + ------- + An object describing the buffer format. Generally a str, such as 'B'. + """ + + cdef void *bptr = NULL + cdef Py_ssize_t blen = 0, bitemlen = 0 + cdef Py_buffer view + cdef int flags = PyBUF_SIMPLE + cdef int mode = 0 + + bfmt = None + + mode = check_buffer(ob) + if mode == 0: + raise TypeError("%r does not provide a buffer interface."%ob) + + if mode == 3: + flags = PyBUF_ANY_CONTIGUOUS + if writable: + flags |= PyBUF_WRITABLE + if format: + flags |= PyBUF_FORMAT + PyObject_GetBuffer(ob, &view, flags) + bptr = view.buf + blen = view.len + if format: + if view.format != NULL: + bfmt = view.format + bitemlen = view.itemsize + PyBuffer_Release(&view) + + if base: base[0] = bptr + if size: size[0] = blen + if itemsize: itemsize[0] = bitemlen + + if bfmt is not None: + return bfmt.decode('ascii') + return bfmt + + +cdef inline object asbuffer_r(object ob, void **base, Py_ssize_t *size): + """Wrapper for standard calls to asbuffer with a readonly buffer.""" + asbuffer(ob, 0, 0, base, size, NULL) + return ob + + +cdef inline object asbuffer_w(object ob, void **base, Py_ssize_t *size): + """Wrapper for standard calls to asbuffer with a writable buffer.""" + asbuffer(ob, 1, 0, base, size, NULL) + return ob + +#------------------------------------------------------------------------------ +# frombuffer: python buffer/view from C buffer +#------------------------------------------------------------------------------ + + +cdef inline object frombuffer(void *ptr, Py_ssize_t s, int readonly): + """Create a Python Memory View of a C array. + + Parameters + ---------- + ptr : void * + Pointer to the array to be copied. + s : size_t + Length of the buffer. + readonly : int + whether the resulting object should be allowed to write to the buffer. + + Returns + ------- + Python Memory View of the C buffer. + """ + cdef Py_buffer pybuf + cdef Py_ssize_t *shape = [s] + cdef str astr="" + PyBuffer_FillInfo(&pybuf, astr, ptr, s, readonly, PyBUF_SIMPLE) + pybuf.format = "B" + pybuf.shape = shape + pybuf.ndim = 1 + return PyMemoryView_FromBuffer(&pybuf) + + +cdef inline object frombuffer_r(void *ptr, Py_ssize_t s): + """Wrapper for readonly view frombuffer.""" + return frombuffer(ptr, s, 1) + + +cdef inline object frombuffer_w(void *ptr, Py_ssize_t s): + """Wrapper for writable view frombuffer.""" + return frombuffer(ptr, s, 0) + +#------------------------------------------------------------------------------ +# viewfromobject: python buffer/view from python object, refcounts intact +# frombuffer(asbuffer(obj)) would lose track of refs +#------------------------------------------------------------------------------ + +cdef inline object viewfromobject(object obj, int readonly): + """Construct a Python Memory View object from another Python object. + + Parameters + ---------- + obj : object + The input object to be cast as a buffer + readonly : int + Whether the result should be prevented from overwriting the original. + + Returns + ------- + MemoryView of the original object. + """ + return PyMemoryView_FromObject(obj) + + +cdef inline object viewfromobject_r(object obj): + """Wrapper for readonly viewfromobject.""" + return viewfromobject(obj, 1) + + +cdef inline object viewfromobject_w(object obj): + """Wrapper for writable viewfromobject.""" + return viewfromobject(obj, 0) diff --git a/.venv/lib/python3.11/site-packages/zmq/utils/garbage.py b/.venv/lib/python3.11/site-packages/zmq/utils/garbage.py new file mode 100644 index 0000000000000000000000000000000000000000..2c700313d917f2dd44a3d62b4a2a79bb0a1f9de0 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/utils/garbage.py @@ -0,0 +1,213 @@ +"""Garbage collection thread for representing zmq refcount of Python objects +used in zero-copy sends. +""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. + +import atexit +import struct +import warnings +from collections import namedtuple +from os import getpid +from threading import Event, Lock, Thread + +import zmq + +gcref = namedtuple('gcref', ['obj', 'event']) + + +class GarbageCollectorThread(Thread): + """Thread in which garbage collection actually happens.""" + + def __init__(self, gc): + super().__init__() + self.gc = gc + self.daemon = True + self.pid = getpid() + self.ready = Event() + + def run(self): + # detect fork at beginning of the thread + if getpid is None or getpid() != self.pid: + self.ready.set() + return + try: + s = self.gc.context.socket(zmq.PULL) + s.linger = 0 + s.bind(self.gc.url) + finally: + self.ready.set() + + while True: + # detect fork + if getpid is None or getpid() != self.pid: + return + msg = s.recv() + if msg == b'DIE': + break + fmt = 'L' if len(msg) == 4 else 'Q' + key = struct.unpack(fmt, msg)[0] + tup = self.gc.refs.pop(key, None) + if tup and tup.event: + tup.event.set() + del tup + s.close() + + +class GarbageCollector: + """PyZMQ Garbage Collector + + Used for representing the reference held by libzmq during zero-copy sends. + This object holds a dictionary, keyed by Python id, + of the Python objects whose memory are currently in use by zeromq. + + When zeromq is done with the memory, it sends a message on an inproc PUSH socket + containing the packed size_t (32 or 64-bit unsigned int), + which is the key in the dict. + When the PULL socket in the gc thread receives that message, + the reference is popped from the dict, + and any tracker events that should be signaled fire. + """ + + refs = None + _context = None + _lock = None + url = "inproc://pyzmq.gc.01" + + def __init__(self, context=None): + super().__init__() + self.refs = {} + self.pid = None + self.thread = None + self._context = context + self._lock = Lock() + self._stay_down = False + self._push = None + self._push_mutex = None + atexit.register(self._atexit) + + @property + def context(self): + if self._context is None: + if Thread.__module__.startswith('gevent'): + # gevent has monkey-patched Thread, use green Context + from zmq import green + + self._context = green.Context() + else: + self._context = zmq.Context() + return self._context + + @context.setter + def context(self, ctx): + if self.is_alive(): + if self.refs: + warnings.warn( + "Replacing gc context while gc is running", RuntimeWarning + ) + self.stop() + self._context = ctx + + def _atexit(self): + """atexit callback + + sets _stay_down flag so that gc doesn't try to start up again in other atexit handlers + """ + self._stay_down = True + self.stop() + + def stop(self): + """stop the garbage-collection thread""" + if not self.is_alive(): + return + self._stop() + + def _clear(self): + """Clear state + + called after stop or when setting up a new subprocess + """ + self._push = None + self._push_mutex = None + self.thread = None + self.refs.clear() + self.context = None + + def _stop(self): + push = self.context.socket(zmq.PUSH) + push.connect(self.url) + push.send(b'DIE') + push.close() + if self._push: + self._push.close() + self.thread.join() + self.context.term() + self._clear() + + @property + def _push_socket(self): + """The PUSH socket for use in the zmq message destructor callback.""" + if getattr(self, "_stay_down", False): + raise RuntimeError("zmq gc socket requested during shutdown") + if not self.is_alive() or self._push is None: + self._push = self.context.socket(zmq.PUSH) + self._push.connect(self.url) + return self._push + + def start(self): + """Start a new garbage collection thread. + + Creates a new zmq Context used for garbage collection. + Under most circumstances, this will only be called once per process. + """ + if self.thread is not None and self.pid != getpid(): + # It's re-starting, must free earlier thread's context + # since a fork probably broke it + self._clear() + self.pid = getpid() + self.refs = {} + self.thread = GarbageCollectorThread(self) + self.thread.start() + self.thread.ready.wait() + + def is_alive(self): + """Is the garbage collection thread currently running? + + Includes checks for process shutdown or fork. + """ + if ( + getpid is None + or getpid() != self.pid + or self.thread is None + or not self.thread.is_alive() + ): + return False + return True + + def store(self, obj, event=None): + """store an object and (optionally) event for zero-copy""" + if not self.is_alive(): + if self._stay_down: + return 0 + # safely start the gc thread + # use lock and double check, + # so we don't start multiple threads + with self._lock: + if not self.is_alive(): + self.start() + tup = gcref(obj, event) + theid = id(tup) + self.refs[theid] = tup + return theid + + def __del__(self): + if not self.is_alive(): + return + try: + self.stop() + except Exception as e: + raise (e) + + +gc = GarbageCollector() diff --git a/.venv/lib/python3.11/site-packages/zmq/utils/getpid_compat.h b/.venv/lib/python3.11/site-packages/zmq/utils/getpid_compat.h new file mode 100644 index 0000000000000000000000000000000000000000..3fe1bec4c24ac472fb9ab8940da43adc1de60257 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/utils/getpid_compat.h @@ -0,0 +1,7 @@ +#pragma once +#ifdef _WIN32 + #include + #define getpid _getpid +#else + #include +#endif diff --git a/.venv/lib/python3.11/site-packages/zmq/utils/interop.py b/.venv/lib/python3.11/site-packages/zmq/utils/interop.py new file mode 100644 index 0000000000000000000000000000000000000000..ab4ffd9a813c7c0e490428123b08b9e38c85d52b --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/utils/interop.py @@ -0,0 +1,29 @@ +"""Utils for interoperability with other libraries. + +Just CFFI pointer casting for now. +""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. + +from typing import Any + + +def cast_int_addr(n: Any) -> int: + """Cast an address to a Python int + + This could be a Python integer or a CFFI pointer + """ + if isinstance(n, int): + return n + try: + import cffi # type: ignore + except ImportError: + pass + else: + # from pyzmq, this is an FFI void * + ffi = cffi.FFI() + if isinstance(n, ffi.CData): + return int(ffi.cast("size_t", n)) + + raise ValueError(f"Cannot cast {n!r} to int") diff --git a/.venv/lib/python3.11/site-packages/zmq/utils/ipcmaxlen.h b/.venv/lib/python3.11/site-packages/zmq/utils/ipcmaxlen.h new file mode 100644 index 0000000000000000000000000000000000000000..7af9a261be4be890aab7df2b570d59e09ff2a3e2 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/utils/ipcmaxlen.h @@ -0,0 +1,27 @@ +/* + +Platform-independant detection of IPC path max length + +Copyright (c) 2012 Godefroid Chapelle + +Distributed under the terms of the New BSD License. The full license is in +the file LICENSE.BSD, distributed as part of this software. + */ + +#pragma once + +#if defined(HAVE_SYS_UN_H) +#if defined _MSC_VER +#include +#else +#include +#endif +int get_ipc_path_max_len(void) { + struct sockaddr_un *dummy; + return sizeof(dummy->sun_path) - 1; +} +#else +int get_ipc_path_max_len(void) { + return 0; +} +#endif diff --git a/.venv/lib/python3.11/site-packages/zmq/utils/jsonapi.py b/.venv/lib/python3.11/site-packages/zmq/utils/jsonapi.py new file mode 100644 index 0000000000000000000000000000000000000000..6a6ee0785229f449ccc9efc3e778909699866de0 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/utils/jsonapi.py @@ -0,0 +1,38 @@ +"""JSON serialize to/from utf8 bytes + +.. versionchanged:: 22.2 + Remove optional imports of different JSON implementations. + Now that we require recent Python, unconditionally use the standard library. + Custom JSON libraries can be used via custom serialization functions. +""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. +from __future__ import annotations + +import json +from typing import Any + +# backward-compatibility, unused +jsonmod = json + + +def dumps(o: Any, **kwargs) -> bytes: + """Serialize object to JSON bytes (utf-8). + + Keyword arguments are passed along to :py:func:`json.dumps`. + """ + return json.dumps(o, **kwargs).encode("utf8") + + +def loads(s: bytes | str, **kwargs) -> dict | list | str | int | float: + """Load object from JSON bytes (utf-8). + + Keyword arguments are passed along to :py:func:`json.loads`. + """ + if isinstance(s, bytes): + s = s.decode("utf8") + return json.loads(s, **kwargs) + + +__all__ = ['dumps', 'loads'] diff --git a/.venv/lib/python3.11/site-packages/zmq/utils/monitor.py b/.venv/lib/python3.11/site-packages/zmq/utils/monitor.py new file mode 100644 index 0000000000000000000000000000000000000000..f549052b5d24d6dfd3abb64136149f97c23ce634 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/utils/monitor.py @@ -0,0 +1,129 @@ +"""Module holding utility and convenience functions for zmq event monitoring.""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. + +from __future__ import annotations + +import struct +from typing import Awaitable, overload + +import zmq +import zmq.asyncio +from zmq._typing import TypedDict +from zmq.error import _check_version + + +class _MonitorMessage(TypedDict): + event: int + value: int + endpoint: bytes + + +def parse_monitor_message(msg: list[bytes]) -> _MonitorMessage: + """decode zmq_monitor event messages. + + Parameters + ---------- + msg : list(bytes) + zmq multipart message that has arrived on a monitor PAIR socket. + + First frame is:: + + 16 bit event id + 32 bit event value + no padding + + Second frame is the endpoint as a bytestring + + Returns + ------- + event : dict + event description as dict with the keys `event`, `value`, and `endpoint`. + """ + if len(msg) != 2 or len(msg[0]) != 6: + raise RuntimeError(f"Invalid event message format: {msg}") + event_id, value = struct.unpack("=hi", msg[0]) + event: _MonitorMessage = { + 'event': zmq.Event(event_id), + 'value': zmq.Event(value), + 'endpoint': msg[1], + } + return event + + +async def _parse_monitor_msg_async( + awaitable_msg: Awaitable[list[bytes]], +) -> _MonitorMessage: + """Like parse_monitor_msg, but awaitable + + Given awaitable message, return awaitable for the parsed monitor message. + """ + + msg = await awaitable_msg + # 4.0-style event API + return parse_monitor_message(msg) + + +@overload +def recv_monitor_message( + socket: zmq.asyncio.Socket, + flags: int = 0, +) -> Awaitable[_MonitorMessage]: ... + + +@overload +def recv_monitor_message( + socket: zmq.Socket[bytes], + flags: int = 0, +) -> _MonitorMessage: ... + + +def recv_monitor_message( + socket: zmq.Socket, + flags: int = 0, +) -> _MonitorMessage | Awaitable[_MonitorMessage]: + """Receive and decode the given raw message from the monitoring socket and return a dict. + + Requires libzmq ≥ 4.0 + + The returned dict will have the following entries: + event : int + the event id as described in `libzmq.zmq_socket_monitor` + value : int + the event value associated with the event, see `libzmq.zmq_socket_monitor` + endpoint : str + the affected endpoint + + .. versionchanged:: 23.1 + Support for async sockets added. + When called with a async socket, + returns an awaitable for the monitor message. + + Parameters + ---------- + socket : zmq.Socket + The PAIR socket (created by other.get_monitor_socket()) on which to recv the message + flags : int + standard zmq recv flags + + Returns + ------- + event : dict + event description as dict with the keys `event`, `value`, and `endpoint`. + """ + + _check_version((4, 0), 'libzmq event API') + # will always return a list + msg = socket.recv_multipart(flags) + + # transparently handle asyncio socket, + # returns a Future instead of a dict + if isinstance(msg, Awaitable): + return _parse_monitor_msg_async(msg) + + # 4.0-style event API + return parse_monitor_message(msg) + + +__all__ = ['parse_monitor_message', 'recv_monitor_message'] diff --git a/.venv/lib/python3.11/site-packages/zmq/utils/mutex.h b/.venv/lib/python3.11/site-packages/zmq/utils/mutex.h new file mode 100644 index 0000000000000000000000000000000000000000..2191d08d1851ec6fabc56cfa73536f52d409a351 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/utils/mutex.h @@ -0,0 +1,82 @@ +/* +* simplified from mutex.c from Foundation Library, in the Public Domain +* https://github.com/rampantpixels/foundation_lib/blob/master/foundation/mutex.c +* +* This file is Copyright (C) PyZMQ Developers +* Distributed under the terms of the Modified BSD License. +* +*/ + +#pragma once + +#if defined(_WIN32) +# include +#else +# include +#endif + +typedef struct { +#if defined(_WIN32) + CRITICAL_SECTION csection; +#else + pthread_mutex_t mutex; +#endif +} mutex_t; + + +static void +_mutex_initialize(mutex_t* mutex) { +#if defined(_WIN32) + InitializeCriticalSectionAndSpinCount(&mutex->csection, 4000); +#else + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mutex->mutex, &attr); + pthread_mutexattr_destroy(&attr); +#endif +} + +static void +_mutex_finalize(mutex_t* mutex) { +#if defined(_WIN32) + DeleteCriticalSection(&mutex->csection); +#else + pthread_mutex_destroy(&mutex->mutex); +#endif +} + +mutex_t* +mutex_allocate(void) { + mutex_t* mutex = (mutex_t*)malloc(sizeof(mutex_t)); + _mutex_initialize(mutex); + return mutex; +} + +void +mutex_deallocate(mutex_t* mutex) { + if (!mutex) + return; + _mutex_finalize(mutex); + free(mutex); +} + +int +mutex_lock(mutex_t* mutex) { +#if defined(_WIN32) + EnterCriticalSection(&mutex->csection); + return 0; +#else + return pthread_mutex_lock(&mutex->mutex); +#endif +} + +int +mutex_unlock(mutex_t* mutex) { +#if defined(_WIN32) + LeaveCriticalSection(&mutex->csection); + return 0; +#else + return pthread_mutex_unlock(&mutex->mutex); +#endif +} diff --git a/.venv/lib/python3.11/site-packages/zmq/utils/pyversion_compat.h b/.venv/lib/python3.11/site-packages/zmq/utils/pyversion_compat.h new file mode 100644 index 0000000000000000000000000000000000000000..fb19dcf09d6840ac12e995a308bb96838813797a --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/utils/pyversion_compat.h @@ -0,0 +1,12 @@ +#include "Python.h" + +// default to Python's own target Windows version(s) +// override by setting WINVER, _WIN32_WINNT, (maybe also NTDDI_VERSION?) macros +#ifdef Py_WINVER +#ifndef WINVER +#define WINVER Py_WINVER +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT Py_WINVER +#endif +#endif diff --git a/.venv/lib/python3.11/site-packages/zmq/utils/strtypes.py b/.venv/lib/python3.11/site-packages/zmq/utils/strtypes.py new file mode 100644 index 0000000000000000000000000000000000000000..3d90a04809ca4f29e1457099b63c03302e837a1e --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/utils/strtypes.py @@ -0,0 +1,62 @@ +"""Declare basic string types unambiguously for various Python versions. + +Authors +------- +* MinRK +""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. + +import warnings + +bytes = bytes +unicode = str +basestring = (str,) + + +def cast_bytes(s, encoding='utf8', errors='strict'): + """cast unicode or bytes to bytes""" + warnings.warn( + "zmq.utils.strtypes is deprecated in pyzmq 23.", + DeprecationWarning, + stacklevel=2, + ) + if isinstance(s, bytes): + return s + elif isinstance(s, str): + return s.encode(encoding, errors) + else: + raise TypeError(f"Expected unicode or bytes, got {s!r}") + + +def cast_unicode(s, encoding='utf8', errors='strict'): + """cast bytes or unicode to unicode""" + warnings.warn( + "zmq.utils.strtypes is deprecated in pyzmq 23.", + DeprecationWarning, + stacklevel=2, + ) + if isinstance(s, bytes): + return s.decode(encoding, errors) + elif isinstance(s, str): + return s + else: + raise TypeError(f"Expected unicode or bytes, got {s!r}") + + +# give short 'b' alias for cast_bytes, so that we can use fake b'stuff' +# to simulate b'stuff' +b = asbytes = cast_bytes +u = cast_unicode + +__all__ = [ + 'asbytes', + 'bytes', + 'unicode', + 'basestring', + 'b', + 'u', + 'cast_bytes', + 'cast_unicode', +] diff --git a/.venv/lib/python3.11/site-packages/zmq/utils/win32.py b/.venv/lib/python3.11/site-packages/zmq/utils/win32.py new file mode 100644 index 0000000000000000000000000000000000000000..019d429715af914d4aeb508bda342f764a74e9d0 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/utils/win32.py @@ -0,0 +1,130 @@ +"""Win32 compatibility utilities.""" + +# ----------------------------------------------------------------------------- +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. +# ----------------------------------------------------------------------------- +from __future__ import annotations + +import os +from typing import Any, Callable + + +class allow_interrupt: + """Utility for fixing CTRL-C events on Windows. + + On Windows, the Python interpreter intercepts CTRL-C events in order to + translate them into ``KeyboardInterrupt`` exceptions. It (presumably) + does this by setting a flag in its "console control handler" and + checking it later at a convenient location in the interpreter. + + However, when the Python interpreter is blocked waiting for the ZMQ + poll operation to complete, it must wait for ZMQ's ``select()`` + operation to complete before translating the CTRL-C event into the + ``KeyboardInterrupt`` exception. + + The only way to fix this seems to be to add our own "console control + handler" and perform some application-defined operation that will + unblock the ZMQ polling operation in order to force ZMQ to pass control + back to the Python interpreter. + + This context manager performs all that Windows-y stuff, providing you + with a hook that is called when a CTRL-C event is intercepted. This + hook allows you to unblock your ZMQ poll operation immediately, which + will then result in the expected ``KeyboardInterrupt`` exception. + + Without this context manager, your ZMQ-based application will not + respond normally to CTRL-C events on Windows. If a CTRL-C event occurs + while blocked on ZMQ socket polling, the translation to a + ``KeyboardInterrupt`` exception will be delayed until the I/O completes + and control returns to the Python interpreter (this may never happen if + you use an infinite timeout). + + A no-op implementation is provided on non-Win32 systems to avoid the + application from having to conditionally use it. + + Example usage: + + .. sourcecode:: python + + def stop_my_application(): + # ... + + with allow_interrupt(stop_my_application): + # main polling loop. + + In a typical ZMQ application, you would use the "self pipe trick" to + send message to a ``PAIR`` socket in order to interrupt your blocking + socket polling operation. + + In a Tornado event loop, you can use the ``IOLoop.stop`` method to + unblock your I/O loop. + """ + + def __init__(self, action: Callable[[], Any] | None = None) -> None: + """Translate ``action`` into a CTRL-C handler. + + ``action`` is a callable that takes no arguments and returns no + value (returned value is ignored). It must *NEVER* raise an + exception. + + If unspecified, a no-op will be used. + """ + if os.name != "nt": + return + self._init_action(action) + + def _init_action(self, action): + from ctypes import WINFUNCTYPE, windll + from ctypes.wintypes import BOOL, DWORD + + kernel32 = windll.LoadLibrary('kernel32') + + # + PHANDLER_ROUTINE = WINFUNCTYPE(BOOL, DWORD) + SetConsoleCtrlHandler = self._SetConsoleCtrlHandler = ( + kernel32.SetConsoleCtrlHandler + ) + SetConsoleCtrlHandler.argtypes = (PHANDLER_ROUTINE, BOOL) + SetConsoleCtrlHandler.restype = BOOL + + if action is None: + + def action(): + return None + + self.action = action + + @PHANDLER_ROUTINE + def handle(event): + if event == 0: # CTRL_C_EVENT + action() + # Typical C implementations would return 1 to indicate that + # the event was processed and other control handlers in the + # stack should not be executed. However, that would + # prevent the Python interpreter's handler from translating + # CTRL-C to a `KeyboardInterrupt` exception, so we pretend + # that we didn't handle it. + return 0 + + self.handle = handle + + def __enter__(self): + """Install the custom CTRL-C handler.""" + if os.name != "nt": + return + result = self._SetConsoleCtrlHandler(self.handle, 1) + if result == 0: + # Have standard library automatically call `GetLastError()` and + # `FormatMessage()` into a nice exception object :-) + raise OSError() + + def __exit__(self, *args): + """Remove the custom CTRL-C handler.""" + if os.name != "nt": + return + result = self._SetConsoleCtrlHandler(self.handle, 0) + if result == 0: + # Have standard library automatically call `GetLastError()` and + # `FormatMessage()` into a nice exception object :-) + raise OSError() diff --git a/.venv/lib/python3.11/site-packages/zmq/utils/z85.py b/.venv/lib/python3.11/site-packages/zmq/utils/z85.py new file mode 100644 index 0000000000000000000000000000000000000000..016bdfe12f303f1542fe00fc92ab1a72a20a20b9 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/utils/z85.py @@ -0,0 +1,58 @@ +"""Python implementation of Z85 85-bit encoding + +Z85 encoding is a plaintext encoding for a bytestring interpreted as 32bit integers. +Since the chunks are 32bit, a bytestring must be a multiple of 4 bytes. +See ZMQ RFC 32 for details. + + +""" + +# Copyright (C) PyZMQ Developers +# Distributed under the terms of the Modified BSD License. +from __future__ import annotations + +import struct + +# Z85CHARS is the base 85 symbol table +Z85CHARS = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#" +# Z85MAP maps integers in [0,84] to the appropriate character in Z85CHARS +Z85MAP = {c: idx for idx, c in enumerate(Z85CHARS)} + +_85s = [85**i for i in range(5)][::-1] + + +def encode(rawbytes): + """encode raw bytes into Z85""" + # Accepts only byte arrays bounded to 4 bytes + if len(rawbytes) % 4: + raise ValueError(f"length must be multiple of 4, not {len(rawbytes)}") + + nvalues = len(rawbytes) // 4 + values = struct.unpack(f'>{nvalues:d}I', rawbytes) + encoded = [] + for v in values: + for offset in _85s: + encoded.append(Z85CHARS[(v // offset) % 85]) + + return bytes(encoded) + + +def decode(z85bytes): + """decode Z85 bytes to raw bytes, accepts ASCII string""" + if isinstance(z85bytes, str): + try: + z85bytes = z85bytes.encode('ascii') + except UnicodeEncodeError: + raise ValueError('string argument should contain only ASCII characters') + + if len(z85bytes) % 5: + raise ValueError(f"Z85 length must be multiple of 5, not {len(z85bytes)}") + + nvalues = len(z85bytes) // 5 + values = [] + for i in range(0, len(z85bytes), 5): + value = 0 + for j, offset in enumerate(_85s): + value += Z85MAP[z85bytes[i + j]] * offset + values.append(value) + return struct.pack(f'>{nvalues:d}I', *values) diff --git a/.venv/lib/python3.11/site-packages/zmq/utils/zmq_compat.h b/.venv/lib/python3.11/site-packages/zmq/utils/zmq_compat.h new file mode 100644 index 0000000000000000000000000000000000000000..7e005d600fd53166c192d12db6d46ed4c217c27b --- /dev/null +++ b/.venv/lib/python3.11/site-packages/zmq/utils/zmq_compat.h @@ -0,0 +1,112 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2010 Brian Granger, Min Ragan-Kelley +// +// Distributed under the terms of the New BSD License. The full license is in +// the file LICENSE.BSD, distributed as part of this software. +//----------------------------------------------------------------------------- + +#pragma once + +#if defined(_MSC_VER) +#define pyzmq_int64_t __int64 +#define pyzmq_uint32_t unsigned __int32 +#else +#include +#define pyzmq_int64_t int64_t +#define pyzmq_uint32_t uint32_t +#endif + + +#include "zmq.h" + +#define _missing (-1) + +#if (ZMQ_VERSION >= 40303) + // libzmq >= 4.3.3 defines zmq_fd_t for us + #define ZMQ_FD_T zmq_fd_t +#else + #ifdef _WIN32 + #if defined(_MSC_VER) && _MSC_VER <= 1400 + #define ZMQ_FD_T UINT_PTR + #else + #define ZMQ_FD_T SOCKET + #endif + #else + #define ZMQ_FD_T int + #endif +#endif + +#if (ZMQ_VERSION >= 40200) + // Nothing to remove +#else + #define zmq_curve_public(z85_public_key, z85_secret_key) _missing +#endif + +// use unambiguous aliases for zmq_send/recv functions + +#if ZMQ_VERSION_MAJOR >= 4 +// nothing to remove + #if ZMQ_VERSION_MAJOR == 4 && ZMQ_VERSION_MINOR == 0 + // zmq 4.1 deprecates zmq_utils.h + // we only get zmq_curve_keypair from it + #include "zmq_utils.h" + #endif +#else + #define zmq_curve_keypair(z85_public_key, z85_secret_key) _missing +#endif + +// libzmq 4.2 draft API +#ifdef ZMQ_BUILD_DRAFT_API + #if ZMQ_VERSION >= 40200 + #define PYZMQ_DRAFT_42 + #endif +#endif +#ifndef PYZMQ_DRAFT_42 + #define zmq_join(s, group) _missing + #define zmq_leave(s, group) _missing + #define zmq_msg_set_routing_id(msg, routing_id) _missing + #define zmq_msg_routing_id(msg) 0 + #define zmq_msg_set_group(msg, group) _missing + #define zmq_msg_group(msg) NULL +#endif + +#if ZMQ_VERSION >= 40100 +// nothing to remove +#else + #define zmq_msg_gets(msg, prop) _missing + #define zmq_has(capability) _missing + #define zmq_proxy_steerable(in, out, mon, ctrl) _missing +#endif + +#if ZMQ_VERSION_MAJOR >= 3 + #define zmq_sendbuf zmq_send + #define zmq_recvbuf zmq_recv + + // 3.x deprecations - these symbols haven't been removed, + // but let's protect against their planned removal + #define zmq_device(device_type, isocket, osocket) _missing + #define zmq_init(io_threads) ((void*)NULL) + #define zmq_term zmq_ctx_destroy +#else + #define zmq_ctx_set(ctx, opt, val) _missing + #define zmq_ctx_get(ctx, opt) _missing + #define zmq_ctx_destroy zmq_term + #define zmq_ctx_new() ((void*)NULL) + + #define zmq_proxy(a,b,c) _missing + + #define zmq_disconnect(s, addr) _missing + #define zmq_unbind(s, addr) _missing + + #define zmq_msg_more(msg) _missing + #define zmq_msg_get(msg, opt) _missing + #define zmq_msg_set(msg, opt, val) _missing + #define zmq_msg_send(msg, s, flags) zmq_send(s, msg, flags) + #define zmq_msg_recv(msg, s, flags) zmq_recv(s, msg, flags) + + #define zmq_sendbuf(s, buf, len, flags) _missing + #define zmq_recvbuf(s, buf, len, flags) _missing + + #define zmq_socket_monitor(s, addr, flags) _missing + +#endif