diff --git a/copyparty/__main__.py b/copyparty/__main__.py index b2f72c61..8eb39cd2 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1234,6 +1234,7 @@ def add_general(ap, nc, srvname): ap2.add_argument("--mimes", action="store_true", help="list default mimetype mapping and exit") ap2.add_argument("--rmagic", action="store_true", help="do expensive analysis to improve accuracy of returned mimetypes; will make file-downloads, rss, and webdav slower (volflag=rmagic)") ap2.add_argument("-j", metavar="CORES", type=int, default=1, help="num cpu-cores for uploads/downloads (0=all); keeping the default is almost always best") + ap2.add_argument("--reload-sig", metavar="S", type=u, default=("" if ANYWIN else "USR1"), help="reload server config when unix-signal \033[33mS\033[0m is received; examples: [\033[32mSIGUSR1\033[0m], [\033[32mUSR1\033[0m], [\033[32m10\033[0m]") ap2.add_argument("--vc-url", metavar="URL", type=u, default="", help="URL to check for vulnerable versions (default-disabled)") ap2.add_argument("--vc-age", metavar="HOURS", type=int, default=3, help="how many hours to wait between vulnerability checks") ap2.add_argument("--vc-exit", action="store_true", help="panic and exit if current version is vulnerable") @@ -1706,6 +1707,7 @@ def add_logging(ap): ap2.add_argument("-lo", metavar="PATH", type=u, default="", help="logfile; use .txt for plaintext or .xz for compressed. Example: \033[32mcpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz\033[0m (NB: some errors may appear on STDOUT only)") ap2.add_argument("--flo", metavar="N", type=int, default=1, help="log format for \033[33m-lo\033[0m; [\033[32m1\033[0m]=classic/colors, [\033[32m2\033[0m]=no-color") ap2.add_argument("--rlo", metavar="TXT", type=u, default=".1", help="logrotate counter format; see \033[33m--help-rlo\033[0m") + ap2.add_argument("--logrot-sig", metavar="S", type=u, default="", help="immediately logrotate when unix-signal \033[33mS\033[0m is received; examples: [\033[32mSIGHUP\033[0m], [\033[32mHUP\033[0m], [\033[32m1\033[0m]") ap2.add_argument("--no-ansi", action="store_true", default=not VT100, help="disable colors; same as environment-variable NO_COLOR") ap2.add_argument("--ansi", action="store_true", help="force colors; overrides environment-variable NO_COLOR") ap2.add_argument("--no-logflush", action="store_true", help="don't flush the logfile after each write; tiny bit faster") @@ -1980,10 +1982,15 @@ def add_debug(ap): ap2.add_argument("--no-scandir", action="store_true", help="kernel-bug workaround: disable scandir; do a listdir + stat on each file instead") ap2.add_argument("--no-fastboot", action="store_true", help="wait for initial filesystem indexing before accepting client requests") ap2.add_argument("--no-htp", action="store_true", help="disable httpserver threadpool, create threads as-needed instead") + if ANYWIN or sys.version_info < (3, 7): + ap2.add_argument("--sig-thr", action="store_true", default=True, help=argparse.SUPPRESS) + else: + ap2.add_argument("--sig-thr", action="store_true", help="start separate thread for OS-signals (try this if CTRL-C is busted)") ap2.add_argument("--rm-sck", action="store_true", help="when listening on unix-sockets, do a basic delete+bind instead of the default atomic bind") ap2.add_argument("--srch-dbg", action="store_true", help="explain search processing, and do some extra expensive sanity checks") ap2.add_argument("--rclone-mdns", action="store_true", help="use mdns-domain instead of server-ip on /?hc") ap2.add_argument("--stackmon", metavar="P,S", type=u, default="", help="write stacktrace to \033[33mP\033[0math every \033[33mS\033[0m second, for example --stackmon=\033[32m./st/%%Y-%%m/%%d/%%H%%M.xz,60") + ap2.add_argument("--stack-sig", metavar="S", type=u, default="", help="show stacktrace when unix-signal \033[33mS\033[0m is received; examples: [\033[32mSIGUSR2\033[0m], [\033[32mUSR2\033[0m], [\033[32m12\033[0m]") ap2.add_argument("--log-thrs", metavar="SEC", type=float, default=0.0, help="list active threads every \033[33mSEC\033[0m") ap2.add_argument("--log-fk", metavar="REGEX", type=u, default="", help="log filekey params for files where path matches \033[33mREGEX\033[0m; [\033[32m.\033[0m] (a single dot) = all files") ap2.add_argument("--bak-flips", action="store_true", help="[up2k] if a client uploads a bitflipped/corrupted chunk, store a copy according to \033[33m--bf-nc\033[0m and \033[33m--bf-dir\033[0m") diff --git a/copyparty/svchub.py b/copyparty/svchub.py index 0f4325d0..28f5cc0c 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -82,6 +82,7 @@ from .util import ( odfusion, pybin, read_utf8, + signame2int, start_log_thrs, start_stackmon, termsize, @@ -107,6 +108,12 @@ if PY2: else: from urllib.request import Request, urlopen +try: + from queue import SimpleQueue +except: + # yuul b. alwright + from queue import Queue as SimpleQueue + VER_IDP_DB = 1 VER_SESSION_DB = 1 @@ -140,13 +147,9 @@ class SvcHub(object): self.logf: Optional[typing.TextIO] = None self.logf_base_fn = "" self.is_dut = False # running in unittest; always False - self.stop_req = False self.stopping = False self.stopped = False - self.reload_req = False self.reload_mutex = threading.Lock() - self.stop_cond = threading.Condition() - self.nsigs = 3 self.retcode = 0 self.httpsrv_up = 0 self.qr_tsz = None @@ -156,6 +159,12 @@ class SvcHub(object): self.cmon = 0 self.tstack = 0.0 + self.sig_logrot = -999 + self.sig_reload = -999 + self.sig_stack = -999 + self.nsigs = 7 + self.sig = SimpleQueue() + self.iphash = HMaccas(os.path.join(self.E.cfg, "iphash"), 8) if args.sss or args.s >= 3: @@ -1451,31 +1460,37 @@ class SvcHub(object): sigs = [signal.SIGINT, signal.SIGTERM] if not ANYWIN: - sigs.append(signal.SIGUSR1) + sigs.append(signal.SIGHUP) + + for (opt, mem) in ( + ("logrot_sig", "sig_logrot"), + ("reload_sig", "sig_reload"), + ("stack_sig", "sig_stack"), + ): + zs = getattr(self.args, opt) + if not zs: + continue + zi = signame2int(zs) + setattr(self, mem, zi) + sigs.append(zi) for sig in sigs: signal.signal(sig, self.signal_handler) - # macos hangs after shutdown on sigterm with while-sleep, - # windows cannot ^c stop_cond (and win10 does the macos thing but winxp is fine??) - # linux is fine with both, - # never lucky - if ANYWIN: - # msys-python probably fine but >msys-python - Daemon(self.stop_thr, "svchub-sig") + if self.args.sig_thr: + Daemon(self._signal_thr, "svchub-sig") try: - while not self.stop_req: + while not self.stopping: time.sleep(1) except: pass - self.shutdown() # cant join; eats signals on win10 while not self.stopped: time.sleep(0.1) else: - self.stop_thr() + self._signal_thr() def start_zeroconf(self) -> None: self.zc_ngen += 1 @@ -1533,17 +1548,6 @@ class SvcHub(object): self.asrv.load_sessions(True) self.broker.reload_sessions() - def stop_thr(self) -> None: - while not self.stop_req: - with self.stop_cond: - self.stop_cond.wait(9001) - - if self.reload_req: - self.reload_req = False - self.reload(True, True) - - self.shutdown() - def kill9(self, delay: float = 0.0) -> None: if delay > 0.01: time.sleep(delay) @@ -1556,26 +1560,42 @@ class SvcHub(object): os.kill(os.getpid(), signal.SIGKILL) def signal_handler(self, sig: int, frame: Optional[FrameType]) -> None: - if self.stopping: - if self.nsigs <= 0: + if sig in (signal.SIGINT, signal.SIGTERM): + self.nsigs -= 1 + + if self.nsigs == 0: try: threading.Thread(target=self.pr, args=("OMBO BREAKER",)).start() time.sleep(0.1) except: pass + if self.nsigs <= 0: self.kill9() - else: - self.nsigs -= 1 - return - if not ANYWIN and sig == signal.SIGUSR1: - self.reload_req = True + self.sig.put(sig) + + def _signal_thr(self) -> None: + while not self.stopping: + sig = self.sig.get() + self._signal_handler(sig) + + def _signal_handler(self, sig: int) -> None: + if sig == self.sig_logrot: + self.log("root", "signal: logrotate") + dt = datetime.now(self.tz) + self.logf_base_fn = "\t" + self._set_next_day(dt) + + elif sig == self.sig_reload: + self.log("root", "signal: reload") + self.reload(True, True) + + elif sig == self.sig_stack: + self.log("root", "signal: stack%s" % (alltrace(),)) + else: - self.stop_req = True - - with self.stop_cond: - self.stop_cond.notify_all() + self.shutdown() def shutdown(self) -> None: if self.stopping: @@ -1583,10 +1603,8 @@ class SvcHub(object): # start_log_thrs(print, 0.1, 1) + self.nsigs = 3 self.stopping = True - self.stop_req = True - with self.stop_cond: - self.stop_cond.notify_all() ret = 1 try: diff --git a/copyparty/util.py b/copyparty/util.py index 2d69d2fe..6396445f 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -1635,6 +1635,16 @@ def expand_osenv_cs(txt) -> str: raise Exception(t) +def signame2int(txt: str) -> int: + try: + return int(txt) + except: + txt = txt.upper() + if not txt.startswith("SIG"): + txt = "SIG" + txt + return int(getattr(signal, txt)) + + def rice_tid() -> str: tid = threading.current_thread().ident c = sunpack(b"B" * 5, spack(b">Q", tid)[-5:])