Cross-platform daemonization tools.

Similar documents
Processes. Processes (cont d)

Assignment 1. Teaching Assistant: Michalis Pachilakis (

ECE 650 Systems Programming & Engineering. Spring 2018

Processes. Johan Montelius KTH

A process. the stack

Computer Science 330 Operating Systems Siena College Spring Lab 5: Unix Systems Programming Due: 4:00 PM, Wednesday, February 29, 2012

Operating Systemss and Multicore Programming (1DT089)

Processes in linux. What s s a process? process? A dynamically executing instance of a program. David Morgan. David Morgan

Signals. POSIX defines a variety of signal types, each for a particular

TH IRD EDITION. Python Cookbook. David Beazley and Brian K. Jones. O'REILLY. Beijing Cambridge Farnham Köln Sebastopol Tokyo

CS24: INTRODUCTION TO COMPUTING SYSTEMS. Spring 2018 Lecture 20

CS Programming Languages: Python

IP address. Subnetting. Ping command in detail. Threads in Python. Sub process in Python

helper Documentation Release Gavin M. Roy

Process Management! Goals of this Lecture!

The Process Abstraction. CMPU 334 Operating Systems Jason Waterman

Introduction to Operating Systems Prof. Chester Rebeiro Department of Computer Science and Engineering Indian Institute of Technology, Madras

Processes. What s s a process? process? A dynamically executing instance of a program. David Morgan

Operating System Structure

Implementation of a simple shell, xssh

CSE 410: Computer Systems Spring Processes. John Zahorjan Allen Center 534

Operating Systems. Threads and Signals. Amir Ghavam Winter Winter Amir Ghavam

Linux Signals and Daemons

Linux shell scripting Getting started *

The Shell, System Calls, Processes, and Basic Inter-Process Communication

Process Management 1

Programming for the Web with PHP

Midterm Exam CPS 210: Operating Systems Spring 2013

System Calls and Signals: Communication with the OS. System Call. strace./hello. Kernel. Context Switch

NAME SYNOPSIS DESCRIPTION. Behavior of other Perl features in forked pseudo-processes. Perl version documentation - perlfork

Most of the work is done in the context of the process rather than handled separately by the kernel

Unix Processes. What is a Process?

CS 4410, Fall 2017 Project 1: My First Shell Assigned: August 27, 2017 Due: Monday, September 11:59PM

What is a Process? Processes and Process Management Details for running a program

Operating Systems, laboratory exercises. List 2.

ffmpy3 Documentation Release Eric Ahn

CSE 451: Operating Systems Winter Module 4 Processes. Mark Zbikowski Allen Center 476

Contents: 1 Basic socket interfaces 3. 2 Servers 7. 3 Launching and Controlling Processes 9. 4 Daemonizing Command Line Programs 11

redis-lua Documentation

3. Process Management in xv6

More Scripting Todd Kelley CST8207 Todd Kelley 1

bash Args, Signals, Functions Administrative Shell Scripting COMP2101 Fall 2018

OS Structure, Processes & Process Management. Don Porter Portions courtesy Emmett Witchel

Lab 4. Out: Friday, February 25th, 2005

CS 326: Operating Systems. Process Execution. Lecture 5

CSCI Computer Systems Fundamentals Shell Lab: Writing Your Own Unix Shell

CSC209F Midterm (L5101) Fall 1998 University of Toronto Department of Computer Science

Lecture 20. Java Exceptional Event Handling. Dr. Martin O Connor CA166

Final Precept: Ish. Slides Originally Prepared by: Wonho Kim

simplevisor Documentation

Shell Execution of Programs. Process Groups, Session and Signals 1

Shell Scripting. Todd Kelley CST8207 Todd Kelley 1

bash Args, Signals, Functions Administrative Shell Scripting COMP2101 Fall 2017

Process management. What s in a process? What is a process? The OS s process namespace. A process s address space (idealized)

PYTHON TRAINING COURSE CONTENT

HW 1: Shell. Contents CS 162. Due: September 18, Getting started 2. 2 Add support for cd and pwd 2. 3 Program execution 2. 4 Path resolution 3

CS24: INTRODUCTION TO COMPUTING SYSTEMS. Spring 2018 Lecture 18

CSCI0330 Intro Computer Systems Doeppner. Project Shell 2. Due: November 8, 2017 at 11:59pm. 1 Introduction 2

Readline: Terminal Interaction

The Kernel. wants to be your friend

PTN-102 Python programming

Killing Zombies, Working, Sleeping, and Spawning Children

Process Management forks, bombs, zombies, and daemons! Lecture 5, Hands-On Unix System Administration DeCal

The WSGI Reference Library

CSC374, Spring 2010 Lab Assignment 1: Writing Your Own Unix Shell

Number of Processes and Process ID Values on HP-UX

Each terminal window has a process group associated with it this defines the current foreground process group. Keyboard-generated signals are sent to

A Unix Process. Joseph Cordina

Last time: introduction. Networks and Operating Systems ( ) Chapter 2: Processes. This time. General OS structure. The kernel is a program!

Uranium Documentation

argcomplete Documentation

Exceptions & a Taste of Declarative Programming in SQL

I m paranoid, but am I paranoid enough? Steven M. Bellovin February 20,

Creating a Shell or Command Interperter Program CSCI411 Lab

Data Communication and Synchronization for Hybrid-x86

Not-So-Mini-Lecture 6. Modules & Scripts

CIS192 Python Programming

CS 213, Fall 2002 Lab Assignment L5: Writing Your Own Unix Shell Assigned: Oct. 24, Due: Thu., Oct. 31, 11:59PM

CS 33. Architecture and the OS. CS33 Intro to Computer Systems XIX 1 Copyright 2017 Thomas W. Doeppner. All rights reserved.

AP COMPUTER SCIENCE JAVA CONCEPTS IV: RESERVED WORDS

NAME SYNOPSIS DESCRIPTION. Behavior of other Perl features in forked pseudo-processes. Perl version documentation - perlfork

The Classical OS Model in Unix

Command-line interpreters

PROCESS CONTROL BLOCK TWO-STATE MODEL (CONT D)

CS 355 Operating Systems. Keeping Track of Processes. When are processes created? Process States 1/26/18. Processes, Unix Processes and System Calls

System Calls & Signals. CS449 Spring 2016

Design Overview of the FreeBSD Kernel CIS 657

\n is used in a string to indicate the newline character. An expression produces data. The simplest expression

Design Overview of the FreeBSD Kernel. Organization of the Kernel. What Code is Machine Independent?

Lecture 3. Functions & Modules

Altering the Control Flow

argcomplete Documentation Andrey Kislyuk

CIS192 Python Programming

System Administration

bash, part 3 Chris GauthierDickey

W4118 Operating Systems. Junfeng Yang

A shell can be used in one of two ways:

(In columns, of course.)

Implementation of a simple shell, xssh

CS 33. Architecture and the OS. CS33 Intro to Computer Systems XIX 1 Copyright 2018 Thomas W. Doeppner. All rights reserved.

Transcription:

Cross-platform daemonization tools. Release 0.1.0 Muterra, Inc Sep 14, 2017

Contents 1 What is Daemoniker? 1 1.1 Installing................................................. 1 1.2 Example usage.............................................. 1 1.3 Comparison to multiprocessing, subprocess, etc....................... 2 1.4 Comparison to signal.signal................................... 2 2 ToC 5 2.1 How it works............................................... 5 2.2 Daemonization API........................................... 6 2.3 Signal handling API........................................... 9 2.4 Exception API.............................................. 11 i

ii

CHAPTER 1 What is Daemoniker? Daemoniker provides a cross-platform Python API for running and signaling daemonized Python code. On Unix, it uses a standard double-fork procedure; on Windows, it creates an separate subprocess for pythonw.exe that exists independently of the initiating process. Daemoniker also provides several utility tools for the resulting daemons. In particular, it includes cross-platform signaling capability for the created daemons. Installing Daemoniker requires Python 3.5 or higher. pip install daemoniker Example usage At the beginning of your script, invoke daemonization through the daemoniker.daemonizer context manager: from daemoniker import Daemonizer with Daemonizer() as (is_setup, daemonizer): if is_setup: # This code is run before daemonization. do_things_here() # We need to explicitly pass resources to the daemon; other variables # may not be correct is_parent, my_arg1, my_arg2 = daemonizer( path_to_pid_file, my_arg1, my_arg2 1

) if is_parent: # Run code in the parent after daemonization parent_only_code() # We are now daemonized, and the parent just exited. code_continues_here() Signal handling works through the same path_to_pid_file: from daemoniker import SignalHandler1 # Create a signal handler that uses the daemoniker default handlers for # ``SIGINT``, ``SIGTERM``, and ``SIGABRT`` sighandler = SignalHandler1(path_to_pid_file) sighandler.start() # Or, define your own handlers, even after starting signal handling def handle_sigint(signum): print('sigint received.') sighandler.sigint = handle_sigint These processes can then be sent signals from other processes: from daemoniker import send from daemoniker import SIGINT # Send a SIGINT to a process denoted by a PID file send(path_to_pid_file, SIGINT) Comparison to multiprocessing, subprocess, etc The modules included in the standard library for creating new processes from the current Python interpreter are intended for dependent subprocesses only. They will not continue to run if the current Python session is terminated, and when called from a Unix terminal in the background using &, etc, they will still result in the process being reaped upon terminal exit (this includes SSH session termination). Daemonziation using daemoniker creates fully-independent, well-behaved processes that place no requirements on the launching terminal. Comparison to signal.signal For Unix systems, Daemoniker provides a lightweight wrapper around signal.signal and the poorly-named os. kill for the three signals (SIGINT, SIGTERM, and SIGABRT) that are both available on Windows and meaningful within the Python interpreter. On Unix systems, Daemoniker signal handling gives you little more than convenience. On Windows systems, signal handling is poorly-supported at best. Furthermore, when sending signals to the independent processes created through Daemoniker, all signals sent to the process through os.kill will result in the target (daemon) process immediately exiting without cleanup (bypassing try:/finally: blocks, atexit calls, etc). On Windows systems, Daemoniker substantially expands this behavior, allowing Python processes to safely handle signals. For more information on standard Windows signal handling, see: 2 Chapter 1. What is Daemoniker?

