arbiter-worker-0.1.0.0
arbiter-worker
Safe HaskellNone
LanguageGHC2024

Arbiter.Worker

Description

Public worker API: pools, job results, configuration, logging, maintenance, and cron.

Synopsis

Running worker pools

runWorkerPool :: forall payload m. (RegistryAdmissionPolicies (RegistryOf m), EncodeJobResult (ResultOf m payload), QueueOperation m payload, RegistryTables (RegistryOf m)) => WorkerConfig m payload -> m ()Source #

Run a worker pool until shutdownWorkerPool, then drain within gracefulShutdownTimeout. Throws WorkerConfigException for an invalid config.

namedWorkerPool :: forall payload (m :: Type -> Type). (EncodeJobResult (ResultOf m payload), QueueOperation m payload, RegistryAdmissionPolicies (RegistryOf m), RegistryTables (RegistryOf m)) => WorkerConfig m payload -> NamedWorkerPool mSource #

Name a pool from its payload's registry entry.

poolConfigForWorkers :: forall (m :: Type -> Type). RegistryTables (RegistryOf m) => [NamedWorkerPool m] -> IO PoolConfigSource #

A single-stripe connection pool sized for the enabled pools' threads and the listener. Unset or blank ARBITER_ENABLED_QUEUES enables all queues. Throws WorkerConfigException for an invalid enabled pool config, and InternalException when ARBITER_ENABLED_QUEUES has an unknown name or names no queues.

runSelectedWorkerPools :: MonadUnliftIO m => [Text] -> [NamedWorkerPool m] -> m ()Source #

Run only named pools. A pool that exits winds down its peers. After every peer has been joined, the failure of the first failed pool in list order is rethrown. Throws WorkerPoolSelectionException for a name with no pool, or when no pool is selected.

runWorkerPools :: (MonadUnliftIO m, RegistryTables (RegistryOf m)) => [NamedWorkerPool m] -> m ()Source #

Run the pools selected by ARBITER_ENABLED_QUEUES, or every configured pool when it is unset or blank. Throws InternalException when ARBITER_ENABLED_QUEUES has an unknown name or names no queues, and WorkerPoolSelectionException for a selected queue with no pool, or when no pool is selected.

shutdownWorkerPools :: forall m (m' :: Type -> Type). MonadIO m => [NamedWorkerPool m'] -> m ()Source #

Signal graceful shutdown to every pool atomically.

data NamedWorkerPool (m :: Type -> Type)Source #

A worker pool paired with its registry-derived queue name.

Constructors

(EncodeJobResult (ResultOf m payload), QueueOperation m payload, RegistryAdmissionPolicies (RegistryOf m), RegistryTables (RegistryOf m)) => NamedWorkerPool 

Fields

getEnabledQueues :: forall (registry :: JobPayloadRegistry). RegistryTables registry => Proxy registry -> IO [Text]Source #

Validated, comma-separated queue names from ARBITER_ENABLED_QUEUES. Unset or blank selects all registered queues. Unknown names, or a value that names no queues, throw InternalException.

Job results

childResults :: (FromJSON (ResultOf m payload), MonadArbiter m) => JobRead payload -> m (Map Int64 (Either Text (ResultOf m payload)), Map Int64 Text)Source #

A rollup parent's immediate child results, keyed by child id, and its DLQ errors, keyed by DLQ row id for retryFromDLQ. A Left is a DLQ child's error or a decode failure.

mergedChildResults :: (FromJSON (ResultOf m payload), MonadArbiter m, Monoid (ResultOf m payload)) => JobRead payload -> m (ResultOf m payload, Map Int64 Text)Source #

childResults with successfully decoded values combined through Monoid.

mergeChildResults :: Monoid a => Map Int64 (Either Text a) -> aSource #

Combine successful child results. A Left counts as mempty.

storeJobResult :: (EncodeJobResult result, MonadArbiter m) => JobRead payload -> result -> m ()Source #

Store a job's result for its parent rollup. A root job's result goes on its archive entry only when its archiveFor is positive. The archive entry exists only after the job's ack.

storeEncodedResult :: MonadArbiter m => JobRead payload -> Maybe Value -> m ()Source #

