[Lazarus] Wycieki pamięci

Tutaj umieszczamy tematy związane z językami programowania niepasującymi do innych działów.
Regulamin forum
Temat prosimy poprzedzić nazwą języka umieszczonego w nawiasach kwadratowych np. [Pascal].
Marhef
Posty: 24
Rejestracja: czwartek 30 maja 2019, 11:43

[Lazarus] Wycieki pamięci

Postautor: Marhef » środa 22 sty 2020, 12:04

Próbuję stworzyć aplikację w Lazarusie. Jej głównym zadaniem jest komunikacja po RS485 (protokół ModBUS). Używam do tego biblioteki PascalSCADA (do pobrania stąd).
Fragmenty programu:
Najpierw tworzę klasę, pochodną (dziedziczną?) po klasie TThread, która zawiera elementy do komunikacji:

Code: Select all

TKomunikacja = class(TThread)
ModBusRTUDriver1: TModBusRTUDriver;
PLCBlock1: TPLCBlock;
SerialPortDriver1: TSerialPortDriver;
procedure PLCBlock1Update(Sender: TObject);
procedure SerialPortDriver1CommErrorReading(Error: TIOResult);
private

public
Constructor Create(CreateSuspended : boolean);
destructor Destroy(); override;
protected
procedure Execute; override;
end;
Następnie powołuję zmienną:

Code: Select all

WatekKomunikacja: TKomunikacja;
Na formularzu mam jeszcze timer, który ma ustawiony interval na 500 ms, i w obsłudze OnTimer ma procedurę:

Code: Select all

procedure TForm1.Timer1Timer(Sender: TObject);
begin
WatekKomunikacja.Terminate;
WatekKomunikacja := NiL;
end;
Procedura ta ma na celu całkowite usunięcie wątku z pamięci komputera. I tu jest problem, ale o problemie za chwilę. Najpierw dokończę opis programu.

Procedury

Code: Select all

procedure PLCBlock1Update(Sender: TObject);
procedure SerialPortDriver1CommErrorReading(Error: TIOResult);
zawierają tylko jedną linijkę (na razie):

Code: Select all

Form1.Timer1.Enabled := False;
Jest jeszcze jeden timer, który co 200 ms uruchamia timer1 (ten, który ma za zadanie usunąć wątek) i jeszcze jeden timer, który co 100 ms realizuje procedurę (czyli, jeżeli nie ma aktywnego wątku, to go tworzy):

Code: Select all

procedure TForm2.Timer1Timer(Sender: TObject);
begin
If not Assigned(WatekKomunikacja) Then WatekKomunikacja := TKomunikacja.Create(False);
end;
Mam nadzieję, że nie pominąłem czegoś ważnego.

I teraz opis problemu:
Ponieważ biblioteka PascalSCADA nie ma zaimplementowanej obsługi timeoutu przy "zniknięciu" wirtualnego portu COM, więc aplikacja "wisi". Stąd pomysł na komunikację w nowym wątku, ponieważ wątek ten mogę zniszczyć jak będzie "wisiał".
A problem jest taki, że z czasem zwiększa się ilość pamięci używana przez aplikację.
Uruchomiłem moduł heapTRC, z którego wynika, że wyciek pamięci jest gdzieś przy okazji wątków.
Podejrzewam, że nie usuwam nieaktywnego wątku z pamięci.
Ktoś wie, jak to zrobić poprawnie?

Wstawiam jeszcze pozostałe procedury z klasy:

Code: Select all

constructor TKomunikacja.Create(CreateSuspended: boolean);
begin
FreeOnTerminate := False;
inherited Create(CreateSuspended);

SerialPortDriver1 := TSerialPortDriver.Create(NiL);
SerialPortDriver1.Active := False;
SerialPortDriver1.OnCommErrorReading := @SerialPortDriver1CommErrorReading;
SerialPortDriver1.COMPort := '';

ModBusRTUDriver1 := TModBusRTUDriver.Create(NiL);
ModBusRTUDriver1.CommunicationPort := SerialPortDriver1;

PLCBlock1 := TPLCBlock.Create(NiL);
PLCBlock1.AutoRead := True;
PLCBlock1.AutoWrite := False;
PLCBlock1.MemAddress := 4000;
PLCBlock1.MemReadFunction := 3;
PLCBlock1.MemWriteFunction := 16;
PLCBlock1.PLCStation := 1;
PLCBlock1.ProtocolDriver := ModBusRTUDriver1;
PLCBlock1.OnUpdate := @PLCBlock1Update;

