Skip to content

Commit

Permalink
consul: avoid extra sync operations when no action required
Browse files Browse the repository at this point in the history
This PR makes it so the Consul sync logic will ignore operations that
do not specify an action to take (i.e. [de-]register [services|checks]).

Ideally such noops would be discarded at the callsites (i.e. users
of [Create|Update|Remove]Workload], but we can also be defensive
at the commit point.

Also adds 2 trace logging statements which are helpful for diagnosing
sync operations with Consul - when they happen and why.

Fixes #10797
  • Loading branch information
shoenig committed Jul 7, 2021
1 parent 5e0ffb9 commit 421a6a8
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .changelog/10865.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
consul: avoid extra sync operations when no action required
```
56 changes: 55 additions & 1 deletion command/agent/consul/service_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,27 @@ type operations struct {
deregChecks []string
}

func (o *operations) empty() bool {
switch {
case o == nil:
return true
case len(o.regServices) > 0:
return false
case len(o.regChecks) > 0:
return false
case len(o.deregServices) > 0:
return false
case len(o.deregChecks) > 0:
return false
default:
return true
}
}

func (o operations) String() string {
return fmt.Sprintf("<%d, %d, %d, %d>", len(o.regServices), len(o.regChecks), len(o.deregServices), len(o.deregChecks))
}

// AllocRegistration holds the status of services registered for a particular
// allocations by task.
type AllocRegistration struct {
Expand Down Expand Up @@ -560,11 +581,24 @@ func (c *ServiceClient) hasSeen() bool {
type syncReason byte

const (
syncPeriodic = iota
syncPeriodic syncReason = iota
syncShutdown
syncNewOps
)

func (sr syncReason) String() string {
switch sr {
case syncPeriodic:
return "periodic"
case syncShutdown:
return "shutdown"
case syncNewOps:
return "operations"
default:
return "unexpected"
}
}

// Run the Consul main loop which retries operations against Consul. It should
// be called exactly once.
func (c *ServiceClient) Run() {
Expand Down Expand Up @@ -680,6 +714,24 @@ INIT:

// commit operations unless already shutting down.
func (c *ServiceClient) commit(ops *operations) {
c.logger.Trace("commit sync operations", "ops", ops)

// Ignore empty operations - ideally callers will optimize out syncs with
// nothing to do, but be defensive anyway. Sending an empty ops on the chan
// will trigger an unnecessary sync with Consul.
if ops.empty() {
return
}

// Prioritize doing nothing if we are being signaled to shutdown.
select {
case <-c.shutdownCh:
return
default:
}

// Send the ops down the ops chan, triggering a sync with Consul. Unless we
// receive a signal to shutdown.
select {
case c.opCh <- ops:
case <-c.shutdownCh:
Expand Down Expand Up @@ -713,6 +765,8 @@ func (c *ServiceClient) merge(ops *operations) {

// sync enqueued operations.
func (c *ServiceClient) sync(reason syncReason) error {
c.logger.Trace("execute sync", "reason", reason)

sreg, creg, sdereg, cdereg := 0, 0, 0, 0
var err error

Expand Down
25 changes: 25 additions & 0 deletions command/agent/consul/service_client_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package consul

import (
"fmt"
"testing"
"time"

Expand Down Expand Up @@ -599,3 +600,27 @@ func TestSyncLogic_proxyUpstreamsDifferent(t *testing.T) {
}
})
}

func TestSyncReason_String(t *testing.T) {
t.Parallel()

require.Equal(t, "periodic", fmt.Sprintf("%s", syncPeriodic))
require.Equal(t, "shutdown", fmt.Sprintf("%s", syncShutdown))
require.Equal(t, "operations", fmt.Sprintf("%s", syncNewOps))
require.Equal(t, "unexpected", fmt.Sprintf("%s", syncReason(128)))
}

func TestSyncOps_empty(t *testing.T) {
t.Parallel()

try := func(ops *operations, exp bool) {
require.Equal(t, exp, ops.empty())
}

try(&operations{regServices: make([]*api.AgentServiceRegistration, 1)}, false)
try(&operations{regChecks: make([]*api.AgentCheckRegistration, 1)}, false)
try(&operations{deregServices: make([]string, 1)}, false)
try(&operations{deregChecks: make([]string, 1)}, false)
try(&operations{}, true)
try(nil, true)
}

0 comments on commit 421a6a8

Please sign in to comment.