Linked repository source
fixtures/native_components/subsystem/reload_worker_test.odin
This read-only view comes from the file in the Gododin repository. It retains its design, example or evidence context.
Download the original file · Return to the guide
// Reload worker owner tests: pending promotion with the bounded handoff
// releasing the displaced record, cooperative cancellation with the token
// reset per request, and a cancel admitted before the worker picks the request
// up. The reload and restart harnesses materialize this package from
// reload-subsystem.odin.in, which the repository tiers hold identical to it
// apart from the compiled-in revision marker.
package reload_worker
import "core:mem"
import "core:sync"
import "core:testing"
import "core:time"
// SHORT_ITERATIONS is the smallest accepted request. PROMOTION_ITERATIONS
// keeps a request active well past a second submit issued right after it, and
// HEAVY_ITERATIONS runs for seconds unless cancelled.
SHORT_ITERATIONS :: 1_000
PROMOTION_ITERATIONS :: 50_000_000
HEAVY_ITERATIONS :: 1_000_000_000
reload_worker_test_wait_status :: proc(worker: ^Worker, expected: Status, attempts := 10_000) -> bool {
for _ in 0 ..< attempts {
if worker_status(worker) == expected do return true
time.sleep(time.Millisecond)
}
return false
}
@(test)
reload_worker_promotes_a_request_submitted_while_active :: proc(t: ^testing.T) {
tracking: mem.Tracking_Allocator
mem.tracking_allocator_init(&tracking, context.allocator)
defer mem.tracking_allocator_destroy(&tracking)
worker, created := worker_create(mem.tracking_allocator(&tracking))
testing.expect(t, created)
if !created do return
// The first submit marks its request active before returning, so the
// second lands on the pending slot while the first still computes.
first, first_error := worker_submit(worker, 1, PROMOTION_ITERATIONS)
second, second_error := worker_submit(worker, 2, SHORT_ITERATIONS)
testing.expect_value(t, first_error, Error.None)
testing.expect_value(t, second_error, Error.None)
testing.expect(t, first > 0 && second > first)
// Promotion happens under the lock that completes the first request, so
// the next observable completion is the promoted request's.
testing.expect(t, reload_worker_test_wait_status(worker, .Completed))
result, taken := worker_take(worker)
testing.expect(t, taken)
testing.expect_value(t, result.request_id, u64(second))
testing.expect(t, !result.cancelled && result.hash != 0)
testing.expect_value(t, worker_status(worker), Status.Idle)
testing.expect(t, worker_destroy(&worker))
// The first request's unconsumed record was displaced and released.
testing.expect_value(t, len(tracking.allocation_map), 0)
}
@(test)
reload_worker_cancels_the_active_request_and_resets_for_the_next :: proc(t: ^testing.T) {
worker, created := worker_create(context.allocator)
testing.expect(t, created)
if !created do return
defer testing.expect(t, worker_destroy(&worker))
id, submit_error := worker_submit(worker, 5, HEAVY_ITERATIONS)
testing.expect_value(t, submit_error, Error.None)
testing.expect(t, id > 0)
testing.expect_value(t, worker_cancel(worker), i64(1))
testing.expect(t, reload_worker_test_wait_status(worker, .Cancelled))
_, published := worker_take(worker)
testing.expect(t, !published)
// The token resets per request: the next submit completes normally.
again, again_error := worker_submit(worker, 6, SHORT_ITERATIONS)
testing.expect_value(t, again_error, Error.None)
testing.expect(t, reload_worker_test_wait_status(worker, .Completed))
result, taken := worker_take(worker)
testing.expect(t, taken)
testing.expect_value(t, result.request_id, u64(again))
testing.expect(t, !result.cancelled && result.hash != 0)
}
@(test)
reload_worker_honors_a_cancel_admitted_before_pickup :: proc(t: ^testing.T) {
worker, created := worker_create(context.allocator)
testing.expect(t, created)
if !created do return
defer testing.expect(t, worker_destroy(&worker))
// worker_submit starts the thread together with the request, so a
// cancel() cannot be ordered ahead of the pickup through it. Admit the
// request exactly as worker_submit does, cancel it, and only then start
// the thread: the pickup deterministically sees the cancel.
sync.mutex_lock(&worker.mutex)
worker.next_request_id = 1
worker.active = true
worker.active_id = 1
worker.active_seed = 7
worker.active_iters = HEAVY_ITERATIONS
worker.status = .Running
sync.mutex_unlock(&worker.mutex)
testing.expect_value(t, worker_cancel(worker), i64(1))
sync.mutex_lock(&worker.mutex)
started := worker_start_thread(worker)
sync.mutex_unlock(&worker.mutex)
testing.expect(t, started)
if !started do return
testing.expect(t, reload_worker_test_wait_status(worker, .Cancelled))
_, published := worker_take(worker)
testing.expect(t, !published)
}