Skip to content

Commit 26e8920

Browse files
committed
Fixes #412, DISPATCH-1962 - Python shutdown leaks
1 parent 5f88d22 commit 26e8920

File tree

11 files changed

+73
-37
lines changed

11 files changed

+73
-37
lines changed

python/skupper_router_internal/dispatch.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,8 @@ def __init__(self, handle: int) -> None:
8181

8282
self._prototype(self.qd_dispatch_configure_policy, None, [self.qd_dispatch_p, py_object])
8383
self._prototype(self.qd_dispatch_register_policy_manager, None, [self.qd_dispatch_p, py_object])
84-
self._prototype(self.qd_dispatch_policy_c_counts_alloc, c_long, [], check=False)
85-
self._prototype(self.qd_dispatch_policy_c_counts_free, None, [c_long], check=False)
86-
self._prototype(self.qd_dispatch_policy_c_counts_refresh, None, [c_long, py_object])
84+
self._prototype(self.qd_dispatch_policy_c_counts_alloc, py_object, [], check=False)
85+
self._prototype(self.qd_dispatch_policy_c_counts_refresh, None, [py_object, py_object])
8786
self._prototype(self.qd_dispatch_policy_host_pattern_add, ctypes.c_bool, [self.qd_dispatch_p, py_object])
8887
self._prototype(self.qd_dispatch_policy_host_pattern_remove, None, [self.qd_dispatch_p, py_object])
8988
self._prototype(self.qd_dispatch_policy_host_pattern_lookup, c_char_p, [self.qd_dispatch_p, py_object])

python/skupper_router_internal/policy/policy_local.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#
1919

2020
"""Entity implementing the business logic of user connection/access policy."""
21-
21+
import ctypes
2222
import json
2323
from typing import Any, Dict, List, Union, TYPE_CHECKING
2424

@@ -590,7 +590,7 @@ def disconnect(self, conn_id, user, host):
590590
def count_other_denial(self) -> None:
591591
self.conn_mgr.count_other_denial()
592592

593-
def get_cstats(self) -> int:
593+
def get_cstats(self) -> ctypes.py_object:
594594
return self._cstats
595595

