Skip to content

Commit b40b234

Browse files
committed
fix(mountsync): bound runtime-pruning frontier requests
1 parent 00394cb commit b40b234

2 files changed

Lines changed: 153 additions & 54 deletions

File tree

internal/mountsync/syncer.go

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,18 @@ const (
113113
defaultCursorResolutionAttempts = 3
114114
defaultCursorRetryBaseDelay = 250 * time.Millisecond
115115
defaultBootstrapReadWorkers = 16
116-
defaultIncrementalEventPageLimit = 50
117-
defaultRetryAfterMaxDelay = 60 * time.Second
118-
defaultWebSocketReconnectBase = 1 * time.Second
119-
defaultWebSocketReconnectMax = 60 * time.Second
120-
defaultWebSocketReconnectJitter = 200 * time.Millisecond
121-
DefaultWebSocketMaintenanceEvery = 1 * time.Second
116+
// fullTreeTraversalDepth bounds each tree request so the client can see and
117+
// prune a reserved .relay directory before the server reaches deep
118+
// outbox/acked/mountcmd_* leaves. Depth 3 also covers the common Slack
119+
// channels/<id>/messages frontier in one request, avoiding one network
120+
// round trip per message directory.
121+
fullTreeTraversalDepth = 3
122+
defaultIncrementalEventPageLimit = 50
123+
defaultRetryAfterMaxDelay = 60 * time.Second
124+
defaultWebSocketReconnectBase = 1 * time.Second
125+
defaultWebSocketReconnectMax = 60 * time.Second
126+
defaultWebSocketReconnectJitter = 200 * time.Millisecond
127+
DefaultWebSocketMaintenanceEvery = 1 * time.Second
122128
)
123129

