Hourglass.ActivityRunner (hourglass v0.1.0)

Copy Markdown View Source

Runs a single activity task. Spawned by the Hourglass.ActivityExecutor.DynamicSupervisor as a Task; invokes <activity_module>.handle(name, args); classifies the outcome through Hourglass.Activity.RetryClassifier; wraps the result into an ActivityTaskCompletion proto; ships back via Hourglass.BridgeHolder.complete_activity_task/2.

Activities are plain Elixir — no determinism constraint, no special process model. They do NOT hold a raw bridge reference(); they call the BridgeHolder API with their task_queue. If the holder no longer has the task queue registered (Worker is shutting down) the call returns {:error, :worker_not_registered} and is logged but not raised — the activity result is lost from this dispatch and Core will redeliver via heartbeat / start-to-close timeout.

Failure handling

Both the {:error, reason} return path and the rescue clause feed the failure value through Hourglass.Activity.RetryClassifier.classify/2. The classifier's verdict drives Failure.application_failure_info:

  • typeApplicationFailureInfo.type and Failure.source
  • messageFailure.message
  • detailsApplicationFailureInfo.details (when non-nil)
  • non_retryableApplicationFailureInfo.non_retryable (computed as classification != :retryable:unclassified is treated as non-retryable in production for safety)

Failure.application_failure_info.non_retryable: true is what tells Temporal Server to stop retrying.

Contract violation

An activity's handle/2 must return {:ok, term()} | {:error, term()}. Any other return shape is a ContractViolation: non-retryable, with a message naming the offending value.

Test-mode strict

Under MIX_ENV=test (or when :strict_unclassified_activity_failures is configured true), an :unclassified classification raises an ArgumentError BEFORE encoding the completion. The error names the originating activity and points at the classifier so missing clauses surface at the source. In production the strict guard collapses at compile time and the runner falls back to the safe non-retryable encoding.

Summary

Functions

run(activity_task, task_queue, complete_fn \\ &BridgeHolder.complete_activity_task/2)

@spec run(
  activity_task :: map(),
  task_queue :: String.t(),
  complete_fn :: (String.t(), binary() -> :ok | {:error, term()})
) :: :ok