Skip to content

Commit dd4ccf4

Browse files
committed
DISPATCH-1962 Fix leak of IoAdapter_init
1 parent 6c6cdd1 commit dd4ccf4

File tree

7 files changed

+62
-20
lines changed

7 files changed

+62
-20
lines changed

.travis.yml

+18-2
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ jobs:
7676
- sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-12 main' -y
7777
- sudo apt-get update -q
7878
- sudo apt-get install -y clang-12 llvm-12-dev -o Debug::pkgProblemResolver=yes
79+
# Python 3.8.2 in Ubuntu LTS has ASan issues
80+
- sudo add-apt-repository -y ppa:deadsnakes/ppa
81+
- sudo apt-get update
82+
- sudo apt-get install python3.9 python3.9-dev python3.9-distutils
83+
- sudo update-alternatives --install /usr/bin/python python /usr/bin/python3.9 10
84+
- sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.9 10
7985
# https://github.com/pypa/virtualenv/issues/1740
8086
# https://github.com/pypa/virtualenv/issues/1873
8187
- python -m pip install --user --upgrade pip
@@ -123,10 +129,15 @@ jobs:
123129
os: linux
124130
dist: focal
125131
compiler: clang
126-
python:
127-
- "3.9"
128132
before_install:
129133
- sudo apt-get install clang-11 llvm-11-dev
134+
# Python 3.8.2 in Ubuntu LTS has ASan issues
135+
- sudo apt install -y python3-pip
136+
- sudo add-apt-repository -y ppa:deadsnakes/ppa
137+
- sudo apt-get update
138+
- sudo apt-get install python3.9 python3.9-dev python3.9-distutils
139+
- sudo update-alternatives --install /usr/bin/python python /usr/bin/python3.9 10
140+
- sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.9 10
130141
# Install and use the latest Node.js LTS version
131142
- nvm install "lts/*"
132143
# https://github.com/pypa/virtualenv/issues/1740
@@ -155,6 +166,11 @@ jobs:
155166
os: linux
156167
dist: focal
157168
before_install:
169+
- sudo add-apt-repository -y ppa:deadsnakes/ppa
170+
- sudo apt-get update
171+
- sudo apt-get install python3.9 python3.9-dev python3.9-distutils
172+
- sudo update-alternatives --install /usr/bin/python python /usr/bin/python3.9 10
173+
- sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.9 10
158174
# https://github.com/pypa/virtualenv/issues/1740
159175
# https://github.com/pypa/virtualenv/issues/1873
160176
- python -m pip install --user --upgrade pip

src/dispatch.c

+1
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ qd_error_t qd_dispatch_prepare(qd_dispatch_t *qd)
341341
void qd_dispatch_set_agent(qd_dispatch_t *qd, void *agent) {
342342
assert(agent);
343343
assert(!qd->agent);
344+
Py_IncRef(agent);
344345
qd->agent = agent;
345346
}
346347

src/python_embedded.c

+21-5
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,8 @@ static PyTypeObject LogAdapterType = {
562562
.tp_dealloc = (destructor)LogAdapter_dealloc,
563563
.tp_flags = Py_TPFLAGS_DEFAULT,
564564
.tp_methods = LogAdapter_methods,
565-
.tp_init = (initproc)LogAdapter_init
565+
.tp_init = (initproc)LogAdapter_init,
566+
.tp_new = PyType_GenericNew,
566567
};
567568

568569

@@ -716,10 +717,24 @@ static int IoAdapter_init(IoAdapter *self, PyObject *args, PyObject *kwds)
716717
return 0;
717718
}
718719

720+
// visit all members which may conceivably participate in reference cycles
721+
static int IoAdapter_traverse(IoAdapter* self, visitproc visit, void *arg)
722+
{
723+
Py_VISIT(self->handler);
724+
return 0;
725+
}
726+
727+
static int IoAdapter_clear(IoAdapter* self)
728+
{
729+
Py_CLEAR(self->handler);
730+
return 0;
731+
}
732+
719733
static void IoAdapter_dealloc(IoAdapter* self)
720734
{
721735
qdr_core_unsubscribe(self->sub);
722-
Py_DECREF(self->handler);
736+
PyObject_GC_UnTrack(self);
737+
IoAdapter_clear(self);
723738
Py_TYPE(self)->tp_free((PyObject*)self);
724739
}
725740

@@ -812,10 +827,13 @@ static PyTypeObject IoAdapterType = {
812827
.tp_name = DISPATCH_MODULE ".IoAdapter",
813828
.tp_doc = "Dispatch IO Adapter",
814829
.tp_basicsize = sizeof(IoAdapter),
830+
.tp_traverse = (traverseproc)IoAdapter_traverse,
831+
.tp_clear = (inquiry)IoAdapter_clear,
815832
.tp_dealloc = (destructor)IoAdapter_dealloc,
816-
.tp_flags = Py_TPFLAGS_DEFAULT,
833+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
817834
.tp_methods = IoAdapter_methods,
818835
.tp_init = (initproc)IoAdapter_init,
836+
.tp_new = PyType_GenericNew,
819837
};
820838

