HandleConfigChange reconciles org stacks when the config snapshot changes.
(old, new *configstore.Snapshot)
| 231 | // same mutex that guards the terminal shutdown transition. This ordering makes |
| 232 | // stackOps.Wait safe: once terminal is set, no later WaitGroup.Add can race it. |
| 233 | func (tr *OrgRouter) beginOrgStackOperation() (func(), bool) { |
| 234 | tr.mu.Lock() |
| 235 | defer tr.mu.Unlock() |
| 236 | if tr.terminal { |
| 237 | return nil, false |
| 238 | } |
| 239 | tr.stackOps.Add(1) |
| 240 | return tr.stackOps.Done, true |
| 241 | } |
| 242 | |
| 243 | // beginOrgStackMutation holds one per-org generation slot through either |
| 244 | // construction/publication or removal/teardown. OrgReservedPool.ShutdownAll is |
| 245 | // intentionally org-scoped rather than pool-instance-scoped, so allowing old |
| 246 | // teardown to overlap replacement construction could retire the replacement's |
| 247 | // workers. |
| 248 | func (tr *OrgRouter) beginOrgStackMutation(orgID string) (func(), error) { |
| 249 | finishOperation, ok := tr.beginOrgStackOperation() |
| 250 | if !ok { |
| 251 | return nil, fmt.Errorf("org router is shutting down") |
| 252 | } |
| 253 | |
| 254 | for { |
| 255 | tr.mu.Lock() |
| 256 | if tr.terminal { |
| 257 | tr.mu.Unlock() |
| 258 | finishOperation() |
| 259 | return nil, fmt.Errorf("org router is shutting down") |
| 260 | } |
| 261 | if tr.orgStackMutations == nil { |
| 262 | tr.orgStackMutations = make(map[string]chan struct{}) |
| 263 | } |
| 264 | if wait, exists := tr.orgStackMutations[orgID]; exists { |
| 265 | tr.mu.Unlock() |
| 266 | <-wait |
| 267 | continue |
| 268 | } |
| 269 | |
| 270 | done := make(chan struct{}) |
| 271 | tr.orgStackMutations[orgID] = done |
| 272 | tr.mu.Unlock() |
| 273 | |
| 274 | var once sync.Once |
| 275 | return func() { |
| 276 | once.Do(func() { |
| 277 | tr.mu.Lock() |
| 278 | delete(tr.orgStackMutations, orgID) |
| 279 | close(done) |
| 280 | tr.mu.Unlock() |
| 281 | finishOperation() |
| 282 | }) |
| 283 | }, nil |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | // beginOrgStackCreation acquires the per-org generation slot and verifies that |
| 288 | // no published stack exists before the caller constructs any org-scoped pool. |
| 289 | func (tr *OrgRouter) beginOrgStackCreation(orgID string) (func(), error) { |
| 290 | finish, err := tr.beginOrgStackMutation(orgID) |