@@ -105,16 +105,53 @@ func (l *Listener) dispatchFSNotify(event fsnotify.Event) {
105105 return
106106 }
107107
108- if _ , loaded := l .watcherMap .LoadOrStore (serviceName , true ); ! loaded {
109- go l .waitForStableState (serviceName )
108+ l .track (serviceName )
109+ }
110+
111+ // track starts a watcher for service, or marks the running one: an event it
112+ // has not seen may postdate the state it is about to settle on (a restart).
113+ func (l * Listener ) track (service string ) {
114+ l .watchMu .Lock ()
115+ defer l .watchMu .Unlock ()
116+ if l .watching == nil {
117+ l .watching = make (map [string ]bool )
118+ }
119+ if _ , running := l .watching [service ]; running {
120+ l .watching [service ] = true
121+ return
110122 }
123+ l .watching [service ] = false
124+ go l .waitForStableState (service )
125+ }
126+
127+ // settle ends the watch on service, unless an event came in meanwhile: then it
128+ // clears the mark and reports false, and the watcher reads the state again.
129+ func (l * Listener ) settle (service string ) bool {
130+ l .watchMu .Lock ()
131+ defer l .watchMu .Unlock ()
132+ if l .watching [service ] {
133+ l .watching [service ] = false
134+ return false
135+ }
136+ delete (l .watching , service )
137+ return true
138+ }
139+
140+ func (l * Listener ) untrack (service string ) {
141+ l .watchMu .Lock ()
142+ defer l .watchMu .Unlock ()
143+ delete (l .watching , service )
111144}
112145
113146func (l * Listener ) waitForStableState (service string ) {
114147 timeout := l .backend .config .Timeout
115148 ctx , cancel := context .WithTimeout (l .backend .ctx , timeout )
149+ settled := false
116150 defer func () {
117- l .watcherMap .Delete (service )
151+ // Once settled the entry may already belong to a newer watcher.
152+ if ! settled {
153+ l .untrack (service )
154+ }
118155 if ctx .Err () == context .DeadlineExceeded {
119156 logger .Warn ("[systemd] %s failed to start in less than %s, cache might be out of sync" , service , timeout )
120157 refreshCtx , refreshCancel := context .WithTimeout (l .backend .ctx , 5 * time .Second )
@@ -146,8 +183,13 @@ func (l *Listener) waitForStableState(service string) {
146183 timer .Reset (waitTime )
147184 continue
148185 }
149- switch unit .ActiveState {
150- case "active" , "inactive" , "failed" :
186+ if isStableState (unit .ActiveState ) {
187+ if ! l .settle (service ) {
188+ logger .Debug ("[systemd] %s/%s changed while settling on %s, reading again" , ScopeUser , service , unit .ActiveState )
189+ timer .Reset (0 )
190+ continue
191+ }
192+ settled = true
151193 logger .Debug ("[systemd] %s/%s reached stable state: %s" , ScopeUser , service , unit .ActiveState )
152194 l .backend .notifyService (* unit )
153195 return
@@ -162,3 +204,33 @@ func (l *Listener) waitForStableState(service string) {
162204 }
163205 }
164206}
207+
208+ func isStableState (state string ) bool {
209+ switch state {
210+ case "active" , "inactive" , "failed" :
211+ return true
212+ }
213+ return false
214+ }
215+
216+ // trackTransitional follows the watched user units the startup snapshot caught
217+ // mid-transition: their invocation link predates the watcher, so no event comes.
218+ func (l * Listener ) trackTransitional (services []Service ) {
219+ if l .supportsUTMP {
220+ return // D-Bus signals report the end of the transition
221+ }
222+ for _ , name := range transitionalUnits (services , l .userWatched ) {
223+ l .track (name )
224+ }
225+ }
226+
227+ // transitionalUnits lists the watched user units not yet in a stable state.
228+ func transitionalUnits (services []Service , watched map [string ]bool ) []string {
229+ var names []string
230+ for _ , svc := range services {
231+ if svc .Scope == ScopeUser && watched [svc .Name ] && ! isStableState (svc .ActiveState ) {
232+ names = append (names , svc .Name )
233+ }
234+ }
235+ return names
236+ }
0 commit comments