Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 55bce63

Browse files
author
Charles-François Natali
committed
Issue #14154: Reimplement the bigmem test memory watchdog as a subprocess.
1 parent 226ed7e commit 55bce63

3 files changed

Lines changed: 38 additions & 240 deletions

File tree

Lib/test/memory_watchdog.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""Memory watchdog: periodically read the memory usage of the main test process
2+
and print it out, until terminated."""
3+
# stdin should refer to the process' /proc/<PID>/statm: we don't pass the
4+
# process' PID to avoid a race condition in case of - unlikely - PID recycling.
5+
# If the process crashes, reading from the /proc entry will fail with ESRCH.
6+
7+
8+
import os
9+
import sys
10+
import time
11+
12+
13+
try:
14+
page_size = os.sysconf('SC_PAGESIZE')
15+
except (ValueError, AttributeError):
16+
try:
17+
page_size = os.sysconf('SC_PAGE_SIZE')
18+
except (ValueError, AttributeError):
19+
page_size = 4096
20+
21+
while True:
22+
sys.stdin.seek(0)
23+
statm = sys.stdin.read()
24+
data = int(statm.split()[5])
25+
sys.stdout.write(" ... process data size: {data:.1f}G\n"
26+
.format(data=data * page_size / (1024 ** 3)))
27+
sys.stdout.flush()
28+
time.sleep(1)

Lib/test/support.py

Lines changed: 10 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,11 @@
3535
except ImportError:
3636
multiprocessing = None
3737

38-
try:
39-
import faulthandler
40-
except ImportError:
41-
faulthandler = None
42-
4338
try:
4439
import zlib
4540
except ImportError:
4641
zlib = None
4742

48-
try:
49-
import fcntl
50-
except ImportError:
51-
fcntl = None
52-
5343
__all__ = [
5444
"Error", "TestFailed", "ResourceDenied", "import_module",
5545
"verbose", "use_resources", "max_memuse", "record_original_stdout",
@@ -1151,62 +1141,26 @@ class _MemoryWatchdog:
11511141
def __init__(self):
11521142
self.procfile = '/proc/{pid}/statm'.format(pid=os.getpid())
11531143
self.started = False
1154-
self.thread = None
1155-
try:
1156-
self.page_size = os.sysconf('SC_PAGESIZE')
1157-
except (ValueError, AttributeError):
1158-
try:
1159-
self.page_size = os.sysconf('SC_PAGE_SIZE')
1160-
except (ValueError, AttributeError):
1161-
self.page_size = 4096
1162-
1163-
def consumer(self, fd):
1164-
HEADER = "l"
1165-
header_size = struct.calcsize(HEADER)
1166-
try:
1167-
while True:
1168-
header = os.read(fd, header_size)
1169-
if len(header) < header_size:
1170-
# Pipe closed on other end
1171-
break
1172-
data_len, = struct.unpack(HEADER, header)
1173-
data = os.read(fd, data_len)
1174-
statm = data.decode('ascii')
1175-
data = int(statm.split()[5])
1176-
print(" ... process data size: {data:.1f}G"
1177-
.format(data=data * self.page_size / (1024 ** 3)))
1178-
finally:
1179-
os.close(fd)
11801144

11811145
def start(self):
1182-
if not faulthandler or not hasattr(faulthandler, '_file_watchdog'):
1183-
return
11841146
try:
1185-
rfd = os.open(self.procfile, os.O_RDONLY)
1147+
f = open(self.procfile, 'r')
11861148
except OSError as e:
11871149
warnings.warn('/proc not available for stats: {}'.format(e),
11881150
RuntimeWarning)
11891151
sys.stderr.flush()
11901152
return
1191-
pipe_fd, wfd = os.pipe()
1192-
# set the write end of the pipe non-blocking to avoid blocking the
1193-
# watchdog thread when the consumer doesn't drain the pipe fast enough
1194-
if fcntl:
1195-
flags = fcntl.fcntl(wfd, fcntl.F_GETFL)
1196-
fcntl.fcntl(wfd, fcntl.F_SETFL, flags|os.O_NONBLOCK)
1197-
# _file_watchdog() doesn't take the GIL in its child thread, and
1198-
# therefore collects statistics timely
1199-
faulthandler._file_watchdog(rfd, wfd, 1.0)
1153+
1154+
watchdog_script = findfile("memory_watchdog.py")
1155+
self.mem_watchdog = subprocess.Popen([sys.executable, watchdog_script],
1156+
stdin=f, stderr=subprocess.DEVNULL)
1157+
f.close()
12001158
self.started = True
1201-
self.thread = threading.Thread(target=self.consumer, args=(pipe_fd,))
1202-
self.thread.daemon = True
1203-
self.thread.start()
12041159

12051160
def stop(self):
1206-
if not self.started:
1207-
return
1208-
faulthandler._cancel_file_watchdog()
1209-
self.thread.join()
1161+
if self.started:
1162+
self.mem_watchdog.terminate()
1163+
self.mem_watchdog.wait()
12101164

12111165

12121166
def bigmemtest(size, memuse, dry_run=True):
@@ -1234,7 +1188,7 @@ def wrapper(self):
12341188
"not enough memory: %.1fG minimum needed"
12351189
% (size * memuse / (1024 ** 3)))
12361190

1237-
if real_max_memuse and verbose and faulthandler and threading:
1191+
if real_max_memuse and verbose:
12381192
print()
12391193
print(" ... expected peak memory use: {peak:.1f}G"
12401194
.format(peak=size * memuse / (1024 ** 3)))

Modules/faulthandler.c

Lines changed: 0 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
#ifdef WITH_THREAD
1515
# define FAULTHANDLER_LATER
16-
# define FAULTHANDLER_WATCHDOG
1716
#endif
1817

1918
#ifndef MS_WINDOWS
@@ -66,20 +65,6 @@ static struct {
6665
} thread;
6766
#endif
6867

69-
#ifdef FAULTHANDLER_WATCHDOG
70-
static struct {
71-
int rfd;
72-
int wfd;
73-
PY_TIMEOUT_T period_us; /* period in microseconds */
74-
/* The main thread always holds this lock. It is only released when
75-
faulthandler_watchdog() is interrupted before this thread exits, or at
76-
Python exit. */
77-
PyThread_type_lock cancel_event;
78-
/* released by child thread when joined */
79-
PyThread_type_lock running;
80-
} watchdog;
81-
#endif
82-
8368
#ifdef FAULTHANDLER_USER
8469
typedef struct {
8570
int enabled;
@@ -604,139 +589,6 @@ faulthandler_cancel_dump_tracebacks_later_py(PyObject *self)
604589
}
605590
#endif /* FAULTHANDLER_LATER */
606591

607-
#ifdef FAULTHANDLER_WATCHDOG
608-
609-
static void
610-
file_watchdog(void *unused)
611-
{
612-
PyLockStatus st;
613-
PY_TIMEOUT_T timeout;
614-
615-
#define MAXDATA 1024
616-
char buf1[MAXDATA], buf2[MAXDATA];
617-
char *data = buf1, *old_data = buf2;
618-
Py_ssize_t data_len, old_data_len = -1;
619-
620-
#if defined(HAVE_PTHREAD_SIGMASK) && !defined(HAVE_BROKEN_PTHREAD_SIGMASK)
621-
sigset_t set;
622-
623-
/* we don't want to receive any signal */
624-
sigfillset(&set);
625-
pthread_sigmask(SIG_SETMASK, &set, NULL);
626-
#endif
627-
628-
/* On first pass, feed file contents immediately */
629-
timeout = 0;
630-
do {
631-
st = PyThread_acquire_lock_timed(watchdog.cancel_event,
632-
timeout, 0);
633-
timeout = watchdog.period_us;
634-
if (st == PY_LOCK_ACQUIRED) {
635-
PyThread_release_lock(watchdog.cancel_event);
636-
break;
637-
}
638-
/* Timeout => read and write data */
639-
assert(st == PY_LOCK_FAILURE);
640-
641-
if (lseek(watchdog.rfd, 0, SEEK_SET) < 0) {
642-
break;
643-
}
644-
data_len = read(watchdog.rfd, data, MAXDATA);
645-
if (data_len < 0) {
646-
break;
647-
}
648-
if (data_len != old_data_len || memcmp(data, old_data, data_len)) {
649-
char *tdata;
650-
Py_ssize_t tlen;
651-
/* Contents changed, feed them to wfd */
652-
long x = (long) data_len;
653-
/* We can't do anything if the consumer is too slow, just bail out */
654-
if (write(watchdog.wfd, (void *) &x, sizeof(x)) < sizeof(x))
655-
break;
656-
if (write(watchdog.wfd, data, data_len) < data_len)
657-
break;
658-
tdata = data;
659-
data = old_data;
660-
old_data = tdata;
661-
tlen = data_len;
662-
data_len = old_data_len;
663-
old_data_len = tlen;
664-
}
665-
} while (1);
666-
667-
close(watchdog.rfd);
668-
close(watchdog.wfd);
669-
670-
/* The only way out */
671-
PyThread_release_lock(watchdog.running);
672-
#undef MAXDATA
673-
}
674-
675-
static void
676-
cancel_file_watchdog(void)
677-
{
678-
/* Notify cancellation */
679-
PyThread_release_lock(watchdog.cancel_event);
680-
681-
/* Wait for thread to join */
682-
PyThread_acquire_lock(watchdog.running, 1);
683-
PyThread_release_lock(watchdog.running);
684-
685-
/* The main thread should always hold the cancel_event lock */
686-
PyThread_acquire_lock(watchdog.cancel_event, 1);
687-
}
688-
689-
static PyObject*
690-
faulthandler_file_watchdog(PyObject *self,
691-
PyObject *args, PyObject *kwargs)
692-
{
693-
static char *kwlist[] = {"rfd", "wfd", "period", NULL};
694-
double period;
695-
PY_TIMEOUT_T period_us;
696-
int rfd, wfd;
697-
698-
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
699-
"iid:_file_watchdog", kwlist,
700-
&rfd, &wfd, &period))
701-
return NULL;
702-
if ((period * 1e6) >= (double) PY_TIMEOUT_MAX) {
703-
PyErr_SetString(PyExc_OverflowError, "period value is too large");
704-
return NULL;
705-
}
706-
period_us = (PY_TIMEOUT_T)(period * 1e6);
707-
if (period_us <= 0) {
708-
PyErr_SetString(PyExc_ValueError, "period must be greater than 0");
709-
return NULL;
710-
}
711-
712-
/* Cancel previous thread, if running */
713-
cancel_file_watchdog();
714-
715-
watchdog.rfd = rfd;
716-
watchdog.wfd = wfd;
717-
watchdog.period_us = period_us;
718-
719-
/* Arm these locks to serve as events when released */
720-
PyThread_acquire_lock(watchdog.running, 1);
721-
722-
if (PyThread_start_new_thread(file_watchdog, NULL) == -1) {
723-
PyThread_release_lock(watchdog.running);
724-
PyErr_SetString(PyExc_RuntimeError,
725-
"unable to start file watchdog thread");
726-
return NULL;
727-
}
728-
729-
Py_RETURN_NONE;
730-
}
731-
732-
static PyObject*
733-
faulthandler_cancel_file_watchdog(PyObject *self)
734-
{
735-
cancel_file_watchdog();
736-
Py_RETURN_NONE;
737-
}
738-
#endif /* FAULTHANDLER_WATCHDOG */
739-
740592
#ifdef FAULTHANDLER_USER
741593
static int
742594
faulthandler_register(int signum, int chain, _Py_sighandler_t *p_previous)
@@ -1126,18 +978,6 @@ static PyMethodDef module_methods[] = {
1126978
"to dump_tracebacks_later().")},
1127979
#endif
1128980

1129-
#ifdef FAULTHANDLER_WATCHDOG
1130-
{"_file_watchdog",
1131-
(PyCFunction)faulthandler_file_watchdog, METH_VARARGS|METH_KEYWORDS,
1132-
PyDoc_STR("_file_watchdog(rfd, wfd, period):\n"
1133-
"feed the contents of 'rfd' to 'wfd', if changed,\n"
1134-
"every 'period seconds'.")},
1135-
{"_cancel_file_watchdog",
1136-
(PyCFunction)faulthandler_cancel_file_watchdog, METH_NOARGS,
1137-
PyDoc_STR("_cancel_file_watchdog():\ncancel the previous call "
1138-
"to _file_watchdog().")},
1139-
#endif
1140-
1141981
#ifdef FAULTHANDLER_USER
1142982
{"register",
1143983
(PyCFunction)faulthandler_register_py, METH_VARARGS|METH_KEYWORDS,
@@ -1263,16 +1103,6 @@ int _PyFaulthandler_Init(void)
12631103
}
12641104
PyThread_acquire_lock(thread.cancel_event, 1);
12651105
#endif
1266-
#ifdef FAULTHANDLER_WATCHDOG
1267-
watchdog.cancel_event = PyThread_allocate_lock();
1268-
watchdog.running = PyThread_allocate_lock();
1269-
if (!watchdog.cancel_event || !watchdog.running) {
1270-
PyErr_SetString(PyExc_RuntimeError,
1271-
"could not allocate locks for faulthandler");
1272-
return -1;
1273-
}
1274-
PyThread_acquire_lock(watchdog.cancel_event, 1);
1275-
#endif
12761106

12771107
return faulthandler_env_options();
12781108
}
@@ -1297,20 +1127,6 @@ void _PyFaulthandler_Fini(void)
12971127
}
12981128
#endif
12991129

1300-
#ifdef FAULTHANDLER_WATCHDOG
1301-
/* file watchdog */
1302-
if (watchdog.cancel_event) {
1303-
cancel_file_watchdog();
1304-
PyThread_release_lock(watchdog.cancel_event);
1305-
PyThread_free_lock(watchdog.cancel_event);
1306-
watchdog.cancel_event = NULL;
1307-
}
1308-
if (watchdog.running) {
1309-
PyThread_free_lock(watchdog.running);
1310-
watchdog.running = NULL;
1311-
}
1312-
#endif
1313-
13141130
#ifdef FAULTHANDLER_USER
13151131
/* user */
13161132
if (user_signals != NULL) {

0 commit comments

Comments
 (0)