Expose Io polling to consumer

My library uses Io interface to consume X11 protocol. It’s nice and dandy until library client needs to hook into polling. Usually poll for some timeout to schedule other tasks. With xlib I could do something like:

pub fn run(app: *App) !void {
    // setup pollfd
    const fd = c.ConnectionNumber(app.ctx.gfx.display);
    var pollfd = [1]std.posix.pollfd{.{
        .fd = xfd,
        .events = std.posix.POLL.IN,
        .revents = 0,
    }};

    // event loop
    while (true) {
        // While there are pending X11 events
        while (c.XPending(app.ctx.gfx.display) > 0) {
            var event: c.XEvent = undefined;
            // Get next event
            _ = c.XNextEvent(app.ctx.gfx.display, &event);
            // Process event
            switch (event.type) { ... }
        }

        app.doStuff() // takes some time

        // There could be more events available already.
        // So only need to block for polling only on zero events.
        if (c.XPending(app.ctx.gfx.display) == 0) {
            const timeout_ms = app.nextPollTimeoutMs();
            _ = try std.posix.poll(&pollfd, timeout_ms);
        }
    }
}

I don’t understand how to make similar API with Io provided facilities. I assume I could check reader buffer and also give fd to client for std.posix.poll. But it doesn’t feel right. Current library event loop looks like:

    while (true) {
        // Blocks until data is available
        // conn is inited with Io interface and uses std.Io.net.Stream
        // to get Reader and Writer.
        const event = try conn.nextEvent();
        switch (event) {
            ...
        }
    }

Am I missing something obvious?

Current solution

I’ve added reader buffer check and pre-poll to guarantee main decode path wouldn’t block.
It allows to use library in simple cases where timeout is enough and to wire a custom poll around.

    pub fn pollEventTimeout(self: *Connection, timeout_ms: c_int) !?xproto.Event {
        ...
        if (self.reader().bufferedLen() == 0) {
            var pollfd = [1]std.posix.pollfd{.{
                .fd = self.fd(),
                .events = std.posix.POLL.IN,
                .revents = 0,
            }};
            const n = try std.posix.poll(&pollfd, timeout_ms);
            if (n == 0) return null;
        }
        // Could safely use reader methods here
    }

std.Io.Event might be of interest?

It’s not currently possible. Will need to convince Andrew to add poll to the interface. :slight_smile:

1 Like

std.Io.Evented implements the same std.Io interface, it doesn’t expose poll functionality.

I don’t know how to apply std.Io.Event in single threaded model. My library shouldn’t force threading policy on user. Intended usage:

  • app waits events
  • process it
  • requests some messages and wait reply from x server

All this stuff should happen sequentially.

It is interesting that both of you decided that I made a typo!

1 Like

Pretty sure this is needed to do this sanely. std.Io.Operation for poll mimicking io_uring’s poll should work.

Huh. I didn’t. Event just a synchronization primitive hardly (I lack imagination) applicable to a single threaded (or non-concurrent) context.

It seems Io.Operation would consume socket data starving Reader. I’m dumb, how to feed data back to Reader? Also it’s big question about buffer impedance and what to do if Reader buffer has no capacity. It’s quite fiddle construction.

Not sure I understand. Operation can be more than streams. (See DeviceIoControl for example)
Poll operation is something you’d set up and then get completition when fd is ready.

Io does not provide such facilities :joy:

I’ve created this issue:

https://codeberg.org/ziglang/zig/issues/35306

Will try to get it implemented.

1 Like