pub const Batch = struct pub fn init(storage: []Operation.Storage) BatchAfter calling this, it is safe to unconditionally defer a call to cancel. storage is a pre-allocated buffer of undefined memory that determines the maximum number of active operations that can be submitted via add and addAt.
pub fn add(batch: *Batch, operation: Operation) u32Adds an operation to be performed at the next await call. Returns the index that will be returned by next after the operation completes. Asserts that no more than storage.len operations are active at a time.
pub fn addAt(batch: *Batch, index: u32, operation: Operation) voidAdds an operation to be performed at the next await call. After the operation completes, next will return index. Asserts that the operation at index is not active.
pub fn next(batch: *Batch) ?CompletionAfter calling awaitAsync, awaitConcurrent, or cancel, this function iterates over the completed operations.
Each completion returned from this function dequeues from the Batch. It is not required to dequeue all completions before awaiting again.
pub fn awaitAsync(batch: *Batch, io: Io) Cancelable!voidWaits for at least one of the submitted operations to complete. After this function returns the completed operations can be iterated with next.
This function provides opportunity for the implementation to introduce concurrency into the batched operations, but unlike awaitConcurrent, does not require it, and therefore cannot fail with error.ConcurrencyUnavailable.
pub fn awaitConcurrent(batch: *Batch, io: Io, timeout: Timeout) AwaitConcurrentError!voidWaits for at least one of the submitted operations to complete. After this function returns the completed operations can be iterated with next.
Unlike awaitAsync, this function requires the implementation to perform the operations concurrently and therefore can fail with error.ConcurrencyUnavailable.
pub fn cancel(batch: *Batch, io: Io) voidRequests all pending operations to be interrupted, then waits for all pending operations to complete. After this returns, the Batch is in a well-defined state, ready to be iterated with next. Successfully canceled operations will be absent from the iteration. Some operations may have successfully completed regardless of the cancel request and will appear in the iteration.
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726pub const Batch = struct {
storage: []Operation.Storage,
unused: Operation.List,
submitted: Operation.List,
pending: Operation.List,
completed: Operation.List,
userdata: ?*anyopaque align(@max(@alignOf(?*anyopaque), 4)),
/// After calling this, it is safe to unconditionally defer a call to
/// `cancel`. `storage` is a pre-allocated buffer of undefined memory that
/// determines the maximum number of active operations that can be
/// submitted via `add` and `addAt`.
pub fn init(storage: []Operation.Storage) Batch {
var prev: Operation.OptionalIndex = .none;
for (storage, 0..) |*operation, index| {
operation.* = .{ .unused = .{ .prev = prev, .next = .fromIndex(index + 1) } };
prev = .fromIndex(index);
}
storage[storage.len - 1].unused.next = .none;
return .{
.storage = storage,
.unused = .{
.head = .fromIndex(0),
.tail = .fromIndex(storage.len - 1),
},
.submitted = .empty,
.pending = .empty,
.completed = .empty,
.userdata = null,
};
}
/// Adds an operation to be performed at the next await call.
/// Returns the index that will be returned by `next` after the operation completes.
/// Asserts that no more than `storage.len` operations are active at a time.
pub fn add(batch: *Batch, operation: Operation) u32 {
const index = batch.unused.head.toIndex();
batch.addAt(index, operation);
return index;
}
/// Adds an operation to be performed at the next await call.
/// After the operation completes, `next` will return `index`.
/// Asserts that the operation at `index` is not active.
pub fn addAt(batch: *Batch, index: u32, operation: Operation) void {
const storage = &batch.storage[index];
const unused = storage.unused;
switch (unused.prev) {
.none => batch.unused.head = unused.next,
else => |prev_index| batch.storage[prev_index.toIndex()].unused.next = unused.next,
}
switch (unused.next) {
.none => batch.unused.tail = unused.prev,
else => |next_index| batch.storage[next_index.toIndex()].unused.prev = unused.prev,
}
switch (batch.submitted.tail) {
.none => batch.submitted.head = .fromIndex(index),
else => |tail_index| batch.storage[tail_index.toIndex()].submission.node.next = .fromIndex(index),
}
storage.* = .{ .submission = .{ .node = .{ .next = .none }, .operation = operation } };
batch.submitted.tail = .fromIndex(index);
}
pub const Completion = struct {
/// The element within the provided operation storage that completed.
/// `addAt` can be used to re-arm the `Batch` using this `index`.
index: u32,
/// The return value of the operation.
result: Operation.Result,
};
/// After calling `awaitAsync`, `awaitConcurrent`, or `cancel`, this
/// function iterates over the completed operations.
///
/// Each completion returned from this function dequeues from the `Batch`.
/// It is not required to dequeue all completions before awaiting again.
pub fn next(batch: *Batch) ?Completion {
const index = batch.completed.head;
if (index == .none) return null;
const storage = &batch.storage[index.toIndex()];
const completion = storage.completion;
const next_index = completion.node.next;
batch.completed.head = next_index;
if (next_index == .none) batch.completed.tail = .none;
const tail_index = batch.unused.tail;
switch (tail_index) {
.none => batch.unused.head = index,
else => batch.storage[tail_index.toIndex()].unused.next = index,
}
storage.* = .{ .unused = .{ .prev = tail_index, .next = .none } };
batch.unused.tail = index;
return .{ .index = index.toIndex(), .result = completion.result };
}
/// Waits for at least one of the submitted operations to complete. After
/// this function returns the completed operations can be iterated with
/// `next`.
///
/// This function provides opportunity for the implementation to introduce
/// concurrency into the batched operations, but unlike `awaitConcurrent`,
/// does not require it, and therefore cannot fail with
/// `error.ConcurrencyUnavailable`.
pub fn awaitAsync(batch: *Batch, io: Io) Cancelable!void {
return io.vtable.batchAwaitAsync(io.userdata, batch);
}
pub const AwaitConcurrentError = ConcurrentError || Cancelable || Timeout.Error;
/// Waits for at least one of the submitted operations to complete. After
/// this function returns the completed operations can be iterated with
/// `next`.
///
/// Unlike `awaitAsync`, this function requires the implementation to
/// perform the operations concurrently and therefore can fail with
/// `error.ConcurrencyUnavailable`.
pub fn awaitConcurrent(batch: *Batch, io: Io, timeout: Timeout) AwaitConcurrentError!void {
return io.vtable.batchAwaitConcurrent(io.userdata, batch, timeout);
}
/// Requests all pending operations to be interrupted, then waits for all
/// pending operations to complete. After this returns, the `Batch` is in a
/// well-defined state, ready to be iterated with `next`. Successfully
/// canceled operations will be absent from the iteration. Some operations
/// may have successfully completed regardless of the cancel request and
/// will appear in the iteration.
pub fn cancel(batch: *Batch, io: Io) void {
{ // abort pending submissions
var tail_index = batch.unused.tail;
defer batch.unused.tail = tail_index;
var index = batch.submitted.head;
errdefer batch.submissions.head = index;
while (index != .none) {
const next_index = batch.storage[index.toIndex()].submission.node.next;
switch (tail_index) {
.none => batch.unused.head = index,
else => batch.storage[tail_index.toIndex()].unused.next = index,
}
batch.storage[index.toIndex()] = .{ .unused = .{ .prev = tail_index, .next = .none } };
tail_index = index;
index = next_index;
}
batch.submitted = .{ .head = .none, .tail = .none };
}
io.vtable.batchCancel(io.userdata, batch);
assert(batch.submitted.head == .none and batch.submitted.tail == .none);
assert(batch.pending.head == .none and batch.pending.tail == .none);
assert(batch.userdata == null); // that was the last chance to deallocate resources
}
}
Submits many operations together without waiting for all of them to complete.
This is a low-level abstraction based on
Operation. For a higher level API that operates onFuture, seeSelectandGroup.