Handling Process Groups with Python

Introduction

In POSIX-conformant operating systems, a process group denotes a collection of one or more processes. Process groups are used to control the distribution of signals. A signal directed to a process group is delivered individually to all of the processes that are members of the group.

Process groups are themselves grouped into sessions. Process groups are not permitted to migrate from one session to another, and a process may only create new process groups belonging to the same session as it itself belongs to. Processes are not permitted to join process groups that are not in the same session as they themselves are. [1]

Motivation

Under certain circumstances one might want to spawn a process from a Python script that is independent of the original process such that the spawned or child process is independent of the parent process and cannot be terminated when te parent process, its process group or session is terminated.

Prerequisites

Before we start with our Python action let's have a short look on our shell tools.

ps

Using ps one can get all the information out processes. In this particular example we are interested in the various afore mentionen IDs [2]:

ps xao comm,pid,ppid,pgid,sid

kill

To kill a process one commonly uses the kill command [3]:

kill -<signal> <pid>

where signal is e.g. 9 or 15 and pid is the process ID of th process to kill. To kill a process group or session one uses the same command, but negates the ID of the process group or session:

kill -<signal> -<id>

where <id> is the process group ID oder session ID.

Solution

This is achieved rather easily by using Python's os.setsid() method [4].

#!/usr/bin/env python
# -*- coding: utf8 -*-

import multiprocessing as mp
import time
import os

def printPIDs():
	"""
	Function printing process ID, process group ID and session ID.
	"""
	
	print "Child PID:", os.getpid(), os.getpgrp(), os.getsid(os.getpid())

class myChild(mp.Process):
	"""
	Derived Process class to demonstrate spawning of new session group.
	"""
	
	def __init__(self):
		"""
		Initialization of object and IDs of parent process.
		"""
		super(myChild,self).__init__()
		printPIDs()
		
	def run(self):
		"""
		The actual method executed in the new process.
		The child process is made session leader by os.setsid() and thus
		independent of the parent.
		"""
		os.setsid()
		printPIDs()
		time.sleep(600)

# Print the IDs of the parent process	
printPIDs()

p = myChild()
p.start()
p.join()

This example code starts a new Python process by calling the start method of the Process class. This new process is a member of the process group of the parent process and its session. The first statement in the new process is setsid [4], which makes the new process to a session leader by changing its session ID and proces group ID.

Now we can start the script and check the output in your console, e.g.:

$ ./script/process-group.py
Child PID: 13130 13130 5979
Child PID: 13130 13130 5979
Child PID: 13131 13131 13131

In a new console we verify the output by calling ps:

$ ps  xao comm,pid,ppid,pgid,sid | grep python
python          13130  5979 13130  5979
python          13131 13130 13131 13131

As we can see in the thir column the child process is now a session leader, but still has a parent: the Python parent process. And now we kill the session leader of our Python parent process and use ps to look whether the child process is still running:

$ kill -9 -5979
$ ps  xao comm,pid,ppid,pgid,sid | grep python
python          13131  2335 13131 13131

After killing the parent process by killing the session the "parent process ID" of our child process has changed. In my example the new parent process ID is 2335, which is the 'init --user' process. And — good news! — our child process is still running.

References

  1. http://en.wikipedia.org/wiki/Process_group
  2. http://linux.die.net/man/1/ps
  3. http://linux.die.net/man/2/kill
  4. https://docs.python.org/2/library/os.html