1. Sending ^C to Python subprocess objects on Windows 2. Python send SIGINT to subprocess using os.kill as if pressing Ctrl+C 3. How to handle the signal in python on windows machine 1.4. Comparison to signal.signal 3

4 Chapter 1. What is Daemoniker?

CHAPTER 2 ToC How it works Daemonization is a little tricky to get right (and very difficult to test). Cross-platform daemonization is even less straightforward. Daemonization: Unix On Unix, daemonization with Daemoniker performs the following steps: 1. Create a PID file, failing if it already exists 2. Register cleanup of the PID file for program exit 3. Double-fork, dissociating itself from the original process group and any possible control terminal 4. Reset umask and change current working directory 5. Write its PID to the PID file 6. Close file descriptors 7. Redirect stdin, stdout, and stderr. Note: To be considered a well-behaved daemon, applications should also, at the least, handle termination through a SIGTERM handler (see Signal handling API for using Daemoniker for this purpose). Daemonization: Windows On Windows, daemonization with Daemoniker performs the following steps: 1. Find the currently-running Python interpreter and file. 2. Search for a copy of pythonw.exe within the same directory. 5

3. Create a PID file, failing if it already exists. 4. Save the current namespace and re-launch the script using pythonw.exe. 5. Bypass any already-completed code using an environment variable switch. 6. Change current working directory. 7. Write its process handle to the PID file and register the file s cleanup for program exit. 8. Redirect stdin, stdout, and stderr. 9. Extract the old namespace. 10. Return the old namespace into the resumed daemonized process and allow the original process to exit. Warning: Due to the implementation of signals on Windows (as well as their use within the CPython interpreter), any signals sent to this daughter process will result in its immediate termination, without any cleanup. That means no atexit calls, no finally: blocks, etc. See Signals: Windows below for more information, or see Signal handling API for using Daemoniker as a workaround. Signals: Unix Signal handling on Unix is very straightforward. The signal handler provided by SigHandler1 provides a thin wrapper around the built-in signal.signal functionality. To maintain uniform cross-platform behavior, the frame argument typically passed to signal.signal callbacks is removed, but otherwise, Daemoniker is simply a convenience wrapper around signal.signal that includes several default signal handlers. Signals: Windows Signals on Windows are not natively supported by the operating system. They are included in the C runtime environment provided by Windows, but their role is substantially different than that in Unix systems. Furthermore, these signals are largely limited to transmission between parent/child processes, and because the daemonization process creates a fully-independent process group, every available signal (including the Windows-specific CTRL_C_EVENT and CTRL_BREAK_EVENT) result in immediate termination of the daughter process without cleanup. To avoid this thoroughly undesirable behavior, Daemoniker uses the following workaround: 1. From the main thread of the (daemonized) Python script, launch a daughter thread devoted to signal handling. 2. From that daughter thread, launch a sleep-loop-forever daughter process. 3. Overwrite the PID file with the PID of the daughter process. 4. Wait for the daughter process to complete. If it was killed by a signal, its return code equals the number of the signal. Handle it accordingly. 5. For every signal received, create a new daughter process. Additionally, to mimic the behavior of signal.signal and replicate Unix behavior, the default Daemoniker signal handlers call a ctypes API to raise an exception in the main thread of the parent script. Daemonization API Simple daemonization may be performed by directly calling the daemonize() funtion. In general, it should be the first thing called by your code (except perhaps import statements, global declarations, and so forth). If you need to 6 Chapter 2. ToC

