Tcl Source Code

View Ticket
Login
Ticket UUID: 3381371
Title: possible memory leak?
Type: Bug Created on: 2011-07-28 21:20:49
Submitter: Assigned to: vasiljevic
Subsystem: 80. Thread Package Severity: Minor
Priority: 5 Medium Last modified: 2026-10-16 13:20:24
Status: Closed Closed by: jan.nijtmans
Resolution: Fixed Closed on: 2026-10-16 13:20:24
Version: 8.6.18, 9.1.0
Description:
Using the latest ActiveState 8.6 and the Thread package on Ubuntu based distro, 32 bit, kernel 2.6.35-30
I ran the following script 4 times:

     time {set ::t [thread::create]; thread::release $::t} 10000

Here are the results from htop, one line for each invocation, first line is a state before the first run. 
VIRT  RES  SHR
15268 3296 1824
32884 4632 1964
33104 4936 1972
33328 5160 1972
33592 5384 1972

There seems to be 100-300 byte leak for each invocation of the script.
User Comments:
ferrieux added on 2011-07-29 04:41:49:
Could this just be history building up, in an interactive shell ?
If not, try increasing the [time] itercount and checking the effect on the slope.

msofer added on 2011-07-29 04:54:25:
To remove history problems do one of:
  - run the thing in an infinite loop (with an [after 10] in there), watch what top has to say about mem
  - do [proc history args {}] before the test

added on 2011-07-29 10:05:42:
Ran a new test, non-interactive, wrapped in a proc, 100000 iterations. The results are:

            VIRT    RES  SHR
START  32476 4192 1960
END      36284 7088 1960

ferrieux added on 2011-07-29 13:55:10:
OK thanks. Now what about 1*100000 vs. 10*10000 ? This to decide whether it leaks rather "per [times]" or "per [thread::*]" invocation.

added on 2011-07-30 01:21:11:
Here are the 10*10000 results:

32872 4652 1960
34120 4964 1960
34384 5244 1960
34664 5504 1960
34888 5732 1960
35116 5956 1960
35380 6204 1960
35584 6428 1960
35868 6672 1960
36092 6916 1960

vasiljevic added on 2011-07-30 02:05:44:
Try this:

time {set ::t [thread::create -joinable]; thread::release $t; thread::join $t} 10000

added on 2011-07-30 09:26:37:
Results for:
 time {set ::t [thread::create -joinable]; thread::release $t; thread::join $t} 10000

ran ten times --- 

24392 4284 1968
24616 4528 1968
24844 4752 1968
25068 4996 1968
25292 5220 1968
25516 5464 1968
25744 5692 1968
25968 5936 1968
26192 6160 1968
26416 6404 1968

dgp added on 2011-08-01 22:26:06:
any progress?

vasiljevic added on 2011-08-01 22:43:43:
Not yet. I do not think we can do anything here fast, as most probably
this is some deep finalization issue in Tcl library. The extension itself
does not leak at this point but the core could. I do not have resources at
the moment for deeper analysis but will throw a valgrind againt it just to
be 100% sure. If you aim at speedy releasing 2.6.7 I think you should not
wait.

vasiljevic added on 2011-08-02 02:37:56:
This is not the issue of the threading extension. I have tried the Tcl 8.4 core
with 2.6.6 thr-extension on Linux and Mac and double-checked with valgrind.
There are no leaks there at all. The Linux top shows pretty much same virtual
process size no matter now often I repeat the following command;

time {set ::t [thread::create -joinable]; thread::release $t; thread::join $t} 

I run if 1000000 times and then check vith valgrind and top and there is no
leak. I guess later (8.5/8.6) cores might not be leak-free; i haven't tested those releases as we are still using 8.4 core.

Unfortunately I missed the new move-fro-sf-cvs so I do not yet) khow where
are the new sources and I need to catch-up with that.

twylite added on 2011-08-02 04:25:06:
Using 8.6b2rc1 I can reproduce a leak on WIN32 using the time{} loop recommended by Zoran.  Only the Windows build leaks about 135,000 bytes per iteration.  Task Manager's thread count confirms that all additional threads have stopped.

For reference, a simple interp create/delete loop doesn't seem to leak memory, implying that this isn't an interp leak.

vasiljevic added on 2011-08-02 14:44:28:
I think the problem is in the Tcl finalization area. 
The interp itself is clean. The finalization isn't :-( 
Some years ago I contemplated to rewrite the 
finalization code using refcounts but this was a 
major undertaking and I never found time for it. 
The 8.4 seems to be more stable in this respect
which is why we still stick to it.  

In the meantime, creating a set of worker threads
upfront (or using tpool with fixed number of workers)
should keep the memory in the "reasonable" area.
Also, turning off the greedy threading allocator that
never releases memory to the system and using the
regular system malloc instead could also be a good 
move. I recall we turned that off some years ago and
I cannot recall our app becomming slower (yet it 
became a fifth of its original memory footprint!).

I think this could be a hard nut to crack. In some days
I will get more spare time to checkout the latest head
and see what all happened there since the last time 
I looked at it.

dgp added on 2011-08-02 19:54:31:
Using the usual tcltest harness for memory leak testing,
I tried this:

testConstraint memory [llength [info commands memory]]
test thread-8.1 {Bug 3381371} -constraints memory -setup {
    proc getbytes {} {
        set lines [split [memory info] "\n"]
        lindex $lines 3 3
    }
    package require Thread
} -body {
    set end [getbytes]
    for {set i 0} {$i < 5} {incr i} {
        set t [thread::create -joinable]
        thread::release $t
        thread::join $t
        set start $end
        set end [getbytes]
    }
    expr {$end - $start}
} -cleanup {
    rename getbytes {}
    unset -nocomplain i start end t
} -result 0

It shows no leak, using the trunk of Tcl & Thread.

What is it that's claimed to be leaking?  Apparently
it's not Tcl_Alloc'ed  memory?  Or is this a leak I 
should not be able to see testing on Linux?

serhiy.storchaka added on 2026-10-12 15:23:29:

On Linux I cannot reproduce this with 8.6.17, 9.0.3 and 9.1 and Thread 3.0.4: the memory stays flat over 100000 threads. On Windows each joinable thread which exits before it is joined leaks about 9 KB: TclSignalExitThread() creates the mutex of the join record in the exiting thread, after its allocator cache has been finalized, so a new cache is created and never freed.

Proposed fix in branch win-join-mutex-leak: create the mutex in TclRememberJoinableThread().


jan.nijtmans added on 2026-10-16 13:20:24:

Fixed in [b2f3ca31ad|trunk] and core-9-0-branch