ZetCode

Python os.setpgid Function

Last modified April 11, 2025

This comprehensive guide explores Python's os.setpgid function, which manages process groups in Unix-like systems. We'll cover process groups, session leadership, and practical examples of process control.

Basic Definitions

The os.setpgid function sets the process group ID for a specified process. It's used to organize processes into groups for signal handling.

Key parameters: pid (process ID to modify), pgid (new process group ID). Returns None on success, raises OSError on failure. Unix systems only.

Creating a New Process Group

This example demonstrates creating a child process and placing it in a new process group. The parent process remains in its original group.

new_process_group.py
import os
import time

pid = os.fork()

if pid == 0:  # Child process
    print(f"Child PID: {os.getpid()}, Original PGID: {os.getpgid(0)}")
    os.setpgid(0, 0)  # Create new process group with child as leader
    print(f"New PGID: {os.getpgid(0)}")
    time.sleep(5)
else:  # Parent process
    print(f"Parent PID: {os.getpid()}, PGID: {os.getpgid(0)}")
    time.sleep(1)
    print(f"Child's PGID after change: {os.getpgid(pid)}")

The child process creates a new process group with itself as the leader. The parent can verify the child's new group ID after the change.

Process group leaders are typically shells or session leaders that manage groups of related processes.

Joining an Existing Process Group

This example shows how to make a process join an existing process group rather than creating a new one. The target group must be in the same session.

join_process_group.py
import os
import time

# Create two child processes
pid1 = os.fork()

if pid1 == 0:  # First child
    print(f"Child1 PID: {os.getpid()}, PGID: {os.getpgid(0)}")
    time.sleep(10)
else:
    pid2 = os.fork()
    
    if pid2 == 0:  # Second child
        print(f"Child2 PID: {os.getpid()}, Original PGID: {os.getpgid(0)}")
        # Join first child's process group
        os.setpgid(0, pid1)
        print(f"New PGID: {os.getpgid(0)}")
        time.sleep(5)
    else:  # Parent
        print(f"Parent PID: {os.getpid()}")
        time.sleep(1)
        print(f"Child1 PGID: {os.getpgid(pid1)}")
        print(f"Child2 PGID: {os.getpgid(pid2)}")

The second child process joins the first child's process group. Both children will now be in the same group, sharing signal handling characteristics.

This technique is useful for creating process hierarchies where related processes should receive signals together.

Error Handling with setpgid

This example demonstrates proper error handling when using os.setpgid, including permission checks and invalid process scenarios.

error_handling.py
import os
import sys
import errno

try:
    # Attempt to set process group for non-existent process
    os.setpgid(99999, 0)
except OSError as e:
    if e.errno == errno.ESRCH:
        print("Error: No such process (ESRCH)")
    elif e.errno == errno.EPERM:
        print("Error: Permission denied (EPERM)")
    elif e.errno == errno.EACCES:
        print("Error: Access denied (EACCES)")
    else:
        print(f"Unexpected error: {e}")

# Valid but restricted operation
pid = os.fork()
if pid == 0:  # Child
    try:
        # Try to change parent's process group
        os.setpgid(os.getppid(), 0)
    except OSError as e:
        print(f"Failed to change parent's PGID: {e}")
    sys.exit()
else:  # Parent
    os.waitpid(pid, 0)

The first attempt fails with ESRCH (no such process). The child process fails to modify the parent's group due to EPERM (permission denied).

Proper error handling is crucial when managing process groups as operations often have strict permission requirements.

Session Leadership and setpgid

This example explores the relationship between sessions and process groups, demonstrating how os.setpgid interacts with session leaders.

session_leader.py
import os
import time

# Create a new session
if os.fork() == 0:  # Child becomes session leader
    print(f"New session leader PID: {os.getpid()}")
    print(f"SID: {os.getsid(0)}")
    os.setsid()  # Create new session
    
    # Now try to set process group
    try:
        os.setpgid(0, 0)
        print("Successfully created new process group")
    except OSError as e:
        print(f"Failed to setpgid: {e}")
    
    time.sleep(5)
    sys.exit()
else:  # Parent
    time.sleep(1)
    print(f"Parent SID: {os.getsid(0)}")

The child process creates a new session with os.setsid() and then attempts to create a new process group. Session leaders have special rules for process group management.

A session leader's process group ID is always equal to its PID, and attempts to change it will fail with EPERM.

Process Group Inheritance

This example demonstrates how process groups are inherited during fork() and how os.setpgid can modify this behavior.

inheritance.py
import os
import time

print(f"Original process PGID: {os.getpgid(0)}")

pid = os.fork()

if pid == 0:  # Child
    print(f"Child inherited PGID: {os.getpgid(0)}")
    
    # Change process group immediately after fork
    os.setpgid(0, 0)
    print(f"Child new PGID: {os.getpgid(0)}")
    
    time.sleep(5)
else:  # Parent
    # Parent can also change child's group (must happen before exec)
    time.sleep(0.1)  # Ensure child runs first
    try:
        os.setpgid(pid, pid)
        print(f"Parent set child's PGID to: {os.getpgid(pid)}")
    except OSError as e:
        print(f"Failed to change child's PGID: {e}")

The child initially inherits the parent's process group. Either the parent or child can change the group, but it must be done before exec() is called.

This coordination between parent and child is crucial for proper process group management in shell implementations.

Signal Handling with Process Groups

This example shows how process groups affect signal delivery, demonstrating how signals can be sent to entire process groups.

signal_handling.py
import os
import signal
import time

def handler(signum, frame):
    print(f"Received signal {signum} in PID {os.getpid()}")

# Set up signal handler
signal.signal(signal.SIGUSR1, handler)

pid = os.fork()

if pid == 0:  # Child
    # Create new process group
    os.setpgid(0, 0)
    print(f"Child in new PGID: {os.getpgid(0)}")
    while True:
        time.sleep(1)
else:  # Parent
    time.sleep(1)  # Let child set up
    
    # Send signal to entire process group
    print(f"Sending SIGUSR1 to group {os.getpgid(pid)}")
    os.kill(-os.getpgid(pid), signal.SIGUSR1)
    
    time.sleep(1)
    os.kill(pid, signal.SIGTERM)

The parent sends a signal to the entire process group using a negative PID. Both processes receive the signal if they're in the same group.

Process groups enable coordinated signal handling for related processes, which is particularly useful for job control in shells.

Security Considerations

Best Practices

Source References

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Python tutorials.