Gododin Gododin Documentation

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)
}