How to quit thread or timeout stdin read?

I am trying make a text UI program. At the moment linux is target os.
Using C, I have enabled reading from the stdin without the need to press ENTER key.

Reading the stdin is in one thread (IN) while updating the state is in another thread (DO).
I can press q in IN thread and I can pass isRunning=false to DO thread and the program exits. So, all works perfectly, except when the DO thread reaches exit condition and sets isRunning=false… the IN thread still waits for user input, because this seems to be blocking:

std.io.getStdIn().reader().readByte()

Now, what I am trying to achieve is either

  • stopping/killing the IN thread (which seems to have removed from zig) or
  • time out the std.io.getStdIn().reader().readByte() so that the thread can see the changed isRunning value.

Any thoughts?

After thoughts:
would there be a way to “fork” the stdin pipe so that I could write ‘q’ into it?

Trying to kill the thread is the wrong way.
Setting a timeout is the way to go.
See: Io - polling for key down event - how to?

1 Like

I’m not sure how std.io.poll works.

The following example does not set the input to raw mode, you have to press a character and then enter. Press ^D to exit. Wait for 3 seconds for timeout.

const std = @import("std");

pub fn main() !void {
    var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{};
    defer _ = gpa.deinit();

    var poller = std.io.poll(
        gpa.allocator(),
        enum { stdin },
        .{ .stdin = std.io.getStdIn() },
    );
    defer poller.deinit();

    const ns = 3 * std.time.ns_per_s;
    while (try poller.pollTimeout(ns)) {
        var fifo = poller.fifo(.stdin);
        if (fifo.readItem()) |ch| {
            std.debug.print("got {c}\n", .{ch});
            fifo.discard(fifo.readableLength());
        } else {
            std.debug.print("timeout!\n", .{});
        }
    }
}
4 Likes

The signature can be a little strange. You’ll want something like this.

var poller = std.io.poll(allocator, enum { stdin }, .{ .stdin = stdin })
mainloop: while (true) {
    const ready = try poller.pollTimeout( 100 * std.time.ns_per_us); // Or however long you want the timeout to be
    if(ready) {
        const n = poller.fifo(.stdin).read(&inbuff);
        // Process inbuff ...
    }
    if (!isRunning) {
        break :mainloop
    }
}

With poll you can define a number of files to be watched for write events. the pollTimeout will block for a timeout.

The key is that you define the set of files to watch using an enum and file descriptors. That lets you select what fifo you want to read from nicely.

EDIT:

I do want to note what is noted in the linked thread: std.io.poll is broken on MacOS if you are polling a tty.

3 Likes

It was a struggle, but I got it to work in the end.