storeJobResult on an already-encoded result. Nothing stores nothing.

Configuration

data WorkerConfig (m :: Type -> Type) payloadSource #

Configuration for a worker pool.

Constructors

WorkerConfig 

Fields

transactionalWorkerConfig :: forall (n :: Type -> Type) m payload. (MonadArbiter n, MonadIO m) => Int -> JobHandler n payload (ResultOf n payload) -> m (WorkerConfig n payload)Source #

Arguments

:: forall (n :: Type -> Type) m payload. (MonadArbiter n, MonadIO m) 
=> Int

Worker count

-> JobHandler n payload (ResultOf n payload)

Handler returning the result type payload's registry entry declares

-> m (WorkerConfig n payload) 

Create a WorkerConfig running one job per group in a worker transaction held for the duration of the handler.

manualWorkerConfig :: (MonadArbiter n, MonadIO m) => Int -> (JobRead payload -> BatchCallbacks n payload (ResultOf n payload) -> n ()) -> m (WorkerConfig n payload)Source #

Arguments

:: (MonadArbiter n, MonadIO m) 
=> Int

Worker count

-> (JobRead payload -> BatchCallbacks n payload (ResultOf n payload) -> n ())

Job handler

-> m (WorkerConfig n payload) 

Create a WorkerConfig that passes one job per handler call, no worker transaction. The handler finalizes the job through BatchCallbacks.

batchedWorkerConfig :: (MonadArbiter n, MonadIO m) => Int -> Int -> (NonEmpty (JobRead payload) -> BatchCallbacks n payload (ResultOf n payload) -> n ()) -> m (WorkerConfig n payload)Source #

Arguments

:: (MonadArbiter n, MonadIO m) 
=> Int

Worker count

-> Int

Batch size: max jobs per batch. A batch is one group, or ungrouped jobs.

-> (NonEmpty (JobRead payload) -> BatchCallbacks n payload (ResultOf n payload) -> n ())

Batch handler

-> m (WorkerConfig n payload) 

Create a WorkerConfig for batched job processing, no worker transaction. The handler receives the batch and a BatchCallbacks record to finalize each job. To store a result per job, ack with ackWith or ackAllWith.

withHooks :: forall (m :: Type -> Type) payload. (ObservabilityHooks m payload -> ObservabilityHooks m payload) -> WorkerConfig m payload -> WorkerConfig m payloadSource #

Transform a pool's observability hooks.

withMaintenance :: MonadUnliftIO m => (MaintenanceOp -> Int64 -> m ()) -> WorkerConfig m payload -> WorkerConfig m payloadSource #

Run report before the pool's own maintenance callback. Each runs even when the other fails.

data HandlerMode (m :: Type -> Type) payloadSource #

Job claim and handler mode. Set by transactionalWorkerConfig, manualWorkerConfig and batchedWorkerConfig.

Constructors

SingleJobMode (JobHandler m payload (ResultOf m payload))

Automatic single-job mode: claim one job per group and run the handler in a worker transaction, storing its result and acking atomically.

BatchedJobsMode

Batched callback mode: claim batches of up to N jobs and hand each batch to the handler with a BatchCallbacks record to finalize each job. No worker transaction. A batch is one group, or ungrouped jobs. Batch size 1 is the manual single-job case.

Fields

handlerBatchSize :: forall (m :: Type -> Type) payload. WorkerConfig m payload -> IntSource #

The maximum jobs in a batch. A batch is one group, or ungrouped jobs.

type ResultOf (m :: Type -> Type) payload = ResultFor payload (RegistryOf m)#

The result type declared by payload's registry entry. It is not injective. A signature naming it needs another argument to determine payload.

validateWorkerConfig :: forall (m :: Type -> Type) payload. WorkerConfig m payload -> Either Text ()Source #

Validate all invariants required for safe worker execution. Return all violations.

defaultMaintenanceSparseInterval :: NominalDiffTimeSource #

Gap the bucket prune and the concurrency reconcile-and-prune keep.

defaultMaintenanceBucketIdle :: NominalDiffTimeSource #

Age after which an idle rate-limit bucket is pruned.

defaultMaintenanceTimeout :: NominalDiffTimeSource #

