@@ -8,16 +8,18 @@ import (
88 "github.com/jr-k/d4s/internal/config"
99 "github.com/jr-k/d4s/internal/dao"
1010 "github.com/jr-k/d4s/internal/ui/dialogs"
11+ "github.com/jr-k/d4s/internal/ui/styles"
1112)
1213
1314func (a * App ) ShowContextPicker () {
14- contexts , err := a .Docker .ListContexts ()
15+ docker := a .GetDocker ()
16+ contexts , err := docker .ListContexts ()
1517 if err != nil {
1618 a .AppendFlashError (fmt .Sprintf ("failed to load docker contexts: %v" , err ))
1719 return
1820 }
1921
20- active := strings .TrimSpace (a . Docker .ContextName )
22+ active := strings .TrimSpace (docker .ContextName )
2123 saved := strings .TrimSpace (a .Cfg .D4S .DefaultContext )
2224
2325 items := make ([]dialogs.PickerItem , 0 , len (contexts ))
@@ -66,14 +68,11 @@ func (a *App) SetDefaultContext(contextName string) {
6668 return
6769 }
6870
69- if a .Docker != nil && a .Docker .ContextName == contextName {
70- a .Cfg .D4S .DefaultContext = contextName
71- if err := config .Save (a .Cfg ); err != nil {
72- a .AppendFlashError (fmt .Sprintf ("failed to save default context: %v" , err ))
73- return
74- }
75-
76- a .AppendFlashSuccess (contextSavedMessage (contextName ))
71+ if docker := a .GetDocker (); docker != nil && docker .ContextName == contextName {
72+ // Re-selecting the active context cancels any older switch that may
73+ // still be preparing another client in the background.
74+ switchGen := a .contextSwitchGen .Add (1 )
75+ a .saveDefaultContext (contextName , switchGen )
7776 a .updateHeader ()
7877 return
7978 }
@@ -90,60 +89,94 @@ func (a *App) ReloadContext(contextName string) {
9089 return
9190 }
9291
92+ switchGen := a .contextSwitchGen .Add (1 )
9393 a .SetFlashPending (fmt .Sprintf ("switching context to %s..." , contextName ))
94- a .SetPaused (true )
95- a .StopAutoRefresh ()
96-
97- if a .ActiveInspector != nil {
98- a .ActiveInspector .OnUnmount ()
99- a .ActiveInspector = nil
100- }
101- if a .Pages .HasPage ("inspect" ) {
102- a .Pages .RemovePage ("inspect" )
103- }
104-
105- a .SafeSetScope (nil )
106- a .ActiveFilter = ""
107- for _ , v := range a .Views {
108- v .SetLoading (true )
109- // Free the fetch guard held by any fetch still running against the
110- // old endpoint (possibly hung), and mark its results as stale.
111- v .InvalidateFetch ()
112- }
113- a .RestoreFocus ()
114- a .UpdateShortcuts ()
11594
11695 a .RunInBackground (func () {
11796 newDocker , err := dao .NewDockerClient (contextName , a .Cfg .D4S .GetAPIServerTimeout (), contextName )
11897 if err != nil {
11998 a .TviewApp .QueueUpdateDraw (func () {
120- a .SetPaused (false )
121- a .StartAutoRefresh ()
99+ if a .contextSwitchGen .Load () != switchGen {
100+ return
101+ }
122102 a .AppendFlashError (fmt .Sprintf ("failed to switch context: %v" , err ))
123- a .RefreshCurrentView ()
124103 a .updateHeader ()
125104 })
126105 return
127106 }
128107
129- a .Cfg .D4S .DefaultContext = contextName
130- saveErr := config .Save (a .Cfg )
131-
132108 a .TviewApp .QueueUpdateDraw (func () {
133- a .Docker = newDocker
134- a .SetPaused (false )
135- a .StartAutoRefresh ()
109+ if a .contextSwitchGen .Load () != switchGen {
110+ a .RunInBackground (func () {
111+ _ = newDocker .Close ()
112+ })
113+ return
114+ }
115+
116+ if a .ActiveInspector != nil {
117+ a .ActiveInspector .OnUnmount ()
118+ a .ActiveInspector = nil
119+ }
120+ if a .Pages .HasPage ("inspect" ) {
121+ a .Pages .RemovePage ("inspect" )
122+ }
123+
124+ a .SafeSetScope (nil )
125+ a .ActiveFilter = ""
126+ currentPage , _ := a .Pages .GetFrontPage ()
127+ for title , v := range a .Views {
128+ // Context metadata remains usable while resources from the
129+ // newly selected Docker endpoint are loading.
130+ if title == currentPage && title != styles .TitleContexts {
131+ v .SetLoading (true )
132+ } else if title != styles .TitleContexts {
133+ v .InvalidateData ()
134+ }
135+ // Cancel requests against the previous endpoint and reject
136+ // any results that arrive after the client swap.
137+ v .InvalidateFetch ()
138+ }
139+
140+ oldDocker := a .swapDocker (newDocker )
141+ if oldDocker != nil {
142+ a .RunInBackground (func () {
143+ _ = oldDocker .Close ()
144+ })
145+ }
146+
147+ a .saveDefaultContext (contextName , switchGen )
148+
136149 a .RestoreFocus ()
137150 a .UpdateShortcuts ()
138151 a .updateHeader ()
139152 a .RefreshCurrentView ()
140- a .preloadViews ()
153+ })
154+ })
155+ }
156+
157+ func (a * App ) saveDefaultContext (contextName string , switchGen uint64 ) {
158+ a .Cfg .D4S .DefaultContext = contextName
159+ cfg := * a .Cfg
160+
161+ a .RunInBackground (func () {
162+ // Serialize writes so an older context switch can never overwrite a
163+ // newer selection after a slow filesystem operation.
164+ a .contextSaveMx .Lock ()
165+ defer a .contextSaveMx .Unlock ()
166+
167+ if a .contextSwitchGen .Load () != switchGen {
168+ return
169+ }
170+ err := config .Save (& cfg )
141171
142- if saveErr != nil {
143- a .AppendFlashError (fmt .Sprintf ("switched to %s, but failed to save default: %v" , contextName , saveErr ))
172+ a .TviewApp .QueueUpdateDraw (func () {
173+ if a .contextSwitchGen .Load () != switchGen {
174+ return
175+ }
176+ if err != nil {
177+ a .AppendFlashError (fmt .Sprintf ("failed to save default context: %v" , err ))
144178 return
145179 }
146-
147180 a .AppendFlashSuccess (contextSavedMessage (contextName ))
148181 })
149182 })
0 commit comments