Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Die lieben Threads mal wieder, es Fehlert so rum (https://www.delphipraxis.net/161866-die-lieben-threads-mal-wieder-es-fehlert-so-rum.html)

Medium 25. Jul 2011 11:33

Die lieben Threads mal wieder, es Fehlert so rum
 
Aloah!
So ganz so grün bin ich in Sachen Multithreading ja eigentlich nicht - dachte ich - aber in meinem aktuellen Projekt hüpft mir gelegentlich dann doch die gelegentliche AV ins Gesicht.
Der Sinn des Teils ist: Es gibt eine Liste von Anfragen, die via TCP/IP an ein anderes System gesendet werden. Da die Antworten dieses Systems keine Zuordnung zur gemachten Anfrage zulassen, muss auf die Antwort gewartet werden, bevor eine nächste geschickt werden kann. Die Anfragen werden in einem definierten Zyklus verschickt, so dass ich das in einen Thread ausgelagert habe, der immer wieder diese Anfragen abschickt wenn sie anstehen, und zurück gelieferte Antworten verarbeitet (=Daten in meine DB schreibt).
Ich versuche mal die grobe Struktur darzustellen:

Delphi-Quellcode:
unit MyThreads;

interface

type
  TFetchThread = class(TThread)
  private
    FetchGroups: TFetchGroupList; // Eine List mit Instanzen von TFetchGroup, die sind nichts großartig spannendes. Reine Datenhalter.
    Sock: TClientSocket;
    Con: TUniConnection;
    Qry: TUniQuery;
    CycleThread: TCycleThread;
    NewDataArrived: Boolean;
    WaitingForReply: Boolean;
    SockForm: TForm;
    procedure IssueFetch(fetchGroupIndex: Integer);
    procedure HandleFetchReply(aReplyBuffer: TSockBuffer);
    procedure MakeDBEntriesForGroup(groupID: Integer; buf: TFetchEntryBuffer);
    procedure InitIssueFetch;
  protected
    procedure Execute; override;
  public
    constructor Create(aSocket: TClientSocket; aSocketForm: TForm; aDBConnection: TUniConnection);
    destructor Destroy; override;
    procedure SocketDataReady(aBuffer: TSockBuffer);
  end;

implementation

constructor TFetchThread.Create(aSocket: TClientSocket; aSocketForm: TForm; aDBConnection: TUniConnection);
var
  i, m: Integer;
begin
  inherited Create(true);

  Sock := aSocket; // Socket kommt vom MainForm
  SockForm := aSocketForm; // Das Handle brauche ich später auch

  // Eigene Connection für den Thread erstellen, Daten von der übergebenen Connection nehmen
  try
    Con := TUniConnection.Create(nil);
    Con.ProviderName := aDBConnection.ProviderName;
    Con.Server  := aDBConnection.Server;
    Con.Database := aDBConnection.Database;
    Con.Port    := aDBConnection.Port;
    Con.Username := aDBConnection.Username;
    Con.Password := aDBConnection.Password;
    Con.LoginPrompt := false;
    Con.Connect;
  except
    Con.Free;
    raise Exception.Create('Mäh');
  end;

  // Thread-eigene Query
  try
    Qry := TUniQuery.Create(nil);
    Qry.Connection := Con;
  except
    Qry.Free;
    raise Exception.Create('Muh');
  end;

  // Gruppen füllen, dies ist die einzige Stelle, an der an diesen strukturell etwas gemacht wird
  FetchGroups := TFetchFunctions.MakeFetchGroups(Qry);

  CycleThread := TCycleThread.Create;
  CycleThread.Items := FetchGroups;
  CycleThread.Resume;

  self.Resume;
end;

// WaitingForReply heisst, es wurde eine Anfrage gesendet, die Antwort steht noch aus. In der Zeit darf nichts gemacht werden.
// NewDataArrived wird im SocketRead gesetzt, wenn die erwartete Menge Bytes angekommen und valide ist.
procedure TFetchThread.Execute;
var
  i, k: Integer;
  lowCycle: Integer;
begin
  repeat
    if not WaitingForReply then
    begin
      // Gruppe suchen, die aktualisiert werden muss (die, die am längsten überfällig ist)
      lowCycle := 0;
      k := -1;
      for i := 0 to FetchGroups.Count-1 do
      begin
        if FetchGroups[i].CurrentCycleTime < lowCycle then
        begin
          lowCycle := FetchGroups[i].CurrentCycleTime;
          k := i;
        end;
      end;
      // Wurde eine gefunden, dann via Socket-Kompo auf dem MainForm die Anfrage senden
      if (k >= 0) then
      begin
        IssueFetchID := k;
        Synchronize(InitIssueFetch);
      end;
    end
    else
    begin
      // Wird eine Antwort zu einer Anfrage erwartet, und es ist eine eingetroffen, diese nun verarbeiten.
      if NewDataArrived then
        HandleFetchReply(ReplyBuffer); // Der Buffer ist ein array[0..8191] of Byte, mehr kommt definitiv nicht
    end;
    Sleep(1);
  until Terminated;
end;

procedure TFetchThread.HandleFetchReply(aReplyBuffer: TSockBuffer);
var
  h: TFetchAnswerHeader; // ein record
  isValid: Boolean;
  buf: TFetchEntryBuffer; // array of Byte
begin
  Move(aReplyBuffer[0], h, SizeOf(TFetchAnswerHeader));
  // Header auf Validität prüfen
  isValid := DiverseDinge;
  if isValid then
  begin
    // ReplyGroupID wird in InitIssueFetch() gesetzt, und ist der Index der zuletzt angefragen Fetchgruppe in der Liste dieser
    SetLength(buf, FetchGroups[ReplyGroupID].ByteCount);
    Move(aReplyBuffer[SizeOf(TFetchAnswerHeader)], buf[0], FetchGroups[ReplyGroupID].ByteCount);
    MakeDBEntriesForGroup(ReplyGroupID, buf);
  end;

  NewDataArrived := false;
  WaitingForReply := false;
end;

// Mein Sorgenkind! Das erstellen des TDBEntryThreads knallt sporadisch, und zwar beim Erzeugen selbt.
// Der Konstruktur läuft sauber durch, das hab ich getestet, dennoch gibt's ab und an eine AV an unterschiedlichen
// Adressen. Mal 0, mal $FFFFFFFF, mal irgendwas im Codesegment, und dort immer leicht verschiedene.
// Das try..except hier ist nur ein Workaround, den ich gern los wäre. Zudem greift dies nicht immer, selten springt
// Delphi auch gleich ins CPU-Fenster, obwohl "bei Delphi-Exceptions stoppen" aus ist. Igitt!
procedure TFetchThread.MakeDBEntriesForGroup(groupID: Integer; buf: TFetchEntryBuffer);
var
  group: TFetchGroup;
  threadOkay: Boolean;
begin
  group := FetchGroups[groupID];
  threadOkay := false;
  repeat
    try
      TDBEntryThread.Create(Qry.Connection, group, buf, SockForm.Handle);
      threadOkay := true;
    except
    end;
    Sleep(1);
  until threadOkay;
end;

// Das ist besagter Konstruktor. Ein paar private Felder werden gesetzt, viel mehr nicht.
constructor TDBEntryThread.Create(aCon: TUniConnection; aGroup: TFetchGroup; aBuf: TFetchEntryBuffer; aWnd: HWND);
begin
  inherited Create(true);
  Con := aCon;
  Sql := TUniSQL.Create(nil);
  Sql.Connection := Con;
  Group := aGroup;
  Buf := aBuf;
  Wnd := aWnd;
  FreeOnTerminate := true;
  Resume;
end;

// Und hier der zugehörige Workload
procedure TDBEntryThread.Execute;
begin
  // Hier wird anhand von Infos aus der "Group" der Empfagspuffer interpretiert. Group wird dabei nur lesend angefasst.
  // Da das recht viel ist, durch diesen Kommentar ersetzt. Im wesentlichen wird ein SQL Statement zusammengestückelt.

  while Sql.Connection.InTransaction do
    Sleep(1);
  Sql.Execute;

  // Puffer zur Änderungserkennung für diesen Fetch-Aufruf speichern (einziger schreibender Zugriff auf Group)
  group.Buffer := Buf;
  group.FirstRun := false;
end;

// Wird vom MainForm aufgerufen, wenn OnSocketRead die geforderte Datenmenge gelesen hat
procedure TFetchThread.SocketDataReady(aBuffer: TSockBuffer);
begin
  ReplyBuffer := aBuffer;
  NewDataArrived := true;
end;

// Das hier macht der ominöse Cyclethread: Er zählt in den Fetchgruppen einen Zeitwert runter,
// anhand dessen ermittelt wird welche Anfrage als nächste ansteht.
procedure TCycleThread.Execute;
var
  i: Integer;
  tickDelta: Int64;
begin
  repeat
    tickDelta := GetTickCount-LastTickCount;
    LastTickCount := GetTickCount;
    for i := 0 to Items.Count-1 do
      Items[i].CurrentCycleTime := Items[i].CurrentCycleTime - tickDelta;
    Sleep(1);
  until Terminated;
end;
Was mich fuchst ist, dass das Erzeugen des DBEntryThreads ab und an knallt (so im Schnitt alle 20 Mal ein Mal, die Zykluszeit ist bei 2 Sekunden), obwohl der Konstruktor selbst keine AV erzeugt (getestet via try..except drum rum und bei Exception Logfile machen, was nie geschah). Das try..Except um den Aufruf des Konstruktors dagegen läuft wie gesagt ab und an auf den Hammer, ich komme beim Debuggen nur nicht an die genaue fehlerhafte Zeile, da diese sich nicht explizit in meinem Code zu befinden scheint.
Ich hab mir ein wenig Sorgen um diese Groups gemacht, da diese ja ein Feld des FetchThreads sind, und sowohl vom Cyclethread als auch vom DBEntryThread dort hineingegriffen wird. Jedoch werden in beiden nur elementare Operationen ausgeführt, und die Struktur der Liste bleibt nach dem Konstruktor von TFetchThread für den Rest des Ablaufs immer gleich.
Was hab ich hier nicht bedacht? Wäre prima, wenn trotz der Länge des Teils jemandem was auffällt. Danke schon mal!

\\Edit:
Hier mal ein Auszug aus meinem Fehlerlog (das schreibe ich im except-Teil der Methode MakeDBEntriesForGroup(), das ist hier der Länge wegen nicht im Code):
Code:
25:07:2011 12:46:17:625 : Zugriffsverletzung bei Adresse 00000000. Lesen von Adresse 00000000 -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:46:31:859 : Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:47:00:953 : Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:47:04:562 : Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:47:21:609 : Zugriffsverletzung bei Adresse 00004244. Lesen von Adresse 00004244 -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:48:00:421 : Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:48:02:859 : Zugriffsverletzung bei Adresse 00004244. Lesen von Adresse 00004244 -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:49:27:796 : Zugriffsverletzung bei Adresse 00000000. Lesen von Adresse 00000000 -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:49:45:890 : Zugriffsverletzung bei Adresse 00000000. Lesen von Adresse 00000000 -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:49:49:937 : Zugriffsverletzung bei Adresse 00000000. Lesen von Adresse 00000000 -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:49:53:203 : Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:49:53:984 : Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:50:00:500 : Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:50:06:125 : Zugriffsverletzung bei Adresse 00000000. Lesen von Adresse 00000000 -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:50:07:796 : Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:50:08:140 : Zugriffsverletzung bei Adresse 00000000. Lesen von Adresse 00000000 -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:50:12:203 : Zugriffsverletzung bei Adresse 00000000. Lesen von Adresse 00000000 -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:50:44:234 : Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:50:52:703 : Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:51:52:156 : Zugriffsverletzung bei Adresse 00000000. Lesen von Adresse 00000000 -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:52:03:109 : Zugriffsverletzung bei Adresse 00000000. Lesen von Adresse 00000000 -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:54:52:859 : Zugriffsverletzung bei Adresse 00004244. Lesen von Adresse 00004244 -> TDBEntryThread.Create(Qry.Connection, group, buf);
25:07:2011 12:58:24:640 : Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF -> TDBEntryThread.Create(Qry.Connection, group, buf);

SirThornberry 25. Jul 2011 12:09

AW: Die lieben Threads mal wieder, es Fehlert so rum
 
Das ist aber nicht die vollständige Unit oder? Ich vermisse da die Declaration vom TDBEntryThread

Medium 25. Jul 2011 13:19

AW: Die lieben Threads mal wieder, es Fehlert so rum
 
Nene, die ganze Unit hat 520 Zeilen und das eine oder andere ist noch in zwei weiteren. Hier passiert aber das Wesentliche. Falls die Deklaration aber hilfreich ist:
Delphi-Quellcode:
  TDBEntryThread = class(TThread)
  private
    Sql: TUniSQL;
    Con: TUniConnection;
    Group: TFetchGroup;
    Buf: TFetchEntryBuffer;
    Wnd: HWND;
    procedure ConfirmDataSent(valueCount: Integer);
  protected
    procedure Execute; override;
  public
    constructor Create(aCon: TUniConnection; aGroup: TFetchGroup; aBuf: TFetchEntryBuffer; aWnd: HWND);
    destructor Destroy; override;
  end;
Im Destruktor wird nur Sql.Free; aufgerufen.

Ich hab den sporadischen Fehler über Mittag wieder bekommen. Also der, der nicht im Log landet, sondern den Debugger ins CPU-Fenster schickt:
Code:
---------------------------
Benachrichtigung über Debugger-Problem
---------------------------
In Projekt E:\Projekte\Server Template\Server\Server.exe trat ein Problem mit folgender Meldung auf: 'Anwendungsdefinierte Excpetion (Code 0xc0000028) bei 0x7c95eb93'. Prozess angehalten. Mit Einzelne Anweisung oder Start fortsetzen.
---------------------------
OK  
---------------------------
Die Codestelle mit vorhergehender Zeile ist:

7C95EB8E call ntdll.RtlRaiseException
7C95EB93 jmp -$0001703f

So ganz habe ich leider nicht nachvollziehen können wo genau das ist, so firm bin ich mit Assembler dann leider doch nicht :(

PS: "Excpetion" ist aber auch nicht ganz richtig da im Fehlertext, hm? :)

Edit: Laut Google ist 0xc0000028 STATUS_BAD_STACK. "An invalid or unaligned stack was encountered during an unwind operation." Was lese ich daraus? :gruebel:

Edit2: Das MSDN ist da auch leider eher wenig hilfreich.
Zitat:

User Action:
This is a Windows 2000 Executive STATUS error message. Choose an option from the message box. Then contact your technical support group because the application is broken.
Mehr steht nicht dazu.

Luckie 25. Jul 2011 13:27

AW: Die lieben Threads mal wieder, es Fehlert so rum
 
Das klingt, als ob du dir deinen Stack zerschossen hättest.

Medium 25. Jul 2011 13:31

AW: Die lieben Threads mal wieder, es Fehlert so rum
 
Ja, aber wie? Ich fummel nirgends mutwillig rum, kein Pointergeschubse, kein Handmade-Assembler, alles schönstes Object-Pascal :?

Edit: Jetzt hab ich an der Stelle auch noch einen Stack-Overflow gesehen. Also irgendwas ist doch da ganz fies im Argen. AVs an Adressen wie 0 und FFFFFFFF sowie anderen, misaligned Stacks, Overflows... dabei wird dort doch nur die Täglich-Brot-Arbeit verrichtet, einen Thread loszutreten. Ich werd langsam blöd =)

Edit:
So, mal haarklein ge-try-excepted:
Delphi-Quellcode:
procedure TFetchThread.MakeDBEntriesForGroup(groupID: Integer; buf: TFetchEntryBuffer);
var
  group: TFetchGroup;
  threadOkay: Boolean;
begin
  group := FetchGroupsInSQLOrder[groupID];
  threadOkay := false;
  repeat
    try
      TDBEntryThread.Create(Qry.Connection, group, buf, SockForm.Handle);
      threadOkay := true;
    except
      on e: Exception do Log(FormatDateTime('dd.mm.yyyy hh:mm:ss.zzz', now)+' : '+e.Message+' -> TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)='+IntToStr(Length(buf)));
    end;
    Sleep(1);
  until threadOkay;
end;


constructor TDBEntryThread.Create(aCon: TUniConnection; aGroup: TFetchGroup; aBuf: TFetchEntryBuffer; aWnd: HWND);
begin
  try
    inherited Create(true);
  except
    on e: Exception do Log(FormatDateTime('dd.mm.yyyy hh:mm:ss.zzz', now)+' : '+e.Message+' -> inherited Create(true);');
  end;
  try
    Con := aCon;
  except
    on e: Exception do Log(FormatDateTime('dd.mm.yyyy hh:mm:ss.zzz', now)+' : '+e.Message+' -> Con := aCon;');
  end;
  try
    Sql := TUniSQL.Create(nil);
  except
    on e: Exception do Log(FormatDateTime('dd.mm.yyyy hh:mm:ss.zzz', now)+' : '+e.Message+' -> Sql := TUniSQL.Create(nil);');
  end;
  try
    Sql.Connection := Con;
  except
    on e: Exception do Log(FormatDateTime('dd.mm.yyyy hh:mm:ss.zzz', now)+' : '+e.Message+' -> Sql.Connection := Con;');
  end;
  try
    Group := aGroup;
  except
    on e: Exception do Log(FormatDateTime('dd.mm.yyyy hh:mm:ss.zzz', now)+' : '+e.Message+' -> Group := aGroup;');
  end;
  try
    Buf := aBuf;
  except
    on e: Exception do Log(FormatDateTime('dd.mm.yyyy hh:mm:ss.zzz', now)+' : '+e.Message+' -> Buf := aBuf;');
  end;
  try
    Wnd := aWnd;
  except
    on e: Exception do Log(FormatDateTime('dd.mm.yyyy hh:mm:ss.zzz', now)+' : '+e.Message+' -> Wnd := aWnd;');
  end;
  try
    FreeOnTerminate := true;
  except
    on e: Exception do Log(FormatDateTime('dd.mm.yyyy hh:mm:ss.zzz', now)+' : '+e.Message+' -> FreeOnTerminate := true;');
  end;
  try
    Resume;
  except
    on e: Exception do Log(FormatDateTime('dd.mm.yyyy hh:mm:ss.zzz', now)+' : '+e.Message+' -> Resume;');
  end;
