Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Back-port: Prevent the router from deadlocking itself when calling Commit() #13744

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions pkg/router/template/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ func NewFakeTemplateRouter() *templateRouter {
}
}

// FakeReloadHandler implements the minimal changes needed to make the locking behavior work
// This MUST match the behavior with the stateChanged of commitAndReload
func (r *templateRouter) FakeReloadHandler() {
r.lock.Lock()
defer r.lock.Unlock()

r.stateChanged = false

return
}

// fakeCertWriter is a certificate writer that records actions but is a no-op
type fakeCertWriter struct {
addedCerts []string
Expand Down
10 changes: 7 additions & 3 deletions pkg/router/template/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,19 +366,21 @@ func (r *templateRouter) readState() error {
// Commit applies the changes made to the router configuration - persists
// the state and refresh the backend. This is all done in the background
// so that we can rate limit + coalesce multiple changes.
// Note: If this is changed FakeCommit() in fake.go should also be updated
func (r *templateRouter) Commit() {
r.lock.Lock()
defer r.lock.Unlock()

if !r.synced {
glog.V(4).Infof("Router state synchronized for the first time")
r.synced = true
r.stateChanged = true
}

if r.stateChanged {
needsCommit := r.stateChanged
r.lock.Unlock()

if needsCommit {
r.rateLimitedCommitFunction.Invoke(r.rateLimitedCommitFunction)
r.stateChanged = false
}
}

Expand All @@ -387,6 +389,8 @@ func (r *templateRouter) commitAndReload() error {
r.lock.Lock()
defer r.lock.Unlock()

r.stateChanged = false

glog.V(4).Infof("Writing the router state")
if err := r.writeState(); err != nil {
return err
Expand Down
10 changes: 6 additions & 4 deletions test/integration/router_stress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func stressRouter(t *testing.T, namespaceCount, routesPerNamespace, routerCount,
// Create the routers
for i := int32(0); i < routerCount; i++ {
routerName := fmt.Sprintf("router_%d", i)
router := launchRouter(oc, kc, maxRouterDelay, routerName, reloadInterval, reloadedMap)
router := launchRouter(t, oc, kc, maxRouterDelay, routerName, reloadInterval, reloadedMap)
plugins = append(plugins, router)
}

Expand Down Expand Up @@ -148,12 +148,12 @@ func stressRouter(t *testing.T, namespaceCount, routesPerNamespace, routerCount,
}
}

for _, reloadCount := range reloadedMap {
for routerName, reloadCount := range reloadedMap {
if reloadCount > 1 {
// If a router reloads more than once, post-sync watch
// events resulting from route status updates are
// incorrectly updating router state.
t.Fatalf("One or more routers reloaded more than once")
t.Fatalf("One or more routers reloaded more than once (%s reloaded %d times)", routerName, reloadCount)
}
}

Expand Down Expand Up @@ -281,12 +281,14 @@ func (p *DelayPlugin) Commit() error {

// launchRouter launches a template router that communicates with the
// api via the provided clients.
func launchRouter(oc osclient.Interface, kc kclientset.Interface, maxDelay int32, name string, reloadInterval int, reloadedMap map[string]int) (templatePlugin *templateplugin.TemplatePlugin) {
func launchRouter(t *testing.T, oc osclient.Interface, kc kclientset.Interface, maxDelay int32, name string, reloadInterval int, reloadedMap map[string]int) (templatePlugin *templateplugin.TemplatePlugin) {
r := templateplugin.NewFakeTemplateRouter()

reloadedMap[name] = 0
r.EnableRateLimiter(reloadInterval, func() error {
reloadedMap[name] += 1
t.Logf("Router %s reloaded (%d times)\n", name, reloadedMap[name])
r.FakeReloadHandler()
return nil
})

Expand Down