rda-python-common 3.0.28__tar.gz → 3.0.30__tar.gz

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.
Files changed (34) hide show
  1. {rda_python_common-3.0.28/src/rda_python_common.egg-info → rda_python_common-3.0.30}/PKG-INFO +2 -2
  2. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/README.md +1 -1
  3. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/pyproject.toml +1 -1
  4. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/__init__.py +1 -1
  5. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/pg_sig.py +111 -8
  6. {rda_python_common-3.0.28 → rda_python_common-3.0.30/src/rda_python_common.egg-info}/PKG-INFO +2 -2
  7. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/LICENSE +0 -0
  8. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/setup.cfg +0 -0
  9. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/PgCMD.py +0 -0
  10. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/PgDBI.py +0 -0
  11. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/PgFile.py +0 -0
  12. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/PgLOG.py +0 -0
  13. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/PgLock.py +0 -0
  14. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/PgOPT.py +0 -0
  15. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/PgSIG.py +0 -0
  16. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/PgSplit.py +0 -0
  17. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/PgUtil.py +0 -0
  18. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/pg_cmd.py +0 -0
  19. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/pg_dbi.py +0 -0
  20. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/pg_file.py +0 -0
  21. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/pg_lock.py +0 -0
  22. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/pg_log.py +0 -0
  23. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/pg_opt.py +0 -0
  24. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/pg_password.py +0 -0
  25. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/pg_split.py +0 -0
  26. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/pg_util.py +0 -0
  27. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/pgpassword.py +0 -0
  28. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common/pgpassword.usg +0 -0
  29. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common.egg-info/SOURCES.txt +0 -0
  30. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common.egg-info/dependency_links.txt +0 -0
  31. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common.egg-info/entry_points.txt +0 -0
  32. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common.egg-info/requires.txt +0 -0
  33. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/src/rda_python_common.egg-info/top_level.txt +0 -0
  34. {rda_python_common-3.0.28 → rda_python_common-3.0.30}/test/test_common.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_common
3
- Version: 3.0.28
3
+ Version: 3.0.30
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.28`). If the import
192
+ You should see the installed version (currently `3.0.30`). 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
 
@@ -165,7 +165,7 @@ PgLOG.pglog("hello", PgLOG.LOGWRN)
165
165
  python -c "import rda_python_common; print(rda_python_common.__version__)"
166
166
  ```
167
167
 
168
- You should see the installed version (currently `3.0.28`). If the import
168
+ You should see the installed version (currently `3.0.30`). If the import
169
169
  fails, double-check that the active Python environment is the one where you
170
170
  ran `pip install`.
171
171
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rda_python_common"
7
- version = "3.0.28"
7
+ version = "3.0.30"
8
8
  authors = [
9
9
  { name="Zaihua Ji", email="zji@ucar.edu" },
10
10
  ]
@@ -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.28"
25
+ __version__ = "3.0.30"
26
26
 
