fix(peerclient): resolve base path per-call from live FrameTable#2585
fix(peerclient): resolve base path per-call from live FrameTable#2585
Conversation
After a peer→storage transition, peerSeekable kept opening base against the original (uncompressed) path captured at construction. With the recent compression work, post-transition reads now target a compressed object, so the cached base resolved to a non-existent path. P2P routing produced the stale binding (it captured a path while the build was still uncompressed); it also contains the fix. peerSeekable now holds (buildID, basic name, base provider, objType) and composes the actual storage path at base-open time using the CompressionType from the live FrameTable. The base seekable is reopened on ct change. No changes outside peerclient — no cache key, no chunker, no public storage interface. Also hoist the transition emit to the top of OpenRangeReader: returning PeerTransitionedError no longer wastes a base open before the caller swaps the header and retries.
❌ 4 Tests Failed:
View the full list of 8 ❄️ flaky test(s)
To view more test analytics, go to the Test Analytics Dashboard |
There was a problem hiding this comment.
Code Review
The removal of nil checks for the uploaded atomic pointer in OpenRangeReader and tryPeer introduces potential nil pointer dereferences. A transition to storage occurring during a peer read attempt may be missed, causing the base storage to be opened with a stale compression type; re-checking the transition state after the peer attempt is necessary to ensure the caller refreshes the compression state correctly.
| // Emit at most once per peerSeekable so V3 builds (no V4 to upgrade to) | ||
| // don't loop against this error. No peer call, no base open — caller | ||
| // retries with the live compression type already in the new FrameTable. | ||
| if s.uploaded.Load() && s.transitionEmitted.CompareAndSwap(false, true) { |
There was a problem hiding this comment.
The defensive check for a nil uploaded atomic pointer was removed in this refactoring. While the provider is typically initialized with this atomic, the original implementation included this check to prevent panics in edge cases where the routing might not provide it. Restoring the check ensures the code remains robust against variations in the initialization path.
| if s.uploaded.Load() && s.transitionEmitted.CompareAndSwap(false, true) { | |
| if s.uploaded != nil && s.uploaded.Load() && s.transitionEmitted.CompareAndSwap(false, true) { |
| if res.hit { | ||
| return res.value, err | ||
| } |
There was a problem hiding this comment.
A transition to storage that occurs during the tryPeer call will be missed, potentially leading to path resolution errors. If the peer signals a transition during the read attempt, tryPeer returns a non-hit result, and the code proceeds to open the base storage using the stale compression type from the current frameTable. Re-checking the transition state after the peer attempt ensures the caller is correctly prompted to refresh its header and compression state when a transition is detected mid-call.
if res.hit {
return res.value, err
}
if s.uploaded != nil && s.uploaded.Load() && s.transitionEmitted.CompareAndSwap(false, true) {
return nil, &storage.PeerTransitionedError{}
}
After a peer→storage transition, peerSeekable kept opening base against the original (uncompressed) path captured at construction. With the recent compression work, post-transition reads now target a compressed object, so the cached base resolved to a non-existent path.
P2P routing produced the stale binding (it captured a path while the build was still uncompressed); it also contains the fix. peerSeekable now holds (buildID, basic name, base provider, objType) and composes the actual storage path at base-open time using the CompressionType from the live FrameTable. The base seekable is reopened on ct change. No changes outside peerclient.