返回 DeepSeek-Reasonix
window_restore_diagnostics.go
根目录 / desktop / window_restore_diagnostics.go
1 package main
2
3 import (
4 "encoding/json"
5 "fmt"
6 "os"
7 "path/filepath"
8 "sync"
9 "sync/atomic"
10 "time"
11
12 "reasonix/internal/config"
13 )
14
15 const (
16 windowRestoreStateVersion = 1
17 windowRestoreTimeout = 12 * time.Second
18 windowRestorePollInterval = 100 * time.Millisecond
19 )
20
21 type windowRestoreState struct {
22 SchemaVersion int `json:"schemaVersion"`
23 PID int `json:"pid"`
24 AttemptID uint64 `json:"attemptId,omitempty"`
25 Source string `json:"source"`
26 StartedAt string `json:"startedAt"`
27 TimeoutReported bool `json:"timeoutReported,omitempty"`
28 }
29
30 var (
31 windowRestoreMu sync.Mutex
32 windowRestoreSequence atomic.Uint64
33 )
34
35 func windowRestoreStatePath() string {
36 return filepath.Join(config.MemoryUserDir(), "repair", "window-restore-state.json")
37 }
38
39 func writeWindowRestoreState(state windowRestoreState) bool {
40 body, err := json.Marshal(state)
41 if err != nil {
42 return false
43 }
44 path := windowRestoreStatePath()
45 if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
46 return false
47 }
48 // This journal is best-effort diagnostics, not user data. Avoid the
49 // fsync-and-retry path here because it runs immediately before WindowShow
50 // and Windows antivirus locks can otherwise delay restoration noticeably.
51 return os.WriteFile(path, body, 0o600) == nil
52 }
53
54 func readWindowRestoreState() (windowRestoreState, error) {
55 body, err := os.ReadFile(windowRestoreStatePath())
56 if err != nil {
57 return windowRestoreState{}, err
58 }
59 var state windowRestoreState
60 if err := json.Unmarshal(body, &state); err != nil {
61 return windowRestoreState{}, err
62 }
63 return state, nil
64 }
65
66 func (a *App) observeIncompleteWindowRestore() {
67 if !windowRestoreDiagnosticsSupported() {
68 return
69 }
70 windowRestoreMu.Lock()
71 state, err := readWindowRestoreState()
72 if err != nil {
73 windowRestoreMu.Unlock()
74 return
75 }
76 if state.PID > 0 && windowRestoreOwnerAlive(state.PID) {
77 windowRestoreMu.Unlock()
78 return
79 }
80 _ = os.Remove(windowRestoreStatePath())
81 windowRestoreMu.Unlock()
82 if state.TimeoutReported {
83 return
84 }
85 _ = writePendingReport(windowRestoreFailureReport("incomplete", state.Source, state.StartedAt), true)
86 a.recordDiagnosticMetric("desktop_restore", "incomplete")
87 }
88
89 func (a *App) showMainWindowFrom(source string) {
90 if a.ctx == nil {
91 return
92 }
93 if !windowRestoreDiagnosticsSupported() {
94 showFromBackground(a.ctx, a.backgroundMaximised.Swap(false))
95 a.kickDeferredRebuildRetry()
96 return
97 }
98
99 windowRestoreMu.Lock()
100 attemptID := windowRestoreSequence.Add(1)
101 state := windowRestoreState{
102 SchemaVersion: windowRestoreStateVersion,
103 PID: os.Getpid(),
104 AttemptID: attemptID,
105 Source: metricBucket(source),
106 StartedAt: time.Now().UTC().Format(time.RFC3339Nano),
107 }
108 _ = writeWindowRestoreState(state)
109 windowRestoreMu.Unlock()
110
111 showFromBackground(a.ctx, a.backgroundMaximised.Swap(false))
112 a.goSafe("windowRestoreMonitor", func() {
113 restored := waitForWindowRestoreConfirmation()
114 a.completeWindowRestoreAttempt(attemptID, state, restored)
115 })
116 a.kickDeferredRebuildRetry()
117 }
118
119 func waitForWindowRestoreConfirmation() bool {
120 ticker := time.NewTicker(windowRestorePollInterval)
121 defer ticker.Stop()
122 timer := time.NewTimer(windowRestoreTimeout)
123 defer timer.Stop()
124 return awaitWindowRestoreConfirmation(windowRestoreConfirmed, ticker.C, timer.C)
125 }
126
127 func awaitWindowRestoreConfirmation(confirmed func() bool, ticks, deadline <-chan time.Time) bool {
128 if confirmed() {
129 return true
130 }
131 for {
132 select {
133 case <-ticks:
134 if confirmed() {
135 return true
136 }
137 case <-deadline:
138 return confirmed()
139 }
140 }
141 }
142
143 func (a *App) completeWindowRestoreAttempt(attemptID uint64, state windowRestoreState, restored bool) {
144 metric := "success"
145 windowRestoreMu.Lock()
146 if windowRestoreSequence.Load() != attemptID {
147 windowRestoreMu.Unlock()
148 return
149 }
150 if restored {
151 _ = os.Remove(windowRestoreStatePath())
152 } else {
153 metric = "timeout"
154 if writePendingReport(windowRestoreFailureReport("timeout", state.Source, state.StartedAt), true) {
155 state.TimeoutReported = true
156 _ = writeWindowRestoreState(state)
157 }
158 }
159 windowRestoreMu.Unlock()
160 a.recordDiagnosticMetric("desktop_restore", metric)
161 }
162
163 func windowRestoreFailureReport(kind, source, startedAt string) crashReport {
164 kind = metricBucket(kind)
165 source = metricBucket(source)
166 report := baseCrashReport("performance")
167 report.SchemaVersion = 2
168 report.Source = "native.window"
169 report.Label = "windows.window_restore." + kind
170 report.ErrorType = "WindowsWindowRestoreFailure"
171 report.ErrorMessage = sanitizeCrashText("Windows window restoration did not complete normally.", maxCrashFieldBytes)
172 report.TopFrame = "windows.window_restore." + source
173 report.FingerprintHint = "windows.window_restore." + kind + "." + source
174 report.OccurredAt = time.Now().UTC().Format(time.RFC3339)
175 report.Message = sanitizeCrashText(fmt.Sprintf(`[windows.window_restore.%s]
176
177 Reasonix could not confirm that the hidden window was restored.
178
179 source: %s
180 attempt started at: %s
181 timeout: %s`, kind, source, sanitizeCrashField(startedAt, 64), windowRestoreTimeout), maxCrashDetailBytes)
182 return report
183 }
184
184 lines GO