27
27
  __all__ = [
28
28
  "PgLOG",
@@ -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
@@ -52,7 +53,12 @@ class PgSIG(PgDBI):
52
53
  'MPROC': 1, # default number of multiple processes
53
54
  'BPROC': 1, # default number of multiple background processes
54
55
  'ETIME': 20, # default error waiting time (in seconds)
55
- 'WTIME': 120, # default waiting time (in seconds)
56
+ 'WTIME': 120, # default waiting time (in seconds), the cap of an adaptive wait
57
+ 'WTMIN': 5, # shortest adaptive wait (in seconds) for child/background processes
58
+ 'WTINC': 5, # seconds added to the adaptive wait after each loop still waiting
59
+ 'CWTIME': 0, # current adaptive wait (in seconds), 0 before the first wait
60
+ 'WLOOP': -1, # loop count of the last adaptive wait sleep
61
+ 'WPIPE': None, # (read fd, write fd) of the pipe SIGCHLD writes to wake up a wait
56
62
  'DTIME': 600, # the daemon record refresh time (in seconds)
57
63
  'RTIME': 2400, # the web rda config unlocking and unconfigured system down waiting time (in seconds)
58
64
  'CTIME': 4800, # the lock cleaning & configued system down waiting time (in seconds)
@@ -154,6 +160,7 @@ class PgSIG(PgDBI):
154
160
  os.umask(0)
155
161
  # setup to catch signals in daemon only
156
162
  signal.signal(signal.SIGCHLD, self.clean_dead_child)
163
+ self.open_wake_pipe()
157
164
  signal.signal(signal.SIGQUIT, self.signal_catch)
158
165
  signal.signal(signal.SIGUSR1, self.signal_catch)
159
166
  signal.signal(signal.SIGUSR2, self.signal_catch)
@@ -556,7 +563,7 @@ class PgSIG(PgDBI):
556
563
  signum (int): The signal number received (expected to be SIGCHLD).
557
564
  frame: The current stack frame (unused).
558
565
  """
559
- live = 0
566
+ live = wake = 0
560
567
  while True:
561
568
  try:
562
569
  dpid, status = os.waitpid(-1, os.WNOHANG)
@@ -570,7 +577,57 @@ class PgSIG(PgDBI):
570
577
  if live > 0: break # wait twice if a process is still a live
571
578
  live += 1
572
579
  elif self.PGSIG['PPID'] < 2:
573
- if dpid in self.CPIDS: del self.CPIDS[dpid]
580
+ if dpid in self.CPIDS:
581
+ del self.CPIDS[dpid]
582
+ wake = 1
583
+ if wake: self.wake_waiter() # a managed child is done; end any wait for it now
584
+
585
+ # open the pipe a SIGCHLD handler writes to, so a wait for processes wakes up at once
586
+ def open_wake_pipe(self):
587
+ """Open the non-blocking self-pipe that wakes ``show_wait_message()`` on SIGCHLD.
588
+
589
+ ``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
591
+ the timeout, so a free process slot is used at once instead of after a sleep.
592
+ """
593
+ if self.PGSIG['WPIPE']: return
594
+ try:
595
+ rfd, wfd = os.pipe()
596
+ os.set_blocking(rfd, False)
597
+ os.set_blocking(wfd, False)
598
+ except OSError as e:
599
+ self.pglog("Cannot open wake pipe, wait by sleeping: {}".format(str(e)), self.LOGWRN)
600
+ return
601
+ self.PGSIG['WPIPE'] = (rfd, wfd)
602
+
603
+ # close the wake pipe, such as in a child process that inherited it
604
+ def close_wake_pipe(self):
605
+ """Close the SIGCHLD wake pipe and forget it."""
606
+ if not self.PGSIG['WPIPE']: return
607
+ for fd in self.PGSIG['WPIPE']:
608
+ try:
609
+ os.close(fd)
610
+ except OSError:
611
+ pass
612
+ self.PGSIG['WPIPE'] = None
613
+
614
+ # write one byte to the wake pipe to end a wait in show_wait_message()
615
+ def wake_waiter(self):
616
+ """Wake a wait in ``show_wait_message()``; a full pipe already wakes it."""
617
+ if not self.PGSIG['WPIPE']: return
618
+ try:
619
+ os.write(self.PGSIG['WPIPE'][1], b'x')
620
+ except OSError:
621
+ pass # pipe full (a wake is pending already) or closed
622
+
623
+ # read out all pending wake bytes, so a past wake does not end the next wait
624
+ def drain_wake_pipe(self):
625
+ """Read out any pending bytes of the SIGCHLD wake pipe."""
626
+ if not self.PGSIG['WPIPE']: return
627
+ try:
628
+ while os.read(self.PGSIG['WPIPE'][0], 512): pass
629
+ except OSError:
630
+ pass # BlockingIOError when empty
574
631
 
575
632
  # send signal to daemon and exit
576
633
  def signal_daemon(self, sname, aname, uname):
@@ -744,6 +801,7 @@ class PgSIG(PgDBI):
744
801
  self.PGSIG['PID'] = pid = os.getpid()
745
802
  self.PGSIG['MPROC'] = 1 # 1 in child process
746
803
  self.CBIDS = {} # empty backgroud proces info in case not
804
+ self.close_wake_pipe() # the wake pipe is the parent's
747
805
  self.PGSIG['DSTR'] += ": CPID {} for {}".format(pid, pname)
748
806
  self.cmdlog("CPID {} for {}".format(pid, pname))
749
807
  self.pgdisconnect(0) # disconnect database in child
@@ -789,6 +847,7 @@ class PgSIG(PgDBI):
789
847
  if logact&self.EXITLG: logact &= ~self.EXITLG
790
848
  ccnt = i = 0
791
849
  if dowait < 0: ccnt = 1 if (pid or pname) else self.PGSIG['MPROC']
850
+ self.drain_wake_pipe() # the check below covers any child done before now
792
851
  while True:
793
852
  pcnt = 0
794
853
  if not pid and pname: pid = self.pname2cpid(pname)
@@ -841,6 +900,7 @@ class PgSIG(PgDBI):
841
900
  self.check_vuser(uname, aname)
842
901
  signal.signal(signal.SIGQUIT, self.signal_catch) # catch quit signal only
843
902
  signal.signal(signal.SIGCHLD, self.clean_dead_child)
903
+ self.open_wake_pipe()
844
904
  self.PGSIG['DSTR'] = dstr
845
905
  self.PGSIG['DNAME'] = aname
846
906
  self.PGSIG['PPID'] = 0
@@ -1281,21 +1341,64 @@ class PgSIG(PgDBI):
1281
1341
  if wtime: time.sleep(wtime)
1282
1342
  return wtime
1283
1343
 
1284
- # show wait message every dintv and then sleep for PGSIG['WTIME']
1344
+ # show wait message every 30 loops and then sleep for an adaptive wait time
1285
1345
  def show_wait_message(self, loop, msg, logact=None, dowait=0):
1286
1346
  """Log a wait message every 30 loops and optionally sleep.
1287
1347
 
1288
1348
  Args:
1289
- loop (int): Current loop iteration count.
1349
+ loop (int): Current loop iteration count, 0 for the first sleep of a wait.
1290
1350
  msg (str): Message to log.
1291
1351
  logact (int, optional): Log action flags. Defaults to ``LOGWRN``.
1292
- dowait (int, optional): If non-zero, sleep for ``PGSIG['WTIME']``
1293
- seconds. Defaults to 0.
1352
+ dowait (int, optional): If non-zero, wait for the adaptive wait time
1353
+ from ``adaptive_wait_time()``, at most ``PGSIG['WTIME']`` seconds,
1354
+ or until a child process in ``CPIDS`` is done, whichever is first
1355
+ (see ``open_wake_pipe()``). Defaults to 0.
1294
1356
  """
1295
1357
  if logact is None: logact = self.LOGWRN
1296
1358
  if loop > 0 and (loop%30) == 0:
1297
1359
  self.pglog("{} at {}".format(msg, self.current_datetime()), logact)
1298
- if dowait: time.sleep(self.PGSIG['WTIME'])
1360
+ if not dowait: return
1361
+ wtime = self.adaptive_wait_time(loop)
1362
+ if self.PGSIG['WPIPE'] and self.PGSIG['PPID'] < 2:
1363
+ # wake as soon as a child is done; the adaptive time is only the longest wait
1364
+ rfd = self.PGSIG['WPIPE'][0]
1365
+ try:
1366
+ select.select([rfd], [], [], wtime)
1367
+ except (OSError, ValueError):
1368
+ time.sleep(wtime)
1369
+ self.drain_wake_pipe()
1370
+ else:
1371
+ time.sleep(wtime)
1372
+
1373
+ # get the adaptive time to sleep for one loop of a wait for processes
1374
+ def adaptive_wait_time(self, loop):
1375
+ """Return how long to sleep for one loop of a wait for child/background processes.
1376
+
1377
+ The wait starts at ``PGSIG['WTMIN']`` seconds and grows by ``PGSIG['WTINC']``
1378
+ after each loop that still finds no process done, capped at the polling time
1379
+ ``PGSIG['WTIME']``, so long-running processes are not checked too often while
1380
+ short ones are not waited on for the full polling time. The current wait
1381
+ carries over to the next wait; it goes back to ``WTMIN`` when the previous
1382
+ wait was over after its first sleep, since the processes are short. Set
1383
+ ``PGSIG['WTMIN']`` to ``WTIME`` or larger for the old fixed wait.
1384
+
1385
+ Args:
1386
+ loop (int): Loop count of the current wait, 0 for its first sleep.
1387
+
1388
+ Returns:
1389
+ int: Seconds to sleep.
1390
+ """
1391
+ wmax = self.PGSIG['WTIME']
1392
+ wmin = min(self.PGSIG['WTMIN'], wmax)
1393
+ wtime = self.PGSIG['CWTIME']
1394
+ if loop == 0:
1395
+ if not wtime or self.PGSIG['WLOOP'] == 0: wtime = wmin
1396
+ else:
1397
+ wtime += self.PGSIG['WTINC']
1398
+ wtime = max(wmin, min(wtime, wmax))
1399
+ self.PGSIG['CWTIME'] = wtime
1400
+ self.PGSIG['WLOOP'] = loop
1401
+ return wtime
1299
1402
 
1300
1403
  # register a time out function to raise a time out error
1301
1404
  @contextmanager
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_common
3
- Version: 3.0.28
3
+ Version: 3.0.30
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.28`). If the import
192
+ You should see the installed version (currently `3.0.30`). 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