IPC ring bug impl with async read
							parent
							
								
									caa76eb30d
								
							
						
					
					
						commit
						8c82a05f4b
					
				|  | @ -0,0 +1,18 @@ | |||
| { pkgs ? import <nixpkgs> {} }: | ||||
| let | ||||
|   nativeBuildInputs = with pkgs; [ | ||||
|     stdenv.cc.cc.lib | ||||
|     uv | ||||
|   ]; | ||||
| 
 | ||||
| in | ||||
| pkgs.mkShell { | ||||
|   inherit nativeBuildInputs; | ||||
| 
 | ||||
|   LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath nativeBuildInputs; | ||||
| 
 | ||||
|   shellHook = '' | ||||
|     set -e | ||||
|     uv venv .venv --python=3.12 | ||||
|   ''; | ||||
| } | ||||
|  | @ -48,6 +48,7 @@ dependencies = [ | |||
|   # typed IPC msging | ||||
|   # TODO, get back on release once 3.13 support is out! | ||||
|   "msgspec>=0.19.0", | ||||
|   "cffi>=1.17.1", | ||||
| ] | ||||
| 
 | ||||
| # ------ project ------ | ||||
|  |  | |||
|  | @ -2,7 +2,10 @@ | |||
| Shared mem primitives and APIs. | ||||
| 
 | ||||
