返回 DeepSeek-Reasonix
session_takeover_return.go
根目录 / internal / cli / session_takeover_return.go
1 package cli
2
3 import (
4 "fmt"
5 "sync"
6
7 "reasonix/internal/agent"
8 )
9
10 // returnCurrentMirror refreshes the binding after pushLocked while sendMu
11 // fences the generation used by both the reverse reservation and mirror-end.
12 func (m *cliTakeoverManager) returnCurrentMirror(expectedPath string, retire func(*cliTakeoverBinding) error) error {
13 return m.returnMirrorTransaction(expectedPath, false, false, retire)
14 }
15
16 // returnMirrorTransaction is the single owner for retiring an active mirror.
17 // returnMu prevents Activate/session switches, sendMu fences re-adoption, and
18 // the binding is re-read after pushLocked so the reverse reservation and
19 // mirror-end always use one current generation. Snapshot is requested for
20 // reclaim/exit, after returnMu excludes a different session but before the
21 // sender lock is held across disk I/O and event fan-out.
22 func (m *cliTakeoverManager) returnMirrorTransaction(expectedPath string, allowReclaim, snapshot bool, retire func(*cliTakeoverBinding) error) error {
23 if m == nil || retire == nil {
24 return fmt.Errorf("takeover return transaction unavailable")
25 }
26 expectedPath = agent.CanonicalSessionPath(expectedPath)
27 m.returnMu.Lock()
28 defer m.returnMu.Unlock()
29 if !allowReclaim && m.reclaiming.Load() {
30 return fmt.Errorf("the remote side is reclaiming the current session")
31 }
32 current, ctrl, _, _ := m.snapshot()
33 if current == nil || m.returned.Load() {
34 return nil
35 }
36 if expectedPath != "" && agent.CanonicalSessionPath(current.path) != expectedPath {
37 return fmt.Errorf("current takeover mirror changed during session switch")
38 }
39 if snapshot && ctrl != nil {
40 if err := ctrl.Snapshot(); err != nil {
41 return err
42 }
43 }
44 m.sendMu.Lock()
45 unlockError := func(err error) error { m.sendMu.Unlock(); return err }
46 current, _, _, _ = m.snapshot()
47 if current == nil || m.returned.Load() || expectedPath != "" && agent.CanonicalSessionPath(current.path) != expectedPath {
48 return unlockError(fmt.Errorf("current takeover mirror changed during session switch"))
49 }
50 if !m.pushLocked(false) {
51 return unlockError(fmt.Errorf("current takeover mirror stopped during session switch"))
52 }
53 current, _, _, _ = m.snapshot()
54 if current == nil || m.returned.Load() || expectedPath != "" && agent.CanonicalSessionPath(current.path) != expectedPath {
55 return unlockError(fmt.Errorf("current takeover mirror changed during session switch"))
56 }
57 if !allowReclaim && m.reclaiming.Load() {
58 return unlockError(fmt.Errorf("the remote side is reclaiming the current session"))
59 }
60 if err := retire(current); err != nil {
61 return unlockError(err)
62 }
63 m.returned.Store(true)
64 m.mirrorEndLocked(current)
65 m.mu.Lock()
66 started, stop, done := m.started, m.stop, m.done
67 m.binding = nil
68 if allowReclaim {
69 m.yielded = current
70 }
71 m.queue.Reset()
72 m.revision++
73 m.failures = 0
74 m.mu.Unlock()
75 m.sendMu.Unlock()
76 if started {
77 m.stopOnce.Do(func() { close(stop) })
78 <-done
79 }
80 m.mu.Lock()
81 m.started, m.wake, m.stop, m.done = false, nil, nil, nil
82 m.stopOnce = sync.Once{}
83 m.reclaiming.Store(false)
84 if !allowReclaim {
85 // A session switch is followed by Activate for a different mirror (or
86 // continues without one). Terminal reclaim/Close keeps returned true so
87 // the outer TUI/headless lifecycle can observe that ownership was yielded.
88 m.returned.Store(false)
89 }
90 m.mu.Unlock()
91 return nil
92 }
93
93 lines GO