Skip to content Skip to sidebar Skip to footer

Python Script Hanging When Running In The Background

I have a Python script (run on 2.7) that behaves differently when I run it in from the command line versus the background. When I run it from the terminal it runs as expected, the

Solution 1:

In order to allow the program to run both from the terminal / in the background do I need to basically put an if statement before the raw_input that checks whether it's in the background or not or am I missing else that would help?

In a way this probably works (I am assuming you are running this on a *nix), however if user were to send the process backing into background (i.e. suspend it using CtrlZ then resuming it in background with %&) while raw_input is waiting on user input, then the read on stdin will then be blocked as it is in the background, thus causing the kernel to stop the process as this is how stdio works. If this is acceptable (basically user has to hit enter before suspending the process), you can simply do this:

import oswhile True:
    userInput = ""ifos.getpgrp() == os.tcgetpgrp(sys.stdout.fileno()):
        userInput = raw_input("")
    time.sleep(1)

What os.getpgrp does is that returns the id of the current os group, and then os.tcgetpgrp gets the process group associated with the stdout for this process, if they match, it means this process is currently in the foreground, meaning you can probably call raw_input without blocking the threads.

Another question raised a similar issue and I have a longer explanation at: Freeze stdin when in the background, unfreeze it when in the foreground.


The better way is to combine this with select.poll, and address interactive I/O separately (by using /dev/tty directly) from standard I/O as you don't want stdin/stdout redirection being "polluted" by that. Here is the more complete version that contains both these ideas:

tty_in = open('/dev/tty', 'r')
tty_out = open('/dev/tty', 'w')
fn = tty_in.fileno()
poll = select.poll()
poll.register(fn, select.POLLIN)

whileTrue:
    if os.getpgrp() == os.tcgetpgrp(fn) and poll.poll(10):  # 10 ms# poll should only return if the input buffer is filled,# which is triggered when a user enters a complete line,# which lets the following readline call to not block on# a lack of input.
        userInput = tty_in.readline()
        # This should allow us to exit outif userInput.strip() == "quit":
            sys.exit()

The background/foreground detection is still needed as the process is not fully detached from the shell (since it can be brought back to the foreground) thus poll will return the fileno of the tty if any input is sent into the shell, and if this triggers the readline which will then stop the process.

This solution has the advantage of not requiring the user to hit enter and quickly suspend the task to send it back to the background before raw_input traps and blocks stdin to stop the process (as poll checks whether there's input to be read), and allow proper stdin/stdout redirection (as all interactive input is handled via /dev/tty) so users can do something like:

$ python script.py < script.py 2> stderrinput stream length: 2116

In the completed example below it also provide a prompt to the user, i.e. a > is shown whenever a command is sent or whenever the process is returned to foreground, and wrapped the entire thing in a main function, and modified the second thread to spit things out at stderr:

import os
import select
import sys
import time
from threading import Thread

defthreadOne():
    whileTrue:
        print("Thread 1")
        time.sleep(1)

defthreadTwo():
    whileTrue:
        # python 2 print does not support file argument like python 3,# so writing to sys.stderr directly to simulate error message.
        sys.stderr.write("Thread 2\n")
        time.sleep(1)

# Run the threads in the background
threadOne = Thread(target = threadOne)
threadOne.daemon = True

threadTwo = Thread(target = threadTwo)
threadTwo.daemon = Truedefmain():
    threadOne.start()
    threadTwo.start()

    tty_in = open('/dev/tty', 'r')
    tty_out = open('/dev/tty', 'w')
    fn = tty_in.fileno()
    poll = select.poll()
    poll.register(fn, select.POLLIN)

    userInput = ""
    chars = []
    prompt = TruewhileTrue:
        if os.getpgrp() == os.tcgetpgrp(fn) and poll.poll(10):  # 10 ms# poll should only return if the input buffer is filled,# which is triggered when a user enters a complete line,# which lets the following readline call to not block on# a lack of input.
            userInput = tty_in.readline()
            # This should allow us to exit outif userInput.strip() == "quit":
                sys.exit()
            # alternatively an empty string from Ctrl-D could be the# other exit method.else:
                tty_out.write("user input: %s\n" % userInput)
                prompt = Trueelifnot os.getpgrp() == os.tcgetpgrp(fn):
            time.sleep(0.1)
            if os.getpgrp() == os.tcgetpgrp(fn):
                # back to foreground, print a prompt:
                prompt = Trueif prompt:
            tty_out.write('> ')
            tty_out.flush()
            prompt = Falseif __name__ == '__main__':
    try:
        # Uncomment if you are expecting stdin# print('input stream length: %d ' % len(sys.stdin.read()))
        main()
    except KeyboardInterrupt:
        print("Forcibly interrupted.  Quitting")
        sys.exit()  # maybe with an error code

Has been an interesting exercise; this was a rather good and interesting question, if I may say.

One final note: this is not cross-platform, it will not work on Windows as it doesn't have select.poll and /dev/tty.

Post a Comment for "Python Script Hanging When Running In The Background"