返回 DeepSeek-Reasonix
worktree_runtime_admission.go
根目录 / desktop / worktree_runtime_admission.go
1 package main
2
3 import (
4 "fmt"
5
6 "reasonix/internal/control"
7 )
8
9 func noopRuntimeAdmission() {}
10
11 // reserveWorkspaceRemoval waits for existing project-runtime publications and
12 // rejects new ones until the sidebar stores and tab bindings agree on removal.
13 // Reserve before lockRuntimeMutation: a publisher may hold this gate while
14 // waiting for the runtime mutation lock.
15 func (a *App) reserveWorkspaceRemoval(workspaceRoot string) (func(), error) {
16 key, err := canonicalRuntimeRootErr(workspaceRoot)
17 if err != nil {
18 return nil, fmt.Errorf("resolve workspace removal identity: %w", err)
19 }
20 a.worktreeReservations.mu.Lock()
21 defer a.worktreeReservations.mu.Unlock()
22 if a.cleanupReservationOverlapsLocked(key) || a.mergeReservationOverlapsLocked(key) {
23 return nil, fmt.Errorf("workspace maintenance is already in progress")
24 }
25 if a.worktreeReservations.cleanup == nil {
26 a.worktreeReservations.cleanup = map[string]struct{}{}
27 }
28 a.worktreeReservations.cleanup[key] = struct{}{}
29 return func() {
30 a.worktreeReservations.mu.Lock()
31 delete(a.worktreeReservations.cleanup, key)
32 a.worktreeReservations.mu.Unlock()
33 }, nil
34 }
35
36 func (a *App) beginProjectRuntimeAdmission(scope, workspaceRoot string) (func(), error) {
37 if scope != "project" {
38 return noopRuntimeAdmission, nil
39 }
40 return a.beginWorkspaceRuntimeAdmission(workspaceRoot)
41 }
42
43 func (a *App) beginChangedProjectRuntimeAdmission(tab *WorkspaceTab, scope, workspaceRoot string) (func(), error) {
44 if scope != "project" {
45 return noopRuntimeAdmission, nil
46 }
47 a.mu.RLock()
48 sameWorkspace := tab.Scope == "project" && sameProjectRoot(tab.WorkspaceRoot, workspaceRoot)
49 a.mu.RUnlock()
50 if sameWorkspace {
51 return noopRuntimeAdmission, nil
52 }
53 return a.beginWorkspaceRuntimeAdmission(workspaceRoot)
54 }
55
56 func (a *App) publishRestoredTab(tab *WorkspaceTab, releaseAdmission func()) {
57 defer releaseAdmission()
58 a.mu.Lock()
59 a.tabs[tab.ID] = tab
60 a.tabOrder = append(a.tabOrder, tab.ID)
61 a.mu.Unlock()
62 }
63
64 // workspaceRuntimeAdmissionErr rejects submits that race cleanup reservation
65 // or controller readiness. Callers must not hold App.mu.
66 func (a *App) workspaceRuntimeAdmissionErr(tab *WorkspaceTab, ctrl control.SessionAPI) error {
67 a.worktreeReservations.mu.Lock()
68 defer a.worktreeReservations.mu.Unlock()
69 a.mu.RLock()
70 defer a.mu.RUnlock()
71 if tab != nil && tab.Scope == "project" {
72 if key := canonicalRuntimeRoot(tab.WorkspaceRoot); key == "" {
73 return fmt.Errorf("workspace identity is unavailable")
74 } else if err := a.workspaceRuntimeReservationErrLocked(key); err != nil {
75 return err
76 }
77 }
78 if tab != nil && ctrl != nil && tab.Ctrl == ctrl {
79 if a.sessionRuntimeViewLocked(tab).Phase == sessionRuntimeReady {
80 return nil
81 }
82 }
83 return a.workspaceNotReadyErrLocked(tab)
84 }
85
86 // workspaceRuntimeReservationErrLocked applies every destructive worktree
87 // lifecycle reservation to one canonical project root. The caller holds
88 // worktreeReservations.mu so a successful check stays ordered with reservation
89 // publication.
90 func (a *App) workspaceRuntimeReservationErrLocked(workspaceKey string) error {
91 if a.workspaceCleanupReservedLocked(workspaceKey) {
92 return fmt.Errorf("workspace cleanup is in progress")
93 }
94 if a.workspaceMergeReservedLocked(workspaceKey) {
95 return fmt.Errorf("workspace merge-back is in progress")
96 }
97 return nil
98 }
99
99 lines GO