Orphaned Processes in Python

May 23, 2022 Python Multiprocessing

An orphan process is a process that does not have a running parent process.

In this tutorial you will discover orphan processes and how to identify them in Python.

Let's get started.

What is an Orphan Process

An orphan process is a process that does not have a parent process.

An orphan process is a computer process whose parent process has finished or terminated, though it remains running itself.

-- Orphan process, Wikipedia.

A process is a running instance of a computer program.

Every Python program is executed in a Process, which is a new instance of the Python interpreter. This process has the name MainProcess and has one thread used to execute the program instructions called the MainThread. Both processes and threads are created and managed by the underlying operating system.

Sometimes we may need to create new child processes in our program in order to execute code concurrently.

Python provides the ability to create and manage new processes via the multiprocessing.Process class.

It is possible to create child processes and for the parent process to stop or be killed, leaving one or more child processes to continue running.

These child processes are called orphan processes.

Next, let's consider how a child process may be orphaned.

How To Orphan a Child Process

An orphan process is created when the parent of a child process is terminated.

This can happen for many reasons, both intentional and otherwise.

Some ways that a parent process may be terminated leaving the child process orphaned include:

Now that we know what orphan processes are, let's look at problems with orphan processes.

Problem of Orphan Processes

Orphan processes can be a problem in some situations.

Typically, we can get a handle on child processes via the parent process. This is helpful for querying the status of the process or forcefully killing child processes.

If a process is orphaned, it can be difficult to programmatically get a handle on it.

In turn, it can be challenging to query the status of orphaned processes or to terminate them.

Some solutions could be:

Next, let's take a look at how a process can check if it is orphaned.

How To Check if a Process is Orphaned

A process is orphaned if it has no parent process, or if the parent process is not running.

By this definition, the MainProcess in a Python program is an orphan.

A process can check if it is orphaned by calling the multiprocessing.parent_process() function.

This function returns the multiprocessing.Process instance for the parent process for the current process.

If this value is None, the process is orphaned.

For example:

...
# check for no parent process
if multiprocessing.parent_process() is None:
	print('Orphaned')

Alternatively, a child process may have a multiprocessing.Process instance returned from the multiprocessing.parent_process() function, but the process may not be running. In which case, the process is orphaned.

We can check if the parent process is running via a call to the Process.is_alive() function.

For example:

...
# get the parent process
parent = multiprocessing.parent_process()
if parent is not None and not parent.is_alive():
	print('Orphaned')

We can wrap this into a function that returns True if a process is orphaned and False otherwise.

For example:

# return True if the current process is orphaned, False otherwise
def is_orphan():
    # get the parent process
    parent = parent_process()
    # check if orphaned
    if parent is None or not parent.is_alive():
        return True
    # not orphaned
    return False

Now that we know how to identify an orphaned process, let's look at some examples of orphaned processes.

Example MainProcess is an Orphan

Technically, the main process for a Python program is an orphan process.

This is because the main process does not have a parent process.

We can demonstrate this with an example. We can call the multiprocessing.parent_process() function and confirm that it returns None.

For example:

...
# get the parent process
parent = parent_process()
print(f'Parent: {parent}')

We can also test our is_orphan() function developed in the previous section.

For example:

...
# check if orphan
print(f'Orphaned: {is_orphan()}')

Tying this together, the complete example of checking if the main process is an orphan process is listed below.

# SuperFastPython.com
# example confirming that the main process is an orphan process
from multiprocessing import parent_process

# return True if the current process is orphaned, False otherwise
def is_orphan():
    # get the parent process
    parent = parent_process()
    # check if orphaned
    if parent is None or not parent.is_alive():
        return True
    # not orphaned
    return False

# entry point
if __name__ == '__main__':
    # get the parent process
    parent = parent_process()
    print(f'Parent: {parent}')
    # check if orphan
    print(f'Orphaned: {is_orphan()}')

Running the example first gets the parent process of the main process and prints it directly.

In this case, we can confirm that the parent of the main process is None. This highlights that technically the main process is an orphan process.

Next, we can test our is_orphan() function for the main process.

This returns True, confirming that our function operates as expected.

Parent: None
Orphaned: True

Example of Orphaned Child Process

We can explore how to create an orphaned child process.

In this example we will create a child process, then from this child process create another child process.

The main process will then terminate the first child process and leave the second child process as an orphan process.

