Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions benchmarks/dirty_benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,6 @@ def exception(self, msg, *args):
def reopen_files(self):
pass

def close_on_exec(self):
pass


class IsolatedBenchmark:
"""
Expand Down
3 changes: 0 additions & 3 deletions benchmarks/dirty_streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,6 @@ def warning(self, msg, *args):
def error(self, msg, *args):
pass

def close_on_exec(self):
pass

def reopen_files(self):
pass

Expand Down
1 change: 0 additions & 1 deletion examples/dirty_example/test_worker_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ def debug(self, msg, *args): print(f"[DEBUG] {msg % args if args else msg}")
def info(self, msg, *args): print(f"[INFO] {msg % args if args else msg}")
def warning(self, msg, *args): print(f"[WARN] {msg % args if args else msg}")
def error(self, msg, *args): print(f"[ERROR] {msg % args if args else msg}")
def close_on_exec(self): pass
def reopen_files(self): pass


Expand Down
2 changes: 0 additions & 2 deletions gunicorn/arbiter.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,6 @@ def init_signals(self):
Initialize master signal handling. Most of the signals
are queued. Child signals only wake up the master.
"""
self.log.close_on_exec()

# initialize all signals

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Continue invoking custom logger CLOEXEC hooks

When --logger-class supplies a custom logger whose close_on_exec() handles inherited or explicitly inheritable logging descriptors, this removal silently stops invoking that hook in both the master and workers. PEP 446 only changes defaults for descriptors created by Python and cannot replace custom cleanup logic, so those descriptors can leak through a master re-exec or an exec launched by application code; retain the lifecycle call even if the built-in logger implementation no longer needs to do work.

Useful? React with 👍 / 👎.

for s in self.SIGNALS:
signal.signal(s, self.signal)
Expand Down
4 changes: 0 additions & 4 deletions gunicorn/dirty/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,6 @@ def init_process(self):
# Reseed random number generator
util.seed()

# Prevent fd inheritance
util.close_on_exec(self.tmp.fileno())
self.log.close_on_exec()

# Set up signals
self.init_signals()

Expand Down
10 changes: 1 addition & 9 deletions gunicorn/glogging.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,15 +411,7 @@ def reopen_files(self):
handler.release()

def close_on_exec(self):
for log in loggers():
for handler in log.handlers:
if isinstance(handler, logging.FileHandler):
handler.acquire()
try:
if handler.stream:
util.close_on_exec(handler.stream.fileno())
finally:
handler.release()
pass

def _get_gunicorn_handler(self, log):
for h in log.handlers:
Expand Down
6 changes: 3 additions & 3 deletions gunicorn/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,9 @@ def parse_address(netloc, default_port='8000'):


def close_on_exec(fd):
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
flags |= fcntl.FD_CLOEXEC
fcntl.fcntl(fd, fcntl.F_SETFD, flags)
if not isinstance(fd, int):
fd = fd.fileno()
os.set_inheritable(fd, False)
Comment thread
methane marked this conversation as resolved.


def set_non_blocking(fd):
Expand Down
8 changes: 0 additions & 8 deletions gunicorn/workers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,9 @@ def init_process(self):
self.PIPE = os.pipe()
for p in self.PIPE:
util.set_non_blocking(p)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore CLOEXEC on worker listener sockets

When application code forks and execs a child without closing file descriptors (for example, subprocess.Popen(..., close_fds=False)), that child now inherits every Gunicorn listener. BaseSocket.set_options() deliberately marks listeners inheritable for the master's re-exec path, so PEP 446 does not protect them; this worker-side loop was what reversed that setting after the fork. A long-lived child can therefore keep the listening port bound after Gunicorn shuts down and prevent a clean restart, so the listeners still need to be made non-inheritable during worker initialization.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Arbitor of gunicorn uses os.fork() instead of Popen(). When creating self.LISTENERS, it is not set to inheritable, so it remains non-inheritable from the parent process. It is not necessary to set it after forking in the child process.

util.close_on_exec(p)

# 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]]

self.log.close_on_exec()

self.init_signals()

# start the reloader
Expand Down
1 change: 0 additions & 1 deletion gunicorn/workers/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ class SyncWorker(base.Worker):
def accept(self, listener):
client, addr = listener.accept()
client.setblocking(1)
util.close_on_exec(client)
self.handle(listener, client, addr)

def wait(self, timeout):
Expand Down
3 changes: 0 additions & 3 deletions tests/dirty/test_multi_app_routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,6 @@ def critical(self, msg, *args):
def exception(self, msg, *args):
self.messages.append(("exception", msg % args if args else msg))

def close_on_exec(self):
pass

def reopen_files(self):
pass

Expand Down
3 changes: 0 additions & 3 deletions tests/dirty/test_per_app_worker_allocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ def critical(self, msg, *args):
def exception(self, msg, *args):
self.messages.append(("exception", msg % args if args else msg))

def close_on_exec(self):
pass

def reopen_files(self):
pass

Expand Down
3 changes: 0 additions & 3 deletions tests/dirty/test_streaming_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ def warning(self, msg, *args):
def error(self, msg, *args):
self.messages.append(("error", msg % args if args else msg))

def close_on_exec(self):
pass

def reopen_files(self):
pass

Expand Down
3 changes: 0 additions & 3 deletions tests/test_dirty_arbiter.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,6 @@ def critical(self, msg, *args):
def exception(self, msg, *args):
self.messages.append(("exception", msg % args if args else msg))

def close_on_exec(self):
pass

def reopen_files(self):
pass

Expand Down
3 changes: 0 additions & 3 deletions tests/test_dirty_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ def debug(self, *a, **kw): pass
def info(self, *a, **kw): pass
def warning(self, *a, **kw): pass
def error(self, *a, **kw): pass
def close_on_exec(self): pass
def reopen_files(self): pass

cfg = Config()
Expand Down Expand Up @@ -205,7 +204,6 @@ def warning(self, *a, **kw): pass
def error(self, *a, **kw): pass
def critical(self, *a, **kw): pass
def exception(self, *a, **kw): pass
def close_on_exec(self): pass
def reopen_files(self): pass

cfg = Config()
Expand Down Expand Up @@ -297,7 +295,6 @@ def warning(self, *a, **kw): pass
def error(self, *a, **kw): pass
def critical(self, *a, **kw): pass
def exception(self, *a, **kw): pass
def close_on_exec(self): pass
def reopen_files(self): pass

cfg = Config()
Expand Down
3 changes: 0 additions & 3 deletions tests/test_dirty_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,6 @@ def warning(self, msg, *args):
def error(self, msg, *args):
self.messages.append(("error", msg % args if args else msg))

def close_on_exec(self):
pass

def reopen_files(self):
pass

Expand Down