end;
Das Fehlerlog dazu:
Code:
25.07.2011 15:03:38.281 : Zugriffsverletzung bei Adresse 00000100. Lesen von Adresse 00000100 -> TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378
25.07.2011 15:04:18.718 : Zugriffsverletzung bei Adresse 00000100. Lesen von Adresse 00000100 -> TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378
25.07.2011 15:05:02.859 : Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF -> TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30
25.07.2011 15:08:05.062 : Zugriffsverletzung bei Adresse 00004244. Lesen von Adresse 00004244 -> TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378
25.07.2011 15:08:37.375 : Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF -> TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378
25.07.2011 15:09:19.812 : Zugriffsverletzung bei Adresse 00000000. Lesen von Adresse 00000000 -> TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378
25.07.2011 15:09:44.015 : Zugriffsverletzung bei Adresse 00003133. Lesen von Adresse 00003133 -> TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378
Keine Zeile im Konstruktor löst die Exceptions aus, dessen Aufruf aber schon :gruebel:. Die Längen von "buf" sind die erwarteten, und bisher gabs noch keinen Fehler, der mich ins CPU-Fenster schmiss. Bisher der längste Run ohne dies - ist klar, ich wills ja auch provozieren :stupid:


Eeeedit...:
Darauf steh ich ja. Jetzt, mit o.g. try-except Monster, treten nur noch die "weichen" Fehler auf, der Stack scheint auf einmal zu schnurren. Immer prima, wenn die Analyse die Ursache entfernt. Dabei ist das doch noch kein Quantencomputer :stupid:. Interessant ist dabei vor allem, dass in den geloggten Fehlern nur noch Fehler beim Lesen auftauchen, zuvor gab es auch welche beim Schreiben. Die sind auf wundersame Weise ebenfalls weg, und das Teil läuft jetzt seit gut 30min ohne Fehler. Zwar diesen geloggten, aber eben keine Bad Stacks oder Überläufe mehr. Aber so lassen würde ich das auch nur sehr ungerne, auch wenn so die Funktion an sich gegeben ist...

