Memory leak error!

const allocator = std.heap.c_allocator;
$zig test tests-1.zig -target aarch64-linux-musl -lc

works without any error !

However, after change allocator to

const allocator = std.testing.allocator;
$zig test tests-1.zig -target aarch64-linux-musl -lc
  [gpa] (err): memory address 0x7680480000 leaked:
   /data/data/com.termux/files/usr/lib/zig/lib/std/hash_map.zig:1477:53: 0x1128b9f in allocate (test)
const slice = try allocator.alignedAlloc(u8, max_align, total_size);
                                        ^                                       
  /data/data/com.termux/files/usr/lib/zig/lib/std/hash_map.zig:1434:29: 0x1115bc3 in grow (test)
            try map.allocate(allocator, new_cap);
                            ^                  
/data/data/com.termux/files/usr/lib/zig/lib/std/hash_map.zig:1295:30: 0x10fca3f in growIfNeeded (test) 
...

Regards!

std.heap.c_allocator doesn’t have leak detection, so the leak exists it just isn’t noticed.

With the std.testing.allocator the error becomes visible.

14 Likes

$./test

[gpa] (erro): memory address 0x77a9fe0000 leaked:                                             
/data/data/com.termux/files/usr/lib/zig/lib/std/hash_map.zig:1477:53: 0x111f35b in allocate (test)                                                       const slice = try allocator.alignedAlloc(u8, max_align, total_size);                                                                  ^                                         
/data/data/com.termux/files/usr/lib/zig/lib/std/hash_map.zig:1434:29: 0x110db77 in grow (test)            
try map.allocate(allocator, new_cap);                                                                         ^              
   /data/data/com.termux/files/usr/lib/zig/lib/std/hash_map.zig:1295:30: 0x10f4fdb in growIfNeeded (test)                                                       

