Skip to content

BackWave 1.7.0

Latest

Choose a tag to compare

@pdevito3 pdevito3 released this 21 Sep 19:07
· 7 commits to main since this release

BackWave 1.7.0

Added

  • Oracle storage adapter. BackWave.Oracle targets Oracle 19c and later, passes the same Conformance Suite as the other adapters, and carries the same OpenTelemetry db.* spans. Its hot paths are batched: a claim of 32 jobs costs 9 statements, an outcome report of 32 costs 5, and a lease sweep costs 4. The adapter measures 4.1x the throughput of a per-row design at a 1 ms round trip. Set OracleStoreOptions.EnableWakeUpHints to wake an idle pump on enqueue through DBMS_ALERT. The hint is off by default. With it on, pickup latency drops from the 5 s poll interval to about 138 ms. A missing EXECUTE grant on SYS.DBMS_ALERT logs one warning (EventId 1501) and the pump keeps polling.
  • Clean-stop lease hand-back. A Worker Group that stops cleanly now returns the Leases it holds, so a healthy node picks the work up at once instead of after a full Lease Duration. IJobStore gains RelinquishLeasesAsync, a default interface method that returns zero, so a custom store compiles and behaves as before. The hand-back and the final outcome report share one ShutdownBudget (default 5 s). A fail-stopped group writes nothing more and lets its Leases lapse.
  • Fail-stop trigger set. InvariantTrigger names every impossible state BackWave detects. InvariantViolationException carries the tripped trigger. The pump and every adapter now halt on a trigger instead of executing past it: a claim that returns a terminal job, an outcome batch answered with a different row count, a short heartbeat answer, a claim wider than its bound, a workflow member cycle, and a live member with no workflow row. The trigger reaches log 2001 as invariant_trigger, the backwave.invariant.violations counter tagged backwave.invariant.trigger and backwave.invariant.action, and the health check state. A fence rejection degrades and is counted where it was dropped before.
  • Tag length bounds. StoreBounds.MaxTagKeyLength and StoreBounds.MaxTagValueLength (both 200 by default) are enforced on every tag write. An over-limit tag is rejected with EnqueueResult.TagTooLong or WorkflowEnqueueResult.TagTooLong and is never truncated. The SQL Server and Oracle adapters store tags in fixed-width columns (200 and 256 characters), so they reject a wider bound when the store is created.
  • Paged Workflow list. IJobStore.ListWorkflowsAsync(WorkflowListQuery) pages by a keyset cursor on (created_at, workflow_id), clamped to MaxMonitorPageSize. The default implementation pages in memory. All five first-party stores read one bounded page. The Pro Workflows page and the MCP list_workflows tool use it, so a refresh no longer loads every workflow.
  • Observer report outcome. IJobStore.TryReportObserverDeliveriesAsync reports whether a delivery report was applied, fenced, or unknown. The default implementation forwards to ReportObserverDeliveriesAsync. The hosting layer now acts on a fenced report instead of discarding it.
  • Rolling upgrade tolerance. Every adapter now accepts a schema version newer than its own, so an N-1 node keeps running while the fleet upgrades. A version older than expected still fail-stops.
  • Conformance Suite capability manifest. A store that declares a capability whose hook still returns the default now fails the suite instead of passing silently.

Changed

  • CAUTION: BackWaveHealthCheck now reports Degraded on a transient store fault or a partially halted group. It reported Healthy before. A readiness probe that fails on Degraded will restart pods on faults it used to ride out. ASP.NET maps Degraded to 200 OK, so a stock MapHealthChecks endpoint is unchanged. Only a wholly halted group reports Unhealthy. The degraded description names the group and the exception type only, so provider text (host, database, login) no longer reaches an unauthenticated probe.
  • RetryPolicy.MaxAttempts is bounded to the shared attempt ceiling of 1000, the same ceiling [Retry] has.
  • Lease sweep round trips. The lease sweep on Postgres, SQL Server, and SQLite records its transitions in one batch. At the default claim batch of 32 a full sweep costs 5 statements on Postgres and 6 on SQL Server, down from 67 and 68. The per-row transition prune is skipped under the retention cap on all three.
  • JobState ordinals are pinned as the persisted wire format and guarded by a test. No value changed.
  • Schema. SQL Server adds 0002_lease_owner_index.sql (an index that lets the hand-back lock only its own rows). SQLite adds 0002_transition_position.sql (a high-water mark so the Transition Log position never regresses after a retention purge). Both are additive. With AutoMigrate on, the store applies them on first use.

Fixed

  • A Worker Group admitted far more than PoolSize handlers under a burst (measured 160 to 400 with PoolSize 20). A claim batch now reserves its slots when it is issued.
  • Idle-poll backoff ramped on the empty-poll count, so a drain tail saturated the delay at once and a job enqueued right after waited the whole ceiling. The ramp now follows elapsed idle time.
  • A recurring schedule with a Skip or Coalesce policy stalled after a long outage: the planner walked 32 missed ticks per poll. The fresh tick now mints in the first poll.
  • A handler that returned a Job Output larger than the store cap halted the whole Worker Group. The job now dead-letters with the rejection as its Failure Detail and the group keeps running.
  • SQL Server: hot-path writes that lose a deadlock (error 1205) retry up to four times. The claim retries one Queue's transaction, not the whole claim. Heartbeat and relinquish ids travel through OPENJSON.
  • SQLite: the Transition Log position regressed after a retention purge, so an Observer cursor missed transitions. The store's own connection pool is now cleared on dispose.
  • SQLite and Oracle: an expired parent's transition is recorded before its children's cascade, so an Observer sees the parent go terminal first.
  • Oracle: heartbeat ids, hand-back transitions, and tag reads are sent in slices under the IN-list limit, so a wide batch or a large workflow stays readable.
  • A drained workflow is purged whole under its worst member's retention class.
  • The outcome fence now allows the clock skew a fleet can carry, so one skewed node no longer trips the invariant counter.
  • The process span records an Error status and the exception on a handler-side halt.
  • The MCP queue-depth description names the real job states.
  • The source generator's BW0004 names the payload type, not the Wire Name.

All 14 packages ship at 1.7.0 on nuget.org.