///|
async test "quick start" {
let log = []
@async.with_task_group group => {
let q : @aqueue.Queue[Int] = Queue(kind=Unbounded)
group.spawn_bg() () => {
for i in 0..<3 {
q.put(i)
log.push("put(\{i})")
}
}
group.spawn_bg() () => {
for _ in 0..<3 {
let v = q.get()
log.push("get() => \{v}")
}
}
}
json_inspect(log, content=[
"put(0)", "put(1)", "put(2)", "get() => 0", "get() => 1", "get() => 2",
])
}///|
async test "unbounded never blocks" {
let q : @aqueue.Queue[Int] = Queue(kind=Unbounded)
for i in 0..<1000 {
q.put(i)
}
// All 1000 items now sit in the buffer.
let first = q.try_get()
debug_inspect(first, content="Some(0)")
}///|
async test "blocking creates backpressure" {
let log = []
@async.with_task_group group => {
let q = @aqueue.Queue(kind=Blocking(1))
group.spawn_bg() () => {
for i in 0..<3 {
q.put(i)
log.push("put(\{i})")
}
}
group.spawn_bg() () => {
for _ in 0..<3 {
let x = q.get()
log.push("get() => \{x}")
@async.sleep(100)
}
}
}
json_inspect(log, content=[
"put(0)", "get() => 0", "put(1)", "get() => 1", "put(2)", "get() => 2",
])
}///|
async test "blocking with zero capacity is a rendezvous" {
let log = []
@async.with_task_group group => {
let q = @aqueue.Queue(kind=Blocking(0))
group.spawn_bg() () => {
for i in 0..<3 {
q.put(i)
log.push("put(\{i})")
}
}
group.spawn_bg() () => {
for _ in 0..<3 {
let x = q.get()
log.push("get() => \{x}")
@async.sleep(50)
}
}
}
// Every `get` strictly precedes the matching `put`'s log entry,
// because the producer cannot record `put(i)` until the buffer
// (size 0) hands the value off to a waiting reader.
json_inspect(log, content=[
"get() => 0", "put(0)", "get() => 1", "put(1)", "get() => 2", "put(2)",
])
}///|
async test "discard oldest" {
let q = @aqueue.Queue(kind=DiscardOldest(2))
for i in 13 {
q.put(i)
}
// Item `1` was pushed out by `3`.
let result = []
while q.try_get() is Some(x) {
result.push(x)
}
json_inspect(result, content=[2, 3])
}///|
async test "discard latest" {
let q = @aqueue.Queue(kind=DiscardLatest(2))
for i in 13 {
q.put(i)
}
// Item `3` was dropped because the buffer was already full.
let result = []
while q.try_get() is Some(x) {
result.push(x)
}
json_inspect(result, content=[1, 2])
}Note: try_put is stricter than put — it returns false for a full DiscardOldest/DiscardLatest queue rather than performing the discard. Use try_put when you want to know if the queue accepted the value.
///|
async test "soft close drains buffered items" {
let q = @aqueue.Queue(kind=Unbounded)
q.put(1)
q.put(2)
q.close()
// Reads still succeed until the buffer is empty.
debug_inspect(q.get(), content="1")
debug_inspect(q.get(), content="2")
inspect(
@test_util.expect_error_async(() => q.get()),
content="QueueAlreadyClosed",
)
}///|
async test "hard close discards buffered items" {
let q = @aqueue.Queue(kind=Unbounded)
q.put(1)
q.put(2)
q.close(clear=true)
inspect(
@test_util.expect_error_async(() => q.get()),
content="QueueAlreadyClosed",
)
}///|
async test "writes after close fail" {
let q = @aqueue.Queue(kind=Unbounded)
q.close()
debug_inspect(
@test_util.expect_error_async(() => q.put(42)),
content="QueueAlreadyClosed",
)
debug_inspect(
@test_util.expect_error_async(() => q.try_put(42)),
content="QueueAlreadyClosed",
)
}///|
async test "close as end-of-stream signal" {
let received = []
@async.with_task_group group => {
// Capacity 1 forces the producer to suspend between puts,
// so the consumer can drain each item before `close()` runs.
let q = @aqueue.Queue(kind=Blocking(1))
group.spawn_bg() () => {
for word in ["alpha", "beta", "gamma"] {
q.put(word)
}
q.close()
}
while true {
let v = q.get() catch { _ => break }
received.push(v)
}
}
json_inspect(received, content=["alpha", "beta", "gamma"])
}///|
async test "blocked put fails when queue is closed" {
@async.with_task_group(root => {
let q = @aqueue.Queue(kind=Blocking(1))
q.put(1) // fills the buffer
root.spawn_bg(() => {
@async.sleep(50)
q.close()
})
// This `put` blocks because the buffer is full, then gets
// unblocked when `close()` runs.
debug_inspect(
@test_util.expect_error_async(() => q.put(2)),
content="QueueAlreadyClosed",
)
})
}///|
suberror MyDone derive(Debug)
///|
async test "custom close error" {
let q : @aqueue.Queue[Int] = Queue(kind=Unbounded)
q.close(error=MyDone)
debug_inspect(@test_util.expect_error_async(() => q.get()), content="MyDone")
}///|
test "try_get and try_put" {
let q = @aqueue.Queue(kind=Blocking(2))
// Empty queue: try_get returns None.
debug_inspect(q.try_get(), content="None")
// Two slots: two try_puts succeed, the third does not.
assert_true(q.try_put(1))
assert_true(q.try_put(2))
assert_false(q.try_put(3))
// Drain.
debug_inspect(q.try_get(), content="Some(1)")
debug_inspect(q.try_get(), content="Some(2)")
debug_inspect(q.try_get(), content="None")
}///|
async test "fan-in merges multiple producers" {
let received = []
@async.with_task_group group => {
let q : @aqueue.Queue[String] = Queue(kind=Unbounded)
// Producer A puts items at time 0, 100, 200 ms.
group.spawn_bg() () => {
for i in 0..<3 {
q.put("A\{i}")
@async.sleep(100)
}
}
// Producer B puts items at 50, 150, 250 ms.
group.spawn_bg() () => {
@async.sleep(50)
for i in 0..<3 {
q.put("B\{i}")
@async.sleep(100)
}
}
// Consumer drains 6 items.
for _ in 0..<6 {
received.push(q.get())
}
}
json_inspect(received, content=["A0", "B0", "A1", "B1", "A2", "B2"])
}///|
async test "fan-out distributes work to a pool" {
let work_by_worker : Array[Array[Int]] = [[], [], []]
@async.with_task_group group => {
let q : @aqueue.Queue[Int] = Queue(kind=Blocking(1))
for w in 0..<3 {
group.spawn_bg(allow_failure=true) () => {
for ;; {
let item = q.get()
work_by_worker[w].push(item)
@async.sleep(20) // simulate per-item processing cost
}
}
}
for i in 0..<9 {
q.put(i)
}
q.close()
}
// Every item is processed exactly once, and the union of the
// workers' logs is the full input range.
let total = []
for arr in work_by_worker {
for x in arr {
total.push(x)
}
}
total.sort()
json_inspect(total, content=[0, 1, 2, 3, 4, 5, 6, 7, 8])
}///|
async test "readers are woken FIFO" {
let log = []
@async.with_task_group(root => {
let q : @aqueue.Queue[Int] = Queue(kind=Unbounded)
// Reader 1 starts waiting at ~0 ms.
root.spawn_bg(() => log.push("r1 got \{q.get()}"))
// Reader 2 starts waiting at ~50 ms.
root.spawn_bg(() => {
@async.sleep(50)
log.push("r2 got \{q.get()}")
})
// Producer puts two items at 100 ms.
@async.sleep(100)
q.put(1)
q.put(2)
})
// Reader 1 enqueued first, so it gets `1`; reader 2 gets `2`.
json_inspect(log, content=["r1 got 1", "r2 got 2"])
}impl Show for QueueAlreadyClosedpub(all) enum Kind {
Unbounded
Blocking(Int)
DiscardOldest(Int)
DiscardLatest(Int)
}Asynchronous programming library for MoonBit