El módulo resource mide y limita recursos usados por procesos Unix. Puede informar tiempo de CPU, page faults, context switches y pico de memoria, además de imponer cotas para file descriptors, tamaño de archivos, address space, creación de procesos y otros recursos soportados.
Estos controles son útiles al ejecutar plugins, compiladores, conversores, tareas de usuarios o subprocesses que no deben consumir CPU, memoria o disco sin límite. No sustituyen containers, cgroups, sandboxes o usuarios sin privilegios, pero añaden una capa defensiva.
Disponibilidad
resource existe en Unix y no en WASI. Las constantes cambian por plataforma, así que comprueba con hasattr().
import resource
if hasattr(resource, "RLIMIT_AS"):
print(resource.getrlimit(resource.RLIMIT_AS))
No asumas que Linux, macOS y BSD ofrecen o aplican los mismos límites.
Soft y hard limit
Cada recurso tiene un par (soft, hard). El soft se aplica actualmente. Un proceso puede bajarlo y normalmente volver a subirlo hasta el hard. Elevar el hard requiere privilegios.
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
print("Soft:", soft, "Hard:", hard)
RLIM_INFINITY representa ilimitado cuando el sistema lo permite.
No reduzcas el hard sin necesidad
Después de bajar el hard, un proceso sin privilegios no puede restaurarlo. Para limitaciones temporales, conserva hard y cambia solo soft.
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
resource.setrlimit(resource.RLIMIT_NOFILE, (min(256, hard), hard))
Incluso cambiar el soft del proceso principal puede romper bibliotecas cargadas después. Prefiere un hijo dedicado.
Limitar file descriptors
RLIMIT_NOFILE controla cuántos descriptors pueden estar abiertos.
def limitar_fds(maximo=128):
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
valor = min(maximo, hard)
resource.setrlimit(resource.RLIMIT_NOFILE, (valor, hard))
Al agotarse, operaciones pueden lanzar EMFILE. Cierra archivos, sockets y pipes correctamente.
Limitar CPU
RLIMIT_CPU cuenta segundos de CPU, no tiempo de reloj.
resource.setrlimit(resource.RLIMIT_CPU, (2, 3))
Superar soft envía SIGXCPU. Llegar a hard puede terminar el proceso.
Una tarea dormida o esperando red puede durar mucho más sin consumir CPU. Usa también timeout externo.
Manejar SIGXCPU
import signal
def cpu_excedida(signum, frame):
guardar_estado_minimo()
raise SystemExit(124)
if hasattr(signal, "SIGXCPU"):
signal.signal(signal.SIGXCPU, cpu_excedida)
Mantén el handler corto. signal en Python explica los riesgos de locks y logging.
Limitar tamaño de archivos
RLIMIT_FSIZE limita el tamaño máximo de un archivo creado.
limite = 10 * 1024 * 1024
resource.setrlimit(resource.RLIMIT_FSIZE, (limite, limite))
No limita el número de archivos ni todo el filesystem. Usa cuota o directorio aislado para protección adicional.
Limitar address space
RLIMIT_AS limita memoria virtual.
if hasattr(resource, "RLIMIT_AS"):
limite = 512 * 1024 * 1024
resource.setrlimit(resource.RLIMIT_AS, (limite, limite))
Bibliotecas científicas, mmap, shared objects y allocators reservan espacio virtual que no equivale al RSS. Un valor bajo puede impedir imports o generar MemoryError.
RLIMIT_DATA y RLIMIT_RSS
RLIMIT_DATA tradicionalmente limita heap, pero allocations modernas pueden evitarlo. RLIMIT_RSS puede ser advisory.
Para memoria en producción, cgroups o limits de container suelen ser más predecibles.
Stack
RLIMIT_STACK limita la stack del thread principal. Bajarlo demasiado puede provocar crash en recursión, extensiones C o inicialización.
Número de procesos
RLIMIT_NPROC limita creación de procesos según la plataforma.
if hasattr(resource, "RLIMIT_NPROC"):
soft, hard = resource.getrlimit(resource.RLIMIT_NPROC)
resource.setrlimit(resource.RLIMIT_NPROC, (min(32, hard), hard))
Threads pueden contar y servicios con el mismo usuario pueden competir.
Aplicar límites en un wrapper
preexec_fn puede ser peligroso en un padre multithread. Un wrapper simple es más seguro.
# wrapper_limitado.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:])
El padre inicia el wrapper con lista de argumentos, sin shell.
prlimit()
En Linux, prlimit() consulta o modifica límites de otro proceso con permisos.
if hasattr(resource, "prlimit"):
anterior = resource.prlimit(pid, resource.RLIMIT_NOFILE, (128, 128))
print(anterior)
Puede lanzar ProcessLookupError o PermissionError. Verifica identidad porque los PIDs se reutilizan.
Auditoría
setrlimit() y prlimit() generan eventos de auditoría.
getrusage()
getrusage() informa sobre el proceso actual, hijos recolectados o thread actual.
uso = resource.getrusage(resource.RUSAGE_SELF)
print("CPU usuario:", uso.ru_utime)
print("CPU sistema:", uso.ru_stime)
print("Pico RSS:", uso.ru_maxrss)
RUSAGE_SELF incluye todas las threads. RUSAGE_CHILDREN incluye hijos terminados y esperados.
Unidades de ru_maxrss
La unidad depende de la plataforma. Linux normalmente devuelve KiB y macOS bytes. Normaliza antes de mostrar MB.
import platform
valor = uso.ru_maxrss
rss_bytes = valor if platform.system() == "Darwin" else valor * 1024
Valida esta regla en otros Unix.
Pico no es memoria actual
ru_maxrss representa el máximo observado y no disminuye al liberar memoria.
Para una serie temporal, usa procfs, psutil o métricas del container.
Tiempo de CPU
ru_utime mide user mode y ru_stime kernel mode.
antes = resource.getrusage(resource.RUSAGE_SELF)
ejecutar_trabajo()
despues = resource.getrusage(resource.RUSAGE_SELF)
cpu = (despues.ru_utime - antes.ru_utime) + (despues.ru_stime - antes.ru_stime)
Para microbenchmarks, usa timeit.
Page faults
ru_minflt cuenta faults sin I/O y ru_majflt los que requieren almacenamiento. Dependen de cache, kernel y entorno.
Context switches
ru_nvcsw cuenta cambios voluntarios y ru_nivcsw involuntarios. Muchos involuntarios pueden indicar competencia por CPU.
Operaciones de bloque
ru_inblock y ru_oublock representan operaciones, no bytes.
Hijos recolectados
Para aparecer en RUSAGE_CHILDREN, el hijo debe terminar y ser recolectado con wait() o waitpid().
Tamaño de página
print(resource.getpagesize())
No describe necesariamente huge pages ni todos los detalles de hardware.
Los límites no son sandbox
Un proceso limitado puede acceder a archivos permitidos, red, entorno y credenciales. Combina rlimits con usuario sin privilegios, directorios aislados, allowlist y containers.
Aplicar temprano
Define límites antes de cargar contenido no confiable. Reducir una cota no deshace recursos ya consumidos.
Diagnóstico
Registra los límites efectivos. Al terminar por señal, informa que CPU, archivos o memoria pueden ser responsables sin afirmar certeza absoluta.
Pruebas
Prueba constantes ausentes, soft mayor que hard, intento de subir hard, CPU-bound, sleep, archivos, descriptors, imports bajo RLIMIT_AS, hijos, threads, Linux/macOS y container.
Errores comunes
Los fallos frecuentes son limitar el proceso principal, confundir CPU y wall time, tratar ru_maxrss como memoria actual, ignorar unidades, bajar hard de forma irreversible, asumir RLIMIT_RSS rígido y llamar sandbox a los rlimits.
Conclusión
resource mide consumo e impone cotas Unix básicas. Funciona mejor en procesos hijos dedicados con limits definidos antes del trabajo y monitoreo externo.
Comprueba constantes, normaliza métricas y combina rlimits con aislamiento real. Consulta la documentación oficial de resource y getrlimit(2).







