返回 DeepSeek-Reasonix
session_catalog_rebuild.go
根目录 / desktop / session_catalog_rebuild.go
1 package main
2
3 import (
4 "errors"
5 "time"
6
7 "reasonix/internal/sessioncatalog"
8 )
9
10 var (
11 errSessionCatalogStopTimeout = errors.New("session catalog did not stop before the rebuild deadline")
12 errSessionCatalogPanic = errors.New("session catalog rebuild failed unexpectedly")
13 )
14
15 type sessionCatalogRebuildFlight struct {
16 done chan struct{}
17 err error
18 }
19
20 // RebuildSessionCatalog owns the bounded, one-shot rebuild transaction. The
21 // replacement watcher is always an ordinary watcher and never owns rebuilding.
22 func (a *App) RebuildSessionCatalog() error {
23 return a.rebuildSessionCatalog(5 * time.Second)
24 }
25
26 func (a *App) rebuildSessionCatalog(stopTimeout time.Duration) (err error) {
27 if a == nil || a.shuttingDown.Load() {
28 return errors.New("application is shutting down")
29 }
30 a.catalogRebuildMu.Lock()
31 if flight := a.catalogRebuild; flight != nil {
32 a.catalogRebuildMu.Unlock()
33 if a.catalogRebuildJoinHook != nil {
34 a.catalogRebuildJoinHook()
35 }
36 <-flight.done
37 return flight.err
38 }
39 flight := &sessionCatalogRebuildFlight{done: make(chan struct{})}
40 a.catalogRebuild = flight
41 a.catalogRebuilding.Store(true)
42 a.catalogRebuildMu.Unlock()
43
44 defer func() {
45 panicValue := recover()
46 if panicValue != nil {
47 flight.err = errSessionCatalogPanic
48 } else {
49 flight.err = err
50 }
51 a.catalogRebuildMu.Lock()
52 a.catalogRebuild = nil
53 close(flight.done)
54 a.catalogRebuildMu.Unlock()
55 if panicValue != nil {
56 panic(panicValue)
57 }
58 }()
59 err = a.runSessionCatalogRebuild(stopTimeout)
60 return err
61 }
62
63 func (a *App) runSessionCatalogRebuild(stopTimeout time.Duration) error {
64 status := a.currentSessionCatalogStatus()
65 finishedRevision := status.Revision
66 defer func() {
67 if !a.shuttingDown.Load() {
68 // Arm the ordinary watcher before releasing the single-flight gate so
69 // another Wails rebuild cannot enter the stop/start handoff gap.
70 a.startSessionCatalog()
71 }
72 a.catalogRebuilding.Store(false)
73 if !a.shuttingDown.Load() {
74 a.emitProjectTreeChangedV2(finishedRevision, nil, "catalog_rebuild_finished")
75 }
76 }()
77 a.emitProjectTreeChangedV2(status.Revision, nil, "catalog_rebuild_started")
78
79 // Rebuild must not race the old SQLite handle on Windows, where publishing
80 // the atomic replacement can fail while that handle is still closing.
81 if !a.stopSessionCatalog(stopTimeout) {
82 return errSessionCatalogStopTimeout
83 }
84 replacement, err := sessioncatalog.RebuildWithRevisionFloor(
85 a.bootContext(), sessioncatalog.DefaultPath(), a.sessionCatalogTargets(), status.Revision,
86 )
87 if err == nil {
88 finishedRevision = max(finishedRevision, replacement.Revision)
89 }
90 return err
91 }
92
92 lines GO