From c834a5b0a2e1519d57e37b253af1d29d468f436a Mon Sep 17 00:00:00 2001 From: nhsmw Date: Wed, 5 Aug 2026 18:28:23 +0800 Subject: [PATCH 1/5] config: Enable data sharing in default event store config --- pkg/config/debug.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/config/debug.go b/pkg/config/debug.go index 9186170b52..0d60d0440d 100644 --- a/pkg/config/debug.go +++ b/pkg/config/debug.go @@ -113,7 +113,7 @@ func NewDefaultEventStoreConfig() *EventStoreConfig { return &EventStoreConfig{ CompressionThreshold: 16384, // 16KB EnableZstdCompression: false, - EnableDataSharing: false, + EnableDataSharing: true, } } From 1f6c652f9ec4cd289ce0a8a001fe72d3a3c299c2 Mon Sep 17 00:00:00 2001 From: wk989898 Date: Wed, 12 Aug 2026 10:17:06 +0000 Subject: [PATCH 2/5] add dispatcher count in log service Signed-off-by: wk989898 --- coordinator/controller.go | 2 + coordinator/controller_drain.go | 27 +- coordinator/controller_drain_test.go | 51 ++- coordinator/drain/controller.go | 23 ++ coordinator/drain/controller_test.go | 37 ++ heartbeatpb/heartbeat.pb.go | 419 ++++++++++++---------- heartbeatpb/heartbeat.proto | 3 + logservice/eventstore/event_store.go | 9 + logservice/eventstore/event_store_test.go | 4 + maintainer/maintainer_manager_node.go | 21 +- maintainer/node_liveness_test.go | 14 + pkg/eventservice/event_service_test.go | 9 + 12 files changed, 414 insertions(+), 205 deletions(-) diff --git a/coordinator/controller.go b/coordinator/controller.go index 39114c7a7d..682e7e995f 100644 --- a/coordinator/controller.go +++ b/coordinator/controller.go @@ -1163,6 +1163,8 @@ func (c *Controller) RemoveNode(id node.ID) { observation.drainingObserved, observation.stoppingObserved, observation.remaining, + observation.logServiceDispatcherCount, + observation.logServiceDispatcherCountObserved, ) } diff --git a/coordinator/controller_drain.go b/coordinator/controller_drain.go index 86f5889f17..0c0ef47e8e 100644 --- a/coordinator/controller_drain.go +++ b/coordinator/controller_drain.go @@ -205,6 +205,8 @@ func (c *Controller) DrainNode(ctx context.Context, target node.ID) (int, error) observation.drainingObserved, observation.stoppingObserved, observation.remaining, + observation.logServiceDispatcherCount, + observation.logServiceDispatcherCountObserved, ) if completionObserved { @@ -225,6 +227,8 @@ func (c *Controller) DrainNode(ctx context.Context, target node.ID) (int, error) zap.Int("dispatcherCountOnTarget", observation.dispatcherCountOnTarget), zap.Int("targetInflightDrainMoveCount", observation.targetInflightDrainMoveCount), zap.Int("pendingStatusCount", observation.pendingStatusCount), + zap.Uint32("logServiceDispatcherCount", observation.logServiceDispatcherCount), + zap.Bool("logServiceDispatcherCountObserved", observation.logServiceDispatcherCountObserved), zap.Int("remaining", observation.remaining)) return ensureDrainRemainingNonZero(observation.remaining), nil } @@ -240,6 +244,8 @@ func (c *Controller) observeRemovedActiveDrainTarget(target node.ID, epoch uint6 observation.drainingObserved, observation.stoppingObserved, observation.remaining, + observation.logServiceDispatcherCount, + observation.logServiceDispatcherCountObserved, ) if completionObserved { log.Info("drain completion observed for removed active target", @@ -259,6 +265,8 @@ func (c *Controller) observeRemovedActiveDrainTarget(target node.ID, epoch uint6 zap.Int("dispatcherCountOnTarget", observation.dispatcherCountOnTarget), zap.Int("targetInflightDrainMoveCount", observation.targetInflightDrainMoveCount), zap.Int("pendingStatusCount", observation.pendingStatusCount), + zap.Uint32("logServiceDispatcherCount", observation.logServiceDispatcherCount), + zap.Bool("logServiceDispatcherCountObserved", observation.logServiceDispatcherCountObserved), zap.Int("remaining", observation.remaining)) return ensureDrainRemainingNonZero(observation.remaining) } @@ -310,10 +318,12 @@ type drainNodeObservation struct { // pendingStatusCount is the number of running changefeeds not converged to the active target epoch. pendingStatusCount int // remaining is the max of all workload dimensions used by drain completion gating. - remaining int - nodeState drain.State - drainingObserved bool - stoppingObserved bool + remaining int + nodeState drain.State + drainingObserved bool + stoppingObserved bool + logServiceDispatcherCount uint32 + logServiceDispatcherCountObserved bool } func (c *Controller) observeDrainNode(target node.ID, epoch uint64) drainNodeObservation { @@ -332,6 +342,8 @@ func (c *Controller) observeDrainNode(target node.ID, epoch uint64) drainNodeObs ) _, observation.drainingObserved, observation.stoppingObserved = c.drainController.GetStatus(target) + observation.logServiceDispatcherCount, observation.logServiceDispatcherCountObserved = + c.drainController.GetLogServiceDispatcherCount(target) observation.nodeState = c.drainController.GetState(target) return observation } @@ -1082,11 +1094,16 @@ func isBestEffortDrainComplete( drainingObserved bool, stoppingObserved bool, remaining int, + logServiceDispatcherCount uint32, + logServiceDispatcherCountObserved bool, ) bool { if nodeState == drain.StateUnknown || !drainingObserved { return false } - return stoppingObserved && remaining == 0 + return stoppingObserved && + remaining == 0 && + logServiceDispatcherCountObserved && + logServiceDispatcherCount == 0 } // drainRemainingEstimate uses the larger workload dimension to avoid obvious double counting. diff --git a/coordinator/controller_drain_test.go b/coordinator/controller_drain_test.go index cd72ad7ca6..b9f84c7b97 100644 --- a/coordinator/controller_drain_test.go +++ b/coordinator/controller_drain_test.go @@ -154,6 +154,39 @@ func TestDrainNodeCompletesAfterCompletionObserved(t *testing.T) { require.Equal(t, epoch, c.drainSession.epoch) } +func TestDrainNodeWaitsForLogServiceDispatchers(t *testing.T) { + c, drainController, target := newDrainTestController(t) + setDrainProtocolVersion(c, target, heartbeatpb.CurrentDrainProtocolVersion) + cf := addRunningChangefeed(c, "cf1", node.ID("other"), 100) + + remaining, err := c.DrainNode(context.Background(), target) + require.NoError(t, err) + require.Equal(t, 1, remaining) + + _, epoch, ok := c.getDispatcherDrainTarget() + require.True(t, ok) + setChangefeedDrainStatus(cf, target, epoch, 0, 0) + + // A STOPPING response alone does not contain the log service dispatcher count. + drainController.ObserveSetNodeLivenessResponse(target, &heartbeatpb.SetNodeLivenessResponse{ + Applied: heartbeatpb.NodeLiveness_STOPPING, + NodeEpoch: 1, + }) + remaining, err = c.DrainNode(context.Background(), target) + require.NoError(t, err) + require.Equal(t, 1, remaining) + + setTargetStoppingHeartbeat(drainController, target, 2) + remaining, err = c.DrainNode(context.Background(), target) + require.NoError(t, err) + require.Equal(t, 1, remaining) + + setTargetStoppingHeartbeat(drainController, target, 0) + remaining, err = c.DrainNode(context.Background(), target) + require.NoError(t, err) + require.Equal(t, 0, remaining) +} + func TestDrainNodeDispatcherCountBlocksCompletion(t *testing.T) { c, drainController, target := newDrainTestController(t) setDrainProtocolVersion(c, target, heartbeatpb.CurrentDrainProtocolVersion) @@ -1171,11 +1204,19 @@ func setTargetStoppingObserved( drainController *drain.Controller, target node.ID, ) { - resp := &heartbeatpb.SetNodeLivenessResponse{ - Applied: heartbeatpb.NodeLiveness_STOPPING, - NodeEpoch: 1, - } - drainController.ObserveSetNodeLivenessResponse(target, resp) + setTargetStoppingHeartbeat(drainController, target, 0) +} + +func setTargetStoppingHeartbeat( + drainController *drain.Controller, + target node.ID, + logServiceDispatcherCount uint32, +) { + drainController.ObserveHeartbeat(target, &heartbeatpb.NodeHeartbeat{ + Liveness: heartbeatpb.NodeLiveness_STOPPING, + NodeEpoch: 1, + LogServiceDispatcherCount: logServiceDispatcherCount, + }) } func drainMessageChannel(ch chan *messaging.TargetMessage) { diff --git a/coordinator/drain/controller.go b/coordinator/drain/controller.go index 81b0b900ee..4301bbfa3d 100644 --- a/coordinator/drain/controller.go +++ b/coordinator/drain/controller.go @@ -64,6 +64,11 @@ type nodeState struct { lastSeen time.Time nodeEpoch uint64 liveness heartbeatpb.NodeLiveness + + // logServiceDispatcherCount is valid only after a STOPPING heartbeat has + // been observed for the current node epoch. + logServiceDispatcherCount uint32 + logServiceDispatcherCountObserved bool } type drainTargetSchedulerGate struct { @@ -185,6 +190,11 @@ func (c *Controller) ObserveHeartbeat(nodeID node.ID, hb *heartbeatpb.NodeHeartb c.mu.Lock() defer c.mu.Unlock() c.observeLivenessLocked(nodeID, hb.NodeEpoch, hb.Liveness) + st := c.ensureNodeStateLocked(nodeID) + if hb.NodeEpoch == st.nodeEpoch && hb.Liveness == heartbeatpb.NodeLiveness_STOPPING { + st.logServiceDispatcherCount = hb.GetLogServiceDispatcherCount() + st.logServiceDispatcherCountObserved = true + } c.observeTargetSchedulerAckLocked(nodeID, hb) } @@ -415,6 +425,19 @@ func (c *Controller) GetStatus(nodeID node.ID) (drainRequested, drainingObserved return st.drainRequested, st.drainingObserved, st.stoppingObserved } +// GetLogServiceDispatcherCount returns the dispatcher count reported by a +// STOPPING heartbeat for the current node epoch. +func (c *Controller) GetLogServiceDispatcherCount(nodeID node.ID) (uint32, bool) { + c.mu.Lock() + defer c.mu.Unlock() + + st, ok := c.nodes[nodeID] + if !ok || !st.logServiceDispatcherCountObserved { + return 0, false + } + return st.logServiceDispatcherCount, true +} + // GetDrainProtocolVersion returns the bootstrap-observed drain capability for a node. func (c *Controller) GetDrainProtocolVersion(nodeID node.ID) (uint32, bool) { c.mu.Lock() diff --git a/coordinator/drain/controller_test.go b/coordinator/drain/controller_test.go index d02e009693..b6c72f13a0 100644 --- a/coordinator/drain/controller_test.go +++ b/coordinator/drain/controller_test.go @@ -126,6 +126,43 @@ func TestDrainControllerResetObservedStateForNewEpoch(t *testing.T) { c.mu.Unlock() } +func TestDrainControllerTracksStoppingLogServiceDispatcherCountByEpoch(t *testing.T) { + c := NewController(messaging.NewMockMessageCenter()) + target := node.ID("n1") + + c.ObserveSetNodeLivenessResponse(target, &heartbeatpb.SetNodeLivenessResponse{ + Applied: heartbeatpb.NodeLiveness_STOPPING, + NodeEpoch: 42, + }) + _, observed := c.GetLogServiceDispatcherCount(target) + require.False(t, observed) + + c.ObserveHeartbeat(target, &heartbeatpb.NodeHeartbeat{ + Liveness: heartbeatpb.NodeLiveness_STOPPING, + NodeEpoch: 42, + LogServiceDispatcherCount: 2, + }) + count, observed := c.GetLogServiceDispatcherCount(target) + require.True(t, observed) + require.Equal(t, uint32(2), count) + + c.ObserveHeartbeat(target, &heartbeatpb.NodeHeartbeat{ + Liveness: heartbeatpb.NodeLiveness_ALIVE, + NodeEpoch: 43, + }) + _, observed = c.GetLogServiceDispatcherCount(target) + require.False(t, observed) + + // A delayed heartbeat from the old process must not satisfy the new epoch. + c.ObserveHeartbeat(target, &heartbeatpb.NodeHeartbeat{ + Liveness: heartbeatpb.NodeLiveness_STOPPING, + NodeEpoch: 42, + LogServiceDispatcherCount: 0, + }) + _, observed = c.GetLogServiceDispatcherCount(target) + require.False(t, observed) +} + func TestDrainControllerSkipStoppingForNewEpochWithoutDraining(t *testing.T) { mc := messaging.NewMockMessageCenter() c := NewController(mc) diff --git a/heartbeatpb/heartbeat.pb.go b/heartbeatpb/heartbeat.pb.go index 94ff2f969d..02015a4434 100644 --- a/heartbeatpb/heartbeat.pb.go +++ b/heartbeatpb/heartbeat.pb.go @@ -1625,6 +1625,9 @@ type NodeHeartbeat struct { // currently applied on this node. Empty target means the drain target is clear. DispatcherDrainTargetNodeId string `protobuf:"bytes,3,opt,name=dispatcher_drain_target_node_id,json=dispatcherDrainTargetNodeId,proto3" json:"dispatcher_drain_target_node_id,omitempty"` DispatcherDrainTargetEpoch uint64 `protobuf:"varint,4,opt,name=dispatcher_drain_target_epoch,json=dispatcherDrainTargetEpoch,proto3" json:"dispatcher_drain_target_epoch,omitempty"` + // log_service_dispatcher_count is the number of dispatchers currently + // reading from this node's event store. + LogServiceDispatcherCount uint32 `protobuf:"varint,5,opt,name=log_service_dispatcher_count,json=logServiceDispatcherCount,proto3" json:"log_service_dispatcher_count,omitempty"` } func (m *NodeHeartbeat) Reset() { *m = NodeHeartbeat{} } @@ -1688,6 +1691,13 @@ func (m *NodeHeartbeat) GetDispatcherDrainTargetEpoch() uint64 { return 0 } +func (m *NodeHeartbeat) GetLogServiceDispatcherCount() uint32 { + if m != nil { + return m.LogServiceDispatcherCount + } + return 0 +} + // SetNodeLivenessRequest asks a node to transition its local liveness. type SetNodeLivenessRequest struct { Target NodeLiveness `protobuf:"varint,1,opt,name=target,proto3,enum=heartbeatpb.NodeLiveness" json:"target,omitempty"` @@ -4006,196 +4016,198 @@ func init() { func init() { proto.RegisterFile("heartbeatpb/heartbeat.proto", fileDescriptor_6d584080fdadb670) } var fileDescriptor_6d584080fdadb670 = []byte{ - // 3020 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x1a, 0x4d, 0x8f, 0x1c, 0x47, - 0xd5, 0xdd, 0xf3, 0xfd, 0x76, 0x67, 0xb7, 0x5d, 0xb6, 0xd7, 0x6b, 0x7b, 0xbd, 0xde, 0x74, 0x02, - 0xda, 0x4c, 0x82, 0x8d, 0x9d, 0x98, 0x8f, 0x10, 0x12, 0xc6, 0x33, 0x9b, 0x78, 0xe4, 0x9d, 0xdd, - 0x55, 0xcd, 0x26, 0x46, 0xe1, 0x30, 0xf4, 0x76, 0x97, 0x67, 0x3b, 0x3b, 0xd3, 0x35, 0xe9, 0xee, - 0xf1, 0xda, 0x96, 0x00, 0x45, 0x88, 0x1b, 0x07, 0xb8, 0x71, 0x20, 0x17, 0x4e, 0x9c, 0x10, 0x7f, - 0x00, 0xc1, 0x81, 0x03, 0x27, 0x14, 0x71, 0x40, 0x39, 0x41, 0x94, 0xdc, 0x11, 0x12, 0x12, 0x5c, - 0x51, 0x7d, 0x74, 0x77, 0xf5, 0x4c, 0xcf, 0x7e, 0xb0, 0xa3, 0x88, 0xd3, 0xf4, 0x7b, 0xf5, 0xde, - 0xab, 0x57, 0xaf, 0x5e, 0xbd, 0x7a, 0xef, 0xd5, 0xc0, 0xb5, 0x7d, 0x62, 0xf9, 0xe1, 0x1e, 0xb1, - 0xc2, 0xe1, 0xde, 0xad, 0xf8, 0xfb, 0xe6, 0xd0, 0xa7, 0x21, 0x45, 0x73, 0xca, 0xa0, 0xf9, 0x14, - 0x2a, 0xbb, 0xd6, 0x5e, 0x9f, 0x74, 0x86, 0x96, 0x87, 0x96, 0xa1, 0xc4, 0x81, 0x56, 0x73, 0x59, - 0x5b, 0xd3, 0xd6, 0x73, 0x38, 0x02, 0xd1, 0x55, 0x28, 0x77, 0x42, 0xcb, 0x0f, 0x1f, 0x90, 0xa7, - 0xcb, 0xfa, 0x9a, 0xb6, 0x3e, 0x8f, 0x63, 0x18, 0x2d, 0x41, 0x71, 0xc3, 0x73, 0xd8, 0x48, 0x8e, - 0x8f, 0x48, 0x08, 0xad, 0x02, 0x3c, 0x20, 0x4f, 0x83, 0xa1, 0x65, 0x33, 0x81, 0xf9, 0x35, 0x6d, - 0xbd, 0x8a, 0x15, 0x8c, 0xf9, 0x57, 0x1d, 0x8c, 0xfb, 0x4c, 0x95, 0x7b, 0xc4, 0x0a, 0x31, 0xf9, - 0x60, 0x44, 0x82, 0x10, 0x7d, 0x1b, 0xe6, 0xed, 0x7d, 0xcb, 0xeb, 0x91, 0x47, 0x84, 0x38, 0x52, - 0x8f, 0xb9, 0x3b, 0x57, 0x6e, 0x2a, 0x3a, 0xdf, 0x6c, 0x28, 0x04, 0x38, 0x45, 0x8e, 0x5e, 0x85, - 0xca, 0xa1, 0x15, 0x12, 0x7f, 0x60, 0xf9, 0x07, 0x5c, 0xd1, 0xb9, 0x3b, 0x4b, 0x29, 0xde, 0x87, - 0xd1, 0x28, 0x4e, 0x08, 0xd1, 0xeb, 0x50, 0xf5, 0x89, 0x43, 0xe3, 0x31, 0xbe, 0x90, 0xe9, 0x9c, - 0x69, 0x62, 0xf4, 0x0d, 0x28, 0x07, 0xa1, 0x15, 0x8e, 0x02, 0x12, 0x2c, 0xe7, 0xd7, 0x72, 0xeb, - 0x73, 0x77, 0x56, 0x52, 0x8c, 0xb1, 0x7d, 0x3b, 0x9c, 0x0a, 0xc7, 0xd4, 0x68, 0x1d, 0x16, 0x6d, - 0x3a, 0x18, 0x92, 0x3e, 0x09, 0x89, 0x18, 0x5c, 0x2e, 0xac, 0x69, 0xeb, 0x65, 0x3c, 0x8e, 0x46, - 0x2f, 0x41, 0x8e, 0xf8, 0xfe, 0x72, 0x31, 0xc3, 0x1a, 0x78, 0xe4, 0x79, 0xae, 0xd7, 0xdb, 0xf0, - 0x7d, 0xea, 0x63, 0x46, 0x65, 0xfe, 0x44, 0x83, 0x4a, 0xa2, 0x9e, 0xc9, 0x2c, 0x4a, 0xec, 0x83, - 0x21, 0x75, 0xbd, 0x70, 0x37, 0xe0, 0x16, 0xcd, 0xe3, 0x14, 0x8e, 0x6d, 0x95, 0x4f, 0x02, 0xda, - 0x7f, 0x4c, 0x9c, 0xdd, 0x80, 0xdb, 0x2d, 0x8f, 0x15, 0x0c, 0x32, 0x20, 0x17, 0x90, 0x0f, 0xb8, - 0x59, 0xf2, 0x98, 0x7d, 0x32, 0xa9, 0x7d, 0x2b, 0x08, 0x3b, 0x4f, 0x3d, 0x9b, 0xf3, 0xe4, 0x85, - 0x54, 0x15, 0x67, 0xfe, 0x00, 0x8c, 0xa6, 0x1b, 0x0c, 0xad, 0xd0, 0xde, 0x27, 0x7e, 0xdd, 0x0e, - 0x5d, 0xea, 0xa1, 0x97, 0xa0, 0x68, 0xf1, 0x2f, 0xae, 0xc7, 0xc2, 0x9d, 0x0b, 0xa9, 0xb5, 0x08, - 0x22, 0x2c, 0x49, 0x98, 0xd7, 0x35, 0xe8, 0x60, 0xe0, 0x86, 0xb1, 0x52, 0x31, 0x8c, 0xd6, 0x60, - 0xae, 0x15, 0xb0, 0xa9, 0x76, 0xd8, 0x1a, 0xb8, 0x6a, 0x65, 0xac, 0xa2, 0xcc, 0x06, 0xe4, 0xea, - 0x8d, 0x07, 0x29, 0x21, 0xda, 0xd1, 0x42, 0xf4, 0x49, 0x21, 0x18, 0x50, 0xab, 0xe7, 0x51, 0x9f, - 0x38, 0xf7, 0xfa, 0xd4, 0x3e, 0x90, 0xdb, 0x71, 0x36, 0x99, 0x3f, 0xd6, 0xe1, 0x52, 0xcb, 0x7b, - 0xd4, 0x1f, 0x11, 0x66, 0xa8, 0xc4, 0x44, 0x01, 0xfa, 0x0e, 0x54, 0xe3, 0x81, 0xdd, 0xa7, 0x43, - 0x22, 0x8d, 0x74, 0x35, 0x65, 0xa4, 0x14, 0x05, 0x4e, 0x33, 0xa0, 0x37, 0xa1, 0x9a, 0x08, 0x6c, - 0x35, 0x99, 0xdd, 0x72, 0x13, 0x2e, 0xa3, 0x52, 0xe0, 0x34, 0x3d, 0x3f, 0xe9, 0xf6, 0x3e, 0x19, - 0x58, 0xad, 0x26, 0x37, 0x6a, 0x0e, 0xc7, 0x30, 0x7a, 0x00, 0x17, 0xc8, 0x13, 0xbb, 0x3f, 0x72, - 0x88, 0xc2, 0xe3, 0xf0, 0xbd, 0x3f, 0x72, 0x8a, 0x2c, 0x2e, 0xf3, 0x17, 0xba, 0xea, 0x1e, 0xd2, - 0xb0, 0xdf, 0x85, 0x4b, 0x6e, 0x96, 0x65, 0x64, 0x1c, 0x30, 0xb3, 0x0d, 0xa1, 0x52, 0xe2, 0x6c, - 0x01, 0xe8, 0x6e, 0xec, 0x78, 0x22, 0x2c, 0x5c, 0x9f, 0xa2, 0xee, 0x98, 0x0b, 0x9a, 0x90, 0xb3, - 0xec, 0x28, 0x20, 0x18, 0x69, 0x67, 0x6d, 0x3c, 0xc0, 0x6c, 0x10, 0x6d, 0x03, 0x72, 0x27, 0x7c, - 0x44, 0x5a, 0xe5, 0x46, 0x5a, 0xe3, 0x09, 0x32, 0x9c, 0xc1, 0x6a, 0x7e, 0xaa, 0xc1, 0x79, 0x25, - 0x32, 0x06, 0x43, 0xea, 0x05, 0xe4, 0xac, 0xa1, 0xb1, 0x0d, 0xc8, 0x19, 0x33, 0x37, 0x89, 0xdc, - 0x63, 0x9a, 0x31, 0x22, 0x1d, 0x27, 0x19, 0x11, 0x82, 0xfc, 0x80, 0x3a, 0x44, 0xfa, 0x08, 0xff, - 0x46, 0x2f, 0x82, 0x31, 0xb0, 0x5c, 0x2f, 0xb4, 0x5c, 0x8f, 0xf8, 0x5d, 0x32, 0xa4, 0xf6, 0xbe, - 0x0c, 0x0c, 0x8b, 0x09, 0x7e, 0x83, 0xa1, 0xcd, 0x27, 0x70, 0xa1, 0xa1, 0x44, 0xa0, 0x36, 0x09, - 0x02, 0xab, 0x77, 0xe6, 0x35, 0x8e, 0xc7, 0x3a, 0x7d, 0x32, 0xd6, 0x99, 0xbf, 0xd7, 0x60, 0x11, - 0x13, 0x87, 0xb6, 0x49, 0x68, 0xcd, 0x68, 0xda, 0xe3, 0xc2, 0xe7, 0xb8, 0x5a, 0xb9, 0x8c, 0x10, - 0x7c, 0x0a, 0xdb, 0xfd, 0x10, 0xae, 0xb3, 0x05, 0xe0, 0x78, 0x82, 0x1d, 0x9f, 0xf6, 0x7c, 0x12, - 0x04, 0x5f, 0xcc, 0x72, 0xcc, 0x5f, 0x6b, 0xb0, 0x92, 0x56, 0xe0, 0x2d, 0xea, 0x1f, 0x5a, 0xbe, - 0xf3, 0x05, 0x99, 0x33, 0xcb, 0x54, 0xb9, 0x6c, 0x53, 0xfd, 0x4b, 0x53, 0x83, 0x4c, 0x83, 0x7a, - 0x8f, 0xdc, 0x1e, 0xaa, 0x41, 0x3e, 0x18, 0x5a, 0x9e, 0x54, 0x6b, 0x29, 0xfb, 0xb2, 0xc6, 0x9c, - 0x86, 0xa5, 0x44, 0x01, 0x4b, 0x74, 0x62, 0x45, 0x22, 0x90, 0x2d, 0xd2, 0x51, 0x82, 0x9c, 0x0c, - 0x11, 0x47, 0x44, 0xc1, 0x14, 0x39, 0x8b, 0xb3, 0x41, 0x14, 0x67, 0xf3, 0x22, 0xce, 0x46, 0x70, - 0x7c, 0xb6, 0x0a, 0xca, 0xd9, 0xaa, 0x81, 0x11, 0x1c, 0xb8, 0xc3, 0x66, 0x7b, 0xb3, 0x1e, 0x74, - 0xa4, 0x46, 0x45, 0x7e, 0xb7, 0x4c, 0xe0, 0xcd, 0x3f, 0xe8, 0x70, 0x85, 0x05, 0x6d, 0x67, 0xd4, - 0x57, 0x62, 0xee, 0x8c, 0x52, 0xac, 0xbb, 0x50, 0xb4, 0xb9, 0x1d, 0x8f, 0x09, 0xa4, 0xc2, 0xd8, - 0x58, 0x12, 0xa3, 0x06, 0x2c, 0x04, 0x52, 0x25, 0x11, 0x62, 0xb9, 0xc1, 0x16, 0xee, 0x5c, 0x4b, - 0xb1, 0x77, 0x52, 0x24, 0x78, 0x8c, 0x85, 0xa9, 0x4e, 0x87, 0xc4, 0xb7, 0x42, 0xea, 0xf3, 0xeb, - 0x31, 0xcf, 0x45, 0xa4, 0x55, 0xdf, 0x56, 0x08, 0x70, 0x8a, 0x3c, 0xd3, 0x71, 0x0a, 0xd9, 0x8e, - 0xf3, 0x2b, 0x1d, 0x96, 0xda, 0xc4, 0xef, 0xcd, 0xde, 0x7e, 0x6f, 0x42, 0xd5, 0x39, 0xe5, 0x0d, - 0x9d, 0xa2, 0x47, 0x2d, 0x40, 0x03, 0xa6, 0x99, 0xd3, 0x3c, 0x95, 0xfb, 0x65, 0x30, 0xc5, 0x8e, - 0x96, 0x3f, 0x26, 0x88, 0x4f, 0x31, 0xd2, 0x0e, 0x5c, 0x68, 0xc7, 0xa8, 0xfb, 0xd1, 0xc4, 0xe8, - 0x9b, 0x4a, 0x42, 0xac, 0x65, 0xdc, 0x2f, 0x09, 0xcf, 0x78, 0x46, 0x6c, 0x7e, 0xa2, 0x41, 0xb5, - 0xe9, 0x5b, 0xae, 0x17, 0x85, 0x34, 0xf4, 0x02, 0x2c, 0x84, 0x96, 0xdf, 0x23, 0x61, 0xd7, 0xa3, - 0x0e, 0xe9, 0xba, 0x0e, 0xb7, 0x77, 0x05, 0xcf, 0x0b, 0xec, 0x16, 0x75, 0x48, 0xcb, 0x41, 0xcf, - 0x81, 0x84, 0xa5, 0xc2, 0xe2, 0xac, 0xce, 0x09, 0x1c, 0x57, 0x16, 0x7d, 0x0d, 0x2e, 0x4b, 0x92, - 0xc4, 0x9c, 0x5d, 0x9b, 0x8e, 0x64, 0xf2, 0x58, 0xc5, 0x97, 0xc4, 0xb0, 0xea, 0xc1, 0x23, 0x2f, - 0x44, 0x6f, 0xc1, 0x9a, 0xe4, 0x63, 0x89, 0x85, 0xdb, 0xdb, 0x0f, 0xbb, 0x0e, 0xd3, 0xb0, 0x3b, - 0xa0, 0x8f, 0x89, 0x14, 0x20, 0x8a, 0x9b, 0x15, 0x41, 0xd7, 0x92, 0x64, 0x7c, 0x1d, 0x6d, 0xfa, - 0x98, 0x70, 0x39, 0xe6, 0x6f, 0x72, 0x60, 0x8c, 0xaf, 0xfc, 0xac, 0xbe, 0x74, 0x1d, 0x80, 0x7d, - 0x75, 0x99, 0xfd, 0x08, 0x5f, 0x74, 0x05, 0x57, 0x18, 0x86, 0x89, 0x27, 0xe8, 0x36, 0x14, 0xc4, - 0x48, 0xd6, 0x51, 0x6b, 0xd0, 0xc1, 0x90, 0x7a, 0xc4, 0x0b, 0x39, 0x2d, 0x16, 0x94, 0xe8, 0x79, - 0xa8, 0x26, 0xd7, 0x52, 0x37, 0x8c, 0x13, 0xfb, 0xd4, 0x5d, 0x25, 0xab, 0x91, 0x42, 0x86, 0xe3, - 0x4e, 0x54, 0x23, 0xe8, 0x4b, 0xb0, 0xb0, 0x47, 0x69, 0x18, 0x84, 0xbe, 0x35, 0xec, 0x3a, 0xd4, - 0x23, 0x32, 0x6c, 0x55, 0x63, 0x6c, 0x93, 0x7a, 0x64, 0xa2, 0xa0, 0x28, 0x4d, 0x16, 0x14, 0xa8, - 0x0e, 0x0b, 0xc2, 0xf4, 0x43, 0xe9, 0x1d, 0xcb, 0x65, 0x6e, 0xaf, 0x74, 0x7e, 0x9c, 0xf2, 0x1f, - 0x5c, 0x75, 0x52, 0xee, 0x94, 0xe5, 0xdd, 0x95, 0x6c, 0xef, 0xfe, 0x87, 0x06, 0x55, 0xe6, 0x5e, - 0x89, 0x63, 0xdf, 0x85, 0x72, 0xdf, 0x7d, 0x4c, 0x3c, 0x36, 0xb3, 0x96, 0x11, 0x7a, 0x18, 0xf5, - 0xa6, 0x24, 0xc0, 0x31, 0x29, 0xdb, 0x25, 0xee, 0xbb, 0xaa, 0x6b, 0x56, 0x18, 0x46, 0x38, 0x66, - 0x13, 0x6e, 0x28, 0x1e, 0x29, 0x16, 0x38, 0xe6, 0xf2, 0x39, 0xbe, 0xb3, 0xd7, 0x12, 0x32, 0xbe, - 0xc6, 0x5d, 0xf5, 0x04, 0xd4, 0xe1, 0xfa, 0x34, 0x29, 0x6a, 0x32, 0x71, 0x35, 0x53, 0x86, 0x58, - 0xf0, 0xfb, 0xb0, 0xd4, 0x11, 0xf2, 0xe2, 0x45, 0xc8, 0x90, 0x77, 0x1b, 0x8a, 0x42, 0xd6, 0xf1, - 0xcb, 0x96, 0x84, 0xc7, 0x2c, 0xda, 0x1c, 0xc0, 0xe5, 0x89, 0xb9, 0x64, 0x9e, 0xfb, 0x0a, 0x94, - 0xac, 0xe1, 0xb0, 0xef, 0x12, 0xe7, 0xf8, 0xd9, 0x22, 0xca, 0xe3, 0xa6, 0x7b, 0x1f, 0x6e, 0x74, - 0xd4, 0xa3, 0xad, 0xac, 0x3d, 0x5a, 0xe3, 0xac, 0x02, 0x8d, 0xf9, 0x75, 0xb8, 0xd6, 0xa0, 0xd4, - 0x77, 0x5c, 0x8f, 0x5d, 0x3c, 0xf7, 0x22, 0x2f, 0x8f, 0xe6, 0x59, 0x86, 0xd2, 0x63, 0xe2, 0x07, - 0x51, 0x09, 0x9c, 0xc3, 0x11, 0xc8, 0x2a, 0xa2, 0x95, 0x6c, 0x4e, 0x69, 0x99, 0xff, 0x3d, 0xb0, - 0xa2, 0x57, 0x61, 0x29, 0x3e, 0x3a, 0x21, 0xb5, 0x69, 0xbf, 0x1b, 0x29, 0xa1, 0xf3, 0xd8, 0x75, - 0x31, 0x3a, 0x26, 0x7c, 0xf0, 0x5d, 0x31, 0xf6, 0xff, 0xe3, 0x9a, 0xff, 0xd6, 0xe0, 0x62, 0xdd, - 0x71, 0x92, 0x05, 0x46, 0xd6, 0x7c, 0x11, 0x74, 0xb9, 0x53, 0x47, 0x86, 0x4d, 0xdd, 0x75, 0xd0, - 0x52, 0x2a, 0x71, 0x99, 0x8f, 0x33, 0x93, 0x89, 0x90, 0x97, 0x95, 0x9e, 0xd7, 0xe0, 0xbc, 0x1b, - 0x74, 0x3d, 0x72, 0xd8, 0x4d, 0x02, 0x30, 0xd7, 0xbb, 0x8c, 0x17, 0xdd, 0x60, 0x8b, 0x1c, 0x26, - 0xd3, 0xa1, 0x1b, 0x30, 0x77, 0x20, 0xdb, 0x5c, 0xcc, 0x42, 0x05, 0xd1, 0xf9, 0x8a, 0x50, 0x2d, - 0x27, 0x33, 0x08, 0x15, 0xb3, 0x83, 0xd0, 0x1f, 0x35, 0xb8, 0x8c, 0x09, 0xbb, 0x6a, 0xce, 0xb4, - 0xf6, 0x65, 0x28, 0xd9, 0x56, 0x60, 0x5b, 0x0e, 0x91, 0x0d, 0x89, 0x08, 0x64, 0x23, 0x3e, 0x97, - 0xef, 0xc8, 0x1e, 0x4a, 0x04, 0x8e, 0x2f, 0x23, 0x7f, 0xa2, 0x65, 0x4c, 0xc9, 0x14, 0xfe, 0x9c, - 0x83, 0xab, 0xc9, 0x02, 0x26, 0xce, 0xc4, 0x19, 0xaf, 0xc1, 0x69, 0x3b, 0x7b, 0x85, 0x9f, 0x17, - 0x5f, 0xd9, 0xd4, 0x38, 0x7b, 0xb7, 0xe1, 0xb9, 0x90, 0xa5, 0xfa, 0xdd, 0xd0, 0x77, 0x7b, 0x3d, - 0xa6, 0xfe, 0x63, 0xe2, 0xa5, 0x52, 0x03, 0xf7, 0x04, 0x8d, 0x8d, 0xeb, 0x5c, 0xc6, 0xae, 0x10, - 0xb1, 0xc1, 0x24, 0xa8, 0x2d, 0x8e, 0x6c, 0xa7, 0x29, 0x64, 0x3b, 0x8d, 0xc5, 0xd2, 0x0c, 0x55, - 0x21, 0x9f, 0x38, 0x74, 0x4c, 0x9f, 0xe2, 0x71, 0xfa, 0xac, 0xa8, 0xfa, 0xb0, 0x12, 0x2d, 0xa5, - 0xce, 0xd8, 0x86, 0x96, 0x4e, 0xb4, 0xa1, 0xe5, 0xec, 0x0d, 0xfd, 0x4b, 0x0e, 0xae, 0x65, 0x6e, - 0xe8, 0x6c, 0x9a, 0x15, 0x77, 0xa1, 0xc0, 0xca, 0xaf, 0x28, 0x39, 0x4e, 0x77, 0x51, 0xe2, 0xd9, - 0x92, 0x62, 0x4d, 0x50, 0x47, 0x89, 0x49, 0xee, 0x24, 0x6d, 0xd2, 0x93, 0xa5, 0x3a, 0x2f, 0x03, - 0xe2, 0x1b, 0x91, 0xa6, 0x14, 0x5e, 0x6e, 0xb0, 0x11, 0xb5, 0x8b, 0x81, 0x9a, 0x50, 0x89, 0x0a, - 0x0e, 0x56, 0x9d, 0x31, 0xd5, 0xbf, 0x9c, 0x59, 0xdf, 0x4c, 0x54, 0x15, 0x38, 0x61, 0xcc, 0xdc, - 0x86, 0x52, 0xe6, 0x36, 0xa0, 0x4d, 0x58, 0xe4, 0x69, 0x7d, 0x37, 0x99, 0xb6, 0xcc, 0xa7, 0x7d, - 0x3e, 0x7d, 0x31, 0x64, 0x56, 0x32, 0x78, 0x81, 0xf3, 0x46, 0x05, 0x53, 0x60, 0xfe, 0x4d, 0x87, - 0xd5, 0x64, 0x53, 0x77, 0x68, 0x10, 0xce, 0xfa, 0xa4, 0x9e, 0xe8, 0xd8, 0xe9, 0x67, 0x3c, 0x76, - 0xb7, 0xa1, 0x24, 0x4a, 0x69, 0x76, 0xea, 0x99, 0x31, 0x2e, 0x4f, 0xec, 0xc1, 0xc0, 0x6a, 0x79, - 0x8f, 0x28, 0x8e, 0xe8, 0xd0, 0x6b, 0x30, 0xcf, 0xb7, 0x39, 0xe2, 0xcb, 0x1f, 0xcd, 0x37, 0xc7, - 0x88, 0x3b, 0x92, 0xf7, 0x14, 0x61, 0xf0, 0x23, 0x1d, 0x6e, 0x4c, 0x35, 0xf0, 0x6c, 0x4e, 0xce, - 0x17, 0x62, 0xe1, 0x53, 0x9d, 0xb3, 0x53, 0x75, 0x05, 0x21, 0xb1, 0x72, 0xaa, 0x15, 0xad, 0x8d, - 0xb5, 0xa2, 0x57, 0x23, 0xca, 0x2d, 0x6b, 0x10, 0x55, 0x3e, 0x0a, 0x06, 0xdd, 0x84, 0x22, 0x8f, - 0x0e, 0x91, 0x0b, 0x64, 0x74, 0x79, 0xf8, 0x4e, 0x4a, 0x2a, 0xb3, 0x21, 0xdf, 0xc1, 0xf8, 0xc4, - 0xd3, 0xdf, 0xc1, 0x56, 0x24, 0x99, 0x32, 0x6b, 0x82, 0x30, 0x7f, 0xa7, 0x03, 0x9a, 0x0c, 0x4e, - 0xec, 0x9e, 0x9e, 0xb2, 0x8f, 0x29, 0x9b, 0xeb, 0xf2, 0x9d, 0x2d, 0x5a, 0xb2, 0x3e, 0xb6, 0xe4, - 0xa8, 0x6d, 0x95, 0x3b, 0x41, 0xdb, 0xea, 0x2d, 0x30, 0xec, 0xa8, 0xbe, 0xeb, 0x06, 0x49, 0x43, - 0xfa, 0x98, 0x22, 0x70, 0xd1, 0x56, 0xe1, 0x51, 0x30, 0x19, 0x23, 0x0b, 0x19, 0x31, 0xf2, 0x15, - 0x98, 0xdb, 0xeb, 0x53, 0xfb, 0x40, 0x96, 0xa1, 0xe2, 0x96, 0x42, 0xe9, 0xb3, 0xc3, 0xc5, 0xc3, - 0x5e, 0xd4, 0xe4, 0x26, 0x71, 0xeb, 0xa1, 0x94, 0xb4, 0x1e, 0xcc, 0x5f, 0x6a, 0xb0, 0x94, 0x1c, - 0x8f, 0x46, 0x9f, 0x06, 0x64, 0x46, 0x71, 0x47, 0xc9, 0x72, 0xf4, 0x74, 0x96, 0x73, 0x8a, 0x66, - 0xe2, 0x47, 0x1a, 0x5c, 0x9e, 0x50, 0x6f, 0x36, 0xa7, 0x76, 0x19, 0x4a, 0xc1, 0xc8, 0xb6, 0x59, - 0x61, 0x29, 0xf5, 0x93, 0xe0, 0x69, 0xf4, 0xfb, 0xa9, 0x06, 0x46, 0xf2, 0x26, 0x22, 0x1c, 0x7b, - 0x06, 0x4f, 0x4a, 0x57, 0xa1, 0x2c, 0xdd, 0x5f, 0x5c, 0xc7, 0x39, 0x1c, 0xc3, 0x47, 0xbd, 0x16, - 0x99, 0xdf, 0x83, 0x02, 0xa7, 0x3b, 0xe6, 0x59, 0x79, 0x9a, 0xbb, 0xaf, 0x40, 0xa5, 0x33, 0xec, - 0xbb, 0x3c, 0x10, 0xc9, 0xd4, 0x34, 0x41, 0x98, 0x1f, 0xea, 0x70, 0x01, 0xd3, 0x51, 0x48, 0xb8, - 0xa8, 0xba, 0x33, 0x70, 0x03, 0x5e, 0xb1, 0xd4, 0xc0, 0xe8, 0xd0, 0x91, 0x6f, 0x13, 0x25, 0x3a, - 0x88, 0x3a, 0x6e, 0x02, 0x8f, 0xd6, 0x61, 0x51, 0xe0, 0xc6, 0x8f, 0xf4, 0x38, 0x9a, 0x49, 0x15, - 0xd5, 0x88, 0x22, 0x55, 0x14, 0x3e, 0x13, 0x78, 0x26, 0x55, 0xe0, 0x12, 0xa9, 0x79, 0x21, 0x75, - 0x0c, 0x8d, 0xde, 0x80, 0xa2, 0x6c, 0x85, 0x16, 0xf8, 0x9e, 0xa4, 0x53, 0x85, 0x8c, 0xd5, 0x45, - 0x6f, 0x53, 0xe2, 0xd7, 0xf4, 0x60, 0x21, 0xb2, 0x96, 0x70, 0xad, 0x23, 0x2c, 0xbd, 0x06, 0x73, - 0xdb, 0x7d, 0x67, 0xcc, 0xd8, 0x2a, 0x8a, 0x51, 0x6c, 0x91, 0xc3, 0xb1, 0xdd, 0x54, 0x51, 0xe6, - 0x7f, 0x72, 0x50, 0x10, 0x87, 0x77, 0x05, 0x2a, 0xad, 0x80, 0xbf, 0x58, 0xc9, 0x22, 0xbd, 0x8c, - 0x13, 0x04, 0xd3, 0x82, 0x7f, 0x26, 0x3d, 0x73, 0x09, 0xa2, 0x37, 0x61, 0x4e, 0x7c, 0x46, 0xa1, - 0x79, 0xb2, 0x81, 0x3c, 0xee, 0xc0, 0x58, 0xe5, 0x40, 0x0f, 0xe0, 0xfc, 0x16, 0x21, 0x4e, 0xd3, - 0xa7, 0xc3, 0x61, 0x44, 0x21, 0xd3, 0xf4, 0x63, 0xc4, 0x4c, 0xf2, 0xa1, 0xd7, 0x61, 0x91, 0x21, - 0xeb, 0x8e, 0x13, 0x8b, 0x12, 0x2d, 0x2d, 0x34, 0x19, 0x5b, 0xf1, 0x38, 0x29, 0x6a, 0xc0, 0xc2, - 0x3b, 0x43, 0xc7, 0x0a, 0x89, 0x34, 0x61, 0x94, 0xf0, 0x5d, 0xcb, 0x4a, 0x1a, 0xe4, 0x06, 0xe1, - 0x31, 0x96, 0xf1, 0xc7, 0xe2, 0xd2, 0xc4, 0x63, 0x31, 0xfa, 0x0a, 0xef, 0xe1, 0xf5, 0x08, 0x4f, - 0xc4, 0x17, 0xc6, 0x52, 0x92, 0xe8, 0xd1, 0xb0, 0x27, 0xfa, 0x77, 0x3d, 0x82, 0x76, 0xe1, 0x62, - 0x86, 0xe3, 0x04, 0xcb, 0x15, 0xae, 0xdb, 0xda, 0x71, 0x1e, 0x86, 0x33, 0xb9, 0xcd, 0x1f, 0xc1, - 0xc5, 0xf8, 0x86, 0x51, 0xdf, 0xc1, 0x4f, 0x71, 0xb3, 0xad, 0x47, 0xbd, 0x48, 0x7d, 0xea, 0xf5, - 0x20, 0x5b, 0x90, 0x19, 0x2f, 0x8b, 0xe6, 0x3f, 0x35, 0x76, 0xaa, 0x52, 0xff, 0xa3, 0x38, 0xcd, - 0xe4, 0x59, 0xd7, 0xa1, 0x3e, 0x8b, 0xeb, 0x30, 0xab, 0x55, 0x70, 0x1b, 0x2e, 0x89, 0x9c, 0x2b, - 0x70, 0x9f, 0x91, 0xee, 0x90, 0xf8, 0xdd, 0x80, 0xd8, 0xd4, 0x13, 0xe5, 0xa4, 0x8e, 0x11, 0x1f, - 0xec, 0xb8, 0xcf, 0xc8, 0x0e, 0xf1, 0x3b, 0x7c, 0x24, 0xeb, 0xc1, 0xc7, 0xfc, 0xad, 0x06, 0x48, - 0x7d, 0x28, 0x9e, 0xcd, 0x45, 0xf8, 0x36, 0x54, 0xf7, 0x12, 0xa1, 0xf1, 0x03, 0xf0, 0x73, 0xd9, - 0xd9, 0x84, 0x3a, 0x7f, 0x9a, 0x2f, 0x73, 0x97, 0x1c, 0x98, 0x57, 0xd3, 0x3f, 0x46, 0x13, 0xba, - 0x71, 0x00, 0xe6, 0xdf, 0x0c, 0xe7, 0x51, 0x27, 0x8a, 0xb4, 0xfc, 0x9b, 0xe1, 0xec, 0x48, 0x56, - 0x05, 0xf3, 0x6f, 0x16, 0x44, 0x06, 0xe2, 0x39, 0x51, 0x86, 0xcf, 0x08, 0x34, 0x5f, 0x85, 0xf9, - 0xf1, 0x47, 0x8c, 0x7d, 0xb7, 0xb7, 0x2f, 0xff, 0x88, 0xc1, 0xbf, 0x91, 0x01, 0xb9, 0x3e, 0x3d, - 0x94, 0xe1, 0x87, 0x7d, 0x32, 0xdd, 0x54, 0xb3, 0x9c, 0x8c, 0x8b, 0x6b, 0x9b, 0x04, 0x7b, 0xfe, - 0xcd, 0x2e, 0xad, 0xa8, 0x66, 0x96, 0xaa, 0xc5, 0xb0, 0xf9, 0x7d, 0xb8, 0xb1, 0x49, 0x7b, 0x4a, - 0x13, 0x2f, 0x79, 0x23, 0x9d, 0xcd, 0x06, 0x9a, 0x1f, 0x6a, 0xb0, 0x36, 0x7d, 0x8a, 0xd9, 0x64, - 0x23, 0xc7, 0x3d, 0x00, 0xf7, 0x99, 0x2d, 0x89, 0x7d, 0x10, 0x8c, 0x06, 0x6d, 0x12, 0x5a, 0xe8, - 0xab, 0xd1, 0xd9, 0xce, 0xca, 0x2d, 0x22, 0xca, 0xd4, 0x19, 0xaf, 0x81, 0x61, 0xab, 0xf8, 0x0e, - 0xf9, 0x40, 0xce, 0x33, 0x81, 0x37, 0x7f, 0xae, 0xc1, 0x25, 0xe5, 0x2f, 0x09, 0x24, 0x8c, 0x24, - 0xa2, 0x8b, 0x50, 0x10, 0xef, 0x2f, 0x62, 0x13, 0x05, 0xc0, 0x3c, 0xe7, 0x09, 0xf5, 0xef, 0xb3, - 0xcd, 0x95, 0xd7, 0x8f, 0x04, 0xd1, 0x12, 0x14, 0x9f, 0x50, 0x7f, 0x93, 0x1e, 0xca, 0x73, 0x2b, - 0x21, 0x91, 0x7d, 0x0d, 0x38, 0x47, 0x5e, 0xb6, 0x89, 0x04, 0xc8, 0x38, 0x82, 0xd1, 0x80, 0x71, - 0x88, 0xc4, 0x57, 0x42, 0x2c, 0x15, 0x5c, 0xcb, 0xd4, 0xa9, 0x6e, 0x1f, 0xcc, 0x6a, 0x17, 0x2e, - 0x42, 0x41, 0xed, 0x31, 0x0b, 0x20, 0xf3, 0x7f, 0x17, 0xf2, 0xef, 0x59, 0xf9, 0xf8, 0xef, 0x59, - 0xe6, 0xdf, 0x35, 0x30, 0x33, 0xf5, 0x13, 0xf7, 0xcf, 0x8c, 0x82, 0xc9, 0x19, 0x34, 0x44, 0x6f, - 0x40, 0x39, 0xda, 0x69, 0x6e, 0xdb, 0xf1, 0x3f, 0xf7, 0x64, 0x6a, 0x8f, 0x63, 0x9e, 0xda, 0xf5, - 0x28, 0x79, 0x42, 0x15, 0x28, 0x3c, 0xf4, 0xdd, 0x90, 0x18, 0xe7, 0x50, 0x19, 0xf2, 0x3b, 0x56, - 0x10, 0x18, 0x5a, 0x6d, 0x5d, 0xe4, 0x46, 0xca, 0xdb, 0x31, 0x40, 0xb1, 0xe1, 0x13, 0x8b, 0xd3, - 0x01, 0x14, 0x45, 0x53, 0xd5, 0xd0, 0x6a, 0x6d, 0x98, 0x57, 0x9f, 0x8c, 0x99, 0xb8, 0xed, 0x6e, - 0xdd, 0x71, 0x8c, 0x73, 0x68, 0x1e, 0xca, 0xdb, 0xdd, 0x88, 0x90, 0x31, 0x6d, 0x77, 0xdb, 0xec, - 0x5b, 0x47, 0x73, 0x50, 0xda, 0xee, 0xf2, 0x6c, 0xd4, 0xc8, 0x09, 0x80, 0xb7, 0x58, 0x8c, 0x7c, - 0xed, 0x2e, 0xcc, 0xab, 0x2f, 0x14, 0x4c, 0x5c, 0x7d, 0xb3, 0xf5, 0xee, 0x86, 0x10, 0xd7, 0xc4, - 0xf5, 0xd6, 0x56, 0x6b, 0xeb, 0x6d, 0x43, 0x63, 0x50, 0x67, 0x77, 0x7b, 0x67, 0x87, 0x41, 0x7a, - 0xed, 0x35, 0x80, 0xe4, 0x32, 0x67, 0xeb, 0xd8, 0xda, 0xde, 0x62, 0x3c, 0x73, 0x50, 0x7a, 0x58, - 0x6f, 0xed, 0x0a, 0x16, 0x06, 0x60, 0x01, 0xe8, 0x8c, 0xa6, 0xc9, 0x68, 0x72, 0xb5, 0x97, 0xc7, - 0x52, 0x7c, 0x54, 0x82, 0x5c, 0xbd, 0xdf, 0x37, 0xce, 0xa1, 0x22, 0xe8, 0xcd, 0x7b, 0x42, 0xf5, - 0x2d, 0xea, 0x0f, 0xac, 0xbe, 0xa1, 0xd7, 0xde, 0x86, 0x2b, 0x53, 0x53, 0x4b, 0xae, 0x6d, 0xb3, - 0xdd, 0xda, 0x15, 0x33, 0xe3, 0x8d, 0xcd, 0x8d, 0x7a, 0x67, 0xc3, 0xd0, 0x10, 0x82, 0x05, 0x09, - 0x74, 0x3b, 0x8d, 0xfb, 0x1b, 0xed, 0xba, 0xa1, 0xd7, 0x9e, 0xc1, 0x42, 0xfa, 0xbe, 0xe4, 0xfa, - 0x51, 0xff, 0xc0, 0xf5, 0x7a, 0x82, 0xbf, 0x13, 0xf2, 0x74, 0x4b, 0x68, 0x2e, 0xec, 0xe8, 0x18, - 0x3a, 0x32, 0x60, 0xbe, 0xe5, 0xb9, 0xa1, 0x6b, 0xf5, 0xdd, 0x67, 0x8c, 0x36, 0x87, 0xaa, 0x50, - 0xd9, 0xf1, 0xc9, 0xd0, 0xf2, 0x19, 0x98, 0x47, 0x0b, 0x00, 0xdc, 0x9c, 0x98, 0x58, 0xce, 0x53, - 0xa3, 0xc0, 0x18, 0x1e, 0x5a, 0x6e, 0xe8, 0x7a, 0x3d, 0x61, 0xe5, 0x62, 0xed, 0x5b, 0x50, 0x4d, - 0xc5, 0x15, 0x74, 0x1e, 0xaa, 0xef, 0x6c, 0xb5, 0xb6, 0x5a, 0xbb, 0xad, 0xfa, 0x66, 0xeb, 0xbd, - 0x8d, 0xa6, 0x30, 0x77, 0xbb, 0xd5, 0x69, 0xd7, 0x77, 0x1b, 0xf7, 0x0d, 0x8d, 0xad, 0x4c, 0x7c, - 0xea, 0xf7, 0xde, 0xf8, 0xd3, 0x67, 0xab, 0xda, 0xc7, 0x9f, 0xad, 0x6a, 0x9f, 0x7e, 0xb6, 0xaa, - 0xfd, 0xec, 0xf3, 0xd5, 0x73, 0x1f, 0x7f, 0xbe, 0x7a, 0xee, 0x93, 0xcf, 0x57, 0xcf, 0xbd, 0xf7, - 0x42, 0xcf, 0x0d, 0xf7, 0x47, 0x7b, 0x37, 0x6d, 0x3a, 0xb8, 0x35, 0x74, 0xbd, 0x9e, 0x6d, 0x0d, - 0x6f, 0x85, 0xae, 0xed, 0xd8, 0xb7, 0x14, 0xd7, 0xdc, 0x2b, 0xf2, 0x37, 0x94, 0x57, 0xfe, 0x1b, - 0x00, 0x00, 0xff, 0xff, 0x50, 0xf3, 0xe4, 0x7c, 0x66, 0x2b, 0x00, 0x00, + // 3045 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x1a, 0x4d, 0x73, 0x1c, 0x57, + 0xd1, 0x33, 0xfb, 0xdd, 0xd2, 0x4a, 0xe3, 0x67, 0x5b, 0x96, 0x6d, 0x59, 0x56, 0x26, 0x81, 0x52, + 0x36, 0xc1, 0xc6, 0x4e, 0xcc, 0x47, 0x08, 0x31, 0xeb, 0x5d, 0x25, 0xde, 0xb2, 0x56, 0x52, 0xbd, + 0x55, 0x62, 0x2a, 0x1c, 0x96, 0xd1, 0xcc, 0xf3, 0x6a, 0xa2, 0xdd, 0x79, 0x9b, 0x99, 0x59, 0xc9, + 0x76, 0x15, 0x50, 0x29, 0x8a, 0x1b, 0x07, 0xb8, 0x71, 0x20, 0x17, 0x4e, 0x9c, 0x80, 0x3f, 0x40, + 0xc1, 0x81, 0x03, 0x27, 0x2a, 0xc5, 0x81, 0xca, 0x09, 0x52, 0xc9, 0x1f, 0xa0, 0x8a, 0x2a, 0xb8, + 0x52, 0xef, 0x63, 0x3e, 0x77, 0x56, 0x2b, 0xa1, 0x2d, 0x17, 0xa7, 0x9d, 0xee, 0xd7, 0xdd, 0xaf, + 0x5f, 0xbf, 0x7e, 0xfd, 0xba, 0xfb, 0x2d, 0x5c, 0xdb, 0x27, 0x86, 0xeb, 0xef, 0x11, 0xc3, 0x1f, + 0xee, 0xdd, 0x0a, 0xbf, 0x6f, 0x0e, 0x5d, 0xea, 0x53, 0x34, 0x17, 0x1b, 0xd4, 0x9f, 0x42, 0x65, + 0xd7, 0xd8, 0xeb, 0x93, 0xce, 0xd0, 0x70, 0xd0, 0x32, 0x94, 0x38, 0xd0, 0x6a, 0x2e, 0x2b, 0x6b, + 0xca, 0x7a, 0x0e, 0x07, 0x20, 0xba, 0x0a, 0xe5, 0x8e, 0x6f, 0xb8, 0xfe, 0x43, 0xf2, 0x74, 0x59, + 0x5d, 0x53, 0xd6, 0xe7, 0x71, 0x08, 0xa3, 0x25, 0x28, 0x6e, 0x38, 0x16, 0x1b, 0xc9, 0xf1, 0x11, + 0x09, 0xa1, 0x55, 0x80, 0x87, 0xe4, 0xa9, 0x37, 0x34, 0x4c, 0x26, 0x30, 0xbf, 0xa6, 0xac, 0x57, + 0x71, 0x0c, 0xa3, 0xff, 0x4d, 0x05, 0xed, 0x01, 0x53, 0xe5, 0x3e, 0x31, 0x7c, 0x4c, 0x3e, 0x1c, + 0x11, 0xcf, 0x47, 0xdf, 0x86, 0x79, 0x73, 0xdf, 0x70, 0x7a, 0xe4, 0x31, 0x21, 0x96, 0xd4, 0x63, + 0xee, 0xce, 0x95, 0x9b, 0x31, 0x9d, 0x6f, 0x36, 0x62, 0x04, 0x38, 0x41, 0x8e, 0x5e, 0x87, 0xca, + 0x91, 0xe1, 0x13, 0x77, 0x60, 0xb8, 0x07, 0x5c, 0xd1, 0xb9, 0x3b, 0x4b, 0x09, 0xde, 0x47, 0xc1, + 0x28, 0x8e, 0x08, 0xd1, 0x9b, 0x50, 0x75, 0x89, 0x45, 0xc3, 0x31, 0xbe, 0x90, 0xc9, 0x9c, 0x49, + 0x62, 0xf4, 0x0d, 0x28, 0x7b, 0xbe, 0xe1, 0x8f, 0x3c, 0xe2, 0x2d, 0xe7, 0xd7, 0x72, 0xeb, 0x73, + 0x77, 0x56, 0x12, 0x8c, 0xa1, 0x7d, 0x3b, 0x9c, 0x0a, 0x87, 0xd4, 0x68, 0x1d, 0x16, 0x4d, 0x3a, + 0x18, 0x92, 0x3e, 0xf1, 0x89, 0x18, 0x5c, 0x2e, 0xac, 0x29, 0xeb, 0x65, 0x9c, 0x46, 0xa3, 0x57, + 0x20, 0x47, 0x5c, 0x77, 0xb9, 0x98, 0x61, 0x0d, 0x3c, 0x72, 0x1c, 0xdb, 0xe9, 0x6d, 0xb8, 0x2e, + 0x75, 0x31, 0xa3, 0xd2, 0x7f, 0xa2, 0x40, 0x25, 0x52, 0x4f, 0x67, 0x16, 0x25, 0xe6, 0xc1, 0x90, + 0xda, 0x8e, 0xbf, 0xeb, 0x71, 0x8b, 0xe6, 0x71, 0x02, 0xc7, 0xb6, 0xca, 0x25, 0x1e, 0xed, 0x1f, + 0x12, 0x6b, 0xd7, 0xe3, 0x76, 0xcb, 0xe3, 0x18, 0x06, 0x69, 0x90, 0xf3, 0xc8, 0x87, 0xdc, 0x2c, + 0x79, 0xcc, 0x3e, 0x99, 0xd4, 0xbe, 0xe1, 0xf9, 0x9d, 0xa7, 0x8e, 0xc9, 0x79, 0xf2, 0x42, 0x6a, + 0x1c, 0xa7, 0xff, 0x00, 0xb4, 0xa6, 0xed, 0x0d, 0x0d, 0xdf, 0xdc, 0x27, 0x6e, 0xdd, 0xf4, 0x6d, + 0xea, 0xa0, 0x57, 0xa0, 0x68, 0xf0, 0x2f, 0xae, 0xc7, 0xc2, 0x9d, 0x0b, 0x89, 0xb5, 0x08, 0x22, + 0x2c, 0x49, 0x98, 0xd7, 0x35, 0xe8, 0x60, 0x60, 0xfb, 0xa1, 0x52, 0x21, 0x8c, 0xd6, 0x60, 0xae, + 0xe5, 0xb1, 0xa9, 0x76, 0xd8, 0x1a, 0xb8, 0x6a, 0x65, 0x1c, 0x47, 0xe9, 0x0d, 0xc8, 0xd5, 0x1b, + 0x0f, 0x13, 0x42, 0x94, 0xe3, 0x85, 0xa8, 0xe3, 0x42, 0x30, 0xa0, 0x56, 0xcf, 0xa1, 0x2e, 0xb1, + 0xee, 0xf7, 0xa9, 0x79, 0x20, 0xb7, 0xe3, 0x6c, 0x32, 0x7f, 0xac, 0xc2, 0xa5, 0x96, 0xf3, 0xb8, + 0x3f, 0x22, 0xcc, 0x50, 0x91, 0x89, 0x3c, 0xf4, 0x1d, 0xa8, 0x86, 0x03, 0xbb, 0x4f, 0x87, 0x44, + 0x1a, 0xe9, 0x6a, 0xc2, 0x48, 0x09, 0x0a, 0x9c, 0x64, 0x40, 0xf7, 0xa0, 0x1a, 0x09, 0x6c, 0x35, + 0x99, 0xdd, 0x72, 0x63, 0x2e, 0x13, 0xa7, 0xc0, 0x49, 0x7a, 0x7e, 0xd2, 0xcd, 0x7d, 0x32, 0x30, + 0x5a, 0x4d, 0x6e, 0xd4, 0x1c, 0x0e, 0x61, 0xf4, 0x10, 0x2e, 0x90, 0x27, 0x66, 0x7f, 0x64, 0x91, + 0x18, 0x8f, 0xc5, 0xf7, 0xfe, 0xd8, 0x29, 0xb2, 0xb8, 0xf4, 0x5f, 0xa8, 0x71, 0xf7, 0x90, 0x86, + 0xfd, 0x2e, 0x5c, 0xb2, 0xb3, 0x2c, 0x23, 0xe3, 0x80, 0x9e, 0x6d, 0x88, 0x38, 0x25, 0xce, 0x16, + 0x80, 0xee, 0x86, 0x8e, 0x27, 0xc2, 0xc2, 0xf5, 0x09, 0xea, 0xa6, 0x5c, 0x50, 0x87, 0x9c, 0x61, + 0x06, 0x01, 0x41, 0x4b, 0x3a, 0x6b, 0xe3, 0x21, 0x66, 0x83, 0x68, 0x1b, 0x90, 0x3d, 0xe6, 0x23, + 0xd2, 0x2a, 0x37, 0x92, 0x1a, 0x8f, 0x91, 0xe1, 0x0c, 0x56, 0xfd, 0x33, 0x05, 0xce, 0xc7, 0x22, + 0xa3, 0x37, 0xa4, 0x8e, 0x47, 0xce, 0x1a, 0x1a, 0xdb, 0x80, 0xac, 0x94, 0xb9, 0x49, 0xe0, 0x1e, + 0x93, 0x8c, 0x11, 0xe8, 0x38, 0xce, 0x88, 0x10, 0xe4, 0x07, 0xd4, 0x22, 0xd2, 0x47, 0xf8, 0x37, + 0x7a, 0x19, 0xb4, 0x81, 0x61, 0x3b, 0xbe, 0x61, 0x3b, 0xc4, 0xed, 0x92, 0x21, 0x35, 0xf7, 0x65, + 0x60, 0x58, 0x8c, 0xf0, 0x1b, 0x0c, 0xad, 0x3f, 0x81, 0x0b, 0x8d, 0x58, 0x04, 0x6a, 0x13, 0xcf, + 0x33, 0x7a, 0x67, 0x5e, 0x63, 0x3a, 0xd6, 0xa9, 0xe3, 0xb1, 0x4e, 0xff, 0x83, 0x02, 0x8b, 0x98, + 0x58, 0xb4, 0x4d, 0x7c, 0x63, 0x46, 0xd3, 0x4e, 0x0b, 0x9f, 0x69, 0xb5, 0x72, 0x19, 0x21, 0xf8, + 0x14, 0xb6, 0xfb, 0x21, 0x5c, 0x67, 0x0b, 0xc0, 0xe1, 0x04, 0x3b, 0x2e, 0xed, 0xb9, 0xc4, 0xf3, + 0x9e, 0xcf, 0x72, 0xf4, 0x5f, 0x2b, 0xb0, 0x92, 0x54, 0xe0, 0x6d, 0xea, 0x1e, 0x19, 0xae, 0xf5, + 0x9c, 0xcc, 0x99, 0x65, 0xaa, 0x5c, 0xb6, 0xa9, 0xfe, 0xa5, 0xc4, 0x83, 0x4c, 0x83, 0x3a, 0x8f, + 0xed, 0x1e, 0xaa, 0x41, 0xde, 0x1b, 0x1a, 0x8e, 0x54, 0x6b, 0x29, 0xfb, 0xb2, 0xc6, 0x9c, 0x86, + 0xa5, 0x44, 0x1e, 0x4b, 0x74, 0x42, 0x45, 0x02, 0x90, 0x2d, 0xd2, 0x8a, 0x05, 0x39, 0x19, 0x22, + 0x8e, 0x89, 0x82, 0x09, 0x72, 0x16, 0x67, 0xbd, 0x20, 0xce, 0xe6, 0x45, 0x9c, 0x0d, 0xe0, 0xf0, + 0x6c, 0x15, 0x62, 0x67, 0xab, 0x06, 0x9a, 0x77, 0x60, 0x0f, 0x9b, 0xed, 0xcd, 0xba, 0xd7, 0x91, + 0x1a, 0x15, 0xf9, 0xdd, 0x32, 0x86, 0xd7, 0xff, 0xa8, 0xc2, 0x15, 0x16, 0xb4, 0xad, 0x51, 0x3f, + 0x16, 0x73, 0x67, 0x94, 0x62, 0xdd, 0x85, 0xa2, 0xc9, 0xed, 0x38, 0x25, 0x90, 0x0a, 0x63, 0x63, + 0x49, 0x8c, 0x1a, 0xb0, 0xe0, 0x49, 0x95, 0x44, 0x88, 0xe5, 0x06, 0x5b, 0xb8, 0x73, 0x2d, 0xc1, + 0xde, 0x49, 0x90, 0xe0, 0x14, 0x0b, 0x53, 0x9d, 0x0e, 0x89, 0x6b, 0xf8, 0xd4, 0xe5, 0xd7, 0x63, + 0x9e, 0x8b, 0x48, 0xaa, 0xbe, 0x1d, 0x23, 0xc0, 0x09, 0xf2, 0x4c, 0xc7, 0x29, 0x64, 0x3b, 0xce, + 0xaf, 0x54, 0x58, 0x6a, 0x13, 0xb7, 0x37, 0x7b, 0xfb, 0xdd, 0x83, 0xaa, 0x75, 0xca, 0x1b, 0x3a, + 0x41, 0x8f, 0x5a, 0x80, 0x06, 0x4c, 0x33, 0xab, 0x79, 0x2a, 0xf7, 0xcb, 0x60, 0x0a, 0x1d, 0x2d, + 0x3f, 0x25, 0x88, 0x4f, 0x30, 0xd2, 0x0e, 0x5c, 0x68, 0x87, 0xa8, 0x07, 0xc1, 0xc4, 0xe8, 0x9b, + 0xb1, 0x84, 0x58, 0xc9, 0xb8, 0x5f, 0x22, 0x9e, 0x74, 0x46, 0xac, 0x7f, 0xaa, 0x40, 0xb5, 0xe9, + 0x1a, 0xb6, 0x13, 0x84, 0x34, 0xf4, 0x12, 0x2c, 0xf8, 0x86, 0xdb, 0x23, 0x7e, 0xd7, 0xa1, 0x16, + 0xe9, 0xda, 0x16, 0xb7, 0x77, 0x05, 0xcf, 0x0b, 0xec, 0x16, 0xb5, 0x48, 0xcb, 0x42, 0x2f, 0x80, + 0x84, 0xa5, 0xc2, 0xe2, 0xac, 0xce, 0x09, 0x1c, 0x57, 0x16, 0x7d, 0x0d, 0x2e, 0x4b, 0x92, 0xc8, + 0x9c, 0x5d, 0x93, 0x8e, 0x64, 0xf2, 0x58, 0xc5, 0x97, 0xc4, 0x70, 0xdc, 0x83, 0x47, 0x8e, 0x8f, + 0xde, 0x86, 0x35, 0xc9, 0xc7, 0x12, 0x0b, 0xbb, 0xb7, 0xef, 0x77, 0x2d, 0xa6, 0x61, 0x77, 0x40, + 0x0f, 0x89, 0x14, 0x20, 0x8a, 0x9b, 0x15, 0x41, 0xd7, 0x92, 0x64, 0x7c, 0x1d, 0x6d, 0x7a, 0x48, + 0xb8, 0x1c, 0xfd, 0x37, 0x39, 0xd0, 0xd2, 0x2b, 0x3f, 0xab, 0x2f, 0x5d, 0x07, 0x60, 0x5f, 0x5d, + 0x66, 0x3f, 0xc2, 0x17, 0x5d, 0xc1, 0x15, 0x86, 0x61, 0xe2, 0x09, 0xba, 0x0d, 0x05, 0x31, 0x92, + 0x75, 0xd4, 0x1a, 0x74, 0x30, 0xa4, 0x0e, 0x71, 0x7c, 0x4e, 0x8b, 0x05, 0x25, 0x7a, 0x11, 0xaa, + 0xd1, 0xb5, 0xd4, 0xf5, 0xc3, 0xc4, 0x3e, 0x71, 0x57, 0xc9, 0x6a, 0xa4, 0x90, 0xe1, 0xb8, 0x63, + 0xd5, 0x08, 0xfa, 0x12, 0x2c, 0xec, 0x51, 0xea, 0x7b, 0xbe, 0x6b, 0x0c, 0xbb, 0x16, 0x75, 0x88, + 0x0c, 0x5b, 0xd5, 0x10, 0xdb, 0xa4, 0x0e, 0x19, 0x2b, 0x28, 0x4a, 0xe3, 0x05, 0x05, 0xaa, 0xc3, + 0x82, 0x30, 0xfd, 0x50, 0x7a, 0xc7, 0x72, 0x99, 0xdb, 0x2b, 0x99, 0x1f, 0x27, 0xfc, 0x07, 0x57, + 0xad, 0x84, 0x3b, 0x65, 0x79, 0x77, 0x25, 0xdb, 0xbb, 0x7f, 0xab, 0x42, 0x95, 0xb9, 0x57, 0xe4, + 0xd8, 0x77, 0xa1, 0xdc, 0xb7, 0x0f, 0x89, 0xc3, 0x66, 0x56, 0x32, 0x42, 0x0f, 0xa3, 0xde, 0x94, + 0x04, 0x38, 0x24, 0x65, 0xbb, 0xc4, 0x7d, 0x37, 0xee, 0x9a, 0x15, 0x86, 0x11, 0x8e, 0xd9, 0x84, + 0x1b, 0x31, 0x8f, 0x14, 0x0b, 0x4c, 0xb9, 0x7c, 0x8e, 0xef, 0xec, 0xb5, 0x88, 0x8c, 0xaf, 0x71, + 0x37, 0x7e, 0x02, 0xea, 0x70, 0x7d, 0x92, 0x94, 0x78, 0x32, 0x71, 0x35, 0x53, 0x86, 0x50, 0xe4, + 0x1e, 0xac, 0xf4, 0x69, 0xaf, 0xeb, 0x11, 0xf7, 0xd0, 0x36, 0xc9, 0xf8, 0x31, 0x29, 0x70, 0x2f, + 0xbf, 0xd2, 0xa7, 0xbd, 0x8e, 0x20, 0x49, 0x1d, 0x15, 0xfd, 0x03, 0x58, 0xea, 0x08, 0x85, 0x42, + 0x2b, 0xc8, 0x98, 0x79, 0x1b, 0x8a, 0x42, 0x99, 0xe9, 0x76, 0x93, 0x84, 0x53, 0xac, 0xa6, 0x0f, + 0xe0, 0xf2, 0xd8, 0x5c, 0x32, 0x51, 0x7e, 0x0d, 0x4a, 0xc6, 0x70, 0xd8, 0xb7, 0x89, 0x35, 0x7d, + 0xb6, 0x80, 0x72, 0xda, 0x74, 0x1f, 0xc0, 0x8d, 0x4e, 0x3c, 0x36, 0xc4, 0x8c, 0x17, 0xac, 0x71, + 0x56, 0x91, 0x4a, 0xff, 0x3a, 0x5c, 0x6b, 0x50, 0xea, 0x5a, 0xb6, 0xc3, 0x6e, 0xae, 0xfb, 0xc1, + 0x31, 0x09, 0xe6, 0x59, 0x86, 0xd2, 0x21, 0x71, 0xbd, 0xa0, 0x86, 0xce, 0xe1, 0x00, 0x64, 0x25, + 0xd5, 0x4a, 0x36, 0xa7, 0xb4, 0xcc, 0xff, 0x1e, 0x99, 0xd1, 0xeb, 0xb0, 0x14, 0x9e, 0x3d, 0x9f, + 0x9a, 0xb4, 0xdf, 0x0d, 0x94, 0x50, 0xb9, 0x5b, 0x5c, 0x0c, 0xce, 0x19, 0x1f, 0x7c, 0x4f, 0x8c, + 0xfd, 0xdf, 0xf8, 0xb6, 0xfe, 0x6f, 0x05, 0x2e, 0xd6, 0x2d, 0x2b, 0x5a, 0x60, 0x60, 0xcd, 0x97, + 0x41, 0x95, 0x3b, 0x75, 0x6c, 0xdc, 0x55, 0x6d, 0x0b, 0x2d, 0x25, 0x32, 0x9f, 0xf9, 0x30, 0xb5, + 0x19, 0x8b, 0x99, 0x59, 0xf9, 0x7d, 0x0d, 0xce, 0xdb, 0x5e, 0xd7, 0x21, 0x47, 0xdd, 0x28, 0x82, + 0x73, 0xbd, 0xcb, 0x78, 0xd1, 0xf6, 0xb6, 0xc8, 0x51, 0x34, 0x1d, 0xba, 0x01, 0x73, 0x07, 0xb2, + 0x4f, 0xc6, 0x2c, 0x24, 0xce, 0x1d, 0x04, 0xa8, 0x96, 0x95, 0x19, 0xc5, 0x8a, 0xd9, 0x51, 0xec, + 0x4f, 0x0a, 0x5c, 0xc6, 0x84, 0xdd, 0x55, 0x67, 0x5a, 0xfb, 0x32, 0x94, 0x4c, 0xc3, 0x33, 0x0d, + 0x8b, 0xc8, 0x8e, 0x46, 0x00, 0xb2, 0x11, 0x97, 0xcb, 0xb7, 0x64, 0x13, 0x26, 0x00, 0xd3, 0xcb, + 0xc8, 0x9f, 0x68, 0x19, 0x13, 0x52, 0x8d, 0xbf, 0xe4, 0xe0, 0x6a, 0xb4, 0x80, 0xb1, 0x33, 0x71, + 0xc6, 0x7b, 0x74, 0xd2, 0xce, 0x5e, 0xe1, 0xe7, 0xc5, 0x8d, 0x6d, 0x6a, 0x98, 0xfe, 0x9b, 0xf0, + 0x82, 0xcf, 0x6a, 0x85, 0xae, 0xef, 0xda, 0xbd, 0x1e, 0x53, 0xff, 0x90, 0x38, 0x89, 0xdc, 0xc2, + 0x3e, 0x41, 0x67, 0xe4, 0x3a, 0x97, 0xb1, 0x2b, 0x44, 0x6c, 0x30, 0x09, 0xf1, 0x1e, 0x49, 0xb6, + 0xd3, 0x14, 0xb2, 0x9d, 0xc6, 0x60, 0x79, 0x4a, 0x5c, 0x21, 0x97, 0x58, 0x34, 0xa5, 0x4f, 0x71, + 0x9a, 0x3e, 0x2b, 0x71, 0x7d, 0x58, 0x8d, 0x97, 0x50, 0x27, 0xb5, 0xa1, 0xa5, 0x13, 0x6d, 0x68, + 0x39, 0x7b, 0x43, 0xff, 0x9a, 0x83, 0x6b, 0x99, 0x1b, 0x3a, 0x9b, 0x6e, 0xc7, 0x5d, 0x28, 0xb0, + 0xfa, 0x2d, 0xc8, 0xae, 0x93, 0x6d, 0x98, 0x70, 0xb6, 0xa8, 0xda, 0x13, 0xd4, 0x41, 0x66, 0x93, + 0x3b, 0x49, 0x9f, 0xf5, 0x64, 0xb9, 0xd2, 0xab, 0x80, 0xf8, 0x46, 0x24, 0x29, 0x85, 0x97, 0x6b, + 0x6c, 0x24, 0xde, 0x06, 0x41, 0x4d, 0xa8, 0x04, 0x15, 0x0b, 0x2b, 0xef, 0x98, 0xea, 0x5f, 0xce, + 0x2c, 0x90, 0xc6, 0xca, 0x12, 0x1c, 0x31, 0x66, 0x6e, 0x43, 0x29, 0x73, 0x1b, 0xd0, 0x26, 0x2c, + 0xf2, 0xba, 0xa0, 0x1b, 0x4d, 0x5b, 0xe6, 0xd3, 0xbe, 0x98, 0xbc, 0x18, 0x32, 0x4b, 0x21, 0xbc, + 0xc0, 0x79, 0x83, 0x8a, 0xcb, 0xd3, 0xff, 0xae, 0xc2, 0x6a, 0xb4, 0xa9, 0x3b, 0xd4, 0xf3, 0x67, + 0x7d, 0x52, 0x4f, 0x74, 0xec, 0xd4, 0x33, 0x1e, 0xbb, 0xdb, 0x50, 0x12, 0xb5, 0x38, 0x3b, 0xf5, + 0xcc, 0x18, 0x97, 0xc7, 0xf6, 0x60, 0x60, 0xb4, 0x9c, 0xc7, 0x14, 0x07, 0x74, 0xe8, 0x0d, 0x98, + 0xe7, 0xdb, 0x1c, 0xf0, 0xe5, 0x8f, 0xe7, 0x9b, 0x63, 0xc4, 0x1d, 0xc9, 0x7b, 0x8a, 0x30, 0xf8, + 0xb1, 0x0a, 0x37, 0x26, 0x1a, 0x78, 0x36, 0x27, 0xe7, 0xb9, 0x58, 0xf8, 0x54, 0xe7, 0xec, 0x54, + 0x6d, 0x45, 0x88, 0xac, 0x9c, 0xe8, 0x65, 0x2b, 0xa9, 0x5e, 0xf6, 0x6a, 0x40, 0xb9, 0x65, 0x0c, + 0x82, 0xd2, 0x29, 0x86, 0x41, 0x37, 0xa1, 0xc8, 0xa3, 0x43, 0xe0, 0x02, 0x19, 0x6d, 0x22, 0xbe, + 0x93, 0x92, 0x4a, 0x6f, 0xc8, 0x87, 0x34, 0x3e, 0xf1, 0xe4, 0x87, 0xb4, 0x15, 0x49, 0x16, 0x9b, + 0x35, 0x42, 0xe8, 0xbf, 0x57, 0x01, 0x8d, 0x07, 0x27, 0x76, 0x4f, 0x4f, 0xd8, 0xc7, 0x84, 0xcd, + 0x55, 0xf9, 0x50, 0x17, 0x2c, 0x59, 0x4d, 0x2d, 0x39, 0xe8, 0x7b, 0xe5, 0x4e, 0xd0, 0xf7, 0x7a, + 0x1b, 0x34, 0x33, 0x28, 0x10, 0xbb, 0x5e, 0xd4, 0xd1, 0x9e, 0x52, 0x45, 0x2e, 0x9a, 0x71, 0x78, + 0xe4, 0x8d, 0xc7, 0xc8, 0x42, 0x46, 0x8c, 0x7c, 0x0d, 0xe6, 0xf6, 0xfa, 0xd4, 0x3c, 0x90, 0x75, + 0xac, 0xb8, 0xa5, 0x50, 0xf2, 0xec, 0x70, 0xf1, 0xb0, 0x17, 0x74, 0xc9, 0x49, 0xd8, 0xbb, 0x28, + 0x45, 0xbd, 0x0b, 0xfd, 0x97, 0x0a, 0x2c, 0x45, 0xc7, 0xa3, 0xd1, 0xa7, 0x1e, 0x99, 0x51, 0xdc, + 0x89, 0x65, 0x39, 0x6a, 0x32, 0xcb, 0x39, 0x45, 0x37, 0xf2, 0x63, 0x05, 0x2e, 0x8f, 0xa9, 0x37, + 0x9b, 0x53, 0xbb, 0x0c, 0x25, 0x6f, 0x64, 0x9a, 0xac, 0x32, 0x95, 0xfa, 0x49, 0xf0, 0x34, 0xfa, + 0xfd, 0x54, 0x01, 0x2d, 0x7a, 0x54, 0x11, 0x8e, 0x3d, 0x83, 0x37, 0xa9, 0xab, 0x50, 0x96, 0xee, + 0x2f, 0xae, 0xe3, 0x1c, 0x0e, 0xe1, 0xe3, 0x9e, 0x9b, 0xf4, 0xef, 0x41, 0x81, 0xd3, 0x4d, 0x79, + 0x97, 0x9e, 0xe4, 0xee, 0x2b, 0x50, 0xe9, 0x0c, 0xfb, 0x36, 0x0f, 0x44, 0x32, 0x35, 0x8d, 0x10, + 0xfa, 0x47, 0x2a, 0x5c, 0xc0, 0x74, 0xe4, 0x13, 0x2e, 0xaa, 0x6e, 0x0d, 0x6c, 0x8f, 0x57, 0x2c, + 0x35, 0xd0, 0x3a, 0x74, 0xe4, 0x9a, 0x24, 0x16, 0x1d, 0x44, 0x1d, 0x37, 0x86, 0x47, 0xeb, 0xb0, + 0x28, 0x70, 0xe9, 0x23, 0x9d, 0x46, 0x33, 0xa9, 0xa2, 0x1a, 0x89, 0x49, 0x15, 0x85, 0xcf, 0x18, + 0x9e, 0x49, 0x15, 0xb8, 0x48, 0x6a, 0x5e, 0x48, 0x4d, 0xa1, 0xd1, 0x5b, 0x50, 0x94, 0xbd, 0xd4, + 0x02, 0xdf, 0x93, 0x64, 0xaa, 0x90, 0xb1, 0xba, 0xe0, 0x71, 0x4b, 0xfc, 0xea, 0x0e, 0x2c, 0x04, + 0xd6, 0x12, 0xae, 0x75, 0x8c, 0xa5, 0xd7, 0x60, 0x6e, 0xbb, 0x6f, 0xa5, 0x8c, 0x1d, 0x47, 0x31, + 0x8a, 0x2d, 0x72, 0x94, 0xda, 0xcd, 0x38, 0x4a, 0xff, 0x4f, 0x0e, 0x0a, 0xe2, 0xf0, 0xae, 0x40, + 0xa5, 0xe5, 0xf1, 0x27, 0x2f, 0x59, 0xa4, 0x97, 0x71, 0x84, 0x60, 0x5a, 0xf0, 0xcf, 0xa8, 0xe9, + 0x2e, 0x41, 0x74, 0x0f, 0xe6, 0xc4, 0x67, 0x10, 0x9a, 0xc7, 0x3b, 0xd0, 0x69, 0x07, 0xc6, 0x71, + 0x0e, 0xf4, 0x10, 0xce, 0x6f, 0x11, 0x62, 0x35, 0x5d, 0x3a, 0x1c, 0x06, 0x14, 0x32, 0x4d, 0x9f, + 0x22, 0x66, 0x9c, 0x0f, 0xbd, 0x09, 0x8b, 0x0c, 0x59, 0xb7, 0xac, 0x50, 0x94, 0xe8, 0x89, 0xa1, + 0xf1, 0xd8, 0x8a, 0xd3, 0xa4, 0xa8, 0x01, 0x0b, 0xef, 0x0e, 0x2d, 0xc3, 0x27, 0xd2, 0x84, 0x41, + 0xc2, 0x77, 0x2d, 0x2b, 0x69, 0x90, 0x1b, 0x84, 0x53, 0x2c, 0xe9, 0xd7, 0xe6, 0xd2, 0xd8, 0x6b, + 0x33, 0xfa, 0x0a, 0x6f, 0x02, 0xf6, 0x08, 0x4f, 0xc4, 0x17, 0x52, 0x29, 0x49, 0xf0, 0xea, 0xd8, + 0x13, 0x0d, 0xc0, 0x1e, 0x41, 0xbb, 0x70, 0x31, 0xc3, 0x71, 0xbc, 0xe5, 0x0a, 0xd7, 0x6d, 0x6d, + 0x9a, 0x87, 0xe1, 0x4c, 0x6e, 0xfd, 0x47, 0x70, 0x31, 0xbc, 0x61, 0xe2, 0x0f, 0xe9, 0xa7, 0xb8, + 0xd9, 0xd6, 0x83, 0x66, 0xa6, 0x3a, 0xf1, 0x7a, 0x90, 0x3d, 0xcc, 0x8c, 0xa7, 0x49, 0xfd, 0x9f, + 0x0a, 0x3b, 0x55, 0x89, 0x3f, 0x62, 0x9c, 0x66, 0xf2, 0xac, 0xeb, 0x50, 0x9d, 0xc5, 0x75, 0x98, + 0xd5, 0x2a, 0xb8, 0x0d, 0x97, 0x44, 0xce, 0xe5, 0xd9, 0xcf, 0x48, 0x77, 0x48, 0xdc, 0xae, 0x47, + 0x4c, 0xea, 0x88, 0x72, 0x52, 0xc5, 0x88, 0x0f, 0x76, 0xec, 0x67, 0x64, 0x87, 0xb8, 0x1d, 0x3e, + 0x92, 0xf5, 0x62, 0xa4, 0xff, 0x4e, 0x01, 0x14, 0x7f, 0x69, 0x9e, 0xcd, 0x45, 0xf8, 0x0e, 0x54, + 0xf7, 0x22, 0xa1, 0xe1, 0x0b, 0xf2, 0x0b, 0xd9, 0xd9, 0x44, 0x7c, 0xfe, 0x24, 0x5f, 0xe6, 0x2e, + 0x59, 0x30, 0x1f, 0x4f, 0xff, 0x18, 0x8d, 0x6f, 0x87, 0x01, 0x98, 0x7f, 0x33, 0x9c, 0x43, 0xad, + 0x20, 0xd2, 0xf2, 0x6f, 0x86, 0x33, 0x03, 0x59, 0x15, 0xcc, 0xbf, 0x59, 0x10, 0x19, 0x88, 0xf7, + 0x48, 0x19, 0x3e, 0x03, 0x50, 0x7f, 0x1d, 0xe6, 0xd3, 0xaf, 0x20, 0xfb, 0x76, 0x6f, 0x5f, 0xfe, + 0x93, 0x83, 0x7f, 0x23, 0x0d, 0x72, 0x7d, 0x7a, 0x24, 0xc3, 0x0f, 0xfb, 0x64, 0xba, 0xc5, 0xcd, + 0x72, 0x32, 0x2e, 0xae, 0x6d, 0x14, 0xec, 0xf9, 0x37, 0xbb, 0xb4, 0x82, 0x9a, 0x59, 0xaa, 0x16, + 0xc2, 0xfa, 0xf7, 0xe1, 0xc6, 0x26, 0xed, 0xc5, 0x9a, 0x78, 0xd1, 0x23, 0xeb, 0x6c, 0x36, 0x50, + 0xff, 0x48, 0x81, 0xb5, 0xc9, 0x53, 0xcc, 0x26, 0x1b, 0x99, 0xf6, 0x82, 0xdc, 0x67, 0xb6, 0x24, + 0xe6, 0x81, 0x37, 0x1a, 0xb4, 0x89, 0x6f, 0xa0, 0xaf, 0x06, 0x67, 0x3b, 0x2b, 0xb7, 0x08, 0x28, + 0x13, 0x67, 0xbc, 0x06, 0x9a, 0x19, 0xc7, 0x77, 0xc8, 0x87, 0x72, 0x9e, 0x31, 0xbc, 0xfe, 0x73, + 0x05, 0x2e, 0xc5, 0xfe, 0xd3, 0x40, 0xfc, 0x40, 0x22, 0xba, 0x08, 0x05, 0xd1, 0xda, 0x16, 0x9b, + 0x28, 0x00, 0xe6, 0x39, 0x4f, 0xa8, 0xfb, 0x80, 0x6d, 0xae, 0xbc, 0x7e, 0x24, 0x88, 0x96, 0xa0, + 0xf8, 0x84, 0xba, 0x9b, 0xf4, 0x48, 0x9e, 0x5b, 0x09, 0x89, 0xec, 0x6b, 0xc0, 0x39, 0xf2, 0xb2, + 0x4d, 0x24, 0x40, 0xc6, 0xe1, 0x8d, 0x06, 0x8c, 0x43, 0x24, 0xbe, 0x12, 0x62, 0xa9, 0xe0, 0x5a, + 0xa6, 0x4e, 0x75, 0xf3, 0x60, 0x56, 0xbb, 0x70, 0x11, 0x0a, 0xf1, 0x1e, 0xb3, 0x00, 0x32, 0xff, + 0xb8, 0x21, 0xff, 0xdf, 0x95, 0x0f, 0xff, 0xdf, 0xa5, 0xff, 0x43, 0x01, 0x3d, 0x53, 0x3f, 0x71, + 0xff, 0xcc, 0x28, 0x98, 0x9c, 0x41, 0x43, 0xf4, 0x16, 0x94, 0x83, 0x9d, 0xe6, 0xb6, 0x4d, 0xff, + 0x3b, 0x28, 0x53, 0x7b, 0x1c, 0xf2, 0xd4, 0xae, 0x07, 0xc9, 0x13, 0xaa, 0x40, 0xe1, 0x91, 0x6b, + 0xfb, 0x44, 0x3b, 0x87, 0xca, 0x90, 0xdf, 0x31, 0x3c, 0x4f, 0x53, 0x6a, 0xeb, 0x22, 0x37, 0x8a, + 0x3d, 0x3e, 0x03, 0x14, 0x1b, 0x2e, 0x31, 0x38, 0x1d, 0x40, 0x51, 0x34, 0x55, 0x35, 0xa5, 0xd6, + 0x86, 0xf9, 0xf8, 0x9b, 0x33, 0x13, 0xb7, 0xdd, 0xad, 0x5b, 0x96, 0x76, 0x0e, 0xcd, 0x43, 0x79, + 0xbb, 0x1b, 0x10, 0x32, 0xa6, 0xed, 0x6e, 0x9b, 0x7d, 0xab, 0x68, 0x0e, 0x4a, 0xdb, 0x5d, 0x9e, + 0x8d, 0x6a, 0x39, 0x01, 0xf0, 0x16, 0x8b, 0x96, 0xaf, 0xdd, 0x85, 0xf9, 0xf8, 0x0b, 0x05, 0x13, + 0x57, 0xdf, 0x6c, 0xbd, 0xb7, 0x21, 0xc4, 0x35, 0x71, 0xbd, 0xb5, 0xd5, 0xda, 0x7a, 0x47, 0x53, + 0x18, 0xd4, 0xd9, 0xdd, 0xde, 0xd9, 0x61, 0x90, 0x5a, 0x7b, 0x03, 0x20, 0xba, 0xcc, 0xd9, 0x3a, + 0xb6, 0xb6, 0xb7, 0x18, 0xcf, 0x1c, 0x94, 0x1e, 0xd5, 0x5b, 0xbb, 0x82, 0x85, 0x01, 0x58, 0x00, + 0x2a, 0xa3, 0x69, 0x32, 0x9a, 0x5c, 0xed, 0xd5, 0x54, 0x8a, 0x8f, 0x4a, 0x90, 0xab, 0xf7, 0xfb, + 0xda, 0x39, 0x54, 0x04, 0xb5, 0x79, 0x5f, 0xa8, 0xbe, 0x45, 0xdd, 0x81, 0xd1, 0xd7, 0xd4, 0xda, + 0x3b, 0x70, 0x65, 0x62, 0x6a, 0xc9, 0xb5, 0x6d, 0xb6, 0x5b, 0xbb, 0x62, 0x66, 0xbc, 0xb1, 0xb9, + 0x51, 0xef, 0x6c, 0x68, 0x0a, 0x42, 0xb0, 0x20, 0x81, 0x6e, 0xa7, 0xf1, 0x60, 0xa3, 0x5d, 0xd7, + 0xd4, 0xda, 0x33, 0x58, 0x48, 0xde, 0x97, 0x5c, 0x3f, 0xea, 0x1e, 0xd8, 0x4e, 0x4f, 0xf0, 0x77, + 0x7c, 0x9e, 0x6e, 0x09, 0xcd, 0x85, 0x1d, 0x2d, 0x4d, 0x45, 0x1a, 0xcc, 0xb7, 0x1c, 0xdb, 0xb7, + 0x8d, 0xbe, 0xfd, 0x8c, 0xd1, 0xe6, 0x50, 0x15, 0x2a, 0x3b, 0x2e, 0x19, 0x1a, 0x2e, 0x03, 0xf3, + 0x68, 0x01, 0x80, 0x9b, 0x13, 0x13, 0xc3, 0x7a, 0xaa, 0x15, 0x18, 0xc3, 0x23, 0xc3, 0xf6, 0x6d, + 0xa7, 0x27, 0xac, 0x5c, 0xac, 0x7d, 0x0b, 0xaa, 0x89, 0xb8, 0x82, 0xce, 0x43, 0xf5, 0xdd, 0xad, + 0xd6, 0x56, 0x6b, 0xb7, 0x55, 0xdf, 0x6c, 0xbd, 0xbf, 0xd1, 0x14, 0xe6, 0x6e, 0xb7, 0x3a, 0xed, + 0xfa, 0x6e, 0xe3, 0x81, 0xa6, 0xb0, 0x95, 0x89, 0x4f, 0xf5, 0xfe, 0x5b, 0x7f, 0xfe, 0x7c, 0x55, + 0xf9, 0xe4, 0xf3, 0x55, 0xe5, 0xb3, 0xcf, 0x57, 0x95, 0x9f, 0x7d, 0xb1, 0x7a, 0xee, 0x93, 0x2f, + 0x56, 0xcf, 0x7d, 0xfa, 0xc5, 0xea, 0xb9, 0xf7, 0x5f, 0xea, 0xd9, 0xfe, 0xfe, 0x68, 0xef, 0xa6, + 0x49, 0x07, 0xb7, 0x86, 0xb6, 0xd3, 0x33, 0x8d, 0xe1, 0x2d, 0xdf, 0x36, 0x2d, 0xf3, 0x56, 0xcc, + 0x35, 0xf7, 0x8a, 0xfc, 0x0d, 0xe5, 0xb5, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0x6e, 0x77, 0x18, + 0x03, 0xa7, 0x2b, 0x00, 0x00, } func (m *TableSpan) Marshal() (dAtA []byte, err error) { @@ -5271,6 +5283,11 @@ func (m *NodeHeartbeat) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.LogServiceDispatcherCount != 0 { + i = encodeVarintHeartbeat(dAtA, i, uint64(m.LogServiceDispatcherCount)) + i-- + dAtA[i] = 0x28 + } if m.DispatcherDrainTargetEpoch != 0 { i = encodeVarintHeartbeat(dAtA, i, uint64(m.DispatcherDrainTargetEpoch)) i-- @@ -7549,6 +7566,9 @@ func (m *NodeHeartbeat) Size() (n int) { if m.DispatcherDrainTargetEpoch != 0 { n += 1 + sovHeartbeat(uint64(m.DispatcherDrainTargetEpoch)) } + if m.LogServiceDispatcherCount != 0 { + n += 1 + sovHeartbeat(uint64(m.LogServiceDispatcherCount)) + } return n } @@ -11320,6 +11340,25 @@ func (m *NodeHeartbeat) Unmarshal(dAtA []byte) error { break } } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field LogServiceDispatcherCount", wireType) + } + m.LogServiceDispatcherCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHeartbeat + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.LogServiceDispatcherCount |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipHeartbeat(dAtA[iNdEx:]) diff --git a/heartbeatpb/heartbeat.proto b/heartbeatpb/heartbeat.proto index b55a9117af..e9e322b204 100644 --- a/heartbeatpb/heartbeat.proto +++ b/heartbeatpb/heartbeat.proto @@ -206,6 +206,9 @@ message NodeHeartbeat { // currently applied on this node. Empty target means the drain target is clear. string dispatcher_drain_target_node_id = 3; uint64 dispatcher_drain_target_epoch = 4; + // log_service_dispatcher_count is the number of dispatchers currently + // reading from this node's event store. + uint32 log_service_dispatcher_count = 5; } // SetNodeLivenessRequest asks a node to transition its local liveness. diff --git a/logservice/eventstore/event_store.go b/logservice/eventstore/event_store.go index 07ec16e5e2..b068a5edc0 100644 --- a/logservice/eventstore/event_store.go +++ b/logservice/eventstore/event_store.go @@ -93,6 +93,9 @@ type EventStore interface { UpdateDispatcherCheckpointTs(dispatcherID common.DispatcherID, checkpointTs uint64) + // DispatcherCount returns the number of dispatchers currently reading from this event store. + DispatcherCount() int + // GetIterator returns an iterator for the requested range and resume cursor. GetIterator(dispatcherID common.DispatcherID, request ScanRequest) (EventIterator, error) @@ -720,6 +723,12 @@ func (e *eventStore) UnregisterDispatcher(changefeedID common.ChangeFeedID, disp zap.Stringer("dispatcherID", dispatcherID)) } +func (e *eventStore) DispatcherCount() int { + e.dispatcherMeta.RLock() + defer e.dispatcherMeta.RUnlock() + return len(e.dispatcherMeta.dispatcherStats) +} + func (e *eventStore) UpdateDispatcherCheckpointTs( dispatcherID common.DispatcherID, checkpointTs uint64, diff --git a/logservice/eventstore/event_store_test.go b/logservice/eventstore/event_store_test.go index 6b58725b18..ac41b62dd1 100644 --- a/logservice/eventstore/event_store_test.go +++ b/logservice/eventstore/event_store_test.go @@ -758,6 +758,7 @@ func TestEventStoreUnregisterDispatcherWithoutDataSharingRemovesSubscription(t * EndKey: []byte("h"), } require.True(t, store.RegisterDispatcher(cfID, dispatcherID, span, 100, func(uint64, uint64) {}, false, false)) + require.Equal(t, 1, store.DispatcherCount()) mockSubClient := subClient.(*mockSubscriptionClient) mockSubClient.mu.Lock() @@ -765,6 +766,7 @@ func TestEventStoreUnregisterDispatcherWithoutDataSharingRemovesSubscription(t * mockSubClient.mu.Unlock() store.UnregisterDispatcher(cfID, dispatcherID) + require.Zero(t, store.DispatcherCount()) mockSubClient.mu.Lock() require.Equal(t, 1, len(mockSubClient.subscriptions)) @@ -800,8 +802,10 @@ func TestEventStoreUnregisterDispatcherWithDataSharingKeepsSubscriptionForTTL(t EndKey: []byte("h"), } require.True(t, store.RegisterDispatcher(cfID, dispatcherID, span, 100, func(uint64, uint64) {}, false, false)) + require.Equal(t, 1, store.DispatcherCount()) store.UnregisterDispatcher(cfID, dispatcherID) + require.Zero(t, store.DispatcherCount()) mockSubClient := subClient.(*mockSubscriptionClient) mockSubClient.mu.Lock() diff --git a/maintainer/maintainer_manager_node.go b/maintainer/maintainer_manager_node.go index 3b67b3d5f1..3e368a474c 100644 --- a/maintainer/maintainer_manager_node.go +++ b/maintainer/maintainer_manager_node.go @@ -19,6 +19,7 @@ import ( "github.com/pingcap/log" "github.com/pingcap/ticdc/heartbeatpb" + appcontext "github.com/pingcap/ticdc/pkg/common/context" "github.com/pingcap/ticdc/pkg/liveness" "github.com/pingcap/ticdc/pkg/messaging" "github.com/pingcap/ticdc/pkg/node" @@ -29,6 +30,10 @@ import ( // Forced heartbeats bypass this throttle to acknowledge state changes immediately. const nodeHeartbeatInterval = 5 * time.Second +type logServiceDispatcherCounter interface { + DispatcherCount() int +} + // managerNodeState owns node-scoped state shared by all local maintainers. type managerNodeState struct { // liveness points to the server-wide node liveness state shared with other @@ -74,9 +79,10 @@ func newNodeEpoch() uint64 { return nodeEpoch } -// sendNodeHeartbeat reports node-scoped liveness and dispatcher drain target to -// coordinator. It is the authoritative acknowledgement channel for node-level -// drain state, including cases where no changefeed maintainer exists locally. +// sendNodeHeartbeat reports node-scoped liveness, dispatcher drain target, and +// the local log service dispatcher count to coordinator. It is the authoritative +// acknowledgement channel for node-level drain state, including cases where no +// changefeed maintainer exists locally. func (m *Manager) sendNodeHeartbeat(force bool) { if !m.isBootstrap() { return @@ -95,9 +101,14 @@ func (m *Manager) sendNodeHeartbeat(force bool) { currentLiveness = m.node.liveness.Load() } drainTarget, drainEpoch := m.getDispatcherDrainTarget() + logServiceDispatcherCount := 0 + if store, ok := appcontext.TryGetService[logServiceDispatcherCounter](appcontext.EventStore); ok { + logServiceDispatcherCount = store.DispatcherCount() + } hb := &heartbeatpb.NodeHeartbeat{ - Liveness: m.toNodeLivenessPB(currentLiveness), - NodeEpoch: m.node.nodeEpoch, + Liveness: m.toNodeLivenessPB(currentLiveness), + NodeEpoch: m.node.nodeEpoch, + LogServiceDispatcherCount: uint32(logServiceDispatcherCount), // Report the manager-level dispatcher drain target so coordinator can // confirm both activation and clearing even when no maintainers exist. DispatcherDrainTargetNodeId: drainTarget.String(), diff --git a/maintainer/node_liveness_test.go b/maintainer/node_liveness_test.go index b35e056dec..da9f86ea64 100644 --- a/maintainer/node_liveness_test.go +++ b/maintainer/node_liveness_test.go @@ -26,6 +26,14 @@ import ( "github.com/stretchr/testify/require" ) +type mockLogServiceDispatcherCounter struct { + count int +} + +func (m *mockLogServiceDispatcherCounter) DispatcherCount() int { + return m.count +} + func TestSetNodeLivenessRejectEpochMismatch(t *testing.T) { mc := messaging.NewMockMessageCenter() appcontext.SetService(appcontext.MessageCenter, mc) @@ -159,6 +167,11 @@ func TestSetDispatcherDrainTargetRejectStaleUpdate(t *testing.T) { func TestSetDispatcherDrainTargetSendsNodeHeartbeatAck(t *testing.T) { mc := messaging.NewMockMessageCenter() appcontext.SetService(appcontext.MessageCenter, mc) + logService := &mockLogServiceDispatcherCounter{count: 2} + appcontext.SetService(appcontext.EventStore, logService) + t.Cleanup(func() { + logService.count = 0 + }) var nodeLiveness liveness.Liveness m := NewMaintainerManager(&node.Info{ID: node.ID("n1")}, &config.SchedulerConfig{}, &nodeLiveness) @@ -185,6 +198,7 @@ func TestSetDispatcherDrainTargetSendsNodeHeartbeatAck(t *testing.T) { hb := apply("n2", 1) require.Equal(t, "n2", hb.DispatcherDrainTargetNodeId) require.Equal(t, uint64(1), hb.DispatcherDrainTargetEpoch) + require.Equal(t, uint32(2), hb.LogServiceDispatcherCount) hb = apply("", 1) require.Equal(t, "", hb.DispatcherDrainTargetNodeId) diff --git a/pkg/eventservice/event_service_test.go b/pkg/eventservice/event_service_test.go index fab9ac0db6..42f1628a16 100644 --- a/pkg/eventservice/event_service_test.go +++ b/pkg/eventservice/event_service_test.go @@ -287,6 +287,15 @@ func (m *mockEventStore) Close(ctx context.Context) error { func (m *mockEventStore) UpdateDispatcherCheckpointTs(dispatcherID common.DispatcherID, gcTS uint64) { } +func (m *mockEventStore) DispatcherCount() int { + count := 0 + m.dispatcherMap.Range(func(_, _ any) bool { + count++ + return true + }) + return count +} + func (m *mockEventStore) UnregisterDispatcher(changefeedID common.ChangeFeedID, dispatcherID common.DispatcherID) { span, ok := m.dispatcherMap.Load(dispatcherID) if ok { From ec0628612e7a65bd7eb58a9fe37f22dc433f3c50 Mon Sep 17 00:00:00 2001 From: wk989898 Date: Wed, 12 Aug 2026 10:21:20 +0000 Subject: [PATCH 3/5] fmt Signed-off-by: wk989898 --- coordinator/controller_drain.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/coordinator/controller_drain.go b/coordinator/controller_drain.go index 0c0ef47e8e..677b4aa622 100644 --- a/coordinator/controller_drain.go +++ b/coordinator/controller_drain.go @@ -342,8 +342,7 @@ func (c *Controller) observeDrainNode(target node.ID, epoch uint64) drainNodeObs ) _, observation.drainingObserved, observation.stoppingObserved = c.drainController.GetStatus(target) - observation.logServiceDispatcherCount, observation.logServiceDispatcherCountObserved = - c.drainController.GetLogServiceDispatcherCount(target) + observation.logServiceDispatcherCount, observation.logServiceDispatcherCountObserved = c.drainController.GetLogServiceDispatcherCount(target) observation.nodeState = c.drainController.GetState(target) return observation } From f4adda14551f9a83d4468ee17dddbb172f668a9f Mon Sep 17 00:00:00 2001 From: wk989898 Date: Thu, 13 Aug 2026 07:33:08 +0000 Subject: [PATCH 4/5] update Signed-off-by: wk989898 --- .../eventcollector/event_collector.go | 35 +++++++++---- .../eventcollector/event_collector_test.go | 27 +++++++--- logservice/schemastore/schema_store.go | 4 ++ logservice/schemastore/schema_store_test.go | 32 ++++++++++++ maintainer/maintainer_manager_node.go | 8 +++ maintainer/node_liveness_test.go | 37 ++++++++++++++ pkg/common/event/dispatcher_heartbeat.go | 13 ++++- pkg/common/event/dispatcher_heartbeat_test.go | 14 ++---- pkg/config/capture.go | 11 ++-- pkg/eventservice/event_service.go | 18 +++++++ pkg/eventservice/event_service_test.go | 15 ++++++ pkg/messaging/message_center.go | 19 +++++-- pkg/messaging/mock_message_center.go | 18 ++++++- pkg/messaging/remote_target.go | 50 +++++++++++-------- pkg/messaging/target_test.go | 23 +++++++++ pkg/node/node.go | 33 +++++++----- pkg/node/node_test.go | 37 ++++++++++++++ server/server.go | 7 +-- server/server_prepare.go | 15 +++--- 19 files changed, 336 insertions(+), 80 deletions(-) create mode 100644 pkg/node/node_test.go diff --git a/downstreamadapter/eventcollector/event_collector.go b/downstreamadapter/eventcollector/event_collector.go index 6456f1ada2..03543c4fe5 100644 --- a/downstreamadapter/eventcollector/event_collector.go +++ b/downstreamadapter/eventcollector/event_collector.go @@ -415,7 +415,7 @@ func (c *EventCollector) groupHeartbeat() map[node.ID]*event.DispatcherHeartbeat group := func(target node.ID, dispatcherID common.DispatcherID, checkpointTs uint64, epoch uint64) { heartbeat, ok := groupedHeartbeats[target] if !ok { - heartbeat = event.NewDispatcherHeartbeat() + heartbeat = event.NewDispatcherHeartbeatWithVersion(c.eventServiceProtocolVersion(target)) groupedHeartbeats[target] = heartbeat } heartbeat.AddDispatcherProgress(dispatcherID, checkpointTs, epoch) @@ -439,6 +439,17 @@ func (c *EventCollector) groupHeartbeat() map[node.ID]*event.DispatcherHeartbeat return groupedHeartbeats } +func (c *EventCollector) eventServiceProtocolVersion(target node.ID) int { + if target == c.serverId { + return node.CurrentMessagingProtocolVersion + } + info := c.mc.GetNodeInfo(target) + if info != nil && info.MessagingProtocolVersion >= node.CurrentMessagingProtocolVersion { + return node.CurrentMessagingProtocolVersion + } + return node.LegacyMessagingProtocolVersion +} + func (c *EventCollector) processDSFeedback(ctx context.Context) error { log.Info("Start process feedback from dynamic stream") defer log.Info("Stop process feedback from dynamic stream") @@ -727,7 +738,8 @@ func (c *EventCollector) newCongestionControlMessages() map[node.ID]*event.Conge // build congestion control messages for each node result := make(map[node.ID]*event.CongestionControl) for nodeID, changefeedDispatchers := range nodeDispatcherMemory { - congestionControl := event.NewCongestionControlWithVersion(event.CongestionControlVersion2) + protocolVersion := c.eventServiceProtocolVersion(nodeID) + congestionControl := event.NewCongestionControlWithVersion(protocolVersion) for changefeedID, dispatcherMemory := range changefeedDispatchers { if len(dispatcherMemory) == 0 { @@ -739,13 +751,18 @@ func (c *EventCollector) newCongestionControlMessages() map[node.ID]*event.Conge if !ok { continue } - congestionControl.AddAvailableMemoryWithDispatchersAndUsageAndReleaseCount( - changefeedID.ID(), - totalAvailable, - changefeedUsageRatio[changefeedID], - dispatcherMemory, - getAndResetMemoryReleaseCount(changefeedID), - ) + if protocolVersion >= event.CongestionControlVersion2 { + congestionControl.AddAvailableMemoryWithDispatchersAndUsageAndReleaseCount( + changefeedID.ID(), + totalAvailable, + changefeedUsageRatio[changefeedID], + dispatcherMemory, + getAndResetMemoryReleaseCount(changefeedID), + ) + } else { + congestionControl.AddAvailableMemoryWithDispatchers( + changefeedID.ID(), totalAvailable, dispatcherMemory) + } } if len(congestionControl.GetAvailables()) > 0 { diff --git a/downstreamadapter/eventcollector/event_collector_test.go b/downstreamadapter/eventcollector/event_collector_test.go index 1d29b8568e..b508b21367 100644 --- a/downstreamadapter/eventcollector/event_collector_test.go +++ b/downstreamadapter/eventcollector/event_collector_test.go @@ -272,11 +272,8 @@ func TestRemoveLastDispatcher(t *testing.T) { } func TestGroupHeartbeatUsesEpochAndClamp(t *testing.T) { - ctx := context.Background() serverInfo := node.NewInfo("127.0.0.1:18300", "") - mc := messaging.NewMessageCenter(ctx, serverInfo.ID, config.NewDefaultMessageCenterConfig(serverInfo.AdvertiseAddr), nil) - mc.Run(ctx) - defer mc.Close() + mc := messaging.NewMockMessageCenter() appcontext.SetService(appcontext.MessageCenter, mc) c := New(serverInfo.ID) @@ -320,12 +317,28 @@ func TestGroupHeartbeatUsesEpochAndClamp(t *testing.T) { remoteHeartbeat := grouped[remoteID] require.NotNil(t, remoteHeartbeat) + require.Equal(t, commonEvent.DispatcherHeartbeatVersion1, remoteHeartbeat.Version) + require.Len(t, remoteHeartbeat.DispatcherProgressesLegacy, 1) + require.Equal(t, remoteDispatcher.id, remoteHeartbeat.DispatcherProgressesLegacy[0].DispatcherID) + require.Equal(t, uint64(210), remoteHeartbeat.DispatcherProgressesLegacy[0].CheckpointTs) + + controlMessages := c.newCongestionControlMessages() + require.Equal(t, commonEvent.CongestionControlVersion2, controlMessages[serverInfo.ID].GetVersion()) + require.Equal(t, commonEvent.CongestionControlVersion1, controlMessages[remoteID].GetVersion()) + + mc.OnNodeChanges(map[node.ID]*node.Info{ + remoteID: { + ID: remoteID, + MessagingProtocolVersion: node.CurrentMessagingProtocolVersion, + }, + }) + grouped = c.groupHeartbeat() + remoteHeartbeat = grouped[remoteID] require.Equal(t, commonEvent.DispatcherHeartbeatVersion2, remoteHeartbeat.Version) require.Len(t, remoteHeartbeat.DispatcherProgresses, 1) - require.Equal(t, uint8(commonEvent.DispatcherProgressVersion1), remoteHeartbeat.DispatcherProgresses[0].Version) - require.Equal(t, remoteDispatcher.id, remoteHeartbeat.DispatcherProgresses[0].DispatcherID) - require.Equal(t, uint64(210), remoteHeartbeat.DispatcherProgresses[0].CheckpointTs) require.Equal(t, uint64(5), remoteHeartbeat.DispatcherProgresses[0].Epoch) + controlMessages = c.newCongestionControlMessages() + require.Equal(t, commonEvent.CongestionControlVersion2, controlMessages[remoteID].GetVersion()) } func TestGroupHeartbeatResetThenHandshake(t *testing.T) { diff --git a/logservice/schemastore/schema_store.go b/logservice/schemastore/schema_store.go index 5273c93ca8..aaeccf2d58 100644 --- a/logservice/schemastore/schema_store.go +++ b/logservice/schemastore/schema_store.go @@ -74,6 +74,7 @@ type DDLEventState struct { type keyspaceSchemaStore struct { cancel context.CancelFunc + wg sync.WaitGroup ddlJobFetcher *ddlJobFetcher gcKeeper *schemaStoreGCKeeper pdClock pdutil.Clock @@ -349,6 +350,7 @@ func (s *schemaStore) Close(ctx context.Context) error { if store.cancel != nil { store.cancel() } + store.wg.Wait() if store.gcKeeper != nil { err := closeSchemaStoreGCKeeper(keyspaceID, store.gcKeeper) if err != nil { @@ -603,7 +605,9 @@ func (s *schemaStore) RegisterKeyspace( return store.resolvedTs.Load() }) + store.wg.Add(1) go func(ctx context.Context, schemaStore *keyspaceSchemaStore) { + defer schemaStore.wg.Done() ticker := time.NewTicker(50 * time.Millisecond) defer ticker.Stop() for { diff --git a/logservice/schemastore/schema_store_test.go b/logservice/schemastore/schema_store_test.go index 1c30d52f95..aacd1d3b31 100644 --- a/logservice/schemastore/schema_store_test.go +++ b/logservice/schemastore/schema_store_test.go @@ -152,6 +152,38 @@ func TestIgnoreDDLByCommitTs(t *testing.T) { require.NotContains(t, tableNames, "t2") } +func TestSchemaStoreCloseWaitsForWorkersBeforeClosingPebble(t *testing.T) { + pstorage := newPersistentStorageForTest(t.TempDir(), nil) + workerStarted := make(chan struct{}) + releaseWorker := make(chan struct{}) + store := &keyspaceSchemaStore{dataStorage: pstorage} + store.wg.Add(1) + go func() { + defer store.wg.Done() + close(workerStarted) + <-releaseWorker + }() + <-workerStarted + + schemaStore := &schemaStore{ + keyspaceSchemaStoreMap: map[uint32]*keyspaceSchemaStore{0: store}, + } + closeDone := make(chan error, 1) + go func() { + closeDone <- schemaStore.Close(context.Background()) + }() + + select { + case err := <-closeDone: + require.FailNow(t, "schema store closed before its worker exited", "error: %v", err) + case <-time.After(50 * time.Millisecond): + } + require.NoError(t, pstorage.db.Flush()) + + close(releaseWorker) + require.NoError(t, <-closeDone) +} + func TestTryUpdateResolvedTsRetryAfterDDLHandleFailure(t *testing.T) { mockPDClock := pdutil.NewClock4Test() appcontext.SetService(appcontext.DefaultPDClock, mockPDClock) diff --git a/maintainer/maintainer_manager_node.go b/maintainer/maintainer_manager_node.go index 3e368a474c..8b42600798 100644 --- a/maintainer/maintainer_manager_node.go +++ b/maintainer/maintainer_manager_node.go @@ -95,6 +95,14 @@ func (m *Manager) sendNodeHeartbeat(force bool) { // Update before sending so a transient send failure will not cause // frequent retries and log spam on the 200ms tick. m.node.lastNodeHeartbeatSentAt = now + // NodeHeartbeat uses an IO type that legacy coordinators do not recognize. + // Suppress it unless membership explicitly advertises current messaging + // support, so a new capture can safely bootstrap against an old coordinator. + coordinatorInfo := m.mc.GetNodeInfo(m.coordinatorID) + if coordinatorInfo == nil || + coordinatorInfo.MessagingProtocolVersion < node.CurrentMessagingProtocolVersion { + return + } currentLiveness := liveness.CaptureAlive if m.node.liveness != nil { diff --git a/maintainer/node_liveness_test.go b/maintainer/node_liveness_test.go index da9f86ea64..34097ecfa4 100644 --- a/maintainer/node_liveness_test.go +++ b/maintainer/node_liveness_test.go @@ -34,6 +34,17 @@ func (m *mockLogServiceDispatcherCounter) DispatcherCount() int { return m.count } +func registerCurrentCoordinator(mc interface { + OnNodeChanges(map[node.ID]*node.Info) +}, coordinatorID node.ID) { + mc.OnNodeChanges(map[node.ID]*node.Info{ + coordinatorID: { + ID: coordinatorID, + MessagingProtocolVersion: node.CurrentMessagingProtocolVersion, + }, + }) +} + func TestSetNodeLivenessRejectEpochMismatch(t *testing.T) { mc := messaging.NewMockMessageCenter() appcontext.SetService(appcontext.MessageCenter, mc) @@ -42,6 +53,7 @@ func TestSetNodeLivenessRejectEpochMismatch(t *testing.T) { m := NewMaintainerManager(&node.Info{ID: node.ID("n1")}, &config.SchedulerConfig{}, &nodeLiveness) m.coordinatorID = node.ID("coordinator") m.coordinatorVersion = 1 + registerCurrentCoordinator(mc, m.coordinatorID) req := &heartbeatpb.SetNodeLivenessRequest{ Target: heartbeatpb.NodeLiveness_DRAINING, @@ -68,6 +80,7 @@ func TestSetNodeLivenessApplyTransition(t *testing.T) { m := NewMaintainerManager(&node.Info{ID: node.ID("n1")}, &config.SchedulerConfig{}, &nodeLiveness) m.coordinatorID = node.ID("coordinator") m.coordinatorVersion = 1 + registerCurrentCoordinator(mc, m.coordinatorID) req := &heartbeatpb.SetNodeLivenessRequest{ Target: heartbeatpb.NodeLiveness_DRAINING, @@ -177,6 +190,7 @@ func TestSetDispatcherDrainTargetSendsNodeHeartbeatAck(t *testing.T) { m := NewMaintainerManager(&node.Info{ID: node.ID("n1")}, &config.SchedulerConfig{}, &nodeLiveness) m.coordinatorID = node.ID("coordinator") m.coordinatorVersion = 1 + registerCurrentCoordinator(mc, m.coordinatorID) apply := func(target string, epoch uint64) *heartbeatpb.NodeHeartbeat { msg := messaging.NewSingleTargetMessage( @@ -232,6 +246,29 @@ func TestCoordinatorBootstrapResponseIncludesDispatcherDrainTarget(t *testing.T) resp := out.Message[0].(*heartbeatpb.CoordinatorBootstrapResponse) require.Equal(t, "n2", resp.DispatcherDrainTargetNodeId) require.Equal(t, uint64(7), resp.DispatcherDrainTargetEpoch) + require.Empty(t, mc.GetMessageChannel()) +} + +func TestCoordinatorBootstrapSendsNodeHeartbeatToCurrentCoordinator(t *testing.T) { + mc := messaging.NewMockMessageCenter() + appcontext.SetService(appcontext.MessageCenter, mc) + coordinatorID := node.ID("coordinator") + registerCurrentCoordinator(mc, coordinatorID) + + var nodeLiveness liveness.Liveness + m := NewMaintainerManager(&node.Info{ID: node.ID("n1")}, &config.SchedulerConfig{}, &nodeLiveness) + req := messaging.NewSingleTargetMessage( + m.nodeInfo.ID, + messaging.MaintainerManagerTopic, + &heartbeatpb.CoordinatorBootstrapRequest{Version: 1}, + ) + req.From = coordinatorID + m.onCoordinatorBootstrapRequest(req) + + first := <-mc.GetMessageChannel() + second := <-mc.GetMessageChannel() + require.Equal(t, messaging.TypeCoordinatorBootstrapResponse, first.Type) + require.Equal(t, messaging.TypeNodeHeartbeatRequest, second.Type) } func TestAddMaintainerIgnoreInvalidConfig(t *testing.T) { diff --git a/pkg/common/event/dispatcher_heartbeat.go b/pkg/common/event/dispatcher_heartbeat.go index 793149bc85..0a9dd2ff52 100644 --- a/pkg/common/event/dispatcher_heartbeat.go +++ b/pkg/common/event/dispatcher_heartbeat.go @@ -107,8 +107,12 @@ type DispatcherHeartbeat struct { } func NewDispatcherHeartbeat() *DispatcherHeartbeat { + return NewDispatcherHeartbeatWithVersion(DispatcherHeartbeatVersion2) +} + +func NewDispatcherHeartbeatWithVersion(version int) *DispatcherHeartbeat { return &DispatcherHeartbeat{ - Version: DispatcherHeartbeatVersion2, + Version: version, // TODO: Pass a real clusterID when we support 1 TiCDC cluster subscribe multiple TiDB clusters ClusterID: 0, DispatcherProgressesLegacy: make([]DispatcherProgressLegacy, 0), @@ -118,6 +122,13 @@ func NewDispatcherHeartbeat() *DispatcherHeartbeat { func (d *DispatcherHeartbeat) AddDispatcherProgress(dispatcherID common.DispatcherID, checkpointTs uint64, epoch uint64) { d.DispatcherCount++ + if d.Version == DispatcherHeartbeatVersion1 { + d.DispatcherProgressesLegacy = append(d.DispatcherProgressesLegacy, DispatcherProgressLegacy{ + DispatcherID: dispatcherID, + CheckpointTs: checkpointTs, + }) + return + } d.DispatcherProgresses = append(d.DispatcherProgresses, DispatcherProgress{ Version: DispatcherProgressVersion1, DispatcherID: dispatcherID, diff --git a/pkg/common/event/dispatcher_heartbeat_test.go b/pkg/common/event/dispatcher_heartbeat_test.go index 649b707367..cf4811a0da 100644 --- a/pkg/common/event/dispatcher_heartbeat_test.go +++ b/pkg/common/event/dispatcher_heartbeat_test.go @@ -151,17 +151,9 @@ func TestDispatcherHeartbeatBackwardCompatibleV1(t *testing.T) { t.Parallel() dispatcherID := common.NewDispatcherID() - heartbeat := &DispatcherHeartbeat{ - Version: DispatcherHeartbeatVersion1, - ClusterID: 123, - DispatcherProgressesLegacy: []DispatcherProgressLegacy{ - { - DispatcherID: dispatcherID, - CheckpointTs: 456, - }, - }, - DispatcherCount: 1, - } + heartbeat := NewDispatcherHeartbeatWithVersion(DispatcherHeartbeatVersion1) + heartbeat.ClusterID = 123 + heartbeat.AddDispatcherProgress(dispatcherID, 456, 789) data, err := heartbeat.Marshal() require.NoError(t, err) diff --git a/pkg/config/capture.go b/pkg/config/capture.go index 65bd9d433f..a5114936f6 100644 --- a/pkg/config/capture.go +++ b/pkg/config/capture.go @@ -45,11 +45,12 @@ type CaptureInfo struct { ID CaptureID `json:"id"` AdvertiseAddr string `json:"address"` - Version string `json:"version"` - GitHash string `json:"git-hash"` - DeployPath string `json:"deploy-path"` - StartTimestamp int64 `json:"start-timestamp"` - IsNewArch bool `json:"is-new-arch"` + Version string `json:"version"` + GitHash string `json:"git-hash"` + DeployPath string `json:"deploy-path"` + StartTimestamp int64 `json:"start-timestamp"` + IsNewArch bool `json:"is-new-arch"` + MessagingProtocolVersion int `json:"messaging-protocol-version,omitempty"` } // Marshal using json.Marshal. diff --git a/pkg/eventservice/event_service.go b/pkg/eventservice/event_service.go index 5f4656742a..254b481cfb 100644 --- a/pkg/eventservice/event_service.go +++ b/pkg/eventservice/event_service.go @@ -15,6 +15,7 @@ package eventservice import ( "context" + "sync" "time" "github.com/pingcap/log" @@ -80,6 +81,9 @@ type eventService struct { // TODO: use a better way to cache the acceptorInfos dispatcherInfoChan chan DispatcherInfo dispatcherHeartbeat chan *DispatcherHeartBeatWithServerID + handlerMu sync.Mutex + handlerWG sync.WaitGroup + closing bool tz *time.Location } @@ -160,6 +164,11 @@ func (s *eventService) Run(ctx context.Context) error { func (s *eventService) Close(_ context.Context) error { log.Info("event service is closing") + s.handlerMu.Lock() + s.closing = true + s.handlerMu.Unlock() + s.mc.DeRegisterHandler(messaging.EventServiceTopic) + s.handlerWG.Wait() for _, c := range s.brokers { c.close() } @@ -168,6 +177,15 @@ func (s *eventService) Close(_ context.Context) error { } func (s *eventService) handleMessage(ctx context.Context, msg *messaging.TargetMessage) error { + s.handlerMu.Lock() + if s.closing { + s.handlerMu.Unlock() + return nil + } + s.handlerWG.Add(1) + s.handlerMu.Unlock() + defer s.handlerWG.Done() + switch msg.Type { case messaging.TypeDispatcherRequest: infos := msgToDispatcherInfo(msg) diff --git a/pkg/eventservice/event_service_test.go b/pkg/eventservice/event_service_test.go index 25864521b0..56b4ca1aed 100644 --- a/pkg/eventservice/event_service_test.go +++ b/pkg/eventservice/event_service_test.go @@ -201,6 +201,21 @@ func TestHandleMessageIgnoresInvalidSingleMessagePayloads(t *testing.T) { }) } +func TestHandleMessageIgnoredAfterClose(t *testing.T) { + mc := messaging.NewMockMessageCenter() + es := &eventService{ + mc: mc, + brokers: make(map[uint64]*eventBroker), + } + require.NoError(t, es.Close(context.Background())) + + heartbeat := commonEvent.NewDispatcherHeartbeat() + require.NoError(t, es.handleMessage(context.Background(), &messaging.TargetMessage{ + Type: messaging.TypeDispatcherHeartbeat, + Message: []messaging.IOTypeT{heartbeat}, + })) +} + var _ eventstore.EventStore = &mockEventStore{} // mockEventStore is a mock implementation of the EventStore interface diff --git a/pkg/messaging/message_center.go b/pkg/messaging/message_center.go index 0a11c31dab..0361ea945f 100644 --- a/pkg/messaging/message_center.go +++ b/pkg/messaging/message_center.go @@ -43,6 +43,7 @@ const ( type MessageCenter interface { MessageSender MessageReceiver + GetNodeInfo(node.ID) *node.Info // OnNodeChanges is called when the nodes in the cluster are changed. The message center should update the target list. OnNodeChanges(map[node.ID]*node.Info) Close() @@ -87,8 +88,11 @@ type messageCenter struct { m map[node.ID]*remoteMessageTarget } - remoteNodeInfos map[node.ID]*node.Info - notifyCh chan map[node.ID]*node.Info + nodeInfos struct { + sync.RWMutex + m map[node.ID]*node.Info + } + notifyCh chan map[node.ID]*node.Info grpcServer *grpc.Server router *router @@ -122,6 +126,7 @@ func NewMessageCenter( notifyCh: make(chan map[node.ID]*node.Info, 128), } mc.remoteTargets.m = make(map[node.ID]*remoteMessageTarget) + mc.nodeInfos.m = make(map[node.ID]*node.Info) log.Info("create message center success.", zap.Stringer("id", id), zap.String("addr", cfg.Addr)) @@ -195,6 +200,12 @@ func (mc *messageCenter) DeRegisterHandler(topic string) { mc.router.deRegisterHandler(topic) } +func (mc *messageCenter) GetNodeInfo(id node.ID) *node.Info { + mc.nodeInfos.RLock() + defer mc.nodeInfos.RUnlock() + return mc.nodeInfos.m[id] +} + func (mc *messageCenter) OnNodeChanges(activeNode map[node.ID]*node.Info) { mc.notifyCh <- activeNode log.Info("notify node changes", zap.Any("activeNode", activeNode)) @@ -205,7 +216,9 @@ func (mc *messageCenter) handleNodeChanges(activeNode map[node.ID]*node.Info) { for id, node := range activeNode { infosMap[id] = node } - mc.remoteNodeInfos = infosMap + mc.nodeInfos.Lock() + mc.nodeInfos.m = infosMap + mc.nodeInfos.Unlock() currentTargets := make(map[node.ID]bool) currentTargets[mc.id] = true diff --git a/pkg/messaging/mock_message_center.go b/pkg/messaging/mock_message_center.go index 6425224260..25436c66c8 100644 --- a/pkg/messaging/mock_message_center.go +++ b/pkg/messaging/mock_message_center.go @@ -13,13 +13,18 @@ package messaging -import "github.com/pingcap/ticdc/pkg/node" +import ( + "sync" + + "github.com/pingcap/ticdc/pkg/node" +) var _ MessageCenter = &mockMessageCenter{} // mockMessageCenter is a mock implementation of the MessageCenter interface type mockMessageCenter struct { messageCh chan *TargetMessage + nodeInfos sync.Map } func NewMockMessageCenter() *mockMessageCenter { @@ -33,6 +38,17 @@ func (m *mockMessageCenter) GetMessageChannel() chan *TargetMessage { } func (m *mockMessageCenter) OnNodeChanges(nodeInfos map[node.ID]*node.Info) { + for id, info := range nodeInfos { + m.nodeInfos.Store(id, info) + } +} + +func (m *mockMessageCenter) GetNodeInfo(id node.ID) *node.Info { + info, ok := m.nodeInfos.Load(id) + if !ok { + return nil + } + return info.(*node.Info) } func (m *mockMessageCenter) SendEvent(event *TargetMessage) error { diff --git a/pkg/messaging/remote_target.go b/pkg/messaging/remote_target.go index 852ebe6336..3a108f893b 100644 --- a/pkg/messaging/remote_target.go +++ b/pkg/messaging/remote_target.go @@ -619,31 +619,39 @@ func (s *remoteMessageTarget) handleIncomingMessage(ctx context.Context, stream return err } - mt := IOType(message.Type) - - targetMsg := &TargetMessage{ - From: node.ID(message.From), - To: node.ID(message.To), - Topic: message.Topic, - Type: mt, + targetMsg := s.decodeMessage(message) + if targetMsg != nil { + ch <- targetMsg } + } +} - for _, payload := range message.Payload { - msg, err := decodeIOType(mt, payload) - if err != nil { - log.Error("Failed to decode message", - zap.Error(err), - zap.Stringer("localID", s.messageCenterID), - zap.String("localAddr", s.localAddr), - zap.Stringer("remoteID", s.targetId), - zap.String("remoteAddr", s.targetAddr)) - continue - } - targetMsg.Message = append(targetMsg.Message, msg) - } +func (s *remoteMessageTarget) decodeMessage(message *proto.Message) *TargetMessage { + mt := IOType(message.Type) + targetMsg := &TargetMessage{ + From: node.ID(message.From), + To: node.ID(message.To), + Topic: message.Topic, + Type: mt, + } - ch <- targetMsg + for _, payload := range message.Payload { + msg, err := decodeIOType(mt, payload) + if err != nil { + log.Error("Failed to decode message", + zap.Error(err), + zap.Stringer("localID", s.messageCenterID), + zap.String("localAddr", s.localAddr), + zap.Stringer("remoteID", s.targetId), + zap.String("remoteAddr", s.targetAddr)) + continue + } + targetMsg.Message = append(targetMsg.Message, msg) + } + if len(targetMsg.Message) == 0 { + return nil } + return targetMsg } // Create a new protocol message from target messages diff --git a/pkg/messaging/target_test.go b/pkg/messaging/target_test.go index 6cffb6b114..7b76e3fbc5 100644 --- a/pkg/messaging/target_test.go +++ b/pkg/messaging/target_test.go @@ -18,7 +18,9 @@ import ( "testing" "github.com/pingcap/log" + commonEvent "github.com/pingcap/ticdc/pkg/common/event" "github.com/pingcap/ticdc/pkg/config" + "github.com/pingcap/ticdc/pkg/messaging/proto" "github.com/pingcap/ticdc/pkg/node" "github.com/stretchr/testify/require" "go.uber.org/zap" @@ -34,6 +36,27 @@ func newRemoteMessageTargetForTest() *remoteMessageTarget { return rt } +func TestDecodeMessageDropsEmptyDecodedPayload(t *testing.T) { + rt := newRemoteMessageTargetForTest() + defer rt.close() + + received := rt.decodeMessage(&proto.Message{ + Type: int32(TypeDispatcherHeartbeat), + Payload: [][]byte{{0}}, + }) + require.Nil(t, received) + + heartbeat := commonEvent.NewDispatcherHeartbeat() + payload, err := heartbeat.Marshal() + require.NoError(t, err) + received = rt.decodeMessage(&proto.Message{ + Type: int32(TypeDispatcherHeartbeat), + Payload: [][]byte{payload}, + }) + require.NotNil(t, received) + require.Len(t, received.Message, 1) +} + func TestRemoteTargetNewMessage(t *testing.T) { rt := newRemoteMessageTargetForTest() defer rt.close() diff --git a/pkg/node/node.go b/pkg/node/node.go index 148c87f926..a4e8a66289 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -26,6 +26,11 @@ import ( type ID string +const ( + LegacyMessagingProtocolVersion = 1 + CurrentMessagingProtocolVersion = 2 +) + func (s ID) String() string { return string(s) } @@ -52,18 +57,21 @@ type Info struct { DeployPath string `json:"deploy-path"` StartTimestamp int64 `json:"start-timestamp"` + MessagingProtocolVersion int `json:"messaging-protocol-version,omitempty"` + // Epoch represents how many times the node has been restarted. Epoch uint64 `json:"epoch"` } func NewInfo(addr string, deployPath string) *Info { return &Info{ - ID: NewID(), - AdvertiseAddr: addr, - Version: version.ReleaseVersion, - GitHash: version.GitHash, - DeployPath: deployPath, - StartTimestamp: time.Now().Unix(), + ID: NewID(), + AdvertiseAddr: addr, + Version: version.ReleaseVersion, + GitHash: version.GitHash, + DeployPath: deployPath, + StartTimestamp: time.Now().Unix(), + MessagingProtocolVersion: CurrentMessagingProtocolVersion, } } @@ -91,12 +99,13 @@ func (c *Info) Unmarshal(data []byte) error { func CaptureInfoToNodeInfo(captureInfo *config.CaptureInfo) *Info { return &Info{ - ID: ID(captureInfo.ID), - AdvertiseAddr: captureInfo.AdvertiseAddr, - Version: captureInfo.Version, - GitHash: captureInfo.GitHash, - DeployPath: captureInfo.DeployPath, - StartTimestamp: captureInfo.StartTimestamp, + ID: ID(captureInfo.ID), + AdvertiseAddr: captureInfo.AdvertiseAddr, + Version: captureInfo.Version, + GitHash: captureInfo.GitHash, + DeployPath: captureInfo.DeployPath, + StartTimestamp: captureInfo.StartTimestamp, + MessagingProtocolVersion: captureInfo.MessagingProtocolVersion, } } diff --git a/pkg/node/node_test.go b/pkg/node/node_test.go new file mode 100644 index 0000000000..f3d1db73bb --- /dev/null +++ b/pkg/node/node_test.go @@ -0,0 +1,37 @@ +// Copyright 2026 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package node + +import ( + "testing" + + "github.com/pingcap/ticdc/pkg/config" + "github.com/stretchr/testify/require" +) + +func TestMessagingProtocolVersion(t *testing.T) { + current := NewInfo("127.0.0.1:8300", "") + require.Equal(t, CurrentMessagingProtocolVersion, current.MessagingProtocolVersion) + + legacy := CaptureInfoToNodeInfo(&config.CaptureInfo{ + ID: config.CaptureID("legacy-node"), + }) + require.Zero(t, legacy.MessagingProtocolVersion) + + capable := CaptureInfoToNodeInfo(&config.CaptureInfo{ + ID: config.CaptureID("capable-node"), + MessagingProtocolVersion: CurrentMessagingProtocolVersion, + }) + require.Equal(t, CurrentMessagingProtocolVersion, capable.MessagingProtocolVersion) +} diff --git a/server/server.go b/server/server.go index 8a604d35cc..eb61e1d50e 100644 --- a/server/server.go +++ b/server/server.go @@ -645,9 +645,10 @@ func (c *server) GetCoordinatorInfo(ctx context.Context) (*node.Info, error) { ID: node.ID(captureInfo.ID), AdvertiseAddr: captureInfo.AdvertiseAddr, - Version: captureInfo.Version, - DeployPath: captureInfo.DeployPath, - StartTimestamp: captureInfo.StartTimestamp, + Version: captureInfo.Version, + DeployPath: captureInfo.DeployPath, + StartTimestamp: captureInfo.StartTimestamp, + MessagingProtocolVersion: captureInfo.MessagingProtocolVersion, // Epoch is now not used in TiCDC, so we just set it to 0. Epoch: 0, diff --git a/server/server_prepare.go b/server/server_prepare.go index 60621e4dc7..60f46fd15b 100644 --- a/server/server_prepare.go +++ b/server/server_prepare.go @@ -201,13 +201,14 @@ func (c *server) setUpDir() { // registerNodeToEtcd the server by put the server's information in etcd func (c *server) registerNodeToEtcd(ctx context.Context) error { cInfo := &config.CaptureInfo{ - ID: config.CaptureID(c.info.ID), - AdvertiseAddr: c.info.AdvertiseAddr, - Version: c.info.Version, - GitHash: c.info.GitHash, - DeployPath: c.info.DeployPath, - StartTimestamp: c.info.StartTimestamp, - IsNewArch: true, + ID: config.CaptureID(c.info.ID), + AdvertiseAddr: c.info.AdvertiseAddr, + Version: c.info.Version, + GitHash: c.info.GitHash, + DeployPath: c.info.DeployPath, + StartTimestamp: c.info.StartTimestamp, + IsNewArch: true, + MessagingProtocolVersion: c.info.MessagingProtocolVersion, } err := c.EtcdClient.PutCaptureInfo(ctx, cInfo, c.session.Lease()) if err != nil { From de8b863e37d74237d108c8de28f1accb858ac64a Mon Sep 17 00:00:00 2001 From: wk989898 Date: Thu, 13 Aug 2026 08:57:14 +0000 Subject: [PATCH 5/5] update Signed-off-by: wk989898 --- coordinator/controller.go | 1 + coordinator/controller_drain.go | 12 +++++- coordinator/controller_drain_test.go | 22 +++++++++++ .../eventcollector/event_collector.go | 14 ++++--- .../eventcollector/event_collector_test.go | 5 ++- heartbeatpb/drain_protocol.go | 11 +++++- maintainer/maintainer_manager_node.go | 9 +++-- maintainer/node_liveness_test.go | 18 +++++---- pkg/config/capture.go | 11 +++--- pkg/node/node.go | 33 ++++++----------- pkg/node/node_test.go | 37 ------------------- server/server.go | 7 ++-- server/server_prepare.go | 15 ++++---- 13 files changed, 97 insertions(+), 98 deletions(-) delete mode 100644 pkg/node/node_test.go diff --git a/coordinator/controller.go b/coordinator/controller.go index 682e7e995f..be254f7654 100644 --- a/coordinator/controller.go +++ b/coordinator/controller.go @@ -1165,6 +1165,7 @@ func (c *Controller) RemoveNode(id node.ID) { observation.remaining, observation.logServiceDispatcherCount, observation.logServiceDispatcherCountObserved, + observation.logServiceDispatcherCountRequired, ) } diff --git a/coordinator/controller_drain.go b/coordinator/controller_drain.go index 677b4aa622..8cfbd01bfa 100644 --- a/coordinator/controller_drain.go +++ b/coordinator/controller_drain.go @@ -207,6 +207,7 @@ func (c *Controller) DrainNode(ctx context.Context, target node.ID) (int, error) observation.remaining, observation.logServiceDispatcherCount, observation.logServiceDispatcherCountObserved, + observation.logServiceDispatcherCountRequired, ) if completionObserved { @@ -229,6 +230,7 @@ func (c *Controller) DrainNode(ctx context.Context, target node.ID) (int, error) zap.Int("pendingStatusCount", observation.pendingStatusCount), zap.Uint32("logServiceDispatcherCount", observation.logServiceDispatcherCount), zap.Bool("logServiceDispatcherCountObserved", observation.logServiceDispatcherCountObserved), + zap.Bool("logServiceDispatcherCountRequired", observation.logServiceDispatcherCountRequired), zap.Int("remaining", observation.remaining)) return ensureDrainRemainingNonZero(observation.remaining), nil } @@ -246,6 +248,7 @@ func (c *Controller) observeRemovedActiveDrainTarget(target node.ID, epoch uint6 observation.remaining, observation.logServiceDispatcherCount, observation.logServiceDispatcherCountObserved, + observation.logServiceDispatcherCountRequired, ) if completionObserved { log.Info("drain completion observed for removed active target", @@ -324,6 +327,7 @@ type drainNodeObservation struct { stoppingObserved bool logServiceDispatcherCount uint32 logServiceDispatcherCountObserved bool + logServiceDispatcherCountRequired bool } func (c *Controller) observeDrainNode(target node.ID, epoch uint64) drainNodeObservation { @@ -343,6 +347,9 @@ func (c *Controller) observeDrainNode(target node.ID, epoch uint64) drainNodeObs _, observation.drainingObserved, observation.stoppingObserved = c.drainController.GetStatus(target) observation.logServiceDispatcherCount, observation.logServiceDispatcherCountObserved = c.drainController.GetLogServiceDispatcherCount(target) + drainProtocolVersion, drainProtocolObserved := c.drainController.GetDrainProtocolVersion(target) + observation.logServiceDispatcherCountRequired = !drainProtocolObserved || + heartbeatpb.SupportsLogServiceDispatcherCount(drainProtocolVersion) observation.nodeState = c.drainController.GetState(target) return observation } @@ -1095,14 +1102,15 @@ func isBestEffortDrainComplete( remaining int, logServiceDispatcherCount uint32, logServiceDispatcherCountObserved bool, + logServiceDispatcherCountRequired bool, ) bool { if nodeState == drain.StateUnknown || !drainingObserved { return false } return stoppingObserved && remaining == 0 && - logServiceDispatcherCountObserved && - logServiceDispatcherCount == 0 + (!logServiceDispatcherCountRequired || + logServiceDispatcherCountObserved && logServiceDispatcherCount == 0) } // drainRemainingEstimate uses the larger workload dimension to avoid obvious double counting. diff --git a/coordinator/controller_drain_test.go b/coordinator/controller_drain_test.go index b9f84c7b97..be451e45c3 100644 --- a/coordinator/controller_drain_test.go +++ b/coordinator/controller_drain_test.go @@ -187,6 +187,28 @@ func TestDrainNodeWaitsForLogServiceDispatchers(t *testing.T) { require.Equal(t, 0, remaining) } +func TestDrainNodeVersion1IgnoresMissingLogServiceDispatcherCount(t *testing.T) { + c, drainController, target := newDrainTestController(t) + setDrainProtocolVersion(c, target, heartbeatpb.DrainProtocolVersion1) + cf := addRunningChangefeed(c, "cf1", node.ID("other"), 100) + + remaining, err := c.DrainNode(context.Background(), target) + require.NoError(t, err) + require.Equal(t, 1, remaining) + + _, epoch, ok := c.getDispatcherDrainTarget() + require.True(t, ok) + setChangefeedDrainStatus(cf, target, epoch, 0, 0) + drainController.ObserveSetNodeLivenessResponse(target, &heartbeatpb.SetNodeLivenessResponse{ + Applied: heartbeatpb.NodeLiveness_STOPPING, + NodeEpoch: 1, + }) + + remaining, err = c.DrainNode(context.Background(), target) + require.NoError(t, err) + require.Equal(t, 0, remaining) +} + func TestDrainNodeDispatcherCountBlocksCompletion(t *testing.T) { c, drainController, target := newDrainTestController(t) setDrainProtocolVersion(c, target, heartbeatpb.CurrentDrainProtocolVersion) diff --git a/downstreamadapter/eventcollector/event_collector.go b/downstreamadapter/eventcollector/event_collector.go index 03543c4fe5..42fac0667d 100644 --- a/downstreamadapter/eventcollector/event_collector.go +++ b/downstreamadapter/eventcollector/event_collector.go @@ -441,13 +441,17 @@ func (c *EventCollector) groupHeartbeat() map[node.ID]*event.DispatcherHeartbeat func (c *EventCollector) eventServiceProtocolVersion(target node.ID) int { if target == c.serverId { - return node.CurrentMessagingProtocolVersion + return event.DispatcherHeartbeatVersion2 } info := c.mc.GetNodeInfo(target) - if info != nil && info.MessagingProtocolVersion >= node.CurrentMessagingProtocolVersion { - return node.CurrentMessagingProtocolVersion - } - return node.LegacyMessagingProtocolVersion + localInfo := c.mc.GetNodeInfo(c.serverId) + // A matching non-empty Git hash proves both endpoints run the same message + // implementation without introducing a separate capability in membership. + // During a rolling upgrade, conservatively use the legacy wire format. + if info != nil && localInfo != nil && localInfo.GitHash != "" && info.GitHash == localInfo.GitHash { + return event.DispatcherHeartbeatVersion2 + } + return event.DispatcherHeartbeatVersion1 } func (c *EventCollector) processDSFeedback(ctx context.Context) error { diff --git a/downstreamadapter/eventcollector/event_collector_test.go b/downstreamadapter/eventcollector/event_collector_test.go index b508b21367..1691d759d1 100644 --- a/downstreamadapter/eventcollector/event_collector_test.go +++ b/downstreamadapter/eventcollector/event_collector_test.go @@ -274,6 +274,7 @@ func TestRemoveLastDispatcher(t *testing.T) { func TestGroupHeartbeatUsesEpochAndClamp(t *testing.T) { serverInfo := node.NewInfo("127.0.0.1:18300", "") mc := messaging.NewMockMessageCenter() + mc.OnNodeChanges(map[node.ID]*node.Info{serverInfo.ID: serverInfo}) appcontext.SetService(appcontext.MessageCenter, mc) c := New(serverInfo.ID) @@ -328,8 +329,8 @@ func TestGroupHeartbeatUsesEpochAndClamp(t *testing.T) { mc.OnNodeChanges(map[node.ID]*node.Info{ remoteID: { - ID: remoteID, - MessagingProtocolVersion: node.CurrentMessagingProtocolVersion, + ID: remoteID, + GitHash: serverInfo.GitHash, }, }) grouped = c.groupHeartbeat() diff --git a/heartbeatpb/drain_protocol.go b/heartbeatpb/drain_protocol.go index cd1b0b0143..74b7c3b8b5 100644 --- a/heartbeatpb/drain_protocol.go +++ b/heartbeatpb/drain_protocol.go @@ -16,11 +16,20 @@ package heartbeatpb const ( // LegacyDrainProtocolVersion means the node only supports legacy hard-restart drain. LegacyDrainProtocolVersion uint32 = 0 + // DrainProtocolVersion1 supports coordinator-driven drain but does not report + // the log service dispatcher count in node heartbeats. + DrainProtocolVersion1 uint32 = 1 // CurrentDrainProtocolVersion is the coordinator-driven drain protocol version. - CurrentDrainProtocolVersion uint32 = 1 + CurrentDrainProtocolVersion uint32 = 2 ) // SupportsCoordinatorDrivenDrain returns whether the node can run coordinator-driven drain. func SupportsCoordinatorDrivenDrain(version uint32) bool { return version != LegacyDrainProtocolVersion } + +// SupportsLogServiceDispatcherCount reports whether STOPPING heartbeats include +// the log service dispatcher count used as a drain-completion gate. +func SupportsLogServiceDispatcherCount(version uint32) bool { + return version >= CurrentDrainProtocolVersion +} diff --git a/maintainer/maintainer_manager_node.go b/maintainer/maintainer_manager_node.go index 8b42600798..f6b3cb8f2e 100644 --- a/maintainer/maintainer_manager_node.go +++ b/maintainer/maintainer_manager_node.go @@ -96,11 +96,12 @@ func (m *Manager) sendNodeHeartbeat(force bool) { // frequent retries and log spam on the 200ms tick. m.node.lastNodeHeartbeatSentAt = now // NodeHeartbeat uses an IO type that legacy coordinators do not recognize. - // Suppress it unless membership explicitly advertises current messaging - // support, so a new capture can safely bootstrap against an old coordinator. + // A matching non-empty Git hash proves the coordinator runs the same message + // implementation. During a rolling upgrade, suppress this new IO type until + // the coordinator has been upgraded to the same build. coordinatorInfo := m.mc.GetNodeInfo(m.coordinatorID) - if coordinatorInfo == nil || - coordinatorInfo.MessagingProtocolVersion < node.CurrentMessagingProtocolVersion { + if coordinatorInfo == nil || m.nodeInfo.GitHash == "" || + coordinatorInfo.GitHash != m.nodeInfo.GitHash { return } diff --git a/maintainer/node_liveness_test.go b/maintainer/node_liveness_test.go index 34097ecfa4..4dcca8e194 100644 --- a/maintainer/node_liveness_test.go +++ b/maintainer/node_liveness_test.go @@ -36,11 +36,12 @@ func (m *mockLogServiceDispatcherCounter) DispatcherCount() int { func registerCurrentCoordinator(mc interface { OnNodeChanges(map[node.ID]*node.Info) -}, coordinatorID node.ID) { +}, manager *Manager) { + manager.nodeInfo.GitHash = "current-build" mc.OnNodeChanges(map[node.ID]*node.Info{ - coordinatorID: { - ID: coordinatorID, - MessagingProtocolVersion: node.CurrentMessagingProtocolVersion, + manager.coordinatorID: { + ID: manager.coordinatorID, + GitHash: manager.nodeInfo.GitHash, }, }) } @@ -53,7 +54,7 @@ func TestSetNodeLivenessRejectEpochMismatch(t *testing.T) { m := NewMaintainerManager(&node.Info{ID: node.ID("n1")}, &config.SchedulerConfig{}, &nodeLiveness) m.coordinatorID = node.ID("coordinator") m.coordinatorVersion = 1 - registerCurrentCoordinator(mc, m.coordinatorID) + registerCurrentCoordinator(mc, m) req := &heartbeatpb.SetNodeLivenessRequest{ Target: heartbeatpb.NodeLiveness_DRAINING, @@ -80,7 +81,7 @@ func TestSetNodeLivenessApplyTransition(t *testing.T) { m := NewMaintainerManager(&node.Info{ID: node.ID("n1")}, &config.SchedulerConfig{}, &nodeLiveness) m.coordinatorID = node.ID("coordinator") m.coordinatorVersion = 1 - registerCurrentCoordinator(mc, m.coordinatorID) + registerCurrentCoordinator(mc, m) req := &heartbeatpb.SetNodeLivenessRequest{ Target: heartbeatpb.NodeLiveness_DRAINING, @@ -190,7 +191,7 @@ func TestSetDispatcherDrainTargetSendsNodeHeartbeatAck(t *testing.T) { m := NewMaintainerManager(&node.Info{ID: node.ID("n1")}, &config.SchedulerConfig{}, &nodeLiveness) m.coordinatorID = node.ID("coordinator") m.coordinatorVersion = 1 - registerCurrentCoordinator(mc, m.coordinatorID) + registerCurrentCoordinator(mc, m) apply := func(target string, epoch uint64) *heartbeatpb.NodeHeartbeat { msg := messaging.NewSingleTargetMessage( @@ -253,10 +254,11 @@ func TestCoordinatorBootstrapSendsNodeHeartbeatToCurrentCoordinator(t *testing.T mc := messaging.NewMockMessageCenter() appcontext.SetService(appcontext.MessageCenter, mc) coordinatorID := node.ID("coordinator") - registerCurrentCoordinator(mc, coordinatorID) var nodeLiveness liveness.Liveness m := NewMaintainerManager(&node.Info{ID: node.ID("n1")}, &config.SchedulerConfig{}, &nodeLiveness) + m.coordinatorID = coordinatorID + registerCurrentCoordinator(mc, m) req := messaging.NewSingleTargetMessage( m.nodeInfo.ID, messaging.MaintainerManagerTopic, diff --git a/pkg/config/capture.go b/pkg/config/capture.go index a5114936f6..65bd9d433f 100644 --- a/pkg/config/capture.go +++ b/pkg/config/capture.go @@ -45,12 +45,11 @@ type CaptureInfo struct { ID CaptureID `json:"id"` AdvertiseAddr string `json:"address"` - Version string `json:"version"` - GitHash string `json:"git-hash"` - DeployPath string `json:"deploy-path"` - StartTimestamp int64 `json:"start-timestamp"` - IsNewArch bool `json:"is-new-arch"` - MessagingProtocolVersion int `json:"messaging-protocol-version,omitempty"` + Version string `json:"version"` + GitHash string `json:"git-hash"` + DeployPath string `json:"deploy-path"` + StartTimestamp int64 `json:"start-timestamp"` + IsNewArch bool `json:"is-new-arch"` } // Marshal using json.Marshal. diff --git a/pkg/node/node.go b/pkg/node/node.go index a4e8a66289..148c87f926 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -26,11 +26,6 @@ import ( type ID string -const ( - LegacyMessagingProtocolVersion = 1 - CurrentMessagingProtocolVersion = 2 -) - func (s ID) String() string { return string(s) } @@ -57,21 +52,18 @@ type Info struct { DeployPath string `json:"deploy-path"` StartTimestamp int64 `json:"start-timestamp"` - MessagingProtocolVersion int `json:"messaging-protocol-version,omitempty"` - // Epoch represents how many times the node has been restarted. Epoch uint64 `json:"epoch"` } func NewInfo(addr string, deployPath string) *Info { return &Info{ - ID: NewID(), - AdvertiseAddr: addr, - Version: version.ReleaseVersion, - GitHash: version.GitHash, - DeployPath: deployPath, - StartTimestamp: time.Now().Unix(), - MessagingProtocolVersion: CurrentMessagingProtocolVersion, + ID: NewID(), + AdvertiseAddr: addr, + Version: version.ReleaseVersion, + GitHash: version.GitHash, + DeployPath: deployPath, + StartTimestamp: time.Now().Unix(), } } @@ -99,13 +91,12 @@ func (c *Info) Unmarshal(data []byte) error { func CaptureInfoToNodeInfo(captureInfo *config.CaptureInfo) *Info { return &Info{ - ID: ID(captureInfo.ID), - AdvertiseAddr: captureInfo.AdvertiseAddr, - Version: captureInfo.Version, - GitHash: captureInfo.GitHash, - DeployPath: captureInfo.DeployPath, - StartTimestamp: captureInfo.StartTimestamp, - MessagingProtocolVersion: captureInfo.MessagingProtocolVersion, + ID: ID(captureInfo.ID), + AdvertiseAddr: captureInfo.AdvertiseAddr, + Version: captureInfo.Version, + GitHash: captureInfo.GitHash, + DeployPath: captureInfo.DeployPath, + StartTimestamp: captureInfo.StartTimestamp, } } diff --git a/pkg/node/node_test.go b/pkg/node/node_test.go deleted file mode 100644 index f3d1db73bb..0000000000 --- a/pkg/node/node_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2026 PingCAP, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// See the License for the specific language governing permissions and -// limitations under the License. - -package node - -import ( - "testing" - - "github.com/pingcap/ticdc/pkg/config" - "github.com/stretchr/testify/require" -) - -func TestMessagingProtocolVersion(t *testing.T) { - current := NewInfo("127.0.0.1:8300", "") - require.Equal(t, CurrentMessagingProtocolVersion, current.MessagingProtocolVersion) - - legacy := CaptureInfoToNodeInfo(&config.CaptureInfo{ - ID: config.CaptureID("legacy-node"), - }) - require.Zero(t, legacy.MessagingProtocolVersion) - - capable := CaptureInfoToNodeInfo(&config.CaptureInfo{ - ID: config.CaptureID("capable-node"), - MessagingProtocolVersion: CurrentMessagingProtocolVersion, - }) - require.Equal(t, CurrentMessagingProtocolVersion, capable.MessagingProtocolVersion) -} diff --git a/server/server.go b/server/server.go index eb61e1d50e..8a604d35cc 100644 --- a/server/server.go +++ b/server/server.go @@ -645,10 +645,9 @@ func (c *server) GetCoordinatorInfo(ctx context.Context) (*node.Info, error) { ID: node.ID(captureInfo.ID), AdvertiseAddr: captureInfo.AdvertiseAddr, - Version: captureInfo.Version, - DeployPath: captureInfo.DeployPath, - StartTimestamp: captureInfo.StartTimestamp, - MessagingProtocolVersion: captureInfo.MessagingProtocolVersion, + Version: captureInfo.Version, + DeployPath: captureInfo.DeployPath, + StartTimestamp: captureInfo.StartTimestamp, // Epoch is now not used in TiCDC, so we just set it to 0. Epoch: 0, diff --git a/server/server_prepare.go b/server/server_prepare.go index 60f46fd15b..60621e4dc7 100644 --- a/server/server_prepare.go +++ b/server/server_prepare.go @@ -201,14 +201,13 @@ func (c *server) setUpDir() { // registerNodeToEtcd the server by put the server's information in etcd func (c *server) registerNodeToEtcd(ctx context.Context) error { cInfo := &config.CaptureInfo{ - ID: config.CaptureID(c.info.ID), - AdvertiseAddr: c.info.AdvertiseAddr, - Version: c.info.Version, - GitHash: c.info.GitHash, - DeployPath: c.info.DeployPath, - StartTimestamp: c.info.StartTimestamp, - IsNewArch: true, - MessagingProtocolVersion: c.info.MessagingProtocolVersion, + ID: config.CaptureID(c.info.ID), + AdvertiseAddr: c.info.AdvertiseAddr, + Version: c.info.Version, + GitHash: c.info.GitHash, + DeployPath: c.info.DeployPath, + StartTimestamp: c.info.StartTimestamp, + IsNewArch: true, } err := c.EtcdClient.PutCaptureInfo(ctx, cInfo, c.session.Lease()) if err != nil {