124130
// resolveDurationEnv returns the option value if non-zero, else parses the
@@ -4047,10 +4053,11 @@ func (s *Syncer) pullRemoteFullTree(ctx context.Context, conflicted map[string]s
40474053
)
40484054
}()
40494055
remotePaths := map[string]struct{}{}
4050-
// Traverse one directory level at a time. A deep ListTree request makes the
4056+
// Traverse in bounded-depth chunks. A depth=200 ListTree request makes the
40514057
// server enumerate every descendant before the client can reject reserved
4052-
// runtime paths. The shallow queue sees a nested .relay directory first and
4053-
// can prune it without ever requesting its state/outbox descendants.
4058+
// runtime paths. A small fixed depth sees a nested .relay directory before
4059+
// it reaches deep outbox/acked/mountcmd_* leaves, while advancing through a
4060+
// representative Slack tree in a few requests rather than one per directory.
40544061
directories := []string{s.remoteRoot}
40554062
cursor := ""
40564063
startedFromEmpty := true
@@ -4067,13 +4074,13 @@ func (s *Syncer) pullRemoteFullTree(ctx context.Context, conflicted map[string]s
40674074
directories = resumedDirectories
40684075
cursor = strings.TrimSpace(s.state.BootstrapCursor)
40694076
startedFromEmpty = false
4070-
s.logf("resuming bootstrap shallow-tree pull at %s from persisted cursor (%d directories pending, %d files already synced)", directories[0], len(directories), s.state.BootstrapFilesSynced)
4077+
s.logf("resuming bootstrap bounded-tree pull at %s from persisted cursor (%d directories pending, %d files already synced)", directories[0], len(directories), s.state.BootstrapFilesSynced)
40714078
}
40724079
} else if !s.state.BootstrapComplete && strings.TrimSpace(s.state.BootstrapCursor) != "" {
40734080
// v0.10.28 and older persisted a cursor into one depth=200 listing.
4074-
// That cursor is invalid for a depth=1 directory page, so restart from
4081+
// That cursor is invalid for a bounded-depth directory page, so restart from
40754082
// the root. The local-hash fast path avoids re-reading unchanged files.
4076-
s.logf("discarding legacy deep-tree bootstrap cursor and restarting shallow traversal from %s", s.remoteRoot)
4083+
s.logf("discarding legacy deep-tree bootstrap cursor and restarting bounded traversal from %s", s.remoteRoot)
40774084
s.state.BootstrapCursor = ""
40784085
}
40794086
queuedDirectories := make(map[string]struct{}, len(directories))
@@ -4099,12 +4106,13 @@ func (s *Syncer) pullRemoteFullTree(ctx context.Context, conflicted map[string]s
40994106
}
41004107
maxObservedRevision := ""
41014108
var transientBootstrapAbort bool
4109+
prunedRuntimeRoots := map[string]struct{}{}
41024110
for len(directories) > 0 {
41034111
currentDirectory := directories[0]
41044112
var page TreeResponse
41054113
var err error
41064114
s.runFullPullIO(func() {
4107-
page, err = s.client.ListTree(ctx, s.workspace, currentDirectory, 1, cursor)
4115+
page, err = s.client.ListTree(ctx, s.workspace, currentDirectory, fullTreeTraversalDepth, cursor)
41084116
})
41094117
metrics.listCalls++
41104118
if err != nil {
@@ -4127,15 +4135,18 @@ func (s *Syncer) pullRemoteFullTree(ctx context.Context, conflicted map[string]s
41274135
case "dir":
41284136
metrics.directoriesSeen++
41294137
}
4130-
if isMountRuntimeRemotePath(remotePath) {
4138+
runtimeRoot := mountRuntimeRemoteRoot(remotePath)
4139+
if runtimeRoot != "" {
41314140
metrics.runtimeEntriesSeen++
41324141
}
41334142
if !isUnderRemoteRoot(s.remoteRoot, remotePath) {
41344143
continue
41354144
}
4136-
if s.skipMountRuntimeRemotePath(remotePath, conflicted) {
4137-
if entry.Type == "dir" {
4145+
if runtimeRoot != "" {
4146+
if _, pruned := prunedRuntimeRoots[runtimeRoot]; !pruned {
4147+
prunedRuntimeRoots[runtimeRoot] = struct{}{}
41384148
metrics.runtimeSubtreesPruned++
4149+
s.skipMountRuntimeRemotePath(runtimeRoot, conflicted)
41394150
}
41404151
continue
41414152
}
@@ -4144,7 +4155,10 @@ func (s *Syncer) pullRemoteFullTree(ctx context.Context, conflicted map[string]s
41444155
continue
41454156
}
41464157
if entry.Type == "dir" {
4147-
if _, exists := queuedDirectories[remotePath]; !exists {
4158+
if remoteDescendantDepth(currentDirectory, remotePath) >= fullTreeTraversalDepth {
4159+
if _, exists := queuedDirectories[remotePath]; exists {
4160+
continue
4161+
}
41484162
queuedDirectories[remotePath] = struct{}{}
41494163
directories = append(directories, remotePath)
41504164
}
@@ -5539,8 +5553,8 @@ func (s *Syncer) applyRemoteFile(remotePath string, file RemoteFile, conflicted
55395553
}
55405554

55415555
func (s *Syncer) skipMountRuntimeRemotePath(remotePath string, conflicted map[string]struct{}) bool {
5542-
remotePath = normalizeRemotePath(remotePath)
5543-
if !isMountRuntimeRemotePath(remotePath) {
5556+
remotePath = mountRuntimeRemoteRoot(remotePath)
5557+
if remotePath == "" {
55445558
return false
55455559
}
55465560
s.logf("skipping mount runtime path surfaced as workspace content: %s", remotePath)
@@ -5586,8 +5600,9 @@ func (s *Syncer) isActiveMountRuntimeLocalPath(localPath string) bool {
55865600
if localPath == stateFile {
55875601
return true
55885602
}
5603+
stateTempPrefix := strings.TrimSuffix(atomicTempPattern(stateFile), "*")
55895604
return filepath.Dir(localPath) == filepath.Dir(stateFile) &&
5590-
strings.HasPrefix(filepath.Base(localPath), "."+filepath.Base(stateFile)+".tmp-")
5605+
strings.HasPrefix(filepath.Base(localPath), stateTempPrefix)
55915606
}
55925607

55935608
func (s *Syncer) applyLocalPermissions(localPath string, canWrite bool) error {
@@ -6223,25 +6238,34 @@ func normalizeScopes(scopes []string) []string {
62236238
}
62246239

62256240
func isMountRuntimeRemotePath(path string) bool {
6226-
return isMountRuntimeRelativePath(strings.TrimPrefix(normalizeRemotePath(path), "/"))
6241+
return mountRuntimeRemoteRoot(path) != ""
62276242
}
62286243

62296244
func isMountRuntimeRelativePath(path string) bool {
6230-
normalized := filepath.ToSlash(strings.TrimSpace(path))
6245+
return mountRuntimeRemoteRoot("/"+strings.TrimPrefix(filepath.ToSlash(strings.TrimSpace(path)), "/")) != ""
6246+
}
6247+
6248+
// mountRuntimeRemoteRoot returns the reserved runtime subtree containing path.
6249+
// Canonicalizing descendants to their first reserved segment lets a bounded
6250+
// tree page clean/log one .relay root without treating its state/outbox
6251+
// children as separate subtrees.
6252+
func mountRuntimeRemoteRoot(path string) string {
6253+
normalized := strings.TrimPrefix(filepath.ToSlash(normalizeRemotePath(path)), "/")
62316254
if normalized == "" || normalized == "." {
6232-
return false
6255+
return ""
62336256
}
6234-
for _, segment := range strings.Split(normalized, "/") {
6257+
segments := strings.Split(normalized, "/")
6258+
for index, segment := range segments {
62356259
if segment == "" || segment == "." {
62366260
continue
62376261
}
62386262
if segment == ".relay" ||
62396263
segment == ".relayfile-mount-state.json" ||
62406264
strings.HasPrefix(segment, ".relayfile-mount-state.json.tmp-") {
6241-
return true
6265+
return normalizeRemotePath("/" + strings.Join(segments[:index+1], "/"))
62426266
}
62436267
}
6244-
return false
6268+
return ""
62456269
}
62466270

62476271
func (s *Syncer) logMountControlPathSkipped(relativePath string) {
@@ -6851,6 +6875,20 @@ func isUnderRemoteRoot(remoteRoot, remotePath string) bool {
68516875
return remotePath == remoteRoot || strings.HasPrefix(remotePath, remoteRoot+"/")
68526876
}
68536877

6878+
func remoteDescendantDepth(remoteRoot, remotePath string) int {
6879+
remoteRoot = normalizeRemotePath(remoteRoot)
6880+
remotePath = normalizeRemotePath(remotePath)
6881+
if remotePath == remoteRoot || !isUnderRemoteRoot(remoteRoot, remotePath) {
6882+
return 0
6883+
}
6884+
relative := strings.TrimPrefix(remotePath, remoteRoot)
6885+
relative = strings.TrimPrefix(relative, "/")
6886+
if relative == "" {
6887+
return 0
6888+
}
6889+
return len(strings.Split(relative, "/"))
6890+
}
6891+
68546892
func remoteToLocalPath(localRoot, remoteRoot, remotePath string) (string, error) {
68556893
localRoot = filepath.Clean(localRoot)
68566894
remoteRoot = normalizeRemotePath(remoteRoot)

0 commit comments

Comments
 (0)