do anything more complicated, use the Daemonizer context manager. daemonize(pid_file, *args, chdir=none, stdin_goto=none, stdout_goto=none, stderr_goto=none, umask=0o027, shielded_fds=none, fd_fallback_limit=1024, success_timeout=30, strip_cmd_args=false) New in version 0.1. The function used to actually perform daemonization. It may be called directly, but is intended to be used within the Daemonizer context manager. Warning: When used directly, all code prior to daemonize() will be repeated by the daemonized process on Windows systems. It is best to limit all pre-daemonize code to import statements, global declarations, etc. If you want to run specialized setup code, use the Daemonizer context manager. Note: All *args must be pickleable on Windows systems. Parameters pid_file (str) The path to use for the PID file. *args All variables to preserve across the daemonization boundary. On Windows, only these values (which will be returned by daemonize) are guaranteed to be persistent. chdir (str) The path to use for the working directory after daemonizing. Defaults to the current directory, which can result in directory busy errors on both Unix and Windows systems. This argument is keyword-only. stdin_goto (str) A filepath to redirect stdin into. A value of None defaults to os.devnull. This argument is keyword-only. stdout_goto (str) A filepath to redirect stdout into. A value of None defaults to os.devnull. This argument is keyword-only. stderr_goto (str) A filepath to redirect stderr into. A value of None defaults to os.devnull. This argument is keyword-only. umask (int) The file creation mask to apply to the daemonized process. Unused on Windows. This argument is keyword-only. shielded_fds An iterable of integer file descriptors to shield from closure. Unused on Windows. This argument is keyword-only. fd_ballback_limit (int) If the file descriptor resource hard limit and soft limit are both infinite, this fallback integer will be one greater than the highest file descriptor closed. Unused on Windows. This argument is keyword-only. success_timeout A numeric limit, in seconds, for how long the parent process should wait for acknowledgment of successful startup by the daughter process. Unused on Unix. This argument is keyword-only. strip_cmd_args (bool) If the current script was started from a prompt using arguments, as in python script.py --arg1 --arg2, this value determines whether or not those arguments should be stripped when re-invoking the script. In this example, calling daemonize with strip_cmd_args=true would be re-invoke the script as python script.py. Unused on Unix. This argument is keyword-only. Returns *args 2.2. Daemonization API 7

