Skip to content

bpo-42085: Introduce dedicated entry in PyAsyncMethods for sending values #22780

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Nov 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Doc/c-api/typeobj.rst
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ sub-slots
+---------------------------------------------------------+-----------------------------------+--------------+
| :c:member:`~PyAsyncMethods.am_anext` | :c:type:`unaryfunc` | __anext__ |
+---------------------------------------------------------+-----------------------------------+--------------+
| :c:member:`~PyAsyncMethods.am_send` | :c:type:`sendfunc` | |
+---------------------------------------------------------+-----------------------------------+--------------+
| |
+---------------------------------------------------------+-----------------------------------+--------------+
| :c:member:`~PyNumberMethods.nb_add` | :c:type:`binaryfunc` | __add__ |
Expand Down Expand Up @@ -2303,6 +2305,7 @@ Async Object Structures
unaryfunc am_await;
unaryfunc am_aiter;
unaryfunc am_anext;
sendfunc am_send;
} PyAsyncMethods;

.. c:member:: unaryfunc PyAsyncMethods.am_await
Expand Down Expand Up @@ -2336,6 +2339,15 @@ Async Object Structures
Must return an :term:`awaitable` object. See :meth:`__anext__` for details.
This slot may be set to ``NULL``.

.. c:member:: sendfunc PyAsyncMethods.am_send

The signature of this function is::

PySendResult am_send(PyObject *self, PyObject *arg, PyObject **result);

See :c:func:`PyIter_Send` for details.
This slot may be set to ``NULL``.


.. _slot-typedefs:

Expand Down Expand Up @@ -2431,6 +2443,10 @@ Slot Type typedefs

.. c:type:: PyObject *(*binaryfunc)(PyObject *, PyObject *)

.. c:type:: PySendResult (*sendfunc)(PyObject *, PyObject *, PyObject **)

See :c:member:`~PyAsyncMethods.am_send`.

.. c:type:: PyObject *(*ternaryfunc)(PyObject *, PyObject *, PyObject *)

.. c:type:: PyObject *(*ssizeargfunc)(PyObject *, Py_ssize_t)
Expand Down
5 changes: 0 additions & 5 deletions Include/abstract.h
Original file line number Diff line number Diff line change
Expand Up @@ -339,11 +339,6 @@ PyAPI_FUNC(int) PyIter_Check(PyObject *);
PyAPI_FUNC(PyObject *) PyIter_Next(PyObject *);

#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030A0000
typedef enum {
PYGEN_RETURN = 0,
PYGEN_ERROR = -1,
PYGEN_NEXT = 1,
} PySendResult;

/* Takes generator, coroutine or iterator object and sends the value into it.
Returns:
Expand Down
3 changes: 3 additions & 0 deletions Include/cpython/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,13 @@ typedef struct {
objobjargproc mp_ass_subscript;
} PyMappingMethods;

typedef PySendResult (*sendfunc)(PyObject *iter, PyObject *value, PyObject **result);

typedef struct {
unaryfunc am_await;
unaryfunc am_aiter;
unaryfunc am_anext;
sendfunc am_send;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not we add corresponding Py_TPFLAGS_HAVE_ flag for binary compatibility?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typeslots.h should be updated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not we add corresponding Py_TPFLAGS_HAVE_ flag for binary compatibility?

Do we support extensions compiled for higher versions of Python in lower versions of Python? If I compile something for 3.10 I wouldn't expect it to work in 3.9.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we don't need a Py_TPFLAGS_HAVE_ flag and I don't remember adding one for the as_async slot when we implemented PEP 492.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if you compile something for 3.9 it is expected to work in 3.10 and do not read uninitialized slot.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps there is a bug in the PEP 492 implementation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, I see now, thanks for explaining! Yeah, in this case we do need a flag.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps there is a bug in the PEP 492 implementation.

Well, I don't think we need to do anything now that as_async has been there since 3.5. But looking back, maybe we should have added a flag.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We used an existing slot for PEP-492, so the ABI did not change.
I don't think we necessarily need a new flag. See https://bugs.python.org/issue32388

} PyAsyncMethods;

typedef struct {
Expand Down
14 changes: 14 additions & 0 deletions Include/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,11 @@ given type object has a specified feature.
/* Type is abstract and cannot be instantiated */
#define Py_TPFLAGS_IS_ABSTRACT (1UL << 20)

#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030A0000
/* Type has am_send entry in tp_as_async slot */
#define Py_TPFLAGS_HAVE_AM_SEND (1UL << 21)
#endif

/* These flags are used to determine if a type is a subclass. */
#define Py_TPFLAGS_LONG_SUBCLASS (1UL << 24)
#define Py_TPFLAGS_LIST_SUBCLASS (1UL << 25)
Expand Down Expand Up @@ -557,6 +562,15 @@ PyAPI_DATA(PyObject) _Py_NotImplementedStruct; /* Don't use this directly */
#define Py_GT 4
#define Py_GE 5

