Remove mutable global variables from daemon script

This commit is contained in:
Foo 2016-08-20 20:57:42 +03:00
parent 03e63fc8d2
commit 25657089db
1 changed files with 88 additions and 85 deletions

View File

@ -6,6 +6,9 @@ import socket
import os
import errno
import sys
import fcntl
import atexit
import stat
from argparse import ArgumentParser
from select import select
@ -15,6 +18,7 @@ from functools import partial
from io import BytesIO
from threading import Event
from itertools import chain
from logging import StreamHandler
from powerline.shell import ShellPowerline
from powerline.commands.main import finish_args, write_output
@ -26,12 +30,7 @@ from powerline.commands.main import get_argparser as get_main_argparser
from powerline.commands.daemon import get_argparser as get_daemon_argparser
is_daemon = False
use_filesystem = not sys.platform.lower().startswith('linux')
address = None
pidfile = None
ts_shutdown_event = Event()
USE_FILESYSTEM = not sys.platform.lower().startswith('linux')
class NonInteractiveArgParser(ArgumentParser):
@ -48,47 +47,48 @@ class NonInteractiveArgParser(ArgumentParser):
raise Exception(self.format_usage())
parser = get_main_argparser(NonInteractiveArgParser)
EOF = b'EOF\0\0'
powerlines = {}
logger = None
config_loader = None
home = os.path.expanduser('~')
started_wm_threads = {}
class State(object):
__slots__ = ('powerlines', 'logger', 'config_loader', 'started_wm_threads',
'ts_shutdown_event')
def __init__(self, **kwargs):
self.logger = None
self.config_loader = None
self.started_wm_threads = {}
self.powerlines = {}
self.ts_shutdown_event = Event()
class PowerlineDaemon(ShellPowerline):
HOME = os.path.expanduser('~')
class NonDaemonShellPowerline(ShellPowerline):
def get_log_handler(self):
if not is_daemon:
import logging
return logging.StreamHandler()
return super(PowerlineDaemon, self).get_log_handler()
return StreamHandler()
def start_wm(args, environ, cwd):
def start_wm(args, environ, cwd, is_daemon, state):
wm_name = args.ext[0][3:]
if wm_name in started_wm_threads:
if wm_name in state.started_wm_threads:
return b''
thread_shutdown_event = Event()
thread = wm_threads[wm_name](
thread_shutdown_event=thread_shutdown_event,
pl_shutdown_event=ts_shutdown_event,
pl_config_loader=config_loader,
pl_shutdown_event=state.ts_shutdown_event,
pl_config_loader=state.config_loader,
)
thread.start()
started_wm_threads[wm_name] = (thread, thread_shutdown_event)
state.started_wm_threads[wm_name] = (thread, thread_shutdown_event)
return b''
def render(args, environ, cwd):
global logger
global config_loader
def render(args, environ, cwd, is_daemon, state):
segment_info = {
'getcwd': lambda: cwd,
'home': environ.get('HOME', home),
'home': environ.get('HOME', HOME),
'environ': environ,
'args': args,
}
@ -103,22 +103,23 @@ def render(args, environ, cwd):
environ.get('POWERLINE_CONFIG_PATHS', ''),
)
PowerlineClass = ShellPowerline if is_daemon else NonDaemonShellPowerline
powerline = None
try:
powerline = powerlines[key]
powerline = state.powerlines[key]
except KeyError:
try:
powerline = powerlines[key] = PowerlineDaemon(
powerline = state.powerlines[key] = PowerlineClass(
args,
logger=logger,
config_loader=config_loader,
logger=state.logger,
config_loader=state.config_loader,
run_once=False,
shutdown_event=ts_shutdown_event,
shutdown_event=state.ts_shutdown_event,
)
if logger is None:
logger = powerline.logger
if config_loader is None:
config_loader = powerline.config_loader
if state.logger is None:
state.logger = powerline.logger
if state.config_loader is None:
state.config_loader = powerline.config_loader
except SystemExit:
# Somebody thought raising system exit was a good idea,
return ''
@ -189,7 +190,7 @@ def safe_bytes(o, encoding=get_preferred_output_encoding()):
return safe_bytes(str(e), encoding)
def parse_args(req, encoding=get_preferred_arguments_encoding()):
def parse_args(req, parser, encoding=get_preferred_arguments_encoding()):
args = [x.decode(encoding) for x in req.split(b'\0') if x]
numargs = int(args[0], 16)
shell_args = parser.parse_args(args[1:numargs + 1])
@ -199,19 +200,20 @@ def parse_args(req, encoding=get_preferred_arguments_encoding()):
return shell_args, environ, cwd
def get_answer(req):
def get_answer(req, is_daemon, argparser, state):
try:
args, environ, cwd = parse_args(req)
finish_args(parser, environ, args, is_daemon=True)
args, environ, cwd = parse_args(req, argparser)
finish_args(argparser, environ, args, is_daemon=True)
if args.ext[0].startswith('wm.'):
return safe_bytes(start_wm(args, environ, cwd))
return safe_bytes(start_wm(args, environ, cwd, is_daemon, state))
else:
return safe_bytes(render(args, environ, cwd))
return safe_bytes(render(args, environ, cwd, is_daemon, state))
except Exception as e:
return safe_bytes(str(e))
def do_one(sock, read_sockets, write_sockets, result_map):
def do_one(sock, read_sockets, write_sockets, result_map, is_daemon, argparser,
state):
r, w, e = select(
tuple(read_sockets) + (sock,),
tuple(write_sockets),
@ -241,7 +243,7 @@ def do_one(sock, read_sockets, write_sockets, result_map):
if req == EOF:
raise SystemExit(0)
elif req:
ans = get_answer(req)
ans = get_answer(req, is_daemon, argparser, state)
result_map[s] = ans
write_sockets.add(s)
else:
@ -257,7 +259,7 @@ def do_one(sock, read_sockets, write_sockets, result_map):
s.close()
def shutdown(sock, read_sockets, write_sockets):
def shutdown(sock, read_sockets, write_sockets, state):
'''Perform operations necessary for nicely shutting down daemon
Specifically it
@ -277,11 +279,11 @@ def shutdown(sock, read_sockets, write_sockets):
s.close()
# Notify ThreadedSegments
ts_shutdown_event.set()
for thread, shutdown_event in started_wm_threads.values():
state.ts_shutdown_event.set()
for thread, shutdown_event in state.started_wm_threads.values():
shutdown_event.set()
for thread, shutdown_event in started_wm_threads.values():
for thread, shutdown_event in state.started_wm_threads.values():
wait_time = total_wait_time - (monotonic() - shutdown_start_time)
if wait_time > 0:
thread.join(wait_time)
@ -290,20 +292,27 @@ def shutdown(sock, read_sockets, write_sockets):
sleep(wait_time)
def main_loop(sock):
def main_loop(sock, is_daemon):
sock.listen(128)
sock.setblocking(0)
read_sockets, write_sockets = set(), set()
result_map = {}
parser = get_main_argparser(NonInteractiveArgParser)
state = State()
try:
try:
while True:
do_one(sock, read_sockets, write_sockets, result_map)
do_one(
sock, read_sockets, write_sockets, result_map,
is_daemon=is_daemon,
argparser=parser,
state=state,
)
except KeyboardInterrupt:
raise SystemExit(0)
except SystemExit as e:
shutdown(sock, read_sockets, write_sockets)
shutdown(sock, read_sockets, write_sockets, state)
raise e
return 0
@ -313,10 +322,10 @@ def daemonize(stdin=os.devnull, stdout=os.devnull, stderr=os.devnull):
pid = os.fork()
if pid > 0:
# exit first parent
sys.exit(0)
raise SystemExit(0)
except OSError as e:
sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(1)
raise SystemExit(1)
# decouple from parent environment
os.chdir("/")
@ -328,10 +337,10 @@ def daemonize(stdin=os.devnull, stdout=os.devnull, stderr=os.devnull):
pid = os.fork()
if pid > 0:
# exit from second parent
sys.exit(0)
raise SystemExit(0)
except OSError as e:
sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
sys.exit(1)
raise SystemExit(1)
# Redirect standard file descriptors.
si = open(stdin, 'rb')
@ -340,12 +349,11 @@ def daemonize(stdin=os.devnull, stdout=os.devnull, stderr=os.devnull):
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
global is_daemon
is_daemon = True
return True
def check_existing():
if use_filesystem:
def check_existing(address):
if USE_FILESYSTEM:
# We cannot bind if the socket file already exists so remove it, we
# already have a lock on pidfile, so this should be safe.
try:
@ -363,7 +371,7 @@ def check_existing():
return sock
def kill_daemon():
def kill_daemon(address):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
try:
@ -377,7 +385,7 @@ def kill_daemon():
return True
def cleanup_lockfile(fd, *args):
def cleanup_lockfile(pidfile, fd, *args):
try:
# Remove the directory entry for the lock file
os.unlink(pidfile)
@ -390,10 +398,7 @@ def cleanup_lockfile(fd, *args):
raise SystemExit(1)
def lockpidfile():
import fcntl
import atexit
import stat
def lockpidfile(pidfile):
fd = os.open(
pidfile,
os.O_WRONLY | os.O_CREAT,
@ -408,24 +413,25 @@ def lockpidfile():
os.ftruncate(fd, 0)
os.write(fd, ('%d' % os.getpid()).encode('ascii'))
os.fsync(fd)
cleanup = partial(cleanup_lockfile, fd)
cleanup = partial(cleanup_lockfile, pidfile, fd)
signal(SIGTERM, cleanup)
atexit.register(cleanup)
return fd
def main():
global address
global pidfile
parser = get_daemon_argparser()
args = parser.parse_args()
is_daemon = False
address = None
pidfile = None
if args.socket:
address = args.socket
if not use_filesystem:
if not USE_FILESYSTEM:
address = '\0' + address
else:
if use_filesystem:
if USE_FILESYSTEM:
address = '/tmp/powerline-ipc-%d'
else:
# Use the abstract namespace for sockets rather than the filesystem
@ -434,13 +440,13 @@ def main():
address = address % os.getuid()
if use_filesystem:
if USE_FILESYSTEM:
pidfile = address + '.pid'
if args.kill:
if args.foreground or args.replace:
parser.error('--kill and --foreground/--replace cannot be used together')
if kill_daemon():
if kill_daemon(address):
if not args.quiet:
print ('Kill command sent to daemon, if it does not die in a couple of seconds use kill to kill it')
raise SystemExit(0)
@ -450,19 +456,19 @@ def main():
raise SystemExit(1)
if args.replace:
while kill_daemon():
while kill_daemon(address):
if not args.quiet:
print ('Kill command sent to daemon, waiting for daemon to exit, press Ctrl-C to terminate wait and exit')
sleep(2)
if use_filesystem and not args.foreground:
if USE_FILESYSTEM and not args.foreground:
# We must daemonize before creating the locked pidfile, unfortunately,
# this means further print statements are discarded
daemonize()
is_daemon = daemonize()
if use_filesystem:
if USE_FILESYSTEM:
# Create a locked pid file containing the daemons PID
if lockpidfile() is None:
if lockpidfile(pidfile) is None:
if not args.quiet:
sys.stderr.write(
'The daemon is already running. Use %s -k to kill it.\n' % (
@ -470,7 +476,7 @@ def main():
raise SystemExit(1)
# Bind to address or bail if we cannot bind
sock = check_existing()
sock = check_existing(address)
if sock is None:
if not args.quiet:
sys.stderr.write(
@ -478,14 +484,11 @@ def main():
os.path.basename(sys.argv[0])))
raise SystemExit(1)
if args.foreground:
return main_loop(sock)
if not use_filesystem:
if not USE_FILESYSTEM and not args.foreground:
# We daemonize on linux
daemonize()
is_daemon = daemonize()
main_loop(sock)
return main_loop(sock, is_daemon)
if __name__ == '__main__':