Medium 25. Jul 2011 15:06

AW: Die lieben Threads mal wieder, es Fehlert so rum
 
Zu früh gefreut. Ich hab nun mal jede Methode in einen try-except gefasst, um ganz genau zu sehen wo es ggf. noch knallen könnte. Zudem hab ich die Zykluszeit von 2s auf 300ms runter gesetzt, um meine Wartezeit zu verkürzen. Hier mein Log von 5 Testruns, die alle abbrachen. Eigentlich waren es 6, aber einer hat mir die IDE abgeschossen noch bevor ein Fehler im Log stand...
Code:
25:07:2011 15:54:20:453: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378 -> Zugriffsverletzung bei Adresse 00000000. Lesen von Adresse 00000000
25:07:2011 15:54:30:906: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378 -> Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF
25:07:2011 15:54:33:656: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 00C80000. Lesen von Adresse FFFFFFFF
25:07:2011 15:54:42:390: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 00000060. Lesen von Adresse 00000060
25:07:2011 15:54:43:234: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378 -> Zugriffsverletzung bei Adresse 00000000. Lesen von Adresse 00000000
25:07:2011 15:54:57:234: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378 -> Stack-Überlauf
--------------------------
25:07:2011 15:56:05:156: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 00000000. Lesen von Adresse 00000000
25:07:2011 15:56:08:687: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378 -> Zugriffsverletzung bei Adresse 00CE51AB. Schreiben von Adresse B0CF1FD1
--------------------------
25:07:2011 15:56:43:171: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378 -> Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF
25:07:2011 15:56:43:250: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 00C80000. Lesen von Adresse FFFFFFFF
25:07:2011 15:56:46:468: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378 -> Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF
25:07:2011 15:56:51:250: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 00000000. Lesen von Adresse 00000000
25:07:2011 15:56:57:531: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Privilegierte Anweisung
--------------------------
25:07:2011 15:57:32:046: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 00C80000. Lesen von Adresse FFFFFFFF
25:07:2011 15:57:32:703: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 00000000. Lesen von Adresse 00000000
25:07:2011 15:57:44:484: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378 -> Zugriffsverletzung bei Adresse 00D04F10. Schreiben von Adresse 011609FD
25:07:2011 15:57:45:750: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF
25:07:2011 15:57:46:203: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 00C80000. Lesen von Adresse FFFFFFFF
25:07:2011 15:57:52:390: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 00000010. Lesen von Adresse 00000010
25:07:2011 15:58:02:125: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF
25:07:2011 15:58:08:562: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378 -> Zugriffsverletzung bei Adresse 00000000. Lesen von Adresse 00000000
25:07:2011 15:58:08:859: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 00000056. Lesen von Adresse 00000056
25:07:2011 15:58:09:546: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 00000056. Lesen von Adresse 00000056
25:07:2011 15:58:10:171: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 00000028. Lesen von Adresse 00000028
25:07:2011 15:58:13:000: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 00003134. Lesen von Adresse 00003134
25:07:2011 15:58:13:656: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 00004244. Lesen von Adresse 00004244
25:07:2011 15:58:17:390: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378 -> Zugriffsverletzung bei Adresse 00D0BF7B. Schreiben von Adresse 7267758A
25:07:2011 15:58:28:812: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378 -> Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF
25:07:2011 15:58:28:875: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF
25:07:2011 15:58:29:531: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 00000020. Lesen von Adresse 00000020
25:07:2011 15:58:33:453: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF
25:07:2011 15:58:34:312: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF
25:07:2011 15:58:34:984: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 000000FC. Lesen von Adresse 000000FC
--------------------------
25:07:2011 16:00:07:687: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378 -> Zugriffsverletzung bei Adresse 00003832. Lesen von Adresse 00003832
25:07:2011 16:00:08:671: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378 -> Zugriffsverletzung bei Adresse 0040399A in Modul 'Server.exe'. Lesen von Adresse FFFFFFFF
25:07:2011 16:00:12:062: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Zugriffsverletzung bei Adresse 00C80000. Lesen von Adresse FFFFFFFF
25:07:2011 16:00:12:578: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378 -> Zugriffsverletzung bei Adresse 00C80000. Lesen von Adresse FFFFFFFF
25:07:2011 16:00:13:812: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=378 -> Zugriffsverletzung bei Adresse 00000297. Lesen von Adresse 00000297
Edit: Auch hübsch, und neu:
Code:
25.07.2011 16:22:04.671: TDBEntryThread.Create(Qry.Connection, group, buf); Length(buf)=30 -> Externe Exception C000001D
Stacküberlauf, Privilegierte Anweisung (die ist auch mir neu an dieser Stelle), diverse Schreib- und Lesegriffe ins Klo und ein Totalzerriss der IDE... so langsam muss meine Frage eher lauten: Wie zum Geier schafft man es, eine derartige Vielfalt an Fehlern mit ein und dem selben Code, auf ein und der selben Datenbasis zu erzeugen!? Ich muss doch irgendwo ganz grundlegend etwas falsch machen, und hab nur Tomaten auf den Augen :(
Darf ich eventuell einen Thread keinen weiteren Thread erzeugen lassen? Das wäre jetzt noch so mein vorerst letzter Anker. Es sieht alles so aus, als würde mein Programcounter bei Erstellung des Threads völlig im zufälligen Nirvana landen, aber ich fasse nichts via Gepointer oder anderen Kunstgriffen an. Hat das schon mal jemand gesehen? Wenn ja, wie/wo/wann? Mir fehlt ein Ansatz.

tinof 25. Jul 2011 15:33

AW: Die lieben Threads mal wieder, es Fehlert so rum
 
Hallo,

aus dem Bauch heraus:

Das der FetchThread und der DBEntryThread gleichzeitig mit der Liste FetchGrops 'rummachen' sieht mit etwas verdächtig aus. Ich würde entweder die Liste im DBThread in eine eigene Liste kopieren (Assign) oder die Aufrufe in beiden Threads in eine critical section 'packen'.

Viel Erfolg
Tino

Medium 25. Jul 2011 15:44

AW: Die lieben Threads mal wieder, es Fehlert so rum
 
Grad noch etwas rumgebaut: Das Problem ist eindeutig das Erzeugen des DBEntryThreads selbst. Ich habe ihm jetzt mal noch einen parameterlosen Konstruktor spendiert, und im Execute() alles auskommentiert, so dass dort keinerlei Zugriffe auf irgendwas mehr passieren, und dem Thread auch kein Buffer mehr übergeben wird, und er auch keine SQL-Zugriffe tätigt.
Die Fehler treten nach wie vor an der Stellt "TDBEntryThread.Create;" auf, in dessen Code jedoch KEINE Exception statt findet. Es muss also irgendwas an der "Magie" von TThread sein, genauer an der, die ihn erzeugt.

Was zwar durch das Herunterbrechen des Konstruktors auf
Delphi-Quellcode:
constructor TDBEntryThread.Create;
begin
  inherited Create(true);
  FreeOnTerminate := true;
  Resume;
end;
brachte ist, dass diese ganz fatalen Fehler nicht mehr auftreten, aber das schreibe ich dem grundsätzlichen Scheitern der Threaderzeugung zu, die bei gleichzeitigem Schreiben von Feldern in diesem vermutlich schwerwiegendere Ausmaße hat als so "plain" ohne mehr als Erzeugen. Es hagelt aber weiterhin AVs an diversen Adressen von 0 bis FFFFFFFF.

Blup 25. Jul 2011 15:56

AW: Die lieben Threads mal wieder, es Fehlert so rum
 
Delphi-Quellcode:
constructor
begin
  inherited Create(true);

{...tu was vor Execute...}

  Resume;
end;
Das ist so nicht nötig, der Thread startet in jedem Fall frühestens, wenn alle Konstruktoren abgeschlossen wurden.
Delphi-Quellcode:
constructor
begin
  inherited Create(false);

{...tu was vor Execute...}

end;
Ich vermute das die Exception im Konstruktor eigentlich nur ein Folgefehler und die Ursache viel früher zu suchen ist.

Problem: Es wird ein Thread erzeugt, der Zugriff auf eine Connection nimmt, die ihm nicht gehört.
Es existiert keine Kontrolle welche Threads existieren, die derzeit mit dieser Connection arbeitet.
Der Zugriff "Sql.Connection.InTransaction" kann schon eine Zugriffsverletzung auslösen.
Es müsste zumindest unmittelbar ein StartTransaction folgen und beides mit einer CriticalSection gekapselt werden.

Insgesamt scheint mir die Konstruktion ziemlich fehleranfällig.
Ich würde die Anwendung im Prinzip so aufbauen:

[1.Liste der wartenden Aufgaben]
-> 1.Thread der jeweils eine Aufgabe per TCP/IP abarbeitet und mit dem Ergebnis in 2.Liste stellt (hat mit DB nichts zu tun)
[2.Liste der wartenden Aufgaben]
-> 2.Thread der jeweils die Datenbankarbeit für eine Aufgabe erledigt und diese mit neuem Termin wieder in die 1.Liste stellt
(hat eigene DB-Verbindung, die niemanden sonst etwas angeht)

Beide Threads existieren permanent und warten, wenn keine Aufgabe ansteht.
Zugriffe auf die Listen müssen natürlich gekapselt werden.

franktron 25. Jul 2011 16:31

AW: Die lieben Threads mal wieder, es Fehlert so rum
 
Ich würde mal sagen das dieser Code den Fehler bringt unidac ist nicht Threadsafe

Delphi-Quellcode:
constructor TDBEntryThread.Create(aCon: TUniConnection; aGroup: TFetchGroup; aBuf: TFetchEntryBuffer; aWnd: HWND);
begin
  try
    inherited Create(true);
  except
    on e: Exception do Log(FormatDateTime('dd.mm.yyyy hh:mm:ss.zzz', now)+' : '+e.Message+' -> inherited Create(true);');
  end;
  try
    Con := aCon; //Das hier
Ich hatte die Probleme auch mal bis ich in jedem Thread die Connection selbst erstellt hatte.

Medium 25. Jul 2011 19:15

AW: Die lieben Threads mal wieder, es Fehlert so rum
 
Die Connection hatte ich auch schon unter Verdacht, da die schon mal mein Problem mit Threads war - daher die eigene Connection für den TFetchThread, beim DBEntryThread hab ich das vergessen. Das wäre auch schon gefixed gewesen, wenn nicht schon die Variante von DBEntryThread, die überhaupt nichts macht die ganzen Fehler beim Create werfen würde! Das ist der Teil, an dem ich den ganzen Tag verzweifel: Das bloße Erzeugen eines Threads, der wirklich nichts tut, also komplett leere Execute-Methode und leerem Konstruktor (bis auf das von Blup angesprochene) führt zu den genannten Random-Fehlern. Und zwar nicht in einer der 3 Zeilen im Konstruktor, sondern erst in der Methode, die den Konstruktor aufruft - dort aber unmittelbar in dieser Zeile. Aus diesem Grunde bin ich von den DB Dingen erstmal weg, weil eben auch ganz ohne rummst es schon unverändert. Wenn das dann mal gelöst ist, bekommen die auch ihre eigene Connection, oder eine gesharete mit Critical-Sections (dürfte deutlich performanter sein; alternativ Pooling an).
Der Witz ist ja, dass sogar die Variante mit voller Funktionalität (also incl. DB Zugriffen) klaglos tut, wenn das Erzeugen denn geklappt hat. Vermutlich eher Zufall, aber dort habe ich noch nie eine Exception gesehen. Der Hund ist im reinen Erzeugen des Threads begraben, und dort auch nur mit einer Häufigkeit von grob geschätzt 5-15% der Erzeugungen. Laut Taskmanager habe icha uch keine Probleme mit zu vielen Handles oder Threads, so lange das Programm läuft, bleibt die Anzahl derer im Mittel gleich. Daher scheinen die auch korrekt zu terminieren.

Ich habe den Weg mit nur einem Abarbeitenden Thread gewählt, weil leider die Zurordnung "Anfrage zu Antwort" nicht möglich ist (danke Siemens...), und es einfacher erschien einen einzelnen Thread warten zu lassen bis die Antwort zur letzten Anfrage da ist, als dies zwischen mehreren Threads (einer pro Anfragegruppe, oder gar dynamische Worker) zu synchronisieren. Die DB-Einträge sind eigentlich nur deswegen in einen weiteren Thread ausgelagert, da in der Zeit, in der das Update zusammengebastelt und ausgeführt wird ja ruhig schon die nächste Anfrage los gehen kann. Der Kram im DBEntryThread fand zuvor komplett im Kontext des TFetchThreads statt, hat diesen aber unnötig aufgehalten.
Und da eh klar ist, dass ich immer nach einer Anfrage auf Antwort warten muss, erschien mir die Variante mit einer Joblist für die DB Einträge überflüssig - da könnte eh immer nur einer drin stehen, also kann ich auch gleich direkt einen Thread zur aktuellen Antwort abfeuern. Eigentlich...

Blup 26. Jul 2011 08:27

AW: Die lieben Threads mal wieder, es Fehlert so rum
 
Da hilft eigentlich nur schrittweise Teile des Codes zu entfernen (z.B. die TCP/IP Teil) und die Funktion zu simulieren um den Rest der Anwendung einem Stresstest zu unterziehen.
Zitat:

Zitat von Medium (Beitrag 1113495)
Ich habe den Weg mit nur einem Abarbeitenden Thread gewählt, weil leider die Zurordnung "Anfrage zu Antwort" nicht möglich ist (danke Siemens...), und es einfacher erschien einen einzelnen Thread warten zu lassen bis die Antwort zur letzten Anfrage da ist, als dies zwischen mehreren Threads (einer pro Anfragegruppe, oder gar dynamische Worker) zu synchronisieren.

Ja, das kenn ich gut, "geht doch viel einfacher so", "wenn tatsächlich neue Anforderungen kommen machen wirs richtig..." usw., nur zahlt man so bei Fehlersuche und späteren Erweiterungen drauf.
Ein automatischer Unit-Test scheint mir in der jetzigen Struktur kaum möglich.

Zitat:

Zitat von Medium (Beitrag 1113495)
Und da eh klar ist, dass ich immer nach einer Anfrage auf Antwort warten muss, erschien mir die Variante mit einer Joblist für die DB Einträge überflüssig - da könnte eh immer nur einer drin stehen, also kann ich auch gleich direkt einen Thread zur aktuellen Antwort abfeuern. Eigentlich...

Das erscheint mir zumindest nicht klar. Es kann durchaus sein, daß der Zugriff auf die Datenbank länger dauert als normal. Im schlimmsten Falls ist die Verbindung zum DB-Server/Dienst gestört.
Falls dann tatsächlich zwei Anfragen kurz nacheinander abgeschlossen werden, existieren plötzlich auch zwei TDBEntryThread, aber nur eine Connection...

Medium 26. Jul 2011 09:33

AW: Die lieben Threads mal wieder, es Fehlert so rum
 
Die Anforderung wird es nie geben, das ist (und ich weiss, dass sich das schnell sagt) gesichert. Sollte sich je etwas an dem Protokoll ändern, sind ganz andere Dinge in Not. Ich weiss, dass das einem Informatiker schwer zu vermitteln ist (bin ja selber einer), aber vertrau mir hier.
Zudem sehe ich den wirklichen Unterschied zwischen einer Joblist mit einem Eintrag und dem direkten Übergeben dieses einen Jobs nicht so ganz. Das Verhalten ist an sich ja wohl definiert, und die Connection wird noch angegangen, das ist klar. Nur bringt mich das mit meinem eigentlichen Problem nicht so viel weiter, dass ja schon ohne Connection besteht :?
Wenn dann zwei oder mehr Threads eine eigene Connection haben ist das auch wieder okay, und eben genau für den genannten Notfall ist eine Timeout- und/oder Mengenüberwachung zumindest gedanklich schon da, damit sich nicht hunderte Threads+Connections aufbäumen wenns mal hakt :)

