guidellm 0.4.0a21__py3-none-any.whl → 0.4.0a169__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of guidellm might be problematic. Click here for more details.

Files changed (115) hide show
  1. guidellm/__init__.py +5 -2
  2. guidellm/__main__.py +452 -252
  3. guidellm/backends/__init__.py +33 -0
  4. guidellm/backends/backend.py +110 -0
  5. guidellm/backends/openai.py +355 -0
  6. guidellm/backends/response_handlers.py +455 -0
  7. guidellm/benchmark/__init__.py +53 -39
  8. guidellm/benchmark/benchmarker.py +150 -317
  9. guidellm/benchmark/entrypoints.py +467 -128
  10. guidellm/benchmark/output.py +519 -771
  11. guidellm/benchmark/profile.py +580 -280
  12. guidellm/benchmark/progress.py +568 -549
  13. guidellm/benchmark/scenarios/__init__.py +40 -0
  14. guidellm/benchmark/scenarios/chat.json +6 -0
  15. guidellm/benchmark/scenarios/rag.json +6 -0
  16. guidellm/benchmark/schemas.py +2086 -0
  17. guidellm/data/__init__.py +28 -4
  18. guidellm/data/collators.py +16 -0
  19. guidellm/data/deserializers/__init__.py +53 -0
  20. guidellm/data/deserializers/deserializer.py +144 -0
  21. guidellm/data/deserializers/file.py +222 -0
  22. guidellm/data/deserializers/huggingface.py +94 -0
  23. guidellm/data/deserializers/memory.py +194 -0
  24. guidellm/data/deserializers/synthetic.py +348 -0
  25. guidellm/data/loaders.py +149 -0
  26. guidellm/data/preprocessors/__init__.py +25 -0
  27. guidellm/data/preprocessors/formatters.py +404 -0
  28. guidellm/data/preprocessors/mappers.py +198 -0
  29. guidellm/data/preprocessors/preprocessor.py +31 -0
  30. guidellm/data/processor.py +31 -0
  31. guidellm/data/schemas.py +13 -0
  32. guidellm/data/utils/__init__.py +6 -0
  33. guidellm/data/utils/dataset.py +94 -0
  34. guidellm/extras/__init__.py +4 -0
  35. guidellm/extras/audio.py +215 -0
  36. guidellm/extras/vision.py +242 -0
  37. guidellm/logger.py +2 -2
  38. guidellm/mock_server/__init__.py +8 -0
  39. guidellm/mock_server/config.py +84 -0
  40. guidellm/mock_server/handlers/__init__.py +17 -0
  41. guidellm/mock_server/handlers/chat_completions.py +280 -0
  42. guidellm/mock_server/handlers/completions.py +280 -0
  43. guidellm/mock_server/handlers/tokenizer.py +142 -0
  44. guidellm/mock_server/models.py +510 -0
  45. guidellm/mock_server/server.py +168 -0
  46. guidellm/mock_server/utils.py +302 -0
  47. guidellm/preprocess/dataset.py +23 -26
  48. guidellm/presentation/builder.py +2 -2
  49. guidellm/presentation/data_models.py +25 -21
  50. guidellm/presentation/injector.py +2 -3
  51. guidellm/scheduler/__init__.py +65 -26
  52. guidellm/scheduler/constraints.py +1035 -0
  53. guidellm/scheduler/environments.py +252 -0
  54. guidellm/scheduler/scheduler.py +140 -368
  55. guidellm/scheduler/schemas.py +272 -0
  56. guidellm/scheduler/strategies.py +519 -0
  57. guidellm/scheduler/worker.py +391 -420
  58. guidellm/scheduler/worker_group.py +707 -0
  59. guidellm/schemas/__init__.py +31 -0
  60. guidellm/schemas/info.py +159 -0
  61. guidellm/schemas/request.py +226 -0
  62. guidellm/schemas/response.py +119 -0
  63. guidellm/schemas/stats.py +228 -0
  64. guidellm/{config.py → settings.py} +32 -21
  65. guidellm/utils/__init__.py +95 -8
  66. guidellm/utils/auto_importer.py +98 -0
  67. guidellm/utils/cli.py +71 -2
  68. guidellm/utils/console.py +183 -0
  69. guidellm/utils/encoding.py +778 -0
  70. guidellm/utils/functions.py +134 -0
  71. guidellm/utils/hf_datasets.py +1 -2
  72. guidellm/utils/hf_transformers.py +4 -4
  73. guidellm/utils/imports.py +9 -0
  74. guidellm/utils/messaging.py +1118 -0
  75. guidellm/utils/mixins.py +115 -0
  76. guidellm/utils/pydantic_utils.py +411 -0
  77. guidellm/utils/random.py +3 -4
  78. guidellm/utils/registry.py +220 -0
  79. guidellm/utils/singleton.py +133 -0
  80. guidellm/{objects → utils}/statistics.py +341 -247
  81. guidellm/utils/synchronous.py +159 -0
  82. guidellm/utils/text.py +163 -50
  83. guidellm/utils/typing.py +41 -0
  84. guidellm/version.py +1 -1
  85. {guidellm-0.4.0a21.dist-info → guidellm-0.4.0a169.dist-info}/METADATA +33 -10
  86. guidellm-0.4.0a169.dist-info/RECORD +95 -0
  87. guidellm/backend/__init__.py +0 -23
  88. guidellm/backend/backend.py +0 -259
  89. guidellm/backend/openai.py +0 -705
  90. guidellm/backend/response.py +0 -136
  91. guidellm/benchmark/aggregator.py +0 -760
  92. guidellm/benchmark/benchmark.py +0 -837
  93. guidellm/benchmark/scenario.py +0 -104
  94. guidellm/data/prideandprejudice.txt.gz +0 -0
  95. guidellm/dataset/__init__.py +0 -22
  96. guidellm/dataset/creator.py +0 -213
  97. guidellm/dataset/entrypoints.py +0 -42
  98. guidellm/dataset/file.py +0 -92
  99. guidellm/dataset/hf_datasets.py +0 -62
  100. guidellm/dataset/in_memory.py +0 -132
  101. guidellm/dataset/synthetic.py +0 -287
  102. guidellm/objects/__init__.py +0 -18
  103. guidellm/objects/pydantic.py +0 -89
  104. guidellm/request/__init__.py +0 -18
  105. guidellm/request/loader.py +0 -284
  106. guidellm/request/request.py +0 -79
  107. guidellm/request/types.py +0 -10
  108. guidellm/scheduler/queues.py +0 -25
  109. guidellm/scheduler/result.py +0 -155
  110. guidellm/scheduler/strategy.py +0 -495
  111. guidellm-0.4.0a21.dist-info/RECORD +0 -62
  112. {guidellm-0.4.0a21.dist-info → guidellm-0.4.0a169.dist-info}/WHEEL +0 -0
  113. {guidellm-0.4.0a21.dist-info → guidellm-0.4.0a169.dist-info}/entry_points.txt +0 -0
  114. {guidellm-0.4.0a21.dist-info → guidellm-0.4.0a169.dist-info}/licenses/LICENSE +0 -0
  115. {guidellm-0.4.0a21.dist-info → guidellm-0.4.0a169.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1118 @@
1
+ """
2
+ Inter-process messaging abstractions for distributed scheduler coordination.
3
+
4
+ Provides high-level interfaces for asynchronous message passing between worker
5
+ processes using various transport mechanisms including queues and pipes. Supports
6
+ configurable encoding, serialization, error handling, and flow control with
7
+ buffering and stop event coordination for distributed scheduler operations.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ import contextlib
14
+ import multiprocessing
15
+ import queue
16
+ import threading
17
+ import time
18
+ from abc import ABC, abstractmethod
19
+ from collections.abc import Callable, Iterable
20
+ from multiprocessing.connection import Connection
21
+ from multiprocessing.context import BaseContext
22
+ from multiprocessing.managers import SyncManager
23
+ from multiprocessing.synchronize import Event as ProcessingEvent
24
+ from threading import Event as ThreadingEvent
25
+ from typing import Any, Generic, Protocol, TypeVar, cast
26
+
27
+ import culsans
28
+ from pydantic import BaseModel
29
+
30
+ from guidellm.utils.encoding import (
31
+ EncodingTypesAlias,
32
+ MessageEncoding,
33
+ SerializationTypesAlias,
34
+ )
35
+
36
+ __all__ = [
37
+ "InterProcessMessaging",
38
+ "InterProcessMessagingManagerQueue",
39
+ "InterProcessMessagingPipe",
40
+ "InterProcessMessagingQueue",
41
+ "MessagingStopCallback",
42
+ "ReceiveMessageT",
43
+ "SendMessageT",
44
+ ]
45
+
46
+ SendMessageT = TypeVar("SendMessageT", bound=Any)
47
+ """Generic type variable for messages sent through the messaging system"""
48
+ ReceiveMessageT = TypeVar("ReceiveMessageT", bound=Any)
49
+ """Generic type variable for messages received through the messaging system"""
50
+
51
+ CheckStopCallableT = Callable[[bool, int], bool]
52
+
53
+
54
+ class MessagingStopCallback(Protocol):
55
+ """Protocol for evaluating stop conditions in messaging operations."""
56
+
57
+ def __call__(
58
+ self, messaging: InterProcessMessaging, pending: bool, queue_empty_count: int
59
+ ) -> bool:
60
+ """
61
+ Evaluate whether messaging operations should stop.
62
+
63
+ :param messaging: The messaging instance to evaluate
64
+ :param pending: Whether there are pending operations
65
+ :param queue_empty_count: The number of times in a row the queue has been empty
66
+ :return: True if operations should stop, False otherwise
67
+ """
68
+ ...
69
+
70
+
71
+ class InterProcessMessaging(Generic[SendMessageT, ReceiveMessageT], ABC):
72
+ """
73
+ Abstract base for inter-process messaging in distributed scheduler coordination.
74
+
75
+ Provides unified interface for asynchronous message passing between scheduler
76
+ components using configurable transport mechanisms, encoding schemes, and
77
+ flow control policies. Manages buffering, serialization, error handling,
78
+ and coordinated shutdown across worker processes for distributed operations.
79
+
80
+ Example:
81
+ ::
82
+ from guidellm.utils.messaging import InterProcessMessagingQueue
83
+
84
+ messaging = InterProcessMessagingQueue(
85
+ serialization="pickle",
86
+ max_pending_size=100
87
+ )
88
+
89
+ await messaging.start()
90
+ await messaging.put(request_data)
91
+ response = await messaging.get(timeout=5.0)
92
+ await messaging.stop()
93
+ """
94
+
95
+ STOP_REQUIRED_QUEUE_EMPTY_COUNT: int = 3
96
+
97
+ def __init__(
98
+ self,
99
+ mp_context: BaseContext | None = None,
100
+ serialization: SerializationTypesAlias = "dict",
101
+ encoding: EncodingTypesAlias | list[EncodingTypesAlias] = None,
102
+ max_pending_size: int | None = None,
103
+ max_buffer_send_size: int | None = None,
104
+ max_done_size: int | None = None,
105
+ max_buffer_receive_size: int | None = None,
106
+ poll_interval: float = 0.1,
107
+ worker_index: int | None = None,
108
+ ):
109
+ """
110
+ Initialize inter-process messaging coordinator.
111
+
112
+ :param serialization: Message serialization method for transport encoding
113
+ :param encoding: Optional encoding scheme for serialized message data
114
+ :param max_pending_size: Maximum items in send queue before blocking
115
+ :param max_buffer_send_size: Maximum items in buffer send queue
116
+ :param max_done_size: Maximum items in done queue before blocking
117
+ :param max_buffer_receive_size: Maximum items in buffer receive queue
118
+ :param poll_interval: Time interval for checking queue status and events
119
+ :param worker_index: Index identifying this worker in the process group
120
+ """
121
+ self.worker_index: int | None = worker_index
122
+ self.mp_context = mp_context or multiprocessing.get_context()
123
+ self.serialization = serialization
124
+ self.encoding = encoding
125
+ self.max_pending_size = max_pending_size
126
+ self.max_buffer_send_size = max_buffer_send_size
127
+ self.max_done_size = max_done_size
128
+ self.max_buffer_receive_size = max_buffer_receive_size
129
+ self.poll_interval = poll_interval
130
+
131
+ self.send_stopped_event: ThreadingEvent | ProcessingEvent | None = None
132
+ self.receive_stopped_event: ThreadingEvent | ProcessingEvent | None = None
133
+ self.shutdown_event: ThreadingEvent | None = None
134
+ self.buffer_send_queue: culsans.Queue[SendMessageT] | None = None
135
+ self.buffer_receive_queue: culsans.Queue[ReceiveMessageT] | None = None
136
+ self.send_task: asyncio.Task | None = None
137
+ self.receive_task: asyncio.Task | None = None
138
+ self.running = False
139
+
140
+ @abstractmethod
141
+ def create_worker_copy(
142
+ self, worker_index: int, **kwargs
143
+ ) -> InterProcessMessaging[ReceiveMessageT, SendMessageT]:
144
+ """
145
+ Create worker-specific copy for distributed process coordination.
146
+
147
+ :param worker_index: Index of the worker process for message routing
148
+ :return: Configured messaging instance for the specified worker
149
+ """
150
+ ...
151
+
152
+ @abstractmethod
153
+ def create_send_messages_threads(
154
+ self,
155
+ send_items: Iterable[Any] | None,
156
+ message_encoding: MessageEncoding,
157
+ check_stop: CheckStopCallableT,
158
+ ) -> list[tuple[Callable, tuple[Any, ...]]]:
159
+ """
160
+ Create send message processing threads for transport implementation.
161
+
162
+ :param send_items: Optional collection of items to send during processing
163
+ :param message_encoding: Message encoding configuration for serialization
164
+ :param check_stop: Callable for evaluating stop conditions during processing
165
+ :return: List of thread callables with their arguments for execution
166
+ """
167
+ ...
168
+
169
+ @abstractmethod
170
+ def create_receive_messages_threads(
171
+ self,
172
+ receive_callback: Callable[[Any], Any] | None,
173
+ message_encoding: MessageEncoding,
174
+ check_stop: CheckStopCallableT,
175
+ ) -> list[tuple[Callable, tuple[Any, ...]]]:
176
+ """
177
+ Create receive message processing threads for transport implementation.
178
+
179
+ :param receive_callback: Optional callback for processing received messages
180
+ :param message_encoding: Message encoding configuration for deserialization
181
+ :param check_stop: Callable for evaluating stop conditions during processing
182
+ :return: List of thread callables with their arguments for execution
183
+ """
184
+ ...
185
+
186
+ async def start(
187
+ self,
188
+ send_items: Iterable[Any] | None = None,
189
+ receive_callback: Callable[[Any], Any] | None = None,
190
+ send_stop_criteria: (
191
+ list[ThreadingEvent | ProcessingEvent | MessagingStopCallback] | None
192
+ ) = None,
193
+ send_stopped_event: ThreadingEvent | ProcessingEvent | None = None,
194
+ receive_stop_criteria: (
195
+ list[ThreadingEvent | ProcessingEvent | MessagingStopCallback] | None
196
+ ) = None,
197
+ receive_stopped_event: ThreadingEvent | ProcessingEvent | None = None,
198
+ pydantic_models: list[type[BaseModel]] | None = None,
199
+ ):
200
+ """
201
+ Start asynchronous message processing tasks with buffering.
202
+
203
+ :param send_items: Optional collection of items to send during processing
204
+ :param receive_callback: Optional callback for processing received messages
205
+ :param send_stop_criteria: Events and callables that trigger send task shutdown
206
+ :param send_stopped_event: Event set when send task has fully stopped
207
+ :param receive_stop_criteria: Events and callables that trigger receive shutdown
208
+ :param receive_stopped_event: Event set when receive task has fully stopped
209
+ :param pydantic_models: Optional list of Pydantic models for serialization
210
+ """
211
+ self.running = True
212
+ self.send_stopped_event = send_stopped_event or ThreadingEvent()
213
+ self.receive_stopped_event = receive_stopped_event or ThreadingEvent()
214
+ self.shutdown_event = ThreadingEvent()
215
+ self.buffer_send_queue = culsans.Queue[SendMessageT](
216
+ maxsize=self.max_buffer_send_size or 0
217
+ )
218
+ self.buffer_receive_queue = culsans.Queue[ReceiveMessageT](
219
+ maxsize=self.max_buffer_receive_size or 0
220
+ )
221
+
222
+ message_encoding: MessageEncoding = MessageEncoding(
223
+ serialization=self.serialization,
224
+ encoding=self.encoding,
225
+ pydantic_models=pydantic_models,
226
+ )
227
+ send_stop_criteria = send_stop_criteria or []
228
+ receive_stop_events = receive_stop_criteria or []
229
+
230
+ self.send_task = asyncio.create_task(
231
+ self.send_messages_coroutine(
232
+ send_items=send_items,
233
+ message_encoding=message_encoding,
234
+ send_stop_criteria=send_stop_criteria,
235
+ )
236
+ )
237
+ self.receive_task = asyncio.create_task(
238
+ self.receive_messages_coroutine(
239
+ receive_callback=receive_callback,
240
+ message_encoding=message_encoding,
241
+ receive_stop_criteria=receive_stop_events,
242
+ )
243
+ )
244
+
245
+ async def stop(self):
246
+ """
247
+ Stop message processing tasks and clean up resources.
248
+ """
249
+ if self.shutdown_event is not None:
250
+ self.shutdown_event.set()
251
+ else:
252
+ raise RuntimeError(
253
+ "shutdown_event is not set; was start() not called or "
254
+ "is this a redundant stop() call?"
255
+ )
256
+ tasks = [self.send_task, self.receive_task]
257
+ tasks_to_run: list[asyncio.Task[Any]] = [
258
+ task for task in tasks if task is not None
259
+ ]
260
+ if len(tasks_to_run) > 0:
261
+ with contextlib.suppress(asyncio.CancelledError):
262
+ await asyncio.gather(*tasks_to_run, return_exceptions=True)
263
+ self.send_task = None
264
+ self.receive_task = None
265
+ if self.worker_index is None:
266
+ if self.buffer_send_queue is not None:
267
+ self.buffer_send_queue.clear()
268
+ await self.buffer_send_queue.aclose()
269
+ if self.buffer_receive_queue is not None:
270
+ self.buffer_receive_queue.clear()
271
+ await self.buffer_receive_queue.aclose()
272
+ self.buffer_send_queue = None
273
+ self.buffer_receive_queue = None
274
+ self.send_stopped_event = None
275
+ self.receive_stopped_event = None
276
+ self.shutdown_event = None
277
+ self.running = False
278
+
279
+ async def send_messages_coroutine(
280
+ self,
281
+ send_items: Iterable[Any] | None,
282
+ message_encoding: MessageEncoding,
283
+ send_stop_criteria: (
284
+ list[ThreadingEvent | ProcessingEvent | MessagingStopCallback] | None
285
+ ),
286
+ ):
287
+ """
288
+ Execute send message processing with encoding and stop condition handling.
289
+
290
+ :param send_items: Optional collection of items to send during processing
291
+ :param message_encoding: Message encoding configuration for serialization
292
+ :param send_stop_criteria: Events and callables that trigger send task shutdown
293
+ """
294
+ canceled_event = ThreadingEvent()
295
+
296
+ try:
297
+ await asyncio.gather(
298
+ *[
299
+ asyncio.to_thread(thread, *args)
300
+ for (thread, args) in self.create_send_messages_threads(
301
+ send_items=send_items,
302
+ message_encoding=message_encoding,
303
+ check_stop=self._create_check_stop_callable(
304
+ send_stop_criteria, canceled_event
305
+ ),
306
+ )
307
+ ]
308
+ )
309
+ except asyncio.CancelledError:
310
+ canceled_event.set()
311
+ raise
312
+ finally:
313
+ if self.send_stopped_event is not None:
314
+ self.send_stopped_event.set()
315
+
316
+ async def receive_messages_coroutine(
317
+ self,
318
+ receive_callback: Callable[[Any], Any] | None,
319
+ message_encoding: MessageEncoding,
320
+ receive_stop_criteria: (
321
+ list[ThreadingEvent | ProcessingEvent | MessagingStopCallback] | None
322
+ ),
323
+ ):
324
+ """
325
+ Execute receive message processing with decoding and callback handling.
326
+
327
+ :param receive_callback: Optional callback for processing received messages
328
+ :param message_encoding: Message encoding configuration for deserialization
329
+ :param receive_stop_criteria: Events and callables that trigger receive shutdown
330
+ """
331
+ canceled_event = ThreadingEvent()
332
+
333
+ try:
334
+ await asyncio.gather(
335
+ *[
336
+ asyncio.to_thread(thread, *args)
337
+ for thread, args in self.create_receive_messages_threads(
338
+ receive_callback=receive_callback,
339
+ message_encoding=message_encoding,
340
+ check_stop=self._create_check_stop_callable(
341
+ receive_stop_criteria, canceled_event
342
+ ),
343
+ )
344
+ ]
345
+ )
346
+ except asyncio.CancelledError:
347
+ canceled_event.set()
348
+ raise
349
+ finally:
350
+ if self.receive_stopped_event is not None:
351
+ self.receive_stopped_event.set()
352
+
353
+ async def get(self, timeout: float | None = None) -> ReceiveMessageT:
354
+ """
355
+ Retrieve a message from receive buffer with optional timeout.
356
+
357
+ :param timeout: Maximum time to wait for a message
358
+ :return: Decoded message from the receive buffer
359
+ """
360
+ if self.buffer_receive_queue is None:
361
+ raise RuntimeError(
362
+ "buffer receive queue is None; check start()/stop() calls"
363
+ )
364
+ return await asyncio.wait_for(
365
+ self.buffer_receive_queue.async_get(), timeout=timeout
366
+ )
367
+
368
+ def get_sync(self, timeout: float | None = None) -> ReceiveMessageT:
369
+ """
370
+ Retrieve message from receive buffer synchronously with optional timeout.
371
+
372
+ :param timeout: Maximum time to wait for a message, if <=0 uses get_nowait
373
+ :return: Decoded message from the receive buffer
374
+ """
375
+ if self.buffer_receive_queue is None:
376
+ raise RuntimeError(
377
+ "buffer receive queue is None; check start()/stop() calls"
378
+ )
379
+ if timeout is not None and timeout <= 0:
380
+ return self.buffer_receive_queue.get_nowait()
381
+ else:
382
+ return self.buffer_receive_queue.sync_get(timeout=timeout)
383
+
384
+ async def put(self, item: SendMessageT, timeout: float | None = None):
385
+ """
386
+ Add message to send buffer with optional timeout.
387
+
388
+ :param item: Message item to add to the send buffer
389
+ :param timeout: Maximum time to wait for buffer space
390
+ """
391
+ if self.buffer_send_queue is None:
392
+ raise RuntimeError(
393
+ "buffer receive queue is None; check start()/stop() calls"
394
+ )
395
+ await asyncio.wait_for(self.buffer_send_queue.async_put(item), timeout=timeout)
396
+
397
+ def put_sync(self, item: SendMessageT, timeout: float | None = None):
398
+ """
399
+ Add message to send buffer synchronously with optional timeout.
400
+
401
+ :param item: Message item to add to the send buffer
402
+ :param timeout: Maximum time to wait for buffer space, if <=0 uses put_nowait
403
+ """
404
+ if self.buffer_send_queue is None:
405
+ raise RuntimeError(
406
+ "buffer receive queue is None; check start()/stop() calls"
407
+ )
408
+ if timeout is not None and timeout <= 0:
409
+ self.buffer_send_queue.put_nowait(item)
410
+ else:
411
+ self.buffer_send_queue.sync_put(item, timeout=timeout)
412
+
413
+ def _create_check_stop_callable(
414
+ self,
415
+ stop_criteria: (
416
+ list[ThreadingEvent | ProcessingEvent | MessagingStopCallback] | None
417
+ ),
418
+ canceled_event: ThreadingEvent,
419
+ ):
420
+ stop_events = tuple(
421
+ item
422
+ for item in stop_criteria or []
423
+ if isinstance(item, ThreadingEvent | ProcessingEvent)
424
+ )
425
+ stop_callbacks = tuple(item for item in stop_criteria or [] if callable(item))
426
+
427
+ def check_stop(pending: bool, queue_empty_count: int) -> bool:
428
+ if canceled_event.is_set():
429
+ return True
430
+
431
+ if stop_callbacks and any(
432
+ cb(self, pending, queue_empty_count) for cb in stop_callbacks
433
+ ):
434
+ return True
435
+
436
+ if self.shutdown_event is None:
437
+ return True
438
+
439
+ return (
440
+ not pending
441
+ and queue_empty_count >= self.STOP_REQUIRED_QUEUE_EMPTY_COUNT
442
+ and (
443
+ self.shutdown_event.is_set()
444
+ or any(event.is_set() for event in stop_events)
445
+ )
446
+ )
447
+
448
+ return check_stop
449
+
450
+
451
+ class InterProcessMessagingQueue(InterProcessMessaging[SendMessageT, ReceiveMessageT]):
452
+ """
453
+ Queue-based inter-process messaging for distributed scheduler coordination.
454
+
455
+ Provides message passing using multiprocessing.Queue objects for communication
456
+ between scheduler workers and main process. Handles message encoding, buffering,
457
+ flow control, and coordinated shutdown with configurable queue behavior and
458
+ error handling policies for distributed operations.
459
+
460
+ Example:
461
+ ::
462
+ from guidellm.utils.messaging import InterProcessMessagingQueue
463
+
464
+ messaging = InterProcessMessagingQueue(
465
+ serialization="pickle",
466
+ max_pending_size=100
467
+ )
468
+
469
+ # Create worker copy for distributed processing
470
+ worker_messaging = messaging.create_worker_copy(worker_index=0)
471
+ """
472
+
473
+ pending_queue: multiprocessing.Queue | queue.Queue[Any] | None
474
+ done_queue: multiprocessing.Queue | queue.Queue[Any] | None
475
+
476
+ def __init__(
477
+ self,
478
+ mp_context: BaseContext | None = None,
479
+ serialization: SerializationTypesAlias = "dict",
480
+ encoding: EncodingTypesAlias | list[EncodingTypesAlias] = None,
481
+ max_pending_size: int | None = None,
482
+ max_buffer_send_size: int | None = None,
483
+ max_done_size: int | None = None,
484
+ max_buffer_receive_size: int | None = None,
485
+ poll_interval: float = 0.1,
486
+ worker_index: int | None = None,
487
+ pending_queue: multiprocessing.Queue | queue.Queue[Any] | None = None,
488
+ done_queue: multiprocessing.Queue | queue.Queue[Any] | None = None,
489
+ ):
490
+ """
491
+ Initialize queue-based messaging for inter-process communication.
492
+
493
+ :param serialization: Message serialization method for transport encoding
494
+ :param encoding: Optional encoding scheme for serialized message data
495
+ :param max_pending_size: Maximum items in send queue before blocking
496
+ :param max_buffer_send_size: Maximum items in buffer send queue
497
+ :param max_done_size: Maximum items in receive queue before blocking
498
+ :param max_buffer_receive_size: Maximum items in buffer receive queue
499
+ :param poll_interval: Time interval for checking queue status and events
500
+ :param worker_index: Index identifying this worker in the process group
501
+ :param pending_queue: Multiprocessing queue for sending messages
502
+ :param done_queue: Multiprocessing queue for receiving completed messages
503
+ :param context: Multiprocessing context for creating queues
504
+ """
505
+ super().__init__(
506
+ mp_context=mp_context,
507
+ serialization=serialization,
508
+ encoding=encoding,
509
+ max_pending_size=max_pending_size,
510
+ max_buffer_send_size=max_buffer_send_size,
511
+ max_done_size=max_done_size,
512
+ max_buffer_receive_size=max_buffer_receive_size,
513
+ poll_interval=poll_interval,
514
+ worker_index=worker_index,
515
+ )
516
+ self.pending_queue = pending_queue or self.mp_context.Queue(
517
+ maxsize=max_pending_size or 0
518
+ )
519
+ self.done_queue = done_queue or self.mp_context.Queue(
520
+ maxsize=max_done_size or 0
521
+ )
522
+
523
+ def create_worker_copy(
524
+ self, worker_index: int, **kwargs
525
+ ) -> InterProcessMessagingQueue[ReceiveMessageT, SendMessageT]:
526
+ """
527
+ Create worker-specific copy for distributed queue-based coordination.
528
+
529
+ :param worker_index: Index of the worker process for message routing
530
+ :return: Configured queue messaging instance for the specified worker
531
+ """
532
+ copy_args = {
533
+ "mp_context": self.mp_context,
534
+ "serialization": self.serialization,
535
+ "encoding": self.encoding,
536
+ "max_pending_size": self.max_pending_size,
537
+ "max_buffer_send_size": self.max_buffer_send_size,
538
+ "max_done_size": self.max_done_size,
539
+ "max_buffer_receive_size": self.max_buffer_receive_size,
540
+ "poll_interval": self.poll_interval,
541
+ "worker_index": worker_index,
542
+ "pending_queue": self.pending_queue,
543
+ "done_queue": self.done_queue,
544
+ }
545
+ final_args = {**copy_args, **kwargs}
546
+
547
+ return InterProcessMessagingQueue[ReceiveMessageT, SendMessageT](**final_args)
548
+
549
+ async def stop(self):
550
+ """
551
+ Stop the messaging system and wait for all tasks to complete.
552
+ """
553
+ await super().stop()
554
+ if self.worker_index is None:
555
+ # only main process should close the queues
556
+ if self.pending_queue is None:
557
+ raise RuntimeError("pending_queue is None; was stop() already called?")
558
+ with contextlib.suppress(queue.Empty):
559
+ while True:
560
+ self.pending_queue.get_nowait()
561
+ if hasattr(self.pending_queue, "close"):
562
+ self.pending_queue.close()
563
+
564
+ if self.done_queue is None:
565
+ raise RuntimeError("done_queue is None; was stop() already called?")
566
+ with contextlib.suppress(queue.Empty):
567
+ while True:
568
+ self.done_queue.get_nowait()
569
+ if hasattr(self.done_queue, "close"):
570
+ self.done_queue.close()
571
+
572
+ self.pending_queue = None
573
+ self.done_queue = None
574
+
575
+ def create_send_messages_threads(
576
+ self,
577
+ send_items: Iterable[Any] | None,
578
+ message_encoding: MessageEncoding,
579
+ check_stop: CheckStopCallableT,
580
+ ) -> list[tuple[Callable, tuple[Any, ...]]]:
581
+ """
582
+ Create send message processing threads for queue-based transport.
583
+
584
+ :param send_items: Optional collection of items to send during processing
585
+ :param message_encoding: Message encoding configuration for serialization
586
+ :param check_stop: Callable for evaluating stop conditions during processing
587
+ :return: List of thread callables with their arguments for execution
588
+ """
589
+ return [
590
+ (
591
+ self._send_messages_task_thread,
592
+ (send_items, message_encoding, check_stop),
593
+ )
594
+ ]
595
+
596
+ def create_receive_messages_threads(
597
+ self,
598
+ receive_callback: Callable[[Any], Any] | None,
599
+ message_encoding: MessageEncoding,
600
+ check_stop: CheckStopCallableT,
601
+ ) -> list[tuple[Callable, tuple[Any, ...]]]:
602
+ """
603
+ Create receive message processing threads for queue-based transport.
604
+
605
+ :param receive_callback: Optional callback for processing received messages
606
+ :param message_encoding: Message encoding configuration for deserialization
607
+ :param check_stop: Callable for evaluating stop conditions during processing
608
+ :return: List of thread callables with their arguments for execution
609
+ """
610
+ return [
611
+ (
612
+ self._receive_messages_task_thread,
613
+ (receive_callback, message_encoding, check_stop),
614
+ )
615
+ ]
616
+
617
+ def _send_messages_task_thread( # noqa: C901, PLR0912
618
+ self,
619
+ send_items: Iterable[Any] | None,
620
+ message_encoding: MessageEncoding,
621
+ check_stop: CheckStopCallableT,
622
+ ):
623
+ send_items_iter = iter(send_items) if send_items is not None else None
624
+ pending_item = None
625
+ queue_empty_count = 0
626
+
627
+ while not check_stop(pending_item is not None, queue_empty_count):
628
+ if pending_item is None:
629
+ try:
630
+ if send_items_iter is not None:
631
+ item = next(send_items_iter)
632
+ else:
633
+ if self.buffer_send_queue is None:
634
+ raise RuntimeError(
635
+ "buffer_send_queue is None; was stop() already called?"
636
+ )
637
+ item = self.buffer_send_queue.sync_get(
638
+ timeout=self.poll_interval
639
+ )
640
+ pending_item = message_encoding.encode(item)
641
+ queue_empty_count = 0
642
+ except (culsans.QueueEmpty, queue.Empty, StopIteration):
643
+ queue_empty_count += 1
644
+
645
+ if pending_item is not None:
646
+ try:
647
+ if self.worker_index is None:
648
+ # Main publisher
649
+ if self.pending_queue is None:
650
+ raise RuntimeError(
651
+ "pending_queue is None; was stop() already called?"
652
+ )
653
+ self.pending_queue.put(pending_item, timeout=self.poll_interval)
654
+ else:
655
+ # Worker
656
+ if self.done_queue is None:
657
+ raise RuntimeError(
658
+ "done_queue is None; was stop() already called?"
659
+ )
660
+ self.done_queue.put(pending_item, timeout=self.poll_interval)
661
+ if send_items_iter is None:
662
+ if self.buffer_send_queue is None:
663
+ raise RuntimeError(
664
+ "buffer_send_queue is None; was stop() already called?"
665
+ )
666
+ self.buffer_send_queue.task_done()
667
+ pending_item = None
668
+ except (culsans.QueueFull, queue.Full):
669
+ pass
670
+
671
+ time.sleep(0) # Yield to other threads
672
+
673
+ def _receive_messages_task_thread( # noqa: C901
674
+ self,
675
+ receive_callback: Callable[[Any], Any] | None,
676
+ message_encoding: MessageEncoding,
677
+ check_stop: CheckStopCallableT,
678
+ ):
679
+ pending_item = None
680
+ received_item = None
681
+ queue_empty_count = 0
682
+
683
+ while not check_stop(pending_item is not None, queue_empty_count):
684
+ if pending_item is None:
685
+ try:
686
+ if self.worker_index is None:
687
+ # Main publisher
688
+ if self.done_queue is None:
689
+ raise RuntimeError(
690
+ "done_queue is None; check start()/stop() calls"
691
+ )
692
+ item = self.done_queue.get(timeout=self.poll_interval)
693
+ else:
694
+ # Worker
695
+ if self.pending_queue is None:
696
+ raise RuntimeError(
697
+ "pending_queue is None; check start()/stop() calls"
698
+ )
699
+ item = self.pending_queue.get(timeout=self.poll_interval)
700
+ pending_item = message_encoding.decode(item)
701
+ queue_empty_count = 0
702
+ except (culsans.QueueEmpty, queue.Empty):
703
+ queue_empty_count += 1
704
+
705
+ if pending_item is not None or received_item is not None:
706
+ try:
707
+ if received_item is None:
708
+ received_item = (
709
+ pending_item
710
+ if not receive_callback
711
+ else receive_callback(pending_item)
712
+ )
713
+
714
+ if self.buffer_receive_queue is None:
715
+ raise RuntimeError(
716
+ "buffer_receive_queue is None; check start()/stop() calls"
717
+ )
718
+ self.buffer_receive_queue.sync_put(
719
+ cast("ReceiveMessageT", received_item)
720
+ )
721
+ pending_item = None
722
+ received_item = None
723
+ except (culsans.QueueFull, queue.Full):
724
+ pass
725
+
726
+ time.sleep(0) # Yield to other threads
727
+
728
+
729
+ class InterProcessMessagingManagerQueue(
730
+ InterProcessMessagingQueue[SendMessageT, ReceiveMessageT]
731
+ ):
732
+ """
733
+ Manager-based queue messaging for inter-process scheduler coordination.
734
+
735
+ Extends queue-based messaging with multiprocessing.Manager support for
736
+ shared state coordination across worker processes. Provides managed queues
737
+ for reliable message passing in distributed scheduler environments with
738
+ enhanced process synchronization and resource management capabilities.
739
+
740
+ Example:
741
+ ::
742
+ import multiprocessing
743
+ from guidellm.utils.messaging import InterProcessMessagingManagerQueue
744
+
745
+ manager = multiprocessing.Manager()
746
+ messaging = InterProcessMessagingManagerQueue(
747
+ manager=manager,
748
+ serialization="pickle"
749
+ )
750
+ """
751
+
752
+ def __init__(
753
+ self,
754
+ manager: SyncManager,
755
+ mp_context: BaseContext | None = None,
756
+ serialization: SerializationTypesAlias = "dict",
757
+ encoding: EncodingTypesAlias | list[EncodingTypesAlias] = None,
758
+ max_pending_size: int | None = None,
759
+ max_buffer_send_size: int | None = None,
760
+ max_done_size: int | None = None,
761
+ max_buffer_receive_size: int | None = None,
762
+ poll_interval: float = 0.1,
763
+ worker_index: int | None = None,
764
+ pending_queue: multiprocessing.Queue | None = None,
765
+ done_queue: multiprocessing.Queue | None = None,
766
+ ):
767
+ """
768
+ Initialize manager-based queue messaging for inter-process communication.
769
+
770
+ :param manager: Multiprocessing manager for shared queue creation
771
+ :param serialization: Message serialization method for transport encoding
772
+ :param encoding: Optional encoding scheme for serialized message data
773
+ :param max_pending_size: Maximum items in send queue before blocking
774
+ :param max_buffer_send_size: Maximum items in buffer send queue
775
+ :param max_done_size: Maximum items in receive queue before blocking
776
+ :param max_buffer_receive_size: Maximum items in buffer receive queue
777
+ :param poll_interval: Time interval for checking queue status and events
778
+ :param worker_index: Index identifying this worker in the process group
779
+ :param pending_queue: Managed multiprocessing queue for sending messages
780
+ :param done_queue: Managed multiprocessing queue for receiving completed
781
+ messages
782
+ """
783
+ super().__init__(
784
+ mp_context=mp_context,
785
+ serialization=serialization,
786
+ encoding=encoding,
787
+ max_pending_size=max_pending_size,
788
+ max_buffer_send_size=max_buffer_send_size,
789
+ max_done_size=max_done_size,
790
+ max_buffer_receive_size=max_buffer_receive_size,
791
+ poll_interval=poll_interval,
792
+ worker_index=worker_index,
793
+ pending_queue=pending_queue or manager.Queue(maxsize=max_pending_size or 0),
794
+ done_queue=done_queue or manager.Queue(maxsize=max_done_size or 0),
795
+ )
796
+
797
+ def create_worker_copy(
798
+ self, worker_index: int, **kwargs
799
+ ) -> InterProcessMessagingManagerQueue[ReceiveMessageT, SendMessageT]:
800
+ """
801
+ Create worker-specific copy for managed queue-based coordination.
802
+
803
+ :param worker_index: Index of the worker process for message routing
804
+ :return: Configured manager queue messaging instance for the specified worker
805
+ """
806
+ copy_args = {
807
+ "manager": None,
808
+ "mp_context": self.mp_context,
809
+ "serialization": self.serialization,
810
+ "encoding": self.encoding,
811
+ "max_pending_size": self.max_pending_size,
812
+ "max_buffer_send_size": self.max_buffer_send_size,
813
+ "max_done_size": self.max_done_size,
814
+ "max_buffer_receive_size": self.max_buffer_receive_size,
815
+ "poll_interval": self.poll_interval,
816
+ "worker_index": worker_index,
817
+ "pending_queue": self.pending_queue,
818
+ "done_queue": self.done_queue,
819
+ }
820
+ final_args = {**copy_args, **kwargs}
821
+
822
+ return InterProcessMessagingManagerQueue(**final_args)
823
+
824
+ async def stop(self):
825
+ """
826
+ Stop the messaging system and wait for all tasks to complete.
827
+ """
828
+ await InterProcessMessaging.stop(self)
829
+ self.pending_queue = None
830
+ self.done_queue = None
831
+
832
+
833
+ class InterProcessMessagingPipe(InterProcessMessaging[SendMessageT, ReceiveMessageT]):
834
+ """
835
+ Pipe-based inter-process messaging for distributed scheduler coordination.
836
+
837
+ Provides message passing using multiprocessing.Pipe objects for direct
838
+ communication between scheduler workers and main process. Offers lower
839
+ latency than queue-based messaging with duplex communication channels
840
+ for high-performance distributed operations.
841
+
842
+ Example:
843
+ ::
844
+ from guidellm.utils.messaging import InterProcessMessagingPipe
845
+
846
+ messaging = InterProcessMessagingPipe(
847
+ num_workers=4,
848
+ serialization="pickle",
849
+ poll_interval=0.05
850
+ )
851
+
852
+ # Create worker copy for specific worker process
853
+ worker_messaging = messaging.create_worker_copy(worker_index=0)
854
+ """
855
+
856
+ def __init__(
857
+ self,
858
+ num_workers: int,
859
+ mp_context: BaseContext | None = None,
860
+ serialization: SerializationTypesAlias = "dict",
861
+ encoding: EncodingTypesAlias | list[EncodingTypesAlias] = None,
862
+ max_pending_size: int | None = None,
863
+ max_buffer_send_size: int | None = None,
864
+ max_done_size: int | None = None,
865
+ max_buffer_receive_size: int | None = None,
866
+ poll_interval: float = 0.1,
867
+ worker_index: int | None = None,
868
+ pipe: tuple[Connection, Connection] | None = None,
869
+ ):
870
+ """
871
+ Initialize pipe-based messaging for inter-process communication.
872
+
873
+ :param num_workers: Number of worker processes requiring pipe connections
874
+ :param serialization: Message serialization method for transport encoding
875
+ :param encoding: Optional encoding scheme for serialized message data
876
+ :param max_pending_size: Maximum items in send queue before blocking
877
+ :param max_buffer_send_size: Maximum items in buffer send queue
878
+ :param max_done_size: Maximum items in receive queue before blocking
879
+ :param max_buffer_receive_size: Maximum items in buffer receive queue
880
+ :param poll_interval: Time interval for checking queue status and events
881
+ :param worker_index: Index identifying this worker in the process group
882
+ :param pipe: Existing pipe connection for worker-specific instances
883
+ """
884
+ super().__init__(
885
+ mp_context=mp_context,
886
+ serialization=serialization,
887
+ encoding=encoding,
888
+ max_pending_size=max_pending_size,
889
+ max_buffer_send_size=max_buffer_send_size,
890
+ max_done_size=max_done_size,
891
+ max_buffer_receive_size=max_buffer_receive_size,
892
+ poll_interval=poll_interval,
893
+ worker_index=worker_index,
894
+ )
895
+ self.num_workers = num_workers
896
+
897
+ self.pipes: list[tuple[Connection, Connection]]
898
+ if pipe is None:
899
+ self.pipes = [self.mp_context.Pipe(duplex=True) for _ in range(num_workers)]
900
+ else:
901
+ self.pipes = [pipe]
902
+
903
+ def create_worker_copy(
904
+ self, worker_index: int, **kwargs
905
+ ) -> InterProcessMessagingPipe[ReceiveMessageT, SendMessageT]:
906
+ """
907
+ Create worker-specific copy for pipe-based coordination.
908
+
909
+ :param worker_index: Index of the worker process for pipe routing
910
+ :return: Configured pipe messaging instance for the specified worker
911
+ """
912
+ copy_args = {
913
+ "num_workers": self.num_workers,
914
+ "mp_context": self.mp_context,
915
+ "serialization": self.serialization,
916
+ "encoding": self.encoding,
917
+ "max_pending_size": self.max_pending_size,
918
+ "max_buffer_send_size": self.max_buffer_send_size,
919
+ "max_done_size": self.max_done_size,
920
+ "max_buffer_receive_size": self.max_buffer_receive_size,
921
+ "poll_interval": self.poll_interval,
922
+ "worker_index": worker_index,
923
+ "pipe": self.pipes[worker_index],
924
+ }
925
+
926
+ final_args = {**copy_args, **kwargs}
927
+
928
+ return InterProcessMessagingPipe(**final_args)
929
+
930
+ async def stop(self):
931
+ """
932
+ Stop the messaging system and wait for all tasks to complete.
933
+ """
934
+ await super().stop()
935
+ if self.worker_index is None:
936
+ # Only main process should close the pipes
937
+ for main_con, worker_con in self.pipes:
938
+ main_con.close()
939
+ worker_con.close()
940
+
941
+ def create_send_messages_threads(
942
+ self,
943
+ send_items: Iterable[Any] | None,
944
+ message_encoding: MessageEncoding,
945
+ check_stop: CheckStopCallableT,
946
+ ) -> list[tuple[Callable, tuple[Any, ...]]]:
947
+ """
948
+ Create send message processing threads for pipe-based transport.
949
+
950
+ :param send_items: Optional collection of items to send during processing
951
+ :param message_encoding: Message encoding configuration for serialization
952
+ :param check_stop: Callable for evaluating stop conditions during processing
953
+ :return: List of thread callables with their arguments for execution
954
+ """
955
+ if self.worker_index is None:
956
+ # Create a separate task for each worker's pipe
957
+ return [
958
+ (
959
+ self._send_messages_task_thread,
960
+ (self.pipes[index], send_items, message_encoding, check_stop),
961
+ )
962
+ for index in range(self.num_workers)
963
+ ]
964
+ else:
965
+ return [
966
+ (
967
+ self._send_messages_task_thread,
968
+ (self.pipes[0], send_items, message_encoding, check_stop),
969
+ )
970
+ ]
971
+
972
+ def create_receive_messages_threads(
973
+ self,
974
+ receive_callback: Callable[[Any], Any] | None,
975
+ message_encoding: MessageEncoding,
976
+ check_stop: CheckStopCallableT,
977
+ ) -> list[tuple[Callable, tuple[Any, ...]]]:
978
+ """
979
+ Create receive message processing threads for pipe-based transport.
980
+
981
+ :param receive_callback: Optional callback for processing received messages
982
+ :param message_encoding: Message encoding configuration for deserialization
983
+ :param check_stop: Callable for evaluating stop conditions during processing
984
+ :return: List of thread callables with their arguments for execution
985
+ """
986
+ if self.worker_index is None:
987
+ # Create a separate task for each worker's pipe
988
+ return [
989
+ (
990
+ self._receive_messages_task_thread,
991
+ (self.pipes[index], receive_callback, message_encoding, check_stop),
992
+ )
993
+ for index in range(self.num_workers)
994
+ ]
995
+ else:
996
+ return [
997
+ (
998
+ self._receive_messages_task_thread,
999
+ (self.pipes[0], receive_callback, message_encoding, check_stop),
1000
+ )
1001
+ ]
1002
+
1003
+ def _send_messages_task_thread( # noqa: C901, PLR0912, PLR0915
1004
+ self,
1005
+ pipe: tuple[Connection, Connection],
1006
+ send_items: Iterable[Any] | None,
1007
+ message_encoding: MessageEncoding,
1008
+ check_stop: CheckStopCallableT,
1009
+ ):
1010
+ local_stop = ThreadingEvent()
1011
+ send_connection: Connection = pipe[0] if self.worker_index is None else pipe[1]
1012
+ send_items_iter = iter(send_items) if send_items is not None else None
1013
+ pending_item = None
1014
+ queue_empty_count = 0
1015
+ pipe_item = None
1016
+ pipe_lock = threading.Lock()
1017
+
1018
+ def _background_pipe_recv():
1019
+ nonlocal pipe_item
1020
+
1021
+ while not local_stop.is_set():
1022
+ try:
1023
+ with pipe_lock:
1024
+ pending = pipe_item
1025
+ pipe_item = None
1026
+
1027
+ if pending is not None:
1028
+ send_connection.send(pending)
1029
+ except (EOFError, ConnectionResetError):
1030
+ break
1031
+
1032
+ if send_items_iter is None:
1033
+ threading.Thread(target=_background_pipe_recv, daemon=True).start()
1034
+
1035
+ try:
1036
+ while not check_stop(pending_item is not None, queue_empty_count):
1037
+ if pending_item is None:
1038
+ try:
1039
+ if send_items_iter is not None:
1040
+ item = next(send_items_iter)
1041
+ else:
1042
+ if self.buffer_send_queue is None:
1043
+ raise RuntimeError(
1044
+ "buffer_send_queue is None; check start()/stop() calls" # noqa: E501
1045
+ )
1046
+ item = self.buffer_send_queue.sync_get(
1047
+ timeout=self.poll_interval
1048
+ )
1049
+ pending_item = message_encoding.encode(item)
1050
+ queue_empty_count = 0
1051
+ except (culsans.QueueEmpty, queue.Empty, StopIteration):
1052
+ queue_empty_count += 1
1053
+
1054
+ if pending_item is not None:
1055
+ try:
1056
+ with pipe_lock:
1057
+ if pipe_item is not None:
1058
+ time.sleep(self.poll_interval / 100)
1059
+ raise queue.Full
1060
+ else:
1061
+ pipe_item = pending_item
1062
+ if send_items_iter is None:
1063
+ if self.buffer_send_queue is None:
1064
+ raise RuntimeError(
1065
+ "buffer_send_queue is None; check start()/stop() calls" # noqa: E501
1066
+ )
1067
+ self.buffer_send_queue.task_done()
1068
+ pending_item = None
1069
+ except (culsans.QueueFull, queue.Full):
1070
+ pass
1071
+ finally:
1072
+ local_stop.set()
1073
+
1074
+ def _receive_messages_task_thread( # noqa: C901
1075
+ self,
1076
+ pipe: tuple[Connection, Connection],
1077
+ receive_callback: Callable[[Any], Any] | None,
1078
+ message_encoding: MessageEncoding,
1079
+ check_stop: CheckStopCallableT,
1080
+ ):
1081
+ receive_connection: Connection = (
1082
+ pipe[0] if self.worker_index is not None else pipe[1]
1083
+ )
1084
+ pending_item = None
1085
+ received_item = None
1086
+ queue_empty_count = 0
1087
+
1088
+ while not check_stop(pending_item is not None, queue_empty_count):
1089
+ if pending_item is None:
1090
+ try:
1091
+ if receive_connection.poll(self.poll_interval):
1092
+ item = receive_connection.recv()
1093
+ pending_item = message_encoding.decode(item)
1094
+ else:
1095
+ raise queue.Empty
1096
+ queue_empty_count = 0
1097
+ except (culsans.QueueEmpty, queue.Empty):
1098
+ queue_empty_count += 1
1099
+
1100
+ if pending_item is not None or received_item is not None:
1101
+ try:
1102
+ if received_item is None:
1103
+ received_item = (
1104
+ pending_item
1105
+ if not receive_callback
1106
+ else receive_callback(pending_item)
1107
+ )
1108
+ if self.buffer_receive_queue is None:
1109
+ raise RuntimeError(
1110
+ "buffer receive queue is None; check start()/stop() calls"
1111
+ )
1112
+ self.buffer_receive_queue.sync_put(
1113
+ cast("ReceiveMessageT", received_item)
1114
+ )
1115
+ pending_item = None
1116
+ received_item = None
1117
+ except (culsans.QueueFull, queue.Full):
1118
+ pass