@@ -72,6 +72,13 @@ type ServiceRegistry struct {
7272 services map [string ]Service
7373 dht dhtProvider
7474
75+ // names claimed by a Register that has not committed yet, guarded by mu
76+ pending map [string ]* pendingRegistration
77+
78+ // bumped by TeardownAll so a Register still running cannot commit into the
79+ // map it just emptied, guarded by mu
80+ generation uint64
81+
7582 // reprovideNow asks the node to run a reprovide cycle now, so a service
7683 // registered after the loop last ran does not wait a whole interval to be
7784 // advertised. Optional; nil outside a running node.
@@ -83,6 +90,12 @@ type ServiceRegistry struct {
8390 backendProbeTimeout time.Duration
8491}
8592
93+ // claim on a name held by a Register that has not committed yet, set when the
94+ // name is unregistered before the registration commits
95+ type pendingRegistration struct {
96+ cancelled bool
97+ }
98+
8699// NewServiceRegistry constructs a registry bounding backend probes by
87100// backendProbeTimeout. A zero or negative value falls back to
88101// defaultDHTProbeTimeout, so callers can pass an unset
@@ -94,6 +107,7 @@ func NewServiceRegistry(d dhtProvider, backendProbeTimeout time.Duration) *Servi
94107 }
95108 return & ServiceRegistry {
96109 services : map [string ]Service {},
110+ pending : map [string ]* pendingRegistration {},
97111 dht : d ,
98112 backendProbeTimeout : backendProbeTimeout ,
99113 }
@@ -106,6 +120,8 @@ func NewServiceRegistry(d dhtProvider, backendProbeTimeout time.Duration) *Servi
106120// A backend that does not answer is registered but not advertised, rather than
107121// rejected: backends routinely start after the node does, and the reprovide
108122// loop picks them up once they answer.
123+ //
124+ // registering a name again replaces the instance and tears the old one down
109125func (r * ServiceRegistry ) Register (ctx context.Context , svc Service ) error {
110126 info := svc .Info ()
111127 if info .Type == api .ServiceType_SERVICE_TYPE_UNSPECIFIED {
@@ -123,26 +139,53 @@ func (r *ServiceRegistry) Register(ctx context.Context, svc Service) error {
123139 return fmt .Errorf ("invalid service name %q: %w" , info .Name , err )
124140 }
125141
126- // Policy is evaluated on type://name and the registry is keyed on name,
127- // so a second type under the same name would let a grant for one reach
128- // the other. One name, one service.
129- r .mu .RLock ()
130- existing , taken := r .services [info .Name ]
131- r .mu .RUnlock ()
132- if taken && existing .Info ().Type != info .Type {
142+ // the registry is keyed on name but policy is evaluated on type://name, so
143+ // one name maps to one type. reserve under the same lock as the check, or
144+ // two concurrent registrations both pass it and disagree about the owner
145+ r .mu .Lock ()
146+ if r .pending == nil {
147+ r .pending = map [string ]* pendingRegistration {}
148+ }
149+ if existing , taken := r .services [info .Name ]; taken && existing .Info ().Type != info .Type {
150+ r .mu .Unlock ()
133151 return fmt .Errorf ("service name %q is already registered as %s; a name cannot serve two types" , info .Name , existing .Info ().Type )
134152 }
153+ if _ , inFlight := r .pending [info .Name ]; inFlight {
154+ r .mu .Unlock ()
155+ return fmt .Errorf ("service name %q is already being registered" , info .Name )
156+ }
157+ reservation := & pendingRegistration {}
158+ r .pending [info .Name ] = reservation
159+ generation := r .generation
160+ r .mu .Unlock ()
161+
162+ // release drops the reservation, teardown is for aborts after Init where
163+ // the service has already built something to clean up
164+ release := func (teardown bool ) {
165+ r .mu .Lock ()
166+ delete (r .pending , info .Name )
167+ r .mu .Unlock ()
168+ if ! teardown {
169+ return
170+ }
171+ if err := svc .Teardown (); err != nil {
172+ logger .Errorf ("[ServiceRegistry] Teardown %s after abandoned registration: %v" , info .Name , err )
173+ }
174+ }
135175
136176 if err := svc .Init (ctx ); err != nil {
177+ release (false )
137178 return fmt .Errorf ("init %s: %w" , info .Name , err )
138179 }
139180
140181 srvNameCID , err := serviceNameToCID (info .Type , info .Name )
141182 if err != nil {
183+ release (true )
142184 return err
143185 }
144186 srvTypeCID , err := serviceTypeToCID (info .Type )
145187 if err != nil {
188+ release (true )
146189 return err
147190 }
148191
@@ -163,10 +206,33 @@ func (r *ServiceRegistry) Register(ctx context.Context, svc Service) error {
163206 }
164207 }
165208
209+ // the registry may have been torn down or the name unregistered while we
210+ // were initialising, so don't leave the service in the map
166211 r .mu .Lock ()
212+ if r .generation != generation {
213+ r .mu .Unlock ()
214+ release (true )
215+ return fmt .Errorf ("registry was torn down while %q was being registered" , info .Name )
216+ }
217+ if reservation .cancelled {
218+ r .mu .Unlock ()
219+ release (true )
220+ return fmt .Errorf ("service name %q was unregistered while it was being registered" , info .Name )
221+ }
222+ delete (r .pending , info .Name )
223+ replaced := r .services [info .Name ]
167224 r .services [info .Name ] = svc
168225 r .mu .Unlock ()
169226
227+ // teardown outside the lock, it kills a subprocess
228+ if replaced != nil && replaced != svc {
229+ if err := replaced .Teardown (); err != nil {
230+ logger .Errorf ("[ServiceRegistry] Teardown replaced %s: %v" , info .Name , err )
231+ } else {
232+ logger .Infof ("[ServiceRegistry] Replaced %s/%s, previous instance torn down" , info .Type , info .Name )
233+ }
234+ }
235+
170236 if probeErr == nil {
171237 logger .Infof ("[ServiceRegistry] Registered %s/%s (name CID: %s, type CID: %s)" , info .Type , info .Name , srvNameCID , srvTypeCID )
172238 } else if r .reprovideNow != nil {
@@ -181,6 +247,11 @@ func (r *ServiceRegistry) Unregister(ctx context.Context, name string) error {
181247 r .mu .Lock ()
182248 svc , ok := r .services [name ]
183249 delete (r .services , name )
250+ // cancel a registration of this name that has not committed, else it lands
251+ // after we return and the name comes back
252+ if p , inFlight := r .pending [name ]; inFlight {
253+ p .cancelled = true
254+ }
184255 r .mu .Unlock ()
185256 if ! ok {
186257 return nil
@@ -238,10 +309,14 @@ func (r *ServiceRegistry) insertService(svc Service) {
238309
239310// TeardownAll calls Teardown on every registered service and clears the
240311// map. Per-service errors are logged; iteration continues.
312+ //
313+ // the generation bump voids registrations still running, which would otherwise
314+ // commit into the map this just emptied
241315func (r * ServiceRegistry ) TeardownAll () {
242316 r .mu .Lock ()
243317 svcs := r .services
244318 r .services = map [string ]Service {}
319+ r .generation ++
245320 r .mu .Unlock ()
246321 for name , svc := range svcs {
247322 if err := svc .Teardown (); err != nil {
0 commit comments