¿Cómo evita un recolector de basura un bucle infinito aquí?

101

Considere el siguiente programa C #, lo envié en codegolf como respuesta para crear un bucle sin bucle:

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

Este programa parece un bucle infinito en mi inspección, pero parece ejecutarse durante varios miles de iteraciones, y luego el programa termina con éxito sin errores (no se lanzan errores). ¿Es una infracción de especificación para la que Pfinalmente no se llama al finalizador ?

Claramente, este es un código estúpido, que nunca debería aparecer, pero tengo curiosidad por saber cómo podría completarse el programa.

Código original de la publicación de golf: /codegolf/33196/loop-without-looping/33218#33218

Michael B
fuente
49
Tengo miedo de ejecutar esto.
Eric Scherrer
6
Que no se llame a un finalizador está ciertamente dentro del ámbito del comportamiento válido . Sin embargo, no sé por qué se molesta en ejecutar varios miles de iteraciones, esperaría cero invocaciones.
27
El CLR tiene protección contra el hilo del finalizador que nunca puede terminar su trabajo. Lo termina con fuerza después de 2 segundos.
Hans Passant
2
Entonces, la respuesta real a su pregunta en el título es que la evita simplemente dejando que el ciclo infinito se ejecute durante 40 segundos y luego se termina.
Lasse V. Karlsen
4
Al intentarlo, parece que el programa acaba con todo después de 2 segundos, pase lo que pase. En realidad, si sigues generando hilos, durará un poco más :)
Michael B

Respuestas:

110

Según Richter en la segunda edición de CLR a través de C # (sí, necesito actualizar):

Página 478

Para (El CLR se está apagando), cada método Finalize tiene aproximadamente dos segundos para regresar. Si un método Finalize no regresa en dos segundos, CLR simplemente mata el proceso; no se llaman más métodos Finalize . Además, si se necesitan más de 40 segundos para llamar a los métodos Finalize de todos los objetos , nuevamente, CLR simplemente mata el proceso.

Además, como menciona Servy, tiene su propio hilo.

Eric Scherrer
fuente
5
Cada método de finalización en este código toma dramáticamente menos de 40 segundos por objeto. El hecho de que se cree un nuevo objeto y luego sea elegible para finalización no es relevante para el finalizador actual.
Jacob Krall
2
En realidad, esto no es lo que hace el trabajo. También hay un tiempo de espera en la cola de espera que se vacía al apagar. Que es en lo que falla este código, sigue agregando nuevos objetos a esa cola.
Hans Passant
Con solo pensar en esto, vaciar la cola de espera no es lo mismo que "Además, si se necesitan más de 40 segundos para llamar a los métodos Finalize de todos los objetos, nuevamente, el CLR simplemente mata el proceso".
Eric Scherrer
23

El finalizador no se ejecuta en el hilo principal. El finalizador tiene su propio subproceso que ejecuta código, y no es un subproceso en primer plano que mantendría la aplicación en ejecución. El hilo principal se completa de forma efectiva de inmediato, momento en el que el hilo del finalizador simplemente se ejecuta tantas veces como tenga la oportunidad antes de que el proceso se detenga. Nada mantiene vivo el programa.

Servy
fuente
Si el finalizador no se ha completado 40 segundos después de que el programa debería haber salido debido a que no hay ningún hilo principal activo, se terminará y el proceso terminará. Sin embargo, estos son valores antiguos, por lo que Microsoft podría haber modificado los números reales o incluso todo el algoritmo a estas alturas. Se blog.stephencleary.com/2009/08/finalizers-at-process-exit.html
Lasse V. Karlsen
@ LasseV.Karlsen ¿Es ese comportamiento documentado del lenguaje, o simplemente cómo MS eligió implementar sus finalizadores como un detalle de implementación? Esperaría lo último.
Servicio
Espero lo último también. La referencia más oficial a este comportamiento que he visto es lo que Eric publicó anteriormente en su respuesta, del libro CLR a través de C # de Jeffrey Richter.
Lasse V. Karlsen
8

Un recolector de basura no es un sistema activo. Se ejecuta "a veces" y principalmente a pedido (por ejemplo, cuando todas las páginas que ofrece el sistema operativo están llenas).

La mayoría de los recolectores de basura se ejecutan de manera similar a la primera generación en un subproceso. En la mayoría de los casos, pueden pasar horas antes de que el objeto se recicle.

El único problema ocurre cuando desea terminar el programa. Sin embargo, eso no es realmente un problema. Cuando utilice killun sistema operativo le pedirá cortésmente que finalice los procesos. Sin embargo, cuando el proceso permanece activo, se puede usar kill -9donde el sistema operativo elimina todo el control.

Cuando ejecuté su código en el csharpentorno interactivo , obtuve:

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

Por lo tanto, su programa se bloquea porque stdoutestá bloqueado por la terminación del entorno.

Al eliminar Console.WriteLiney matar el programa. Después de cinco segundos, el programa termina (en otras palabras, el recolector de basura se rinde y simplemente liberará toda la memoria sin tener en cuenta a los finalizadores).

Willem Van Onsem
fuente
Es fascinante que el csharp interactivo explote por razones completamente diferentes. El fragmento de programa original no tenía la línea de escritura de la consola, tengo curiosidad por saber si terminaría también.
Michael B
@MichaelB: También he probado esto (ver el comentario a continuación). Espera cinco segundos y luego termina. Supongo que el finalizador de la primera Pinstancia simplemente agotó el tiempo de espera.
Willem Van Onsem