Statement timeout for one maintenance operation.

Batch callbacks

data BatchCallbacks (m :: Type -> Type) payload resultSource #

Per-job finalizers passed to a batched handler. When the handler returns, untouched jobs are reprocessed after the lease expires, and their attempt is used. When it throws, untouched jobs fail. A JobNackException releases them with their attempt restored. A JobScopedFailure that names jobs in the batch fails only those jobs. A JobGoneException that names jobs in the batch reports only those jobs as unavailable, or as cancelled after a force-cancel. Both release the rest. The With variants store a result for the job's parent rollup or its archive entry.

Each callback commits in its own transaction. Inside withDbTransaction, it runs as a savepoint in the outer transaction. The success hook fires at savepoint release. An outer rollback reprocesses the job after its lease expires.

ack, ackWith and spawn abort the handler if another worker holds the job. The abort nacks the siblings still unfinalized.

Constructors

BatchCallbacks 

Fields

hoistBatchCallbacks :: (forall a. m a -> n a) -> BatchCallbacks m payload result -> BatchCallbacks n payload resultSource #

Callbacks for m, called from n through the given natural transformation. It must be a monad morphism that keeps the worker's connection and schema. Each callback then joins the handler's transaction.

Worker state

data WorkerStateSource #

A worker pool's effective state, read off the shutdown and pause flags on its WorkerConfig. Shutdown wins, then pause, then running.

Constructors

Running

Claims and runs jobs.

Paused

Claims no new jobs.

ShuttingDown

Claims no new jobs and stops.

Instances

Instances details
Eq WorkerState Source # 
Instance details

Defined in Arbiter.Worker.WorkerState

Show WorkerState Source # 
Instance details

Defined in Arbiter.Worker.WorkerState

data WorkerRuntimeSource #

Mutable state owned by one worker pool. The config constructors allocate it.

shutdownWorkerPool :: forall m (n :: Type -> Type) payload. MonadIO m => WorkerConfig n payload -> m ()Source #

Initiate graceful shutdown of the worker pool.

Stops claiming new jobs. In-flight jobs get up to gracefulShutdownTimeout to finish. Then the pool exits.

getWorkerState :: forall m (n :: Type -> Type) payload. MonadIO m => WorkerConfig n payload -> m WorkerStateSource #

The pool's state, with a pause reported as paused.

getListenerReady :: forall m (n :: Type -> Type) payload. MonadIO m => WorkerConfig n payload -> m BoolSource #

Whether this pool's LISTEN channels are subscribed (or there is no listener).

readEffectiveState :: forall (n :: Type -> Type) payload. WorkerConfig n payload -> STM WorkerStateSource #

Logging

data LogConfigSource #

Worker log level, destination, and context.

Constructors

LogConfig 

Fields

data LogDestinationSource #

Where Arbiter writes log output.

Constructors

LogStdout

Log to stdout. The default.

LogStderr

Log to stderr.

LogFastLogger LoggerSet

Log to a custom fast-logger LoggerSet.

LogCallback (LogLevel -> Text -> [Pair] -> IO ())

Log to a user-provided callback. The callback receives the LogLevel, the plain message Text, and all structured context as [Pair] such as job information and additional context.

let cb level msg ctx = myLogger level msg ctx
in defaultLogConfig { logDestination = LogCallback cb }
LogTee LogDestination LogDestination

Emit every message to both destinations, the first one before the second.

LogDiscard

Discard all logs.

defaultLogConfig :: LogConfigSource #

Default log configuration: Info level to stdout, no additional context.

silentLogConfig :: LogConfigSource #

Silent log configuration: discards all logs.

data LogLevelSource #

Log severity levels.

Constructors

Debug

Detail for diagnosis.

Info

Normal events.

Warning

Problems that do not stop work.

Error

Failed operations.

type Pair = (Key, Value)#

A key/value pair for an Object.

(.=) :: (KeyValue e kv, ToJSON v) => Key -> v -> kv infixr 8#

Maintenance

runMaintenancePass :: (RegistryAdmissionPolicies (RegistryOf m), MonadArbiter m, RegistryTables (RegistryOf m)) => LogConfig -> (MaintenanceOp -> Int64 -> m ()) -> MaintenancePace -> NominalDiffTime -> m [MaintenanceOp]Source #

