rda-python-common 3.0.29__py3-none-any.whl → 3.0.31__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rda_python_common/__init__.py +1 -1
- rda_python_common/pg_sig.py +106 -19
- {rda_python_common-3.0.29.dist-info → rda_python_common-3.0.31.dist-info}/METADATA +2 -2
- {rda_python_common-3.0.29.dist-info → rda_python_common-3.0.31.dist-info}/RECORD +8 -8
- {rda_python_common-3.0.29.dist-info → rda_python_common-3.0.31.dist-info}/WHEEL +0 -0
- {rda_python_common-3.0.29.dist-info → rda_python_common-3.0.31.dist-info}/entry_points.txt +0 -0
- {rda_python_common-3.0.29.dist-info → rda_python_common-3.0.31.dist-info}/licenses/LICENSE +0 -0
- {rda_python_common-3.0.29.dist-info → rda_python_common-3.0.31.dist-info}/top_level.txt +0 -0
rda_python_common/__init__.py
CHANGED
rda_python_common/pg_sig.py
CHANGED
|
@@ -13,6 +13,7 @@ import re
|
|
|
13
13
|
import sys
|
|
14
14
|
import errno
|
|
15
15
|
import signal
|
|
16
|
+
import select
|
|
16
17
|
import time
|
|
17
18
|
import subprocess
|
|
18
19
|
import psutil
|
|
@@ -41,6 +42,7 @@ class PgSIG(PgDBI):
|
|
|
41
42
|
super().__init__() # initialize parent class
|
|
42
43
|
self.VUSERS = [] # allow users to start this daemon
|
|
43
44
|
self.CPIDS = {} # allow upto 'mproc' processes at one time for daemon
|
|
45
|
+
self.CEXITS = {} # pname -> exit code of a child process in CPIDS reaped by SIGCHLD
|
|
44
46
|
self.CBIDS = {} # allow upto 'bproc' background processes at one time for each child
|
|
45
47
|
self.SDUMP = {
|
|
46
48
|
'DEF': '/dev/null',
|
|
@@ -57,6 +59,7 @@ class PgSIG(PgDBI):
|
|
|
57
59
|
'WTINC': 5, # seconds added to the adaptive wait after each loop still waiting
|
|
58
60
|
'CWTIME': 0, # current adaptive wait (in seconds), 0 before the first wait
|
|
59
61
|
'WLOOP': -1, # loop count of the last adaptive wait sleep
|
|
62
|
+
'WPIPE': None, # (read fd, write fd) of the pipe SIGCHLD writes to wake up a wait
|
|
60
63
|
'DTIME': 600, # the daemon record refresh time (in seconds)
|
|
61
64
|
'RTIME': 2400, # the web rda config unlocking and unconfigured system down waiting time (in seconds)
|
|
62
65
|
'CTIME': 4800, # the lock cleaning & configued system down waiting time (in seconds)
|
|
@@ -158,6 +161,7 @@ class PgSIG(PgDBI):
|
|
|
158
161
|
os.umask(0)
|
|
159
162
|
# setup to catch signals in daemon only
|
|
160
163
|
signal.signal(signal.SIGCHLD, self.clean_dead_child)
|
|
164
|
+
self.open_wake_pipe()
|
|
161
165
|
signal.signal(signal.SIGQUIT, self.signal_catch)
|
|
162
166
|
signal.signal(signal.SIGUSR1, self.signal_catch)
|
|
163
167
|
signal.signal(signal.SIGUSR2, self.signal_catch)
|
|
@@ -556,11 +560,15 @@ class PgSIG(PgDBI):
|
|
|
556
560
|
def clean_dead_child(self, signum, frame):
|
|
557
561
|
"""Reap zombie child processes in response to SIGCHLD.
|
|
558
562
|
|
|
563
|
+
The exit code of a reaped child in ``CPIDS`` is kept in ``CEXITS`` under
|
|
564
|
+
its process name (negative signal number if killed by a signal), so a
|
|
565
|
+
parent can tell whether the work it gave the child succeeded.
|
|
566
|
+
|
|
559
567
|
Args:
|
|
560
568
|
signum (int): The signal number received (expected to be SIGCHLD).
|
|
561
569
|
frame: The current stack frame (unused).
|
|
562
570
|
"""
|
|
563
|
-
live = 0
|
|
571
|
+
live = wake = 0
|
|
564
572
|
while True:
|
|
565
573
|
try:
|
|
566
574
|
dpid, status = os.waitpid(-1, os.WNOHANG)
|
|
@@ -574,7 +582,64 @@ class PgSIG(PgDBI):
|
|
|
574
582
|
if live > 0: break # wait twice if a process is still a live
|
|
575
583
|
live += 1
|
|
576
584
|
elif self.PGSIG['PPID'] < 2:
|
|
577
|
-
if dpid in self.CPIDS:
|
|
585
|
+
if dpid in self.CPIDS:
|
|
586
|
+
if os.WIFEXITED(status):
|
|
587
|
+
self.CEXITS[self.CPIDS[dpid]] = os.WEXITSTATUS(status)
|
|
588
|
+
elif os.WIFSIGNALED(status):
|
|
589
|
+
self.CEXITS[self.CPIDS[dpid]] = -os.WTERMSIG(status)
|
|
590
|
+
del self.CPIDS[dpid]
|
|
591
|
+
wake = 1
|
|
592
|
+
elif dpid in self.CBIDS:
|
|
593
|
+
del self.CBIDS[dpid]
|
|
594
|
+
wake = 1
|
|
595
|
+
if wake: self.wake_waiter() # a managed child is done; end any wait for it now
|
|
596
|
+
|
|
597
|
+
# open the pipe a SIGCHLD handler writes to, so a wait for processes wakes up at once
|
|
598
|
+
def open_wake_pipe(self):
|
|
599
|
+
"""Open the non-blocking self-pipe that wakes ``show_wait_message()`` on SIGCHLD.
|
|
600
|
+
|
|
601
|
+
``clean_dead_child()`` writes one byte to it when a child process in ``CPIDS``
|
|
602
|
+
or a background process in ``CBIDS`` is done, and ``show_wait_message()`` waits on it with the adaptive wait time as
|
|
603
|
+
the timeout, so a free process slot is used at once instead of after a sleep.
|
|
604
|
+
"""
|
|
605
|
+
if self.PGSIG['WPIPE']: return
|
|
606
|
+
try:
|
|
607
|
+
rfd, wfd = os.pipe()
|
|
608
|
+
os.set_blocking(rfd, False)
|
|
609
|
+
os.set_blocking(wfd, False)
|
|
610
|
+
except OSError as e:
|
|
611
|
+
self.pglog("Cannot open wake pipe, wait by sleeping: {}".format(str(e)), self.LOGWRN)
|
|
612
|
+
return
|
|
613
|
+
self.PGSIG['WPIPE'] = (rfd, wfd)
|
|
614
|
+
|
|
615
|
+
# close the wake pipe, such as in a child process that inherited it
|
|
616
|
+
def close_wake_pipe(self):
|
|
617
|
+
"""Close the SIGCHLD wake pipe and forget it."""
|
|
618
|
+
if not self.PGSIG['WPIPE']: return
|
|
619
|
+
for fd in self.PGSIG['WPIPE']:
|
|
620
|
+
try:
|
|
621
|
+
os.close(fd)
|
|
622
|
+
except OSError:
|
|
623
|
+
pass
|
|
624
|
+
self.PGSIG['WPIPE'] = None
|
|
625
|
+
|
|
626
|
+
# write one byte to the wake pipe to end a wait in show_wait_message()
|
|
627
|
+
def wake_waiter(self):
|
|
628
|
+
"""Wake a wait in ``show_wait_message()``; a full pipe already wakes it."""
|
|
629
|
+
if not self.PGSIG['WPIPE']: return
|
|
630
|
+
try:
|
|
631
|
+
os.write(self.PGSIG['WPIPE'][1], b'x')
|
|
632
|
+
except OSError:
|
|
633
|
+
pass # pipe full (a wake is pending already) or closed
|
|
634
|
+
|
|
635
|
+
# read out all pending wake bytes, so a past wake does not end the next wait
|
|
636
|
+
def drain_wake_pipe(self):
|
|
637
|
+
"""Read out any pending bytes of the SIGCHLD wake pipe."""
|
|
638
|
+
if not self.PGSIG['WPIPE']: return
|
|
639
|
+
try:
|
|
640
|
+
while os.read(self.PGSIG['WPIPE'][0], 512): pass
|
|
641
|
+
except OSError:
|
|
642
|
+
pass # BlockingIOError when empty
|
|
578
643
|
|
|
579
644
|
# send signal to daemon and exit
|
|
580
645
|
def signal_daemon(self, sname, aname, uname):
|
|
@@ -748,6 +813,7 @@ class PgSIG(PgDBI):
|
|
|
748
813
|
self.PGSIG['PID'] = pid = os.getpid()
|
|
749
814
|
self.PGSIG['MPROC'] = 1 # 1 in child process
|
|
750
815
|
self.CBIDS = {} # empty backgroud proces info in case not
|
|
816
|
+
self.close_wake_pipe() # the wake pipe is the parent's
|
|
751
817
|
self.PGSIG['DSTR'] += ": CPID {} for {}".format(pid, pname)
|
|
752
818
|
self.cmdlog("CPID {} for {}".format(pid, pname))
|
|
753
819
|
self.pgdisconnect(0) # disconnect database in child
|
|
@@ -793,6 +859,7 @@ class PgSIG(PgDBI):
|
|
|
793
859
|
if logact&self.EXITLG: logact &= ~self.EXITLG
|
|
794
860
|
ccnt = i = 0
|
|
795
861
|
if dowait < 0: ccnt = 1 if (pid or pname) else self.PGSIG['MPROC']
|
|
862
|
+
self.drain_wake_pipe() # the check below covers any child done before now
|
|
796
863
|
while True:
|
|
797
864
|
pcnt = 0
|
|
798
865
|
if not pid and pname: pid = self.pname2cpid(pname)
|
|
@@ -845,6 +912,7 @@ class PgSIG(PgDBI):
|
|
|
845
912
|
self.check_vuser(uname, aname)
|
|
846
913
|
signal.signal(signal.SIGQUIT, self.signal_catch) # catch quit signal only
|
|
847
914
|
signal.signal(signal.SIGCHLD, self.clean_dead_child)
|
|
915
|
+
self.open_wake_pipe()
|
|
848
916
|
self.PGSIG['DSTR'] = dstr
|
|
849
917
|
self.PGSIG['DNAME'] = aname
|
|
850
918
|
self.PGSIG['PPID'] = 0
|
|
@@ -1120,8 +1188,8 @@ class PgSIG(PgDBI):
|
|
|
1120
1188
|
open before starting. Defaults to 0.
|
|
1121
1189
|
|
|
1122
1190
|
Returns:
|
|
1123
|
-
int or str: Result from ``pgsystem`` (synchronous mode) or
|
|
1124
|
-
``
|
|
1191
|
+
int or str: Result from ``pgsystem`` (synchronous mode), or 1 when the
|
|
1192
|
+
background process started and its shell pid is recorded in ``CBIDS``.
|
|
1125
1193
|
"""
|
|
1126
1194
|
if logact is None: logact = self.LOGWRN
|
|
1127
1195
|
if self.PGSIG['BPROC'] < 2: return self.pgsystem(cmd, logact, cmdopt) # no background
|
|
@@ -1149,12 +1217,19 @@ class PgSIG(PgDBI):
|
|
|
1149
1217
|
if not self.PGLOG['ERRFILE']:
|
|
1150
1218
|
self.PGLOG['ERRFILE'] = re.sub(r'\.log$', '.err', self.PGLOG['LOGFILE'], 1)
|
|
1151
1219
|
bckcmd += " 2>> {}/{}".format(self.PGLOG['LOGPATH'], self.PGLOG['ERRFILE'])
|
|
1152
|
-
|
|
1153
|
-
#
|
|
1154
|
-
#
|
|
1155
|
-
#
|
|
1156
|
-
|
|
1157
|
-
|
|
1220
|
+
# shell=True is required for the redirections (>> / 2>>). The shell runs the
|
|
1221
|
+
# command and lives as long as it does, so its pid, a direct child, is the one
|
|
1222
|
+
# to track: check_process() sees it running until it is done, and SIGCHLD reaps
|
|
1223
|
+
# it. A new session keeps it running if this process is interrupted, as the old
|
|
1224
|
+
# '&' did. (The old way searched for the orphaned '&' command with ppid 1 in
|
|
1225
|
+
# record_background(), whose match test was inverted, so it never recorded the
|
|
1226
|
+
# command it had just started: nothing was throttled or waited for.)
|
|
1227
|
+
try:
|
|
1228
|
+
proc = subprocess.Popen(bckcmd, shell=True, start_new_session=True)
|
|
1229
|
+
except Exception as e:
|
|
1230
|
+
return self.pglog("{}: cannot start background process: {}".format(cmd, str(e)), act)
|
|
1231
|
+
self.CBIDS[proc.pid] = cmd
|
|
1232
|
+
return 1
|
|
1158
1233
|
|
|
1159
1234
|
# get background process id for given bcmd
|
|
1160
1235
|
def bcmd2cbid(self, bcmd):
|
|
@@ -1205,13 +1280,13 @@ class PgSIG(PgDBI):
|
|
|
1205
1280
|
# Bug fix: cannot delete from a dict while iterating over it;
|
|
1206
1281
|
# iterate over a copy of the keys instead.
|
|
1207
1282
|
cbids = list(self.CBIDS)
|
|
1208
|
-
for
|
|
1209
|
-
if self.check_process(
|
|
1283
|
+
for cbid in cbids: # not bid: it would turn the next loop into a one-pid check
|
|
1284
|
+
if self.check_process(cbid): # process is not done yet
|
|
1210
1285
|
bcnt += 1
|
|
1211
1286
|
else:
|
|
1212
|
-
del self.CBIDS[
|
|
1287
|
+
if cbid in self.CBIDS: del self.CBIDS[cbid] # SIGCHLD may have removed it
|
|
1213
1288
|
if not (bcnt and dowait): break
|
|
1214
|
-
self.show_wait_message(i, "{}: wait {}/{} background processes".format(self.PGSIG['DSTR'], bcnt, self.PGSIG['
|
|
1289
|
+
self.show_wait_message(i, "{}: wait {}/{} background processes".format(self.PGSIG['DSTR'], bcnt, self.PGSIG['BPROC']), logact, dowait)
|
|
1215
1290
|
i += 1
|
|
1216
1291
|
bcnt = 0
|
|
1217
1292
|
return bcnt
|
|
@@ -1251,7 +1326,7 @@ class PgSIG(PgDBI):
|
|
|
1251
1326
|
acmd = line[idx+len(aname):]
|
|
1252
1327
|
if uid == commonuser:
|
|
1253
1328
|
acmd = re.sub(r'^\.(pl|py)\s+', '', acmd, 1)
|
|
1254
|
-
if
|
|
1329
|
+
if not bcmd.startswith(aname + acmd): continue # not the command started
|
|
1255
1330
|
self.CBIDS[bid] = bcmd
|
|
1256
1331
|
return 1
|
|
1257
1332
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
@@ -1293,14 +1368,26 @@ class PgSIG(PgDBI):
|
|
|
1293
1368
|
loop (int): Current loop iteration count, 0 for the first sleep of a wait.
|
|
1294
1369
|
msg (str): Message to log.
|
|
1295
1370
|
logact (int, optional): Log action flags. Defaults to ``LOGWRN``.
|
|
1296
|
-
dowait (int, optional): If non-zero,
|
|
1297
|
-
from ``adaptive_wait_time()``, at most ``PGSIG['WTIME']`` seconds
|
|
1298
|
-
|
|
1371
|
+
dowait (int, optional): If non-zero, wait for the adaptive wait time
|
|
1372
|
+
from ``adaptive_wait_time()``, at most ``PGSIG['WTIME']`` seconds,
|
|
1373
|
+
or until a child process in ``CPIDS`` is done, whichever is first
|
|
1374
|
+
(see ``open_wake_pipe()``). Defaults to 0.
|
|
1299
1375
|
"""
|
|
1300
1376
|
if logact is None: logact = self.LOGWRN
|
|
1301
1377
|
if loop > 0 and (loop%30) == 0:
|
|
1302
1378
|
self.pglog("{} at {}".format(msg, self.current_datetime()), logact)
|
|
1303
|
-
if dowait:
|
|
1379
|
+
if not dowait: return
|
|
1380
|
+
wtime = self.adaptive_wait_time(loop)
|
|
1381
|
+
if self.PGSIG['WPIPE'] and self.PGSIG['PPID'] < 2:
|
|
1382
|
+
# wake as soon as a child is done; the adaptive time is only the longest wait
|
|
1383
|
+
rfd = self.PGSIG['WPIPE'][0]
|
|
1384
|
+
try:
|
|
1385
|
+
select.select([rfd], [], [], wtime)
|
|
1386
|
+
except (OSError, ValueError):
|
|
1387
|
+
time.sleep(wtime)
|
|
1388
|
+
self.drain_wake_pipe()
|
|
1389
|
+
else:
|
|
1390
|
+
time.sleep(wtime)
|
|
1304
1391
|
|
|
1305
1392
|
# get the adaptive time to sleep for one loop of a wait for processes
|
|
1306
1393
|
def adaptive_wait_time(self, loop):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rda_python_common
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.31
|
|
4
4
|
Summary: RDA Python common library codes shared by other RDA python packages
|
|
5
5
|
Author-email: Zaihua Ji <zji@ucar.edu>
|
|
6
6
|
Project-URL: Homepage, https://github.com/NCAR/rda-python-common
|
|
@@ -189,7 +189,7 @@ PgLOG.pglog("hello", PgLOG.LOGWRN)
|
|
|
189
189
|
python -c "import rda_python_common; print(rda_python_common.__version__)"
|
|
190
190
|
```
|
|
191
191
|
|
|
192
|
-
You should see the installed version (currently `3.0.
|
|
192
|
+
You should see the installed version (currently `3.0.31`). If the import
|
|
193
193
|
fails, double-check that the active Python environment is the one where you
|
|
194
194
|
ran `pip install`.
|
|
195
195
|
|
|
@@ -7,7 +7,7 @@ rda_python_common/PgOPT.py,sha256=0nQPF19N-1VWwrjFHEeruVSwVDwvFKd4uowPc8dK7e4,56
|
|
|
7
7
|
rda_python_common/PgSIG.py,sha256=0MJPH0fTtSshIsvUXkzQjReHPyCWdzHYOY5LbFSh7-A,34318
|
|
8
8
|
rda_python_common/PgSplit.py,sha256=En7xcdkE4YNRcMX88qXnrUgUOnQU3Kqh8uxRIdlJhEA,9156
|
|
9
9
|
rda_python_common/PgUtil.py,sha256=7hXAznAI8tYh-nJRKBIXejNe2ROQ2Wxn28OAndeg0Iw,48433
|
|
10
|
-
rda_python_common/__init__.py,sha256=
|
|
10
|
+
rda_python_common/__init__.py,sha256=FTPc_6PSJ2E2CjQET_-Dbs3zGy8sewMbnI7GMCtQXbk,995
|
|
11
11
|
rda_python_common/pg_cmd.py,sha256=MYldUuM8mSUIT34yKQyUd0i4NL4zdSB8C6Jcc28NRxA,33809
|
|
12
12
|
rda_python_common/pg_dbi.py,sha256=ASIW2vsIjMab2TMfhqJiXS7LQhKKGpKjzNMqo7RCePw,122415
|
|
13
13
|
rda_python_common/pg_file.py,sha256=7NipcgMy4t67zbH2MHtRL3vxyY1FHdIgMTQ-kE0H2fA,181122
|
|
@@ -15,14 +15,14 @@ rda_python_common/pg_lock.py,sha256=31EaVDjCkcx3-n8-KnzG18R8Pz7Z6KyFsEqcml6Iq5c,
|
|
|
15
15
|
rda_python_common/pg_log.py,sha256=erQN4YYPwiORa3OE0Jt8s05rlWsjtyIs0fJ_KLbNAQ8,82959
|
|
16
16
|
rda_python_common/pg_opt.py,sha256=LxVAf47wi2STkVGSXMvV3IZphEJcoO8bvYXy6kQ5ox4,82403
|
|
17
17
|
rda_python_common/pg_password.py,sha256=X-eIDwdqBhtrhrbDTNWle-0JtWsyIVZdDOZaBu7cFHM,2343
|
|
18
|
-
rda_python_common/pg_sig.py,sha256=
|
|
18
|
+
rda_python_common/pg_sig.py,sha256=gmOIHRx-SqiFmjQorQcJoEsiXoXI-akDrYoP3SuKVfM,62032
|
|
19
19
|
rda_python_common/pg_split.py,sha256=aAWKUZPmZ-LQ_fJ3DSKzPKxJw0fMDJ2fgP8ZT629M30,16375
|
|
20
20
|
rda_python_common/pg_util.py,sha256=JL9NoW-VoQvsjXsUr09oBUWYJJNltU4GyQIfWlpkf90,86084
|
|
21
21
|
rda_python_common/pgpassword.py,sha256=aTQaO59QokWrv2EKom4TvKhNW2Mr6aFMMBVG4DCbcKI,5023
|
|
22
22
|
rda_python_common/pgpassword.usg,sha256=Wrle6A8sdlMGx3ZEAmE6TNDPOlG84Whe0T3rCNFzPfY,2013
|
|
23
|
-
rda_python_common-3.0.
|
|
24
|
-
rda_python_common-3.0.
|
|
25
|
-
rda_python_common-3.0.
|
|
26
|
-
rda_python_common-3.0.
|
|
27
|
-
rda_python_common-3.0.
|
|
28
|
-
rda_python_common-3.0.
|
|
23
|
+
rda_python_common-3.0.31.dist-info/licenses/LICENSE,sha256=1dck4EAQwv8QweDWCXDx-4Or0S8YwiCstaso_H57Pno,1097
|
|
24
|
+
rda_python_common-3.0.31.dist-info/METADATA,sha256=MH_cd7VJNUJsx2lMUxE4MaKuwoRAVSbZb0kyywMbOYk,14530
|
|
25
|
+
rda_python_common-3.0.31.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
26
|
+
rda_python_common-3.0.31.dist-info/entry_points.txt,sha256=pZgVNWspcK-F1TbPav7C3C9NdeHDZMm_25fW9weix00,65
|
|
27
|
+
rda_python_common-3.0.31.dist-info/top_level.txt,sha256=KVQmx7D3DD-jsiheqL8HdTrRE14hpRnZY5_ioMArA5k,18
|
|
28
|
+
rda_python_common-3.0.31.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|