consul/agent/proxycfg-sources/catalog/mock_ConfigManager.go

79 lines
2.4 KiB
Go
Raw Normal View History

Fix xDS deadlock due to syncLoop termination. (#20867) * Fix xDS deadlock due to syncLoop termination. This fixes an issue where agentless xDS streams can deadlock permanently until a server is restarted. When this issue occurs, no new proxies are able to successfully connect to the server. Effectively, the trigger for this deadlock stems from the following return statement: https://github.com/hashicorp/consul/blob/v1.18.0/agent/proxycfg-sources/catalog/config_source.go#L199-L202 When this happens, the entire `syncLoop()` terminates and stops consuming from the following channel: https://github.com/hashicorp/consul/blob/v1.18.0/agent/proxycfg-sources/catalog/config_source.go#L182-L192 Which results in the `ConfigSource.cleanup()` function never receiving a response and holding a mutex indefinitely: https://github.com/hashicorp/consul/blob/v1.18.0/agent/proxycfg-sources/catalog/config_source.go#L241-L247 Because this mutex is shared, it effectively deadlocks the server's ability to process new xDS streams. ---- The fix to this issue involves removing the `chan chan struct{}` used like an RPC-over-channels pattern and replacing it with two distinct channels: + `stopSyncLoopCh` - indicates that the `syncLoop()` should terminate soon. + `syncLoopDoneCh` - indicates that the `syncLoop()` has terminated. Splitting these two concepts out and deferring a `close(syncLoopDoneCh)` in the `syncLoop()` function ensures that the deadlock above should no longer occur. We also now evict xDS connections of all proxies for the corresponding `syncLoop()` whenever it encounters an irrecoverable error. This is done by hoisting the new `syncLoopDoneCh` upwards so that it's visible to the xDS delta processing. Prior to this fix, the behavior was to simply orphan them so they would never receive catalog-registration or service-defaults updates. * Add changelog.
2024-03-15 18:57:11 +00:00
// Code generated by mockery v2.37.1. DO NOT EDIT.
package catalog
import (
proxycfg "github.com/hashicorp/consul/agent/proxycfg"
mock "github.com/stretchr/testify/mock"
proxysnapshot "github.com/hashicorp/consul/internal/mesh/proxy-snapshot"
structs "github.com/hashicorp/consul/agent/structs"
)
// MockConfigManager is an autogenerated mock type for the ConfigManager type
type MockConfigManager struct {
mock.Mock
}
// Deregister provides a mock function with given fields: proxyID, source
func (_m *MockConfigManager) Deregister(proxyID proxycfg.ProxyID, source proxycfg.ProxySource) {
_m.Called(proxyID, source)
}
// Register provides a mock function with given fields: proxyID, service, source, token, overwrite
func (_m *MockConfigManager) Register(proxyID proxycfg.ProxyID, service *structs.NodeService, source proxycfg.ProxySource, token string, overwrite bool) error {
ret := _m.Called(proxyID, service, source, token, overwrite)
var r0 error
if rf, ok := ret.Get(0).(func(proxycfg.ProxyID, *structs.NodeService, proxycfg.ProxySource, string, bool) error); ok {
r0 = rf(proxyID, service, source, token, overwrite)
} else {
r0 = ret.Error(0)
}
return r0
}
// Watch provides a mock function with given fields: req
func (_m *MockConfigManager) Watch(req proxycfg.ProxyID) (<-chan proxysnapshot.ProxySnapshot, proxysnapshot.CancelFunc) {
ret := _m.Called(req)
var r0 <-chan proxysnapshot.ProxySnapshot
var r1 proxysnapshot.CancelFunc
if rf, ok := ret.Get(0).(func(proxycfg.ProxyID) (<-chan proxysnapshot.ProxySnapshot, proxysnapshot.CancelFunc)); ok {
return rf(req)
}
if rf, ok := ret.Get(0).(func(proxycfg.ProxyID) <-chan proxysnapshot.ProxySnapshot); ok {
r0 = rf(req)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(<-chan proxysnapshot.ProxySnapshot)
}
}
if rf, ok := ret.Get(1).(func(proxycfg.ProxyID) proxysnapshot.CancelFunc); ok {
r1 = rf(req)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(proxysnapshot.CancelFunc)
}
}
return r0, r1
}
// NewMockConfigManager creates a new instance of MockConfigManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockConfigManager(t interface {
mock.TestingT
Cleanup(func())
}) *MockConfigManager {
mock := &MockConfigManager{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}