* refactor(ecstore): migrate the background-services cancel token into InstanceContext (Phase 5 Slice 13) Phase 5 Slice 13 (backlog#939): move the background-services cancellation token out of the process static into the per-instance InstanceContext, so cancelling one instance's background workers (scanner/heal/tier/lifecycle) no longer touches another instance. - InstanceContext gains `background_cancel_token: OnceLock<CancellationToken>` with `init_background_cancel_token` (set-once) and `background_cancel_token()` returning an owned clone. - global.rs `init_/get_/create_/shutdown_` helpers keep their signatures and route through the current instance's context; the static is removed. The getter now returns an owned `Option<CancellationToken>` instead of a `Option<&'static _>`, which is what lets the token live in the context. - Callers adapt to the owned token: the metadata-refresh loop drops `.cloned()`; the lifecycle worker/loops take the owned token (a shared fallback is cloned when the token is somehow uninitialized). No Arc cycle is introduced — workers hold a token clone, not the instance context. Single-instance behavior is unchanged: startup creates one token in the bootstrap context the ECStore adopts, and shutdown cancels that same token. Tests: the token is set-once and cancelling one instance's token leaves a distinct instance uninitialized. Verification: cargo test -p rustfs-ecstore (23 instance-context tests green), cargo clippy -p rustfs-ecstore --all-targets (clean), make pre-commit (pass). Refs: backlog#939 (Phase 5, Slice 13) * test(ecstore): prove multi-instance isolation; document embedded guard retention (Phase 5 Slice 14) (#4588) Phase 5 (backlog#939) capstone. The prior 13 slices moved every piece of per-instance runtime state out of process globals into ECStore's InstanceContext. This slice proves the result and records the remaining work. - Add `two_instances_isolate_all_migrated_state`: an end-to-end acceptance test that constructs two independent InstanceContexts and verifies NONE of the migrated state is shared — erasure setup, lock manager, region, deployment id, the four service handles (tier/notifier/expiry/transition), the local disk registry, the bucket monitor, and the background cancel token. This is the object-graph isolation carrier working end to end. - Document why the embedded single-instance guard (EMBEDDED_SERVER_STARTED) is intentionally retained: storage startup still publishes into the process-level bootstrap context (write-once region/endpoints/deployment id) and the single GLOBAL_OBJECT_API handle, so a second startup would fail-fast on that shared state. Lifting the guard requires threading a per-instance context through startup — a follow-up beyond migrating the globals. The guard is NOT removed: rejecting the second start is safer than the panic it would otherwise become. Verification: cargo test -p rustfs-ecstore (acceptance test + all instance-context tests green), cargo clippy -p rustfs-ecstore --all-targets (clean), make pre-commit (pass). Refs: backlog#939 (Phase 5, Slice 14). Stacked on Slice 13 (#4586).
ECStore Lifecycle Split Inventory
This directory still belongs to ECStore. It is not ready to become a standalone crate because lifecycle workers currently depend on ECStore runtime state, object IO, bucket metadata, replication scheduling, notification/audit sinks, and tier services.
Current Modules
| Module | Current role | Split blocker |
|---|---|---|
core.rs |
Lifecycle rule model, action evaluation, object options, and transition/expiry decisions. | Uses ECStore object metadata types and compatibility DTO re-exports. |
bucket_lifecycle_ops.rs |
Worker orchestration, expiry, transition, stale multipart cleanup, audit, replication delete scheduling, and queue state. | Depends on ECStore, SetDisks, runtime globals, bucket metadata/versioning, disk internals, event notification, tier services, and lifecycle-local object-lock/replication boundaries. |
evaluator.rs |
Bucket lifecycle evaluation wrapper. | Uses lifecycle-local object-lock boundary and replication state through lifecycle-local replication sink. |
rule.rs |
Lifecycle rule filter helpers. | Uses lifecycle-local tagging boundary. |
tier_delete_journal.rs |
Remote tier delete journal persistence and recovery. | Uses lifecycle-local config persistence boundary, object IO contracts, metadata bucket paths, and ECStore. |
tier_free_version_recovery.rs |
Free-version recovery queue and object restoration path. | Depends on ECStore, object metadata, storage-api contracts, and lifecycle queue callbacks. |
tier_last_day_stats.rs |
Tier statistics helpers. | Pure data/stat logic, but still part of lifecycle worker reporting. |
tier_sweeper.rs |
Remote tier deletion worker and transition cleanup. | Depends on runtime sources, ECStore, tier journal persistence, signer-error handling, and lifecycle object options. |
bucket_lifecycle_audit.rs |
Lifecycle audit event source labels. | Must remain wired to lifecycle audit and notification sinks. |
Required Contracts
| Contract | Responsibility | Current dependency to remove |
|---|---|---|
LifecycleObjectStore |
Object stat, delete, transition, restore, multipart cleanup, and version-aware metadata operations. | Direct ECStore, SetDisks, disk, and object API access in worker paths. |
LifecycleMetadataStore |
Lifecycle, object-lock, replication, bucket versioning, and stale multipart metadata reads. | Direct bucket metadata/versioning imports; object-lock and replication are still backed by local ECStore boundaries. |
LifecycleRuntime |
Expiry state, transition state, tier config, deployment ID, local node name, queue metrics, cancellation, and worker sizing. | Direct runtime source/global access and process environment reads inside worker code. |
LifecycleConfigStore |
Persist, read, and remove lifecycle-owned journal/config objects. | Direct ECStore config persistence helper imports from worker paths. |
LifecycleTagFilter |
Decode object tag strings for lifecycle rule matching. | Direct bucket tagging helper imports from lifecycle rule paths. |
LifecycleObjectLockStore |
Object-lock retention and deletion checks used by lifecycle evaluation and worker deletion paths. | Direct object-lock module imports from lifecycle code. |
LifecycleReplicationSink |
Lifecycle-originated delete and version-purge replication scheduling. | Boundary is local, but still backed by ECStore bucket replication internals. |
LifecycleAuditSink |
Lifecycle audit and notification event emission. | Direct event notification service calls and audit-side effects from worker code. |
Migration Rules
- Do not move
bucket/lifecycleinto a new crate while any worker importscrate::store::ECStore,crate::set_disk::SetDisks, runtime globals, or bucket replication internals directly. - Extract pure contracts before runtime movement. The first code-bearing PR should introduce one contract boundary and keep the old ECStore call path.
- Preserve lifecycle queue behavior, transition/expiry state, replication delete scheduling, notification/audit events, scanner-visible metrics, and stale multipart cleanup semantics.
- Keep
rustfs_ecstore::api::bucket::lifecyclecompatibility until scanner, OBS, and test boundary files compile through replacement paths. - Verify each code-bearing step with focused lifecycle tests before attempting broad gates.
First Code-Bearing Step
Start with LifecycleRuntime or LifecycleAuditSink. Both can be introduced
as narrow internal contracts while keeping the current ECStore worker behavior
unchanged. Do not start with a crate move.
Current first boundary: runtime_boundary.rs centralizes lifecycle access to
runtime state while preserving the existing ECStore-backed implementations.
config_boundary.rs centralizes lifecycle-owned config object persistence for
tier delete journal recovery while preserving the existing ECStore config store.
tagging_boundary.rs centralizes lifecycle tag decoding while preserving the
existing ECStore bucket tagging implementation.
object_lock_boundary.rs centralizes lifecycle object-lock checks while
preserving the existing ECStore object-lock implementation.
replication_sink.rs centralizes lifecycle-originated replication config checks
and delete scheduling while preserving the existing ECStore replication worker
path.