feat(engine): add renew operation to StoreHandler#1792
Merged
Conversation
…stStoreHandler The engine test store handler is reused across multiple specs to exercise store lock/unlock/watch (introduced in PR #1790). Each worker constructs its own TestStoreHandler, so per-store state — entries, watchers, locks — must be shared via TestStoreContext to give cross-worker semantics that match what production store implementations (memory, redis, hazelcast) provide. Extends TestStoreContext to supply per-store watcher and lock maps (mirroring the existing supplyEntries pattern, keyed by storeConfig.id). TestStoreHandler now stores TestWatcher records that carry the registering worker's signaler, so a put on worker A correctly dispatches the listener back onto the registering worker's I/O thread instead of firing inline on whichever worker performed the put. Without this, a listener registered on worker A but fired by worker B would access state from the wrong thread, violating the engine's single-threaded-per- worker contract. The new TestLockEntry record carries the lease token and expiresAt for ownership-checked unlock. TestStoreHandler.lock uses ConcurrentMap.compute for atomic acquire-or-fail; unlock uses ConcurrentMap.computeIfPresent with a token check so a worker that never acquired the lock cannot release another worker's lock. This brings the in-tree test store implementation up to the same contract surface as the lock/unlock/watch SPI requires, so spec-level ITs that rely on those operations can be written against `type: test` without depending on `store-memory` for correctness.
Adds StoreHandler.renew(key, token, ttl, completion) to the engine SPI, following the same ownership-checked, async-completion contract as unlock. Callers that hold a coordination lock for longer than its initial TTL — e.g. a singleton worker that owns a binding-scoped resource for the lifetime of the binding — schedule renewals at an interval shorter than the lease TTL. A failed renewal signals that ownership has been lost (the lock was reacquired by another holder after a TTL expiry), giving callers a deterministic cue to surrender state and let the new owner take over. store-memory and the engine TestStoreHandler implement renew with an atomic ConcurrentMap.replace against the previously-observed LockEntry: if the token matches the unexpired current holder, the entry is replaced with a renewed expiresAt and the original token is returned; otherwise null is returned. Expired entries are evicted opportunistically, mirroring the unlock cleanup behaviour. TestBindingFactory gains a renew assertion alongside the existing lock/unlock/watch ops. The new spec config store-memory.spec/config/ store.renew.yaml exercises the full acquire-renew-release cycle for the IT.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
StoreHandler.renew(key, token, ttl, completion)to the engine SPI for callers that hold a coordination lock longer than its initial TTL — they schedule renewals at an interval shorter than the lease TTL, and a failed renewal signals that ownership has been lost so the caller can surrender state and let a new owner take over.type: testwithout depending onstore-memoryfor correctness. TestStoreContext now supplies per-store watcher and lock maps shared across workers (mirroring the existingsupplyEntriespattern); the newTestWatcherrecord carries the registering worker's signaler so cross-worker notify dispatches listener invocations onto the right thread.store-memoryandTestStoreHandlerboth implementrenewwith an atomicConcurrentMap.replaceagainst the previously-observed lock entry — token match under unexpired TTL returns the original token; mismatch or expiry returns null. Expired entries are evicted opportunistically, matching the unlock cleanup behaviour.Commits
02630399test(engine): share watchers and locks per store across workers in TestStoreHandler— the cross-worker test infrastructure that the renew SPI tests depend on.aba18122feat(engine): add renew operation to StoreHandler— the SPI method plus store-memory and TestStoreHandler implementations, TestBindingFactory renew assertion, and thestore.renew.yamlspec config.Consumer
This SPI is consumed by
binding-mcp's cache lifecycle-lock-hold change (on a separate branch, depends on this PR). That change schedulesrenewatleaseTtl / 3for the worker that owns a binding's upstream lifecycle stream, so a single cache owner stays uninterrupted during normal operation and a holding node's crash triggers TTL-bounded takeover by another worker.Test plan
./mvnw clean verify -pl runtime/store-memory -am—MemoryStoreHandlerIT5/5 (including newshouldRenew)../mvnw clean install -DskipTests -DskipITs -pl runtime/engine -am— engine compiles after both commits.https://claude.ai/code/session_01Gx5yC2CuFd54Fyoy7kL3qg
Generated by Claude Code