77#include "io.h"
88
99#include <aws/common/array_list.h>
10+ #include <aws/common/ref_count.h>
1011#include <aws/http/connection.h>
1112#include <aws/http/proxy.h>
1213#include <aws/http/request_response.h>
@@ -16,20 +17,22 @@ static const char *s_capsule_name_http_connection = "aws_http_connection";
1617
1718/**
1819 * Lifetime notes:
19- * - If connect() reports immediate failure, binding can be destroyed.
20- * - If on_connection_setup reports failure, binding can be destroyed.
21- * - Otherwise, binding cannot be destroyed until BOTH release() has been called AND on_connection_shutdown has fired.
20+ * - Binding starts with ref_count=1 (owned by whoever will destroy on failure, or by the capsule on success).
21+ * - If on_connection_setup succeeds, acquire another refcount. The additional ref is for the connection
22+ * thread to invoke callbacks (e.g. s_on_connection_shutdown) with a valid binding. The shutdown callback
23+ * is the last callback invoked by the connection thread, so it releases this ref.
24+ * - The last release (ref_count 1→0) calls s_connection_destroy().
2225 */
2326struct http_connection_binding {
2427 struct aws_http_connection * native ;
2528 /* Reference to python object that reference to other related python object to keep it alive */
2629 PyObject * py_core ;
2730
28- bool release_called ;
29- bool shutdown_called ;
31+ struct aws_ref_count ref_count ;
3032};
3133
32- static void s_connection_destroy (struct http_connection_binding * connection ) {
34+ static void s_connection_destroy (void * user_data ) {
35+ struct http_connection_binding * connection = user_data ;
3336 Py_XDECREF (connection -> py_core );
3437
3538 aws_mem_release (aws_py_get_allocator (), connection );
@@ -40,38 +43,23 @@ struct aws_http_connection *aws_py_get_http_connection(PyObject *connection) {
4043 connection , s_capsule_name_http_connection , "HttpConnectionBase" , http_connection_binding );
4144}
4245
43- static void s_connection_release (struct http_connection_binding * connection ) {
44- AWS_FATAL_ASSERT (!connection -> release_called );
45- connection -> release_called = true;
46-
47- bool destroy_after_release = connection -> shutdown_called ;
46+ static void s_connection_capsule_destructor (PyObject * capsule ) {
47+ struct http_connection_binding * connection = PyCapsule_GetPointer (capsule , s_capsule_name_http_connection );
4848
4949 aws_http_connection_release (connection -> native );
5050
51- if (destroy_after_release ) {
52- s_connection_destroy (connection );
53- }
54- }
55-
56- static void s_connection_capsule_destructor (PyObject * capsule ) {
57- struct http_connection_binding * connection = PyCapsule_GetPointer (capsule , s_capsule_name_http_connection );
58- s_connection_release (connection );
51+ aws_ref_count_release (& connection -> ref_count );
5952}
6053
6154static void s_on_connection_shutdown (struct aws_http_connection * native_connection , int error_code , void * user_data ) {
6255 (void )native_connection ;
6356 struct http_connection_binding * connection = user_data ;
64- AWS_FATAL_ASSERT (!connection -> shutdown_called );
6557
6658 PyGILState_STATE state ;
6759 if (aws_py_gilstate_ensure (& state )) {
6860 return ; /* Python has shut down. Nothing matters anymore, but don't crash */
6961 }
7062
71- connection -> shutdown_called = true;
72-
73- bool destroy_after_shutdown = connection -> release_called ;
74-
7563 /* Invoke on_shutdown, then clear our reference to it */
7664 PyObject * result = PyObject_CallMethod (connection -> py_core , "_on_shutdown" , "(i)" , error_code );
7765
@@ -82,9 +70,8 @@ static void s_on_connection_shutdown(struct aws_http_connection *native_connecti
8270 PyErr_WriteUnraisable (PyErr_Occurred ());
8371 }
8472
85- if (destroy_after_shutdown ) {
86- s_connection_destroy (connection );
87- }
73+ /* This is the last callback invoked by the connection thread. Release the connection-thread ref. */
74+ aws_ref_count_release (& connection -> ref_count );
8875
8976 PyGILState_Release (state );
9077}
@@ -107,6 +94,10 @@ static void s_on_client_connection_setup(
10794 /* If setup was successful, encapsulate binding so we can pass it to python */
10895 PyObject * capsule = NULL ;
10996 if (!error_code ) {
97+ /* Acquire ref for the connection thread to invoke callbacks with a valid binding.
98+ * The shutdown callback is the last one invoked, and will release this ref. */
99+ aws_ref_count_acquire (& connection -> ref_count );
100+
110101 capsule = PyCapsule_New (connection , s_capsule_name_http_connection , s_connection_capsule_destructor );
111102 if (!capsule ) {
112103 error_code = AWS_ERROR_UNKNOWN ;
@@ -125,13 +116,18 @@ static void s_on_client_connection_setup(
125116 }
126117
127118 if (native_connection ) {
128- /* Connection exists, but failed to create capsule. Release connection, which eventually destroys binding */
129119 if (!capsule ) {
130- s_connection_release (connection );
120+ /* Native connection exists but capsule creation failed. We won't use the connection as a capsule,
121+ * but the connection thread is still running. Release the initial ref (no capsule to own it),
122+ * then release the native connection to initiate shutdown. The shutdown callback will release
123+ * the connection-thread ref. */
124+ aws_ref_count_release (& connection -> ref_count );
125+ aws_http_connection_release (connection -> native );
131126 }
132127 } else {
133- /* Connection failed its setup, destroy binding now */
134- s_connection_destroy (connection );
128+ /* Native connection failed to create. No capsule, no connection thread running.
129+ * Release the sole ref to destroy the binding. */
130+ aws_ref_count_release (& connection -> ref_count );
135131 }
136132
137133 Py_XDECREF (capsule );
@@ -281,6 +277,7 @@ PyObject *aws_py_http_client_connection_new(PyObject *self, PyObject *args) {
281277 }
282278
283279 struct http_connection_binding * connection = aws_mem_calloc (allocator , 1 , sizeof (struct http_connection_binding ));
280+ aws_ref_count_init (& connection -> ref_count , connection , s_connection_destroy );
284281 /* From hereon, we need to clean up if errors occur */
285282 struct aws_http2_setting * http2_settings = NULL ;
286283 size_t http2_settings_count = 0 ;
@@ -388,7 +385,7 @@ PyObject *aws_py_http_client_connection_new(PyObject *self, PyObject *args) {
388385 aws_mem_release (allocator , http2_settings );
389386 }
390387 if (!success ) {
391- s_connection_destroy ( connection );
388+ aws_ref_count_release ( & connection -> ref_count );
392389 return NULL ;
393390 }
394391 Py_RETURN_NONE ;
0 commit comments