From 3ef73e8673e709d45e40e4af93ba8e18445bc379 Mon Sep 17 00:00:00 2001 From: Matthew Francis Brunetti Date: Fri, 13 Feb 2026 04:13:59 -0500 Subject: [PATCH 1/2] Make pconnect cache keys thread-specific Include thread id in the persistent connection cache key so pconnect entries are isolated per thread. This prevents accidental cross-thread reuse of cached connections. Add regression test for thread safety of persistent connections. --- ibm_db.c | 2 + .../test_6793_PconnectThreadIsolation.py | 90 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 ibm_db_tests/test_6793_PconnectThreadIsolation.py diff --git a/ibm_db.c b/ibm_db.c index 3e4fcda5..b3f872bf 100644 --- a/ibm_db.c +++ b/ibm_db.c @@ -1980,6 +1980,8 @@ static PyObject *_python_ibm_db_connect_helper(PyObject *self, PyObject *args, i hKey = PyUnicode_Concat(hKey, PyUnicode_FromFormat("%ld", PyLong_AsLong(pid))); Py_DECREF(pid); + hKey = PyUnicode_Concat(hKey, PyUnicode_FromFormat("%ld", (long)PyThread_get_thread_ident())); + entry = PyDict_GetItem(persistent_list, hKey); if (entry != NULL) diff --git a/ibm_db_tests/test_6793_PconnectThreadIsolation.py b/ibm_db_tests/test_6793_PconnectThreadIsolation.py new file mode 100644 index 00000000..ec1528e7 --- /dev/null +++ b/ibm_db_tests/test_6793_PconnectThreadIsolation.py @@ -0,0 +1,90 @@ +# +# Licensed Materials - Property of IBM +# +# (c) Copyright IBM Corp. 2007-2008 +# + +from __future__ import print_function +import sys +import threading +import unittest +import ibm_db +import config +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_6793_PconnectThreadIsolation(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_6793) + + def run_test_6793(self): + start_event = threading.Event() + lock = threading.Lock() + results = [] + errors = [] + + def worker(): + try: + start_event.wait() + + if sys.platform == 'zos': + conn = ibm_db.pconnect(config.database, '', '') + else: + conn = ibm_db.pconnect(config.database, config.user, config.password) + + if not conn: + with lock: + errors.append("connect_failed") + return + + is_active = ibm_db.active(conn) + conn_id = id(conn) + ibm_db.close(conn) + + with lock: + results.append((conn_id, is_active)) + except Exception: + with lock: + errors.append("worker_exception") + + threads = [threading.Thread(target=worker) for _ in range(2)] + + for t in threads: + t.start() + + start_event.set() + + for t in threads: + t.join() + + if errors: + print("errors:", len(errors)) + return + + unique_conn_handles = len(set([conn_id for conn_id, _ in results])) + active_count = sum(1 for _, is_active in results if is_active) + + print("workers:", len(results)) + print("active_count:", active_count) + print("unique_conn_handles:", unique_conn_handles) + + +#__END__ +#__LUW_EXPECTED__ +#workers: 2 +#active_count: 2 +#unique_conn_handles: 2 +#__ZOS_EXPECTED__ +#workers: 2 +#active_count: 2 +#unique_conn_handles: 2 +#__SYSTEMI_EXPECTED__ +#workers: 2 +#active_count: 2 +#unique_conn_handles: 2 +#__IDS_EXPECTED__ +#workers: 2 +#active_count: 2 +#unique_conn_handles: 2 From 1e668dcdf7cd382759609cbe6413a4981272c48c Mon Sep 17 00:00:00 2001 From: Matthew Francis Brunetti Date: Fri, 13 Feb 2026 17:41:31 -0500 Subject: [PATCH 2/2] Fix PyUnicode_FromFormat reference leaks in pconnect cache key construction - Ensure PID and thread-id temporary unicode objects are NULL-checked and decref'd after PyUnicode_Concat. - Prevents incremental memory growth in long-running workloads using persistent connections. --- ibm_db.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/ibm_db.c b/ibm_db.c index b3f872bf..d93ab7eb 100644 --- a/ibm_db.c +++ b/ibm_db.c @@ -1921,6 +1921,8 @@ static PyObject *_python_ibm_db_connect_helper(PyObject *self, PyObject *args, i char server[2048]; int isNewBuffer = 0; PyObject *pid = NULL; + PyObject *pidStr = NULL; + PyObject *threadIdStr = NULL; conn_alive = 1; if (!PyArg_ParseTuple(args, "OOO|OO", &databaseObj, &uidObj, &passwordObj, &options, &literal_replacementObj)) { @@ -1977,10 +1979,23 @@ static PyObject *_python_ibm_db_connect_helper(PyObject *self, PyObject *args, i } snprintf(messageStr, sizeof(messageStr), "Obtain process id: %p", pid); LogMsg(INFO, messageStr); - hKey = PyUnicode_Concat(hKey, PyUnicode_FromFormat("%ld", PyLong_AsLong(pid))); + pidStr = PyUnicode_FromFormat("%ld", PyLong_AsLong(pid)); + if (pidStr == NULL) + { + Py_DECREF(pid); + return NULL; + } + hKey = PyUnicode_Concat(hKey, pidStr); + Py_DECREF(pidStr); Py_DECREF(pid); - hKey = PyUnicode_Concat(hKey, PyUnicode_FromFormat("%ld", (long)PyThread_get_thread_ident())); + threadIdStr = PyUnicode_FromFormat("%ld", (long)PyThread_get_thread_ident()); + if (threadIdStr == NULL) + { + return NULL; + } + hKey = PyUnicode_Concat(hKey, threadIdStr); + Py_DECREF(threadIdStr); entry = PyDict_GetItem(persistent_list, hKey);