Como um coletor de lixo evita um loop infinito aqui?

101

Considere o seguinte programa C #, eu o enviei no codegolf como uma resposta para criar um loop sem loop:

class P{
    static int x=0;
    ~P(){
        System.Console.WriteLine(++x);
        new P();
    }
    static void Main(){
        new P();
    }
}

Este programa parece um loop infinito em minha inspeção, mas parece ser executado por vários milhares de iterações e, em seguida, o programa é encerrado com êxito sem erros (nenhum erro é gerado). É uma violação de especificação para a qual o finalizador Peventualmente não é chamado?

Claramente, este é um código estúpido, que nunca deveria aparecer, mas estou curioso para saber como o programa poderia ser concluído.

Código original da postagem de golfe :: /codegolf/33196/loop-without-looping/33218#33218

Michael B
fonte
49
Estou com medo de executar isso.
Eric Scherrer
6
O fato de um finalizador não ser chamado certamente está dentro do reino do comportamento válido . Eu não sei por que ele se preocupa em executar vários milhares de iterações, porém, esperaria zero invocações.
27
O CLR tem proteção contra o thread finalizador nunca conseguir terminar seu trabalho. Ele o encerra à força após 2 segundos.
Hans Passant
2
Portanto, a verdadeira resposta à sua pergunta no título é que ele a evita apenas deixando o loop infinito funcionar por 40 segundos e depois é encerrado.
Lasse V. Karlsen
4
Ao tentar, parece que o programa simplesmente mata tudo após 2 segundos, aconteça o que acontecer. Na verdade, se você continuar gerando tópicos, isso vai durar um pouco mais :)
Michael B

Respostas:

110

De acordo com Richter na segunda edição do CLR via C # (sim, preciso atualizar):

Página 478

Para (O CLR está sendo encerrado), cada método Finalize recebe aproximadamente dois segundos para retornar. Se um método Finalize não retornar em dois segundos, o CLR apenas interromperá o processo - não mais método Finalize será chamado. Além disso, se demorar mais de 40 segundos para chamar os métodos Finalize de todos os objetos , novamente, o CLR simplesmente elimina o processo.

Além disso, como Servy menciona, ele tem seu próprio segmento.

Eric Scherrer
fonte
5
Cada método finalize neste código leva menos de 40 segundos por objeto. O fato de um novo objeto ser criado e elegível para finalização não é relevante para o finalizador atual.
Jacob Krall
2
Não é isso que realmente faz o trabalho. Também há um tempo limite na fila de freachable sendo esvaziada no desligamento. Que é o que este código falha, ele continua adicionando novos objetos a essa fila.
Hans Passant
Só de pensar nisso, não é esvaziar a fila de freachable da mesma forma que "Além disso, se demorar mais de 40 segundos para chamar os métodos Finalize de todos os objetos, novamente, o CLR simplesmente mata o processo."
Eric Scherrer
23

O finalizador não é executado no thread principal. O finalizador tem seu próprio thread que executa o código, e não é um thread de primeiro plano que manteria o aplicativo em execução. O encadeamento principal é concluído com eficácia imediatamente, momento em que o encadeamento finalizador simplesmente é executado quantas vezes puder antes que o processo seja interrompido. Nada está mantendo o programa vivo.

Servy
fonte
Se o finalizador não tiver completado 40 segundos depois que o programa deveria ter saído devido a nenhum thread principal estar ativo, ele será encerrado e o processo será encerrado. Esses são valores antigos, portanto, a Microsoft pode ter ajustado os números reais ou até mesmo todo o algoritmo até agora. Se blog.stephencleary.com/2009/08/finalizers-at-process-exit.html
Lasse V. Karlsen
@ LasseV.Karlsen Esse é o comportamento documentado da linguagem ou simplesmente como a MS escolheu implementar seus finalizadores como um detalhe de implementação? Eu esperaria o último.
Servy
Eu espero o último também. A referência mais oficial que vi a esse comportamento é o que Eric postou em sua resposta, do livro CLR via C # de Jeffrey Richter.
Lasse V. Karlsen
8

Um coletor de lixo não é um sistema ativo. Ele funciona "às vezes" e principalmente sob demanda (por exemplo, quando todas as páginas oferecidas pelo sistema operacional estão cheias).

A maioria dos coletores de lixo é executada de maneira semelhante à da primeira geração em um subtread. Na maioria dos casos, pode levar horas até que o objeto seja reciclado.

O único problema ocorre quando você deseja encerrar o programa. No entanto, isso não é realmente um problema. Quando você usa killum sistema operacional irá pedir educadamente para encerrar processos. Quando o processo, no entanto, permanece ativo, pode-se usarkill -9 onde o sistema operacional remove todo o controle.

Quando executei seu código no csharpambiente interativo , obtive:

csharp>  

1
2

Unhandled Exception:
System.NotSupportedException: Stream does not support writing
  at System.IO.FileStream.Write (System.Byte[] array, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.FlushBytes () [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.FlushCore () [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.Write (System.Char[] buffer, Int32 index, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.Char[] buffer, Int32 index, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.Char[] val) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.String val) [0x00000] in <filename unknown>:0 
  at System.IO.TextWriter.Write (Int32 value) [0x00000] in <filename unknown>:0 
  at System.IO.TextWriter.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at System.IO.SynchronizedWriter.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at System.Console.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at P.Finalize () [0x00000] in <filename unknown>:0

Portanto, seu programa trava porque stdoutestá bloqueado pela terminação do ambiente.

Ao remover Console.WriteLinee matar o programa. Depois de cinco segundos, o programa termina (em outras palavras, o coletor de lixo desiste e simplesmente vai liberar toda a memória sem levar os finalizadores em consideração).

Willem Van Onsem
fonte
É fascinante que o csharp interativo exploda por motivos totalmente diferentes. O trecho do programa original não tinha a linha de escrita do console, estou curioso para saber se ele também seria encerrado.
Michael B
@MichaelB: Eu testei isso também (veja o comentário abaixo). Ele espera cinco segundos e então termina. Acho que o finalizador da primeira Pinstância simplesmente expirou.
Willem Van Onsem