From 21577fa2d5ee41ec00b9139bbdac056ea4cd3564 Mon Sep 17 00:00:00 2001 From: Rick Branson Date: Wed, 17 Jul 2024 17:22:30 -0700 Subject: [PATCH] Use in-memory notifications for workers --- gunicorn/arbiter.py | 7 ------ gunicorn/config.py | 22 ----------------- gunicorn/workers/base.py | 3 +-- gunicorn/workers/workertmp.py | 46 ++++------------------------------- tests/test_arbiter.py | 1 - 5 files changed, 6 insertions(+), 73 deletions(-) diff --git a/gunicorn/arbiter.py b/gunicorn/arbiter.py index 1cf436748..dca770754 100644 --- a/gunicorn/arbiter.py +++ b/gunicorn/arbiter.py @@ -556,7 +556,6 @@ def reap_workers(self): worker = self.WORKERS.pop(wpid, None) if not worker: continue - worker.tmp.close() self.cfg.child_exit(self, worker) except OSError as e: if e.errno != errno.ECHILD: @@ -596,10 +595,6 @@ def spawn_worker(self): self.WORKERS[pid] = worker return pid - # Do not inherit the temporary files of other workers - for sibling in self.WORKERS.values(): - sibling.tmp.close() - # Process Child worker.pid = os.getpid() try: @@ -624,7 +619,6 @@ def spawn_worker(self): finally: self.log.info("Worker exiting (pid: %s)", worker.pid) try: - worker.tmp.close() self.cfg.worker_exit(self, worker) except Exception: self.log.warning("Exception during worker exit:\n%s", @@ -664,7 +658,6 @@ def kill_worker(self, pid, sig): if e.errno == errno.ESRCH: try: worker = self.WORKERS.pop(pid) - worker.tmp.close() self.cfg.worker_exit(self, worker) return except (KeyError, OSError): diff --git a/gunicorn/config.py b/gunicorn/config.py index 144acaecc..7b45fedf6 100644 --- a/gunicorn/config.py +++ b/gunicorn/config.py @@ -1113,28 +1113,6 @@ class Pidfile(Setting): """ -class WorkerTmpDir(Setting): - name = "worker_tmp_dir" - section = "Server Mechanics" - cli = ["--worker-tmp-dir"] - meta = "DIR" - validator = validate_string - default = None - desc = """\ - A directory to use for the worker heartbeat temporary file. - - If not set, the default temporary directory will be used. - - .. note:: - The current heartbeat system involves calling ``os.fchmod`` on - temporary file handlers and may block a worker for arbitrary time - if the directory is on a disk-backed filesystem. - - See :ref:`blocking-os-fchmod` for more detailed information - and a solution for avoiding this problem. - """ - - class User(Setting): name = "user" section = "Server Mechanics" diff --git a/gunicorn/workers/base.py b/gunicorn/workers/base.py index f97d923c7..e36ff0f3b 100644 --- a/gunicorn/workers/base.py +++ b/gunicorn/workers/base.py @@ -61,7 +61,7 @@ def __init__(self, age, ppid, sockets, app, timeout, cfg, log): self.alive = True self.log = log - self.tmp = WorkerTmp(cfg) + self.tmp = WorkerTmp() def __str__(self): return "" % self.pid @@ -109,7 +109,6 @@ def init_process(self): # Prevent fd inheritance for s in self.sockets: util.close_on_exec(s) - util.close_on_exec(self.tmp.fileno()) self.wait_fds = self.sockets + [self.PIPE[0]] diff --git a/gunicorn/workers/workertmp.py b/gunicorn/workers/workertmp.py index a9ae39de0..5ad3351e3 100644 --- a/gunicorn/workers/workertmp.py +++ b/gunicorn/workers/workertmp.py @@ -3,52 +3,16 @@ # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. -import os +import multiprocessing import time -import platform -import tempfile - -from gunicorn import util - -PLATFORM = platform.system() -IS_CYGWIN = PLATFORM.startswith('CYGWIN') class WorkerTmp(object): - - def __init__(self, cfg): - old_umask = os.umask(cfg.umask) - fdir = cfg.worker_tmp_dir - if fdir and not os.path.isdir(fdir): - raise RuntimeError("%s doesn't exist. Can't create workertmp." % fdir) - fd, name = tempfile.mkstemp(prefix="wgunicorn-", dir=fdir) - os.umask(old_umask) - - # change the owner and group of the file if the worker will run as - # a different user or group, so that the worker can modify the file - if cfg.uid != os.geteuid() or cfg.gid != os.getegid(): - util.chown(name, cfg.uid, cfg.gid) - - # unlink the file so we don't leak temporary files - try: - if not IS_CYGWIN: - util.unlink(name) - # In Python 3.8, open() emits RuntimeWarning if buffering=1 for binary mode. - # Because we never write to this file, pass 0 to switch buffering off. - self._tmp = os.fdopen(fd, 'w+b', 0) - except Exception: - os.close(fd) - raise + def __init__(self): + self._val = multiprocessing.Value('d', lock=False) def notify(self): - new_time = time.monotonic() - os.utime(self._tmp.fileno(), (new_time, new_time)) + self._val.value = time.monotonic() def last_update(self): - return os.fstat(self._tmp.fileno()).st_mtime - - def fileno(self): - return self._tmp.fileno() - - def close(self): - return self._tmp.close() + return self._val.value diff --git a/tests/test_arbiter.py b/tests/test_arbiter.py index e856282c8..9b2def236 100644 --- a/tests/test_arbiter.py +++ b/tests/test_arbiter.py @@ -144,7 +144,6 @@ def test_arbiter_reap_workers(mock_os_waitpid): mock_worker = mock.Mock() arbiter.WORKERS = {42: mock_worker} arbiter.reap_workers() - mock_worker.tmp.close.assert_called_with() arbiter.cfg.child_exit.assert_called_with(arbiter, mock_worker)