mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-14 10:08:35 +02:00
refactor: extract host effects adapter and define Phase 17 stop line
Move dispatcher-facing host effects out of volume_server_block.go into blockcmd while keeping server-owned cache/state semantics in weed/server. Document Batch 10 delivery and Batch 11 stop-line review so the separation line closes without over-extracting readiness-state mutation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
38b5042997
commit
1453274988
6 files changed
+940
-43
No files matched your search
@@ -0,0 +1,371 @@
|
||||
Purpose: append-only technical pack and delivery log for `Phase 17`
|
||||
post-`Phase 16` separation tracking.
|
||||
|
||||
---
|
||||
|
||||
### `Phase 17` Start Note
|
||||
|
||||
Date: 2026-04-04
|
||||
Intent: restore a continuous engineering log for the migration batches that
|
||||
followed `Phase 16` runtime closure
|
||||
|
||||
This phase is intentionally a tracking phase.
|
||||
|
||||
It records the code-separation line that ran after `Phase 16` but before a new
|
||||
single semantic/runtime claim had replaced it.
|
||||
|
||||
It exists so that:
|
||||
|
||||
1. `Batch 1-9` have one durable phase home
|
||||
2. reviews can reference a stable migration timeline
|
||||
3. the next seam can be chosen from a clear current-state snapshot
|
||||
|
||||
---
|
||||
|
||||
### Batch 1 Delivery Note
|
||||
|
||||
Date: 2026-04-04
|
||||
Scope: canonical translation and contract ownership check
|
||||
|
||||
What changed:
|
||||
|
||||
1. canonical replica identity and recovery-target translation were confirmed to
|
||||
belong to `sw-block/bridge/blockvol`
|
||||
2. adapter-side inline mapping was removed from `weed/storage/blockvol/v2bridge/control.go`
|
||||
3. the remaining Batch 1 ports (`reader`, `pinner`, `executor`) were reviewed
|
||||
and found already aligned with the intended ownership split
|
||||
|
||||
Proof / evidence:
|
||||
|
||||
1. commit `a38e04c03`
|
||||
2. no further code changes required for `Task B/C/D`
|
||||
|
||||
Conclusion:
|
||||
|
||||
1. Batch 1 closed semantic drift first, without forcing unnecessary code motion
|
||||
|
||||
---
|
||||
|
||||
### Batch 2 Delivery Note
|
||||
|
||||
Date: 2026-04-04
|
||||
Scope: backend-binding shim reduction
|
||||
|
||||
What changed:
|
||||
|
||||
1. `v2bridge.Reader` now returns `bridge.BlockVolState` directly
|
||||
2. `pinnerShimForRecovery` was removed from `weed/server/block_recovery.go`
|
||||
3. executor binding was rechecked and kept as the already-correct thin binding
|
||||
|
||||
Proof / evidence:
|
||||
|
||||
1. commit `680b53031`
|
||||
2. commit `519c84994`
|
||||
|
||||
Conclusion:
|
||||
|
||||
1. the backend-binding layer became thinner without changing semantic ownership
|
||||
|
||||
---
|
||||
|
||||
### Batch 3 Delivery Note
|
||||
|
||||
Date: 2026-04-04
|
||||
Scope: reusable recovery coordination extraction
|
||||
|
||||
What changed:
|
||||
|
||||
1. `sw-block/engine/replication/runtime/pending.go` introduced
|
||||
`PendingCoordinator`
|
||||
2. `sw-block/engine/replication/runtime/executor.go` introduced reusable
|
||||
recovery execution helpers
|
||||
3. `weed/server/block_recovery.go` was later rewired so production code uses the
|
||||
runtime helpers directly
|
||||
4. no-core execution was split into explicit legacy helpers instead of remaining
|
||||
implicit inline branches
|
||||
|
||||
Proof / evidence:
|
||||
|
||||
1. commit `6fea93e82`
|
||||
2. commit `e200df779`
|
||||
3. commit `e075d7761`
|
||||
4. commit `3a5fbbfde`
|
||||
|
||||
Conclusion:
|
||||
|
||||
1. Batch 3 only became complete after the wiring fix; the final state removes
|
||||
helper duplication from the production path
|
||||
|
||||
---
|
||||
|
||||
### Batch 4 Delivery Note
|
||||
|
||||
Date: 2026-04-04
|
||||
Scope: typed runtime boundary and host-shell reduction
|
||||
|
||||
What changed:
|
||||
|
||||
1. `PendingExecution` became fully typed
|
||||
2. type assertions and `interface{}` drift were removed from the production
|
||||
recovery path
|
||||
3. rebuild completion shaping moved into a dedicated runtime helper
|
||||
4. recovery bundle assembly inside `block_recovery.go` was further reduced into
|
||||
a bounded helper
|
||||
|
||||
Proof / evidence:
|
||||
|
||||
1. commit `0bcfc678d`
|
||||
2. commit `ded84b25e`
|
||||
|
||||
Conclusion:
|
||||
|
||||
1. Batch 4 made the recovery host shell easier to reason about and safer to
|
||||
test
|
||||
|
||||
---
|
||||
|
||||
### Batch 5 Delivery Note
|
||||
|
||||
Date: 2026-04-04
|
||||
Scope: recovery binding factory extraction
|
||||
|
||||
What changed:
|
||||
|
||||
1. concrete construction of `Reader`, `Pinner`, `StorageAdapter`, and
|
||||
`Executor` moved behind `v2bridge.BuildRecoveryBundle()`
|
||||
2. `weed/server/block_recovery.go` stopped assembling those concrete bindings
|
||||
directly
|
||||
|
||||
Proof / evidence:
|
||||
|
||||
1. commit `263611004`
|
||||
|
||||
Conclusion:
|
||||
|
||||
1. the backend-binding layer now owns its own assembly seam
|
||||
|
||||
---
|
||||
|
||||
### Batch 6 Delivery Note
|
||||
|
||||
Date: 2026-04-04
|
||||
Scope: recovery context resolver extraction
|
||||
|
||||
What changed:
|
||||
|
||||
1. `resolveRecoveryContext()` consolidated host-side context assembly
|
||||
2. inline derivation of `rebuildAddr` and related runtime inputs was removed
|
||||
3. `runCatchUp()` and `runRebuild()` now follow a simple:
|
||||
- resolve
|
||||
- plan
|
||||
- branch
|
||||
structure
|
||||
|
||||
Proof / evidence:
|
||||
|
||||
1. commit `a48da0f67`
|
||||
2. commit `41082bf92`
|
||||
|
||||
Conclusion:
|
||||
|
||||
1. the recovery host path became structurally thin enough to review by shape,
|
||||
not only by behavior
|
||||
|
||||
---
|
||||
|
||||
### Batch 7 Delivery Note
|
||||
|
||||
Date: 2026-04-04
|
||||
Scope: command dispatch extraction
|
||||
|
||||
What changed:
|
||||
|
||||
1. the `engine.Command` switch moved out of `weed/server/volume_server_block.go`
|
||||
2. new package `weed/server/blockcmd` became the server-adapter command
|
||||
dispatcher
|
||||
3. host effects remained intentionally on the server side instead of being
|
||||
pushed into `v2bridge`
|
||||
|
||||
Proof / evidence:
|
||||
|
||||
1. commit `11c6aaf31`
|
||||
|
||||
Conclusion:
|
||||
|
||||
1. Batch 7 established the correct ownership seam:
|
||||
- dispatch in server adapter
|
||||
- backend bindings elsewhere
|
||||
- host effects still local
|
||||
|
||||
---
|
||||
|
||||
### Batch 8 Delivery Note
|
||||
|
||||
Date: 2026-04-04
|
||||
Scope: `BlockVol` command-binding extraction
|
||||
|
||||
What changed:
|
||||
|
||||
1. concrete `BlockVol` command operations moved into
|
||||
`weed/storage/blockvol/v2bridge/command_bindings.go`
|
||||
2. direct `WithVolume` execution for role apply / receiver startup / primary
|
||||
replication setup stopped living in `volume_server_block.go`
|
||||
|
||||
Proof / evidence:
|
||||
|
||||
1. commit `38b504299`
|
||||
|
||||
Conclusion:
|
||||
|
||||
1. `v2bridge` now owns concrete backend command bindings, which is the correct
|
||||
side of the seam
|
||||
|
||||
---
|
||||
|
||||
### Batch 9 Delivery Note
|
||||
|
||||
Date: 2026-04-04
|
||||
Scope: non-`BlockVol` command-op extraction
|
||||
|
||||
What changed:
|
||||
|
||||
1. non-backend command operations moved into `weed/server/blockcmd/service_ops.go`
|
||||
2. dispatcher rebinding no longer requires `volume_server_block.go` to own the
|
||||
full command-op surface
|
||||
3. nil-safe service-op construction was added so typed nil pointers are not
|
||||
smuggled through interfaces
|
||||
|
||||
Proof / evidence:
|
||||
|
||||
1. commit `38b504299`
|
||||
2. focused server proofs remained green after rebinding
|
||||
|
||||
Conclusion:
|
||||
|
||||
1. after Batch 9, `volume_server_block.go` is much closer to a host shell than
|
||||
a command-runtime implementation file
|
||||
|
||||
---
|
||||
|
||||
### Batch 10 Start Note
|
||||
|
||||
Date: 2026-04-04
|
||||
Scope: `10A` host-effects adapter extraction
|
||||
|
||||
Execution rule:
|
||||
|
||||
1. extract only command-completion host effects
|
||||
2. keep the slice bounded to:
|
||||
- `RecordCommand`
|
||||
- `EmitCoreEvent`
|
||||
- `PublishProjection`
|
||||
- projection-cache write routing
|
||||
3. do not mix backend readiness mutation into the same cut unless the code
|
||||
proves it is already inseparable
|
||||
|
||||
Acceptance target:
|
||||
|
||||
1. `coreCommandEffects` disappears from `volume_server_block.go`
|
||||
2. publish-projection cache writes stop being inline there
|
||||
3. dispatcher keeps consuming a server-side host-effects object
|
||||
4. focused proofs stay green
|
||||
|
||||
Why this is the next seam:
|
||||
|
||||
1. dispatch is already extracted
|
||||
2. backend bindings are already extracted
|
||||
3. the largest remaining concentrated non-shell logic in
|
||||
`volume_server_block.go` is now host effects
|
||||
|
||||
---
|
||||
|
||||
### Batch 10 Delivery Note
|
||||
|
||||
Date: 2026-04-04
|
||||
Scope: `10A` host-effects adapter extraction
|
||||
|
||||
What changed:
|
||||
|
||||
1. concrete dispatcher-facing host effects moved into
|
||||
`weed/server/blockcmd/host_effects.go`
|
||||
2. `volume_server_block.go` now wires host-effect callbacks and projection cache
|
||||
storage into that adapter instead of defining `coreCommandEffects` locally
|
||||
3. server-owned projection cache writes moved behind
|
||||
`BlockService.StoreProjection()`
|
||||
|
||||
Proof / evidence:
|
||||
|
||||
1. `go test ./weed/server/blockcmd -count=1 -timeout 60s`
|
||||
2. `go test ./weed/server -count=1 -timeout 120s -run "TestBlockService_(ApplyAssignments|DebugInfoForVolume|CollectBlockVolumeHeartbeat|ReadinessSnapshot|HeartbeatReplicaDegraded)"`
|
||||
3. result: `PASS`
|
||||
|
||||
Conclusion:
|
||||
|
||||
1. `volume_server_block.go` is thinner again, but host-effect semantics still
|
||||
remain explicitly on the server side
|
||||
2. backend readiness mutation was intentionally left untouched in this slice
|
||||
|
||||
---
|
||||
|
||||
### Batch 11 Delivery Note
|
||||
|
||||
Date: 2026-04-04
|
||||
Scope: stop-line review for remaining readiness-state mutation
|
||||
|
||||
What was reviewed:
|
||||
|
||||
1. `noteRoleApplied`
|
||||
2. `markPrimaryTransportConfigured`
|
||||
3. `markReceiverReady`
|
||||
4. `ReadinessSnapshot` as the read-side consumer of the same local state
|
||||
|
||||
Decision:
|
||||
|
||||
1. do not extract these methods into `weed/server/blockcmd`
|
||||
|
||||
Why:
|
||||
|
||||
1. they are direct mutations of `BlockService.replStates`
|
||||
2. they belong to adapter-local host state, not dispatcher-side orchestration
|
||||
3. a further extraction would mostly replace direct method calls with callback
|
||||
plumbing while leaving ownership unchanged
|
||||
|
||||
Accepted stop line:
|
||||
|
||||
1. `blockcmd` keeps dispatch / service ops / host-effects adapter
|
||||
2. `v2bridge` keeps concrete backend bindings
|
||||
3. `weed/server` keeps local readiness/cache state and its mutation paths
|
||||
|
||||
Conclusion:
|
||||
|
||||
1. the current boundary is the correct stopping point for the separation line
|
||||
2. the next meaningful work item should be cleanup within that boundary or a new
|
||||
semantic/runtime step, not another package shuffle
|
||||
|
||||
---
|
||||
|
||||
### Current State Snapshot
|
||||
|
||||
Date: 2026-04-04
|
||||
State after Batch 10:
|
||||
|
||||
1. `sw-block` owns:
|
||||
- canonical bridge helpers
|
||||
- reusable recovery runtime helpers
|
||||
2. `weed/storage/blockvol/v2bridge` owns:
|
||||
- concrete `BlockVol` recovery bundle assembly
|
||||
- concrete `BlockVol` command bindings
|
||||
3. `weed/server/blockcmd` owns:
|
||||
- command dispatch
|
||||
- service-side command operations
|
||||
- host-effects adapter
|
||||
4. `weed/server` still owns:
|
||||
- assignment ingress
|
||||
- projection/publication cache writes
|
||||
- host-owned cache/state fields
|
||||
- product-facing integration state
|
||||
|
||||
Open next seam:
|
||||
|
||||
1. no additional ownership move is currently justified on the readiness-state
|
||||
path
|
||||
@@ -0,0 +1,411 @@
|
||||
# Phase 17
|
||||
|
||||
Date: 2026-04-04
|
||||
Status: active
|
||||
Purpose: track post-`Phase 16` code separation so adapter/runtime ownership in
|
||||
`weed/server` and backend-binding ownership in `weed/storage/blockvol/v2bridge`
|
||||
continue to converge without losing engineering continuity
|
||||
|
||||
## Why This Phase Exists
|
||||
|
||||
`Phase 14-16` established and widened one bounded `V2`-owned runtime path:
|
||||
|
||||
1. `Phase 14` made the explicit core real
|
||||
2. `Phase 15` rebound bounded integrated surfaces to consume core-owned truth
|
||||
3. `Phase 16` closed one bounded runtime path where the core owns command and
|
||||
observation semantics
|
||||
|
||||
That semantic/runtime line is now real, but a separate engineering line has
|
||||
been running in parallel:
|
||||
|
||||
1. move canonical rules toward `sw-block`
|
||||
2. thin `weed/server` into a host shell
|
||||
3. keep `v2bridge` limited to backend-binding responsibilities
|
||||
4. avoid creating a new mixed-ownership layer while the migration proceeds
|
||||
|
||||
This phase exists to track that separation line explicitly so commits, reviews,
|
||||
and next steps do not drift away from the phase log.
|
||||
|
||||
## Relationship To Phase 16
|
||||
|
||||
`Phase 16` remains the semantic/runtime-ownership phase.
|
||||
|
||||
`Phase 17` is different:
|
||||
|
||||
1. it does not redefine the bounded `V2` runtime claims from `Phase 16`
|
||||
2. it tracks code movement and ownership cleanup after those claims became real
|
||||
3. it records which adapter/binding responsibilities moved where
|
||||
|
||||
In short:
|
||||
|
||||
1. `Phase 16` answers: who owns runtime semantics?
|
||||
2. `Phase 17` answers: where does the code now live, and what is left to move?
|
||||
|
||||
## Phase Goal
|
||||
|
||||
Track and complete the post-`Phase 16` separation of command/runtime/binding
|
||||
responsibilities so the codebase approaches this split:
|
||||
|
||||
1. `sw-block`
|
||||
- owns canonical contracts, core/runtime helpers, and reusable semantics
|
||||
2. `weed/storage/blockvol/v2bridge`
|
||||
- owns concrete `BlockVol` and wire/backend bindings
|
||||
3. `weed/server`
|
||||
- owns host-shell orchestration, host effects, and product integration only
|
||||
|
||||
## Scope
|
||||
|
||||
### In scope
|
||||
|
||||
1. migration batches that thin `weed/server`
|
||||
2. migration batches that keep backend binding in `v2bridge`
|
||||
3. migration batches that move reusable coordination toward `sw-block`
|
||||
4. explicit tracking of delivered / partial / next separation seams
|
||||
|
||||
### Out of scope
|
||||
|
||||
1. changing the semantic claims already owned by `Phase 14-16`
|
||||
2. broad protocol redesign
|
||||
3. full removal of legacy compatibility paths without replacement criteria
|
||||
4. changing `blockvol` into a `sw-block` package
|
||||
|
||||
## Batch Tracking
|
||||
|
||||
### Batch 1: Contracts And Canonical Translation
|
||||
|
||||
Goal:
|
||||
|
||||
1. stabilize contract ownership and canonical identity/recovery-target mapping
|
||||
|
||||
Status:
|
||||
|
||||
1. delivered
|
||||
|
||||
Delivered result:
|
||||
|
||||
1. `sw-block/bridge/blockvol` owns the canonical contract/helper layer
|
||||
2. `Task A` removed duplicate identity/recovery-target derivation from adapter
|
||||
code
|
||||
3. `Task B/C/D` were confirmed already clean at the acceptance bar
|
||||
|
||||
Evidence:
|
||||
|
||||
1. `a38e04c03`
|
||||
2. design notes:
|
||||
- `sw-block/design/v2-first-migration-batch.md`
|
||||
- `sw-block/design/v2-first-migration-task-pack.md`
|
||||
|
||||
### Batch 2: Backend-Binding Shim Reduction
|
||||
|
||||
Goal:
|
||||
|
||||
1. remove redundant reader/pinner shims and confirm thin backend bindings
|
||||
|
||||
Status:
|
||||
|
||||
1. delivered
|
||||
|
||||
Delivered result:
|
||||
|
||||
1. `v2bridge.Reader` returns bridge contract state directly
|
||||
2. `pinnerShimForRecovery` was removed
|
||||
3. `Executor` was confirmed already clean as a direct engine-IO implementer
|
||||
|
||||
Evidence:
|
||||
|
||||
1. `680b53031`
|
||||
2. `519c84994`
|
||||
3. design notes:
|
||||
- `sw-block/design/v2-second-migration-batch.md`
|
||||
- `sw-block/design/v2-second-migration-task-pack.md`
|
||||
|
||||
### Batch 3: Recovery Runtime Helper Extraction
|
||||
|
||||
Goal:
|
||||
|
||||
1. move reusable recovery coordination out of `weed/server/block_recovery.go`
|
||||
|
||||
Status:
|
||||
|
||||
1. delivered
|
||||
|
||||
Delivered result:
|
||||
|
||||
1. `sw-block/engine/replication/runtime` owns pending coordination and reusable
|
||||
execution helpers
|
||||
2. `block_recovery.go` uses those helpers on the production path
|
||||
3. no-core behavior is structurally isolated behind explicit legacy helpers
|
||||
|
||||
Evidence:
|
||||
|
||||
1. `6fea93e82`
|
||||
2. `e200df779`
|
||||
3. `e075d7761`
|
||||
4. `3a5fbbfde`
|
||||
|
||||
### Batch 4: Typed Runtime Boundary And Host-Shell Reduction
|
||||
|
||||
Goal:
|
||||
|
||||
1. remove `interface{}` drift and simplify recovery host-shell logic
|
||||
|
||||
Status:
|
||||
|
||||
1. delivered
|
||||
|
||||
Delivered result:
|
||||
|
||||
1. `PendingExecution` is fully typed
|
||||
2. rebuild completion shaping moved into runtime helper
|
||||
3. `buildRecoveryBundle()` removed repeated host-side assembly
|
||||
|
||||
Evidence:
|
||||
|
||||
1. `0bcfc678d`
|
||||
2. `ded84b25e`
|
||||
|
||||
### Batch 5: Recovery Binding Factory Extraction
|
||||
|
||||
Goal:
|
||||
|
||||
1. move recovery bundle assembly into the backend-binding layer
|
||||
|
||||
Status:
|
||||
|
||||
1. delivered
|
||||
|
||||
Delivered result:
|
||||
|
||||
1. `block_recovery.go` no longer constructs `Reader/Pinner/StorageAdapter/Executor`
|
||||
directly
|
||||
2. `v2bridge.BuildRecoveryBundle()` became the concrete backend-binding seam
|
||||
|
||||
Evidence:
|
||||
|
||||
1. `263611004`
|
||||
|
||||
### Batch 6: Recovery Context Resolver Extraction
|
||||
|
||||
Goal:
|
||||
|
||||
1. make one resolver own the host-side recovery context
|
||||
|
||||
Status:
|
||||
|
||||
1. delivered
|
||||
|
||||
Delivered result:
|
||||
|
||||
1. `resolveRecoveryContext()` owns:
|
||||
- `volPath`
|
||||
- `rebuildAddr`
|
||||
- bundle construction
|
||||
- replica flushed progress lookup
|
||||
2. `runCatchUp()` / `runRebuild()` now read as:
|
||||
- resolve
|
||||
- plan
|
||||
- branch
|
||||
|
||||
Evidence:
|
||||
|
||||
1. `a48da0f67`
|
||||
2. `41082bf92`
|
||||
|
||||
### Batch 7: Command Dispatch Split
|
||||
|
||||
Goal:
|
||||
|
||||
1. move command-switch orchestration out of `volume_server_block.go`
|
||||
2. keep it out of `v2bridge`
|
||||
|
||||
Status:
|
||||
|
||||
1. delivered
|
||||
|
||||
Delivered result:
|
||||
|
||||
1. `weed/server/blockcmd` owns command dispatch
|
||||
2. `weed/server` keeps host effects
|
||||
3. `v2bridge` remains free of `engine.Command` switch / event-emission
|
||||
semantics
|
||||
|
||||
Evidence:
|
||||
|
||||
1. `11c6aaf31`
|
||||
|
||||
### Batch 8: BlockVol Command-Binding Extraction
|
||||
|
||||
Goal:
|
||||
|
||||
1. move concrete `BlockVol`-backed command operations out of
|
||||
`volume_server_block.go`
|
||||
|
||||
Status:
|
||||
|
||||
1. delivered
|
||||
|
||||
Delivered result:
|
||||
|
||||
1. `v2bridge.CommandBindings` owns:
|
||||
- role apply binding
|
||||
- receiver startup binding
|
||||
- primary shipper configuration binding
|
||||
- shipper-connected probe
|
||||
2. `weed/server` still owns readiness-state updates and event semantics
|
||||
|
||||
Evidence:
|
||||
|
||||
1. `38b504299`
|
||||
|
||||
### Batch 9: Non-BlockVol Service-Ops Extraction
|
||||
|
||||
Goal:
|
||||
|
||||
1. move the remaining non-`BlockVol` command operations out of
|
||||
`volume_server_block.go` into the server-adapter layer
|
||||
|
||||
Status:
|
||||
|
||||
1. delivered
|
||||
|
||||
Delivered result:
|
||||
|
||||
1. `weed/server/blockcmd.ServiceOps` owns:
|
||||
- recovery-task startup
|
||||
- pending catch-up/rebuild execution
|
||||
- sender invalidation through projection + resolver
|
||||
2. `volume_server_block.go` now keeps:
|
||||
- host effects
|
||||
- backend-facing thin wrappers
|
||||
- assignment/event ingress
|
||||
|
||||
Evidence:
|
||||
|
||||
1. `38b504299`
|
||||
|
||||
### Batch 10: Host-Effects Adapter Extraction
|
||||
|
||||
Goal:
|
||||
|
||||
1. move the remaining command-completion host effects out of
|
||||
`volume_server_block.go` while keeping them on the `weed/server` side
|
||||
|
||||
Status:
|
||||
|
||||
1. delivered
|
||||
|
||||
Chosen slice:
|
||||
|
||||
1. `10A`: extract a dedicated server-side host-effects adapter into
|
||||
`weed/server/blockcmd`
|
||||
2. keep this slice bounded to:
|
||||
- `RecordCommand`
|
||||
- `EmitCoreEvent`
|
||||
- `PublishProjection`
|
||||
- projection-cache write routing
|
||||
3. do not fold backend-side readiness mutation into this first cut
|
||||
|
||||
Acceptance:
|
||||
|
||||
1. `coreCommandEffects` is no longer defined in `volume_server_block.go`
|
||||
2. `PublishProjection` cache-write logic is no longer inline in
|
||||
`volume_server_block.go`
|
||||
3. `weed/server` still owns host-effect semantics, but exposes them through a
|
||||
thinner adapter object
|
||||
4. focused proofs remain green
|
||||
|
||||
Delivered result:
|
||||
|
||||
1. `weed/server/blockcmd/host_effects.go` now owns the concrete dispatcher-side
|
||||
host-effects adapter
|
||||
2. `volume_server_block.go` now only wires recorder/event/cache dependencies
|
||||
into that adapter
|
||||
3. server-owned projection cache writing moved behind `BlockService.StoreProjection()`
|
||||
|
||||
Evidence:
|
||||
|
||||
1. working tree change on top of `38b504299`
|
||||
|
||||
### Batch 11: Stop-Line Review For Readiness Mutation
|
||||
|
||||
Goal:
|
||||
|
||||
1. decide whether the remaining adapter-local readiness mutation should move out
|
||||
of `volume_server_block.go`
|
||||
|
||||
Status:
|
||||
|
||||
1. delivered
|
||||
|
||||
Decision:
|
||||
|
||||
1. stop at the current package boundary
|
||||
2. do not extract readiness-state mutation into `weed/server/blockcmd`
|
||||
|
||||
Reason:
|
||||
|
||||
1. the remaining methods are not dispatcher-side host effects
|
||||
2. they are direct writes to `BlockService`-owned `replStates`
|
||||
3. they encode adapter-local cache/state semantics, not reusable command
|
||||
orchestration logic
|
||||
4. moving them into `blockcmd` would mostly add callback indirection without
|
||||
clarifying ownership
|
||||
|
||||
Covered methods:
|
||||
|
||||
1. `noteRoleApplied`
|
||||
2. `markPrimaryTransportConfigured`
|
||||
3. `markReceiverReady`
|
||||
4. `ReadinessSnapshot` remains the read-side consumer of the same local state
|
||||
|
||||
Accepted stop line:
|
||||
|
||||
1. `weed/server/blockcmd`
|
||||
- owns dispatch, service ops, and host-effects adapter
|
||||
2. `weed/server`
|
||||
- owns host state fields and adapter-local readiness/cache mutation
|
||||
3. `weed/storage/blockvol/v2bridge`
|
||||
- owns concrete backend bindings only
|
||||
|
||||
## Current Position
|
||||
|
||||
The separation line is now in the late-stage thinning phase.
|
||||
|
||||
Current split:
|
||||
|
||||
1. `sw-block`
|
||||
- canonical contracts / helpers
|
||||
- runtime coordination helpers
|
||||
2. `weed/storage/blockvol/v2bridge`
|
||||
- `BlockVol`-backed recovery and command bindings
|
||||
3. `weed/server/blockcmd`
|
||||
- command dispatch, service-side command operations, and host-effects adapter
|
||||
4. `weed/server`
|
||||
- assignment ingress, product integration, and adapter-local readiness/cache state
|
||||
|
||||
## Non-Claims
|
||||
|
||||
`Phase 17` does not yet claim:
|
||||
|
||||
1. every remaining host-side helper must move
|
||||
2. full deletion of compatibility-only paths
|
||||
3. that `weed/server` becomes empty or trivial
|
||||
|
||||
The stop line is practical, not ideological:
|
||||
|
||||
1. keep real host/product integration in `weed/server`
|
||||
2. keep backend binding in `v2bridge`
|
||||
3. keep reusable semantics/runtime helpers out of both
|
||||
|
||||
## Immediate Next Step
|
||||
|
||||
The current separation stop line is now explicit:
|
||||
|
||||
1. keep dispatcher-side logic in `weed/server/blockcmd`
|
||||
2. keep backend bindings in `weed/storage/blockvol/v2bridge`
|
||||
3. keep `BlockService` local state mutation in `weed/server`
|
||||
|
||||
Any next task should now be one of two kinds:
|
||||
|
||||
1. cleanup inside the accepted stop line
|
||||
2. a new semantic/runtime phase, not another ownership-shuffle task
|
||||
@@ -0,0 +1,17 @@
|
||||
package weed_server
|
||||
|
||||
import engine "github.com/seaweedfs/seaweedfs/sw-block/engine/replication"
|
||||
|
||||
// StoreProjection keeps the latest published projection in the server-owned
|
||||
// adapter cache used by outward/product-facing surfaces.
|
||||
func (bs *BlockService) StoreProjection(volumeID string, projection engine.PublicationProjection) {
|
||||
if bs == nil {
|
||||
return
|
||||
}
|
||||
bs.coreProjMu.Lock()
|
||||
if bs.coreProj == nil {
|
||||
bs.coreProj = make(map[string]engine.PublicationProjection)
|
||||
}
|
||||
bs.coreProj[volumeID] = projection
|
||||
bs.coreProjMu.Unlock()
|
||||
}
|
||||
@@ -189,11 +189,11 @@ type fakeRecoveryCoordinator struct {
|
||||
startedReplica string
|
||||
startedAssigns []blockvol.BlockVolumeAssignment
|
||||
catchUpCalls []struct {
|
||||
volumeID string
|
||||
volumeID string
|
||||
targetLSN uint64
|
||||
}
|
||||
rebuildCalls []struct {
|
||||
volumeID string
|
||||
volumeID string
|
||||
targetLSN uint64
|
||||
}
|
||||
}
|
||||
@@ -205,7 +205,7 @@ func (f *fakeRecoveryCoordinator) StartRecoveryTask(replicaID string, assignment
|
||||
|
||||
func (f *fakeRecoveryCoordinator) ExecutePendingCatchUp(volumeID string, targetLSN uint64) error {
|
||||
f.catchUpCalls = append(f.catchUpCalls, struct {
|
||||
volumeID string
|
||||
volumeID string
|
||||
targetLSN uint64
|
||||
}{volumeID: volumeID, targetLSN: targetLSN})
|
||||
return nil
|
||||
@@ -213,7 +213,7 @@ func (f *fakeRecoveryCoordinator) ExecutePendingCatchUp(volumeID string, targetL
|
||||
|
||||
func (f *fakeRecoveryCoordinator) ExecutePendingRebuild(volumeID string, targetLSN uint64) error {
|
||||
f.rebuildCalls = append(f.rebuildCalls, struct {
|
||||
volumeID string
|
||||
volumeID string
|
||||
targetLSN uint64
|
||||
}{volumeID: volumeID, targetLSN: targetLSN})
|
||||
return nil
|
||||
@@ -231,6 +231,17 @@ func (f fakeProjectionReader) Projection(volumeID string) (engine.PublicationPro
|
||||
return f.proj, true
|
||||
}
|
||||
|
||||
type fakeProjectionCache struct {
|
||||
published map[string]engine.PublicationProjection
|
||||
}
|
||||
|
||||
func (f *fakeProjectionCache) StoreProjection(volumeID string, projection engine.PublicationProjection) {
|
||||
if f.published == nil {
|
||||
f.published = make(map[string]engine.PublicationProjection)
|
||||
}
|
||||
f.published[volumeID] = projection
|
||||
}
|
||||
|
||||
type fakeSessionInvalidator struct {
|
||||
reasons []string
|
||||
states []engine.ReplicaState
|
||||
@@ -257,6 +268,56 @@ func TestServiceOps_StartRecoveryTaskUsesRecoveryCoordinator(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostEffects_PublishProjectionPrefersLatestCoreProjection(t *testing.T) {
|
||||
cache := &fakeProjectionCache{}
|
||||
effects := NewHostEffects(
|
||||
nil,
|
||||
nil,
|
||||
fakeProjectionReader{
|
||||
proj: engine.PublicationProjection{VolumeID: "vol1", Role: engine.RoleReplica},
|
||||
ok: true,
|
||||
},
|
||||
cache,
|
||||
)
|
||||
err := effects.PublishProjection("vol1", engine.PublicationProjection{VolumeID: "vol1", Role: engine.RolePrimary})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, ok := cache.published["vol1"]
|
||||
if !ok {
|
||||
t.Fatal("expected cached projection")
|
||||
}
|
||||
if got.Role != engine.RoleReplica {
|
||||
t.Fatalf("role=%v", got.Role)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostEffects_RecordAndEmitUseCallbacks(t *testing.T) {
|
||||
var recorded []string
|
||||
var events []engine.Event
|
||||
effects := NewHostEffects(
|
||||
func(volumeID, name string) {
|
||||
recorded = append(recorded, volumeID+":"+name)
|
||||
},
|
||||
func(ev engine.Event) {
|
||||
events = append(events, ev)
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
effects.RecordCommand("vol1", "apply_role")
|
||||
effects.EmitCoreEvent(engine.RoleApplied{ID: "vol1"})
|
||||
if !reflect.DeepEqual(recorded, []string{"vol1:apply_role"}) {
|
||||
t.Fatalf("recorded=%v", recorded)
|
||||
}
|
||||
if len(events) != 1 {
|
||||
t.Fatalf("events=%d", len(events))
|
||||
}
|
||||
if _, ok := events[0].(engine.RoleApplied); !ok {
|
||||
t.Fatalf("event=%T", events[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceOps_InvalidateSessionUsesProjectionAndSenderResolver(t *testing.T) {
|
||||
s1 := &fakeSessionInvalidator{}
|
||||
s2 := &fakeSessionInvalidator{}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package blockcmd
|
||||
|
||||
import engine "github.com/seaweedfs/seaweedfs/sw-block/engine/replication"
|
||||
|
||||
// CommandRecorder records one executed command on the host side.
|
||||
type CommandRecorder func(volumeID, name string)
|
||||
|
||||
// CoreEventEmitter feeds one runtime observation back into the core.
|
||||
type CoreEventEmitter func(ev engine.Event)
|
||||
|
||||
// ProjectionCacheWriter stores the latest published projection in a host-local
|
||||
// cache owned by the server adapter.
|
||||
type ProjectionCacheWriter interface {
|
||||
StoreProjection(volumeID string, projection engine.PublicationProjection)
|
||||
}
|
||||
|
||||
type hostEffects struct {
|
||||
recordCommand CommandRecorder
|
||||
emitCoreEvent CoreEventEmitter
|
||||
projection ProjectionReader
|
||||
cache ProjectionCacheWriter
|
||||
}
|
||||
|
||||
// NewHostEffects keeps host-side command completion effects in the server
|
||||
// adapter layer while exposing only the narrow Dispatcher-facing surface.
|
||||
func NewHostEffects(
|
||||
recordCommand CommandRecorder,
|
||||
emitCoreEvent CoreEventEmitter,
|
||||
projection ProjectionReader,
|
||||
cache ProjectionCacheWriter,
|
||||
) HostEffects {
|
||||
return &hostEffects{
|
||||
recordCommand: recordCommand,
|
||||
emitCoreEvent: emitCoreEvent,
|
||||
projection: projection,
|
||||
cache: cache,
|
||||
}
|
||||
}
|
||||
|
||||
func (effects *hostEffects) RecordCommand(volumeID, name string) {
|
||||
if effects == nil || effects.recordCommand == nil {
|
||||
return
|
||||
}
|
||||
effects.recordCommand(volumeID, name)
|
||||
}
|
||||
|
||||
func (effects *hostEffects) EmitCoreEvent(ev engine.Event) {
|
||||
if effects == nil || effects.emitCoreEvent == nil {
|
||||
return
|
||||
}
|
||||
effects.emitCoreEvent(ev)
|
||||
}
|
||||
|
||||
func (effects *hostEffects) PublishProjection(volumeID string, projection engine.PublicationProjection) error {
|
||||
if effects == nil || effects.cache == nil {
|
||||
return nil
|
||||
}
|
||||
proj := projection
|
||||
if effects.projection != nil {
|
||||
if latest, ok := effects.projection.Projection(volumeID); ok {
|
||||
proj = latest
|
||||
}
|
||||
}
|
||||
effects.cache.StoreProjection(volumeID, proj)
|
||||
return nil
|
||||
}
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
bridgeblockvol "github.com/seaweedfs/seaweedfs/sw-block/bridge/blockvol"
|
||||
engine "github.com/seaweedfs/seaweedfs/sw-block/engine/replication"
|
||||
"github.com/seaweedfs/seaweedfs/weed/server/blockcmd"
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/server/blockcmd"
|
||||
"github.com/seaweedfs/seaweedfs/weed/storage"
|
||||
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol"
|
||||
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol/iscsi"
|
||||
@@ -580,6 +580,14 @@ func (bs *BlockService) coreCommandDispatcher() *blockcmd.Dispatcher {
|
||||
if bs != nil && bs.v2Core != nil {
|
||||
projection = bs.v2Core
|
||||
}
|
||||
var recordCommand blockcmd.CommandRecorder
|
||||
var emitCoreEvent blockcmd.CoreEventEmitter
|
||||
var projectionCache blockcmd.ProjectionCacheWriter
|
||||
if bs != nil {
|
||||
recordCommand = bs.recordExecutedCoreCommand
|
||||
emitCoreEvent = bs.applyCoreEvent
|
||||
projectionCache = bs
|
||||
}
|
||||
return blockcmd.NewDispatcher(
|
||||
blockcmd.NewServiceOps(
|
||||
coreCommandBackend{bs: bs},
|
||||
@@ -592,7 +600,7 @@ func (bs *BlockService) coreCommandDispatcher() *blockcmd.Dispatcher {
|
||||
return bs.v2Orchestrator.Registry.Sender(replicaID)
|
||||
},
|
||||
),
|
||||
coreCommandEffects{bs: bs},
|
||||
blockcmd.NewHostEffects(recordCommand, emitCoreEvent, projection, projectionCache),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -660,43 +668,6 @@ func (ops coreCommandBackend) ConfigureShipper(volumeID string, replicas []engin
|
||||
return true, ops.bs.isPrimaryShipperConnected(volumeID), nil
|
||||
}
|
||||
|
||||
type coreCommandEffects struct {
|
||||
bs *BlockService
|
||||
}
|
||||
|
||||
func (effects coreCommandEffects) RecordCommand(volumeID, name string) {
|
||||
if effects.bs == nil {
|
||||
return
|
||||
}
|
||||
effects.bs.recordExecutedCoreCommand(volumeID, name)
|
||||
}
|
||||
|
||||
func (effects coreCommandEffects) EmitCoreEvent(ev engine.Event) {
|
||||
if effects.bs == nil {
|
||||
return
|
||||
}
|
||||
effects.bs.applyCoreEvent(ev)
|
||||
}
|
||||
|
||||
func (effects coreCommandEffects) PublishProjection(volumeID string, projection engine.PublicationProjection) error {
|
||||
if effects.bs == nil {
|
||||
return nil
|
||||
}
|
||||
proj := projection
|
||||
if core := effects.bs.V2Core(); core != nil {
|
||||
if latest, ok := core.Projection(volumeID); ok {
|
||||
proj = latest
|
||||
}
|
||||
}
|
||||
effects.bs.coreProjMu.Lock()
|
||||
if effects.bs.coreProj == nil {
|
||||
effects.bs.coreProj = make(map[string]engine.PublicationProjection)
|
||||
}
|
||||
effects.bs.coreProj[volumeID] = proj
|
||||
effects.bs.coreProjMu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bs *BlockService) applyRoleAssignment(a blockvol.BlockVolumeAssignment) error {
|
||||
if bs == nil || bs.blockStore == nil {
|
||||
return nil
|
||||
|
||||
Reference in new issue
Block a user