Python: ulimit and nice for subprocess.call / subprocess.Popen?

Parand picture Parand · Nov 6, 2009 · Viewed 26k times · Source

I need to limit the amount of time and cpu taken by external command line apps I spawn from a python process using subprocess.call , mainly because sometimes the spawned process gets stuck and pins the cpu at 99%.

nice and ulimit seem like reasonable ways to do this, but I'm not sure how they'd interact with subprocess.

  • The limits look something like:
    • Kill the process if it's taking more than 60 seconds
    • Limit it to 20% of cpu
  • I want to apply the resource limiting to the subprocess, not to the python process that's spawning the subprocesses.

Is there a way to apply nice and ulimit to the subprocess.call spawned process? Are there better python-native alternatives?

This is on a linux (ubuntu) system.

Answer

Erik Forsberg picture Erik Forsberg · Nov 6, 2009

Use the preexec_fn parameter to subprocess.Popen, and the resource module. Example:

parent.py:

#!/usr/bin/env python

import os
import sys
import resource
import subprocess

def setlimits():
    # Set maximum CPU time to 1 second in child process, after fork() but before exec()
    print "Setting resource limit in child (pid %d)" % os.getpid()
    resource.setrlimit(resource.RLIMIT_CPU, (1, 1))

print "CPU limit of parent (pid %d)" % os.getpid(), resource.getrlimit(resource.RLIMIT_CPU)
p = subprocess.Popen(["./child.py"], preexec_fn=setlimits)
print "CPU limit of parent (pid %d) after startup of child" % os.getpid(), resource.getrlimit(resource.RLIMIT_CPU)
p.wait()
print "CPU limit of parent (pid %d) after child finished executing" % os.getpid(), resource.getrlimit(resource.RLIMIT_CPU)

child.py:

#!/usr/bin/env python

import os
import sys
import resource

print "CPU limit of child (pid %d)" % os.getpid(), resource.getrlimit(resource.RLIMIT_CPU)

parent.py will fork into a new process. In the new process, it will call setlimits(), then exec child.py. This means the resource will be limited in the child process, but not in the parent.

Output when running program:

./parent.py
CPU limit of parent (pid 17404) (-1, -1)
Setting resource limit in child (pid 17405)
CPU limit of parent (pid 17404) after startup of child (-1, -1)
CPU limit of child (pid 17405) (1, 1)
CPU limit of parent (pid 17404) after child finished executing (-1, -1)

This is in many cases a better solution than trying to use ulimit, since it's not always a good idea to spawn subprocess via shell, especially since it often causes ugly parameter quoting trouble.