Python resource: CPU and Memory Limits

Published on: August 26, 2026
Reading time: 5 minutes
High-resolution close-up of a CPU processor, RAM sticks, and a hard drive, showcasing modern computer hardware.

The resource module measures and limits resources used by Unix processes. It can report CPU time, page faults, context switches, and peak memory while enforcing quotas for file descriptors, file size, address space, process creation, and other platform-supported resources.

These controls are useful when running plugins, compilers, converters, user jobs, or subprocesses that must not consume CPU, memory, or disk without bounds. They do not replace containers, cgroups, sandboxes, or a dedicated unprivileged account, but they add an important defensive layer.

Availability

resource is available on Unix and not on WASI. Constants vary by platform, so check them with hasattr().

import resource

if hasattr(resource, "RLIMIT_AS"):
    print(resource.getrlimit(resource.RLIMIT_AS))

Do not assume Linux, macOS, and BSD expose or enforce the same limits.

Soft and hard limits

Every resource has a (soft, hard) pair. The soft limit is currently enforced. A process can normally lower it and raise it again up to the hard limit. Raising the hard limit requires appropriate privileges.

soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
print("Soft:", soft, "Hard:", hard)

RLIM_INFINITY represents unlimited when supported by the system.

Avoid lowering the hard limit unnecessarily

After an unprivileged process lowers a hard limit, it cannot restore it. For temporary self-limiting, keep the hard value and change only the soft limit.

soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
resource.setrlimit(resource.RLIMIT_NOFILE, (min(256, hard), hard))

Even changing the main process’s soft limit may break libraries loaded later. Prefer applying limits inside a dedicated child process.

Limit file descriptors

RLIMIT_NOFILE controls how many descriptors can be open.

def limit_fds(maximum=128):
    soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
    value = min(maximum, hard)
    resource.setrlimit(resource.RLIMIT_NOFILE, (value, hard))

When exhausted, operations may raise OSError with EMFILE. Close files, sockets, and pipes correctly; a quota is not a cleanup strategy.

Limit CPU time

RLIMIT_CPU counts CPU seconds rather than wall-clock time.

resource.setrlimit(resource.RLIMIT_CPU, (2, 3))

Exceeding the soft value sends SIGXCPU. Continuing until the hard value may terminate the process.

A sleeping or network-bound task can run much longer than two seconds while consuming little CPU. Use an external wall-clock timeout as well.

Handle SIGXCPU

import signal


def cpu_exceeded(signum, frame):
    save_minimal_state()
    raise SystemExit(124)

if hasattr(signal, "SIGXCPU"):
    signal.signal(signal.SIGXCPU, cpu_exceeded)

Keep handlers short. Python signal explains why locks and complex logging are unsafe there.

Limit file size

RLIMIT_FSIZE limits the maximum size of a file created by the process.

limit = 10 * 1024 * 1024
resource.setrlimit(resource.RLIMIT_FSIZE, (limit, limit))

This reduces the risk of one huge output file but does not limit file count or all filesystem usage. Use a quota or isolated directory for stronger protection.

Limit address space

RLIMIT_AS limits virtual address space.

if hasattr(resource, "RLIMIT_AS"):
    limit = 512 * 1024 * 1024
    resource.setrlimit(resource.RLIMIT_AS, (limit, limit))

Scientific libraries, mmap, shared objects, and allocators reserve virtual space that is not equal to resident memory. A low value may prevent imports or raise MemoryError before useful work begins.

RLIMIT_DATA and RLIMIT_RSS

RLIMIT_DATA traditionally limits the heap, but modern allocations may bypass it. RLIMIT_RSS may be advisory or weakly enforced.

For production memory control, container or cgroup limits are usually more predictable.

Stack size

RLIMIT_STACK limits the main thread’s stack. Lowering it too far can crash recursive code, C extensions, or library initialization.

It is not a safe substitute for controlling Python recursion. sys.setrecursionlimit() addresses a different layer.

Process count

RLIMIT_NPROC limits process creation according to platform rules.

if hasattr(resource, "RLIMIT_NPROC"):
    soft, hard = resource.getrlimit(resource.RLIMIT_NPROC)
    resource.setrlimit(resource.RLIMIT_NPROC, (min(32, hard), hard))

Threads may count on some systems, and services sharing a user account may compete for the same quota.

Apply limits in a child wrapper

preexec_fn exists on Unix but can be unsafe in a multithreaded parent. A simple wrapper executable is easier to reason about.

# limited_wrapper.py
import os
import resource
import sys

resource.setrlimit(resource.RLIMIT_CPU, (2, 3))
resource.setrlimit(resource.RLIMIT_FSIZE, (10_000_000, 10_000_000))
os.execvp(sys.argv[1], sys.argv[1:])

The parent starts the wrapper with an argument list and no shell.

prlimit()

On Linux, prlimit() reads or changes another process’s limits when permitted.

if hasattr(resource, "prlimit"):
    previous = resource.prlimit(pid, resource.RLIMIT_NOFILE, (128, 128))
    print(previous)

It can raise ProcessLookupError when the PID disappears and PermissionError without sufficient capability. Verify process identity because PIDs are reused.

Auditing

