http.zig icon indicating copy to clipboard operation
http.zig copied to clipboard

The `write` of websocket.Conn gets blocked if called from different thread when `blocking_mode` is true

Open IamSanjid opened this issue 1 year ago • 2 comments

I am not sure if I am using it wrong, but using default httpz config, blocking_mode is true, after receiving websocket.Conn when I try to write to it from a different thread, it blocks that thread and never properly writes it meaning the Client never receives the message, did a litttle investigation call to std.posix.writev hangs, so I think I am using it the wrong way? Window's WriteFile API doesn't support different thread "write"(s)? or it's the Zig's standard library at fault? Any suggestions would be helpful.

IamSanjid avatar Oct 09 '24 13:10 IamSanjid

If I'm being honest, these window issues have been my bane. See https://github.com/karlseguin/websocket.zig/issues/46 for another example. For one, I have no means of reproducing them.

It doesn't sound like you're doing anything wrong. And I'm unsure if my libraries are wrong, or if Zig's windows abstractions are wrong.

I believe my use of writev is correct, or at least, it does the same thing as Zig'd stdlib: https://github.com/ziglang/zig/blob/2e2927735d26fc6047343f0c620f20e9048ebaa5/lib/std/net.zig#L1884

I find the windows documentation frustrating - I don't see it mention anything about thread-safety, although non-official sources seem to indicate that it is thread-safe. Anyways, *websocket.Conn.write itself uses a mutex to ensure a single thread writes at any given time.

The best thing to do is to strip out httpz and websocket.zig and see if the problem can be reproduced using Zig's standard library - as we did with the linked issue above.

karlseguin avatar Oct 10 '24 03:10 karlseguin

I believe the only reliable way to properly shutdown and close the socket on Windows is to use either a non-blocking socket and/or to use overlapped IO.

It looks like the zig standard library stream code doesn't expose the OVERLAPPED structure as part of read or ReadFile though, making overlapped a bit more challenging to access (but neither function is doing much else, so copying the code wrapping Win32 ReadFile would be minimal).

The hEvent needs to be provided by the caller, but it's null always in the current ReadFile wrapper.

const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
    overlapped_data = .{
        .Internal = 0,
        .InternalHigh = 0,
        .DUMMYUNIONNAME = .{
            .DUMMYSTRUCTNAME = .{
                .Offset = @as(u32, @truncate(off)),
                .OffsetHigh = @as(u32, @truncate(off >> 32)),
            },
        },
        .hEvent = null,
    };
    break :blk &overlapped_data;
} else null;

The socket needs to be opened differently as well in either case ... I didn't look at that code. WriteFile would need to be adjusted as well if stream uses that too.

wiredprairie avatar Feb 23 '25 16:02 wiredprairie