One pass of the maintenance the reaper runs, with each operation independently gated across all callers. An operation whose window has not elapsed is skipped. The pass covers every queue in the schema (schemaQueues), not only the caller's registry. Which caller wins a gate does not change the result. Returns the operations that threw. A failure for one queue is logged and not returned. A failure does not stop the pass. When the schema's queues cannot be read, no operation runs and every operation is returned.

data MaintenancePaceSource #

Gaps between runs of each kind of work, and the bucket idle age. A zero gap runs that work every pass.

Constructors

MaintenancePace 

Fields

data MaintenanceOpSource #

Which reaper op a maintenance report came from.

Constructors

RefreshGroups

Refresh the group summary rows.

SweepStaleWorkers

Delete worker rows whose heartbeat is stale.

SweepExhaustedJobs

Move exhausted jobs to the DLQ.

SweepCancelledJobs

Delete force-cancelled jobs whose lease has lapsed.

PruneRateLimitBuckets

Delete idle rate-limit buckets that are full.

ReconcileConcurrencyStale

Rebuild the concurrency counts after a crash truncated them.

ReconcilePruneConcurrency

Recount the concurrency keys, then delete the unused ones.

PurgeArchives

Delete expired archived jobs.

PurgeDLQ

Delete DLQ jobs older than their queue's DLQ retention.

maintenanceOpName :: MaintenanceOp -> TextSource #

The op's stable name, used to coordinate replicas and to label its metrics.

Cron

data CronJob payloadSource #

A cron schedule. Built with cronJob, or cronJobInTimezone for a non-UTC one.

Constructors

CronJob 

Fields

Instances

Instances details
Generic (CronJob payload) Source # 
Instance details

Defined in Arbiter.Worker.Cron.Types

Associated Types

type Rep (CronJob payload) 
Instance details

Defined in Arbiter.Worker.Cron.Types

Methods

from :: CronJob payload -> Rep (CronJob payload) x#

to :: Rep (CronJob payload) x -> CronJob payload#

type Rep (CronJob payload) Source # 
Instance details

Defined in Arbiter.Worker.Cron.Types

data OverlapPolicySource #

Cron tick overlap policy.

Constructors

SkipOverlap

At most one pending or running job per schedule.

AllowOverlap

One job per tick. Concurrent execution of prior ticks is allowed.

Instances

Instances details
Eq OverlapPolicy Source # 
Instance details

Defined in Arbiter.Worker.Cron.Types

Generic OverlapPolicy Source # 
Instance details

Defined in Arbiter.Worker.Cron.Types

Associated Types

type Rep OverlapPolicy 
Instance details

Defined in Arbiter.Worker.Cron.Types

