IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage Delphi Discussion :

Tracer la pile du thread principal lorsqu'il se gèle


Sujet :

Langage Delphi

  1. #1
    Membre averti Avatar de franckcl
    Homme Profil pro
    Developpeur Delphi
    Inscrit en
    Septembre 2004
    Messages
    516
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Developpeur Delphi
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Septembre 2004
    Messages : 516
    Points : 443
    Points
    443
    Par défaut Tracer la pile du thread principal lorsqu'il se gèle
    Bonjour à tous,

    Mon application se gèle de temps en temps et pour une durée variable de quelques secondes à quelques minutes sans savoir comment localiser le problème.
    L'application fonctionne H24 et l'évènement ne se produit que rarement (une fois par semaine environ, voire même une fois par mois et est très variable) mais très gênant et peut être critique pour l'utilisateur.
    Il faut donc absolument que j'en trouve la cause.

    J'utilise un thread pour gérer des taches annexes sur les ports séries RS232 qui lui ne se gèle jamais.
    Dans ce thread j'arrive à détecter le moment ou le thread principal se bloque et se débloque car, dans ce thread principal, j'incrémente un compteur dans Application.onIdle
    Du coup si ce compteur n'évolue pas pendant 3 secondes, je log l'information dans un fichier texte et je log lorsque le compteur repars.

    Lorsque je détecte un gèle du thread applicatif, j'aimerai savoir où se trouve le blocage.
    Pour cela, je pense compiler mon application en debug et utiliser peut-être une fonction permettant de tracer le contenu de la pile du thread principal.
    Mais est-ce possible ? Existe-t-il d'autres solutions ??
    Vos avis et votre expérience seront les bienvenues.
    merci
    Franck

  2. #2
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 459
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 459
    Points : 24 873
    Points
    24 873
    Par défaut
    On parlait récemment de JCLDebug pour récupérer la pile lors d'une exception, je te laisse chercher.

    Perso, je ne sais récupérer le StackTrace que lors d'une exception, je serais aussi intéresser de pouvoir la récupérer indépendamment voire même d'un thread à l'autre ... comme le fait Delphi (voir le DebugHook, si cela se trouve, il faut faire une application hôte qui gère les informations de Debug)
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  3. #3
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 694
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 694
    Points : 13 130
    Points
    13 130
    Par défaut
    Oui c'est possible avec jclDebug.

    Voilà une petite démo.
    TIsFreezeThread intègre son propre timer exécuté dans le thread appelant sur la moitié de l'intervalle de contrôle et se charge de l'incrémentation du WatchDog.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    unit Unit1;
     
    interface
     
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.StdCtrls;
     
    type
      TIsFreezeThread = class(TThread)
      private
        Wnd         :hWnd;
        Interval    :integer;
     
        WatchThread :THandle;
        WatchDog    :integer;
     
        class procedure TimerProc(aWnd: hWnd; aMsg: UINT; idEvent: NativeUInt; dwTime: DWORD); stdcall; static;
      protected
        procedure   Execute; override;
      public
        constructor Create(aInterval :integer);
        destructor  Destroy; override;
      end;
     
      TForm1 = class(TForm)
        Button1: TButton;
        procedure Button1Click(Sender: TObject);
        procedure FormCreate(Sender: TObject);
        procedure FormDestroy(Sender: TObject);
      private
        FreezeThread :TIsFreezeThread;
      public
      end;
     
    var
      Form1: TForm1;
     
    implementation
     
    uses jclDebug;
     
    //=====================================================================================================================================================================================================
     
    {$R *.dfm}
     
    procedure TForm1.Button1Click(Sender: TObject);
    begin
      // Simule un traitement long
      Sleep(5000);
    end;
     
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      // Contrôle toutes les 3s
      FreezeThread := TIsFreezeThread.Create(3000);
    end;
     
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
      FreezeThread.Free;
    end;
     
    //=====================================================================================================================================================================================================
     
    { TIsFreezeThread }
     
    constructor TIsFreezeThread.Create(aInterval :integer);
    begin
      inherited Create;
     
      Interval := aInterval;
     
      Wnd := AllocateHWnd(nil);
      SetTimer(Wnd, NativeUInt(Self), Interval div 2, @TimerProc);
     
      DuplicateHandle(GetCurrentProcess, GetCurrentThread, GetCurrentProcess, @WatchThread, 0, FALSE, DUPLICATE_SAME_ACCESS);
    end;
     
    destructor TIsFreezeThread.Destroy;
    begin
      KillTimer(Wnd, NativeUInt(Self));
      DeallocateHWnd(Wnd);
     
      inherited;
    end;
     
    procedure TIsFreezeThread.Execute;
    begin
      var PrevDog := -1;
     
      while not Terminated do
      begin
        if InterlockedExchange(PrevDog, WatchDog) = PrevDog then
        begin
          var Stack := TStringList.Create;
     
          try
            with JclCreateThreadStackTrace(FALSE, WatchThread) do
            try
              AddToStrings(Stack, TRUE, FALSE, FALSE, FALSE);
              Stack.SaveToFile('d:\Stack.txt');
            finally
              Free;
            end;
     
          finally
            Stack.Free;
          end;
        end;
     
        Sleep(Interval);
      end;
    end;
     
    class procedure TIsFreezeThread.TimerProc(aWnd: hWnd; aMsg: UINT; idEvent: NativeUInt; dwTime: DWORD);
    begin
      InterlockedIncrement(TIsFreezeThread(idEvent).WatchDog);
    end;
     
    end.
    Il faudrait encore retravailler un peu. AllocateHWnd n'est pas threadsafe et devrait être remplacé pour surveiller un thread de travail. Sleep aussi devrait être remplacé pour de longs intervalles.

    En RELEASE, il faudra activer les informations de débogages détaillées et joindre un fichier map (ou jdbg).

  4. #4
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 459
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 459
    Points : 24 873
    Points
    24 873
    Par défaut
    Tu peux remplacer le Timer et Sleep par un TEvent, en wrTimeOut pour le cycle, en wrSignaled au moment du SetEvent dans le Destroy mais j'ai un peu l'impression que ça utilise le WM_TIMER pour vérifier que le MainThread est bloqué, c'est spécifique à ce cas.

    Sinon ça ressemble à ce que je faisais, si un compteur ne bougeait pas dans le ThreadKeeper pour les threads monitorés, c'était signe d'un blocage.

    JclCreateThreadStackTrace effectivement semble plus simple que StackWalk que fourni Winapi.
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  5. #5
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 694
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 694
    Points : 13 130
    Points
    13 130
    Par défaut
    Oui Sleep doit être remplacé par un wait timeout. Le timer non ! Il doit être bloqué mais pas bloquant, il tourne dans la tâche à surveiller.

  6. #6
    Membre averti Avatar de franckcl
    Homme Profil pro
    Developpeur Delphi
    Inscrit en
    Septembre 2004
    Messages
    516
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Developpeur Delphi
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Septembre 2004
    Messages : 516
    Points : 443
    Points
    443
    Par défaut
    Merci à tous les deux, je suis entrain de tester ça.

  7. #7
    Membre averti Avatar de franckcl
    Homme Profil pro
    Developpeur Delphi
    Inscrit en
    Septembre 2004
    Messages
    516
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Developpeur Delphi
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Septembre 2004
    Messages : 516
    Points : 443
    Points
    443
    Par défaut
    Bon alors voilà, je viens de m'y remettre et cela fait plusieurs heures que je suis dessus car dans mon appli, impossible de tracer le thread principal alors qu'avec le programme de test, ça marche.
    En fait ça marche très bien en 32 bits mais pas en 64 bits ... et mon appli est en 64 bits.
    Il doit donc y avoir une subtilité pour le faire marcher en 64 bits.

    Même le programme de test ne fonctionne pas en 64 bits.

    Voici le fichier obtenu en 32 bits:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
    [77892D1C]{ntdll.dll   } ZwDelayExecution
    [005F482D]{Project1.exe} Unit1.TForm1.Button1Click (Line 56, "Unit1.pas")
    [00524F53]{Project1.exe} Vcl.Controls.TControl.Click (Line 7660, "Vcl.Controls.pas")
    [00529AED]{Project1.exe} Vcl.Controls.TWinControl.WndProc (Line 10579, "Vcl.Controls.pas")
    [0053D608]{Project1.exe} Vcl.StdCtrls.Stdctrls.TButtonControl.WndProc (Line 5708, "Vcl.StdCtrls.pas")
    [00529C47]{Project1.exe} Vcl.Controls.DoControlMsg$qqrp6HWND__pv (Line 10648, "Vcl.Controls.pas")
    [00529AED]{Project1.exe} Vcl.Controls.TWinControl.WndProc (Line 10579, "Vcl.Controls.pas")
    [005AEE7C]{Project1.exe} Vcl.Forms.TCustomForm.WndProc (Line 4986, "Vcl.Forms.pas")
    [00529068]{Project1.exe} Vcl.Controls.TWinControl.MainWndProc (Line 10267, "Vcl.Controls.pas")
    [004C3A58]{Project1.exe} System.Classes.StdWndProc$qqsp6HWND__uiuiui (Line 18318, "System.Classes.pas")
    [00529BF5]{Project1.exe} Vcl.Controls.TWinControl.DefaultHandler (Line 10620, "Vcl.Controls.pas")
    [00529AED]{Project1.exe} Vcl.Controls.TWinControl.WndProc (Line 10579, "Vcl.Controls.pas")
    [0053D608]{Project1.exe} Vcl.StdCtrls.Stdctrls.TButtonControl.WndProc (Line 5708, "Vcl.StdCtrls.pas")
    [004C3A58]{Project1.exe} System.Classes.StdWndProc$qqsp6HWND__uiuiui (Line 18318, "System.Classes.pas")
    Et le même programme de test en 64 bits (ce n'est pas le thread principal qui est dépilé):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    [00007FFD0A4CD3F4]{ntdll.dll   } ZwDelayExecution
    [00000000006E2C64]{Project1.exe} JclDebug._ZN8Jcldebug17TJclStackInfoListC3EbiPvbS1_S1_ (Line 5088, "JclDebug.pas")
    [00000000006E2623]{Project1.exe} JclDebug._ZN8Jcldebug18JclCreateStackListEbiPvbS0_S0_ (Line 4940, "JclDebug.pas")
    [00000000006E2778]{Project1.exe} JclDebug._ZN8Jcldebug25JclCreateThreadStackTraceEby (Line 4983, "JclDebug.pas")
    [00000000006E569E]{Project1.exe} Unit1._ZN5Unit115TIsFreezeThread7ExecuteEv (Line 104, "Unit1.pas")
    [00000000005189F3]{Project1.exe} System.Classes._ZN6System7Classes10ThreadProcEPNS0_7TThreadE (Line 15686, "System.Classes.pas")
    [000000000040FB3D]{Project1.exe} System._ZN6System13ThreadWrapperEPv (Line 25373, "System.pas")
    [00007FFD098F7034]{KERNEL32.DLL} BaseThreadInitThunk
    [00007FFD0A482651]{ntdll.dll   } RtlUserThreadStart
    Je ne vois pas ce qui peut poser ce problème...

Discussions similaires

  1. utiliser un thread séparé et revenir au thread principal
    Par k6971 dans le forum EDT/SwingWorker
    Réponses: 5
    Dernier message: 19/09/2007, 11h42
  2. appel asynchrone dans le thread principal
    Par mrrenard dans le forum C#
    Réponses: 6
    Dernier message: 05/04/2007, 09h07
  3. [Thread]thread principal et autres thread.
    Par david06600 dans le forum Concurrence et multi-thread
    Réponses: 5
    Dernier message: 19/12/2006, 15h59
  4. Réponses: 1
    Dernier message: 19/05/2006, 09h24
  5. Pile et thread
    Par Tchetch dans le forum POSIX
    Réponses: 6
    Dernier message: 22/04/2004, 10h42

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo