#how to make http.Server exit when ctrl+c is pressed

1 messages · Page 1 of 1 (latest)

fleet mango
#

the goal is for the following program to print "done" after ctrl+c is pressed (i really just want gpa.deinit() to happen so i can see if i leaked any memory). the problem seems to be that server.accept() is blocking. if i remove the http server code, it works and "done" is printed.

warning: if you run this program, ctrl+c does nothing and you'll have to kill it manually.

i've tried many things like moving gpa.deinit() into the signal handler (crashes), using an atomic store, changing the flags and mask. but nothing seems to work.

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

var should_run = true;

pub fn main() !void {
    // handle ctrl+c
    try std.os.sigaction(std.os.SIG.INT, &.{
        .handler = .{
            .handler = struct {
                fn func(sig: c_int) callconv(.C) void {
                    _ = sig;
                    // @atomicStore(bool, &should_run, false, std.builtin.AtomicOrder.Monotonic);
                    should_run = false;
                }
            }.func,
        },
        .mask = std.os.empty_sigset,
        .flags = 0,
    }, null);

    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const alloc = gpa.allocator();

    // init server
    const address = try std.net.Ip4Address.parse("127.0.0.1", 4000);
    var server = std.http.Server.init(
        alloc,
        .{ .reuse_port = true, .reuse_address = true },
    );
    defer server.deinit();
    try server.listen(.{ .in = address });
    std.debug.print("\nlistening on http://{}\n", .{address});

    while (should_run) {
        var res = try server.accept(.{
            .allocator = alloc,
            .header_strategy = .{ .dynamic = std.mem.page_size },
        });
        defer res.deinit();
        defer _ = res.reset();
        try res.wait();
        res.transfer_encoding = .chunked;
        try res.headers.append("content-type", "application/json");
        try res.do();
        _ = try res.writer().write("foobar");
        try res.finish();
    }
    std.debug.print("\ndone\n", .{});
}
#

really, all i want to know is how to tell the server to quit after calling accept()

#

i also tried closing and deiniting the server.socket but that doesn't work either

shrewd mason
#

Does ctrl c work as expected if you move the web server section into its own thread ?

dense wadi
#

closing the socket doesn't stop it?

fleet mango
#

i haven't tried moving to its own thread. will try that soon.

fleet mango
dense wadi
#

I think you need to shutdown the socket

#

not close it

#

closing the socket is a race condition since someone else could open up a file descriptor

#

try std.os.shutdown(server.socket, .both)

fleet mango
#

thanks! let me try...

dense wadi
#

it should return an error at the server.

#

but not unreachable panic

fleet mango
#

well thats an improvement:

/tmp $ zig build-exe tmp.zig && ./tmp

listening on http://127.0.0.1:4000
^Csig=2
error: SocketNotListening
???:?:?: 0x27a21c in accept (tmp)
???:?:?: 0x23f200 in accept (tmp)
???:?:?: 0x238875 in accept (tmp)
???:?:?: 0x23818c in main (tmp)
#

i'll just have to catch that error and break 👍

dense wadi
#

yeah the problem you were running into is that zig has this unreachable thing in linux EBAF

fleet mango
#

ayy! success

/tmp $ zig build-exe tmp.zig && ./tmp

listening on http://127.0.0.1:4000
^Csig=2

done
#

thanks a log @dense wadi zeroLike

dense wadi
#

logs thanked

fleet mango
#

haha 😅 oops.

#

here is some working code incase anyone else runs into this:

const std = @import("std");

var serverptr: *std.http.Server = undefined;

pub fn main() !void {
    // handle ctrl+c
    try std.os.sigaction(std.os.SIG.INT, &.{
        .handler = .{
            .handler = struct {
                fn func(sig: c_int) callconv(.C) void {
                    std.debug.print("sig={}\n", .{sig});
                    std.os.shutdown(serverptr.socket.sockfd.?, .both) catch unreachable;
                }
            }.func,
        },
        .mask = std.os.empty_sigset,
        .flags = 0,
    }, null);

    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const alloc = gpa.allocator();

    // init server
    const address = try std.net.Ip4Address.parse("127.0.0.1", 4000);
    var server = std.http.Server.init(
        alloc,
        .{ .reuse_port = true, .reuse_address = true },
    );
    defer server.deinit();
    serverptr = &server;
    try server.listen(.{ .in = address });
    std.debug.print("\nlistening on http://{}\n", .{address});

    while (true) {
        var res = server.accept(.{
            .allocator = alloc,
            .header_strategy = .{ .dynamic = std.mem.page_size },
        }) catch |e| switch (e) {
            error.SocketNotListening => break,
            else => return e,
        };
        defer res.deinit();
        defer _ = res.reset();
        try res.wait();
        res.transfer_encoding = .chunked;
        try res.headers.append("content-type", "application/json");
        try res.do();
        _ = try res.writer().write("foobar");
        try res.finish();
    }
    std.debug.print("\ndone\n", .{});
}