mirror of
https://github.com/python/cpython.git
synced 2025-08-04 17:08:35 +00:00
gh-76785: Expand How Interpreter Channels Handle Interpreter Finalization (gh-121805)
See 6b98b274b6
for an explanation of the problem and solution. Here I've applied the solution to channels.
This commit is contained in:
parent
fd085a411e
commit
8b209fd4f8
9 changed files with 898 additions and 306 deletions
|
@ -18,7 +18,9 @@
|
|||
#endif
|
||||
|
||||
#define REGISTERS_HEAP_TYPES
|
||||
#define HAS_UNBOUND_ITEMS
|
||||
#include "_interpreters_common.h"
|
||||
#undef HAS_UNBOUND_ITEMS
|
||||
#undef REGISTERS_HEAP_TYPES
|
||||
|
||||
|
||||
|
@ -511,8 +513,14 @@ _waiting_finish_releasing(_waiting_t *waiting)
|
|||
struct _channelitem;
|
||||
|
||||
typedef struct _channelitem {
|
||||
/* The interpreter that added the item to the queue.
|
||||
The actual bound interpid is found in item->data.
|
||||
This is necessary because item->data might be NULL,
|
||||
meaning the interpreter has been destroyed. */
|
||||
int64_t interpid;
|
||||
_PyCrossInterpreterData *data;
|
||||
_waiting_t *waiting;
|
||||
int unboundop;
|
||||
struct _channelitem *next;
|
||||
} _channelitem;
|
||||
|
||||
|
@ -524,11 +532,22 @@ _channelitem_ID(_channelitem *item)
|
|||
|
||||
static void
|
||||
_channelitem_init(_channelitem *item,
|
||||
_PyCrossInterpreterData *data, _waiting_t *waiting)
|
||||
int64_t interpid, _PyCrossInterpreterData *data,
|
||||
_waiting_t *waiting, int unboundop)
|
||||
{
|
||||
if (interpid < 0) {
|
||||
interpid = _get_interpid(data);
|
||||
}
|
||||
else {
|
||||
assert(data == NULL
|
||||
|| _PyCrossInterpreterData_INTERPID(data) < 0
|
||||
|| interpid == _PyCrossInterpreterData_INTERPID(data));
|
||||
}
|
||||
*item = (_channelitem){
|
||||
.interpid = interpid,
|
||||
.data = data,
|
||||
.waiting = waiting,
|
||||
.unboundop = unboundop,
|
||||
};
|
||||
if (waiting != NULL) {
|
||||
waiting->itemid = _channelitem_ID(item);
|
||||
|
@ -536,17 +555,15 @@ _channelitem_init(_channelitem *item,
|
|||
}
|
||||
|
||||
static void
|
||||
_channelitem_clear(_channelitem *item)
|
||||
_channelitem_clear_data(_channelitem *item, int removed)
|
||||
{
|
||||
item->next = NULL;
|
||||
|
||||
if (item->data != NULL) {
|
||||
// It was allocated in channel_send().
|
||||
(void)_release_xid_data(item->data, XID_IGNORE_EXC & XID_FREE);
|
||||
item->data = NULL;
|
||||
}
|
||||
|
||||
if (item->waiting != NULL) {
|
||||
if (item->waiting != NULL && removed) {
|
||||
if (item->waiting->status == WAITING_ACQUIRED) {
|
||||
_waiting_release(item->waiting, 0);
|
||||
}
|
||||
|
@ -554,15 +571,23 @@ _channelitem_clear(_channelitem *item)
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_channelitem_clear(_channelitem *item)
|
||||
{
|
||||
item->next = NULL;
|
||||
_channelitem_clear_data(item, 1);
|
||||
}
|
||||
|
||||
static _channelitem *
|
||||
_channelitem_new(_PyCrossInterpreterData *data, _waiting_t *waiting)
|
||||
_channelitem_new(int64_t interpid, _PyCrossInterpreterData *data,
|
||||
_waiting_t *waiting, int unboundop)
|
||||
{
|
||||
_channelitem *item = GLOBAL_MALLOC(_channelitem);
|
||||
if (item == NULL) {
|
||||
PyErr_NoMemory();
|
||||
return NULL;
|
||||
}
|
||||
_channelitem_init(item, data, waiting);
|
||||
_channelitem_init(item, interpid, data, waiting, unboundop);
|
||||
return item;
|
||||
}
|
||||
|
||||
|
@ -585,17 +610,48 @@ _channelitem_free_all(_channelitem *item)
|
|||
|
||||
static void
|
||||
_channelitem_popped(_channelitem *item,
|
||||
_PyCrossInterpreterData **p_data, _waiting_t **p_waiting)
|
||||
_PyCrossInterpreterData **p_data, _waiting_t **p_waiting,
|
||||
int *p_unboundop)
|
||||
{
|
||||
assert(item->waiting == NULL || item->waiting->status == WAITING_ACQUIRED);
|
||||
*p_data = item->data;
|
||||
*p_waiting = item->waiting;
|
||||
*p_unboundop = item->unboundop;
|
||||
// We clear them here, so they won't be released in _channelitem_clear().
|
||||
item->data = NULL;
|
||||
item->waiting = NULL;
|
||||
_channelitem_free(item);
|
||||
}
|
||||
|
||||
static int
|
||||
_channelitem_clear_interpreter(_channelitem *item)
|
||||
{
|
||||
assert(item->interpid >= 0);
|
||||
if (item->data == NULL) {
|
||||
// Its interpreter was already cleared (or it was never bound).
|
||||
// For UNBOUND_REMOVE it should have been freed at that time.
|
||||
assert(item->unboundop != UNBOUND_REMOVE);
|
||||
return 0;
|
||||
}
|
||||
assert(_PyCrossInterpreterData_INTERPID(item->data) == item->interpid);
|
||||
|
||||
switch (item->unboundop) {
|
||||
case UNBOUND_REMOVE:
|
||||
// The caller must free/clear it.
|
||||
return 1;
|
||||
case UNBOUND_ERROR:
|
||||
case UNBOUND_REPLACE:
|
||||
// We won't need the cross-interpreter data later
|
||||
// so we completely throw it away.
|
||||
_channelitem_clear_data(item, 0);
|
||||
return 0;
|
||||
default:
|
||||
Py_FatalError("not reachable");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
typedef struct _channelqueue {
|
||||
int64_t count;
|
||||
_channelitem *first;
|
||||
|
@ -634,9 +690,10 @@ _channelqueue_free(_channelqueue *queue)
|
|||
|
||||
static int
|
||||
_channelqueue_put(_channelqueue *queue,
|
||||
_PyCrossInterpreterData *data, _waiting_t *waiting)
|
||||
int64_t interpid, _PyCrossInterpreterData *data,
|
||||
_waiting_t *waiting, int unboundop)
|
||||
{
|
||||
_channelitem *item = _channelitem_new(data, waiting);
|
||||
_channelitem *item = _channelitem_new(interpid, data, waiting, unboundop);
|
||||
if (item == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
@ -659,7 +716,8 @@ _channelqueue_put(_channelqueue *queue,
|
|||
|
||||
static int
|
||||
_channelqueue_get(_channelqueue *queue,
|
||||
_PyCrossInterpreterData **p_data, _waiting_t **p_waiting)
|
||||
_PyCrossInterpreterData **p_data, _waiting_t **p_waiting,
|
||||
int *p_unboundop)
|
||||
{
|
||||
_channelitem *item = queue->first;
|
||||
if (item == NULL) {
|
||||
|
@ -671,7 +729,7 @@ _channelqueue_get(_channelqueue *queue,
|
|||
}
|
||||
queue->count -= 1;
|
||||
|
||||
_channelitem_popped(item, p_data, p_waiting);
|
||||
_channelitem_popped(item, p_data, p_waiting, p_unboundop);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -737,7 +795,8 @@ _channelqueue_remove(_channelqueue *queue, _channelitem_id_t itemid,
|
|||
}
|
||||
queue->count -= 1;
|
||||
|
||||
_channelitem_popped(item, p_data, p_waiting);
|
||||
int unboundop;
|
||||
_channelitem_popped(item, p_data, p_waiting, &unboundop);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -748,14 +807,17 @@ _channelqueue_clear_interpreter(_channelqueue *queue, int64_t interpid)
|
|||
while (next != NULL) {
|
||||
_channelitem *item = next;
|
||||
next = item->next;
|
||||
if (_PyCrossInterpreterData_INTERPID(item->data) == interpid) {
|
||||
int remove = (item->interpid == interpid)
|
||||
? _channelitem_clear_interpreter(item)
|
||||
: 0;
|
||||
if (remove) {
|
||||
_channelitem_free(item);
|
||||
if (prev == NULL) {
|
||||
queue->first = item->next;
|
||||
queue->first = next;
|
||||
}
|
||||
else {
|
||||
prev->next = item->next;
|
||||
prev->next = next;
|
||||
}
|
||||
_channelitem_free(item);
|
||||
queue->count -= 1;
|
||||
}
|
||||
else {
|
||||
|
@ -1018,12 +1080,15 @@ typedef struct _channel {
|
|||
PyThread_type_lock mutex;
|
||||
_channelqueue *queue;
|
||||
_channelends *ends;
|
||||
struct {
|
||||
int unboundop;
|
||||
} defaults;
|
||||
int open;
|
||||
struct _channel_closing *closing;
|
||||
} _channel_state;
|
||||
|
||||
static _channel_state *
|
||||
_channel_new(PyThread_type_lock mutex)
|
||||
_channel_new(PyThread_type_lock mutex, int unboundop)
|
||||
{
|
||||
_channel_state *chan = GLOBAL_MALLOC(_channel_state);
|
||||
if (chan == NULL) {
|
||||
|
@ -1041,6 +1106,7 @@ _channel_new(PyThread_type_lock mutex)
|
|||
GLOBAL_FREE(chan);
|
||||
return NULL;
|
||||
}
|
||||
chan->defaults.unboundop = unboundop;
|
||||
chan->open = 1;
|
||||
chan->closing = NULL;
|
||||
return chan;
|
||||
|
@ -1061,7 +1127,8 @@ _channel_free(_channel_state *chan)
|
|||
|
||||
static int
|
||||
_channel_add(_channel_state *chan, int64_t interpid,
|
||||
_PyCrossInterpreterData *data, _waiting_t *waiting)
|
||||
_PyCrossInterpreterData *data, _waiting_t *waiting,
|
||||
int unboundop)
|
||||
{
|
||||
int res = -1;
|
||||
PyThread_acquire_lock(chan->mutex, WAIT_LOCK);
|
||||
|
@ -1075,7 +1142,7 @@ _channel_add(_channel_state *chan, int64_t interpid,
|
|||
goto done;
|
||||
}
|
||||
|
||||
if (_channelqueue_put(chan->queue, data, waiting) != 0) {
|
||||
if (_channelqueue_put(chan->queue, interpid, data, waiting, unboundop) != 0) {
|
||||
goto done;
|
||||
}
|
||||
// Any errors past this point must cause a _waiting_release() call.
|
||||
|
@ -1088,7 +1155,8 @@ done:
|
|||
|
||||
static int
|
||||
_channel_next(_channel_state *chan, int64_t interpid,
|
||||
_PyCrossInterpreterData **p_data, _waiting_t **p_waiting)
|
||||
_PyCrossInterpreterData **p_data, _waiting_t **p_waiting,
|
||||
int *p_unboundop)
|
||||
{
|
||||
int err = 0;
|
||||
PyThread_acquire_lock(chan->mutex, WAIT_LOCK);
|
||||
|
@ -1102,11 +1170,15 @@ _channel_next(_channel_state *chan, int64_t interpid,
|
|||
goto done;
|
||||
}
|
||||
|
||||
int empty = _channelqueue_get(chan->queue, p_data, p_waiting);
|
||||
assert(empty == 0 || empty == ERR_CHANNEL_EMPTY);
|
||||
int empty = _channelqueue_get(chan->queue, p_data, p_waiting, p_unboundop);
|
||||
assert(!PyErr_Occurred());
|
||||
if (empty && chan->closing != NULL) {
|
||||
chan->open = 0;
|
||||
if (empty) {
|
||||
assert(empty == ERR_CHANNEL_EMPTY);
|
||||
if (chan->closing != NULL) {
|
||||
chan->open = 0;
|
||||
}
|
||||
err = ERR_CHANNEL_EMPTY;
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
|
@ -1528,18 +1600,27 @@ done:
|
|||
PyThread_release_lock(channels->mutex);
|
||||
}
|
||||
|
||||
static int64_t *
|
||||
struct channel_id_and_info {
|
||||
int64_t id;
|
||||
int unboundop;
|
||||
};
|
||||
|
||||
static struct channel_id_and_info *
|
||||
_channels_list_all(_channels *channels, int64_t *count)
|
||||
{
|
||||
int64_t *cids = NULL;
|
||||
struct channel_id_and_info *cids = NULL;
|
||||
PyThread_acquire_lock(channels->mutex, WAIT_LOCK);
|
||||
int64_t *ids = PyMem_NEW(int64_t, (Py_ssize_t)(channels->numopen));
|
||||
struct channel_id_and_info *ids =
|
||||
PyMem_NEW(struct channel_id_and_info, (Py_ssize_t)(channels->numopen));
|
||||
if (ids == NULL) {
|
||||
goto done;
|
||||
}
|
||||
_channelref *ref = channels->head;
|
||||
for (int64_t i=0; ref != NULL; ref = ref->next, i++) {
|
||||
ids[i] = ref->cid;
|
||||
ids[i] = (struct channel_id_and_info){
|
||||
.id = ref->cid,
|
||||
.unboundop = ref->chan->defaults.unboundop,
|
||||
};
|
||||
}
|
||||
*count = channels->numopen;
|
||||
|
||||
|
@ -1624,13 +1705,13 @@ _channel_finish_closing(_channel_state *chan) {
|
|||
|
||||
// Create a new channel.
|
||||
static int64_t
|
||||
channel_create(_channels *channels)
|
||||
channel_create(_channels *channels, int unboundop)
|
||||
{
|
||||
PyThread_type_lock mutex = PyThread_allocate_lock();
|
||||
if (mutex == NULL) {
|
||||
return ERR_CHANNEL_MUTEX_INIT;
|
||||
}
|
||||
_channel_state *chan = _channel_new(mutex);
|
||||
_channel_state *chan = _channel_new(mutex, unboundop);
|
||||
if (chan == NULL) {
|
||||
PyThread_free_lock(mutex);
|
||||
return -1;
|
||||
|
@ -1662,7 +1743,7 @@ channel_destroy(_channels *channels, int64_t cid)
|
|||
// Optionally request to be notified when it is received.
|
||||
static int
|
||||
channel_send(_channels *channels, int64_t cid, PyObject *obj,
|
||||
_waiting_t *waiting)
|
||||
_waiting_t *waiting, int unboundop)
|
||||
{
|
||||
PyInterpreterState *interp = _get_current_interp();
|
||||
if (interp == NULL) {
|
||||
|
@ -1698,7 +1779,7 @@ channel_send(_channels *channels, int64_t cid, PyObject *obj,
|
|||
}
|
||||
|
||||
// Add the data to the channel.
|
||||
int res = _channel_add(chan, interpid, data, waiting);
|
||||
int res = _channel_add(chan, interpid, data, waiting, unboundop);
|
||||
PyThread_release_lock(mutex);
|
||||
if (res != 0) {
|
||||
// We may chain an exception here:
|
||||
|
@ -1735,7 +1816,7 @@ channel_clear_sent(_channels *channels, int64_t cid, _waiting_t *waiting)
|
|||
// Like channel_send(), but strictly wait for the object to be received.
|
||||
static int
|
||||
channel_send_wait(_channels *channels, int64_t cid, PyObject *obj,
|
||||
PY_TIMEOUT_T timeout)
|
||||
int unboundop, PY_TIMEOUT_T timeout)
|
||||
{
|
||||
// We use a stack variable here, so we must ensure that &waiting
|
||||
// is not held by any channel item at the point this function exits.
|
||||
|
@ -1746,7 +1827,7 @@ channel_send_wait(_channels *channels, int64_t cid, PyObject *obj,
|
|||
}
|
||||
|
||||
/* Queue up the object. */
|
||||
int res = channel_send(channels, cid, obj, &waiting);
|
||||
int res = channel_send(channels, cid, obj, &waiting, unboundop);
|
||||
if (res < 0) {
|
||||
assert(waiting.status == WAITING_NO_STATUS);
|
||||
goto finally;
|
||||
|
@ -1788,7 +1869,7 @@ finally:
|
|||
// The current interpreter gets associated with the recv end of the channel.
|
||||
// XXX Support a "wait" mutex?
|
||||
static int
|
||||
channel_recv(_channels *channels, int64_t cid, PyObject **res)
|
||||
channel_recv(_channels *channels, int64_t cid, PyObject **res, int *p_unboundop)
|
||||
{
|
||||
int err;
|
||||
*res = NULL;
|
||||
|
@ -1816,13 +1897,15 @@ channel_recv(_channels *channels, int64_t cid, PyObject **res)
|
|||
// Pop off the next item from the channel.
|
||||
_PyCrossInterpreterData *data = NULL;
|
||||
_waiting_t *waiting = NULL;
|
||||
err = _channel_next(chan, interpid, &data, &waiting);
|
||||
err = _channel_next(chan, interpid, &data, &waiting, p_unboundop);
|
||||
PyThread_release_lock(mutex);
|
||||
if (err != 0) {
|
||||
return err;
|
||||
}
|
||||
else if (data == NULL) {
|
||||
// The item was unbound.
|
||||
assert(!PyErr_Occurred());
|
||||
*res = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1915,6 +1998,23 @@ channel_is_associated(_channels *channels, int64_t cid, int64_t interpid,
|
|||
return (end != NULL && end->open);
|
||||
}
|
||||
|
||||
static int
|
||||
_channel_get_count(_channels *channels, int64_t cid, Py_ssize_t *p_count)
|
||||
{
|
||||
PyThread_type_lock mutex = NULL;
|
||||
_channel_state *chan = NULL;
|
||||
int err = _channels_lookup(channels, cid, &mutex, &chan);
|
||||
if (err != 0) {
|
||||
return err;
|
||||
}
|
||||
assert(chan != NULL);
|
||||
int64_t count = chan->queue->count;
|
||||
PyThread_release_lock(mutex);
|
||||
|
||||
*p_count = (Py_ssize_t)count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* channel info */
|
||||
|
||||
|
@ -2767,9 +2867,22 @@ clear_interpreter(void *data)
|
|||
|
||||
|
||||
static PyObject *
|
||||
channelsmod_create(PyObject *self, PyObject *Py_UNUSED(ignored))
|
||||
channelsmod_create(PyObject *self, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
int64_t cid = channel_create(&_globals.channels);
|
||||
static char *kwlist[] = {"unboundop", NULL};
|
||||
int unboundop;
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "i:create", kwlist,
|
||||
&unboundop))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
if (!check_unbound(unboundop)) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"unsupported unboundop %d", unboundop);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int64_t cid = channel_create(&_globals.channels, unboundop);
|
||||
if (cid < 0) {
|
||||
(void)handle_channel_error(-1, self, cid);
|
||||
return NULL;
|
||||
|
@ -2796,7 +2909,7 @@ channelsmod_create(PyObject *self, PyObject *Py_UNUSED(ignored))
|
|||
}
|
||||
|
||||
PyDoc_STRVAR(channelsmod_create_doc,
|
||||
"channel_create() -> cid\n\
|
||||
"channel_create(unboundop) -> cid\n\
|
||||
\n\
|
||||
Create a new cross-interpreter channel and return a unique generated ID.");
|
||||
|
||||
|
@ -2831,7 +2944,8 @@ static PyObject *
|
|||
channelsmod_list_all(PyObject *self, PyObject *Py_UNUSED(ignored))
|
||||
{
|
||||
int64_t count = 0;
|
||||
int64_t *cids = _channels_list_all(&_globals.channels, &count);
|
||||
struct channel_id_and_info *cids =
|
||||
_channels_list_all(&_globals.channels, &count);
|
||||
if (cids == NULL) {
|
||||
if (count == 0) {
|
||||
return PyList_New(0);
|
||||
|
@ -2848,19 +2962,26 @@ channelsmod_list_all(PyObject *self, PyObject *Py_UNUSED(ignored))
|
|||
ids = NULL;
|
||||
goto finally;
|
||||
}
|
||||
int64_t *cur = cids;
|
||||
struct channel_id_and_info *cur = cids;
|
||||
for (int64_t i=0; i < count; cur++, i++) {
|
||||
PyObject *cidobj = NULL;
|
||||
int err = newchannelid(state->ChannelIDType, *cur, 0,
|
||||
int err = newchannelid(state->ChannelIDType, cur->id, 0,
|
||||
&_globals.channels, 0, 0,
|
||||
(channelid **)&cidobj);
|
||||
if (handle_channel_error(err, self, *cur)) {
|
||||
if (handle_channel_error(err, self, cur->id)) {
|
||||
assert(cidobj == NULL);
|
||||
Py_SETREF(ids, NULL);
|
||||
break;
|
||||
}
|
||||
assert(cidobj != NULL);
|
||||
PyList_SET_ITEM(ids, (Py_ssize_t)i, cidobj);
|
||||
|
||||
PyObject *item = Py_BuildValue("Oi", cidobj, cur->unboundop);
|
||||
Py_DECREF(cidobj);
|
||||
if (item == NULL) {
|
||||
Py_SETREF(ids, NULL);
|
||||
break;
|
||||
}
|
||||
PyList_SET_ITEM(ids, (Py_ssize_t)i, item);
|
||||
}
|
||||
|
||||
finally:
|
||||
|
@ -2942,16 +3063,24 @@ receive end.");
|
|||
static PyObject *
|
||||
channelsmod_send(PyObject *self, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
static char *kwlist[] = {"cid", "obj", "blocking", "timeout", NULL};
|
||||
static char *kwlist[] = {"cid", "obj", "unboundop", "blocking", "timeout",
|
||||
NULL};
|
||||
struct channel_id_converter_data cid_data = {
|
||||
.module = self,
|
||||
};
|
||||
PyObject *obj;
|
||||
int unboundop = UNBOUND_REPLACE;
|
||||
int blocking = 1;
|
||||
PyObject *timeout_obj = NULL;
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&O|$pO:channel_send", kwlist,
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&O|i$pO:channel_send", kwlist,
|
||||
channel_id_converter, &cid_data, &obj,
|
||||
&blocking, &timeout_obj)) {
|
||||
&unboundop, &blocking, &timeout_obj))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
if (!check_unbound(unboundop)) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"unsupported unboundop %d", unboundop);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -2964,10 +3093,10 @@ channelsmod_send(PyObject *self, PyObject *args, PyObject *kwds)
|
|||
/* Queue up the object. */
|
||||
int err = 0;
|
||||
if (blocking) {
|
||||
err = channel_send_wait(&_globals.channels, cid, obj, timeout);
|
||||
err = channel_send_wait(&_globals.channels, cid, obj, unboundop, timeout);
|
||||
}
|
||||
else {
|
||||
err = channel_send(&_globals.channels, cid, obj, NULL);
|
||||
err = channel_send(&_globals.channels, cid, obj, NULL, unboundop);
|
||||
}
|
||||
if (handle_channel_error(err, self, cid)) {
|
||||
return NULL;
|
||||
|
@ -2985,17 +3114,24 @@ By default this waits for the object to be received.");
|
|||
static PyObject *
|
||||
channelsmod_send_buffer(PyObject *self, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
static char *kwlist[] = {"cid", "obj", "blocking", "timeout", NULL};
|
||||
static char *kwlist[] = {"cid", "obj", "unboundop", "blocking", "timeout",
|
||||
NULL};
|
||||
struct channel_id_converter_data cid_data = {
|
||||
.module = self,
|
||||
};
|
||||
PyObject *obj;
|
||||
int unboundop = UNBOUND_REPLACE;
|
||||
int blocking = 1;
|
||||
PyObject *timeout_obj = NULL;
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds,
|
||||
"O&O|$pO:channel_send_buffer", kwlist,
|
||||
"O&O|i$pO:channel_send_buffer", kwlist,
|
||||
channel_id_converter, &cid_data, &obj,
|
||||
&blocking, &timeout_obj)) {
|
||||
&unboundop, &blocking, &timeout_obj)) {
|
||||
return NULL;
|
||||
}
|
||||
if (!check_unbound(unboundop)) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"unsupported unboundop %d", unboundop);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -3013,10 +3149,11 @@ channelsmod_send_buffer(PyObject *self, PyObject *args, PyObject *kwds)
|
|||
/* Queue up the object. */
|
||||
int err = 0;
|
||||
if (blocking) {
|
||||
err = channel_send_wait(&_globals.channels, cid, tempobj, timeout);
|
||||
err = channel_send_wait(
|
||||
&_globals.channels, cid, tempobj, unboundop, timeout);
|
||||
}
|
||||
else {
|
||||
err = channel_send(&_globals.channels, cid, tempobj, NULL);
|
||||
err = channel_send(&_globals.channels, cid, tempobj, NULL, unboundop);
|
||||
}
|
||||
Py_DECREF(tempobj);
|
||||
if (handle_channel_error(err, self, cid)) {
|
||||
|
@ -3048,25 +3185,28 @@ channelsmod_recv(PyObject *self, PyObject *args, PyObject *kwds)
|
|||
cid = cid_data.cid;
|
||||
|
||||
PyObject *obj = NULL;
|
||||
int err = channel_recv(&_globals.channels, cid, &obj);
|
||||
if (handle_channel_error(err, self, cid)) {
|
||||
int unboundop = 0;
|
||||
int err = channel_recv(&_globals.channels, cid, &obj, &unboundop);
|
||||
if (err == ERR_CHANNEL_EMPTY && dflt != NULL) {
|
||||
// Use the default.
|
||||
obj = Py_NewRef(dflt);
|
||||
err = 0;
|
||||
}
|
||||
else if (handle_channel_error(err, self, cid)) {
|
||||
return NULL;
|
||||
}
|
||||
Py_XINCREF(dflt);
|
||||
if (obj == NULL) {
|
||||
// Use the default.
|
||||
if (dflt == NULL) {
|
||||
(void)handle_channel_error(ERR_CHANNEL_EMPTY, self, cid);
|
||||
return NULL;
|
||||
}
|
||||
obj = Py_NewRef(dflt);
|
||||
else if (obj == NULL) {
|
||||
// The item was unbound.
|
||||
return Py_BuildValue("Oi", Py_None, unboundop);
|
||||
}
|
||||
Py_XDECREF(dflt);
|
||||
return obj;
|
||||
|
||||
PyObject *res = Py_BuildValue("OO", obj, Py_None);
|
||||
Py_DECREF(obj);
|
||||
return res;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(channelsmod_recv_doc,
|
||||
"channel_recv(cid, [default]) -> obj\n\
|
||||
"channel_recv(cid, [default]) -> (obj, unboundop)\n\
|
||||
\n\
|
||||
Return a new object from the data at the front of the channel's queue.\n\
|
||||
\n\
|
||||
|
@ -3167,6 +3307,34 @@ Close the channel for the current interpreter. 'send' and 'recv'\n\
|
|||
(bool) may be used to indicate the ends to close. By default both\n\
|
||||
ends are closed. Closing an already closed end is a noop.");
|
||||
|
||||
static PyObject *
|
||||
channelsmod_get_count(PyObject *self, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
static char *kwlist[] = {"cid", NULL};
|
||||
struct channel_id_converter_data cid_data = {
|
||||
.module = self,
|
||||
};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds,
|
||||
"O&:get_count", kwlist,
|
||||
channel_id_converter, &cid_data)) {
|
||||
return NULL;
|
||||
}
|
||||
int64_t cid = cid_data.cid;
|
||||
|
||||
Py_ssize_t count = -1;
|
||||
int err = _channel_get_count(&_globals.channels, cid, &count);
|
||||
if (handle_channel_error(err, self, cid)) {
|
||||
return NULL;
|
||||
}
|
||||
assert(count >= 0);
|
||||
return PyLong_FromSsize_t(count);
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(channelsmod_get_count_doc,
|
||||
"get_count(cid)\n\
|
||||
\n\
|
||||
Return the number of items in the channel.");
|
||||
|
||||
static PyObject *
|
||||
channelsmod_get_info(PyObject *self, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
|
@ -3194,6 +3362,38 @@ PyDoc_STRVAR(channelsmod_get_info_doc,
|
|||
\n\
|
||||
Return details about the channel.");
|
||||
|
||||
static PyObject *
|
||||
channelsmod_get_channel_defaults(PyObject *self, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
static char *kwlist[] = {"cid", NULL};
|
||||
struct channel_id_converter_data cid_data = {
|
||||
.module = self,
|
||||
};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds,
|
||||
"O&:get_channel_defaults", kwlist,
|
||||
channel_id_converter, &cid_data)) {
|
||||
return NULL;
|
||||
}
|
||||
int64_t cid = cid_data.cid;
|
||||
|
||||
PyThread_type_lock mutex = NULL;
|
||||
_channel_state *channel = NULL;
|
||||
int err = _channels_lookup(&_globals.channels, cid, &mutex, &channel);
|
||||
if (handle_channel_error(err, self, cid)) {
|
||||
return NULL;
|
||||
}
|
||||
int unboundop = channel->defaults.unboundop;
|
||||
PyThread_release_lock(mutex);
|
||||
|
||||
PyObject *defaults = Py_BuildValue("i", unboundop);
|
||||
return defaults;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(channelsmod_get_channel_defaults_doc,
|
||||
"get_channel_defaults(cid)\n\
|
||||
\n\
|
||||
Return the channel's default values, set when it was created.");
|
||||
|
||||
static PyObject *
|
||||
channelsmod__channel_id(PyObject *self, PyObject *args, PyObject *kwds)
|
||||
{
|
||||
|
@ -3240,8 +3440,8 @@ channelsmod__register_end_types(PyObject *self, PyObject *args, PyObject *kwds)
|
|||
}
|
||||
|
||||
static PyMethodDef module_functions[] = {
|
||||
{"create", channelsmod_create,
|
||||
METH_NOARGS, channelsmod_create_doc},
|
||||
{"create", _PyCFunction_CAST(channelsmod_create),
|
||||
METH_VARARGS | METH_KEYWORDS, channelsmod_create_doc},
|
||||
{"destroy", _PyCFunction_CAST(channelsmod_destroy),
|
||||
METH_VARARGS | METH_KEYWORDS, channelsmod_destroy_doc},
|
||||
{"list_all", channelsmod_list_all,
|
||||
|
@ -3258,8 +3458,12 @@ static PyMethodDef module_functions[] = {
|
|||
METH_VARARGS | METH_KEYWORDS, channelsmod_close_doc},
|
||||
{"release", _PyCFunction_CAST(channelsmod_release),
|
||||
METH_VARARGS | METH_KEYWORDS, channelsmod_release_doc},
|
||||
{"get_count", _PyCFunction_CAST(channelsmod_get_count),
|
||||
METH_VARARGS | METH_KEYWORDS, channelsmod_get_count_doc},
|
||||
{"get_info", _PyCFunction_CAST(channelsmod_get_info),
|
||||
METH_VARARGS | METH_KEYWORDS, channelsmod_get_info_doc},
|
||||
{"get_channel_defaults", _PyCFunction_CAST(channelsmod_get_channel_defaults),
|
||||
METH_VARARGS | METH_KEYWORDS, channelsmod_get_channel_defaults_doc},
|
||||
{"_channel_id", _PyCFunction_CAST(channelsmod__channel_id),
|
||||
METH_VARARGS | METH_KEYWORDS, NULL},
|
||||
{"_register_end_types", _PyCFunction_CAST(channelsmod__register_end_types),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue