Comment un garbage collector évite-t-il une boucle infinie ici?

101

Considérez le programme C # suivant, je l'ai soumis sur codegolf comme réponse pour créer une boucle sans boucle:

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

Ce programme ressemble à une boucle infinie dans mon inspection, mais il semble fonctionner pendant plusieurs milliers d'itérations, puis le programme se termine avec succès sans erreur (aucune erreur n'est générée). Est-ce une violation de spécification pour laquelle le finaliseur Pn'est finalement pas appelé?

Il s'agit clairement d'un code stupide, qui ne devrait jamais apparaître, mais je suis curieux de savoir comment le programme pourrait se terminer.

Code de poste de golf d'origine :: /codegolf/33196/loop-without-looping/33218#33218

Michael B
la source
49
J'ai peur de lancer ça.
Eric Scherrer
6
Le fait qu'un finaliseur ne soit pas appelé relève certainement du domaine du comportement valide . Je ne sais pas pourquoi cela dérange d'exécuter plusieurs milliers d'itérations, je m'attendrais à zéro invocations.
27
Le CLR a une protection contre le thread du finaliseur ne pouvant jamais terminer son travail. Il le termine de force après 2 secondes.
Hans Passant
2
Donc, la vraie réponse à votre question dans le titre est qu'elle l'évite en laissant simplement la boucle infinie fonctionner pendant 40 secondes, puis elle est terminée.
Lasse V. Karlsen
4
Après l'avoir essayé, il semble que le programme tue tout au bout de 2 secondes, quoi qu'il arrive. En fait, si vous continuez à générer des threads, cela durera un peu plus longtemps :)
Michael B

Réponses:

110

Selon Richter dans la deuxième édition de CLR via C # (oui, je dois mettre à jour):

Épisode 478

Pour (Le CLR s'arrête), chaque méthode Finalize dispose d' environ deux secondes pour revenir. Si une méthode Finalize ne retourne pas dans les deux secondes, le CLR arrête simplement le processus - aucune méthode Finalize n'est appelée. De plus, s'il faut plus de 40 secondes pour appeler les méthodes Finalize de tous les objets , encore une fois, le CLR arrête simplement le processus.

De plus, comme le mentionne Servy, il a son propre fil.

Eric Scherrer
la source
5
Chaque méthode de finalisation de ce code prend considérablement moins de 40 secondes par objet. Le fait qu'un nouvel objet soit créé puis éligible pour la finalisation n'est pas pertinent pour le finaliseur actuel.
Jacob Krall
2
Ce n'est pas vraiment ce qui fait le travail. Il existe également un délai d'expiration de la file d'attente pouvant être vidée à l'arrêt. C'est ce sur quoi ce code échoue, il continue d'ajouter de nouveaux objets à cette file d'attente.
Hans Passant
Le simple fait de penser à cela n'est pas de vider la file d'attente accessible de la même manière que "De plus, s'il faut plus de 40 secondes pour appeler les méthodes Finalize de tous les objets, encore une fois, le CLR tue simplement le processus."
Eric Scherrer
23

Le finaliseur ne s'exécute pas dans le thread principal. Le finaliseur a son propre thread qui exécute le code, et ce n'est pas un thread de premier plan qui permettrait à l'application de continuer à fonctionner. Le thread principal se termine efficacement immédiatement, à quel point le thread du finaliseur s'exécute simplement autant de fois qu'il le peut avant que le processus ne soit interrompu. Rien ne maintient le programme en vie.

Servy
la source
Si le finaliseur n'a pas terminé 40 secondes après la fin du programme car aucun thread principal n'est actif, il sera interrompu et le processus se terminera. Ce sont des valeurs anciennes, donc Microsoft a peut-être modifié les chiffres réels ou même tout l'algorithme maintenant. Se blog.stephencleary.com/2009/08/finalizers-at-process-exit.html
Lasse V. Karlsen
@ LasseV.Karlsen Est-ce le comportement documenté du langage, ou simplement comment MS a choisi d'implémenter ses finaliseurs comme détail d'implémentation? Je m'attendrais à ce dernier.
Servy le
J'attends aussi ce dernier. La référence la plus officielle à ce comportement que j'ai vue est ce qu'Eric ci-dessus a posté dans sa réponse, du livre CLR via C # de Jeffrey Richter.
Lasse V.Karlsen
8

Un garbage collector n'est pas un système actif. Il s'exécute «parfois» et principalement à la demande (par exemple lorsque toutes les pages proposées par l'OS sont pleines).

La plupart des garbage collector s'exécutent à la manière de la première génération dans un sous-thread. Dans la plupart des cas, le recyclage de l'objet peut prendre des heures.

Le seul problème se produit lorsque vous souhaitez mettre fin au programme. Cependant, ce n'est pas vraiment un problème. Lorsque vous utilisez killun système d'exploitation, vous demanderez poliment de mettre fin aux processus. Lorsque le processus reste cependant actif, on peut utiliser kill -9là où le système d'exploitation supprime tout contrôle.

Lorsque j'ai exécuté votre code dans l' csharpenvironnement interactif , j'ai:

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

Ainsi , votre programme se bloque à cause stdoutest bloqué par le termintation de l'environnement.

Lors de la suppression Console.WriteLineet de la suppression du programme. Au bout de cinq secondes, le programme se termine (en d'autres termes, le ramasse-miettes abandonne et libère simplement toute la mémoire sans prendre en compte les finaliseurs).

Willem Van Onsem
la source
C'est fascinant que le csharp interactif explose pour des raisons entièrement différentes. L'extrait de programme d'origine n'avait pas l'écriture de la console, je suis curieux de savoir s'il se terminerait également.
Michael B
@MichaelB: J'ai également testé cela (voir commentaire ci-dessous). Il attend cinq secondes puis se termine. Je suppose que le finaliseur de la première Pinstance a simplement expiré.
Willem Van Onsem