Ich werd wohl mal eine Kopie nach und nach ausziehen, und schauen was passiert. Mehr ist mir auch in meiner sonst sehr produktiven (und oftmals daher viel zu langen) Einschlafphase auch nicht eingefallen. Danke euch bis hier her schon mal kräftig!

franktron 26. Jul 2011 10:03

AW: Die lieben Threads mal wieder, es Fehlert so rum
 
Haste mal versucht diesen Parameter raus zunehmen

[DELPHI aCon: TUniConnection;[/DELPHI] im Create

Es kann nämlich sein das die aCon nicht mehr da ist das würde zumin. die seltsammen Esceptions erklären.

Ich hab früher auch mit MyDAC auch solche Probleme gehabt und Übertrage die USer Daten jetzt in einem Record wo die Parameter drin sind wie Username,pw .u.s.w. dann geht alles.

Medium 26. Jul 2011 10:08

AW: Die lieben Threads mal wieder, es Fehlert so rum
 
Die Connection ist schon lange nicht mehr drin, hab ich auch gelegentlich erwähnt ;) Aber Blup, du bist mein Retter! Es war tatsächlich das suspended create! Sobald das raus ist, läuft alles wie gewollt. Da macht man's Jahre lang falsch, es ging immer, und dann trifft's einen von hinten durch die Brust (und kostet 1,5 Tage Kopfkratzen) :)
Ahhh, endlich geht's weiter. Erstmal die Connection fixen. Danke!


Alle Zeitangaben in WEZ +1. Es ist jetzt 05:32 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz