@@ -49,10 +49,6 @@ struct mqtt_connection_binding {
4949 * Lets us invoke callbacks on the python object without preventing the GC from cleaning it up. */
5050 PyObject * self_proxy ;
5151
52- /* To not run into a segfault calling on_close with the connection being freed before the callback
53- * can be invoked, we need to keep the PyCapsule alive. */
54- PyObject * self_capsule ;
55-
5652 PyObject * on_connect ;
5753 PyObject * on_any_publish ;
5854
@@ -62,24 +58,25 @@ struct mqtt_connection_binding {
6258
6359static void s_mqtt_python_connection_finish_destruction (struct mqtt_connection_binding * py_connection ) {
6460
65- /* Do not call the on_stopped callback if the python object is finished/destroyed */
66- aws_mqtt_client_connection_set_connection_closed_handler (py_connection -> native , NULL , NULL );
67-
68- aws_mqtt_client_connection_release (py_connection -> native );
69-
7061 Py_DECREF (py_connection -> self_proxy );
7162 Py_DECREF (py_connection -> client );
7263 Py_XDECREF (py_connection -> on_any_publish );
7364
7465 aws_mem_release (aws_py_get_allocator (), py_connection );
7566}
7667
77- static void s_mqtt_python_connection_destructor_on_disconnect (
78- struct aws_mqtt_client_connection * connection ,
79- void * userdata ) {
68+ static void s_start_destroy_native (struct mqtt_connection_binding * py_connection ) {
69+ if (py_connection == NULL || py_connection -> native == NULL ) {
70+ return ;
71+ }
8072
81- if (connection == NULL || userdata == NULL ) {
82- return ; // The connection is dead - skip!
73+ aws_mqtt_client_connection_release (py_connection -> native );
74+ }
75+
76+ static void s_mqtt_python_connection_termination (void * userdata ) {
77+
78+ if (userdata == NULL ) {
79+ return ; // The binding is dead - skip!
8380 }
8481
8582 struct mqtt_connection_binding * py_connection = userdata ;
@@ -93,20 +90,36 @@ static void s_mqtt_python_connection_destructor_on_disconnect(
9390 PyGILState_Release (state );
9491}
9592
93+ static void s_mqtt_python_connection_destructor_on_disconnect (
94+ struct aws_mqtt_client_connection * connection ,
95+ void * user_data ) {
96+ if (connection == NULL || user_data == NULL ) {
97+ return ; // The connection is dead - skip!
98+ }
99+
100+ struct mqtt_connection_binding * py_connection = user_data ;
101+ PyGILState_STATE state ;
102+ if (aws_py_gilstate_ensure (& state )) {
103+ return ; /* Python has shut down. Nothing matters anymore, but don't crash */
104+ }
105+ s_start_destroy_native (py_connection );
106+ PyGILState_Release (state );
107+ }
108+
96109static void s_mqtt_python_connection_destructor (PyObject * connection_capsule ) {
97110
98111 struct mqtt_connection_binding * py_connection =
99112 PyCapsule_GetPointer (connection_capsule , s_capsule_name_mqtt_client_connection );
100- assert (py_connection );
113+ AWS_FATAL_ASSERT (py_connection );
114+ AWS_FATAL_ASSERT (py_connection -> native );
101115
102116 /* This is the destructor from Python - so we can ignore the closed callback here */
103117 aws_mqtt_client_connection_set_connection_closed_handler (py_connection -> native , NULL , NULL );
104118
105119 if (aws_mqtt_client_connection_disconnect (
106120 py_connection -> native , s_mqtt_python_connection_destructor_on_disconnect , py_connection )) {
107-
108- /* If this returns an error, we should immediately destroy the connection */
109- s_mqtt_python_connection_finish_destruction (py_connection );
121+ /* If we already disconnected, we should immediately release the native connection */
122+ s_start_destroy_native (py_connection );
110123 }
111124}
112125
@@ -254,15 +267,6 @@ static void s_on_connection_closed(
254267 PyErr_WriteUnraisable (PyErr_Occurred ());
255268 }
256269 }
257- Py_DECREF (py_connection -> self_proxy );
258-
259- /** Allow the PyCapsule to be freed like normal again.
260- * If this is the last reference (I.E customer code called disconnect and threw the Python object away)
261- * Then this will allow the MQTT311 class to be fully cleaned.
262- * If it is not the last reference (customer still has reference) then when the customer is done
263- * it will be freed like normal.
264- **/
265- Py_DECREF (py_connection -> self_capsule );
266270
267271 PyGILState_Release (state );
268272}
@@ -272,6 +276,7 @@ PyObject *aws_py_mqtt_client_connection_new(PyObject *self, PyObject *args) {
272276
273277 struct aws_allocator * allocator = aws_py_get_allocator ();
274278
279+ PyObject * self_proxy ;
275280 PyObject * self_py ;
276281 PyObject * client_py ;
277282 PyObject * use_websocket_py ;
@@ -310,13 +315,19 @@ PyObject *aws_py_mqtt_client_connection_new(PyObject *self, PyObject *args) {
310315 }
311316 if (!py_connection -> native ) {
312317 PyErr_SetAwsLastError ();
313- goto connection_new_failed ;
318+ goto on_error ;
319+ }
320+
321+ if (aws_mqtt_client_connection_set_connection_termination_handler (
322+ py_connection -> native , s_mqtt_python_connection_termination , py_connection )) {
323+ PyErr_SetAwsLastError ();
324+ goto on_error ;
314325 }
315326
316327 if (aws_mqtt_client_connection_set_connection_result_handlers (
317328 py_connection -> native , s_on_connection_success , py_connection , s_on_connection_failure , py_connection )) {
318329 PyErr_SetAwsLastError ();
319- goto set_connection_handlers_failed ;
330+ goto on_error ;
320331 }
321332
322333 if (aws_mqtt_client_connection_set_connection_interruption_handlers (
@@ -327,13 +338,13 @@ PyObject *aws_py_mqtt_client_connection_new(PyObject *self, PyObject *args) {
327338 py_connection )) {
328339
329340 PyErr_SetAwsLastError ();
330- goto set_interruption_failed ;
341+ goto on_error ;
331342 }
332343
333344 if (aws_mqtt_client_connection_set_connection_closed_handler (
334345 py_connection -> native , s_on_connection_closed , py_connection )) {
335346 PyErr_SetAwsLastError ();
336- goto set_interruption_failed ;
347+ goto on_error ;
337348 }
338349
339350 if (PyObject_IsTrue (use_websocket_py )) {
@@ -345,39 +356,32 @@ PyObject *aws_py_mqtt_client_connection_new(PyObject *self, PyObject *args) {
345356 NULL /*validator userdata*/ )) {
346357
347358 PyErr_SetAwsLastError ();
348- goto use_websockets_failed ;
359+ goto on_error ;
349360 }
350361 }
351362
352- PyObject * self_proxy = PyWeakref_NewProxy (self_py , NULL );
363+ self_proxy = PyWeakref_NewProxy (self_py , NULL );
353364 if (!self_proxy ) {
354- goto proxy_new_failed ;
365+ goto on_error ;
355366 }
356367
357368 PyObject * capsule =
358369 PyCapsule_New (py_connection , s_capsule_name_mqtt_client_connection , s_mqtt_python_connection_destructor );
359370 if (!capsule ) {
360- goto capsule_new_failed ;
371+ goto on_error ;
361372 }
362373
363374 /* From hereon, nothing will fail */
364-
365- py_connection -> self_capsule = capsule ;
366375 py_connection -> self_proxy = self_proxy ;
367376
368377 py_connection -> client = client_py ;
369378 Py_INCREF (py_connection -> client );
370379
371380 return capsule ;
372381
373- capsule_new_failed :
374- Py_DECREF (self_proxy );
375- proxy_new_failed :
376- use_websockets_failed :
377- set_interruption_failed :
378- set_connection_handlers_failed :
382+ on_error :
383+ Py_XDECREF (self_proxy );
379384 aws_mqtt_client_connection_release (py_connection -> native );
380- connection_new_failed :
381385 aws_mem_release (allocator , py_connection );
382386 return NULL ;
383387}
@@ -1329,14 +1333,10 @@ PyObject *aws_py_mqtt_client_connection_disconnect(PyObject *self, PyObject *arg
13291333 }
13301334
13311335 Py_INCREF (on_disconnect );
1332- Py_INCREF (connection -> self_proxy ); /* We need to keep self_proxy alive for on_closed, which will dec-ref this */
1333- Py_INCREF (connection -> self_capsule ); /* Do not allow the PyCapsule to be freed, we need it alive for on_closed */
13341336
13351337 int err = aws_mqtt_client_connection_disconnect (connection -> native , s_on_disconnect , on_disconnect );
13361338 if (err ) {
13371339 Py_DECREF (on_disconnect );
1338- Py_DECREF (connection -> self_proxy );
1339- Py_DECREF (connection -> self_capsule );
13401340 return PyErr_AwsLastError ();
13411341 }
13421342
0 commit comments