#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030A0000
/* Result of calling PyIter_Send */
typedef enum {
PYGEN_RETURN = 0,
PYGEN_ERROR = -1,
PYGEN_NEXT = 1,
} PySendResult;
#endif

/*
* Macro for implementing rich comparisons
*
Expand Down
4 changes: 4 additions & 0 deletions Include/typeslots.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,7 @@
/* New in 3.5 */
#define Py_tp_finalize 80
#endif
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030A0000
/* New in 3.10 */
#define Py_am_send 81
#endif
2 changes: 1 addition & 1 deletion Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -1340,7 +1340,7 @@ def delx(self): del self.__x
check(int, s)
# class
s = vsize(fmt + # PyTypeObject
'3P' # PyAsyncMethods
'4P' # PyAsyncMethods
'36P' # PyNumberMethods
'3P' # PyMappingMethods
'10P' # PySequenceMethods
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add dedicated entry to PyAsyncMethods for sending values
56 changes: 45 additions & 11 deletions Modules/_asynciomodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1484,7 +1484,8 @@ future_cls_getitem(PyObject *cls, PyObject *type)
static PyAsyncMethods FutureType_as_async = {
(unaryfunc)future_new_iter, /* am_await */
0, /* am_aiter */
0 /* am_anext */
0, /* am_anext */
0, /* am_send */
};

static PyMethodDef FutureType_methods[] = {
Expand Down Expand Up @@ -1602,37 +1603,60 @@ FutureIter_dealloc(futureiterobject *it)
}
}

static PyObject *
FutureIter_iternext(futureiterobject *it)
static PySendResult
FutureIter_am_send(futureiterobject *it,
PyObject *Py_UNUSED(arg),
PyObject **result)
{
/* arg is unused, see the comment on FutureIter_send for clarification */

PyObject *res;
FutureObj *fut = it->future;

*result = NULL;
if (fut == NULL) {
return NULL;
return PYGEN_ERROR;
}

if (fut->fut_state == STATE_PENDING) {
if (!fut->fut_blocking) {
fut->fut_blocking = 1;
Py_INCREF(fut);
return (PyObject *)fut;
*result = (PyObject *)fut;
return PYGEN_NEXT;
}
PyErr_SetString(PyExc_RuntimeError,
"await wasn't used with future");
return NULL;
return PYGEN_ERROR;
}

it->future = NULL;
res = _asyncio_Future_result_impl(fut);
if (res != NULL) {
/* The result of the Future is not an exception. */
(void)_PyGen_SetStopIterationValue(res);
Py_DECREF(res);
*result = res;
return PYGEN_RETURN;
}

Py_DECREF(fut);
return NULL;
return PYGEN_ERROR;
}

static PyObject *
FutureIter_iternext(futureiterobject *it)
{
PyObject *result;
switch (FutureIter_am_send(it, Py_None, &result)) {
case PYGEN_RETURN:
(void)_PyGen_SetStopIterationValue(result);
Py_DECREF(result);
return NULL;
case PYGEN_NEXT:
return result;
case PYGEN_ERROR:
return NULL;
default:
Py_UNREACHABLE();
}
}

static PyObject *
Expand Down Expand Up @@ -1721,14 +1745,24 @@ static PyMethodDef FutureIter_methods[] = {
{NULL, NULL} /* Sentinel */
};

static PyAsyncMethods FutureIterType_as_async = {
0, /* am_await */
0, /* am_aiter */
0, /* am_anext */
(sendfunc)FutureIter_am_send, /* am_send */
};


static PyTypeObject FutureIterType = {
PyVarObject_HEAD_INIT(NULL, 0)
"_asyncio.FutureIter",
.tp_basicsize = sizeof(futureiterobject),
.tp_itemsize = 0,
.tp_dealloc = (destructor)FutureIter_dealloc,
.tp_as_async = &FutureIterType_as_async,
.tp_getattro = PyObject_GenericGetAttr,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_HAVE_AM_SEND,
.tp_traverse = (traverseproc)FutureIter_traverse,
.tp_iter = PyObject_SelfIter,
.tp_iternext = (iternextfunc)FutureIter_iternext,
Expand Down
3 changes: 2 additions & 1 deletion Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -6142,7 +6142,8 @@ awaitObject_await(awaitObject *ao)
static PyAsyncMethods awaitType_as_async = {
(unaryfunc)awaitObject_await, /* am_await */
0, /* am_aiter */
0 /* am_anext */
0, /* am_anext */
0, /* am_send */
};


Expand Down
26 changes: 26 additions & 0 deletions Objects/abstract.c
Original file line number Diff line number Diff line change
Expand Up @@ -2669,6 +2669,32 @@ PyIter_Next(PyObject *iter)
return result;
}

