refactor(session): share session state locks · Entire
refactor(session): share session state locks
3d3df24→main·
peyton-alt·3w ago·3 files·+57 added/-65 removed
Sessions
03f1aba5f48fView transcript
[?
Adopt Active Sessions Across Repos and WorktreesCodex·GPT-5.5·1 step](/content/gh/entireio/cli/session/019ef6af-f809-76d3-8c95-7d7f0bc0c5ef#timeline-03f1aba5f48f/index.html)
Changes
3
cmd/entire/cli
Msession_adopt.go+2/-59
Msession_adopt_test.go+2/-2
strategy
- Msession_state.go+53/-4
6 unmodified lines
7
8
9
10
10
11
12
3 unmodified lines
16
17
18
20
19
20
21
24
22
23
24
114 unmodified lines
139
140
141
145
142
143
144
145
31 unmodified lines
177
178
179
183
180
181
182
183
58 unmodified lines
242
243
244
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
245
246
247
6 unmodified lines
"fmt"
"io"
"maps"
"os"
"os/exec"
"path/filepath"
"slices"
3 unmodified lines
"github.com/entireio/cli/cmd/entire/cli/agent"
"github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
"github.com/entireio/cli/cmd/entire/cli/internal/flock"
"github.com/entireio/cli/cmd/entire/cli/paths"
"github.com/entireio/cli/cmd/entire/cli/session"
"github.com/entireio/cli/cmd/entire/cli/strategy"
"github.com/entireio/cli/cmd/entire/cli/validation"
"github.com/entireio/cli/cmd/entire/cli/versioninfo"
"github.com/spf13/cobra"
)
114 unmodified lines
var adopted *session.State
var filesTouched []string
err := withAdoptSessionLocks(ctx, sessionID, []string{sourceCommonDir, targetCommonDir}, func() error {
err := strategy.WithSessionStateLocks(ctx, sessionID, []string{sourceCommonDir, targetCommonDir}, func() error {
sourceState, err := sourceStore.Load(ctx, sessionID)
if err != nil {
return fmt.Errorf("load source session state: %w", err)
}
31 unmodified lines
return nil
})
if err != nil {
return nil, nil, err
return nil, nil, fmt.Errorf("adopt external session state: %w", err)
}
return adopted, filesTouched, nil
}
func withAdoptSessionLocks(ctx context.Context, sessionID string, commonDirs []string, fn func() error) error {
lockPaths := make([]string, 0, len(commonDirs))
seen := make(map[string]struct{}, len(commonDirs))
for _, commonDir := range commonDirs {
lockPath, err := adoptSessionLockPath(commonDir, sessionID)
if err != nil {
return err
}
if _, ok := seen[lockPath]; ok {
continue
}
seen[lockPath] = struct{}{}
lockPaths = append(lockPaths, lockPath)
}
sort.Strings(lockPaths)
releases := make([]func(), 0, len(lockPaths))
for _, lockPath := range lockPaths {
if err := ctx.Err(); err != nil {
releaseAdoptSessionLocks(releases)
return fmt.Errorf("adopt session lock canceled: %w", err)
}
release, err := flock.Acquire(lockPath)
if err != nil {
releaseAdoptSessionLocks(releases)
return fmt.Errorf("acquire session state lock: %w", err)
}
releases = append(releases, release)
}
defer releaseAdoptSessionLocks(releases)
return fn()
}
func releaseAdoptSessionLocks(releases []func()) {
for i := len(releases) - 1; i >= 0; i-- {
releases[i]()
}
}
func adoptSessionLockPath(commonDir, sessionID string) (string, error) {
if strings.TrimSpace(commonDir) == "" {
return "", errors.New("resolve session state lock: empty git common dir")
}
if err := validation.ValidateSessionID(sessionID); err != nil {
return "", fmt.Errorf("invalid session ID: %w", err)
}
lockDir := filepath.Join(commonDir, "entire-session-locks")
if err := os.MkdirAll(lockDir, 0o750); err != nil {
return "", fmt.Errorf("create session lock directory: %w", err)
}
return filepath.Join(lockDir, sessionID+".lock"), nil
}
func stateStoreForWorktree(ctx context.Context, worktreePath string) (*session.StateStore, string, string, error) {
absWorktree, err := filepath.Abs(worktreePath)
if err != nil {
Mcmd/entire/cli/session_adopt.go+2/-59
263 unmodified lines
264 265 266 267 268 267 268 269 270 271
263 unmodified lines
t.Fatal(err) }
lockPath, err := adoptSessionLockPath(targetCommonDir, sessionID) if err != nil { lockPath := filepath.Join(targetCommonDir, "entire-session-locks", sessionID+".lock") if err := os.MkdirAll(filepath.Dir(lockPath), 0o750); err != nil { t.Fatal(err) } release, err := flock.Acquire(lockPath)
Mcmd/entire/cli/session_adopt_test.go+2/-2
591 unmodified lines
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 36 unmodified lines
676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 640 641 642 643 693 694 695
591 unmodified lines
}, nil }
// WithSessionStateLocks acquires the per-session state lock in each git common // dir, then runs fn. Lock paths are deduplicated and sorted so callers that // span repositories or worktrees can safely acquire more than one lock. func WithSessionStateLocks(ctx context.Context, sessionID string, commonDirs []string, fn func() error) error { lockPaths := make([]string, 0, len(commonDirs)) seen := make(map[string]struct{}, len(commonDirs)) for _, commonDir := range commonDirs { lockPath, err := stateLockPathInCommonDir(commonDir, sessionID) if err != nil { return err } if _, ok := seen[lockPath]; ok { continue } seen[lockPath] = struct{}{} lockPaths = append(lockPaths, lockPath) } slices.Sort(lockPaths)
releases := make([]func(), 0, len(lockPaths)) releaseAll := func() { for i := len(releases) - 1; i >= 0; i-- { releasesi } } for _, lockPath := range lockPaths { if err := ctx.Err(); err != nil { releaseAll() return fmt.Errorf("session state lock canceled: %w", err) } release, err := flock.Acquire(lockPath) if err != nil { releaseAll() return fmt.Errorf("acquire session state lock: %w", err) } releases = append(releases, release) } defer releaseAll()
return fn() }
// ErrMutationSkip signals MutateSessionState to skip the save without // treating fn's return as an error. Use it when the mutation function // observes the loaded state and decides no write is needed (for example, 36 unmodified lines
// holder distinct from the data — Save's atomic-rename pattern would // otherwise unlink the inode the flock is held on. func stateLockPath(ctx context.Context, sessionID string) (string, error) { commonDir, err := GetGitCommonDir(ctx) if err != nil { return "", err } return stateLockPathInCommonDir(commonDir, sessionID) }
func stateLockPathInCommonDir(commonDir, sessionID string) (string, error) { if strings.TrimSpace(commonDir) == "" { return "", errors.New("empty git common dir") } if err := validation.ValidateSessionID(sessionID); err != nil { return "", fmt.Errorf("invalid session ID: %w", err) } commonDir, err := GetGitCommonDir(ctx) if err != nil { return "", err } lockDir := filepath.Join(commonDir, "entire-session-locks") if err := os.MkdirAll(lockDir, 0o750); err != nil { return "", fmt.Errorf("create session lock directory: %w", err) }