try self.grow(allocator, capacityForSize(self.load() + new_count), ctx);                                   ^                 
/data/data/com.termux/files/usr/lib/zig/lib/std/hash_map.zig:1114:34: 0x10d6f6b in getOrPutContextAdapted__anon_12385 (test)
                                self.growIfNeeded(allocator, 1, ctx) catch |err| {                                                             ^             /data/data/com.termux/files/usr/lib/zig/lib/std/hash_map.zig:1099:56: 0x10bce0f in getOrPutContext (test)                                                const gop = try self.getOrPutContextAdapted(allocator, key, ctx, ctx);                                                                   ^                                   
  /data/data/com.termux/files/usr/lib/zig/lib/std/hash_map.zig:1025:52: 0x10b43eb in putContext (test)                                                     const result = try self.getOrPutContext(allocator, key, ctx); 
...```
...
$valgrind --leak-check=full test
...

…
==13379== HEAP SUMMARY: ==13379== in use at exit: 0 bytes in 0 blocks
==13379== total heap usage: 0 allocs, 0 frees, 0 bytes allocated ==13379==
==13379== All heap blocks were freed – no leaks are possible ==13379==
==13379== For lists of detected and suppressed errors, rerun with: -s ==13379== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) Aborted


 It is hard for me to figure the leak out. Any info is appreciated !

I have no idea how to help you unless you show the code of the actual test, or describe the problem in more detail.

1 Like
const std = @import("std");

const expectEqual = std.testing.expectEqual;
const expectEqualSlices = std.testing.expectEqualSlices;
const allocator = std.testing.allocator; //const allocator = std.heap.c_allocator;

const stdout = std.io.getStdOut().writer();
const print = std.debug.print;

test "Search" {
    tables.initAll(allocator);
    defer tables.deinitAll(allocator);

    var s: position.State = position.State{};
    var pos: position.Position = try position.Position.setFen(&s, position.start_fen);
    var move: types.Move = .none;
    var limits = interface.limits;
    limits.depth = 7;
    move = try search.iterativeDeepening(allocator, stdout, &pos, limits, evaluate.evaluateShannon);

    //move = try search.iterativeDeepening(allocator, stdout, &pos, limits, evaluate.evaluateTable);                                             try stdout.print("bestmove ", .{});

    try move.printUCI(stdout);
    try stdout.print("\n", .{});
}                    

The code is from a chess engine i used to study Zig!

What are you doing, why do you mess up the formatting I just fixed?
Do you write from some tiny screen phone, or what is going on?

It looks like it’s from a phone or tablet using termux.

1 Like

I donot know you are helping me to format it. I am using a phone to input it. please reformat it again, thank you!

1 Like

If I would have to guess, you are using some hashmap in this function that is not being deinitialized.

1 Like

In this case, I think valgrind is reporting on the test runner, not the test code. I don’t know for sure, but I would assume that the test runner correctly manages the memory and so Valgrind will not find any issues with the test runner.

1 Like

This looks like you have only posted part of the error message, there also should be stackframes about which allocation in your code was leaked.

1 Like

The original code is a bit longer. I choose what i think relative parts.

var root_moves: std.ArrayListUnmanaged(RootMove) = .empty;
const RootMove = struct {
    // ...
    pv: std.ArrayListUnmanaged(types.Move) = .empty,
};
var move_list: std.ArrayListUnmanaged(types.Move) = .empty;
defer move_list.deinit(allocator); // ...
for (root_moves.items) |*root_move| {
    root_move.pv.deinit(allocator);
}
root_moves.deinit(allocator); // <- added by me

Regards!

So those snippets you added don’t use a hashmap, they use ArrayLists which is a different data structure. Somewhere you are using a hashmap and that is the issue.

As a note, you can put your code as ```zig block it will do syntax highlighting.

bestmove a2a3                                                                                 Black to move                                                                                 fen: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - 1 1                                 zobrist: 0
[gpa] (err): memory address 0x77bf740000 leaked:  
 /data/data/com.termux/files/usr/lib/zig/lib/std/hash_map.zig:1477:53: 0x111f35b in allocate (test)
 const slice = try allocator.alignedAlloc(u8, max_align, total_size);                                                                         /data/data/com.termux/files/usr/lib/zig/lib/std/hash_map.zig:1434:29: 0x110db77 in grow (test)try map.allocate(allocator, new_cap);                                                         /data/data/com.termux/files/usr/lib/zig/lib/std/hash_map.zig:1295:30: 0x10f4fdb in growIfNeeded (test)   
 try self.grow(allocator, capacityForSize(self.load() + new_count), ctx); 
  /data/data/com.termux/files/usr/lib/zig/lib/std/hash_map.zig:1114:34: 0x10d6f6b in getOrPutContextAdapted__anon_12385 (test)                                 self.growIfNeeded(allocator, 1, ctx) catch |err| {
   /data/data/com.termux/files/usr/lib/zig/lib/std/hash_map.zig:1099:56: 0x10bce0f in getOrPutContext (test)                                      const gop = try self.getOrPutContextAdapted(allocator, key, ctx, ctx); 
 /data/data/com.termux/files/usr/lib/zig/lib/std/hash_map.zig:1025:52: 0x10b43eb in putContext (test) 
const result = try self.getOrPutContext(allocator, key, ctx);                                                                    /data/data/com.termux/files/usr/lib/zig/lib/std/hash_map.zig:1022:35: 0x10a10f7 in put (test) 
return self.putContext(allocator, key, value, undefined); 
/data/data/com.termux/files/home/z/chess/search.zig:392:63: 0x10a3c3b in abSearch__anon_4517 (test )

try tables.transposition_table.put(allocator, key, .{ score, current_depth - 1, move })  /data/data/com.termux/files/home/z/chess/search.zig:388:42: 0x10a3aaf in abSearch__anon_4517 (test) 
score = -try abSearch(allocator, NodeType.pv, ss + 1, pos, limits, eval, -beta, -alpha, current_depth - 1 + @intFromBool(pos.state.checkers != 0), false); 
 /data/data/com.termux/files/home/z/chess/search.zig:388:42: 0x10a3aaf in abSearch__anon_4517 (test) 
 score = -try abSearch(allocator, NodeType.pv, ss + 1, pos, limits, eval, -beta, -alpha, current_depth - 1 + @intFromBool(pos.state.checkers != 0), false);         

All 1 tests passed.
1 errors were logged.
1 tests leaked memory.

Regards

Seems like your deinitAll function doesn’t clear tables.transposition_table try adding self.transposition_table.clearAndFree(allocator); to that function.

Or post the code of the deinitAll function.

1 Like
 pub fn deinitAll(allocator: std.mem.Allocator) void {
var sq: u8 = Square.a1.index(); 
while (sq <= Square.h8.index()) : (sq += 1) {
        moves_bishop[sq].deinit(allocator);
        moves_rook[sq].deinit(allocator);
    }
}                   transposition_table.clearAndFree(allocator); // added as you suggested!

It seems transposition_table.deinit(allocator) works too ?

Great! memory leak disappear !

Where do you create/declare the transposition_table?

Maybe it would make more sense to put the call to transposition_table.deinit(allocator) somewhere else if it is a global variable (for example using defer), the benefit of clearAndFree is that it should reset the hash table to the empty state without making the hash table unusable, so if it is used as a caching table it just resets the cache without making it unusable (needing to create a new cache instance), and resetting the cache to empty should be enough to avoid memory leaks because empty hash tables don’t own any dynamically allocated memory.

So basically you have to decide where and why you want to deinit or clearAndFree the memory, it depends a bit on your api/implementation design.

2 Likes

Generally, when asking for help, you should distill the problem down to the smallest amount of code that generates the error, and then post the entire code sample and error trace- not just the part you think is relevant. If you knew what the problem was, you likely wouldn’t need to ask for help :slightly_smiling_face:

4 Likes