PySendResult
PyIter_Send(PyObject *iter, PyObject *arg, PyObject **result)
{
_Py_IDENTIFIER(send);
assert(arg != NULL);
assert(result != NULL);
if (PyType_HasFeature(Py_TYPE(iter), Py_TPFLAGS_HAVE_AM_SEND)) {
assert (Py_TYPE(iter)->tp_as_async != NULL);
assert (Py_TYPE(iter)->tp_as_async->am_send != NULL);
return Py_TYPE(iter)->tp_as_async->am_send(iter, arg, result);
}
if (arg == Py_None && PyIter_Check(iter)) {
*result = Py_TYPE(iter)->tp_iternext(iter);
}
else {
*result = _PyObject_CallMethodIdOneArg(iter, &PyId_send, arg);
}
if (*result != NULL) {
return PYGEN_NEXT;
}
if (_PyGen_FetchStopIterationValue(result) == 0) {
return PYGEN_RETURN;
}
return PYGEN_ERROR;
}

/*
* Flatten a sequence of bytes() objects into a C array of
* NULL terminated string pointers with a NULL char* terminating the array.
Expand Down
57 changes: 26 additions & 31 deletions Objects/genobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -268,30 +268,10 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
return result ? PYGEN_RETURN : PYGEN_ERROR;
}

PySendResult
PyIter_Send(PyObject *iter, PyObject *arg, PyObject **result)
static PySendResult
PyGen_am_send(PyGenObject *gen, PyObject *arg, PyObject **result)
{
_Py_IDENTIFIER(send);
assert(arg != NULL);
assert(result != NULL);

if (PyGen_CheckExact(iter) || PyCoro_CheckExact(iter)) {
return gen_send_ex2((PyGenObject *)iter, arg, result, 0, 0);
}

if (arg == Py_None && PyIter_Check(iter)) {
*result = Py_TYPE(iter)->tp_iternext(iter);
}
else {
*result = _PyObject_CallMethodIdOneArg(iter, &PyId_send, arg);
}
if (*result != NULL) {
return PYGEN_NEXT;
}
if (_PyGen_FetchStopIterationValue(result) == 0) {
return PYGEN_RETURN;
}
return PYGEN_ERROR;
return gen_send_ex2(gen, arg, result, 0, 0);
}

static PyObject *
Expand Down Expand Up @@ -788,6 +768,14 @@ static PyMethodDef gen_methods[] = {
{NULL, NULL} /* Sentinel */
};

static PyAsyncMethods gen_as_async = {
0, /* am_await */
0, /* am_aiter */
0, /* am_anext */
(sendfunc)PyGen_am_send, /* am_send */
};


PyTypeObject PyGen_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"generator", /* tp_name */
Expand All @@ -798,7 +786,7 @@ PyTypeObject PyGen_Type = {
0, /* tp_vectorcall_offset */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_as_async */
&gen_as_async, /* tp_as_async */
(reprfunc)gen_repr, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
Expand All @@ -809,7 +797,8 @@ PyTypeObject PyGen_Type = {
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_HAVE_AM_SEND, /* tp_flags */
0, /* tp_doc */
(traverseproc)gen_traverse, /* tp_traverse */
0, /* tp_clear */
Expand Down Expand Up @@ -1031,7 +1020,8 @@ static PyMethodDef coro_methods[] = {
static PyAsyncMethods coro_as_async = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, with this PR regular generators no longer benefit from the faster PyIter_Send. I think we should implement am_send for regular generators too.

(unaryfunc)coro_await, /* am_await */
0, /* am_aiter */
0 /* am_anext */
0, /* am_anext */
(sendfunc)PyGen_am_send, /* am_send */
};

PyTypeObject PyCoro_Type = {
Expand All @@ -1055,7 +1045,8 @@ PyTypeObject PyCoro_Type = {
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_HAVE_AM_SEND, /* tp_flags */
0, /* tp_doc */
(traverseproc)gen_traverse, /* tp_traverse */
0, /* tp_clear */
Expand Down Expand Up @@ -1413,7 +1404,8 @@ static PyMethodDef async_gen_methods[] = {
static PyAsyncMethods async_gen_as_async = {
0, /* am_await */
PyObject_SelfIter, /* am_aiter */
(unaryfunc)async_gen_anext /* am_anext */
(unaryfunc)async_gen_anext, /* am_anext */
(sendfunc)PyGen_am_send, /* am_send */
};


Expand All @@ -1438,7 +1430,8 @@ PyTypeObject PyAsyncGen_Type = {
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_HAVE_AM_SEND, /* tp_flags */
0, /* tp_doc */
(traverseproc)async_gen_traverse, /* tp_traverse */
0, /* tp_clear */
Expand Down Expand Up @@ -1676,7 +1669,8 @@ static PyMethodDef async_gen_asend_methods[] = {
static PyAsyncMethods async_gen_asend_as_async = {
PyObject_SelfIter, /* am_await */
0, /* am_aiter */
0 /* am_anext */
0, /* am_anext */
0, /* am_send */
};


Expand Down Expand Up @@ -2084,7 +2078,8 @@ static PyMethodDef async_gen_athrow_methods[] = {
static PyAsyncMethods async_gen_athrow_as_async = {
PyObject_SelfIter, /* am_await */
0, /* am_aiter */
0 /* am_anext */
0, /* am_anext */
0, /* am_send */
};


Expand Down
Loading