@@ -49,6 +49,9 @@ static PyObject *current_tasks;
4949 all running event loops. {EventLoop: Task} */
5050static PyObject * all_tasks ;
5151
52+ /* An isinstance type cache for the 'is_coroutine()' function. */
53+ static PyObject * iscoroutine_typecache ;
54+
5255
5356typedef enum {
5457 STATE_PENDING ,
@@ -118,6 +121,71 @@ static PyObject* future_new_iter(PyObject *);
118121static inline int future_call_schedule_callbacks (FutureObj * );
119122
120123
124+ static int
125+ _is_coroutine (PyObject * coro )
126+ {
127+ /* 'coro' is not a native coroutine, call asyncio.iscoroutine()
128+ to check if it's another coroutine flavour.
129+
130+ Do this check after 'future_init()'; in case we need to raise
131+ an error, __del__ needs a properly initialized object.
132+ */
133+ PyObject * res = PyObject_CallFunctionObjArgs (
134+ asyncio_iscoroutine_func , coro , NULL );
135+ if (res == NULL ) {
136+ return -1 ;
137+ }
138+
139+ int is_res_true = PyObject_IsTrue (res );
140+ Py_DECREF (res );
141+ if (is_res_true <= 0 ) {
142+ return is_res_true ;
143+ }
144+
145+ if (PySet_Size (iscoroutine_typecache ) < 100 ) {
146+ /* Just in case we don't want to cache more than 100
147+ positive types. That shouldn't ever happen, unless
148+ someone stressing the system on purpose.
149+ */
150+ if (PySet_Add (iscoroutine_typecache , (PyObject * ) Py_TYPE (coro ))) {
151+ return -1 ;
152+ }
153+ }
154+
155+ return 1 ;
156+ }
157+
158+
159+ static inline int
160+ is_coroutine (PyObject * coro )
161+ {
162+ if (PyCoro_CheckExact (coro )) {
163+ return 1 ;
164+ }
165+
166+ /* Check if `type(coro)` is in the cache.
167+ Caching makes is_coroutine() function almost as fast as
168+ PyCoro_CheckExact() for non-native coroutine-like objects
169+ (like coroutines compiled with Cython).
170+
171+ asyncio.iscoroutine() has its own type caching mechanism.
172+ This cache allows us to avoid the cost of even calling
173+ a pure-Python function in 99.9% cases.
174+ */
175+ int has_it = PySet_Contains (
176+ iscoroutine_typecache , (PyObject * ) Py_TYPE (coro ));
177+ if (has_it == 0 ) {
178+ /* type(coro) is not in iscoroutine_typecache */
179+ return _is_coroutine (coro );
180+ }
181+
182+ /* either an error has occured or
183+ type(coro) is in iscoroutine_typecache
184+ */
185+ return has_it ;
186+ }
187+
188+
121189static int
122190get_running_loop (PyObject * * loop )
123191{
@@ -1778,37 +1846,20 @@ static int
17781846_asyncio_Task___init___impl (TaskObj * self , PyObject * coro , PyObject * loop )
17791847/*[clinic end generated code: output=9f24774c2287fc2f input=8d132974b049593e]*/
17801848{
1781- PyObject * res ;
1782-
17831849 if (future_init ((FutureObj * )self , loop )) {
17841850 return -1 ;
17851851 }
17861852
1787- if (!PyCoro_CheckExact (coro )) {
1788- /* 'coro' is not a native coroutine, call asyncio.iscoroutine()
1789- to check if it's another coroutine flavour.
1790-
1791- Do this check after 'future_init()'; in case we need to raise
1792- an error, __del__ needs a properly initialized object.
1793- */
1794- res = PyObject_CallFunctionObjArgs (
1795- asyncio_iscoroutine_func , coro , NULL );
1796- if (res == NULL ) {
1797- return -1 ;
1798- }
1799-
1800- int tmp = PyObject_Not (res );
1801- Py_DECREF (res );
1802- if (tmp < 0 ) {
1803- return -1 ;
1804- }
1805- if (tmp ) {
1806- self -> task_log_destroy_pending = 0 ;
1807- PyErr_Format (PyExc_TypeError ,
1808- "a coroutine was expected, got %R" ,
1809- coro , NULL );
1810- return -1 ;
1811- }
1853+ int is_coro = is_coroutine (coro );
1854+ if (is_coro == -1 ) {
1855+ return -1 ;
1856+ }
1857+ if (is_coro == 0 ) {
1858+ self -> task_log_destroy_pending = 0 ;
1859+ PyErr_Format (PyExc_TypeError ,
1860+ "a coroutine was expected, got %R" ,
1861+ coro , NULL );
1862+ return -1 ;
18121863 }
18131864
18141865 self -> task_fut_waiter = NULL ;
@@ -3007,8 +3058,9 @@ module_free(void *m)
30073058 Py_CLEAR (asyncio_InvalidStateError );
30083059 Py_CLEAR (asyncio_CancelledError );
30093060
3010- Py_CLEAR (current_tasks );
30113061 Py_CLEAR (all_tasks );
3062+ Py_CLEAR (current_tasks );
3063+ Py_CLEAR (iscoroutine_typecache );
30123064
30133065 module_free_freelists ();
30143066}
@@ -3028,6 +3080,11 @@ module_init(void)
30283080 goto fail ;
30293081 }
30303082
3083+ iscoroutine_typecache = PySet_New (NULL );
3084+ if (iscoroutine_typecache == NULL ) {
3085+ goto fail ;
3086+ }
3087+
30313088#define WITH_MOD (NAME ) \
30323089 Py_CLEAR(module); \
30333090 module = PyImport_ImportModule(NAME); \
0 commit comments