Nota: Esta pergunta foi feita originalmente aqui, mas o tempo de recompensa expirou, embora uma resposta aceitável não tenha sido encontrada. Estou fazendo novamente esta pergunta, incluindo todos os detalhes fornecidos na pergunta original.
Um script Python está executando um conjunto de funções de classe a cada 60 segundos usando o módulo sched :
# sc is a sched.scheduler instance
sc.enter(60, 1, self.doChecks, (sc, False))
O script está sendo executado como um processo daemonizado usando o código aqui .
Vários métodos de classe que são chamados como parte de doChecks usam o módulo de subprocesso para chamar funções do sistema a fim de obter estatísticas do sistema:
ps = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE).communicate()[0]
Isso funciona bem por um período de tempo antes de todo o script travar com o seguinte erro:
File "/home/admin/sd-agent/checks.py", line 436, in getProcesses
File "/usr/lib/python2.4/subprocess.py", line 533, in __init__
File "/usr/lib/python2.4/subprocess.py", line 835, in _get_handles
OSError: [Errno 12] Cannot allocate memory
A saída de free -m no servidor depois que o script travou é:
$ free -m
total used free shared buffers cached
Mem: 894 345 549 0 0 0
-/+ buffers/cache: 345 549
Swap: 0 0 0
O servidor está executando CentOS 5.3. Não consigo reproduzir em minhas próprias caixas CentOS nem com nenhum outro usuário relatando o mesmo problema.
Tentei várias coisas para depurar isso, conforme sugerido na pergunta original:
Registrando a saída de free -m antes e depois da chamada Popen. Não há mudança significativa no uso da memória, ou seja, a memória não está sendo gradualmente usada à medida que o script é executado.
Eu adicionei close_fds = True para a chamada Popen, mas isso não fez diferença - o script ainda travava com o mesmo erro. Sugerido aqui e aqui .
Eu verifiquei os rlimits que mostraram (-1, -1) em RLIMIT_DATA e RLIMIT_AS como sugerido aqui .
Um artigo sugeriu que não ter espaço de troca pode ser a causa, mas a troca está realmente disponível sob demanda (de acordo com o host da web) e isso também foi sugerido como uma causa falsa aqui .
Os processos estão sendo fechados porque esse é o comportamento de usar .communicate () conforme respaldado pelo código-fonte Python e comentários aqui .
As verificações inteiras podem ser encontradas no GitHub aqui com a função getProcesses definida na linha 442. Isso é chamado por doChecks () começando na linha 520.
O script foi executado com strace com a seguinte saída antes da falha:
recv(4, "Total Accesses: 516662\nTotal kBy"..., 234, 0) = 234
gettimeofday({1250893252, 887805}, NULL) = 0
write(3, "2009-08-21 17:20:52,887 - checks"..., 91) = 91
gettimeofday({1250893252, 888362}, NULL) = 0
write(3, "2009-08-21 17:20:52,888 - checks"..., 74) = 74
gettimeofday({1250893252, 888897}, NULL) = 0
write(3, "2009-08-21 17:20:52,888 - checks"..., 67) = 67
gettimeofday({1250893252, 889184}, NULL) = 0
write(3, "2009-08-21 17:20:52,889 - checks"..., 81) = 81
close(4) = 0
gettimeofday({1250893252, 889591}, NULL) = 0
write(3, "2009-08-21 17:20:52,889 - checks"..., 63) = 63
pipe([4, 5]) = 0
pipe([6, 7]) = 0
fcntl64(7, F_GETFD) = 0
fcntl64(7, F_SETFD, FD_CLOEXEC) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0xb7f12708) = -1 ENOMEM (Cannot allocate memory)
write(2, "Traceback (most recent call last"..., 35) = 35
open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, " File \"/usr/bin/sd-agent/agent."..., 52) = 52
open("/home/admin/sd-agent/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, " File \"/home/admin/sd-agent/dae"..., 60) = 60
open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, " File \"/usr/bin/sd-agent/agent."..., 54) = 54
open("/usr/lib/python2.4/sched.py", O_RDONLY|O_LARGEFILE) = 8
write(2, " File \"/usr/lib/python2.4/sched"..., 55) = 55
fstat64(8, {st_mode=S_IFREG|0644, st_size=4054, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7d28000
read(8, "\"\"\"A generally useful event sche"..., 4096) = 4054
write(2, " ", 4) = 4
write(2, "void = action(*argument)\n", 25) = 25
close(8) = 0
munmap(0xb7d28000, 4096) = 0
open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, " File \"/usr/bin/sd-agent/checks"..., 60) = 60
open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, " File \"/usr/bin/sd-agent/checks"..., 64) = 64
open("/usr/lib/python2.4/subprocess.py", O_RDONLY|O_LARGEFILE) = 8
write(2, " File \"/usr/lib/python2.4/subpr"..., 65) = 65
fstat64(8, {st_mode=S_IFREG|0644, st_size=39931, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7d28000
read(8, "# subprocess - Subprocesses with"..., 4096) = 4096
read(8, "lso, the newlines attribute of t"..., 4096) = 4096
read(8, "code < 0:\n print >>sys.st"..., 4096) = 4096
read(8, "alse does not exist on 2.2.0\ntry"..., 4096) = 4096
read(8, " p2cread\n # c2pread <-"..., 4096) = 4096
write(2, " ", 4) = 4
write(2, "errread, errwrite)\n", 19) = 19
close(8) = 0
munmap(0xb7d28000, 4096) = 0
open("/usr/lib/python2.4/subprocess.py", O_RDONLY|O_LARGEFILE) = 8
write(2, " File \"/usr/lib/python2.4/subpr"..., 71) = 71
fstat64(8, {st_mode=S_IFREG|0644, st_size=39931, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7d28000
read(8, "# subprocess - Subprocesses with"..., 4096) = 4096
read(8, "lso, the newlines attribute of t"..., 4096) = 4096
read(8, "code < 0:\n print >>sys.st"..., 4096) = 4096
read(8, "alse does not exist on 2.2.0\ntry"..., 4096) = 4096
read(8, " p2cread\n # c2pread <-"..., 4096) = 4096
read(8, "table(self, handle):\n "..., 4096) = 4096
read(8, "rrno using _sys_errlist (or siml"..., 4096) = 4096
read(8, " p2cwrite = None, None\n "..., 4096) = 4096
write(2, " ", 4) = 4
write(2, "self.pid = os.fork()\n", 21) = 21
close(8) = 0
munmap(0xb7d28000, 4096) = 0
write(2, "OSError", 7) = 7
write(2, ": ", 2) = 2
write(2, "[Errno 12] Cannot allocate memor"..., 33) = 33
write(2, "\n", 1) = 1
unlink("/var/run/sd-agent.pid") = 0
close(3) = 0
munmap(0xb7e0d000, 4096) = 0
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x589978}, {0xb89a60, [], SA_RESTORER, 0x589978}, 8) = 0
brk(0xa022000) = 0xa022000
exit_group(1) = ?
/var/log/messages
oudmesg
comande.Respostas:
Como regra geral (ou seja, baunilha kernels),
fork
/clone
falhas comENOMEM
ocorrem especificamente por causa de qualquer um honesto a Deus fora-de-memória condição (dup_mm
,dup_task_struct
,alloc_pid
,mpol_dup
,mm_init
etc. coaxar), ou porquesecurity_vm_enough_memory_mm
você falhou enquanto reforçando a política overcommit .Comece verificando o tamanho de vms do processo que falhou na bifurcação, no momento da tentativa de bifurcação e, em seguida, compare a quantidade de memória livre (física e de troca) no que se refere à política de overcommit (insira os números).
Em seu caso específico, observe que o Virtuozzo tem verificações adicionais na aplicação de overcommit . Além disso, não tenho certeza de quanto controle você realmente tem, de dentro do seu contêiner, sobre a configuração de troca e overcommit (para influenciar o resultado da aplicação).
Agora, para realmente seguir em frente, eu diria que você tem duas opções :
NOTE que o esforço de codificação pode ser em vão se descobrir que não é você, mas algum outro cara colocado em uma instância diferente no mesmo servidor que você está executando o amock.
Em termos de memória, já sabemos que
subprocess.Popen
usafork
/ porclone
baixo do capô , o que significa que toda vez que você o chama, você está solicitando mais uma vez a quantidade de memória que o Python já está consumindo , ou seja, centenas de MB adicionais, tudo para entãoexec
um pequeno executável de 10kB, comofree
oups
. No caso de uma política de supercomprometimento desfavorável, você verá em breveENOMEM
.Alternativas para
fork
que não tenham este problema de cópia de tabelas de páginas pai etc. sãovfork
eposix_spawn
. Mas se você não quiser reescrever pedaços desubprocess.Popen
em termos devfork
/posix_spawn
, considere usarsuprocess.Popen
apenas uma vez, no início de seu script (quando a área de cobertura de memória do Python é mínima), para gerar um script de shell que então executafree
/ps
/sleep
e tudo o mais em um loop paralelo ao seu script; sondar a saída do script ou lê-lo de forma síncrona, possivelmente a partir de um thread separado se você tiver outras coisas para cuidar de forma assíncrona - faça sua análise de dados em Python, mas deixe a bifurcação para o processo subordinado.NO ENTANTO , no seu caso particular, você pode pular a invocação
ps
efree
; essas informações estão prontamente disponíveis para você em Python diretamenteprocfs
, quer você escolha acessá-las sozinho ou por meio de bibliotecas e / ou pacotes existentes . Seps
efree
fossem os únicos utilitários que você estava executando, você pode acabar com elessubprocess.Popen
completamente .Finalmente, faça o que fizer no que diz
subprocess.Popen
respeito a você, se o seu script vazar memória, você ainda vai bater na parede eventualmente. Fique de olho nele e verifique se há vazamentos de memória .fonte
gc.collect()
logo antessubprocess.Popen
ajuda nos casos em que o coletor de lixo não funcionou por um tempo./proc/fd/maps
para determinar se a memória supercomprometida é de fato o problemaOlhando para o resultado de
free -m
, parece-me que você realmente não tem memória swap disponível. Não tenho certeza se no Linux o swap sempre estará disponível automaticamente sob demanda, mas estava tendo o mesmo problema e nenhuma das respostas aqui realmente me ajudou. Adicionar um pouco de memória de swap, no entanto, corrigiu o problema no meu caso, portanto, como isso pode ajudar outras pessoas que enfrentam o mesmo problema, postarei minha resposta sobre como adicionar um swap de 1 GB (no Ubuntu 12.04, mas deve funcionar da mesma forma para outras distribuições).Você pode primeiro verificar se há alguma memória swap habilitada.
se estiver vazio, significa que você não tem nenhuma troca habilitada. Para adicionar uma troca de 1 GB:
Adicione a seguinte linha ao
fstab
para tornar a troca permanente.Fonte e mais informações podem ser encontradas aqui .
fonte
a troca pode não ser a pista falsa sugerida anteriormente. Qual é o tamanho do processo python em questão antes de
ENOMEM
?No kernel 2.6,
/proc/sys/vm/swappiness
controla a agressividade com que o kernel se tornará a troca eovercommit*
arquiva a quantidade e a precisão com que o kernel pode distribuir memória com um piscar de olhos e um aceno. Como seu status de relacionamento no Facebook, é complicado .mas não de acordo com a saída de seu
free(1)
comando, que mostra nenhum espaço de troca reconhecido por sua instância de servidor. Agora, seu host da web pode certamente saber muito mais do que eu sobre este tópico, mas os sistemas RHEL / CentOS virtuais que usei relataram troca disponível para o sistema operacional convidado.Adaptando o Artigo 15252 do Red Hat KB :
Compare suas
/proc/sys/vm
configurações com uma instalação simples do CentOS 5.3. Adicione um arquivo de troca. Ratchet downswappiness
e veja se você vive mais.fonte
ps -o user,pid,vsz="Mem(Kb)" -o cmd $PYTHON_PID
, ou top (1), deve fazê-lo.Para uma solução fácil, você poderia
se você tiver certeza de que seu sistema tem memória suficiente. Veja Linux sobre a heurística de commit .
fonte
Continuo a suspeitar que seu cliente / usuário tem algum módulo de kernel ou driver carregado que está interferindo na
clone()
chamada do sistema (talvez algum aprimoramento de segurança obscuro, algo como LIDS, mas mais obscuro?) Ou está de alguma forma preenchendo algumas das estruturas de dados do kernel que são necessários parafork()
/clone()
para operar (tabela de processos, tabelas de páginas, tabelas de descritores de arquivo, etc.).Esta é a parte relevante da
fork(2)
página de manual:Eu sugiro que o usuário tente fazer isso depois de inicializar em um kernel genérico e com apenas um conjunto mínimo de módulos e drivers carregados (mínimo necessário para executar seu aplicativo / script). A partir daí, supondo que funcione naquela configuração, eles podem realizar uma pesquisa binária entre aquela e a configuração que exibe o problema. Esta é a solução de problemas 101 do administrador de sistemas padrão.
A linha relevante em seu
strace
é:... Eu sei que outras pessoas falaram sobre swap e disponibilidade de memória (e eu recomendo que você configure pelo menos uma pequena partição de swap, ironicamente mesmo se estiver em um disco RAM ... os caminhos de código através do kernel do Linux quando tiver até mesmo uma pequena quantidade de swap disponível foi exercida muito mais extensivamente do que aqueles (caminhos de tratamento de exceção) em que não há swap disponível.
No entanto, suspeito que isso ainda seja uma pista falsa.
O fato de
free
estar relatando memória 0 (ZERO) em uso pelo cache e buffers é muito perturbador. Suspeito que afree
saída ... e possivelmente o problema do seu aplicativo aqui, são causados por algum módulo proprietário do kernel que está interferindo na alocação de memória de alguma forma.De acordo com as páginas de manual de fork () / clone (), a chamada do sistema fork () deve retornar EAGAIN se sua chamada causar uma violação de limite de recursos (RLIMIT_NPROC) ... no entanto, não diz se EAGAIN deve ser retornado por outras violações RLIMIT *. Em qualquer caso, se o seu destino / host tiver algum tipo de Vormetric estranho ou outras configurações de segurança (ou mesmo se o seu processo estiver sendo executado sob alguma política SELinux estranha), isso pode estar causando essa falha -ENOMEM.
É muito improvável que seja um problema comum do Linux / UNIX. Você tem algo fora do padrão acontecendo aí.
fonte
Você já tentou usar:
Achei que isso tinha resolvido exatamente o mesmo problema para mim. Mas então meu processo acabou sendo morto em vez de falhar na geração, o que é ainda pior.
Depois de alguns testes, descobri que isso ocorria apenas em versões mais antigas do python: isso acontece com 2.6.5, mas não com 2.7.2
Minha pesquisa me trouxe aqui python-close_fds-issue , mas desconfigurar closed_fds não resolveu o problema. Ainda vale a pena ler.
Descobri que o python estava vazando descritores de arquivo apenas por ficar de olho nele:
Como você, quero capturar a saída do comando e evitar erros de OOM ... mas parece que a única maneira é as pessoas usarem uma versão menos bugada do Python. Não é ideal...
fonte
Eu vi um código desleixado parecido com este:
Você deve verificar se é isso que está acontecendo no código python. Errno só é válido se a chamada de sistema em andamento falhar.
Editado para adicionar:
Você não diz quanto tempo dura esse processo. Possíveis consumidores de memória
fonte
errno
seja redefinida várias vezes ao longo do caminho.Talvez você possa simplesmente
Funciona no meu caso.
Referência: https://github.com/openai/gym/issues/110#issuecomment-220672405
fonte