O módulo resource mede e limita recursos consumidos por processos Unix. Ele permite consultar tempo de CPU, page faults, mudanças de contexto e pico de memória, além de definir cotas para file descriptors, tamanho de arquivos, endereço virtual, processos filhos e outros recursos suportados pelo sistema.
Esses limites são especialmente úteis ao executar plugins, conversores, tarefas de usuários, compiladores ou subprocessos que não devem consumir CPU, memória ou arquivos indefinidamente. Eles não substituem isolamento por container, cgroup, sandbox ou usuário dedicado, mas adicionam uma camada importante de defesa.
Disponibilidade
resource está disponível em Unix e não em WASI. As constantes variam por sistema; uma aplicação deve testar com hasattr().
import resource
if hasattr(resource, "RLIMIT_AS"):
print(resource.getrlimit(resource.RLIMIT_AS))
Não presuma que Linux, macOS e BSD oferecem os mesmos limites ou que os aplicam da mesma forma.
Soft limit e hard limit
Cada recurso possui um par (soft, hard). O soft limit é o valor aplicado atualmente. O processo pode reduzi-lo e, normalmente, aumentá-lo até o hard limit. O hard limit não pode ser elevado sem privilégios adequados.
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
print("Soft:", soft, "Hard:", hard)
RLIM_INFINITY representa ilimitado quando o sistema permite.
Não reduza o hard limit sem necessidade
Depois de baixar o hard limit, um processo sem privilégios não consegue restaurá-lo. Para limitações temporárias no próprio processo, preserve o hard limit e altere somente o soft.
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
resource.setrlimit(resource.RLIMIT_NOFILE, (min(256, hard), hard))
Mesmo alterar o soft limit do processo principal pode quebrar bibliotecas carregadas depois. Em geral, aplique cotas dentro de um subprocesso dedicado.
Limitar file descriptors
RLIMIT_NOFILE controla quantos descriptors podem ficar abertos.
def limitar_fds(maximo=128):
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
novo = min(maximo, hard)
resource.setrlimit(resource.RLIMIT_NOFILE, (novo, hard))
Ao atingir o limite, operações podem gerar OSError com EMFILE. Feche arquivos, sockets e pipes corretamente; não use a cota como substituto de cleanup.
Limitar CPU
RLIMIT_CPU limita segundos de CPU, não tempo de relógio.
resource.setrlimit(resource.RLIMIT_CPU, (2, 3))
Ao ultrapassar o soft limit, o sistema envia SIGXCPU. Se o processo continuar e atingir o hard limit, pode ser encerrado.
Uma tarefa que dorme ou espera rede pode durar muito mais que dois segundos sem consumir dois segundos de CPU. Use timeout externo para tempo total.
Tratar SIGXCPU
import signal
def cpu_excedida(signum, frame):
registrar_estado_minimo()
raise SystemExit(124)
if hasattr(signal, "SIGXCPU"):
signal.signal(signal.SIGXCPU, cpu_excedida)
O handler deve ser curto. O guia de signal no Python explica por que locks e logging complexo podem ser perigosos em handlers.
Limitar tamanho de arquivos
RLIMIT_FSIZE limita o tamanho máximo de arquivos criados pelo processo.
resource.setrlimit(resource.RLIMIT_FSIZE, (10 * 1024 * 1024, 10 * 1024 * 1024))
Isso reduz risco de uma tarefa preencher o disco com um único arquivo, mas não limita o número total de arquivos nem espaço já existente. Use quota de filesystem ou diretório isolado para proteção mais completa.
Limitar address space
RLIMIT_AS limita o espaço de endereçamento virtual.
if hasattr(resource, "RLIMIT_AS"):
limite = 512 * 1024 * 1024
resource.setrlimit(resource.RLIMIT_AS, (limite, limite))
Bibliotecas científicas, mmap, shared libraries e alocadores reservam espaço virtual que não corresponde ao RSS real. Um valor muito baixo pode impedir imports ou gerar MemoryError antes do trabalho começar.
RLIMIT_DATA e RLIMIT_RSS
RLIMIT_DATA limita tradicionalmente o heap, mas nem todas as alocações modernas passam por ele. RLIMIT_RSS pode ser apenas uma sugestão ou não ser aplicado de forma rígida.
Para memória em produção, cgroups ou limites do container costumam ser mais previsíveis que um único rlimit.
Stack
RLIMIT_STACK limita a stack da thread principal. Reduzi-lo demais pode causar crash em recursão, extensões C ou inicialização de bibliotecas.
Ele não é uma forma segura de controlar recursão Python. Use também sys.setrecursionlimit() com cautela.
Número de processos
RLIMIT_NPROC limita processos para o usuário ou processo conforme a plataforma.
if hasattr(resource, "RLIMIT_NPROC"):
soft, hard = resource.getrlimit(resource.RLIMIT_NPROC)
resource.setrlimit(resource.RLIMIT_NPROC, (min(32, hard), hard))
Threads podem ou não entrar nessa contagem dependendo do sistema. Containers e serviços compartilhando usuário também podem competir pela mesma cota.
Aplicar limites em subprocessos
Em Unix, subprocess.Popen() aceita preexec_fn, mas seu uso é perigoso em processos multithread. Prefira um executável wrapper simples que aplica limites e depois chama exec.
# 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:])
O processo pai inicia esse wrapper com uma lista de argumentos, sem shell.
prlimit()
Em Linux, prlimit() consulta ou altera limites de outro processo quando há permissão.
if hasattr(resource, "prlimit"):
anterior = resource.prlimit(pid, resource.RLIMIT_NOFILE, (128, 128))
print(anterior)
Pode gerar ProcessLookupError se o PID desapareceu e PermissionError sem capacidade suficiente. PIDs podem ser reutilizados; confirme identidade.
Auditoria
setrlimit() e prlimit() geram eventos de auditoria. Ambientes controlados podem registrar ou bloquear alterações.
getrusage()
getrusage() retorna métricas do processo atual, filhos já coletados ou thread atual quando suportado.
uso = resource.getrusage(resource.RUSAGE_SELF)
print("CPU usuário:", uso.ru_utime)
print("CPU sistema:", uso.ru_stime)
print("Pico RSS:", uso.ru_maxrss)
RUSAGE_SELF soma todas as threads do processo. RUSAGE_CHILDREN inclui filhos encerrados e aguardados. RUSAGE_THREAD pode medir apenas a thread atual.
Unidade de ru_maxrss
A unidade de ru_maxrss depende da plataforma. Linux normalmente reporta KiB; macOS reporta bytes. Não compare ou exiba como MB sem normalizar pelo sistema.
import platform
valor = uso.ru_maxrss
bytes_rss = valor if platform.system() == "Darwin" else valor * 1024
Documente essa aproximação e valide em outros Unix.
Pico não é memória atual
ru_maxrss representa o máximo observado, não o consumo atual. Depois que a aplicação libera memória, o valor não diminui.
Para série temporal, use métricas do sistema, procfs, psutil ou observabilidade do container.
Tempo de CPU
ru_utime mede tempo em user mode e ru_stime em kernel mode.
antes = resource.getrusage(resource.RUSAGE_SELF)
executar_trabalho()
depois = resource.getrusage(resource.RUSAGE_SELF)
cpu = (depois.ru_utime - antes.ru_utime) + (depois.ru_stime - antes.ru_stime)
Isso mede CPU acumulada e não substitui benchmark repetível. Para microbenchmarks, use timeit.
Page faults
ru_minflt conta faults que não exigiram I/O; ru_majflt conta aqueles que exigiram acesso a armazenamento.
Os números ajudam a comparar cargas, mas dependem de cache, kernel e ambiente. Uma execução isolada não prova regressão.
Context switches
ru_nvcsw conta trocas voluntárias e ru_nivcsw involuntárias. Muitas involuntárias podem indicar competição por CPU; muitas voluntárias podem refletir espera ou sincronização.
Operações de bloco
ru_inblock e ru_oublock representam operações de entrada e saída em bloco quando a plataforma fornece a métrica. Não interprete como bytes.
Filhos precisam ser aguardados
Para aparecer em RUSAGE_CHILDREN, o processo filho precisa ter terminado e sido coletado com wait() ou waitpid().
Evite zombies e meça depois do wait.
Tamanho de página
getpagesize() retorna o tamanho de página usado pelo sistema.
pagina = resource.getpagesize()
print(pagina)
Ele pode ser útil para alinhamento e estimativas, mas não necessariamente corresponde a todas as huge pages ou detalhes de hardware.
Limites não são sandbox
Um processo limitado ainda pode acessar arquivos permitidos, rede, variáveis de ambiente e credenciais. Combine rlimits com usuário sem privilégio, diretório isolado, allowlist de executáveis, seccomp/capsicum quando disponível e container.
Ordem de aplicação
Aplique limites antes de carregar ou executar conteúdo não confiável. Depois que a tarefa abriu milhares de arquivos ou alocou memória, reduzir a cota não desfaz o consumo existente.
Logs e diagnóstico
Registre os limites efetivos no início do filho. Ao detectar término por sinal, converta o status e informe se CPU, arquivo ou memória podem ter sido responsáveis.
Não assuma que todo SIGKILL veio de OOM; o operador também pode ter enviado o sinal.
Testes
Teste constantes ausentes, soft maior que hard, tentativa de elevar hard limit, CPU-bound, sleep, criação de arquivo, muitos descriptors, import de bibliotecas sob RLIMIT_AS, filhos, threads, Linux/macOS e container.
Erros comuns
Os erros mais frequentes são aplicar limites no processo principal, confundir CPU com tempo total, tratar ru_maxrss como memória atual, ignorar unidade por plataforma, reduzir hard limit sem retorno, presumir RLIMIT_RSS rígido e considerar rlimit uma sandbox completa.
Conclusão
resource mede consumo e impõe cotas Unix básicas. Ele funciona melhor em subprocessos dedicados, com limites definidos antes do trabalho e observação externa de timeout e memória.
Teste cada constante no sistema alvo, normalize métricas e combine rlimits com isolamento real. Consulte a documentação oficial de resource e o manual getrlimit(2).







