Want to see memory leak but i couldn't, I intentionally created a memory leak, but i couldn't see the leak, why?

main.zig

const std = @import("std");
const c = @import("c");

const Io = std.Io;
const crypto = std.crypto;

const Parser = @import("bencode.zig").Parser;
const Server = @import("tracker_client.zig").Server;

var global_server_ptr: ?*Server = null;

pub var should_run: std.atomic.Value(bool) = .init(true);

pub fn interrupt_handler(sig: std.os.linux.SIG) callconv(.c) void {
    switch (sig) {
        .INT => {
            should_run.store(false, .release);

            if (global_server_ptr) |srv| {
                // Forcefully stop the server right now!
                srv.listener.socket.close(srv.io);
            }
        },
        else => {},
    }
}

pub fn main(init: std.process.Init) !void {
    // var debug_allocator = std.heap.DebugAllocator(.{ .thread_safe = true }).init;
    // defer _ = debug_allocator.deinit();

    // const allocator = debug_allocator.allocator();
    const allocator = init.gpa;

    const io = init.io;

    const file = try std.Io.Dir.openFile(.cwd(), io, "aapun.txt.torrent", .{});
    defer file.close(io);

    const file_stats = try file.stat(io);
    const file_size: usize = file_stats.size;

    var file_reader_buffer: [512]u8 = undefined;
    var file_reader = file.reader(io, &file_reader_buffer);
    var file_iface = &file_reader.interface;

    const buffer = try allocator.alloc(u8, file_size);
    defer allocator.free(buffer);

    file_iface.readSliceAll(buffer) catch |err| switch (err) {
        error.ReadFailed => {
            return error.ReadFailed;
        },
        error.EndOfStream => {
            return error.EndOfStream;
        },
    };

    std.debug.print("\n\n{s}'\n\n", .{buffer});

    var parser = Parser.init(buffer, allocator);
    var value = try parser.parse_value();
    defer Parser.deinit(&value, allocator);
    Parser.print_value(value);
    if (Parser.get_dict_val(value.data.dict, "info")) |val| {
        const info_bytes = buffer[val.span.start..val.span.end];
        // std.debug.print("\nRaw Info Dictonary: \n{s}\n", .{info_bytes});

        var hash: [32]u8 = undefined;
        crypto.hash.sha2.Sha256.hash(info_bytes, &hash, .{});

        std.debug.print("\nInfo Hash: \n", .{});

        for (hash) |byte| {
            std.debug.print("{x:0>2}", .{byte});
        }

        std.debug.print("\n", .{});
    }

    const sigaction: std.os.linux.Sigaction = .{
        .flags = 0,
        .handler = .{
            .handler = &interrupt_handler,
        },
        .mask = @splat(0),
    };

    switch (std.os.linux.errno(std.os.linux.sigaction(.INT, &sigaction, null))) {
        .SUCCESS => {}, // no error
        else => |err| std.log.err("{t}: Sigaction interrupt handler failure!", .{err}),
    }

    var server = try Server.init("127.0.0.1", 9001, allocator, io);
    defer server.deinit();

    global_server_ptr = &server;
    defer global_server_ptr = null;

    try server.accept_connection();
}

and server module

const std = @import("std");
const c = @import("c");
const main_root = @import("main.zig");