596596
#
@@ -939,7 +939,7 @@ def lookup_settings(
939939
self,
940940
vhost_in: str,
941941
groupname: str,
942-
upolicy: Dict[Any, Any]
942+
upolicy: Dict[str, Any]
943943
) -> bool:
944944
"""
945945
Given a settings name, return the aggregated policy blob.

src/dispatch.c

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ void qd_router_free(qd_router_t *router);
5858
void qd_error_initialize();
5959
static void qd_dispatch_set_router_id(qd_dispatch_t *qd, char *_id);
6060
static void qd_dispatch_set_router_area(qd_dispatch_t *qd, char *_area);
61+
static void qd_dispatch_policy_c_counts_free(PyObject *capsule);
6162

6263
const char *CLOSEST_DISTRIBUTION = "closest";
6364
const char *MULTICAST_DISTRIBUTION = "multicast";
@@ -270,20 +271,24 @@ QD_EXPORT qd_error_t qd_dispatch_register_display_name_service(qd_dispatch_t *qd
270271
return qd_register_display_name_service(qd, object);
271272
}
272273

273-
274-
QD_EXPORT long qd_dispatch_policy_c_counts_alloc()
274+
QD_EXPORT PyObject* qd_dispatch_policy_c_counts_alloc()
275275
{
276-
return qd_policy_c_counts_alloc();
276+
return PyCapsule_New(qd_policy_c_counts_alloc(), "qd_policy_c_counts", qd_dispatch_policy_c_counts_free);
277277
}
278278

279-
280-
QD_EXPORT void qd_dispatch_policy_c_counts_free(long ccounts)
279+
static void qd_dispatch_policy_c_counts_free(PyObject *capsule)
281280
{
281+
void *ccounts = PyCapsule_GetPointer(capsule, "qd_policy_c_counts");
282282
qd_policy_c_counts_free(ccounts);
283283
}
284284

285-
QD_EXPORT void qd_dispatch_policy_c_counts_refresh(long ccounts, qd_entity_t *entity)
285+
QD_EXPORT void qd_dispatch_policy_c_counts_refresh(PyObject *ccounts_capsule, qd_entity_t *entity)
286286
{
287+
assert(PyCapsule_CheckExact(ccounts_capsule));
288+
const char * name = PyCapsule_GetName(ccounts_capsule);
289+
assert(PyCapsule_IsValid(ccounts_capsule, name));
290+
void* ccounts = PyCapsule_GetPointer(ccounts_capsule, name);
291+
qd_error_py();
287292
qd_policy_c_counts_refresh(ccounts, entity);
288293
}
289294

@@ -324,6 +329,7 @@ qd_error_t qd_dispatch_prepare(qd_dispatch_t *qd)
324329
void qd_dispatch_set_agent(qd_dispatch_t *qd, void *agent) {
325330
assert(agent);
326331
assert(!qd->agent);
332+
Py_IncRef(agent);
327333
qd->agent = agent;
328334
}
329335

src/entity.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,18 @@ long qd_entity_get_long(qd_entity_t *entity, const char* attribute) {
7070
return result;
7171
}
7272

73+
void *qd_entity_get_pointer_from_capsule(qd_entity_t *entity, const char *attribute) {
74+
qd_error_clear();
75+
PyObject *py_obj = qd_entity_get_py(entity, attribute);
76+
assert(PyCapsule_CheckExact(py_obj));
77+
const char * name = PyCapsule_GetName(py_obj);
78+
assert(PyCapsule_IsValid(py_obj, name));
79+
void* result = PyCapsule_GetPointer(py_obj, name);
80+
Py_XDECREF(py_obj);
81+
qd_error_py();
82+
return result;
83+
}
84+
7385
bool qd_entity_get_bool(qd_entity_t *entity, const char* attribute) {
7486
qd_error_clear();
7587
PyObject *py_obj = qd_entity_get_py(entity, attribute);

src/entity.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ char *qd_entity_get_string(qd_entity_t *entity, const char* attribute);
4444
/** Get an integer valued attribute. Return -1 and set qd_error if there is an error. */
4545
long qd_entity_get_long(qd_entity_t *entity, const char* attribute);
4646

47+
/** Get a void* valued attribute stored in a PyCapsule. Return NULL and set qd_error if there is an error. */
48+
void *qd_entity_get_pointer_from_capsule(qd_entity_t *entity, const char *attribute);
49+
4750
/** Get a boolean valued attribute. Return false and set qd_error if there is an error. */
4851
bool qd_entity_get_bool(qd_entity_t *entity, const char *attribute);
4952

src/policy.c

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -191,26 +191,23 @@ qd_error_t qd_register_policy_manager(qd_policy_t *policy, void *policy_manager)
191191
}
192192

193193

194-
long qd_policy_c_counts_alloc()
194+
void *qd_policy_c_counts_alloc()
195195
{
196-
qd_policy_denial_counts_t * dc = NEW(qd_policy_denial_counts_t);
196+
qd_policy_denial_counts_t *dc = NEW(qd_policy_denial_counts_t);
197197
assert(dc);
198198
ZERO(dc);
199-
return (long)dc;
199+
return dc;
200200
}
201201

202-
203-
void qd_policy_c_counts_free(long ccounts)
202+
void qd_policy_c_counts_free(void *dc)
204203
{
205-
void *dc = (void *)ccounts;
206204
assert(dc);
207205
free(dc);
208206
}
209207

210208

211-
qd_error_t qd_policy_c_counts_refresh(long ccounts, qd_entity_t *entity)
209+
qd_error_t qd_policy_c_counts_refresh(qd_policy_denial_counts_t* dc, qd_entity_t *entity)
212210
{
213-
qd_policy_denial_counts_t *dc = (qd_policy_denial_counts_t*)ccounts;
214211
if (!qd_entity_set_long(entity, "sessionDenied", dc->sessionDenied) &&
215212
!qd_entity_set_long(entity, "senderDenied", dc->senderDenied) &&
216213
!qd_entity_set_long(entity, "receiverDenied", dc->receiverDenied) &&
@@ -573,7 +570,7 @@ bool qd_policy_open_fetch_settings(
573570
settings->sourceParseTree = qd_policy_parse_tree(settings->sourcePattern);
574571
settings->targetParseTree = qd_policy_parse_tree(settings->targetPattern);
575572
settings->denialCounts = (qd_policy_denial_counts_t*)
576-
qd_entity_get_long((qd_entity_t*)upolicy, "denialCounts");
573+
qd_entity_get_pointer_from_capsule((qd_entity_t*)upolicy, "denialCounts");
577574
res = true; // named settings content returned
578575
} else {
579576
// lookup failed: object did not exist in python database

src/policy.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,17 @@ qd_error_t qd_register_policy_manager(qd_policy_t *policy, void *policy_manager)
8585
/** Allocate counts statistics block.
8686
* Called from Python
8787
*/
88-
long qd_policy_c_counts_alloc();
88+
void* qd_policy_c_counts_alloc();
8989

9090
/** Free counts statistics block.
9191
* Called from Python
9292
*/
93-
void qd_policy_c_counts_free(long ccounts);
93+
void qd_policy_c_counts_free(void* dc);
9494

9595
/** Refresh a counts statistics block
9696
* Called from Python
9797
*/
98-
qd_error_t qd_policy_c_counts_refresh(long ccounts, qd_entity_t*entity);
98+
qd_error_t qd_policy_c_counts_refresh(qd_policy_denial_counts_t* dc, qd_entity_t*entity);
9999

100100

101101
/** Allow or deny an incoming connection based on connection count(s).

src/python_embedded.c

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ void qd_python_finalize(void)
6666
{
6767
(void) qd_python_lock();
6868

69+
Py_DECREF(message_type);
6970
Py_DECREF(dispatch_module);
7071
dispatch_module = 0;
7172
PyGC_Collect();
@@ -565,7 +566,8 @@ static PyTypeObject LogAdapterType = {
565566
.tp_dealloc = (destructor)LogAdapter_dealloc,
566567
.tp_flags = Py_TPFLAGS_DEFAULT,
567568
.tp_methods = LogAdapter_methods,
568-
.tp_init = (initproc)LogAdapter_init
569+
.tp_init = (initproc)LogAdapter_init,
570+
.tp_new = PyType_GenericNew,
569571
};
570572

571573

@@ -710,10 +712,24 @@ static int IoAdapter_init(IoAdapter *self, PyObject *args, PyObject *kwds)
710712
return 0;
711713
}
712714

715+
// visit all members which may conceivably participate in reference cycles
716+
static int IoAdapter_traverse(IoAdapter* self, visitproc visit, void *arg)
717+
{
718+
Py_VISIT(self->handler);
719+
return 0;
720+
}
721+
722+
static int IoAdapter_clear(IoAdapter* self)
723+
{
724+
Py_CLEAR(self->handler);
725+
return 0;
726+
}
727+
713728
static void IoAdapter_dealloc(IoAdapter* self)
714729
{
715730
qdr_core_unsubscribe(self->sub);
716-
Py_DECREF(self->handler);
731+
PyObject_GC_UnTrack(self);
732+
IoAdapter_clear(self);
717733
Py_TYPE(self)->tp_free((PyObject*)self);
718734
}
719735

@@ -795,10 +811,13 @@ static PyTypeObject IoAdapterType = {
795811
.tp_name = DISPATCH_MODULE ".IoAdapter",
796812
.tp_doc = "Dispatch IO Adapter",
797813
.tp_basicsize = sizeof(IoAdapter),
814+
.tp_traverse = (traverseproc)IoAdapter_traverse,
815+
.tp_clear = (inquiry)IoAdapter_clear,
798816
.tp_dealloc = (destructor)IoAdapter_dealloc,
799-
.tp_flags = Py_TPFLAGS_DEFAULT,
817+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
800818
.tp_methods = IoAdapter_methods,
801819
.tp_init = (initproc)IoAdapter_init,
820+
.tp_new = PyType_GenericNew,
802821
};
803822

804823

@@ -814,8 +833,6 @@ static void qd_register_constant(PyObject *module, const char *name, uint32_t va
814833

815834
static void qd_python_setup(void)
816835
{
817-
LogAdapterType.tp_new = PyType_GenericNew;
818-
IoAdapterType.tp_new = PyType_GenericNew;
819836
if ((PyType_Ready(&LogAdapterType) < 0) || (PyType_Ready(&IoAdapterType) < 0)) {
820837
qd_error_py();
821838
qd_log(log_source, QD_LOG_CRITICAL, "Unable to initialize Adapters");

src/router_node.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2092,12 +2092,12 @@ void qd_router_free(qd_router_t *router)
20922092

20932093
qd_container_set_default_node_type(router->qd, 0, 0, QD_DIST_BOTH);
20942094

2095+
qd_router_python_free(router);
20952096
qdr_core_free(router->router_core);
20962097
qd_tracemask_free(router->tracemask);
20972098
qd_timer_free(router->timer);
20982099
sys_mutex_free(router->lock);
20992100
qd_router_configure_free(router);
2100-
qd_router_python_free(router);
21012101

21022102
free(router);
21032103
qd_router_id_finalize();

src/router_pynode.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,7 @@ qd_error_t qd_router_python_setup(qd_router_t *router)
443443
// Instantiate the router
444444
//
445445
pyRouter = PyObject_CallObject(pClass, pArgs);
446+
Py_DECREF(pClass);
446447
Py_DECREF(pArgs);
447448
Py_DECREF(adapterType);
448449
QD_ERROR_PY_RET();
@@ -455,7 +456,14 @@ qd_error_t qd_router_python_setup(qd_router_t *router)
455456
}
456457

457458
void qd_router_python_free(qd_router_t *router) {
458-
// empty
459+
qd_python_lock_state_t ls = qd_python_lock();
460+
Py_XDECREF(pyRouter);
461+
Py_CLEAR(pyTick);
462+
Py_CLEAR(pySetMobileSeq);
463+
Py_CLEAR(pySetMyMobileSeq);
464+
Py_CLEAR(pyLinkLost);
465+
PyGC_Collect();
466+
qd_python_unlock(ls);
459467
}
460468

461469

0 commit comments

Comments
 (0)