end;

destructor TKomunikacja.Destroy();
begin
inherited Destroy;
inherited Free;
end;

procedure TKomunikacja.Execute;
const
i : Integer = 0;
var
portyCOM : TMemo;
port : String;
znaleziony : Boolean = False;
listaPortow : String;
begin
portyCOM := TMemo.Create(NiL);
While Not Terminated Do
begin
repeat
Form1.Timer1.Enabled := False;
listaPortow:=GetSerialPortNames;
ExtractStrings([','], [], PChar(listaPortow), portyCOM.Lines );
For port in portyCOM.Lines Do
begin
If port = 'COM48' Then znaleziony := True;
end;
sleep(1);
until znaleziony;

PLCBlock1.AutoRead := True;
If Not SerialPortDriver1.Active Then
begin
SerialPortDriver1.COMPort := 'COM48';
SerialPortDriver1.Active:=True;
end;
PLCBlock1.Read;
i := i + 1;
If i = 10 Then
begin
i := 0;
z := z + 1;
end;
Sleep(1);
end;
Destroy();
end;

Awatar użytkownika
tasza
Geek
Geek
Posty: 1082
Rejestracja: czwartek 12 sty 2017, 10:24
Kontaktowanie:

Re: [Lazarus] Wycieki pamięci

Postautor: tasza » środa 22 sty 2020, 14:55

odnośnie wnętrzności TForm1.Timer1Timer() to może warto użyć https://wiki.freepascal.org/FreeAndNil
a zwalnianie zasobów wątku to spróbuj z FreeOnTerminate := true; w konstruktorze i daj to po wywołaniu inherited Create(CreateSuspended);
wołanie Destroy() będzie zbędne, sam się posprząta...
______________________________________________ ____ ___ __ _ _ _ _
Kończysz tworzyć dopiero, gdy umierasz. (Marina Abramović)

Marhef
Posty: 24
Rejestracja: czwartek 30 maja 2019, 11:43

Re: [Lazarus] Wycieki pamięci

Postautor: Marhef » środa 22 sty 2020, 15:36

Dziękuję za szybki odzew.
Z freeAndNil próbowałem. Zawiesza aplikację :-(
A FreeOnTerminate też nie zadziała, bo nie wyjdzie z pętli w procedurze Execute
Teraz widzę, że problem jest innej natury. Jeżeli komunikacja w wątku się zawiesi, to wisi cały wątek. Więc nie wchodzi do destruktora. Przypisując NiL do zmiennej WatekKomunikacja sprawiam, że wątek istnieje (bo wisi i nie może sam się zakończyć), natomiast tracę do niego uchwyt.

Więc nowe pytanie: jak "zabić" wątek z innego wątku?

Marhef
Posty: 24
Rejestracja: czwartek 30 maja 2019, 11:43

Re: [Lazarus] Wycieki pamięci

Postautor: Marhef » piątek 24 sty 2020, 13:03

Mam nowy pomysł. Ale nie do końca wiem, jak się za niego zabrać.
Z tego, co wiem, mogę ręcznie zarządzać pamięcią, korzystając z funkcji getMem/FreeMem lub new/dispose
Tylko teraz nie wiem, czy ma to sens i jak to poprawnie rozwiązać.
Na razie nie mam jeszcze nic napisane, ale czy takie postępowanie będzie ok?
1) tworzę wskaźnik typu TKomunikacja:

Code: Select all

p : ^TKomunikacja;
2) rezerwuję miejsce w pamięci dla tej zmiennej:

Code: Select all

new(p); // czy może:
P := GetMem(SizeOf(TKomunikacja)); // a może tak:
P := AllocMem(SizeOf(TKomunikacja));
3) wywołuję konstruktor:

Code: Select all

P^ := TKomunikacja.Create(False);
4) wszystkie operacje wykonuję odwołując się o wskaźnika P
5) przy usuwaniu wątku wystarczy tylko:

Code: Select all

dispose(P); // lub:
Freemem(P,sizeof(TKomunikacja)); // a co dla allocMem? FreeMem?
i oczywiście na koniec przypisanie:

Code: Select all

P := NiL;

Mam wątpliwości co do korzystania z tych funkcji, to będzie moje pierwsze zmierzenie się z takim problemem.
Bardzo proszę o wskazanie drogi.


Wróć do „Inne języki programowania”

Kto jest online

Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 3 gości