Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

[WIP/Need Help] Redirect stdio to a file with synctl --daemonize #6169

Closed
wants to merge 4 commits into from
Closed
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ _trial_temp*/
/*.log
/*.log.config
/*.pid
/*.out
/.python-version
/*.signing.key
/env/
Expand Down
8 changes: 5 additions & 3 deletions synapse/app/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import sys
import traceback

from daemonize import Daemonize
from synapse.app import _daemonize

from twisted.internet import defer, error, reactor
from twisted.protocols.tls import TLSMemoryBIOFactory
Expand Down Expand Up @@ -71,6 +71,7 @@ def start_worker_reactor(appname, config, run_command=reactor.run):
soft_file_limit=config.soft_file_limit,
gc_thresholds=config.gc_thresholds,
pid_file=config.worker_pid_file,
outfile=config.out_file,
daemonize=config.worker_daemonize,
print_pidfile=config.print_pidfile,
logger=logger,
Expand All @@ -83,6 +84,7 @@ def start_reactor(
soft_file_limit,
gc_thresholds,
pid_file,
outfile,
daemonize,
print_pidfile,
logger,
Expand Down Expand Up @@ -126,13 +128,13 @@ def run():
if print_pidfile:
print(pid_file)

daemon = Daemonize(
daemon = _daemonize.Daemonize(
app=appname,
pid=pid_file,
action=run,
auto_close_fds=False,
verbose=True,
logger=logger,
outfile=outfile
)
daemon.start()
else:
Expand Down
143 changes: 143 additions & 0 deletions synapse/app/_daemonize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# -*- coding: utf-8 -*
# HEAVILY "inspired" by https://github.com/thesharp/daemonize/blob/master/daemonize.py
# TODO: License header
# app, pid, action, autoclose, verbose, logger,


import fcntl
import os
import pwd
import grp
import sys
import signal
import resource
import logging
import atexit
from logging import handlers
import traceback


__version__ = "2.5.0"


class Daemonize(object):
"""
Daemonize object.

Object constructor expects three arguments.

:param app: contains the application name which will be sent to syslog.
:param pid: path to the pidfile.
:param action: your custom function which will be executed after daemonization.
:param verbose: send debug messages to logger if provided.
:param logger: use this logger object instead of creating new one, if provided.
:param chdir: change working directory if provided or /
"""
def __init__(self, app, pid, action,
keep_fds=None, auto_close_fds=True, privileged_action=None,
user=None, group=None, verbose=False, logger=None,
foreground=False, chdir="/", outfile="homeserver.out"):
self.app = app
self.pid = os.path.abspath(pid)
self.outfile = os.path.abspath(outfile)
self.action = action
self.logger = logger
self.verbose = verbose
self.chdir = chdir

def sigterm(self, signum, frame):
"""
These actions will be done after SIGTERM.
"""
self.logger.warning("Caught signal %s. Stopping daemon." % signum)
sys.exit(0)

def exit(self):
"""
Cleanup pid file at exit.
"""
self.logger.warning("Stopping daemon.")
os.remove(self.pid)
sys.exit(0)

def start(self):
"""
Start daemonization 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(self.pid):
with open(self.pid, "r") as old_pidfile:
old_pid = old_pidfile.read()
# Create a lockfile so that only one instance of this daemon is running at any time.
try:
lockfile = open(self.pid, "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(lockfile, 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.
with open(self.pid, "w") as pidfile:
pidfile.write(old_pid)
sys.exit(1)

# Fork, creating a new process for the child.
try:
process_id = os.fork()
except OSError as e:
self.logger.error("Unable to fork, errno: {0}".format(e.errno))
sys.exit(1)
if process_id != 0:
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.
process_id = os.setsid()
if process_id == -1:
# Uh oh, there was a problem.
sys.exit(1)

with open(self.outfile, 'w+') as f:
f.write("***STARTED STDIO REDIRECT FILE")
os.dup2(f.fileno(), 2)
os.dup2(f.fileno(), 1)



# 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(self.chdir)

try:
lockfile.write("%s" % (os.getpid()))
lockfile.flush()
except IOError:
self.logger.error("Unable to write pid to the pidfile.")
print("Unable to write pid to the pidfile.")
sys.exit(1)

# Set custom action on SIGTERM.
signal.signal(signal.SIGTERM, self.sigterm)
atexit.register(self.exit)

self.logger.warning("Starting daemon.")


try:
self.action()
except Exception:
for line in traceback.format_exc().split("\n"):
self.logger.error(line)
1 change: 1 addition & 0 deletions synapse/app/homeserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,7 @@ def start_generate_monthly_active_users():
daemonize=hs.config.daemonize,
print_pidfile=hs.config.print_pidfile,
logger=logger,
outfile=hs.config.out_file
)


Expand Down
11 changes: 7 additions & 4 deletions synapse/config/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def add_arguments(parser):
"--no-redirect-stdio",
action="store_true",
default=None,
help="Do not redirect stdout/stderr to the log",
help="[DEFUNCT] Do not redirect stdout/stderr to the log",
)

def generate_files(self, config, config_dir_path):
Expand Down Expand Up @@ -170,9 +170,10 @@ def _log(event):

return observer(event)

logBeginner.beginLoggingTo([_log], redirectStandardIO=not config.no_redirect_stdio)
if not config.no_redirect_stdio:
print("Redirected stdout/stderr to logs")
# logBeginner.beginLoggingTo([_log], redirectStandardIO=not config.no_redirect_stdio)
logBeginner.beginLoggingTo([_log], redirectStandardIO=False)
# if not config.no_redirect_stdio:
# print("Redirected stdout/stderr to logs")

return observer

Expand Down Expand Up @@ -237,4 +238,6 @@ def read_config(*args, callback=None):
logging.warn("Server %s version %s", sys.argv[0], get_version_string(synapse))
logging.info("Server hostname: %s", config.server_name)

logging.warn("--no-redirect-stdio is ignored. STDOUT/ERR is redirected to out_file defined in the server config")

return logger
7 changes: 7 additions & 0 deletions synapse/config/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def read_config(self, config, **kwargs):
raise ConfigError(str(e))

self.pid_file = self.abspath(config.get("pid_file"))
self.out_file = self.abspath(config.get("out_file"))
self.web_client_location = config.get("web_client_location", None)
self.soft_file_limit = config.get("soft_file_limit", 0)
self.daemonize = config.get("daemonize")
Expand Down Expand Up @@ -384,6 +385,7 @@ def generate_config_section(
unsecure_port = 8008

pid_file = os.path.join(data_dir_path, "homeserver.pid")
out_file = os.path.join(data_dir_path, "homeserver.out")

# Bring DEFAULT_ROOM_VERSION into the local-scope for use in the
# default config string
Expand Down Expand Up @@ -467,6 +469,11 @@ def generate_config_section(
#
pid_file: %(pid_file)s


# When running as a daemon, the file to redirect stdout and stderr to
#
out_file: %(out_file)s

# The path to the web client which will be served at /_matrix/client/
# if 'webclient' is configured under the 'listeners' configuration.
#
Expand Down