type Rep OverlapPolicy = D1 ('MetaData "OverlapPolicy" "Arbiter.Worker.Cron.Types" "arbiter-worker-0.1.0.0-inplace" 'False) (C1 ('MetaCons "SkipOverlap" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "AllowOverlap" 'PrefixI 'False) (U1 :: Type -> Type))
Show OverlapPolicy Source # 
Instance details

Defined in Arbiter.Worker.Cron.Types

type Rep OverlapPolicy Source # 
Instance details

Defined in Arbiter.Worker.Cron.Types

type Rep OverlapPolicy = D1 ('MetaData "OverlapPolicy" "Arbiter.Worker.Cron.Types" "arbiter-worker-0.1.0.0-inplace" 'False) (C1 ('MetaCons "SkipOverlap" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "AllowOverlap" 'PrefixI 'False) (U1 :: Type -> Type))

data BackfillPolicySource #

Bound on replay of missed ticks. Applies at startup and mid-flight.

Constructors

NoBackfill

Drop missed minutes silently. Default.

Backfill NominalDiffTime

Replay minutes missed in the last given number of seconds. A SkipOverlap schedule replays only the earliest one.

Instances

Instances details
Eq BackfillPolicy Source # 
Instance details

Defined in Arbiter.Worker.Cron.Types

Generic BackfillPolicy Source # 
Instance details

Defined in Arbiter.Worker.Cron.Types

Associated Types

type Rep BackfillPolicy 
Instance details

Defined in Arbiter.Worker.Cron.Types

type Rep BackfillPolicy = D1 ('MetaData "BackfillPolicy" "Arbiter.Worker.Cron.Types" "arbiter-worker-0.1.0.0-inplace" 'False) (C1 ('MetaCons "NoBackfill" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "Backfill" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 NominalDiffTime)))
Show BackfillPolicy Source # 
Instance details

Defined in Arbiter.Worker.Cron.Types

type Rep BackfillPolicy Source # 
Instance details

Defined in Arbiter.Worker.Cron.Types

type Rep BackfillPolicy = D1 ('MetaData "BackfillPolicy" "Arbiter.Worker.Cron.Types" "arbiter-worker-0.1.0.0-inplace" 'False) (C1 ('MetaCons "NoBackfill" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "Backfill" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 NominalDiffTime)))

data TickKindSource #

Whether a tick is for the current minute or a replay of a past minute.

Constructors

Live

The current scheduler minute.

Replay

A past minute, replayed at startup or in mid-flight catch-up.

Instances

Instances details
Eq TickKind Source # 
Instance details

Defined in Arbiter.Worker.Cron.Types

Generic TickKind Source # 
Instance details

Defined in Arbiter.Worker.Cron.Types

Associated Types

type Rep TickKind 
Instance details

Defined in Arbiter.Worker.Cron.Types

type Rep TickKind = D1 ('MetaData "TickKind" "Arbiter.Worker.Cron.Types" "arbiter-worker-0.1.0.0-inplace" 'False) (C1 ('MetaCons "Live" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "Replay" 'PrefixI 'False) (U1 :: Type -> Type))
Show TickKind Source # 
Instance details

Defined in Arbiter.Worker.Cron.Types

type Rep TickKind Source # 
Instance details

Defined in Arbiter.Worker.Cron.Types

type Rep TickKind = D1 ('MetaData "TickKind" "Arbiter.Worker.Cron.Types" "arbiter-worker-0.1.0.0-inplace" 'False) (C1 ('MetaCons "Live" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "Replay" 'PrefixI 'False) (U1 :: Type -> Type))

cronJob :: Text -> Text -> OverlapPolicy -> (TickKind -> UTCTime -> JobWrite payload) -> Either String (CronJob payload)Source #

Arguments

:: Text

Schedule name (used in dedup keys and logging)

-> Text

Cron expression (5-field: minute hour day-of-month month day-of-week)

-> OverlapPolicy

Overlap policy

-> (TickKind -> UTCTime -> JobWrite payload)

Job builder. Receives the tick kind and the tick time.

-> Either String (CronJob payload) 

Build a CronJob evaluated in UTC. A bad expression returns Left. cronJobInTimezone is the local-time form. Set backfill and initiallyEnabled by record update.

cronJob "nightly-report" "0 3 * * *" SkipOverlap
  (\_kind _tick -> defaultJob (GenerateReport "nightly"))

cronJobInTimezone :: Text -> Text -> Text -> OverlapPolicy -> (TickKind -> UTCTime -> JobWrite payload) -> Either String (CronJob payload)Source #

Arguments

:: Text

Schedule name

-> Text

IANA tz name (e.g. "America/New_York")

-> Text

Cron expression (5-field)

-> OverlapPolicy

Overlap policy

-> (TickKind -> UTCTime -> JobWrite payload)

Job builder

-> Either String (CronJob payload) 

Like cronJob but evaluated in a specific timezone. The tz name is validated eagerly against the bundled tzdata database.

overlapPolicyToText :: OverlapPolicy -> TextSource #

Convert an OverlapPolicy to its text representation.

validateCronScheduleUpdate :: CronScheduleUpdate -> Either Text ()Source #

Check a cron patch before it is written. At tick time a bad override stops firing or falls back to the default.

updateCronScheduleChecked :: MonadArbiter m => Text -> CronScheduleUpdate -> m (Either Text Int64)Source #

updateCronScheduleUnchecked behind validateCronScheduleUpdate. Returns rows affected, 0 for an unknown name or an empty patch.