返回 DeepSeek-Reasonix
runtime_state.go
根目录 / internal / jobs / runtime_state.go
1 package jobs
2
3 import "sync"
4
5 // RuntimeState counts operational work, including cancelled processes which
6 // have not exited. Revisions order updates from concurrent jobs in a manager.
7 type RuntimeState struct {
8 SessionID string
9 JobID string
10 Revision uint64
11 Running int
12 }
13
14 type runtimeObservers struct {
15 mu sync.Mutex
16 revision uint64
17 next uint64
18 listeners map[uint64]*runtimeSubscription
19 }
20
21 type runtimeSubscription struct {
22 mu sync.Mutex
23 session string
24 callback func(RuntimeState)
25 pending *RuntimeState
26 draining bool
27 closed bool
28 }
29
30 func (s *runtimeSubscription) enqueue(state RuntimeState) {
31 s.mu.Lock()
32 if s.closed {
33 s.mu.Unlock()
34 return
35 }
36 s.pending = &state
37 if s.draining {
38 s.mu.Unlock()
39 return
40 }
41 s.draining = true
42 s.mu.Unlock()
43 go func() {
44 for {
45 s.mu.Lock()
46 if s.closed || s.pending == nil {
47 s.draining = false
48 s.mu.Unlock()
49 return
50 }
51 state := *s.pending
52 s.pending = nil
53 s.mu.Unlock()
54 s.callback(state)
55 }
56 }()
57 }
58
59 // SubscribeRuntime registers before reading, so a completion cannot disappear
60 // in the subscribe/read gap. Callbacks run off-lock with one pending snapshot.
61 func (m *Manager) SubscribeRuntime(session string, callback func(RuntimeState)) (RuntimeState, func()) {
62 m.runtimeObservers.mu.Lock()
63 defer m.runtimeObservers.mu.Unlock()
64 m.runtimeObservers.next++
65 id := m.runtimeObservers.next
66 if m.runtimeObservers.listeners == nil {
67 m.runtimeObservers.listeners = map[uint64]*runtimeSubscription{}
68 }
69 sub := &runtimeSubscription{session: session, callback: callback}
70 m.runtimeObservers.listeners[id] = sub
71 initial := RuntimeState{SessionID: session, Revision: m.runtimeObservers.revision, Running: len(m.RunningForSession(session))}
72 return initial, func() {
73 m.runtimeObservers.mu.Lock()
74 delete(m.runtimeObservers.listeners, id)
75 m.runtimeObservers.mu.Unlock()
76 sub.mu.Lock()
77 sub.closed = true
78 sub.pending = nil
79 sub.mu.Unlock()
80 }
81 }
82
83 // notifyRuntime runs only after committing a start or closing done. Notices
84 // still precede done to protect DrainCompletedNote; they are not state signals.
85 func (m *Manager) notifyRuntime(session, jobID string) {
86 m.runtimeObservers.mu.Lock()
87 defer m.runtimeObservers.mu.Unlock()
88 m.runtimeObservers.revision++
89 for _, sub := range m.runtimeObservers.listeners {
90 if sub.session != "" && session != "" && sub.session != session {
91 continue
92 }
93 sub.enqueue(RuntimeState{SessionID: sub.session, JobID: jobID, Revision: m.runtimeObservers.revision, Running: len(m.RunningForSession(sub.session))})
94 }
95 }
96
96 lines GO