>>> from daemoniker import daemonize >>> daemonize('pid.pid') class Daemonizer New in version 0.1. A context manager for more advanced daemonization. Entering the context assigns a tuple of (boolean is_setup, callable daemonizer) to the with Daemonizer() as target: target. from daemoniker import Daemonizer with Daemonizer() as (is_setup, daemonizer): if is_setup: # This code is run before daemonization. do_things_here() # We need to explicitly pass resources to the daemon; other variables # may not be correct is_parent, my_arg1, my_arg2 = daemonizer( 'path/to/pid/file.pid', my_arg1, my_arg2,..., **daemonize_kwargs ) # This allows us to run parent-only post-daemonization code if is_parent: run_some_parent_code_here() # We are now daemonized and the parent has exited. code_continues_here(my_arg1, my_arg2) When used in this manner, the Daemonizer context manager will return a boolean is_setup and a wrapped daemonize() function. Note: Do not include an else clause after is_setup. It will not be run on Unix: from daemoniker import Daemonizer with Daemonizer() as (is_setup, daemonizer): if is_setup: # This code is run before daemonization. do_things_here() else: # This code will never run on Unix systems. do_no_things_here()... Note: To prevent resource contention with the daemonized child, the parent process must be terminated via os._exit when exiting the context. You must perform any cleanup inside the if is_parent: block. enter () 8 Chapter 2. ToC

