rda-python-common 3.0.30__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.
@@ -22,7 +22,7 @@ object that existing callers expect.
22
22
 
23
23
  from . import PgLOG, PgUtil, PgDBI, PgFile, PgLock, PgCMD, PgSIG, PgOPT, PgSplit
24
24
 
25
- __version__ = "3.0.30"
25
+ __version__ = "3.0.31"
26
26
 
27
27
  __all__ = [
28
28
  "PgLOG",
@@ -42,6 +42,7 @@ class PgSIG(PgDBI):
42
42
  super().__init__() # initialize parent class
43
43
  self.VUSERS = [] # allow users to start this daemon
44
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
45
46
  self.CBIDS = {} # allow upto 'bproc' background processes at one time for each child
46
47
  self.SDUMP = {
47
48
  'DEF': '/dev/null',
@@ -559,6 +560,10 @@ class PgSIG(PgDBI):
559
560
  def clean_dead_child(self, signum, frame):
560
561
  """Reap zombie child processes in response to SIGCHLD.
561
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
+
562
567
  Args:
563
568
  signum (int): The signal number received (expected to be SIGCHLD).
564
569
  frame: The current stack frame (unused).
@@ -578,8 +583,15 @@ class PgSIG(PgDBI):
578
583
  live += 1
579
584
  elif self.PGSIG['PPID'] < 2:
580
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)
581
590
  del self.CPIDS[dpid]
582
591
  wake = 1
592
+ elif dpid in self.CBIDS:
593
+ del self.CBIDS[dpid]
594
+ wake = 1
583
595
  if wake: self.wake_waiter() # a managed child is done; end any wait for it now
584
596
 
585
597
  # open the pipe a SIGCHLD handler writes to, so a wait for processes wakes up at once
@@ -587,7 +599,7 @@ class PgSIG(PgDBI):
587
599
  """Open the non-blocking self-pipe that wakes ``show_wait_message()`` on SIGCHLD.
588
600
 
589
601
  ``clean_dead_child()`` writes one byte to it when a child process in ``CPIDS``
590
- is done, and ``show_wait_message()`` waits on it with the adaptive wait time as
602
+ or a background process in ``CBIDS`` is done, and ``show_wait_message()`` waits on it with the adaptive wait time as
591
603
  the timeout, so a free process slot is used at once instead of after a sleep.
592
604
  """
593
605
  if self.PGSIG['WPIPE']: return
@@ -1176,8 +1188,8 @@ class PgSIG(PgDBI):
1176
1188
  open before starting. Defaults to 0.
1177
1189
 
1178
1190
  Returns:
1179
- int or str: Result from ``pgsystem`` (synchronous mode) or
1180
- ``record_background``.
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``.
1181
1193
  """
1182
1194
  if logact is None: logact = self.LOGWRN
1183
1195
  if self.PGSIG['BPROC'] < 2: return self.pgsystem(cmd, logact, cmdopt) # no background
@@ -1205,12 +1217,19 @@ class PgSIG(PgDBI):
1205
1217
  if not self.PGLOG['ERRFILE']:
1206
1218
  self.PGLOG['ERRFILE'] = re.sub(r'\.log$', '.err', self.PGLOG['LOGFILE'], 1)
1207
1219
  bckcmd += " 2>> {}/{}".format(self.PGLOG['LOGPATH'], self.PGLOG['ERRFILE'])
1208
- bckcmd += " &"
1209
- # shell=True is required for the redirections (>> / 2>>) and trailing '&';
1210
- # the '&' makes the shell fork the command and exit, so the command gets
1211
- # reparented to init (ppid=1), matching the lookup logic in record_background().
1212
- subprocess.Popen(bckcmd, shell=True)
1213
- return self.record_background(cmd, logact)
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
1214
1233
 
1215
1234
  # get background process id for given bcmd
1216
1235
  def bcmd2cbid(self, bcmd):
@@ -1261,13 +1280,13 @@ class PgSIG(PgDBI):
1261
1280
  # Bug fix: cannot delete from a dict while iterating over it;
1262
1281
  # iterate over a copy of the keys instead.
1263
1282
  cbids = list(self.CBIDS)
1264
- for bid in cbids:
1265
- if self.check_process(bid): # process is not done yet
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
1266
1285
  bcnt += 1
1267
1286
  else:
1268
- del self.CBIDS[bid]
1287
+ if cbid in self.CBIDS: del self.CBIDS[cbid] # SIGCHLD may have removed it
1269
1288
  if not (bcnt and dowait): break
1270
- self.show_wait_message(i, "{}: wait {}/{} background processes".format(self.PGSIG['DSTR'], bcnt, self.PGSIG['MPROC']), logact, dowait)
1289
+ self.show_wait_message(i, "{}: wait {}/{} background processes".format(self.PGSIG['DSTR'], bcnt, self.PGSIG['BPROC']), logact, dowait)
1271
1290
  i += 1
1272
1291
  bcnt = 0
1273
1292
  return bcnt
@@ -1307,7 +1326,7 @@ class PgSIG(PgDBI):
1307
1326
  acmd = line[idx+len(aname):]
1308
1327
  if uid == commonuser:
1309
1328
  acmd = re.sub(r'^\.(pl|py)\s+', '', acmd, 1)
1310
- if re.match(r'^{}{}'.format(aname, acmd), bcmd): continue
1329
+ if not bcmd.startswith(aname + acmd): continue # not the command started
1311
1330
  self.CBIDS[bid] = bcmd
1312
1331
  return 1
1313
1332
  except (psutil.NoSuchProcess, psutil.AccessDenied):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_common
3
- Version: 3.0.30
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.30`). If the import
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=t_HqEyWCT68DilDBF5TWAQxg5jGDA17eAq42v4-30Js,995
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=-jTKNmacOztzE2OQCE1GYRPwddDAI9s8voaHGWzgC_s,60653
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.30.dist-info/licenses/LICENSE,sha256=1dck4EAQwv8QweDWCXDx-4Or0S8YwiCstaso_H57Pno,1097
24
- rda_python_common-3.0.30.dist-info/METADATA,sha256=r3u-SRHrJBXzM7jTvDuuxxDqSs1gt52BZW7pjYmDz94,14530
25
- rda_python_common-3.0.30.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
26
- rda_python_common-3.0.30.dist-info/entry_points.txt,sha256=pZgVNWspcK-F1TbPav7C3C9NdeHDZMm_25fW9weix00,65
27
- rda_python_common-3.0.30.dist-info/top_level.txt,sha256=KVQmx7D3DD-jsiheqL8HdTrRE14hpRnZY5_ioMArA5k,18
28
- rda_python_common-3.0.30.dist-info/RECORD,,
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,,