Partage
  • Partager sur Facebook
  • Partager sur Twitter

[C#] Crash du serveur

    25 septembre 2011 à 16:59:10

    Bonjours a tous

    Depuis quelques temps j’héberge et administre un serveur minecraft classic.

    J'ai réussi a trouver les sources de ce serveur (mclawl) et je les ai modifier
    Le serveur est en C# et compiler avec Microsoft Visual C# 2010 Express

    Sauf que depuis quelques temps le serveur crash de façon aléatoire et pour une raison que je ne réussi pas a trouver

    L'erreur laisse une trace de ce type :
    -------------------------
    ----24/09/2011 19:57:32 ----
    Type: InvalidOperationException
    Source: mscorlib
    Message: La collection a été modifiée ; l'opération d'énumération peut ne pas s'exécuter.
    Target: ThrowInvalidOperationException
    Trace:    à System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
       à System.Collections.Generic.List`1.Enumerator.MoveNextRare()
       à System.Collections.Generic.List`1.Enumerator.MoveNext()
       à MCLawl.Server.<Start>b__8()
       à System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       à System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       à System.Threading.ThreadHelper.ThreadStart()


    Auriez vous une idée de ce qui déclenche l’erreur et comment la surmonter ?

    Merci de vous être pencher sur mon problème

    nico69

    PS : J'ai de faibles connaissances en C# donc il est possible que la solution est tout bette :-°
    • Partager sur Facebook
    • Partager sur Twitter
    "Tout devrait être rendu aussi simple que possible, mais pas plus." A.Einstein
      25 septembre 2011 à 19:21:01

      Exécute l'application en mode Debug, directement depuis Visual Studio, en appuyant sur la touche F5.

      Demande à plusieurs personnes de se connecter au serveur de test et cherche à localiser ce qui génère cette erreur pour pouvoir la reproduire.

      Une fois que c'est fait, recherche le code indiqué par la ligne "MCLawl.Server.<Start>b__8()".

      L'erreur indiquée dit que à cet endroit, tu tente d'effectuer une énumération et que, pendant cette énumération, tu cherches à modifier la liste énumérée. Un exemple simple qui peut générer ce bug serait :
      List<String> maListe = new List<String>();
      maListe.Add("KiKoo");
      maListe.Add("LoL");
      foreach(String str in maListe)
      {
          maListe.Add(str + "B");//Cette ligne va lever l'exception que tu rencontres.
      }
      
      foreach(String str in maListe)
      {
          maListe.Remove(str);//Cette ligne va lever l'exception que tu rencontres.
      }
      


      Malheureusement, si tu ne connais pas bien le C# et/ou le .NET, je vois pas comment tu vas pouvoir corriger ce bug; les solutions étant multiples.

      Un exemple de solution, si tu te trouves dans le deuxième cas (mais uniquement si tu as un code similaire dont le but est supprimer tout ce que contient la liste):
      //Au lieu de
      foreach(String str in maListe)
      {
          maListe.Remove(str);//Cette ligne va lever l'exception que tu rencontres.
      }
      
      //Tu pourrais mettre
      while(maListe.Count != 0)//Tant que ta liste n'est pas vide
      {
          maListe.RemoveAt(0);//Supprime le premier élément de cette liste
      }
      

      De manière générale, les solutions de contournement consistent à changer de méthode d'analyse de la liste pour passer d'une méthode énumérative (premier bloc de l'exemple ci-dessus) à une méthode non-énumérative (deuxième bloc de l'exemple ci-dessus).
      • Partager sur Facebook
      • Partager sur Twitter
        26 septembre 2011 à 0:54:40

        En fait le programme essais d’accéder a un élément qui a déjà été supprimer ?

        Le problème c'est que l’erreur se déclenche de façon complétement aléatoire (elle peut bien arriver plusieurs fois en 1h ou ne même pas arriver en 2 jours)

        D’après les logs l’erreur arrive systématiquement au déchargement d'une map (la provoquer prendra du temps surtout a 4 ou 5)

        merci pour les infos

        nico69



        • Partager sur Facebook
        • Partager sur Twitter
        "Tout devrait être rendu aussi simple que possible, mais pas plus." A.Einstein
          26 septembre 2011 à 9:58:35

          Citation : n!co69

          En fait le programme essais d’accéder a un élément qui a déjà été supprimer ?


          Non. Le problème ne se situe pas là.

          Le problème est lié à l'opération d'énumération (avec un foreach par exemple, qui transforme automatiquement les listes ou les tableaux statiques en énumération avant de boucler sur cette énumération).

          Lors de cette opération d'énumération, tu n'as plus le droit de modifier la liste d'élément qui est en est à l'origine. Si tu le fait => Exception.

          Ce qui est possible, c'est que le serveur utilise des thread (soit directement, soit dans des appels asynchrones). Du coup, il y a des fois où le bug se produit, parce que le thread A, chargé de supprimer les éléments de la carte à été plus rapide que Thread B qui énumère cette même liste d'élément...Et des fois où il ne se produit pas.

          Dans ce dernier de cas, corriger le bug ne sera peut-être pas de changer la méthode d'analyser mais de revoir les verrous utilisés lors de ces opérations.

          A mon avis, si tu ne connais pas bien le C#/.NET, tu devrais simplement contacter le développeur à l'origine de ce serveur pour lui signaler le bug. En plus de résoudre tes problèmes, ça ferait progresser son projet et la communauté qui s'est peut-être formée autour.
          • Partager sur Facebook
          • Partager sur Twitter
            27 septembre 2011 à 22:16:26

            Je pence comprendre le problème

            Oui le serveur utilise les threads

            Lorsque une map est déchargée, la fonction Level.unload(); est appelée et dedans un Server.levels.Remove(this); est appelé
            ( Server.levels est une liste de Level )

            Je pence que mon niveau est pas si mauvais que ca car j'ai compris tout ce que tu m'a dit
            (j'ai déjà manipuler les threads (juste écrire le thread, mettre des délais et le démarrer), les listes et le foreach)

            Le problème c'est que mclawl s'est arrêté de développer le serveur (leur site a disparu du web) -> http://www.mclawl.tk/

            ps : Si tu (ou d'autres personnes) veut te plonger dans les codes, je peut te les passer

            • Partager sur Facebook
            • Partager sur Twitter
            "Tout devrait être rendu aussi simple que possible, mais pas plus." A.Einstein
              7 octobre 2011 à 17:43:40

              Depuis 1 semaine, j'ai un peut rechercher si il y avais d'autres choses de ce type mais rien de plus.

              Il me fraudais un moyen de bloquer le Level.unload(); pendant que les foreatch gérant les Level mais je ne vois pas du tout comment faire (sachant que le foreatch et le Level.unload(); sont situer dans 2 fonctions différentes mais qui s’exécutent en même temps grâce a un thread).

              Vous n'auriez pas une idée ?
              • Partager sur Facebook
              • Partager sur Twitter
              "Tout devrait être rendu aussi simple que possible, mais pas plus." A.Einstein
                7 octobre 2011 à 21:55:11

                Faire de la synchronisation....

                Par exemple :
                public class ThreadSafeTest
                {
                    private int _value;
                    public int Increment()
                    {
                        lock(this)
                        {
                            _value++;
                        }
                    }
                }
                

                va effectuer une addition protégée contre les accès concurrentiels en utilisant la référence en cours comme objet de synchronisation.
                Concrètement, voici ce que ça donne :
                -Thread A appelle Increment(). Thread A obtient donc un verrou d'exclusivité sur l'objet this (une instance de ThreadSafeTest donc). On pourrait dire que "l'instance de ThreadSafeTest est liée à Thread A".
                -Thread B appelle Increment(). A ce moment là, il y a deux cas de figure :
                -Si Thread A a terminé son travail, alors Thread B obtient un verrou d'exclusivité sur l'objet this.
                -Si Thread A n'a pas terminé son travail lorsque Thread B tente de verrouiller l'objet this, Thread B sera mis en attente jusqu'à que ce Thread A libère le verrou placé sur this an arrivant à la fin du bloc définit par le mot clé "lock". Une fois que Thread A aura terminé, Thread B se réveillera, obtiendra un verrou d'exclusivité et pourra continuer à s'exécuter.
                -Si Thread A tente d'accéder à cette zone alors que Thread B est en cours d'exécution, alors Thread A sera mis en attente de la même façon que Thread B l'a été juste auparavant.

                Cette histoire de verrou est "puissante" mais peut poser de sérieux problèmes :
                -Dans un environnement multi-thread, bloquer les thread de cette façon fait perdre en performance (genre faire 2 thread pour vider une liste de 2 000 000 d'éléments, en synchronisant comme ça, c'est débile puisque les deux threads ne peuvent pas s'exécuter en même temps...). Le multi-thread devient alors une complexité supplémentaire, totalement inutile.
                -Si les verrous sont mal pensés, tu peux arriver dans un cas de verrous mort (deadlock) dans lequel deux thread attendent chacun que l'autre ai terminé sont travail pour continuer => La situation est bloquée.
                • Partager sur Facebook
                • Partager sur Twitter
                  7 octobre 2011 à 22:45:18

                  Citation : Nisnor

                  Faire de la synchronisation....



                  En effet c'est la solution.
                  n!co69, je te conseil de lire la MSDN sur le sujet lock
                  Attention cependant au accès publique d'instance, comme préciser dans la doc, mais aussi aux problèmes énoncés par Nisnor qui peuvent vraiment te pourrir la vie, surtout les deadlocks.
                  • Partager sur Facebook
                  • Partager sur Twitter
                    7 octobre 2011 à 23:35:32

                    Ça augmente d'un level la :lol:

                    Cet élément lock permet de 'bloquer' l’exécution d'une fonction pour éviter qu'elle s’exécute en double (par deux fonctions différentes), dit le moi si je me trompe.

                    Si j'ai bien compris ce que disait Nisnor dans les explications précédentes, il est 'interdit' de changer les éléments d'une liste si on la parcourt avec un foreach

                    moi dans mes codes j'ai un truc de ce style

                    dans une fonction permettant d'avoir la liste des maps
                    Server.levels.ForEach(delegate(Level level)
                    {
                    
                        if (level.pervisit || !level.pervisit && (p.name.ToLower() == p.level.world.ToLower() || p.group.Permission > LevelPermission.Admin))
                        { maps +=", &2" + level.name + " (" + level.world + ",&b[" + level.physics + "]&2)"; }
                        else
                        { maps +=", &c" + level.name + " (" + level.world + ",&b[" + level.physics + "]&c)"; }
                    
                    });
                    

                    ou encore
                    foreach (Level l in Server.levels)
                    {
                         if (file.Name.Replace(".lvl", "").ToLower() == l.name.ToLower() && message.ToLower() == l.world.ToLower() && !load)
                         { 
                              maps += "&2, " + l.name + "&b[" + l.physics + "]";
                              load = true;
                         }
                    }
                    

                    (d’ailleurs je ne sais pas ce qui vaux mieux entre les 2)

                    et en même temps il se peut qu'une map soit déchargée et ceci est appelé

                    Server.levels.Remove(this);
                    


                    Donc ça vas lever l’exception

                    Dans mon problème c'est pas une fonction que l'on doit lock par l’appelle a deux endroit du code mais plutot un élément a bloquer pendant exécution des foreach (quitte a modifier tout les codes pour ça)

                    J’espère avoir été assez clair
                    • Partager sur Facebook
                    • Partager sur Twitter
                    "Tout devrait être rendu aussi simple que possible, mais pas plus." A.Einstein
                      8 octobre 2011 à 2:54:40

                      Si ton foreach est utilisé par un thread et ton remove par un autre tu auras pas trop le choix.
                      Utiliser le lock c'est dire au premier thread qui prend le lock de bloquer les threads qui pourrait déclencher une opération non désirable comme c'est ton cas.
                      Tant que le premier n'a pas libéré le lock, les autres attendent la fin d’exécution du bloc contenu dans le lock.
                      Si tu lock ton foreach, aucun autre thread ne pourra y accéder. Et inversement pour ton remove. Je penses que ça résoudra ton problème.
                      • Partager sur Facebook
                      • Partager sur Twitter
                        10 octobre 2011 à 22:09:46

                        Je n'arrive pas a utiliser les locks dans mon problème.

                        D’après ce que j'ai compris les locks bloquent la 2e demande d'utilisation de la fonction (ou des fonctions) lorsque la précédente est encore active (ce qui permet de ne pas exécuter plusieurs foit la même fonction en même temps).

                        Dans mon cas j'ai deux fonctions (le foreatch et le Server.levels.Remove(this);) dont le fonctionnement ne doit pas être superposé.

                        Il fraudais pouvoir lock l'une quand l'autre est utilisée.

                        Désolé si je ne comprend pas bien mais coté threads je ne suis pas aller bien loin

                        J'ai trouver un cours sur les threads en C# .NET, j'en apprend un peut plus dessus puis je r'attaque mon problème
                        http://www.siteduzero.com/tutoriel-3-2 [...] s-en-net.html
                        • Partager sur Facebook
                        • Partager sur Twitter
                        "Tout devrait être rendu aussi simple que possible, mais pas plus." A.Einstein
                          11 octobre 2011 à 11:59:52

                          Citation : n!co69

                          D’après ce que j'ai compris les locks bloquent la 2e demande d'utilisation de la fonction (ou des fonctions) lorsque la précédente est encore active (ce qui permet de ne pas exécuter plusieurs foit la même fonction en même temps).


                          Correction

                          Citation : Nisnor

                          D’après ce que j'ai compris les locks bloquent la 2e demande d'utilisation de la fonction (ou des fonctions), en partant d'un thread différent, lorsque la précédente dans le premier thread est encore active (ce qui permet de ne pas exécuter plusieurs foit la même fonction en même temps).



                          Si le Server.levels.Remove(this) se trouve à l'intérieur du foreach, mettre des locks partout ne changera rien puisque c'est le même thread qui effectue la tâche.

                          Citation : n!co69

                          Il fraudais pouvoir lock l'une quand l'autre est utilisée.


                          Il faut voir l'architecture du logiciel...

                          Si tu rencontres ce genre de problème, c'est parce qu'il y a surement plusieurs modules, dans le logiciel, qui cherchent à accéder à la même liste de données pour effectuer leur travail. Dès lors, la logique voudrait que ces modules soient managés par un gestionnaire particulier et que les données communes à tous les modules soient dans ce gestionnaire. Le lock vient alors se placer dans le gestionnaire de modules.

                          En ce qui me concerne, j'aurais fait des méthodes thread-safe (avec les lock dedans) dans ledit gestionnaire pour permettre aux modules de modifier les données (par exemple, une méthode "ClearLevels" pour effacer tous les niveau ou encore "ExecuteAction(Action<Level> action)" pour effectuer une action spécifique sur tous les niveaux)...Peut-être est-il possible de faire une propriété simple avec le lock dedans mais je n'ai jamais testé ça et je ne sais pas si ça marche aussi bien que de faire des locks dans des méthodes...Dans tous les cas, il sera possible de locker le gestionnaire (s'il ne contient qu'un seul champ de données) ou de locker la variable dans le gestionnaire (s'il en contient plusieurs).
                          • Partager sur Facebook
                          • Partager sur Twitter
                            12 octobre 2011 à 21:26:00

                            Le serveur est assez (voir très) complexe (mais en même temps assez simple pour faire quelques modifications mineurs) donc il me serais très difficile de rassembler tous les éléments au même endroit pour en faire une gestion poussée.

                            Il vaudrais presque mieux recommencer un autre serveur a partir de zéro.

                            Je pence que je ne vais pas pouvoir régler ce problème avec mon faible niveau (même avec votre aide)

                            Merci de vous être penché sur mon problème même si il n'y a pas eu de résultats.

                            nico69

                            PS : Je ne veut pas supprimer tous les levels d'un coup mais juste un seul (celais qui est désigné par le pointeur this) mais ça me fait crash le serveur si cette fonction (ou méthode) s’exécute au moment ou un foreatch est en train de parcourir la liste des levels chargés

                            • Partager sur Facebook
                            • Partager sur Twitter
                            "Tout devrait être rendu aussi simple que possible, mais pas plus." A.Einstein

                            [C#] Crash du serveur

                            × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                            × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                            • Editeur
                            • Markdown