Entering the context will return a tuple of: with Daemonizer() as (is_setup, daemonizer): is_setup is a bool that will be True when code is running in the parent (pre-daemonization) process. daemonizer wraps daemonize(), prepending a bool is_parent to its return value. To prevent accidental manipulation of already-passed variables from the parent process, it also replaces them with None in the parent caller. It is otherwise identical to daemonize(). For example: from daemoniker import Daemonizer with Daemonizer() as (is_setup, daemonizer): if is_setup: my_arg1 = 'foo' my_arg2 = 'bar' is_parent, my_arg1, my_arg2 = daemonizer( 'path/to/pid/file.pid', my_arg1, my_arg2 ) # This code will only be run in the parent process if is_parent: # These will return True my_arg1 == None my_arg2 == None # This code will only be run in the daemonized child process else: # These will return True my_arg1 == 'foo' my_arg2 == 'bar' # The parent has now exited. All following code will only be run in # the daemonized child process. program_continues_here(my_arg1, my_arg2) exit () Exiting the context will do nothing in the child. In the parent, leaving the context will initiate a forced termination via os._exit to prevent resource contention with the daemonized child. Signal handling API class SignalHandler1(pid_file, sigint=none, sigterm=none, sigabrt=none) New in version 0.1. Handles signals for the daemonized process. Parameters pid_file (str) The path to the PID file. sigint A callable handler for the SIGINT signal. May also be daemoniker. IGNORE_SIGNAL to explicitly ignore the signal. Passing the default value of None will assign the default SIGINT handler, which will simply raise daemoniker.sigint within the main thread. 2.3. Signal handling API 9

