Zig TCP Server SIGINT Handling Issue
Overview
This document describes an issue with two Zig TCP server implementations (server.zig and server1.zig) designed for learning purposes. Both servers listen on port 8080 and handle SIGINT (Ctrl+C) to exit gracefully, but they exhibit different behaviors when interrupted. The goal is to understand and resolve the issue where server.zig hangs on Ctrl+C while server1.zig exits cleanly.
Problem Description
Both programs set up a TCP server that:
- Listens on
127.0.0.1:8080. - Uses a
controller: boolflag to manage the event loop. - Registers a SIGINT handler to set
controller = falseand print a shutdown message. - Accepts client connections in a
while(controller)loop.
However, their behavior on Ctrl+C differs:
server.zig: Prints the shutdown message multiple times but does not exit, hanging in theacceptcall.server1.zig: Prints the shutdown message once and exits cleanly.
Code Listings
server.zig
This implementation uses Zig’s std.posix API for socket operations.
///epoll server implementation for learning purposes
const std = @import("std");
const linux = std.os.linux;
const posix = std.posix;
const net = std.net;
// server configurations
const port: u16 = 8080;
const tpe: u32 = posix.SOCK.STREAM;
const protocol = posix.IPPROTO.TCP;
const backlog: u31 = 128;
// controller controls the event-loop
var controller: bool = true;
pub fn handleCtrlC(signum: i32) callconv(.C) void {
controller = false;
std.debug.print("\n[!] Caught Ctrl+C (signal {}), Shutting down...\n", .{signum});
}
pub fn main() !void {
// signal handling
var sa = linux.Sigaction{
.handler = .{ .handler = handleCtrlC },
.mask = linux.sigemptyset(),
.flags = 0,
};
_ = linux.sigaction(linux.SIG.INT, &sa, null);
const address = try std.net.Address.resolveIp("127.0.0.1", port);
// socket configuration
const listener = try posix.socket(address.any.family, tpe, protocol);
defer posix.close(listener);
// binding socket with the port
try posix.bind(listener, &address.any, address.getOsSockLen());
// listen in the background
try posix.listen(listener, backlog);
std.debug.print("[+] Server listening on port {d}\n", .{port});
// event-loop
while(controller) {
std.debug.print("[*] Waiting for client...\n", .{});
var clientAddr: net.Address = undefined;
var clientAddrLen: posix.socklen_t = @sizeOf(net.Address);
// accept a new connection
const client = try posix.accept(listener, &clientAddr.any, &clientAddrLen, 0);
defer posix.close(client);
std.debug.print("[✓] Client Connected \n", .{});
}
std.debug.print("[✓] Server stopped cleanly.\n", .{});
}
Output:
$ zig build-exe server.zig
$ ./server
[+] Server listening on port 8080
[*] Waiting for client...
^C
[!] Caught Ctrl+C (signal 2), Shutting down...
^C
[!] Caught Ctrl+C (signal 2), Shutting down...
^C
[!] Caught Ctrl+C (signal 2), Shutting down...
^C
[!] Caught Ctrl+C (signal 2), Shutting down...
...
Observation: The server prints the shutdown message repeatedly but does not exit, requiring multiple Ctrl+C presses and still hanging.
server1.zig
This implementation uses Zig’s lower-level std.os.linux API for socket operations.
///epoll server implementation for learning purposes
const std = @import("std");
const os = std.os.linux;
var controller: bool = true;
const port: u16 = 8080;
pub fn handleCtrlC(signum: i32) callconv(.C) void {
controller = false;
std.debug.print("\n[!] Caught Ctrl+C (signal {}), Shutting down...\n", .{signum});
}
pub fn main() !void {
const domain = os.AF.INET;
const socket = os.SOCK.STREAM;
const protocol: u32 = 0;
// signal handling
var sa = os.Sigaction{
.handler = .{ .handler = handleCtrlC },
.mask = os.sigemptyset(),
.flags = 0,
};
_ = os.sigaction(os.SIG.INT, &sa, null);
// socket configuration
const serverFd = os.socket(domain, socket, protocol);
defer _ = os.close(@as(i32, @intCast(serverFd)));
const addr = os.sockaddr.in{
.family = os.AF.INET,
.port = port,
.addr = 0,
.zero = [_]u8{0} ** 8,
};
const len: os.socket_t = @sizeOf(os.sockaddr.in);
// binding socket with the port
_ = os.bind(
@as(i32, @intCast(serverFd)),
@as(*const os.sockaddr, @ptrCast(&addr)),
len
);
_ = os.listen(@as(i32, @intCast(serverFd)), os.SOMAXCONN);
while(controller) {
var client_addr: os.sockaddr.in = undefined;
var client_addr_len: os.socklen_t = @sizeOf(os.sockaddr.in);
// Accept a new connection
const clientFd = os.accept(
@as(i32, @intCast(serverFd)),
@as(*os.sockaddr, @ptrCast(&client_addr)),
&client_addr_len
);
defer {
if (clientFd < std.math.maxInt(i32)) {
_ = os.close(@as(i32, @intCast(clientFd)));
}
}
}
}
Output:
$ zig build-exe server1.zig
$ ./server1
^C
[!] Caught Ctrl+C (signal 2), Shutting down...
Observation: The server exits cleanly after a single Ctrl+C press.
Environment
- OS: Linux
- Zig Version: Not specified (assumed recent, e.g., 0.15.0 or later)
How can i exit by pressing ctrl + c in case of posix API ??