Skip to content

Commit 062a322

Browse files
committed
fork checker updates
Fixes #602
1 parent f014069 commit 062a322

3 files changed

Lines changed: 81 additions & 53 deletions

File tree

apsw/tests/fork_checker.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,39 @@ def suppressWarning(name):
1616
# Name deliberate to make it run last
1717
class zzZForkChecker(unittest.TestCase):
1818

19+
def install(self):
20+
gc.collect(2)
21+
assert not apsw.connections()
22+
apsw.shutdown()
23+
apsw.fork_checker()
24+
25+
def testAlreadyInit(self):
26+
apsw.initialize()
27+
con = apsw.Connection(":memory:")
28+
self.assertRaisesRegex(apsw.MisuseError, ".*All connections need to be closed.*", apsw.shutdown)
29+
con.close()
30+
self.assertRaisesRegex(apsw.MisuseError, ".*needs to be shutdown.*", apsw.fork_checker)
31+
32+
def testManyConnections(self):
33+
# make sure dynamic stuff is done
34+
self.install()
35+
c = []
36+
for i in range(50):
37+
c.append(apsw.Connection(":memory:"))
38+
while x:=apsw.connections():
39+
x[0].close()
40+
1941
def testForkChecker(self):
2042
"Test detection of using objects across fork"
21-
# need to free up everything that already exists
22-
gc.collect()
23-
# install it
24-
apsw.fork_checker()
43+
self.install()
44+
45+
class Dummy(apsw.VFS):
46+
def __init__(self):
47+
super().__init__("one", "")
48+
49+
# grabs vfs mutex
50+
d = Dummy()
51+
del d
2552

2653
# return some objects
2754
def getstuff():
@@ -119,8 +146,6 @@ def ueh(unraisable):
119146
del child
120147
del parent
121148
gc.collect()
122-
apsw.shutdown()
123-
apsw.initialize()
124149

125150

126151
# Fork checker is becoming less useful on newer Pythons because
@@ -144,7 +169,7 @@ def ueh(unraisable):
144169
pass
145170

146171
if forkcheck:
147-
__all__ = (zzZForkChecker,)
172+
__all__ = ("zzZForkChecker",)
148173
else:
149174
__all__ = tuple()
150175

doc/changes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ is used in :func:`apsw.ext.format_query_table` to centre column names,
4747
and right align integers. That updates the :doc:`shell <shell>` output.
4848
(:issue:`601`)
4949

50+
Update :func:`fork_checker` and :func:`shutdown` for more robustness
51+
(:issue:`602`)
52+
5053
3.51.2.0
5154
========
5255

src/apsw.c

Lines changed: 46 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,10 @@ static int allow_missing_dict_bindings = 0;
283283
/* constants */
284284
#include "constants.c"
285285

286+
#ifdef APSW_FORK_CHECKER
287+
static sqlite3_mutex_methods apsw_orig_mutex_methods;
288+
#endif
289+
286290
/* MODULE METHODS */
287291

288292
/* Although pyobject is marked as a method, it is really a class but
@@ -499,33 +503,41 @@ initialize(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(unused))
499503

500504
/** .. method:: shutdown() -> None
501505
502-
It is unlikely you will want to call this method and there is no
503-
need to do so. It is a **really** bad idea to call it unless you
504-
are absolutely sure all :class:`connections <Connection>`,
505-
:class:`blobs <Blob>`, :class:`cursors <Cursor>`, :class:`vfs <VFS>`
506-
etc have been closed, deleted and garbage collected.
506+
It is unlikely you will want to call this method and there is no need
507+
to do so. You will get :exc:`MisuseError` if there are any
508+
:func:`connections active <connections>` and need to close them first.
509+
510+
VFS remain registered across a shutdown and initialise.
507511
508512
-* sqlite3_shutdown
509513
*/
510-
#ifdef APSW_FORK_CHECKER
511-
static void free_fork_checker(void);
512-
#endif
513-
514514
static PyObject *
515515
sqliteshutdown(PyObject *Py_UNUSED(unused1), PyObject *Py_UNUSED(unused2))
516516
{
517517
int res;
518518

519+
PyObject *conns = apsw_connections(NULL, NULL);
520+
if (!conns)
521+
return NULL;
522+
Py_ssize_t count = PyList_Size(conns);
523+
Py_DECREF(conns);
524+
525+
if (count != 0)
526+
return PyErr_Format(get_exception_for_code(SQLITE_MISUSE), "All connections need to be closed to call shutdown");
527+
519528
res = sqlite3_shutdown();
520529
SET_EXC(res, NULL);
521530

522-
if (PyErr_Occurred())
523-
return NULL;
524-
525531
#ifdef APSW_FORK_CHECKER
526-
free_fork_checker();
532+
if (apsw_orig_mutex_methods.xMutexInit)
533+
{
534+
sqlite3_config(SQLITE_CONFIG_MUTEX, &apsw_orig_mutex_methods);
535+
}
527536
#endif
528537

538+
if (PyErr_Occurred())
539+
return NULL;
540+
529541
Py_RETURN_NONE;
530542
}
531543

