ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
34.79k stars 2.54k forks source link

process.Child: memory leak when child receive a SIGTTIN signal #18572

Closed perillo closed 9 months ago

perillo commented 9 months ago

Zig Version

0.12.0-dev.2150+63de8a598

Steps to Reproduce and Observed Behavior

This example has been adapted from https://ziggit.dev/t/how-to-really-trigger-term-stopped-term-unknown-for-std-childprocess/.

The purpose of the test is to make Child.run return the Stopped error.

const std = @import("std");
const log = std.log;
const process = std.process;
const os = std.os;
const testing = std.testing;

test {
    const cmd = "read&";
    const argv: []const []const u8 = &.{ "bash", "-i", "-c", cmd };
    const rr = try process.Child.run(.{
        .argv = argv,
        .allocator = testing.allocator,
    });

    switch (rr.term) {
        .Exited => |ret| {
            log.debug("Command exit with {d}!", .{ret});
        },
        .Signal => |ret| {
            log.debug("Command exited with signal {d}! Error!", .{ret});
        },
        .Stopped => |ret| {
            log.debug("Command stopped with {d}! Error!", .{ret});
        },
        .Unknown => |ret| {
            log.debug("Command exited with unknown reason {d}! Error!", .{ret});
        },
    }
}
$ zig test debug-sigstop.zig
Test [1/1] test_0... [gpa] (err): memory address 0x7f961e789000 leaked: 
/home/manlio/.local/share/sdk/zig/master/lib/std/fifo.zig:125:54: 0x103870e in ensureTotalCapacity (test)
                self.buf = try self.allocator.realloc(self.buf, new_size);
                                                     ^
/home/manlio/.local/share/sdk/zig/master/lib/std/fifo.zig:135:48: 0x1030b5c in ensureUnusedCapacity (test)
            return try self.ensureTotalCapacity(math.add(usize, self.count, size) catch return error.OutOfMemory);
                                               ^
/home/manlio/.local/share/sdk/zig/master/lib/std/fifo.zig:261:42: 0x10309e3 in writableWithSize (test)
            try self.ensureUnusedCapacity(size);
                                         ^
/home/manlio/.local/share/sdk/zig/master/lib/std/io.zig:591:55: 0x1031d66 in pollPosix (test)
                    const buf = try q.writableWithSize(bump_amt);
                                                      ^
/home/manlio/.local/share/sdk/zig/master/lib/std/io.zig:488:33: 0x10324d0 in poll (test)
                return pollPosix(self);
                                ^
/home/manlio/.local/share/sdk/zig/master/lib/std/child_process.zig:309:31: 0x10327a4 in collectOutput (test)
        while (try poller.poll()) {
                              ^
/home/manlio/.local/share/sdk/zig/master/lib/std/child_process.zig:353:32: 0x102b874 in run (test)
        try child.collectOutput(&stdout, &stderr, args.max_output_bytes);
                               ^
/home/manlio/.local/share/sdk/zig/master/lib/debug-sigstop.zig:11:37: 0x102b5fc in test_0 (test)
/home/manlio/.local/share/sdk/zig/master/lib/test_runner.zig:181:28: 0x103fa00 in mainTerminal (test)
        } else test_fn.func();
                           ^
/home/manlio/.local/share/sdk/zig/master/lib/test_runner.zig:36:28: 0x10348ba in main (test)
        return mainTerminal();
                           ^

All 1 tests passed.
1 errors were logged.
1 tests leaked memory.
error: the following test command failed with exit code 1:
/home/manlio/.cache/zig/o/594f82a5bf9925d7f103102e682453a6/test

Expected Behavior

No idea.

See also #10776 and #18548.

jacobly0 commented 9 months ago

This code is missing this after calling process.Child.run:

    defer {
        testing.allocator.free(rr.stdout);
        testing.allocator.free(rr.stderr);
    }

As stated in the documentation:

/// If it succeeds, the caller owns result.stdout and result.stderr memory.
perillo commented 9 months ago

Thanks and sorry for the nuisance. I forgot that the default stdout and stderr behavior is .Pipe.