sigterm A callable handler for the SIGTERM signal. May also be daemoniker. IGNORE_SIGNAL to explicitly ignore the signal. Passing the default value of None will assign the default SIGTERM handler, which will simply raise daemoniker.sigterm within the main thread. sigabrt A callable handler for the SIGABRT signal. May also be daemoniker. IGNORE_SIGNAL to explicitly ignore the signal. Passing the default value of None will assign the default SIGABRT handler, which will simply raise daemoniker.sigabrt within the main thread. Warning: There is a slight difference in handler calling between Windows and Unix systems. In every case, the default handler will always raise from within the main thread. However, if you define a custom signal handler, on Windows systems it will be called from within a daughter thread devoted to signal handling. This has two consequences: 1.All signal handlers must be threadsafe 2.On Windows, future signals will be silently dropped until the callback completes On Unix systems, the handler will be called from within the main thread. Note: On Windows, CTRL_C_EVENT and CTRL_BREAK_EVENT signals are both converted to SIGINT signals internally. This also applies to custom signal handlers. >>> from daemoniker import SignalHandler1 >>> sighandler = SignalHandler1('pid.pid') sigint The current handler for SIGINT signals. This must be a callable. It will be invoked with a single argument: the signal number. It may be re-assigned, even after calling start(). Deleting it will restore the default Daemoniker signal handler; to ignore it, instead assign daemoniker.ignore_signal as the handler. sigterm The current handler for SIGTERM signals. This must be a callable. It will be invoked with a single argument: the signal number. It may be re-assigned, even after calling start(). Deleting it will restore the default Daemoniker signal handler; to ignore it, instead assign daemoniker.ignore_signal as the handler. sigabrt The current handler for SIGABRT signals. This must be a callable. It will be invoked with a single argument: the signal number. It may be re-assigned, even after calling start(). Deleting it will restore the default Daemoniker signal handler; to ignore it, instead assign daemoniker.ignore_signal as the handler. start() Starts signal handling. Must be called to receive signals with the SignalHandler. stop() Stops signal handling. Must be called to stop receive signals. stop will be automatically called: 1.at the interpreter exit, and 2.when the main thread exits. stop is idempotent. On Unix systems, it will also restore the previous signal handlers. 10 Chapter 2. ToC

IGNORE_SIGNAL A constant used to explicitly declare that a SignalHandler1 should ignore a particular signal. send(pid_file, signal) New in version 0.1. Send a signal to the process at pid_file. Parameters pid_file (str) The path to the PID file. signal The signal to send. This may be either: 1. an instance of one of the ReceivedSignal exceptions, for example: daemoniker. SIGINT() (see SIGINT) 2. the class for one of the ReceivedSignal exceptions, for example: daemoniker. SIGINT (see SIGINT) 3. an integer-like value, corresponding to the signal number, for example: signal. SIGINT >>> from daemoniker import send >>> from daemoniker import SIGINT >>> send('pid.pid', SIGINT) Exception API exception DaemonikerException All of the custom exceptions in Daemoniker subclass this exception. As such, an application can catch any Daemoniker exception via: try: code_goes_here() except DaemonikerException: handle_error_here() exception SignalError These errors are only raised if something goes wrong internally while handling signals. exception ReceivedSignal Subclasses of ReceivedSignal exceptions are raised by the default signal handlers. A ReceivedSignal will only be raise directly: 1.if the actual signal number passed to the callback does not match its expected value. 2.if, on Windows, the signal handling daughter process terminates abnormally. Note: Calling int() on a ReceivedSignal class or instance, or a class or instance of any of its subclasses, will return the signal number associated with the signal. exception SIGINT Raised for incoming SIGINT signals. May also be used to send() signals to other processes. Attr SIGNUM The signal number associated with the signal. 2.4. Exception API 11

exception SIGTERM Raised for incoming SIGTERM signals. May also be used to send() signals to other processes. Attr SIGNUM The signal number associated with the signal. exception SIGABRT Raised for incoming SIGABRT signals. May also be used to send() signals to other processes. Attr SIGNUM The signal number associated with the signal. Exception hierarchy The Daemoniker exceptions have the following inheritance: DaemonikerException SignalError ReceivedSignal SIGINT SIGTERM SIGABRT 12 Chapter 2. ToC

Index Symbols enter () (Daemonizer method), 8 exit () (Daemonizer method), 9 D DaemonikerException, 11 daemonize() (built-in function), 7 Daemonizer (built-in class), 8 I IGNORE_SIGNAL (built-in variable), 10 R ReceivedSignal, 11 S send() (built-in function), 11 SIGABRT, 12 sigabrt (SignalHandler1 attribute), 10 SIGINT, 11 sigint (SignalHandler1 attribute), 10 SignalError, 11 SignalHandler1 (built-in class), 9 SIGTERM, 11 sigterm (SignalHandler1 attribute), 10 start() (SignalHandler1 method), 10 stop() (SignalHandler1 method), 10 13