pub const Server = struct {
    allocator: std.mem.Allocator,
    io: std.Io,
    listener: std.Io.net.Server,
    group: std.Io.Group,

    pub fn init(server_url: []const u8, port: u16, allocator: std.mem.Allocator, io: std.Io) !Server {
        const ip_addr = try std.Io.net.IpAddress.parse(server_url, port);

        return .{
            .allocator = allocator,
            .io = io,
            .listener = try ip_addr.listen(io, .{ .reuse_address = true }),
            .group = .init,
        };
    }

    pub fn deinit(self: *Server) void {
        std.debug.print("\n\nServer Shutdown Gracefully.\n\n", .{});

        self.group.cancel(self.io);
        self.group.await(self.io) catch |err| {
            std.debug.print("Group await error: {}\n", .{err});
        };
    }

    pub fn accept_connection(self: *Server) !void {
        while (main_root.should_run.load(.acquire)) {
            const client_stream = self.listener.accept(self.io) catch |err| {
                if (!main_root.should_run.load(.monotonic)) break;
                std.debug.print("Accept error: {}\n", .{err});
                continue;
            };

            self.group.concurrent(self.io, handle_client, .{ self.io, self.allocator, client_stream }) catch |err| {
                std.debug.print("Failed to spawn background task: {}\n", .{err});
                client_stream.close(self.io);
                continue;
            };
        }

        self.listener.socket.close(self.io);
        std.debug.print("\nServer loop exited.", .{});
    }

    fn handle_client(io: std.Io, allocator: std.mem.Allocator, stream: std.Io.net.Stream) std.Io.Cancelable!void {
        defer stream.close(io);

        _ = allocator.alloc(u8, 4096) catch |err| {
            std.debug.print("err: {any}", .{err});
            return std.Io.Cancelable.Canceled;
        };
        _ = allocator.alloc(u8, 4096) catch |err| {
            std.debug.print("err: {any}", .{err});
            return std.Io.Cancelable.Canceled;
        };
        // _ = allocator.alloc(u8, 4096) catch |err| {
        //     std.debug.print("err: {any}", .{err});
        //     return std.Io.Cancelable.Canceled;
        // };
        // _ = allocator.alloc(u8, 4096) catch |err| {
        //     std.debug.print("err: {any}", .{err});
        //     return std.Io.Cancelable.Canceled;
        // };
        // _ = allocator.alloc(u8, 4096) catch |err| {
        //     std.debug.print("err: {any}", .{err});
        //     return std.Io.Cancelable.Canceled;
        // };

        std.debug.print("New Client Connectedddd {any}:{d}\n", .{
            stream.socket.address.ip4.bytes, stream.socket.address.ip4.port,
        });

        // TODO:
        // Read request
        // Process request
        // Write response

        io.sleep(std.Io.Duration.fromSeconds(5), .awake) catch |err| {
            std.debug.print("Task sleep interrupted or canceled: {}\n", .{err});
            return;
        };

        std.debug.print("Client Disconnected\n", .{});
        std.debug.print("Client Disconnected cleanly\n", .{});
    }
};

Where is the leak meant to be, and how are you intending to observe it?

If you’re just expecting gpa to print leaks - you have to call gpa.deinit() at exit

1 Like

They are using the allocator from juicy main which does that for them, they can’t do it themselves anyway as they only get the interface.

The leak seems to be in server.handle_client, which is called by sever.accept_connection.

2 reasons I can think of that would result in no leak being logged.

  1. you are not trying to connect to the server, so handle_client never gets called so no memory gets leaked
  2. you are using an unsafe build mode, where juicy main chooses an allocator that does not detect leaks.

I use this to run the program:

zig build -Doptimize=ReleaseSafe run

otherwise i am getting .sframe register error

release safe still has leak checking.

Post the full error, this looks like a more complicated problem

Wait a sec..

torrent main  ❯ zig build run
run
└─ run exe torrent
   └─ install
      └─ install torrent
         └─ compile exe torrent Debug native 2 errors
error: fatal linker error: unhandled relocation type R_X86_64_PC64 at offset 0x1c
    note: in /usr/lib/gcc/x86_64-pc-linux-gnu/16.1.1/../../../../lib/crt1.o:.sframe
error: fatal linker error: unhandled relocation type R_X86_64_PC64 at offset 0x2c
    note: in /usr/lib/gcc/x86_64-pc-linux-gnu/16.1.1/../../../../lib/crt1.o:.sframe
error: 2 compilation errors
failed command: /home/itsmonday/.local/share/mise/installs/zig/0.16.0/zig build-exe -ODebug --dep torrent --dep c "-Mroot=/run/media/itsmonday/Queenines reveng/torrent/src/main.zig" "-Mtorrent=/run/media/itsmonday/Queenines reveng/torrent/src/main.zig" -ODebug -Mc=.zig-cache/o/2e24a39d0c683e55f77845d377b054a9/c.zig -lc --cache-dir .zig-cache --global-cache-dir /home/itsmonday/.cache/zig --name torrent --zig-lib-dir /home/itsmonday/.local/share/mise/installs/zig/0.16.0/lib/ --listen=-

See i pasted the error

this is a bug in the compiler, it has already been fixed on master, but you should be able to get around it by forcing llvm with .use_llvm = true in the exe options in your build.zig

1 Like

I used this, but then i get differnet error

send the error..

2 Likes

See This

in the terminal

New Client Connectedddd { 127, 0, 0, 1 }:37984
Client Disconnected
Client Disconnected cleanly

and from another terminal

torrent main  ✗ telnet localhost 9001
Trying ::1...
Connection failed: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
^C^]
telnet> quit
Connection closed.

The leak the i produced intentionally i wanted to see this

This is the repo:

You can verify by running it, in your own

Did you check it?

There are multiple issues that are overlapping to make this more difficult.

You print the torrent file as though it is completely text, but it is not all text!

Unfortunately some of the non text data happens to be some sequences that make the terminal obscure any following output, which reappears after running it again, and (in my terminal) somehow can jump to another terminal pane if its focused. That was really confusing :sob:

You are linking libc, which means you will only get leak checking in debug mode.

