Repository navigation
A major release. Read Upgrading from 2.x to 3.0 before
upgrading; it starts with the order to do things in. In short:
- Dependencies: sqlx 0.9 (your own sqlx must move too) and Rust 1.94.0.
- Features: no default features (add the ones you use, or
full);azure-kvis
azure-kmsandtestistest-queue. - API:
DatabaseQueueandMigrationRunnerare sealed; onedequeue(&DequeueOptions)
replaces the five claim methods; configuration, statistics and growing enums are
#[non_exhaustive]with builders; zero rates and other invalid settings are rejected
by fallible constructors;cargo-hammerworkis binary-only andhammerwork-webexposes
only the dashboard types. - Database: migration 021 moves MySQL to
DATETIME(6)(no year-2038 limit; MySQL
8.0.13+), rewriting every table. Stop 1.x workers first: the stale-job reaper now
relies on leases alone (cargo hammerwork maintenance check --fixrepairs rows 1.x
left behind).
Breaking changes
See Upgrading from 2.x to 3.0 for what to change.
- sqlx 0.9 (#85). Hammerwork now depends on sqlx 0.9 instead of 0.8. sqlx types are
part of the public API (the pool passed toJobQueue::new,JobQueue<DB: sqlx::Database>,
DatabaseQueue, the migration runners, sqlxEncode/DecodeforJobStatusand
BatchStatus), so applications must move their own sqlx dependency to 0.9 as well.
sqlx 0.9 also changes behaviour that applications may notice through their own sqlx
code: the combined runtime/TLS features are gone (runtime-tokio-rustlsis now
runtime-tokio+tls-rustls-ring-webpki), dynamic SQL strings must be wrapped in
sqlx::AssertSqlSafe, MySQL RSA password encryption for non-TLS connections needs the
mysql-rsafeature (the CLI and dashboard enable it; the library does not), and MySQL
connections no longer force theutf8mb4_unicode_cicollation. No schema migration is
needed. - MSRV is now Rust 1.94.0 (was 1.91.1), as required by sqlx 0.9.
- No features are enabled by default (#92).
defaultused to be["metrics", "alerting", "webhooks"], which pulledwarp,reqwest,lettreandprometheusinto every build. It is now empty. Enable what you use, or the newfullfeature (metrics,alerting,webhooks,streaming,tracing,encryption; no database backend, KMS key source, event stream sink ortest-queue). Code that useshammerwork::metrics,hammerwork::alertingorhammerwork::webhooksmust now enable those features.cargo-hammerworkandhammerwork-webenable the features they need themselves. azure-kvis renamedazure-kms, to matchaws-kms,gcp-kmsandvault-kms(#92). There is no alias. The same rename applies to thecargo-hammerworkfeature.testis renamedtest-queue, after theTestQueueit enables (#92). There is no alias.DatabaseQueueis sealed (#87). It can no longer be implemented outside Hammerwork; its implementations areJobQueue<Postgres>,JobQueue<MySql>andTestQueue, and it stays usable as a bound (impl DatabaseQueue,Q: DatabaseQueue). UseTestQueue(featuretest-queue) to test without a database. The default method bodies that existed only for implementations outside the crate are gone (the one-at-a-time fallback for batch claims, and the fallbacks ofrelease_job_run,finish_job_run,complete_job_run_with_children,heartbeat_job,start_claimed_run,requeue_stale_jobs,purge_expired_encrypted_jobs,purge_archived_jobs_in_queueandlist_queue_names); every backend implements these methods, so behaviour does not change.queue::RELEASED_JOB_MESSAGE, the error message of the removedrelease_job_runfallback, is removed.- One claim method (#89).
DatabaseQueue::dequeue(queue, &DequeueOptions)replacesdequeue,dequeue_with_priority_weights,dequeue_leased,dequeue_batchanddequeue_batch_leased. It returnsVec<Job>. The new#[non_exhaustive]queue::DequeueOptions(alsohammerwork::DequeueOptions) haspriority_weights: Option<PriorityWeights>,lease_duration: Option<Duration>(NoneisDEFAULT_LEASE_DURATION) andmax_jobs(default 1), built withDequeueOptions::new()/default(),DequeueOptions::batch(n),with_priority_weights,with_lease_durationandwith_max_jobs.DatabaseQueue::dequeue_one(queue)keeps the common case short: it is the 2.xdequeue(queue)(one job, strict priority, default lease,Option<Job>). Claims behave as before: amax_jobsof 1 claims like the old single-job methods, larger values like the old batch claims. Worker::with_batch_processing_enabledis removed (#89).with_batch_size(n)enables batch mode, as it already did;with_batch_size(1)replaceswith_batch_processing_enabled(true)(batch statistics, one job per poll). New optional[worker]settingsbatch_sizeandbatch_concurrency(WorkerConfig::batch_size,WorkerConfig::batch_concurrency) set them from configuration throughWorker::with_config;WorkerConfigis now non-exhaustive (see below), so build it withWorkerConfig::default().with_batch_size(n).Worker::with_max_retriesis renamedwith_attempt_cap(#89), since it can only lower a job's ownmax_attempts. There is no alias.- MySQL times are stored as
DATETIME(6)(#91). Migration021_mysql_datetimeconverts everyTIMESTAMPcolumn of every Hammerwork table toDATETIME(6), which lifts the year-2038 limit: jobs can be scheduled, retried, leased and archived up to year 9999. The MySQL backend no longer rejects times after 2038-01-19 03:14:07 UTC (and no longer caps leases at it); only times outside years 1000 to 9999 are rejected withHammerworkError::InvalidJobPayload. MySQL rebuilds every table for the conversion, which blocks writes while it runs: run it off-peak. Its UTC column defaults need MySQL 8.0.13 or later. See MySQL timestamps (migration 021). - Configuration, statistics and record structs are
#[non_exhaustive](#81). Code outside Hammerwork can no longer build them with struct literals (not even with..Default::default()); fields stay public for reading and assignment. Configuration types are built fromdefault()or a constructor andwith_*methods, and every field of the configuration sections now has one: new setters onDatabaseConfig,WorkerConfig,WebhookConfigs,WebhookGlobalSettings,StreamingConfigs,StreamingGlobalSettings,ArchiveConfig,RateLimitingConfig(pluswith_queue_throttle),LoggingConfig,PayloadEncryptionConfig(pluswith_decryption_key),KeyRotationConfig,EventConfig,webhooks::RetryPolicy,WebhookManagerConfig,StreamRetryPolicy,BufferConfig,StreamManagerConfig,SpawnConfig,StatsConfigandKeyDerivationConfig;HammerworkConfig::with_database/with_worker/... for each section;KeyManagerConfig::with_key_derivation_config; and new constructorsJobError::new,JobLifecycleEvent::new(with setters),stats::JobEvent::new(with setters),ExternalKmsConfig::new(withwith_auth,with_auth_config,with_region,with_namespace),Migration::new,ArchivedJob::newandSpawnContext::new.stats::QueueStatsnow implementsDefault. Also non-exhaustive:Job,ResultConfig,JobBatch,JobGroup,EventFilter,WebhookConfig,StreamConfig,AlertingConfig,SmtpConfig,ArchivalPolicy,ArchivalConfig,EncryptionConfig,KeyManagerConfig,KeyAuditFilter,MetricsConfig,TracingConfig,ThrottleConfig,AutoscaleConfigandJobEventHooks(all already had builders), every*Statsstruct, and the recordsWebhookDelivery,StreamDelivery,StreamedEvent,EncryptionKey,KeyAuditRecord,KmsWrappedKey,EncryptionMetadata,EncryptedPayload,MigrationRecord,QueuePauseInfo,RecordedOutcome,SpawnResult,JobHookEvent,JobResult,BatchResult,AlertandScheduledArchivalPass. Inhammerwork-web,DashboardConfig,AuthConfigandWebSocketConfigare non-exhaustive, with new setters (DashboardConfig::with_pool_size,with_auth_config,with_websocket,with_allowed_origins; every field ofAuthConfigandWebSocketConfig;AuthConfig::disabled()). See the upgrade guide for the full list. ArchivedJobhasworkflow_idandworkflow_name(#81), filled in bylist_archived_jobson PostgreSQL, MySQL andTestQueuefrom the archive columns added in migration 019. No migration is needed.- Dropping an
EventSubscriptionunregisters it from itsEventManager(#81), sosubscription_countandpayload_wantedstop counting it without a call toEventManager::unsubscribe(which still works). The subscription holds a private reference to the manager's registry, so itsreceivercan no longer be moved out of it: keep the subscription alive while reading. The registry uses a std lock, so dropping works outside a Tokio runtime. The webhook and stream managers' listeners hold their subscriptions and unregister when they stop. WebhookStats::failed_attemptsandWebhookManagerStats::failed_attempts(#81) count failed HTTP delivery attempts, retries included, next tototal_attemptsand the per-delivery counters.GET /api/system/metricsdropsmetrics_endpoint,custom_metrics_countandlast_scrape(#81). The dashboard serves no/metricsroute and keeps no metrics registry, so they were always"/metrics",nullandnull; onlyprometheus_enabledremains.- Growing public enums are
#[non_exhaustive](#86). Amatchon them outside Hammerwork needs a wildcard arm. Affected: the errorsHammerworkError,EncryptionError,CronError,SpawnErrorandPriorityError; the eventsJobLifecycleEventType,stats::JobEventType,ArchiveEvent,AlertTypeandKeyOperation; and the backend and option choicesStreamBackend,SerializationFormat,PartitioningStrategy,PartitionField,AlertTarget,SecretSource,WebhookAuth,HttpMethod,KeySource,RetentionPolicy,EncryptionAlgorithm,PayloadEncryptionAlgorithm,KeyPurpose,ArchivalReason,PartialFailureMode,ResultStorage,PrioritySelectionStrategy,RetryStrategy,JitterType,FailurePolicy,JobHandlerType,JobTransitionandJobOutcome.JobStatus,JobPriority,BatchStatus,WorkflowStatus,DependencyStatus,KeyStatus,AlertSeverityandSmtpTlsstay exhaustive. The CLI refuses towebhook testa webhook whose HTTP method or authentication it does not know, and the dashboard shows an unknown job event as statusUnknownand does not forward unknown archive events. - The stale job reaper relies on leases alone (#90).
DatabaseQueue::requeue_stale_jobs(queue_name: Option<&str>)loses itsolder_thanargument and takes an optional queue name (Nonereclaims every queue;Some(name)reclaims that queue only, atomically with the lease check): theolder_thanwindow only reclaimedRunningjobs that Hammerwork 1.x had claimed without a lease, and that fallback is gone. ARunningjob without a lease of its current run is never reclaimed; every 2.x and 3.x claim writes one.WorkerPool::with_stale_job_reaper(interval)loses itsolder_thanargument too. Stop every 1.x worker before upgrading, and fix the jobs they leftRunningonce (cargo hammerwork maintenance check --fix, or the SQL in the upgrade guide). - CLI:
job requeue-stale --older-than-secsandmaintenance dead-jobs --stale-hoursare removed (#90). Both commands now use the lease rule only: aRunningjob is stale when its lease expired; lease-less jobs (left by 1.x) are left alone.maintenance checkreportsRunningjobs without a lease, andcheck --fixgives them a lease that has already expired, so the next reaper pass reclaims them. AlertingConfig::email(recipient)is removed (#90), deprecated since 2.0.0 because the target it created could not send. Useemail_via_smtp(recipient, SmtpConfig).AlertTarget::Email::smtpis now a requiredSmtpConfig(wasOption<SmtpConfig>), so a configuration file with an email target without ansmtptable fails to load with a missing-field error instead of a "no SMTP settings" validation error.JobArchiverholds no pool (#90).JobArchiver::new()takes no argument andJobArchiveris no longer generic over the database (JobArchiver<DB>is nowJobArchiver); the pool it took was never used, since every operation takes the queue it works on.JobArchivernow implementsDefault.- Zero rates are rejected (#90). A rate limit of zero never refilled, so a worker using it waited forever.
RateLimit::per_second,per_minuteandper_hournow returnResult<RateLimit>and fail withHammerworkError::Configfor zero.RateLimit's fields are private (read them withrate(),per()andburst_limit()), so a zero rate cannot be built. For an enabledThrottleConfigwithrate_per_minute(0): the newThrottleConfig::validateandRateLimitingConfig::validatereject it,ThrottleConfig::to_rate_limitreturnsResult<Option<RateLimit>>,Worker::with_throttle_configandWorker::with_hammerwork_confignow returnResult<Worker>,JobQueue::set_throttlefails, andHammerworkConfig::from_fileandWorkerPool::from_hammerwork_configreject such arate_limitingsection. MigrationRunneris sealed (#90). Hammerwork's migrations exist for PostgreSQL and MySQL only, so the trait is implemented byPostgresMigrationRunnerandMySqlMigrationRunneronly; it stays usable as a bound and asBox<dyn MigrationRunner<DB> + Send + Sync>. The no-op default bodies ofacquire_migration_lockandrelease_migration_lockare removed; both runners implement them.- Configuration sections follow the features (#130).
[webhooks],[streaming],[alerting]and[metrics]are optional in aHammerworkConfigfile when their feature is enabled (they were required), so a file saved by a build without the feature loads. Without the feature,HammerworkConfig::from_filenow fails withHammerworkError::Confignaming the feature to enable, instead of silently ignoring the section. config::SimpleEventFilteris removed (#130). It existed only in builds withoutwebhooksand was never used; useevents::EventFilter.RateLimit::with_burst_limitreturnsResult<RateLimit>and rejects 0 (it acted as 1) withHammerworkError::Config(#130).Worker::with_attempt_capreturnsResult<Worker>and rejects a cap below 1 withHammerworkError::Config(it silently acted as 1) (#130).WebhookManager::newandnew_defaultreturnResult<WebhookManager>instead of panicking when the HTTP client cannot be built;WebhookManager::try_newis removed (#130).WorkerConfig::validatechecks the batch settings (#130):batch_size = 0(which turned batch mode on), abatch_sizeaboveMAX_DEQUEUE_BATCH_SIZEandbatch_concurrency = 0are rejected byHammerworkConfig::from_file/from_env,WorkerPool::from_hammerwork_configand now alsoWorker::with_hammerwork_config.cargo-hammerworkis a binary only (#88). The crate no longer has a library target:cargo_hammerwork::commands,cargo_hammerwork::config,cargo_hammerwork::utilsand their glob re-exports are gone. Drive the CLI through thecargo hammerworkexecutable.hammerwork-webexposes onlyWebDashboard,DashboardConfig,AuthConfigandWebSocketConfig(#88). Theapi,auth,config,live,security,serverandwebsocketmodules are private, sohammerwork_web::server::app_routesand the other building blocks are no longer public API.WebSocketConfigis now re-exported at the crate root (it washammerwork_web::config::WebSocketConfig). There is no__privatere-export of the internals either: the crate's own tests live inside the crate.
Added
job requeue-stale --queue <name>and--dry-run.--queuereclaims only that queue's stale jobs throughrequeue_stale_jobs(Some(queue));--dry-runcounts the jobs the lease rule would reclaim without changing anything. Both use bound parameters on PostgreSQL and MySQL.migration statuslists applied and pending migrations, fromhammerwork_migrationsagainst the migrations the library registers, plus applied migrations this version does not know (it used to list the columns ofhammerwork_jobs; the column count remains as a summary). NewMigrationManager::registered_migrations()returns the registered migrations without touching the database.
Fixed
-
monitor healthandqueue healthno longer call aRunningjob stuck after an hour. A job is stuck when its lease expired (the rule ofrequeue_stale_jobs, shared withmaintenance dead-jobsandjob requeue-stale), so a long-running job that keeps heartbeating is healthy.Runningjobs without a lease (left by a Hammerwork 1.x worker) are reported on their ownLease-less Running Jobsrow;monitor health --format jsongains that check. -
Building with
encryptionbut no database backend (and so withfull) no longer fails clippy with dead-code and unused-import errors. -
Workers publish lifecycle events to an
EventManager(Worker::with_event_manager) without thewebhooksfeature, so streaming-only builds and plainEventManagersubscribers get them (#130). In 2.x this worked only becausewebhookswas a default feature. -
TestQueue::set_throttle_configrejects an enabled throttle with a zero rate, as the PostgreSQL and MySQL queues do (#130). -
DatabaseQueue::list_queue_namesreturns the names in byte (str) order without duplicates on every backend; PostgreSQL and MySQL sorted them by the database collation, so the two could disagree with each other and withTestQueue(#130). -
The MySQL key manager writes and compares key times with
UTC_TIMESTAMP(6)instead of the session-localNOW(6), now that the columns areDATETIME(6)(#130).
Changed
hammerwork-webno longer enables hammerwork'smetricsfeature, which it never used (the dashboard still reportsprometheus_enabled: false).config/init-mysql.sqlandconfig/init-postgres.sql(docker-compose) no longer create ahammerwork_jobstable and a seed job: the schema comes fromcargo hammerwork migration run, so it always matches the library. The MySQL script only grants the user's permissions.- CI checks the feature combinations
full,postgres,full,mysql,fullandtest-queue. - The library no longer depends on the
rsacrate (RUSTSEC-2023-0071), which sqlx 0.9
made optional. It stays in the lockfile only through the CLI and dashboard binaries.
Database
- Migration
021_mysql_datetime(#91): on MySQL, converts all 39TIMESTAMP/TIMESTAMP(6)columns ofhammerwork_jobs,hammerwork_jobs_archive,hammerwork_batches,hammerwork_encryption_keys,hammerwork_key_audit_log,hammerwork_kms_data_keys,hammerwork_queue_pauseandhammerwork_migrations, and the 3 second-precisionDATETIMEcolumns ofhammerwork_workflows(created_at,completed_at,failed_at), toDATETIME(6), keeping nullability, values and defaults (CURRENT_TIMESTAMPdefaults becomeCURRENT_TIMESTAMP(6);hammerwork_queue_pause.updated_atkeepsON UPDATE). OneALTER TABLEper table, run under a UTC sessiontime_zone, safe to re-run. A no-op on PostgreSQL.