setrlimit() and prlimit() raise auditing events. Restricted runtimes may log or block changes.

getrusage()

getrusage() reports the current process, reaped children, or current thread when supported.

usage = resource.getrusage(resource.RUSAGE_SELF)
print("User CPU:", usage.ru_utime)
print("System CPU:", usage.ru_stime)
print("Peak RSS:", usage.ru_maxrss)

RUSAGE_SELF includes all threads. RUSAGE_CHILDREN includes terminated and waited-for children. RUSAGE_THREAD may report the current thread.

ru_maxrss units

The unit of ru_maxrss is platform-dependent. Linux normally reports KiB, while macOS reports bytes. Normalize before showing MB or comparing hosts.

import platform

value = usage.ru_maxrss
rss_bytes = value if platform.system() == "Darwin" else value * 1024

Validate this rule on other Unix systems.

Peak is not current memory

ru_maxrss is the highest observed resident usage. It never decreases when memory is released.

Use procfs, psutil, container metrics, or a system monitor for a time series of current memory.

CPU accounting

ru_utime is user-mode CPU and ru_stime is kernel-mode CPU.

before = resource.getrusage(resource.RUSAGE_SELF)
run_work()
after = resource.getrusage(resource.RUSAGE_SELF)

cpu = (after.ru_utime - before.ru_utime) + (after.ru_stime - before.ru_stime)

This measures accumulated CPU, not a controlled benchmark. Use timeit for microbenchmarks.

Page faults

ru_minflt counts faults not requiring I/O, while ru_majflt counts faults requiring storage access.

Results depend on cache, kernel, and environment. One run is not enough to prove a regression.

Context switches

ru_nvcsw counts voluntary switches and ru_nivcsw involuntary switches. Many involuntary switches can indicate CPU contention; many voluntary switches often reflect waiting or synchronization.

Block operations

ru_inblock and ru_oublock represent block I/O operations when supported. They are not byte counts.

Children must be reaped

For usage to appear under RUSAGE_CHILDREN, a child must terminate and be collected with wait() or waitpid().

Page size

getpagesize() returns the system page size used by this interface.

print(resource.getpagesize())

It does not necessarily describe huge pages or every hardware detail.

Limits are not a sandbox

A limited process may still read allowed files, access the network, inherit environment variables, and use credentials. Combine rlimits with an unprivileged user, isolated directories, executable allowlists, and OS sandboxing or containers.

Apply limits early

Set limits before loading or executing untrusted content. Lowering a quota does not undo files already opened or memory already allocated.

Diagnostics

Log effective limits at child startup. When a process exits by signal, report that CPU, file-size, or memory policy may be involved without claiming certainty. SIGKILL can also come from an operator.

Testing

Test missing constants, soft greater than hard, attempts to raise hard limits, CPU-bound and sleeping tasks, file growth, descriptor exhaustion, library imports under RLIMIT_AS, child usage, threads, Linux/macOS, and containers.

Common mistakes

Common failures include limiting the main application, confusing CPU time with wall time, treating ru_maxrss as current memory, ignoring platform units, lowering hard limits permanently, assuming RLIMIT_RSS is strict, and calling rlimits a complete sandbox.

Conclusion

resource measures consumption and enforces basic Unix quotas. It works best in dedicated child processes with limits set before work and external monitoring for wall time and memory.

Check every constant on the target system, normalize metrics, and combine rlimits with real isolation. Consult the official resource documentation and getrlimit(2).

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python pty: Automate Unix Terminals

    Learn Python pty to run and test interactive programs, control pseudo-terminals, handle EOF, resize, signals, timeouts, and cleanup.

    Ler mais

    Tempo de leitura: 5 minutos
    25/08/2026
    Close-up of hands typing on a laptop keyboard, Python book in sight, coding in progress.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python tty: Raw and Cbreak Modes

    Learn Python tty to use raw and cbreak modes, read keys, parse sequences, handle Unicode, and safely restore Unix terminals.

    Ler mais

    Tempo de leitura: 5 minutos
    25/08/2026
    Detailed view of programming code in a dark theme on a computer screen.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python termios: Safe Terminal Control

    Learn Python termios for canonical mode, echo, key reads, baud rate, queues, window size, and safe restoration of POSIX terminals.

    Ler mais

    Tempo de leitura: 5 minutos
    25/08/2026
    Side view of contemplating female assistant in casual style standing near shelves and choosing file with documents
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python fcntl: File Locks and Control

    Learn Python fcntl for file locks, descriptor flags, ioctl, pipes, and Unix control while avoiding invalid buffers and memory corruption.

    Ler mais

    Tempo de leitura: 5 minutos
    25/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python readline: History and Autocomplete

    Learn Python readline for command history, tab completion, line editing, GNU Readline, libedit, and safer interactive terminal prompts.

    Ler mais

    Tempo de leitura: 5 minutos
    25/08/2026
    Peaceful river scene with mossy boulders and flowing water captured in long exposure.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python io: Master Streams and Buffers

    Learn Python io for text and binary streams, buffering, encodings, StringIO, BytesIO, raw I/O, and file-like interfaces.

    Ler mais

    Tempo de leitura: 6 minutos
    24/08/2026