@@ -1137,8 +1149,6 @@ static apsw_mutex *apsw_mutexes[]
11371149
NULL, /* from this point on corresponds to the various static mutexes */
11381150
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
11391151

1140-
static sqlite3_mutex_methods apsw_orig_mutex_methods;
1141-
11421152
static int
11431153
apsw_xMutexInit(void)
11441154
{
@@ -1151,10 +1161,6 @@ apsw_xMutexEnd(void)
11511161
return apsw_orig_mutex_methods.xMutexEnd();
11521162
}
11531163

1154-
#define MUTEX_MAX_ALLOC 20
1155-
static apsw_mutex *fork_checker_mutexes[MUTEX_MAX_ALLOC];
1156-
static int current_apsw_fork_mutex = 0;
1157-
11581164
static sqlite3_mutex *
11591165
apsw_xMutexAlloc(int which)
11601166
{
@@ -1166,15 +1172,16 @@ apsw_xMutexAlloc(int which)
11661172
sqlite3_mutex *m = apsw_orig_mutex_methods.xMutexAlloc(which);
11671173

11681174
if (!m)
1169-
return m;
1170-
assert(current_apsw_fork_mutex < MUTEX_MAX_ALLOC);
1171-
fork_checker_mutexes[current_apsw_fork_mutex++] = am = malloc(sizeof(apsw_mutex));
1175+
return NULL;
1176+
am = malloc(sizeof(apsw_mutex));
1177+
if (!am)
1178+
return NULL;
11721179
am->pid = getpid();
11731180
am->underlying_mutex = m;
11741181
return (sqlite3_mutex *)am;
11751182
}
11761183
default:
1177-
/* verify we have space */
1184+
/* verify we have space for a static mutex */
11781185
assert((unsigned)which < sizeof(apsw_mutexes) / sizeof(apsw_mutexes[0]));
11791186
/* fill in if missing */
11801187
if (!apsw_mutexes[which])
@@ -1187,23 +1194,6 @@ apsw_xMutexAlloc(int which)
11871194
}
11881195
}
11891196

1190-
static void
1191-
free_fork_checker(void)
1192-
{
1193-
unsigned i;
1194-
for (i = 0; i < sizeof(apsw_mutexes) / sizeof(apsw_mutexes[0]); i++)
1195-
{
1196-
free(apsw_mutexes[i]);
1197-
apsw_mutexes[i] = NULL;
1198-
}
1199-
for (i = 0; i < MUTEX_MAX_ALLOC; i++)
1200-
{
1201-
free(fork_checker_mutexes[i]);
1202-
fork_checker_mutexes[i] = 0;
1203-
}
1204-
current_apsw_fork_mutex = 0;
1205-
}
1206-
12071197
static int
12081198
apsw_check_mutex(apsw_mutex *am)
12091199
{
@@ -1228,6 +1218,7 @@ apsw_xMutexFree(sqlite3_mutex *mutex)
12281218
apsw_mutex *am = (apsw_mutex *)mutex;
12291219
apsw_check_mutex(am);
12301220
apsw_orig_mutex_methods.xMutexFree(am->underlying_mutex);
1221+
free(am);
12311222
}
12321223

12331224
static void
@@ -1300,7 +1291,7 @@ static sqlite3_mutex_methods apsw_mutex_methods
13001291
13011292
One example of how you may end up using fork is if you use the
13021293
:mod:`multiprocessing module <multiprocessing>` which can use
1303-
fork to make child processes.
1294+
fork to make child processes in less recent Python versions.
13041295
13051296
If you do use fork or multiprocessing on a platform that supports fork
13061297
then you **must** ensure database connections and their objects
@@ -1322,11 +1313,10 @@ static sqlite3_mutex_methods apsw_mutex_methods
13221313
arose. (Destructors of objects you didn't close also run between
13231314
lines.)
13241315
1325-
You should only call this method as the first line after importing
1326-
APSW, as it has to shutdown and re-initialize SQLite. If you have
1327-
any SQLite objects already allocated when calling the method then
1328-
the program will later crash. The recommended use is to use the fork
1329-
checking as part of your test suite.
1316+
Calling this method requires doing a :func:`shutdown` which means there
1317+
can be no active connections.
1318+
1319+
The recommended use is to use the fork checking as part of your test suite.
13301320
*/
13311321
static PyObject *
13321322
apsw_fork_checker(PyObject *Py_UNUSED(self))
@@ -1337,6 +1327,16 @@ apsw_fork_checker(PyObject *Py_UNUSED(self))
13371327
if (apsw_orig_mutex_methods.xMutexInit)
13381328
goto ok;
13391329

1330+
sqlite3_mutex_methods dummy;
1331+
1332+
rc = sqlite3_config(SQLITE_CONFIG_GETMUTEX, &dummy);
1333+
if (rc)
1334+
{
1335+
PyErr_Format(get_exception_for_code(rc),
1336+
"SQLite needs to be shutdown while no connections are active in order to install the fork checker");
1337+
goto fail;
1338+
}
1339+
13401340
/* Ensure mutex methods available and installed */
13411341
rc = sqlite3_initialize();
13421342
if (rc)

0 commit comments

Comments
 (0)