Skip to content

Commit 6741d5a

Browse files
gh-76785: Add *Channel.is_closed (gh-110606)
1 parent a89708a commit 6741d5a

File tree

3 files changed

+302
-2
lines changed

3 files changed

+302
-2
lines changed

Lib/test/support/interpreters.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,14 @@ def __eq__(self, other):
161161
def id(self):
162162
return self._id
163163

164+
@property
165+
def _info(self):
166+
return _channels.get_info(self._id)
167+
168+
@property
169+
def is_closed(self):
170+
return self._info.closed
171+
164172

165173
_NOT_SET = object()
166174

@@ -213,6 +221,11 @@ class SendChannel(_ChannelEnd):
213221

214222
_end = 'send'
215223

224+
@property
225+
def is_closed(self):
226+
info = self._info
227+
return info.closed or info.closing
228+
216229
def send(self, obj, timeout=None):
217230
"""Send the object (i.e. its data) to the channel's receiving end.
218231

Lib/test/test_interpreters.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,19 @@ def test_shareable(self):
850850
self.assertEqual(rch2, rch)
851851
self.assertEqual(sch2, sch)
852852

853+
def test_is_closed(self):
854+
rch, sch = interpreters.create_channel()
855+
rbefore = rch.is_closed
856+
sbefore = sch.is_closed
857+
rch.close()
858+
rafter = rch.is_closed
859+
safter = sch.is_closed
860+
861+
self.assertFalse(rbefore)
862+
self.assertFalse(sbefore)
863+
self.assertTrue(rafter)
864+
self.assertTrue(safter)
865+
853866

854867
class TestRecvChannelAttrs(TestBase):
855868

Modules/_xxinterpchannelsmodule.c

Lines changed: 276 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,8 @@ static PyTypeObject *
223223
add_new_type(PyObject *mod, PyType_Spec *spec, crossinterpdatafunc shared,
224224
struct xid_class_registry *classes)
225225
{
226-
PyTypeObject *cls = (PyTypeObject *)PyType_FromMetaclass(
227-
NULL, mod, spec, NULL);
226+
PyTypeObject *cls = (PyTypeObject *)PyType_FromModuleAndSpec(
227+
mod, spec, NULL);
228228
if (cls == NULL) {
229229
return NULL;
230230
}
@@ -402,6 +402,7 @@ typedef struct {
402402
PyTypeObject *recv_channel_type;
403403

404404
/* heap types */
405+
PyTypeObject *ChannelInfoType;
405406
PyTypeObject *ChannelIDType;
406407
PyTypeObject *XIBufferViewType;
407408

@@ -441,6 +442,7 @@ static int
441442
traverse_module_state(module_state *state, visitproc visit, void *arg)
442443
{
443444
/* heap types */
445+
Py_VISIT(state->ChannelInfoType);
444446
Py_VISIT(state->ChannelIDType);
445447
Py_VISIT(state->XIBufferViewType);
446448

@@ -457,10 +459,12 @@ traverse_module_state(module_state *state, visitproc visit, void *arg)
457459
static int
458460
clear_module_state(module_state *state)
459461
{
462+
/* external types */
460463
Py_CLEAR(state->send_channel_type);
461464
Py_CLEAR(state->recv_channel_type);
462465

463466
/* heap types */
467+
Py_CLEAR(state->ChannelInfoType);
464468
if (state->ChannelIDType != NULL) {
465469
(void)_PyCrossInterpreterData_UnregisterClass(state->ChannelIDType);
466470
}
@@ -2088,6 +2092,236 @@ channel_is_associated(_channels *channels, int64_t cid, int64_t interpid,
20882092
}
20892093

20902094

2095+
/* channel info */
2096+
2097+
struct channel_info {
2098+
struct {
2099+
// 1: closed; -1: closing
2100+
int closed;
2101+
struct {
2102+
Py_ssize_t nsend_only; // not released
2103+
Py_ssize_t nsend_only_released;
2104+
Py_ssize_t nrecv_only; // not released
2105+
Py_ssize_t nrecv_only_released;
2106+
Py_ssize_t nboth; // not released
2107+
Py_ssize_t nboth_released;
2108+
Py_ssize_t nboth_send_released;
2109+
Py_ssize_t nboth_recv_released;
2110+
} all;
2111+
struct {
2112+
// 1: associated; -1: released
2113+
int send;
2114+
int recv;
2115+
} cur;
2116+
} status;
2117+
Py_ssize_t count;
2118+
};
2119+
2120+
static int
2121+
_channel_get_info(_channels *channels, int64_t cid, struct channel_info *info)
2122+
{
2123+
int err = 0;
2124+
*info = (struct channel_info){0};
2125+
2126+
// Get the current interpreter.
2127+
PyInterpreterState *interp = _get_current_interp();
2128+
if (interp == NULL) {
2129+
return -1;
2130+
}
2131+
Py_ssize_t interpid = PyInterpreterState_GetID(interp);
2132+
2133+
// Hold the global lock until we're done.
2134+
PyThread_acquire_lock(channels->mutex, WAIT_LOCK);
2135+
2136+
// Find the channel.
2137+
_channelref *ref = _channelref_find(channels->head, cid, NULL);
2138+
if (ref == NULL) {
2139+
err = ERR_CHANNEL_NOT_FOUND;
2140+
goto finally;
2141+
}
2142+
_channel_state *chan = ref->chan;
2143+
2144+
// Check if open.
2145+
if (chan == NULL) {
2146+
info->status.closed = 1;
2147+
goto finally;
2148+
}
2149+
if (!chan->open) {
2150+
assert(chan->queue->count == 0);
2151+
info->status.closed = 1;
2152+
goto finally;
2153+
}
2154+
if (chan->closing != NULL) {
2155+
assert(chan->queue->count > 0);
2156+
info->status.closed = -1;
2157+
}
2158+
else {
2159+
info->status.closed = 0;
2160+
}
2161+
2162+
// Get the number of queued objects.
2163+
info->count = chan->queue->count;
2164+
2165+
// Get the ends statuses.
2166+
assert(info->status.cur.send == 0);
2167+
assert(info->status.cur.recv == 0);
2168+
_channelend *send = chan->ends->send;
2169+
while (send != NULL) {
2170+
if (send->interpid == interpid) {
2171+
info->status.cur.send = send->open ? 1 : -1;
2172+
}
2173+
2174+
if (send->open) {
2175+
info->status.all.nsend_only += 1;
2176+
}
2177+
else {
2178+
info->status.all.nsend_only_released += 1;
2179+
}
2180+
send = send->next;
2181+
}
2182+
_channelend *recv = chan->ends->recv;
2183+
while (recv != NULL) {
2184+
if (recv->interpid == interpid) {
2185+
info->status.cur.recv = recv->open ? 1 : -1;
2186+
}
2187+
2188+
// XXX This is O(n*n). Why do we have 2 linked lists?
2189+
_channelend *send = chan->ends->send;
2190+
while (send != NULL) {
2191+
if (send->interpid == recv->interpid) {
2192+
break;
2193+
}
2194+
send = send->next;
2195+
}
2196+
if (send == NULL) {
2197+
if (recv->open) {
2198+
info->status.all.nrecv_only += 1;
2199+
}
2200+
else {
2201+
info->status.all.nrecv_only_released += 1;
2202+
}
2203+
}
2204+
else {
2205+
if (recv->open) {
2206+
if (send->open) {
2207+
info->status.all.nboth += 1;
2208+
info->status.all.nsend_only -= 1;
2209+
}
2210+
else {
2211+
info->status.all.nboth_recv_released += 1;
2212+
info->status.all.nsend_only_released -= 1;
2213+
}
2214+
}
2215+
else {
2216+
if (send->open) {
2217+
info->status.all.nboth_send_released += 1;
2218+
info->status.all.nsend_only -= 1;
2219+
}
2220+
else {
2221+
info->status.all.nboth_released += 1;
2222+
info->status.all.nsend_only_released -= 1;
2223+
}
2224+
}
2225+
}
2226+
recv = recv->next;
2227+
}
2228+
2229+
finally:
2230+
PyThread_release_lock(channels->mutex);
2231+
return err;
2232+
}
2233+
2234+
PyDoc_STRVAR(channel_info_doc,
2235+
"ChannelInfo\n\
2236+
\n\
2237+
A named tuple of a channel's state.");
2238+
2239+
static PyStructSequence_Field channel_info_fields[] = {
2240+
{"open", "both ends are open"},
2241+
{"closing", "send is closed, recv is non-empty"},
2242+
{"closed", "both ends are closed"},
2243+
{"count", "queued objects"},
2244+
2245+
{"num_interp_send", "interpreters bound to the send end"},
2246+
{"num_interp_send_released",
2247+
"interpreters bound to the send end and released"},
2248+
2249+
{"num_interp_recv", "interpreters bound to the send end"},
2250+
{"num_interp_recv_released",
2251+
"interpreters bound to the send end and released"},
2252+
2253+
{"num_interp_both", "interpreters bound to both ends"},
2254+
{"num_interp_both_released",
2255+
"interpreters bound to both ends and released_from_both"},
2256+
{"num_interp_both_send_released",
2257+
"interpreters bound to both ends and released_from_the send end"},
2258+
{"num_interp_both_recv_released",
2259+
"interpreters bound to both ends and released_from_the recv end"},
2260+
2261+
{"send_associated", "current interpreter is bound to the send end"},
2262+
{"send_released", "current interpreter *was* bound to the send end"},
2263+
{"recv_associated", "current interpreter is bound to the recv end"},
2264+
{"recv_released", "current interpreter *was* bound to the recv end"},
2265+
{0}
2266+
};
2267+
2268+
static PyStructSequence_Desc channel_info_desc = {
2269+
.name = MODULE_NAME ".ChannelInfo",
2270+
.doc = channel_info_doc,
2271+
.fields = channel_info_fields,
2272+
.n_in_sequence = 8,
2273+
};
2274+
2275+
static PyObject *
2276+
new_channel_info(PyObject *mod, struct channel_info *info)
2277+
{
2278+
module_state *state = get_module_state(mod);
2279+
if (state == NULL) {
2280+
return NULL;
2281+
}
2282+
2283+
assert(state->ChannelInfoType != NULL);
2284+
PyObject *self = PyStructSequence_New(state->ChannelInfoType);
2285+
if (self == NULL) {
2286+
return NULL;
2287+
}
2288+
2289+
int pos = 0;
2290+
#define SET_BOOL(val) \
2291+
PyStructSequence_SET_ITEM(self, pos++, \
2292+
Py_NewRef(val ? Py_True : Py_False))
2293+
#define SET_COUNT(val) \
2294+
do { \
2295+
PyObject *obj = PyLong_FromLongLong(val); \
2296+
if (obj == NULL) { \
2297+
Py_CLEAR(info); \
2298+
return NULL; \
2299+
} \
2300+
PyStructSequence_SET_ITEM(self, pos++, obj); \
2301+
} while(0)
2302+
SET_BOOL(info->status.closed == 0);
2303+
SET_BOOL(info->status.closed == -1);
2304+
SET_BOOL(info->status.closed == 1);
2305+
SET_COUNT(info->count);
2306+
SET_COUNT(info->status.all.nsend_only);
2307+
SET_COUNT(info->status.all.nsend_only_released);
2308+
SET_COUNT(info->status.all.nrecv_only);
2309+
SET_COUNT(info->status.all.nrecv_only_released);
2310+
SET_COUNT(info->status.all.nboth);
2311+
SET_COUNT(info->status.all.nboth_released);
2312+
SET_COUNT(info->status.all.nboth_send_released);
2313+
SET_COUNT(info->status.all.nboth_recv_released);
2314+
SET_BOOL(info->status.cur.send == 1);
2315+
SET_BOOL(info->status.cur.send == -1);
2316+
SET_BOOL(info->status.cur.recv == 1);
2317+
SET_BOOL(info->status.cur.recv == -1);
2318+
#undef SET_COUNT
2319+
#undef SET_BOOL
2320+
assert(!PyErr_Occurred());
2321+
return self;
2322+
}
2323+
2324+
20912325
/* ChannelID class */
20922326

20932327
typedef struct channelid {
@@ -3079,6 +3313,33 @@ Close the channel for the current interpreter. 'send' and 'recv'\n\
30793313
(bool) may be used to indicate the ends to close. By default both\n\
30803314
ends are closed. Closing an already closed end is a noop.");
30813315

3316+
static PyObject *
3317+
channelsmod_get_info(PyObject *self, PyObject *args, PyObject *kwds)
3318+
{
3319+
static char *kwlist[] = {"cid", NULL};
3320+
struct channel_id_converter_data cid_data = {
3321+
.module = self,
3322+
};
3323+
if (!PyArg_ParseTupleAndKeywords(args, kwds,
3324+
"O&:_get_info", kwlist,
3325+
channel_id_converter, &cid_data)) {
3326+
return NULL;
3327+
}
3328+
int64_t cid = cid_data.cid;
3329+
3330+
struct channel_info info;
3331+
int err = _channel_get_info(&_globals.channels, cid, &info);
3332+
if (handle_channel_error(err, self, cid)) {
3333+
return NULL;
3334+
}
3335+
return new_channel_info(self, &info);
3336+
}
3337+
3338+
PyDoc_STRVAR(channelsmod_get_info_doc,
3339+
"get_info(cid)\n\
3340+
\n\
3341+
Return details about the channel.");
3342+
30823343
static PyObject *
30833344
channelsmod__channel_id(PyObject *self, PyObject *args, PyObject *kwds)
30843345
{
@@ -3143,6 +3404,8 @@ static PyMethodDef module_functions[] = {
31433404
METH_VARARGS | METH_KEYWORDS, channelsmod_close_doc},
31443405
{"release", _PyCFunction_CAST(channelsmod_release),
31453406
METH_VARARGS | METH_KEYWORDS, channelsmod_release_doc},
3407+
{"get_info", _PyCFunction_CAST(channelsmod_get_info),
3408+
METH_VARARGS | METH_KEYWORDS, channelsmod_get_info_doc},
31463409
{"_channel_id", _PyCFunction_CAST(channelsmod__channel_id),
31473410
METH_VARARGS | METH_KEYWORDS, NULL},
31483411
{"_register_end_types", _PyCFunction_CAST(channelsmod__register_end_types),
@@ -3179,19 +3442,30 @@ module_exec(PyObject *mod)
31793442

31803443
/* Add other types */
31813444

3445+
// ChannelInfo
3446+
state->ChannelInfoType = PyStructSequence_NewType(&channel_info_desc);
3447+
if (state->ChannelInfoType == NULL) {
3448+
goto error;
3449+
}
3450+
if (PyModule_AddType(mod, state->ChannelInfoType) < 0) {
3451+
goto error;
3452+
}
3453+
31823454
// ChannelID
31833455
state->ChannelIDType = add_new_type(
31843456
mod, &channelid_typespec, _channelid_shared, xid_classes);
31853457
if (state->ChannelIDType == NULL) {
31863458
goto error;
31873459
}
31883460

3461+
// XIBufferView
31893462
state->XIBufferViewType = add_new_type(mod, &XIBufferViewType_spec, NULL,
31903463
xid_classes);
31913464
if (state->XIBufferViewType == NULL) {
31923465
goto error;
31933466
}
31943467

3468+
// Register external types.
31953469
if (register_builtin_xid_types(xid_classes) < 0) {
31963470
goto error;
31973471
}

0 commit comments

Comments
 (0)