Forum > General
Finishing thread does not release Virtual memory
Martin_fr:
Ah, I forgot something, for valgrind you need to use "CMem". Afaik if you did specify the "... for valgrind (-gv)" option, then that is done automatically.
Otherwise you have to do that yourself (and then no heaptrc)
--- Quote from: jollytall on January 11, 2022, 07:28:25 pm ---==27332== still reachable: 15,497 bytes in 149 blocks
==27332== suppressed: 0 bytes in 0 blocks
--- End quote ---
Afaik, "still reachable" is memory that was released, but only when the program terminated.
Some of this would be expected. Question is, does the number grow, if the program runs for longer? I.e. does your app accumulate more and more memory that it only frees at exit?
Mind that leaks can be in libraries too. 10 years ago, I had a webserver with mem issues, turned out openssl was leaking (mind you 10 years, so probably not what you have now).
--- Quote ---I need more time to understand it, but most of them are in libcrypto (openssl) and libc. I don't know what I can do with them, and whether it can keep the reserved large virtual memory locked for few Kbyte of still
--- End quote ---
Well, the first issue is, if the grow (in count and accumulated byte-size), if you run the app for longer.
It is very well possible that some code "leaks" something when it is initialized. If that memory is indeed needed for the entire run time, and if it is only taken just once, then that is not an issue.
But if over time, it gets more...
It can be an issue in the library, but it can also be how it is called...
engkin:
--- Quote from: jollytall on January 11, 2022, 07:28:25 pm ---I need more time to understand it, but most of them are in libcrypto (openssl) and libc
--- End quote ---
You might have a leak in your usage of libCrypto. Any function that gives a big number, for instance,, you are supposed to free the memory when done with that number. Like BN_bin2bn, SRP_calc_u..etc
Jonas Maebe:
--- Quote from: jollytall on January 11, 2022, 12:57:51 pm ---@Jonas
Thanks, it might be leading to the solution. Two problems:
I do not use TThread, but the one level lower BeginThread, so I would need to call whatever has to be called manually. It would not be a problem if I knew what to call. I also have my own Wait implementation, so that is not a problem either.
I check the TThread implementation and FFreeOnTermiante I found only once in ThreadProc, and it calls Free (i.e. Destroy), but in Destroy I did not find what to do. Can you help?
--- End quote ---
We indeed don't have the functional equivalent for detaching a thread with the procedural interface. However, just to make sure, are you calling waitforthreadterminate on all of (non-detached) your threads? Otherwise they will indeed linger on forever.
jollytall:
Thanks for the many useful comments, replies. I try to answer them and report my current progress:
--- Quote from: Martin_fr on January 11, 2022, 10:25:13 pm ---Ah, I forgot something, for valgrind you need to use "CMem". Afaik if you did specify the "... for valgrind (-gv)" option, then that is done automatically.
Otherwise you have to do that yourself (and then no heaptrc)
--- End quote ---
Yes, you are right. Specifying -gv adds cmem. Trying to add cmem manually results in a Duplicate Identifier error.
While we are at this point, a bit OFF spin-off question, that bothers me. It is always said (and indeed necessary) to put cmem and cthread to the top of the uses lists. I guess it is probably because their Initialization section does some initialization that is needed before other units start to initialize themselves. However I also thought that some of these special units redeclare some methods to provide extra functionality. The issue is that when two units have the same method then always the last one is used not the first. How is it managed for these special units, or they do not overwrite any existing method?
--- Quote from: Martin_fr on January 11, 2022, 10:25:13 pm ---Afaik, "still reachable" is memory that was released, but only when the program terminated.
Some of this would be expected. Question is, does the number grow, if the program runs for longer? I.e. does your app accumulate more and more memory that it only frees at exit?
--- End quote ---
No, this is not the case. Actually, this is only possible if one makes some special efforts, e.g. a dynamic array with pointers or linked objects, but I use none of them. Under "normal" circumstances it cannot happen, because by definition "Still reachable" mean that their pointer (i.e. the variable holding that pointer) still points to the reserved memory area. Having a fix number of variables mean that the amount of Still reachable cannot grow unlimited.
To make things even stranger, I analyzed my program a lot and found that when I only start and stop it, it has 13 blocks (5243 bytes) still reachable, but if I use it and exit after that, then this number goes down to 9 (3597). I am trying to check where the difference comes from.
--- Quote from: Martin_fr on January 11, 2022, 10:25:13 pm ---Mind that leaks can be in libraries too. 10 years ago, I had a webserver with mem issues, turned out openssl was leaking (mind you 10 years, so probably not what you have now).
--- End quote ---
The openssl problem I found. What happened was, I had a class level variable to store the SSL Context. When a new thread (object running in the thread) was created, I checked if the class var was already set or not. If not, it was initialized. The problem was that threads were started too close to each other without control to make it thread safe and so the same initialization was called multiple times. Adding a CriticalSection solved it.
--- Quote from: Martin_fr on January 11, 2022, 10:25:13 pm ---Well, the first issue is, if the grow (in count and accumulated byte-size), if you run the app for longer.
It is very well possible that some code "leaks" something when it is initialized. If that memory is indeed needed for the entire run time, and if it is only taken just once, then that is not an issue.
But if over time, it gets more...
It can be an issue in the library, but it can also be how it is called...
--- End quote ---
I could make a minimal example (see later) to show what grows and how.
--- Quote from: engkin on January 11, 2022, 11:12:02 pm ---You might have a leak in your usage of libCrypto. Any function that gives a big number, for instance,, you are supposed to free the memory when done with that number. Like BN_bin2bn, SRP_calc_u..etc
--- End quote ---
Thanks God it was not the case. It is only that it was started multiple times parallel without thread control (see above).
--- Quote from: Thaddy on January 11, 2022, 10:02:24 pm ---Wait..wait..waitfor! Use waitfor as already mentioned.
--- End quote ---
--- Quote from: Jonas Maebe on January 12, 2022, 08:18:22 am ---We indeed don't have the functional equivalent for detaching a thread with the procedural interface. However, just to make sure, are you calling waitforthreadterminate on all of (non-detached) your threads? Otherwise they will indeed linger on forever.
--- End quote ---
Where shall I put it? See the example below that (a) is copied (and further simplified) from the manual (https://www.freepascal.org/docs-html/prog/progse44.html) (b) demonstrates the problem and (c) does not have waitfor in it (maybe this is why it has the same problem).
So, the minimal program, I can show the problem with is:
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---program project1; {$mode objfpc} uses cthreads, sysutils; function InThread(p : pointer) : Int64; begin Sleep(Random(50)); result:=0; end; var i : longint;beginfor i:=1 to 100 do begin BeginThread(@InThread,nil); Sleep(Random(50)); end;Sleep(600);writeln('Finished');readln;end.After 100 run it uses 659.4MB VIRT, 2.6MB RES and 1.7MB SHR memory. If I run it 200 times, the memory usage goes up to 1059.8MB/3.4MB/1.7MB. With other words one extra thread blocks 4MB VIRT and 8KB RES.
Valgrind shows 100blocks (i.e. the number of threads) and 27200 bytes (i.e. 272byte/thread) possible lost.
These numbers bother me.
Where shall I put the waitfor? Will it stop this leak?
Thanks again, and sorry for the long post.
jollytall:
It seems I have the solution. Thank to all who helped.
The solution has two parts.
It is true, that there was a memory leak. Although it was reported as a "possibly lost" I think it was really a "still reachable". What happens, when the thread is created it gets a memory area. In the c library they reserve a bit larger space than needed for the array to have enough memory for the array length and reference count (-1, -2 index) and then moves the array pointer to the third block, i.e. element #0. This is why Valgrind reports it as a block that has a pointer pointing inside it (offset 16) and not at the first byte. So it thinks the pointer might have lost it.
But it did not. It was worse. The pointer was really there and "in use". As many of you pointed out, although the thread was gone, it left behind its pointer and the allocated memory. If the pointer had pointed to the first byte of the block, Valgrind would report it as "still reachable", clearly indicating that something is not released.
To release it, not Waitfor but EndThread was needed. In the above example, placing EndThread between lines 12 and 13 does the trick, and all memory leak is gone.
The other part of the story is the Virtual memory usage. It is clear that while the thread did not end correctly (as above), the virtual memory allocated was still reserved.
That is also true that an area once reserved is not necessarily released. The good news is though, that it is reused if available. So, what happens is, that the total VIRT memory used is capped by the maximum concurrent threads running at any point in the past. Apparently the OS manages it smart and when I tried to overload it with many threads the VIRT usage temporarily went up high, but then also decreased back to a reasonable level, i.e. the OS freed up some of the reserved memory addresses.
Navigation
[0] Message Index
[#] Next page
[*] Previous page