2023-12-12 11:24:31 -04:00
|
|
|
"""Cross-interpreter Queues High Level Module."""
|
|
|
|
|
2024-02-28 19:08:08 -04:00
|
|
|
import pickle
|
2023-12-12 11:24:31 -04:00
|
|
|
import queue
|
|
|
|
import time
|
|
|
|
import weakref
|
2024-04-24 13:18:24 -03:00
|
|
|
import _interpqueues as _queues
|
2024-07-15 16:43:59 -03:00
|
|
|
from . import _crossinterp
|
2023-12-12 11:24:31 -04:00
|
|
|
|
|
|
|
# aliases:
|
2024-04-24 13:18:24 -03:00
|
|
|
from _interpqueues import (
|
2023-12-12 13:43:30 -04:00
|
|
|
QueueError, QueueNotFoundError,
|
2023-12-12 11:24:31 -04:00
|
|
|
)
|
2024-07-15 16:43:59 -03:00
|
|
|
from ._crossinterp import (
|
|
|
|
UNBOUND_ERROR, UNBOUND_REMOVE,
|
|
|
|
)
|
2023-12-12 11:24:31 -04:00
|
|
|
|
|
|
|
__all__ = [
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
'UNBOUND', 'UNBOUND_ERROR', 'UNBOUND_REMOVE',
|
2023-12-12 11:24:31 -04:00
|
|
|
'create', 'list_all',
|
|
|
|
'Queue',
|
|
|
|
'QueueError', 'QueueNotFoundError', 'QueueEmpty', 'QueueFull',
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
'ItemInterpreterDestroyed',
|
2023-12-12 11:24:31 -04:00
|
|
|
]
|
|
|
|
|
|
|
|
|
2024-03-05 11:54:46 -04:00
|
|
|
class QueueEmpty(QueueError, queue.Empty):
|
2023-12-12 13:43:30 -04:00
|
|
|
"""Raised from get_nowait() when the queue is empty.
|
|
|
|
|
|
|
|
It is also raised from get() if it times out.
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
2024-03-05 11:54:46 -04:00
|
|
|
class QueueFull(QueueError, queue.Full):
|
2023-12-12 13:43:30 -04:00
|
|
|
"""Raised from put_nowait() when the queue is full.
|
|
|
|
|
|
|
|
It is also raised from put() if it times out.
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
2024-07-15 16:43:59 -03:00
|
|
|
class ItemInterpreterDestroyed(QueueError,
|
|
|
|
_crossinterp.ItemInterpreterDestroyed):
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
"""Raised from get() and get_nowait()."""
|
|
|
|
|
|
|
|
|
2024-02-28 19:08:08 -04:00
|
|
|
_SHARED_ONLY = 0
|
|
|
|
_PICKLED = 1
|
|
|
|
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
|
2024-07-15 16:43:59 -03:00
|
|
|
UNBOUND = _crossinterp.UnboundItem.singleton('queue', __name__)
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
|
|
|
|
|
|
|
|
def _serialize_unbound(unbound):
|
2024-07-15 16:43:59 -03:00
|
|
|
if unbound is UNBOUND:
|
|
|
|
unbound = _crossinterp.UNBOUND
|
|
|
|
return _crossinterp.serialize_unbound(unbound)
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
|
|
|
|
|
|
|
|
def _resolve_unbound(flag):
|
2024-07-15 16:43:59 -03:00
|
|
|
resolved = _crossinterp.resolve_unbound(flag, ItemInterpreterDestroyed)
|
|
|
|
if resolved is _crossinterp.UNBOUND:
|
|
|
|
resolved = UNBOUND
|
|
|
|
return resolved
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
|
|
|
|
|
|
|
|
def create(maxsize=0, *, syncobj=False, unbounditems=UNBOUND):
|
2023-12-12 11:24:31 -04:00
|
|
|
"""Return a new cross-interpreter queue.
|
|
|
|
|
|
|
|
The queue may be used to pass data safely between interpreters.
|
2024-02-28 19:08:08 -04:00
|
|
|
|
|
|
|
"syncobj" sets the default for Queue.put()
|
|
|
|
and Queue.put_nowait().
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
|
|
|
|
"unbounditems" likewise sets the default. See Queue.put() for
|
|
|
|
supported values. The default value is UNBOUND, which replaces
|
|
|
|
the unbound item.
|
2023-12-12 11:24:31 -04:00
|
|
|
"""
|
2024-02-28 19:08:08 -04:00
|
|
|
fmt = _SHARED_ONLY if syncobj else _PICKLED
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
unbound = _serialize_unbound(unbounditems)
|
|
|
|
unboundop, = unbound
|
|
|
|
qid = _queues.create(maxsize, fmt, unboundop)
|
|
|
|
return Queue(qid, _fmt=fmt, _unbound=unbound)
|
2023-12-12 11:24:31 -04:00
|
|
|
|
|
|
|
|
|
|
|
def list_all():
|
|
|
|
"""Return a list of all open queues."""
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
return [Queue(qid, _fmt=fmt, _unbound=(unboundop,))
|
|
|
|
for qid, fmt, unboundop in _queues.list_all()]
|
2023-12-12 11:24:31 -04:00
|
|
|
|
|
|
|
|
|
|
|
_known_queues = weakref.WeakValueDictionary()
|
|
|
|
|
|
|
|
class Queue:
|
|
|
|
"""A cross-interpreter queue."""
|
|
|
|
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
def __new__(cls, id, /, *, _fmt=None, _unbound=None):
|
2023-12-12 11:24:31 -04:00
|
|
|
# There is only one instance for any given ID.
|
|
|
|
if isinstance(id, int):
|
2023-12-12 13:43:30 -04:00
|
|
|
id = int(id)
|
|
|
|
else:
|
2023-12-12 11:24:31 -04:00
|
|
|
raise TypeError(f'id must be an int, got {id!r}')
|
2024-02-28 19:08:08 -04:00
|
|
|
if _fmt is None:
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
if _unbound is None:
|
|
|
|
_fmt, op = _queues.get_queue_defaults(id)
|
|
|
|
_unbound = (op,)
|
|
|
|
else:
|
|
|
|
_fmt, _ = _queues.get_queue_defaults(id)
|
|
|
|
elif _unbound is None:
|
|
|
|
_, op = _queues.get_queue_defaults(id)
|
|
|
|
_unbound = (op,)
|
2023-12-12 11:24:31 -04:00
|
|
|
try:
|
2023-12-12 13:43:30 -04:00
|
|
|
self = _known_queues[id]
|
2023-12-12 11:24:31 -04:00
|
|
|
except KeyError:
|
|
|
|
self = super().__new__(cls)
|
|
|
|
self._id = id
|
2024-02-28 19:08:08 -04:00
|
|
|
self._fmt = _fmt
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
self._unbound = _unbound
|
2023-12-12 13:43:30 -04:00
|
|
|
_known_queues[id] = self
|
|
|
|
_queues.bind(id)
|
2023-12-12 11:24:31 -04:00
|
|
|
return self
|
|
|
|
|
2023-12-12 13:43:30 -04:00
|
|
|
def __del__(self):
|
|
|
|
try:
|
|
|
|
_queues.release(self._id)
|
|
|
|
except QueueNotFoundError:
|
|
|
|
pass
|
|
|
|
try:
|
|
|
|
del _known_queues[self._id]
|
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
|
2023-12-12 11:24:31 -04:00
|
|
|
def __repr__(self):
|
|
|
|
return f'{type(self).__name__}({self.id})'
|
|
|
|
|
|
|
|
def __hash__(self):
|
|
|
|
return hash(self._id)
|
|
|
|
|
2024-03-05 11:54:46 -04:00
|
|
|
# for pickling:
|
|
|
|
def __getnewargs__(self):
|
|
|
|
return (self._id,)
|
|
|
|
|
|
|
|
# for pickling:
|
|
|
|
def __getstate__(self):
|
|
|
|
return None
|
|
|
|
|
2023-12-12 11:24:31 -04:00
|
|
|
@property
|
|
|
|
def id(self):
|
2023-12-12 13:43:30 -04:00
|
|
|
return self._id
|
2023-12-12 11:24:31 -04:00
|
|
|
|
|
|
|
@property
|
|
|
|
def maxsize(self):
|
2023-12-12 13:43:30 -04:00
|
|
|
try:
|
|
|
|
return self._maxsize
|
|
|
|
except AttributeError:
|
|
|
|
self._maxsize = _queues.get_maxsize(self._id)
|
|
|
|
return self._maxsize
|
2023-12-12 11:24:31 -04:00
|
|
|
|
|
|
|
def empty(self):
|
2023-12-12 13:43:30 -04:00
|
|
|
return self.qsize() == 0
|
2023-12-12 11:24:31 -04:00
|
|
|
|
|
|
|
def full(self):
|
2023-12-12 13:43:30 -04:00
|
|
|
return _queues.is_full(self._id)
|
2023-12-12 11:24:31 -04:00
|
|
|
|
|
|
|
def qsize(self):
|
2023-12-12 13:43:30 -04:00
|
|
|
return _queues.get_count(self._id)
|
2023-12-12 11:24:31 -04:00
|
|
|
|
2023-12-12 13:43:30 -04:00
|
|
|
def put(self, obj, timeout=None, *,
|
2024-02-28 19:08:08 -04:00
|
|
|
syncobj=None,
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
unbound=None,
|
2023-12-12 13:43:30 -04:00
|
|
|
_delay=10 / 1000, # 10 milliseconds
|
|
|
|
):
|
|
|
|
"""Add the object to the queue.
|
|
|
|
|
|
|
|
This blocks while the queue is full.
|
2024-02-28 19:08:08 -04:00
|
|
|
|
|
|
|
If "syncobj" is None (the default) then it uses the
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
queue's default, set with create_queue().
|
2024-02-28 19:08:08 -04:00
|
|
|
|
|
|
|
If "syncobj" is false then all objects are supported,
|
|
|
|
at the expense of worse performance.
|
|
|
|
|
|
|
|
If "syncobj" is true then the object must be "shareable".
|
|
|
|
Examples of "shareable" objects include the builtin singletons,
|
|
|
|
str, and memoryview. One benefit is that such objects are
|
|
|
|
passed through the queue efficiently.
|
|
|
|
|
|
|
|
The key difference, though, is conceptual: the corresponding
|
|
|
|
object returned from Queue.get() will be strictly equivalent
|
|
|
|
to the given obj. In other words, the two objects will be
|
|
|
|
effectively indistinguishable from each other, even if the
|
|
|
|
object is mutable. The received object may actually be the
|
|
|
|
same object, or a copy (immutable values only), or a proxy.
|
|
|
|
Regardless, the received object should be treated as though
|
|
|
|
the original has been shared directly, whether or not it
|
|
|
|
actually is. That's a slightly different and stronger promise
|
|
|
|
than just (initial) equality, which is all "syncobj=False"
|
|
|
|
can promise.
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
|
|
|
|
"unbound" controls the behavior of Queue.get() for the given
|
|
|
|
object if the current interpreter (calling put()) is later
|
|
|
|
destroyed.
|
|
|
|
|
|
|
|
If "unbound" is None (the default) then it uses the
|
|
|
|
queue's default, set with create_queue(),
|
|
|
|
which is usually UNBOUND.
|
|
|
|
|
|
|
|
If "unbound" is UNBOUND_ERROR then get() will raise an
|
|
|
|
ItemInterpreterDestroyed exception if the original interpreter
|
|
|
|
has been destroyed. This does not otherwise affect the queue;
|
|
|
|
the next call to put() will work like normal, returning the next
|
|
|
|
item in the queue.
|
|
|
|
|
|
|
|
If "unbound" is UNBOUND_REMOVE then the item will be removed
|
|
|
|
from the queue as soon as the original interpreter is destroyed.
|
|
|
|
Be aware that this will introduce an imbalance between put()
|
|
|
|
and get() calls.
|
|
|
|
|
|
|
|
If "unbound" is UNBOUND then it is returned by get() in place
|
|
|
|
of the unbound item.
|
2023-12-12 13:43:30 -04:00
|
|
|
"""
|
2024-02-28 19:08:08 -04:00
|
|
|
if syncobj is None:
|
|
|
|
fmt = self._fmt
|
|
|
|
else:
|
|
|
|
fmt = _SHARED_ONLY if syncobj else _PICKLED
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
if unbound is None:
|
|
|
|
unboundop, = self._unbound
|
|
|
|
else:
|
|
|
|
unboundop, = _serialize_unbound(unbound)
|
2023-12-12 13:43:30 -04:00
|
|
|
if timeout is not None:
|
|
|
|
timeout = int(timeout)
|
|
|
|
if timeout < 0:
|
|
|
|
raise ValueError(f'timeout value must be non-negative')
|
|
|
|
end = time.time() + timeout
|
2024-02-28 19:08:08 -04:00
|
|
|
if fmt is _PICKLED:
|
|
|
|
obj = pickle.dumps(obj)
|
2023-12-12 13:43:30 -04:00
|
|
|
while True:
|
|
|
|
try:
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
_queues.put(self._id, obj, fmt, unboundop)
|
2024-03-05 11:54:46 -04:00
|
|
|
except QueueFull as exc:
|
2023-12-12 13:43:30 -04:00
|
|
|
if timeout is not None and time.time() >= end:
|
|
|
|
raise # re-raise
|
|
|
|
time.sleep(_delay)
|
|
|
|
else:
|
|
|
|
break
|
2023-12-12 11:24:31 -04:00
|
|
|
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
def put_nowait(self, obj, *, syncobj=None, unbound=None):
|
2024-02-28 19:08:08 -04:00
|
|
|
if syncobj is None:
|
|
|
|
fmt = self._fmt
|
|
|
|
else:
|
|
|
|
fmt = _SHARED_ONLY if syncobj else _PICKLED
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
if unbound is None:
|
|
|
|
unboundop, = self._unbound
|
|
|
|
else:
|
|
|
|
unboundop, = _serialize_unbound(unbound)
|
2024-02-28 19:08:08 -04:00
|
|
|
if fmt is _PICKLED:
|
|
|
|
obj = pickle.dumps(obj)
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
_queues.put(self._id, obj, fmt, unboundop)
|
2023-12-12 11:24:31 -04:00
|
|
|
|
|
|
|
def get(self, timeout=None, *,
|
2023-12-12 13:43:30 -04:00
|
|
|
_delay=10 / 1000, # 10 milliseconds
|
|
|
|
):
|
2023-12-12 11:24:31 -04:00
|
|
|
"""Return the next object from the queue.
|
|
|
|
|
|
|
|
This blocks while the queue is empty.
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
|
|
|
|
If the next item's original interpreter has been destroyed
|
|
|
|
then the "next object" is determined by the value of the
|
|
|
|
"unbound" argument to put().
|
2023-12-12 11:24:31 -04:00
|
|
|
"""
|
|
|
|
if timeout is not None:
|
|
|
|
timeout = int(timeout)
|
|
|
|
if timeout < 0:
|
|
|
|
raise ValueError(f'timeout value must be non-negative')
|
|
|
|
end = time.time() + timeout
|
2023-12-12 13:43:30 -04:00
|
|
|
while True:
|
|
|
|
try:
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
obj, fmt, unboundop = _queues.get(self._id)
|
2024-03-05 11:54:46 -04:00
|
|
|
except QueueEmpty as exc:
|
2023-12-12 13:43:30 -04:00
|
|
|
if timeout is not None and time.time() >= end:
|
|
|
|
raise # re-raise
|
|
|
|
time.sleep(_delay)
|
2024-02-28 19:08:08 -04:00
|
|
|
else:
|
|
|
|
break
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
if unboundop is not None:
|
|
|
|
assert obj is None, repr(obj)
|
|
|
|
return _resolve_unbound(unboundop)
|
2024-02-28 19:08:08 -04:00
|
|
|
if fmt == _PICKLED:
|
|
|
|
obj = pickle.loads(obj)
|
|
|
|
else:
|
|
|
|
assert fmt == _SHARED_ONLY
|
2023-12-12 11:24:31 -04:00
|
|
|
return obj
|
|
|
|
|
2023-12-12 13:43:30 -04:00
|
|
|
def get_nowait(self):
|
2023-12-12 11:24:31 -04:00
|
|
|
"""Return the next object from the channel.
|
|
|
|
|
|
|
|
If the queue is empty then raise QueueEmpty. Otherwise this
|
|
|
|
is the same as get().
|
|
|
|
"""
|
2023-12-12 13:43:30 -04:00
|
|
|
try:
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
obj, fmt, unboundop = _queues.get(self._id)
|
2024-03-05 11:54:46 -04:00
|
|
|
except QueueEmpty as exc:
|
2023-12-12 13:43:30 -04:00
|
|
|
raise # re-raise
|
gh-76785: Expand How Interpreter Queues Handle Interpreter Finalization (gh-116431)
Any cross-interpreter mechanism for passing objects between interpreters must be very careful to respect isolation, even when the object is effectively immutable (e.g. int, str). Here this especially relates to when an interpreter sends one of its objects, and then is destroyed while the inter-interpreter machinery (e.g. queue) still holds a reference to the object.
When I added interpreters.Queue, I dealt with that case (using an atexit hook) by silently removing all items from the queue that were added by the finalizing interpreter.
Later, while working on concurrent.futures.InterpreterPoolExecutor (gh-116430), I noticed it was somewhat surprising when items were silently removed from the queue when the originating interpreter was destroyed. (See my comment on that PR.)
It took me a little while to realize what was going on. I expect that users, which much less context than I have, would experience the same pain.
My approach, here, to improving the situation is to give users three options:
1. return a singleton (interpreters.queues.UNBOUND) from Queue.get() in place of each removed item
2. raise an exception (interpreters.queues.ItemInterpreterDestroyed) from Queue.get() in place of each removed item
3. existing behavior: silently remove each item (i.e. Queue.get() skips each one)
The default will now be (1), but users can still explicitly opt in any of them, including to the silent removal behavior.
The behavior for each item may be set with the corresponding Queue.put() call. and a queue-wide default may be set when the queue is created. (This is the same as I did for "synconly".)
2024-07-15 15:49:23 -03:00
|
|
|
if unboundop is not None:
|
|
|
|
assert obj is None, repr(obj)
|
|
|
|
return _resolve_unbound(unboundop)
|
2024-03-01 12:36:35 -04:00
|
|
|
if fmt == _PICKLED:
|
|
|
|
obj = pickle.loads(obj)
|
|
|
|
else:
|
|
|
|
assert fmt == _SHARED_ONLY
|
|
|
|
return obj
|
2023-12-12 11:24:31 -04:00
|
|
|
|
|
|
|
|
2024-03-05 11:54:46 -04:00
|
|
|
_queues._register_heap_types(Queue, QueueEmpty, QueueFull)
|