Pagepiper (and possibly other products copying its pattern) imported
dispatch_task(caller, args) -> task_id / get_task_status(task_id) -> dict
from circuitforge_core.tasks, expecting a "product/task_name" + kwargs-dict
interface. Neither function existed, so every call silently hit an
except-Exception fallback with no visible error.
This is a new module, not a TaskScheduler wrapper — TaskScheduler is keyed
by task_id/job_id/params against a specific SQLite background_tasks table
(VRAM-budgeted LLM queue), a different shape from the generic named-runnable
dispatch pagepiper actually needed.
Scope: free-tier, in-process, single-node only. Routing through the
circuitforge-orch coordinator would need a new generic task-dispatch
endpoint on that separate BSL package (CFOrchClient only exposes model/
service allocation today) — tracked as follow-up, not attempted here.
Consuming products additionally need to call register_task_runner() at
startup to benefit; that's product-side work in a separate repo.
Bump to 0.22.0.
Closes: #67
- C1: Remove _reserved_vram decrement from _scheduler_loop reaper; sole
responsibility now belongs to _batch_worker's finally block, eliminating
the double-decrement race that could drive _reserved_vram negative.
- C2: Move TaskScheduler construction (including VRAM detection httpx call)
outside _scheduler_lock in get_scheduler(); lock is now only held for the
final singleton assignment, preventing 2s lock contention on first call.
- I1: Add RunTaskFn type alias (Callable[...]) and use it in __init__ and
get_scheduler() instead of bare Callable.
- I2: Replace namedtuple TaskSpec with typed NamedTuple class.
- I3: Parameterize _queues annotation as dict[str, deque[TaskSpec]].
- I4: Wrap _queues read in start() with self._lock.
- I5: Replace time.sleep() ordering assertion in test_vram_budget_blocks_second_type
with event-based synchronization using type_a_started/type_b_started events.
- M2: Use sqlite3.connect() as context manager in _load_queued_tasks.
- M3: Strengthen weak assertion in test_enqueue_returns_false_when_queue_full.
- M4: Add test_reserved_vram_zero_after_task_completes to catch C1 regression.
Adds test_detect_vram_preflight_fallback to cover the spec path where
cf-orch is unreachable but scripts.preflight.get_gpus() succeeds,
verifying detect_available_vram_gb() returns the summed total VRAM.
Uses sys.modules injection to simulate the preflight module being present.
Extract generic batch scheduler into circuitforge_core.tasks.scheduler
so any CircuitForge product can use it. Includes VRAM detection via
cf-orch coordinator (cooperative free-VRAM), preflight fallback, and
unlimited fallback; singleton API; full test coverage (12 tests).