Skip to content

queue

In-memory ring buffer queue with configurable overflow strategies. Transport-agnostic — use it as a buffer between data producers (sensors, MQTT, CAN bus) and consumers (HTTP POST, TCP send, logging).

Version v0.1
Platform PC, ESP32
Type Native (C)

star.mod

require dev-libs/queue v0.1

Usage

import "queue"

Functions

Create / Destroy

Function Signature Description
queue.new(cap) (i32) -> i32 Create queue with capacity, returns handle
queue.destroy(q) (i32) -> void Free queue resources
var q: i32 = queue.new(100)
# ... use queue ...
queue.destroy(q)

Maximum 32 queues can be active at the same time.

Push / Pop

Function Signature Description
queue.push(q, val) (i32, value) -> void Push value. On a full block queue it parks the task (backpressure) until a slot frees
queue.tryPush(q, val) (i32, value) -> bool Non-blocking push. Returns false if a full block queue would reject; never parks
queue.pop(q) (i32) -> value Pop front value (FIFO); wakes one parked producer
queue.peek(q) (i32) -> value Read front value without removing
queue.popBatch(q, n) (i32, i32) -> array Pop up to N values at once; wakes parked producers

Queues carry the dynamic value type, so any value (number, string, struct, array, dict) can be queued. Use checked narrowing when reading back a typed value (see Type System).

var q: i32 = queue.new(10)
queue.push(q, 42)
queue.push(q, 99)

var first: number = queue.pop(q)     # 42 (FIFO)
var next: number = queue.peek(q)     # 99 (still in queue)

var batch: array = queue.popBatch(q, 5)  # up to 5 items

Status

Function Signature Description
queue.len(q) (i32) -> i32 Current item count
queue.cap(q) (i32) -> i32 Maximum capacity
queue.full(q) (i32) -> bool Is queue at capacity?
queue.empty(q) (i32) -> bool Is queue empty?
queue.clear(q) (i32) -> void Remove all items

Overflow Control

Function Signature Description
queue.setOverflow(q, mode) (i32, string) -> void Set overflow strategy
queue.setOverflow(q, "drop_oldest")
Mode Behavior
"block" Default. Zero loss. queue.push parks the producer task until a consumer frees a slot (backpressure-as-yield); queue.tryPush returns false instead of parking
"drop_oldest" Oldest item is dropped to make room for the new item (push never parks)
"drop_newest" New item is discarded when full (push never parks)

Backpressure (block mode)

In block mode a full queue does not drop and does not spin — the producing task is parked and the scheduler runs other tasks. A consumer's pop/popBatch/clear frees slots and wakes one parked producer per freed slot, which re-runs its push. This is the data-plane mechanism that gives zero loss without a busy-wait.

If the queue is full and no other task can drain it (the producer is the only runnable task), push raises fault code 13 (queue deadlock) rather than parking forever. Use tryPush when you want to handle fullness yourself instead of blocking.

Statistics

Function Signature Description
queue.stats(q) (i32) -> dict Get queue statistics
var s: dict = queue.stats(q)
# s["pushed"]  — total items pushed
# s["popped"]  — total items popped
# s["dropped"] — total items dropped (overflow)
# s["len"]     — current item count

Pattern: Store and Forward

package main

import "queue"

var outbox: i32 = 0

fn sensor_reader():void {
    # simulated high-frequency sensor data
    var i: i32 = 0
    for (i = 0; i < 100; i = i + 1) {
        queue.push(outbox, i * 10)
        runtime.yield()
    }
}

fn http_sender():void {
    # drain queue and send batches
    for (var tick: i32 = 0; tick < 20; tick = tick + 1) {
        if (!queue.empty(outbox)) {
            var batch: array = queue.popBatch(outbox, 10)
            # http.post(endpoint, json.stringify(batch))
            console.log(batch.len())
        }
        runtime.yield()
    }
}

fn main():void {
    outbox = queue.new(200)
    queue.setOverflow(outbox, "drop_oldest")

    go sensor_reader()
    go http_sender()
    runtime.keepAlive()

    queue.destroy(outbox)
}

Notes

  • Queue is a FIFO ring buffer — O(1) push/pop
  • popBatch returns fewer items than requested if the queue doesn't have enough
  • pop on an empty queue returns nil
  • Each queue is identified by an i32 handle
  • Stats track lifetime totals, not just current state — under block mode dropped stays 0 by design
  • Queue contents are GC roots: values held in a queue survive collection
  • The queue is the data plane — you choose its retention policy. For reliable control-plane delivery that must never drop, use events instead
  • On ESP32, queue capacity should be sized to available RAM