Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aio: multishot operations #7

Open
Cloudef opened this issue Jul 24, 2024 · 5 comments
Open

aio: multishot operations #7

Cloudef opened this issue Jul 24, 2024 · 5 comments

Comments

@Cloudef
Copy link
Owner

Cloudef commented Jul 24, 2024

It's possible to have multishot operations that take a callback parameter. These are important for repeating timers and socket accept loops. Should not need any special interface, but there might be some lifetime shenanigans that has to be taken in account.

@Cloudef
Copy link
Owner Author

Cloudef commented Jul 24, 2024

How it could work:

fn acceptCallback(context: *anyopaque, socket: std.posix.socket_t, addr: std.posix.addrinfo) !void {
    // cancel from inside a callback
    return error.Canceled;
}

var id: aio.Id = undefined;
try io.single(aio.AcceptMultishot{ .callback = acceptCallback, .context = arg, .out_id = &id });
// cancel from outside the callback
io.single(aio.Cancel{ .id = id });

@Cloudef Cloudef added the enhancement New feature or request label Dec 31, 2024
@Cloudef
Copy link
Owner Author

Cloudef commented Jan 29, 2025

I've been thinking about this and the callbacks isn't good general strategy. I think having callbacks for more specialized cases is still good, but for general case I see too many problems, especially when coro is in the mix, so the callbacks would have to be scheduled for the yielding task, or not do this but in that case the function would not be able to do any coro stuff inside as it would not be running inside the task itself. Of course scheduling the call to the task is not a problem, but it might complicate scheduling quite a bit and introduce overhead in there. It's also slightly annoying because you can't use coroutine local context from the function simply, but have to rely on the userdata.

I'm braindumping something like:

const aio.MultiRecv {
  buffers: aio.BufferSet, // .{ .id = <registered buffers id>, .set = <pointer to bitset marking modified buffers> }
  // 0 == wait for all buffers to be filled
  // this waits at least n completitions, but more 
  // buffers might be completed when the operation finishes
  wait_at_least_n_completions: u16 = 0,
};

Which would also map very well to io_uring registered buffers.

@Cloudef
Copy link
Owner Author

Cloudef commented Jan 30, 2025

New braindump, since i think the idea above goes bit too far away from the io_uring api:

var id: aio.Id = undefined;
try coro.single(.multishot_recv, .{ .ring = &msg_ring, .out_id = &id });
defer coro.single(.cancel, .{ .id = id }) catch @panic("welp");
while (true) {
  // new op that can be used for non multishot stuff as well
  try coro.single(.select, .{ .ids = &.{ id } }); 
  // or maybe:
  try coro.select(.{
      aio.op(.wait, .{ .id = id }, .unlinked),
      // could have wait for other id here or some other op
  });
  // ^ you want to avoid using select on recv and recv likes unless it's MSG.PEEK though as you could lose the data
  // I like aio.select / coro.select more myself, as it seems more general, but idk yet which approach I'll take

  var iter = msg_ring.iterator();
  while (iter.next()) |entry| {  
    if (entry.id != id) continue;
    process_packet(entry.buffer);
    entry.release(); // buffer is free to be reused again
  }
}

@Cloudef
Copy link
Owner Author

Cloudef commented Feb 11, 2025

I've been playing with multishot operations a bit. They don't really fit very well the zig-aio model and the benefits don't seem to impress me yet, but I feel like I may have to work with bigger buffers. RECVSEND_BUNDLE from io_uring though looks way more interesting though.

@Cloudef Cloudef added experimentation and removed enhancement New feature or request labels Feb 11, 2025
@Cloudef
Copy link
Owner Author

Cloudef commented Feb 11, 2025

Seems like my results were skewed by the fact I was doing all communication in one process in my testing, multishot performs better when communication happens in separate processes, I presume this also would map to communication over network as well.

Anyhow, I continue working out what kind of approach I should take here. I still favor RECVSEND_BUNDLE design, and I could probably emulate that for operations that do not support it using multishot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant