karlseguin / websocket.zig

A websocket implementation for zig
MIT License
283 stars 25 forks source link

How to safely exit a client with a thread blocking on read()? #46

Open pfgithub opened 17 hours ago

pfgithub commented 17 hours ago

This code would implement a program that lets you enter a message to send to the websocket server and sends it on enter. When you exit the program by pressing enter on windows, it just hangs and never closes the websocket connection. How do you close the connection and stop an active blocking read() call on another thread?

const std = @import("std");
const ws = @import("websocket");

const App = struct { client: ws.Client };

pub fn main() !void {
    var gpa_backing = std.heap.GeneralPurposeAllocator(.{}){};
    defer std.debug.assert(gpa_backing.deinit() == .ok);
    const gpa = gpa_backing.allocator();

    var app: App = .{
        .client = try ws.Client.init(gpa, .{
            .port = 9224,
            .host = "localhost",
        }),
    };
    defer app.client.deinit();

    try app.client.handshake("/ws", .{
        .timeout_ms = 10_000,
        .headers = "Host: localhost:9224", // separate multiple headers with \r\n
    });

    const recv_thread = try std.Thread.spawn(.{}, recvThread, .{&app});
    defer recv_thread.join();

    std.log.info("wsnc. enter to exit", .{});

    // wait for enter key to be pressed
    while (true) {
        const msg = try std.io.getStdIn().reader().readUntilDelimiterAlloc(gpa, '\n', std.math.maxInt(usize));
        defer gpa.free(msg);
        if (std.mem.eql(u8, msg, "") or std.mem.eql(u8, msg, "\r")) {
            // exit
            break;
        }
        std.log.info("sending \"{}\"...", .{std.zig.fmtEscapes(msg)});
        try app.client.writeBin(msg);
        std.log.info("-> sent", .{});
    }

    std.log.info("closing...", .{});
    try app.client.close(.{});
    std.log.info("-> closed", .{});
}

fn recvThread(self: *App) void {
    while (true) {
        const msg = (self.client.read() catch return) orelse unreachable;
        defer self.client.done(msg);

        std.log.info("received message: \"{}\"", .{std.zig.fmtEscapes(msg.data)});
    }
}
karlseguin commented 13 hours ago

This works fine on Mac and Linux, so I assume it's a windows issue, or an issue with Zig's window wrapper. Unfortunately, I can't test.

Can you try replacing the call to:

try app.client.close(.{});

with:

try std.posix.shutdown(app.client.stream.handle, .both); 
std.posix.close(app.client.stream.handle); 
pfgithub commented 5 hours ago

It hangs on std.posix.shutdown. I guess there's some kind of other problem because it also hangs on writeBin and setting writeTimeout has no effect.

It works fine as long as the recvThread isn't spawned (app.client.close() and std.posix.shutdown both work then), so maybe some problem with writing while there is an active read call on windows? readTimeout doesn't seem to work either so I can't see what would happen if trying to write while there isn't a read active.

Possibly a problem with zig's windows implementations of the posix fns