First, we must define a function to be executed by the second level child process. This function will first check if it is an orphan (it won't be), then sleep for a moment, then check if it is an orphan again (it will be).

We can call our is_orphan() function developed above to check if the process is an orphan.

The level2() function below implements this.

# task for level 3 child
def level2():
    # check if orphaned
    print(f'Orphaned: {is_orphan()}', flush=True)
    # block for a moment
    sleep(3)
    # check if orphaned
    print(f'Orphaned: {is_orphan()}', flush=True)

Next, we need a function to be executed by the first-level child process.

This is the process created directly by the MainProcess that will in turn create the second level process that executes level2() listed above.

The level1() function below implements this.

# task for level 1 child
def level1():
    # create a new process
    process = Process(target=level2)
    # start the new process
    process.start()

Finally, the main process will create a new child process that executes the level1() function, then start this process.

...
# create a new process
process = Process(target=level1)
# start the new process
process.start()

The main process will then block for a moment.

...
# block for a moment
sleep(1)

Finally, it will terminate the level1 child process so that the level2 child process will be orphaned.

...
# terminate the child process
process.terminate()

Tying this together, the complete example is listed below.

# SuperFastPython.com
# example of creating an orphan process
from time import sleep
from multiprocessing import Process
from multiprocessing import parent_process

# return True if the current process is orphaned, False otherwise
def is_orphan():
    # get the parent process
    parent = parent_process()
    # check if orphaned
    if parent is None or not parent.is_alive():
        return True
    # not orphaned
    return False

# task for level 3 child
def level2():
    # check if orphaned
    print(f'Orphaned: {is_orphan()}', flush=True)
    # block for a moment
    sleep(3)
    # check if orphaned
    print(f'Orphaned: {is_orphan()}', flush=True)

# task for level 1 child
def level1():
    # create a new process
    process = Process(target=level2)
    # start the new process
    process.start()

# entry point
if __name__ == '__main__':
    # create a new process
    process = Process(target=level1)
    # start the new process
    process.start()
    # block for a moment
    sleep(1)
    # terminate the child process
    process.terminate()

Running the example first creates a child process in the main process.

The main process then blocks.

The first-level child process then creates a second-level child process and finishes running.

The second-level child process checks if it is orphaned, which it is not, then blocks.

The main process then terminates the first-level child process and finishes running.

The first-level child process had already finished running, but was still alive. This is because it had at least one non-daemon child process still running. Once terminated, it was no longer alive.

At this time the main process is no longer "running" but is still alive because there is still at least one non-daemon process running.

The second-level process wakes-up, checks if it is orphaned, which it is, then the program terminates as all non-daemon processes have finished.

Orphaned: False
Orphaned: True

Common Questions

This section lists some common questions about orphan processes.

Do you have any questions about orphan processes?
List your questions below in the comments and I may add them to this section.

Will Calling sys.exit() Create an Orphan Process?

No.

The sys.exit() function will raise a SystemExit exception and signal to the interpreter that the process should exit.

This may stop the process from "running" but it will stay alive if it has child processes.

For example:

# SuperFastPython.com
# example of creating an orphan process
from time import sleep
from multiprocessing import Process
from multiprocessing import parent_process
import sys

# return True if the current process is orphaned, False otherwise
def is_orphan():
    # get the parent process
    parent = parent_process()
    # check if orphaned
    if parent is None or not parent.is_alive():
        return True
    # not orphaned
    return False

# task for level 3 child
def level2():
    # check if orphaned
    print(f'Orphaned: {is_orphan()}', flush=True)
    # block for a moment
    sleep(3)
    # check if orphaned
    print(f'Orphaned: {is_orphan()}', flush=True)

# task for level 1 child
def level1():
    # create a new process
    process = Process(target=level2)
    # start the new process
    process.start()
    # attempt to terminate
    sys.exit(0)

# entry point
if __name__ == '__main__':
    # create a new process
    process = Process(target=level1)
    # start the new process
    process.start()
    # block for a moment
    sleep(1)
    # check if alive
    print(f'Child Alive: {process.is_alive()}')

Running the example shows that the first-level child process stops running and attempts to exit, yet remains running according to both the parent and the second-level child process.

Orphaned: False
Child Alive: True
Orphaned: False

Can We Terminate the MainProcess To Create an Orphan?

Yes, but not easily.

We can call sys.exit(0) to exit the main process, yet it stays alive.

For example:

# SuperFastPython.com
# example of creating an orphan process
from time import sleep
from multiprocessing import Process
from multiprocessing import parent_process
import sys

# return True if the current process is orphaned, False otherwise
def is_orphan():
    # get the parent process
    parent = parent_process()
    # check if orphaned
    if parent is None or not parent.is_alive():
        return True
    # not orphaned
    return False

# function executed in a new process
def task():
    # check if orphaned
    print(f'Orphaned: {is_orphan()}', flush=True)
    # block for a moment
    sleep(3)
    # check if orphaned
    print(f'Orphaned: {is_orphan()}', flush=True)
    print(f'Parent Alive: {parent_process().is_alive()}', flush=True)

# entry point
if __name__ == '__main__':
    # create a new process
    process = Process(target=task)
    # start the new process
    process.start()
    # block for a moment
    sleep(1)
    # all done
    print('MainProcess all done')
    # process terminates itself
    sys.exit(0)

Running the example shows that the child is not an orphan and that the parent process is still running.

Orphaned: False
MainProcess all done
Orphaned: False
Parent Alive: True

We can attempt to get the multiprocessing.Process instance for the main process via multiprocessing.current_process() function and have the main process terminate itself, but Python reports no such function or attribute.

For example:

# SuperFastPython.com
# example of creating an orphan process
from time import sleep
from multiprocessing import Process
from multiprocessing import parent_process
from multiprocessing import current_process

# return True if the current process is orphaned, False otherwise
def is_orphan():
    # get the parent process
    parent = parent_process()
    # check if orphaned
    if parent is None or not parent.is_alive():
        return True
    # not orphaned
    return False

# function executed in a new process
def task():
    # check if orphaned
    print(f'Orphaned: {is_orphan()}', flush=True)
    # block for a moment
    sleep(3)
    # check if orphaned
    print(f'Orphaned: {is_orphan()}', flush=True)

# entry point
if __name__ == '__main__':
    # create a new process
    process = Process(target=task)
    # start the new process
    process.start()
    # block for a moment
    sleep(1)
    # all done
    print('MainProcess all done')
    # process terminates itself
    p = current_process()
    p.terminate()

Running the example reports that there is no such method as terminate() on the multiprocessing.Process class.

Although, of course there is, e.g. multiprocessing.Process.terminate().

Orphaned: False
MainProcess all done
Traceback (most recent call last):
  ...
AttributeError: 'NoneType' object has no attribute 'terminate'
Orphaned: False

We get the same result if the child tries to terminate the parent process itself.

For example:

# SuperFastPython.com
# example of creating an orphan process
from time import sleep
from multiprocessing import Process
from multiprocessing import parent_process
from multiprocessing import current_process

# return True if the current process is orphaned, False otherwise
def is_orphan():
    # get the parent process
    parent = parent_process()
    # check if orphaned
    if parent is None or not parent.is_alive():
        return True
    # not orphaned
    return False

# function executed in a new process
def task():
    # check if orphaned
    print(f'Orphaned: {is_orphan()}', flush=True)
    # block for a moment
    sleep(3)
    # get the parent process and terminate it
    p = parent_process()
    p.terminate()
    # check if orphaned
    print(f'Orphaned: {is_orphan()}', flush=True)

# entry point
if __name__ == '__main__':
    # create a new process
    process = Process(target=task)
    # start the new process
    process.start()
    # block for a moment
    sleep(1)
    # all done
    print('MainProcess all done')

Running the example again reports that there is no such function as terminate() on the multiprocessing.Process class, although we know there is.

Orphaned: False
MainProcess all done
Process Process-1:
Traceback (most recent call last):
  ...
AttributeError: 'NoneType' object has no attribute 'terminate'

Nevertheless, the main process can terminate itself via the os._exit() function which will create an orphan child process.

For example:

# SuperFastPython.com
# example of creating an orphan process
from time import sleep
from multiprocessing import Process
from multiprocessing import parent_process
import os

# return True if the current process is orphaned, False otherwise
def is_orphan():
    # get the parent process
    parent = parent_process()
    # check if orphaned
    if parent is None or not parent.is_alive():
        return True
    # not orphaned
    return False

# function executed in a new process
def task():
    # check if orphaned
    print(f'Orphaned: {is_orphan()}', flush=True)
    # block for a moment
    sleep(3)
    # check if orphaned
    print(f'Orphaned: {is_orphan()}', flush=True)
    print(f'Parent Alive: {parent_process().is_alive()}', flush=True)

# entry point
if __name__ == '__main__':
    # create a new process
    process = Process(target=task)
    # start the new process
    process.start()
    # block for a moment
    sleep(1)
    # all done
    print('MainProcess all done')
    # process terminates itself
    os._exit(0)

Running the example shows that the parent process is no longer running and the child was correctly orphaned.

Orphaned: False
MainProcess all done
Orphaned: True
Parent Alive: False

Takeaways

You now know how to identify an orphaned process in Python.