Logging the leak only happens when deinit or checkLeaks is called on the debug allocator, since you use the one from init, that happens after main.
However, your signal handler closes the socket, which triggers a panic in the listener.accept implementation. Panics abort the program, so the code after main never runs and you never get your leak!

Your signal handler should instead cancel the task that is accepting connections; unfortunately, you can’t cancel the main task, so you need to make another task to accept connections, this can just be async since you shouldn’t need concurrency relative to the main task.

When running through the build system, your program is a child process of zig, which has some annoying, but not erronious, behavior

  1. ctrl-c interupts zig, which does forward to your program, but because zig was its parent process it may kill your program if you take to long to exit. That timeout is generous, I tried a handler that just ignores the signal, which should prevent you from killing the program, but being a child process negated that.
  2. zig exiting in the middle of your program printing info/leak logs messes up the output a little, just a minor annoyance

you can get around both by running the program directly ./zig-out/bin/torrent btw you can ctr-\ to forcefully terminate, instead of ctrl-cs polite request

6 Likes
var debug_allocator = std.heap.DebugAllocator(.{
        .thread_safe = true,
        .verbose_log = true,
    }).init;
    defer _ = debug_allocator.deinit();

    const allocator = debug_allocator.allocator();

instead of using from juicy main

const allocator = init.gpa

as i enabled the verbose_log = true
I got that leak details

info(DebugAllocator): small alloc 4096 bytes at 0x7fe11eaa0000
info(DebugAllocator): small alloc 4096 bytes at 0x7fe11eaa1000
New Client Connectedddd { 127, 0, 0, 1 }:48078
Client Disconnected
Client Disconnected cleanly
info(DebugAllocator): small alloc 4096 bytes at 0x7fe11eaa2000
info(DebugAllocator): small alloc 4096 bytes at 0x7fe11eaa3000
New Client Connectedddd { 127, 0, 0, 1 }:38446
Client Disconnected
Client Disconnected cleanly
info(DebugAllocator): small alloc 4096 bytes at 0x7fe11eaa4000
info(DebugAllocator): small alloc 4096 bytes at 0x7fe11eaa5000
New Client Connectedddd { 127, 0, 0, 1 }:38450
Client Disconnected
Client Disconnected cleanly
info(DebugAllocator): small alloc 4096 bytes at 0x7fe11eaa6000
info(DebugAllocator): small alloc 4096 bytes at 0x7fe11eaa7000
New Client Connectedddd { 127, 0, 0, 1 }:49858
Client Disconnected
Client Disconnected cleanly
^C
Server loop exited.

Server Shutdown Gracefully.


 ByteString: free
 Integer: free
 Integer: free
 ByteString: free
info(DebugAllocator): small free 168 bytes at u8@7fe11eac0100
info(DebugAllocator): small free 24 bytes at u8@7fe11eae0080

 Dictonary: freeinfo(DebugAllocator): small free 168 bytes at u8@7fe11eac0200
info(DebugAllocator): small free 24 bytes at u8@7fe11eae0060

 Dictonary: freeinfo(DebugAllocator): small free 168 bytes at u8@7fe11eac0300
info(DebugAllocator): small free 24 bytes at u8@7fe11eae0040

 Dictonary: free
 Integer: free
 Integer: free
 ByteString: free
 Integer: free
 ByteString: freeinfo(DebugAllocator): small free 448 bytes at u8@7fe11eb00200
info(DebugAllocator): small free 24 bytes at u8@7fe11eae0020

 Dictonary: freeinfo(DebugAllocator): small free 24 bytes at u8@7fe11eae00a0

 Dictonary: freeinfo(DebugAllocator): small free 448 bytes at u8@7fe11eb00400
info(DebugAllocator): small free 24 bytes at u8@7fe11eae0000

 Dictonary: freeinfo(DebugAllocator): small free 280 bytes at u8@7fe11eb00000
error(DebugAllocator): memory address 0x7fe11eaa0000 leaked:
(empty stack trace)

error(DebugAllocator): memory address 0x7fe11eaa1000 leaked:
(empty stack trace)

error(DebugAllocator): memory address 0x7fe11eaa2000 leaked:
(empty stack trace)

error(DebugAllocator): memory address 0x7fe11eaa3000 leaked:
(empty stack trace)

error(DebugAllocator): memory address 0x7fe11eaa4000 leaked:
(empty stack trace)

error(DebugAllocator): memory address 0x7fe11eaa5000 leaked:
(empty stack trace)

error(DebugAllocator): memory address 0x7fe11eaa6000 leaked:
(empty stack trace)

error(DebugAllocator): memory address 0x7fe11eaa7000 leaked:
(empty stack trace)