Skip to content

Moved locking to protect a read of a map in the router #15385

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

Merged
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
32 changes: 23 additions & 9 deletions pkg/router/template/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -592,16 +592,22 @@ func (r *templateRouter) FilterNamespaces(namespaces sets.String) {

// CreateServiceUnit creates a new service named with the given id.
func (r *templateRouter) CreateServiceUnit(id string) {
r.lock.Lock()
defer r.lock.Unlock()

r.createServiceUnitInternal(id)
}

// CreateServiceUnit creates a new service named with the given id - internal
// lockless form, caller needs to ensure lock acquisition [and release].
func (r *templateRouter) createServiceUnitInternal(id string) {
parts := strings.SplitN(id, "/", 2)
service := ServiceUnit{
Name: id,
Hostname: fmt.Sprintf("%s.%s.svc", parts[1], parts[0]),
EndpointTable: []Endpoint{},
}

r.lock.Lock()
defer r.lock.Unlock()

r.serviceUnits[id] = service
r.stateChanged = true
}
Expand Down Expand Up @@ -767,6 +773,11 @@ func (r *templateRouter) AddRoute(route *routeapi.Route) {

newConfig := r.createServiceAliasConfig(route, backendKey)

// We have to call the internal form of functions after this
// because we are holding the state lock.
r.lock.Lock()
defer r.lock.Unlock()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is AddRoute called a lot in parallel? do we know what level of contention locking here is going to cause? is it worth making this a read lock, e.g.:

r.lock.RLock()
existingConfig, exists := r.state[backendKey]
r.lock.RUnlock()
if exists {
  ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It won't contend much... it's fed by the stream of events from the event queue and those are popped and handled one-by-one. The other access is when the router state is written out, and that only happens periodically.

BTW the current model is really appalling, we do the following:

  • Pop event
  • Lock the structure
  • Update state
  • Unlock the structure
  • Call a rate-limited function to write state (default is no more often than 5s)

If it hasn't written state recently, it will:

  • Lock the structure
  • Write the conf file
  • Reload haproxy
  • Unlock the structure

On a system with lots of routes it can take 10+ seconds to reload haproxy... so we process one event and then reload... and do the same forever.

We have a card open to fix this. We should only hold the state lock while the state is being written out. But we need to not touch state again until the reload is complete... so we need to add another lock there. And make sure that we don't block the event processing, just the event writing, while the reload happens.


if existingConfig, exists := r.state[backendKey]; exists {
if configsAreEqual(newConfig, &existingConfig) {
return
Expand All @@ -775,7 +786,7 @@ func (r *templateRouter) AddRoute(route *routeapi.Route) {
glog.V(4).Infof("Updating route %s/%s", route.Namespace, route.Name)

// Delete the route first, because modify is to be treated as delete+add
r.RemoveRoute(route)
r.removeRouteInternal(route)

// TODO - clean up service units that are no longer
// referenced. This may be challenging if a service unit can
Expand All @@ -788,15 +799,12 @@ func (r *templateRouter) AddRoute(route *routeapi.Route) {

// Add service units referred to by the config
for key := range newConfig.ServiceUnitNames {
if _, ok := r.FindServiceUnit(key); !ok {
if _, ok := r.findMatchingServiceUnit(key); !ok {
glog.V(4).Infof("Creating new frontend for key: %v", key)
r.CreateServiceUnit(key)
r.createServiceUnitInternal(key)
}
}

r.lock.Lock()
defer r.lock.Unlock()

r.state[backendKey] = *newConfig
r.stateChanged = true
}
Expand All @@ -806,6 +814,12 @@ func (r *templateRouter) RemoveRoute(route *routeapi.Route) {
r.lock.Lock()
defer r.lock.Unlock()

r.removeRouteInternal(route)
}

// removeRouteInternal removes the given route - internal
// lockless form, caller needs to ensure lock acquisition [and release].
func (r *templateRouter) removeRouteInternal(route *routeapi.Route) {
routeKey := r.routeKey(route)
serviceAliasConfig, ok := r.state[routeKey]
if !ok {
Expand Down