821839

@@ -831,8 +849,6 @@ static void qd_register_constant(PyObject *module, const char *name, uint32_t va
831849

832850
static void qd_python_setup(void)
833851
{
834-
LogAdapterType.tp_new = PyType_GenericNew;
835-
IoAdapterType.tp_new = PyType_GenericNew;
836852
if ((PyType_Ready(&LogAdapterType) < 0) || (PyType_Ready(&IoAdapterType) < 0)) {
837853
qd_error_py();
838854
qd_log(log_source, QD_LOG_CRITICAL, "Unable to initialize Adapters");

src/router_node.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -2157,12 +2157,12 @@ void qd_router_free(qd_router_t *router)
21572157

21582158
qd_container_set_default_node_type(router->qd, 0, 0, QD_DIST_BOTH);
21592159

2160+
qd_router_python_free(router);
21602161
qdr_core_free(router->router_core);
21612162
qd_tracemask_free(router->tracemask);
21622163
qd_timer_free(router->timer);
21632164
sys_mutex_free(router->lock);
21642165
qd_router_configure_free(router);
2165-
qd_router_python_free(router);
21662166

21672167
free(router);
21682168
free(node_id);

src/router_pynode.c

+7-1
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ qd_error_t qd_router_python_setup(qd_router_t *router)
444444
// Instantiate the router
445445
//
446446
pyRouter = PyObject_CallObject(pClass, pArgs);
447+
Py_DECREF(pClass);
447448
Py_DECREF(pArgs);
448449
Py_DECREF(adapterType);
449450
QD_ERROR_PY_RET();
@@ -456,7 +457,12 @@ qd_error_t qd_router_python_setup(qd_router_t *router)
456457
}
457458

458459
void qd_router_python_free(qd_router_t *router) {
459-
// empty
460+
Py_XDECREF(pyRouter);
461+
Py_XDECREF(pyTick);
462+
Py_XDECREF(pySetMobileSeq);
463+
Py_XDECREF(pySetMyMobileSeq);
464+
Py_XDECREF(pyLinkLost);
465+
PyGC_Collect();
460466
}
461467

462468

tests/lsan.supp

-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
# found by AddressSanitizer (ASAN)
33
#
44

5-
# to be triaged; pretty much all tests
6-
leak:^IoAdapter_init$
7-
85
# to be triaged; pretty much all tests
96
leak:^load_server_config$
107

tests/system_test.py

+14-8
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,21 @@
3434
from __future__ import unicode_literals
3535

3636
import errno
37-
import sys
38-
import time
39-
40-
import __main__
4137
import functools
4238
import os
4339
import random
4440
import re
4541
import shutil
4642
import socket
4743
import subprocess
44+
import sys
45+
import time
4846
from copy import copy
4947
from datetime import datetime
5048
from subprocess import PIPE, STDOUT
5149

50+
import __main__
51+
5252
try:
5353
import queue as Queue # 3.x
5454
except ImportError:
@@ -202,10 +202,13 @@ def port_available(port, protocol_family='IPv4'):
202202
def wait_port(port, protocol_family='IPv4', **retry_kwargs):
203203
"""Wait up to timeout for port (on host) to be connectable.
204204
Takes same keyword arguments as retry to control the timeout"""
205+
205206
def check(e):
206207
"""Only retry on connection refused"""
207-
if not isinstance(e, socket.error) or not e.errno == errno.ECONNREFUSED:
208-
raise
208+
# TODO(DISPATCH-1539): in Python 3.3+ it is sufficient to catch only OSError
209+
if isinstance(e, (socket.error, IOError, OSError)) and e.errno == errno.ECONNREFUSED:
210+
return
211+
raise
209212

210213
host = None
211214

@@ -381,7 +384,11 @@ def __init__(self, name=None, listen_port=None, wait=True,
381384
self.args += self.cl_args
382385
super(Http2Server, self).__init__(self.args, name=name, expect=expect)
383386
if wait:
384-
self.wait_ready()
387+
try:
388+
self.wait_ready()
389+
except Exception:
390+
self.teardown()
391+
raise
385392

386393
def wait_ready(self, **retry_kwargs):
387394
"""
@@ -506,7 +513,6 @@ def __init__(self, name=None, config=Config(), pyinclude=None, wait=True,
506513
elif env_home:
507514
args += ['-I', os.path.join(env_home, 'python')]
508515

509-
args = os.environ.get('QPID_DISPATCH_RUNNER', '').split() + args
510516
super(Qdrouterd, self).__init__(args, name=name, expect=expect)
511517
self._management = None
512518
self._wait_ready = False

0 commit comments

Comments
 (0)