| """ | ||||
| import time | ||||
| import uuid | ||||
| import string | ||||
| import random | ||||
| 
 | ||||
| # import numpy | ||||
| import pytest | ||||
|  | @ -11,6 +14,7 @@ import tractor | |||
| from tractor._shm import ( | ||||
|     open_shm_list, | ||||
|     attach_shm_list, | ||||
|     EventFD, open_ringbuffer_sender, open_ringbuffer_receiver, | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -165,3 +169,79 @@ def test_parent_writer_child_reader( | |||
|             await portal.cancel_actor() | ||||
| 
 | ||||
|     trio.run(main) | ||||
| 
 | ||||
| 
 | ||||
| def random_string(size=256): | ||||
|     return ''.join(random.choice(string.ascii_lowercase) for i in range(size)) | ||||
| 
 | ||||
| 
 | ||||
| async def child_read_shm( | ||||
|     msg_amount: int, | ||||
|     key: str, | ||||
|     write_event_fd: int, | ||||
|     wrap_event_fd: int, | ||||
|     max_bytes: int, | ||||
| ) -> None: | ||||
|     log = tractor.log.get_console_log(level='info') | ||||
|     recvd_msgs = 0 | ||||
|     start_ts = time.time() | ||||
|     async with open_ringbuffer_receiver( | ||||
|         write_event_fd, | ||||
|         wrap_event_fd, | ||||
|         key, | ||||
|         max_bytes=max_bytes | ||||
|     ) as receiver: | ||||
|         while recvd_msgs < msg_amount: | ||||
|             msg = await receiver.receive_some() | ||||
|             msgs = bytes(msg).split(b'\n') | ||||
|             first = msgs[0] | ||||
|             last = msgs[-2] | ||||
|             log.info((receiver.ptr - len(msg), receiver.ptr, first[:10], last[:10])) | ||||
|             recvd_msgs += len(msgs) | ||||
| 
 | ||||
|     end_ts = time.time() | ||||
|     elapsed = end_ts - start_ts | ||||
|     elapsed_ms = int(elapsed * 1000) | ||||
|     log.info(f'elapsed ms: {elapsed_ms}') | ||||
|     log.info(f'msg/sec: {int(msg_amount / elapsed):,}') | ||||
|     log.info(f'bytes/sec: {int(max_bytes / elapsed):,}') | ||||
| 
 | ||||
| def test_ring_buff(): | ||||
|     log = tractor.log.get_console_log(level='info') | ||||
|     msg_amount = 100_000 | ||||
|     log.info(f'generating {msg_amount} messages...') | ||||
|     msgs = [ | ||||
|         f'[{i:08}]: {random_string()}\n'.encode('utf-8') | ||||
|         for i in range(msg_amount) | ||||
|     ] | ||||
|     buf_size = sum((len(m) for m in msgs)) | ||||
|     log.info(f'done! buffer size: {buf_size}') | ||||
|     async def main(): | ||||
|         with ( | ||||
|             EventFD(initval=0) as write_event, | ||||
|             EventFD(initval=0) as wrap_event, | ||||
|         ): | ||||
|             async with ( | ||||
|                 tractor.open_nursery() as an, | ||||
|                 open_ringbuffer_sender( | ||||
|                     write_event.fd, | ||||
|                     wrap_event.fd, | ||||
|                     max_bytes=buf_size | ||||
|                 ) as sender | ||||
|             ): | ||||
|                 await an.run_in_actor( | ||||
|                     child_read_shm, | ||||
|                     msg_amount=msg_amount, | ||||
|                     key=sender.key, | ||||
|                     write_event_fd=write_event.fd, | ||||
|                     wrap_event_fd=wrap_event.fd, | ||||
|                     max_bytes=buf_size, | ||||
|                     proc_kwargs={ | ||||
|                         'pass_fds': (write_event.fd, wrap_event.fd) | ||||
|                     } | ||||
|                 ) | ||||
|                 for msg in msgs: | ||||
|                     await sender.send_all(msg) | ||||
| 
 | ||||
| 
 | ||||
|     trio.run(main) | ||||
|  |  | |||
							
								
								
									
										299
									
								
								tractor/_shm.py
								
								
								
								
							
							
						
						
									
										299
									
								
								tractor/_shm.py
								
								
								
								
							|  | @ -25,6 +25,7 @@ considered optional within the context of this runtime-library. | |||
| from __future__ import annotations | ||||
| from sys import byteorder | ||||
| import time | ||||
| import platform | ||||
| from typing import Optional | ||||
| from multiprocessing import shared_memory as shm | ||||
| from multiprocessing.shared_memory import ( | ||||
|  | @ -32,7 +33,7 @@ from multiprocessing.shared_memory import ( | |||
|     ShareableList, | ||||
| ) | ||||
| 
 | ||||
| from msgspec import Struct | ||||
| from msgspec import Struct, to_builtins | ||||
| import tractor | ||||
| 
 | ||||
| from .log import get_logger | ||||
|  | @ -142,7 +143,7 @@ class NDToken(Struct, frozen=True): | |||
|         ).descr | ||||
| 
 | ||||
|     def as_msg(self): | ||||
|         return self.to_dict() | ||||
|         return to_builtins(self) | ||||
| 
 | ||||
|     @classmethod | ||||
|     def from_msg(cls, msg: dict) -> NDToken: | ||||
|  | @ -831,3 +832,297 @@ def attach_shm_list( | |||
|         name=key, | ||||
|         readonly=readonly, | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| if platform.system() == 'Linux': | ||||
|     import os | ||||
|     import errno | ||||
|     import string | ||||
|     import random | ||||
|     from contextlib import asynccontextmanager as acm | ||||
| 
 | ||||
|     import cffi | ||||
|     import trio | ||||
| 
 | ||||
|     ffi = cffi.FFI() | ||||
| 
 | ||||
|     # Declare the C functions and types we plan to use. | ||||
|     #    - eventfd: for creating the event file descriptor | ||||
|     #    - write:   for writing to the file descriptor | ||||
|     #    - read:    for reading from the file descriptor | ||||
|     #    - close:   for closing the file descriptor | ||||
|     ffi.cdef( | ||||
|         ''' | ||||
|         int eventfd(unsigned int initval, int flags); | ||||
| 
 | ||||
|         ssize_t write(int fd, const void *buf, size_t count); | ||||
|         ssize_t read(int fd, void *buf, size_t count); | ||||
| 
 | ||||
|         int close(int fd); | ||||
|         ''' | ||||
|     ) | ||||
| 
 | ||||
|     # Open the default dynamic library (essentially 'libc' in most cases) | ||||
|     C = ffi.dlopen(None) | ||||
| 
 | ||||
|     # Constants from <sys/eventfd.h>, if needed. | ||||
|     EFD_SEMAPHORE = 1 << 0  # 0x1 | ||||
|     EFD_CLOEXEC   = 1 << 1  # 0x2 | ||||
|     EFD_NONBLOCK  = 1 << 2  # 0x4 | ||||
| 
 | ||||
| 
 | ||||
|     def open_eventfd(initval: int = 0, flags: int = 0) -> int: | ||||
|         ''' | ||||
|         Open an eventfd with the given initial value and flags. | ||||
|         Returns the file descriptor on success, otherwise raises OSError. | ||||
|         ''' | ||||
|         fd = C.eventfd(initval, flags) | ||||
|         if fd < 0: | ||||
|             raise OSError(errno.errorcode[ffi.errno], 'eventfd failed') | ||||
|         return fd | ||||
| 
 | ||||
|     def write_eventfd(fd: int, value: int) -> int: | ||||
|         ''' | ||||
|         Write a 64-bit integer (uint64_t) to the eventfd's counter. | ||||
|         ''' | ||||
|         # Create a uint64_t* in C, store `value` | ||||
|         data_ptr = ffi.new('uint64_t *', value) | ||||
| 
 | ||||
|         # Call write(fd, data_ptr, 8) | ||||
|         # We expect to write exactly 8 bytes (sizeof(uint64_t)) | ||||
|         ret = C.write(fd, data_ptr, 8) | ||||
|         if ret < 0: | ||||
|             raise OSError(errno.errorcode[ffi.errno], 'write to eventfd failed') | ||||
|         return ret | ||||
| 
 | ||||
|     def read_eventfd(fd: int) -> int: | ||||
|         ''' | ||||
|         Read a 64-bit integer (uint64_t) from the eventfd, returning the value. | ||||
|         Reading resets the counter to 0 (unless using EFD_SEMAPHORE). | ||||
|         ''' | ||||
|         # Allocate an 8-byte buffer in C for reading | ||||
|         buf = ffi.new('char[]', 8) | ||||
| 
 | ||||
|         ret = C.read(fd, buf, 8) | ||||
|         if ret < 0: | ||||
|             raise OSError(errno.errorcode[ffi.errno], 'read from eventfd failed') | ||||
|         # Convert the 8 bytes we read into a Python integer | ||||
|         data_bytes = ffi.unpack(buf, 8)  # returns a Python bytes object of length 8 | ||||
|         value = int.from_bytes(data_bytes, byteorder='little', signed=False) | ||||
|         return value | ||||
| 
 | ||||
|     def close_eventfd(fd: int) -> int: | ||||
|         ''' | ||||
|         Close the eventfd. | ||||
|         ''' | ||||
|         ret = C.close(fd) | ||||
|         if ret < 0: | ||||
|             raise OSError(errno.errorcode[ffi.errno], 'close failed') | ||||
| 
 | ||||
| 
 | ||||
|     class EventFD: | ||||
| 
 | ||||
|         def __init__( | ||||
|             self, | ||||
|             initval: int = 0, | ||||
|             flags: int = 0, | ||||
|             fd: int | None = None, | ||||
|             omode: str = 'r' | ||||
|         ): | ||||
|             self._initval: int = initval | ||||
|             self._flags: int = flags | ||||
|             self._fd: int | None = fd | ||||
|             self._omode: str = omode | ||||
|             self._fobj = None | ||||
| 
 | ||||
|         @property | ||||
|         def fd(self) -> int | None: | ||||
|             return self._fd | ||||
| 
 | ||||
|         def write(self, value: int) -> int: | ||||
|             return write_eventfd(self._fd, value) | ||||
| 
 | ||||
|         async def read(self) -> int: | ||||
|             return await trio.to_thread.run_sync(read_eventfd, self._fd) | ||||
| 
 | ||||
|         def open(self): | ||||
|             if not self._fd: | ||||
|                 self._fd = open_eventfd( | ||||
|                     initval=self._initval, flags=self._flags) | ||||
| 
 | ||||
|             else: | ||||
|                 self._fobj = os.fdopen(self._fd, self._omode) | ||||
| 
 | ||||
|         def close(self): | ||||
|             if self._fobj: | ||||
|                 self._fobj.close() | ||||
|                 return | ||||
| 
 | ||||
|             if self._fd: | ||||
|                 close_eventfd(self._fd) | ||||
| 
 | ||||
|         def __enter__(self): | ||||
|             self.open() | ||||
|             return self | ||||
| 
 | ||||
|         def __exit__(self, exc_type, exc_value, traceback): | ||||
|             self.close() | ||||
| 
 | ||||
| 
 | ||||
|     class RingBuffSender(trio.abc.SendStream): | ||||
| 
 | ||||
|         def __init__( | ||||
|             self, | ||||
|             shm: SharedMemory, | ||||
|             write_event: EventFD, | ||||
|             wrap_event: EventFD, | ||||
|             start_ptr: int = 0 | ||||
|         ): | ||||
|             self._shm: SharedMemory = shm | ||||
|             self._write_event = write_event | ||||
|             self._wrap_event = wrap_event | ||||
|             self._ptr = start_ptr | ||||
| 
 | ||||
|         @property | ||||
|         def key(self) -> str: | ||||
|             return self._shm.name | ||||
| 
 | ||||
|         @property | ||||
|         def size(self) -> int: | ||||
|             return self._shm.size | ||||
| 
 | ||||
|         @property | ||||
|         def ptr(self) -> int: | ||||
|             return self._ptr | ||||
| 
 | ||||
|         @property | ||||
|         def write_fd(self) -> int: | ||||
|             return self._write_event.fd | ||||
| 
 | ||||
|         @property | ||||
|         def wrap_fd(self) -> int: | ||||
|             return self._wrap_event.fd | ||||
| 
 | ||||
|         async def send_all(self, data: bytes | bytearray | memoryview): | ||||
|             target_ptr = self.ptr + len(data) | ||||
|             if target_ptr > self.size: | ||||
|                 remaining = self.size - self.ptr | ||||
|                 self._shm.buf[self.ptr:] = data[:remaining] | ||||
|                 self._write_event.write(remaining) | ||||
|                 await self._wrap_event.read() | ||||
|                 self._ptr = 0 | ||||
|                 data = data[remaining:] | ||||
|                 target_ptr = self._ptr + len(data) | ||||
| 
 | ||||
|             self._shm.buf[self.ptr:target_ptr] = data | ||||
|             self._write_event.write(len(data)) | ||||
|             self._ptr = target_ptr | ||||
| 
 | ||||
|         async def wait_send_all_might_not_block(self): | ||||
|             ... | ||||
| 
 | ||||
|         async def aclose(self): | ||||
|             ... | ||||
| 
 | ||||
|         async def __aenter__(self): | ||||
|             self._write_event.open() | ||||
|             self._wrap_event.open() | ||||
|             return self | ||||
| 
 | ||||
|         async def __aexit__(self, exc_type, exc_value, traceback): | ||||
|             await self.aclose() | ||||
| 
 | ||||
| 
 | ||||
|     class RingBuffReceiver(trio.abc.ReceiveStream): | ||||
| 
 | ||||
|         def __init__( | ||||
|             self, | ||||
|             shm: SharedMemory, | ||||
|             write_event: EventFD, | ||||
|             wrap_event: EventFD, | ||||
|             start_ptr: int = 0 | ||||
|         ): | ||||
|             self._shm: SharedMemory = shm | ||||
|             self._write_event = write_event | ||||
|             self._wrap_event = wrap_event | ||||
|             self._ptr = start_ptr | ||||
| 
 | ||||
|         @property | ||||
|         def key(self) -> str: | ||||
|             return self._shm.name | ||||
| 
 | ||||
|         @property | ||||
|         def size(self) -> int: | ||||
|             return self._shm.size | ||||
| 
 | ||||
|         @property | ||||
|         def ptr(self) -> int: | ||||
|             return self._ptr | ||||
| 
 | ||||
|         @property | ||||
|         def write_fd(self) -> int: | ||||
|             return self._write_event.fd | ||||
| 
 | ||||
|         @property | ||||
|         def wrap_fd(self) -> int: | ||||
|             return self._wrap_event.fd | ||||
| 
 | ||||
|         async def receive_some(self, max_bytes: int | None = None) -> bytes: | ||||
|             delta = await self._write_event.read() | ||||
|             next_ptr = self._ptr + delta | ||||
|             segment = bytes(self._shm.buf[self._ptr:next_ptr]) | ||||
|             self._ptr = next_ptr | ||||
|             if self.ptr == self.size: | ||||
|                 self._ptr = 0 | ||||
|                 self._wrap_event.write(1) | ||||
|             return segment | ||||
| 
 | ||||
|         async def aclose(self): | ||||
|             ... | ||||
| 
 | ||||
|         async def __aenter__(self): | ||||
|             self._write_event.open() | ||||
|             self._wrap_event.open() | ||||
|             return self | ||||
| 
 | ||||
|         async def __aexit__(self, exc_type, exc_value, traceback): | ||||
|             await self.aclose() | ||||
| 
 | ||||
|     @acm | ||||
|     async def open_ringbuffer_sender( | ||||
|         write_event_fd: int, | ||||
|         wrap_event_fd: int, | ||||
|         key: str | None = None, | ||||
|         max_bytes: int = 10 * 1024, | ||||
|         start_ptr: int = 0, | ||||
|     ) -> RingBuffSender: | ||||
|         if not key: | ||||
|             key: str = ''.join(random.choice(string.ascii_lowercase) for i in range(32)) | ||||
| 
 | ||||
|         shm = SharedMemory( | ||||
|             name=key, | ||||
|             size=max_bytes, | ||||
|             create=True | ||||
|         ) | ||||
|         async with RingBuffSender( | ||||
|             shm, EventFD(fd=write_event_fd, omode='w'), EventFD(fd=wrap_event_fd), start_ptr=start_ptr | ||||
|         ) as s: | ||||
|             yield s | ||||
| 
 | ||||
|     @acm | ||||
|     async def open_ringbuffer_receiver( | ||||
|         write_event_fd: int, | ||||
|         wrap_event_fd: int, | ||||
|         key: str, | ||||
|         max_bytes: int = 10 * 1024, | ||||
|         start_ptr: int = 0, | ||||
|     ) -> RingBuffSender: | ||||
|         shm = SharedMemory( | ||||
|             name=key, | ||||
|             size=max_bytes, | ||||
|             create=False | ||||
|         ) | ||||
|         async with RingBuffReceiver( | ||||
|             shm, EventFD(fd=write_event_fd), EventFD(fd=wrap_event_fd, omode='w'), start_ptr=start_ptr | ||||
|         ) as r: | ||||
|             yield r | ||||
|  |  | |||
|  | @ -399,7 +399,8 @@ async def new_proc( | |||
|     *, | ||||
| 
 | ||||
|     infect_asyncio: bool = False, | ||||
|     task_status: TaskStatus[Portal] = trio.TASK_STATUS_IGNORED | ||||
|     task_status: TaskStatus[Portal] = trio.TASK_STATUS_IGNORED, | ||||
|     proc_kwargs: dict[str, any] = {} | ||||
| 
 | ||||
| ) -> None: | ||||
| 
 | ||||
|  | @ -419,6 +420,7 @@ async def new_proc( | |||
|         _runtime_vars,  # run time vars | ||||
|         infect_asyncio=infect_asyncio, | ||||
|         task_status=task_status, | ||||
|         proc_kwargs=proc_kwargs | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -434,7 +436,8 @@ async def trio_proc( | |||
|     _runtime_vars: dict[str, Any],  # serialized and sent to _child | ||||
|     *, | ||||
|     infect_asyncio: bool = False, | ||||
|     task_status: TaskStatus[Portal] = trio.TASK_STATUS_IGNORED | ||||
|     task_status: TaskStatus[Portal] = trio.TASK_STATUS_IGNORED, | ||||
|     proc_kwargs: dict[str, any] = {} | ||||
| 
 | ||||
| ) -> None: | ||||
|     ''' | ||||
|  | @ -475,7 +478,7 @@ async def trio_proc( | |||
|     proc: trio.Process|None = None | ||||
|     try: | ||||
|         try: | ||||
|             proc: trio.Process = await trio.lowlevel.open_process(spawn_cmd) | ||||
|             proc: trio.Process = await trio.lowlevel.open_process(spawn_cmd, **proc_kwargs) | ||||
|             log.runtime( | ||||
|                 'Started new child\n' | ||||
|                 f'|_{proc}\n' | ||||
|  | @ -640,7 +643,8 @@ async def mp_proc( | |||
|     _runtime_vars: dict[str, Any],  # serialized and sent to _child | ||||
|     *, | ||||
|     infect_asyncio: bool = False, | ||||
|     task_status: TaskStatus[Portal] = trio.TASK_STATUS_IGNORED | ||||
|     task_status: TaskStatus[Portal] = trio.TASK_STATUS_IGNORED, | ||||
|     proc_kwargs: dict[str, any] = {} | ||||
| 
 | ||||
| ) -> None: | ||||
| 
 | ||||
|  |  | |||
|  | @ -141,6 +141,7 @@ class ActorNursery: | |||
|         # a `._ria_nursery` since the dependent APIs have been | ||||
|         # removed! | ||||
|         nursery: trio.Nursery|None = None, | ||||
|         proc_kwargs: dict[str, any] = {} | ||||
| 
 | ||||
|     ) -> Portal: | ||||
|         ''' | ||||
|  | @ -204,6 +205,7 @@ class ActorNursery: | |||
|                 parent_addr, | ||||
|                 _rtv,  # run time vars | ||||
|                 infect_asyncio=infect_asyncio, | ||||
|                 proc_kwargs=proc_kwargs | ||||
|             ) | ||||
|         ) | ||||
| 
 | ||||
|  | @ -227,6 +229,7 @@ class ActorNursery: | |||
|         enable_modules: list[str] | None = None, | ||||
|         loglevel: str | None = None,  # set log level per subactor | ||||
|         infect_asyncio: bool = False, | ||||
|         proc_kwargs: dict[str, any] = {}, | ||||
| 
 | ||||
|         **kwargs,  # explicit args to ``fn`` | ||||
| 
 | ||||
|  | @ -257,6 +260,7 @@ class ActorNursery: | |||
|             # use the run_in_actor nursery | ||||
|             nursery=self._ria_nursery, | ||||
|             infect_asyncio=infect_asyncio, | ||||
|             proc_kwargs=proc_kwargs | ||||
|         ) | ||||
| 
 | ||||
|         # XXX: don't allow stream funcs | ||||
|  |  | |||
							
								
								
									
										30
									
								
								uv.lock
								
								
								
								
							
							
						
						
									
										30
									
								
								uv.lock
								
								
								
								
							|  | @ -29,10 +29,38 @@ dependencies = [ | |||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, | ||||
|     { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, | ||||
| ] | ||||
|  | @ -351,6 +379,7 @@ name = "tractor" | |||
| version = "0.1.0a6.dev0" | ||||
| source = { editable = "." } | ||||
| dependencies = [ | ||||
|     { name = "cffi" }, | ||||
|     { name = "colorlog" }, | ||||
|     { name = "msgspec" }, | ||||
|     { name = "pdbp" }, | ||||
|  | @ -374,6 +403,7 @@ dev = [ | |||
| 
 | ||||
| [package.metadata] | ||||
| requires-dist = [ | ||||
|     { name = "cffi", specifier = ">=1.17.1" }, | ||||
|     { name = "colorlog", specifier = ">=6.8.2,<7" }, | ||||
|     { name = "msgspec", specifier = ">=0.19.0" }, | ||||
|     { name = "pdbp", specifier = ">=1.6,<2" }, | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue