Skip to content

Commit 5b9bebf

Browse files
committed
Deal with controller failures in the init path
It is very difficult to tell in the chain from as_async through the other thread, the tp_new call and all the intermediate CPython APIs when failure occurred and who cleans up the mess. This finally gets everything right by making the boxed_call doing the init free the connection unless tp_new succeeds.
1 parent fcec60f commit 5b9bebf

2 files changed

Lines changed: 14 additions & 8 deletions

File tree

src/async.c

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ typedef struct BoxedCall
3131
PyObject *connection;
3232
PyObject *args;
3333
PyObject *kwargs;
34+
int call_success;
3435
} ConnectionInit;
3536

3637
/* note this must be the largest member of the union because
@@ -81,6 +82,13 @@ BoxedCall_clear(PyObject *self_)
8182
Py_DECREF(self->ConnectionInit.connection);
8283
Py_DECREF(self->ConnectionInit.args);
8384
Py_XDECREF(self->ConnectionInit.kwargs);
85+
if (!self->ConnectionInit.call_success)
86+
{
87+
/* this causes close on init failure so threads don't get leaked
88+
because our original code in as_async can't know about
89+
downstream failures */
90+
Py_DECREF(self->ConnectionInit.connection);
91+
}
8492
break;
8593

8694
case FastCallWithKeywords: {
@@ -135,8 +143,11 @@ BoxedCall_internal_call(BoxedCall *self)
135143
if (0
136144
== Py_TYPE(self->ConnectionInit.connection)
137145
->tp_init(self->ConnectionInit.connection, self->ConnectionInit.args, self->ConnectionInit.kwargs))
146+
{
138147
result = Py_NewRef(self->ConnectionInit.connection);
139-
break;
148+
self->ConnectionInit.call_success = 1;
149+
}
150+
break;
140151
case FastCallWithKeywords:
141152
result = self->FastCallWithKeywords.function(
142153
self->FastCallWithKeywords.object, self->FastCallWithKeywords.fast_args + 1,
@@ -165,10 +176,6 @@ BoxedCall_internal_call(BoxedCall *self)
165176
if (!result && PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_StopAsyncIteration)
166177
&& !PyErr_ExceptionMatches(PyExc_StopIteration))
167178
{
168-
if (self->call_type == ConnectionInit)
169-
/* this causes close on init failure so threads don't get leaked */
170-
Py_DECREF(self->ConnectionInit.connection);
171-
172179
AddTraceBackHere(__FILE__, __LINE__, "apsw.aio.BoxedCall.__call__", "{s:i}", "call_type", (int)self->call_type);
173180
}
174181

src/connection.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,7 @@ Connection_as_async(PyObject *klass_, PyObject *args, PyObject *kwargs)
808808
boxed_call->ConnectionInit.connection = Py_NewRef((PyObject *)connection);
809809
boxed_call->ConnectionInit.args = Py_NewRef(args);
810810
boxed_call->ConnectionInit.kwargs = Py_XNewRef(kwargs);
811+
boxed_call->ConnectionInit.call_success = 0;
811812
connection->async_controller = NULL;
812813

813814
if(!PyContextVar_Get(async_controller_context_var, NULL, &connection->async_controller))
@@ -827,17 +828,15 @@ Connection_as_async(PyObject *klass_, PyObject *args, PyObject *kwargs)
827828
if (!connection->async_controller)
828829
goto error;
829830

830-
PyObject *result = async_send_boxed_call((PyObject *)connection, (PyObject*)boxed_call);
831+
PyObject *result = async_send_boxed_call((PyObject *)connection, (PyObject *)boxed_call);
831832
/* send_boxed took the reference */
832833
boxed_call = NULL;
833-
connection = NULL;
834834

835835
if (result)
836836
return result;
837837

838838
error:
839839
assert(PyErr_Occurred());
840-
Py_XDECREF((PyObject *)connection);
841840
Py_XDECREF(boxed_call);
842841

843842
return NULL;

0 commit comments

Comments
 (0)