-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
re-implement daemonize #8011
re-implement daemonize #8011
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Replace daemonize library with a local implementation. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
# -*- coding: utf-8 -*- | ||
# Copyright (c) 2012, 2013, 2014 Ilya Otyutskiy <[email protected]> | ||
# Copyright 2020 The Matrix.org Foundation C.I.C. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
import atexit | ||
import fcntl | ||
import logging | ||
import os | ||
import signal | ||
import sys | ||
|
||
|
||
def daemonize_process(pid_file: str, logger: logging.Logger, chdir: str = "/") -> None: | ||
"""daemonize the current process | ||
|
||
This calls fork(), and has the main process exit. When it returns we will be | ||
running in the child process. | ||
""" | ||
|
||
# If pidfile already exists, we should read pid from there; to overwrite it, if | ||
# locking will fail, because locking attempt somehow purges the file contents. | ||
if os.path.isfile(pid_file): | ||
with open(pid_file, "r") as pid_fh: | ||
old_pid = pid_fh.read() | ||
|
||
# Create a lockfile so that only one instance of this daemon is running at any time. | ||
try: | ||
lock_fh = open(pid_file, "w") | ||
except IOError: | ||
print("Unable to create the pidfile.") | ||
sys.exit(1) | ||
|
||
try: | ||
# Try to get an exclusive lock on the file. This will fail if another process | ||
# has the file locked. | ||
fcntl.flock(lock_fh, fcntl.LOCK_EX | fcntl.LOCK_NB) | ||
except IOError: | ||
print("Unable to lock on the pidfile.") | ||
# We need to overwrite the pidfile if we got here. | ||
# | ||
# XXX better to avoid overwriting it, surely. this looks racey as the pid file | ||
# could be created between us trying to read it and us trying to lock it. | ||
with open(pid_file, "w") as pid_fh: | ||
pid_fh.write(old_pid) | ||
sys.exit(1) | ||
|
||
# Fork, creating a new process for the child. | ||
process_id = os.fork() | ||
Comment on lines
+59
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Curious why the error handling code here was removed from the original library? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. basically: I kinda felt that we should be allowing exceptions raised in this function to propagate, rather than suddenly calling sys.exit. It's not particularly clear though. I could be persuaded to go either way. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The result should be the same either way. Showing the stack trace should give more info, I suppose. 👍 |
||
|
||
if process_id != 0: | ||
# parent process | ||
sys.exit(0) | ||
|
||
# This is the child process. Continue. | ||
|
||
# Stop listening for signals that the parent process receives. | ||
# This is done by getting a new process id. | ||
# setpgrp() is an alternative to setsid(). | ||
# setsid puts the process in a new parent group and detaches its controlling | ||
# terminal. | ||
|
||
os.setsid() | ||
Comment on lines
+68
to
+74
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, this differs from the original code by removing error handling. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. os.setsid returns |
||
|
||
# point stdin, stdout, stderr at /dev/null | ||
devnull = "/dev/null" | ||
if hasattr(os, "devnull"): | ||
# Python has set os.devnull on this system, use it instead as it might be | ||
# different than /dev/null. | ||
devnull = os.devnull | ||
|
||
devnull_fd = os.open(devnull, os.O_RDWR) | ||
os.dup2(devnull_fd, 0) | ||
os.dup2(devnull_fd, 1) | ||
os.dup2(devnull_fd, 2) | ||
os.close(devnull_fd) | ||
|
||
# Set umask to default to safe file permissions when running as a root daemon. 027 | ||
# is an octal number which we are typing as 0o27 for Python3 compatibility. | ||
os.umask(0o27) | ||
|
||
# Change to a known directory. If this isn't done, starting a daemon in a | ||
# subdirectory that needs to be deleted results in "directory busy" errors. | ||
os.chdir(chdir) | ||
|
||
try: | ||
lock_fh.write("%s" % (os.getpid())) | ||
lock_fh.flush() | ||
except IOError: | ||
logger.error("Unable to write pid to the pidfile.") | ||
print("Unable to write pid to the pidfile.") | ||
sys.exit(1) | ||
|
||
# write a log line on SIGTERM. | ||
def sigterm(signum, frame): | ||
logger.warning("Caught signal %s. Stopping daemon." % signum) | ||
sys.exit(0) | ||
|
||
signal.signal(signal.SIGTERM, sigterm) | ||
|
||
# Cleanup pid file at exit. | ||
def exit(): | ||
logger.warning("Stopping daemon.") | ||
os.remove(pid_file) | ||
sys.exit(0) | ||
|
||
atexit.register(exit) | ||
|
||
logger.warning("Starting daemon.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've realised that the original code wrapped this with a
try/except
for a reason, which is that after stderr gets redirected to /dev/null, any uncaught exceptions will otherwise get thrown away.I'm currently wondering how best to solve that.