@@ -35,6 +35,11 @@ import (
3535 "go.uber.org/zap/zapcore"
3636)
3737
38+ const (
39+ outdatedActionDoneReportInterval = time .Second
40+ outdatedActionDoneReportMaxEntries = 256
41+ )
42+
3843// DispatcherService defines the interface for providing dispatcher information and basic event handling.
3944type DispatcherService interface {
4045 GetId () common.DispatcherID
@@ -217,6 +222,11 @@ type BasicDispatcher struct {
217222
218223 BootstrapState bootstrapState
219224
225+ // outdatedActionDoneAt throttles DONE reports for the same outdated action to avoid
226+ // flooding blockStatusesChan when maintainer resends stale actions repeatedly.
227+ outdatedActionDoneMu sync.Mutex
228+ outdatedActionDoneAt map [BlockEventIdentifier ]time.Time
229+
220230 // tableModeCompatibilityChecked indicates whether we have already validated the newest
221231 // table schema is compatible with the current replication mode configuration.
222232 // Only when the initial case or a ddl event is received, we will reset tableModeCompatibilityChecked to check the compatibility.
@@ -256,12 +266,39 @@ func NewBasicDispatcher(
256266 creationPDTs : currentPDTs ,
257267 mode : mode ,
258268 BootstrapState : BootstrapFinished ,
269+ outdatedActionDoneAt : make (map [BlockEventIdentifier ]time.Time ),
259270 }
260271 dispatcher .resolvedTs .Store (startTs )
261272
262273 return dispatcher
263274}
264275
276+ func (d * BasicDispatcher ) shouldReportOutdatedActionDone (identifier BlockEventIdentifier , now time.Time ) bool {
277+ d .outdatedActionDoneMu .Lock ()
278+ defer d .outdatedActionDoneMu .Unlock ()
279+
280+ if last , ok := d .outdatedActionDoneAt [identifier ]; ok && now .Sub (last ) < outdatedActionDoneReportInterval {
281+ return false
282+ }
283+
284+ if len (d .outdatedActionDoneAt ) >= outdatedActionDoneReportMaxEntries {
285+ expireBefore := now .Add (- 2 * outdatedActionDoneReportInterval )
286+ for id , ts := range d .outdatedActionDoneAt {
287+ if ts .Before (expireBefore ) {
288+ delete (d .outdatedActionDoneAt , id )
289+ }
290+ }
291+ if len (d .outdatedActionDoneAt ) >= outdatedActionDoneReportMaxEntries {
292+ for id := range d .outdatedActionDoneAt {
293+ delete (d .outdatedActionDoneAt , id )
294+ }
295+ }
296+ }
297+
298+ d .outdatedActionDoneAt [identifier ] = now
299+ return true
300+ }
301+
265302// AddDMLEventsToSink filters events for special tables, registers batch wake
266303// callbacks, and returns true when at least one event remains to be written to
267304// the downstream sink.
@@ -836,6 +873,13 @@ func (d *BasicDispatcher) HandleDispatcherStatus(dispatcherStatus *heartbeatpb.D
836873 }
837874
838875 // Step3: whether the outdate message or not, we need to return message show we have finished the event.
876+ identifier := BlockEventIdentifier {
877+ CommitTs : action .CommitTs ,
878+ IsSyncPoint : action .IsSyncPoint ,
879+ }
880+ if ! d .shouldReportOutdatedActionDone (identifier , time .Now ()) {
881+ return false
882+ }
839883 d .sharedInfo .blockStatusesChan <- & heartbeatpb.TableSpanBlockStatus {
840884 ID : d .id .ToPB (),
841885 State : & heartbeatpb.State {
0 commit comments