github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/pkg/p2p/message_handler_manager.go (about) 1 // Copyright 2022 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package p2p 15 16 import ( 17 "context" 18 "sync" 19 "time" 20 21 "github.com/pingcap/log" 22 "github.com/pingcap/tiflow/pkg/errors" 23 "go.uber.org/atomic" 24 "go.uber.org/zap" 25 ) 26 27 const ( 28 defaultHandlerOperationTimeout = 3 * time.Second 29 ) 30 31 // MessageHandlerManager is for managing message topic handlers. 32 // NOTE: for each topic, only one handler is allowed. 33 type MessageHandlerManager interface { 34 RegisterHandler(ctx context.Context, topic Topic, tpi TypeInformation, fn HandlerFunc) (bool, error) 35 UnregisterHandler(ctx context.Context, topic Topic) (bool, error) 36 CheckError(ctx context.Context) error 37 38 // Clean unregisters all existing handlers. 39 Clean(ctx context.Context) error 40 41 // SetTimeout sets the timeout for handler operations. 42 // A timeout is needed because the underlying handler operations are 43 // asynchronous in the MessageServer. 44 SetTimeout(timeout time.Duration) 45 } 46 47 func newMessageHandlerManager(registrar handlerRegistrar) MessageHandlerManager { 48 return &messageHandlerManagerImpl{ 49 messageServer: registrar, 50 timeout: atomic.NewDuration(defaultHandlerOperationTimeout), 51 topics: make(map[Topic]<-chan error), 52 } 53 } 54 55 // handlerRegistrar is an interface for the handler management-related 56 // functionalities of MessageServer. 57 // This interface is for easier unit-testing. 58 type handlerRegistrar interface { 59 SyncAddHandler(context.Context, Topic, TypeInformation, HandlerFunc) (<-chan error, error) 60 SyncRemoveHandler(context.Context, Topic) error 61 } 62 63 type messageHandlerManagerImpl struct { 64 messageServer handlerRegistrar 65 // timeout is atomic to avoid unnecessary blocking 66 timeout *atomic.Duration 67 68 // mu protects topics 69 mu sync.Mutex 70 topics map[Topic]<-chan error 71 } 72 73 func (m *messageHandlerManagerImpl) RegisterHandler( 74 ctx context.Context, 75 topic Topic, 76 tpi TypeInformation, 77 fn HandlerFunc, 78 ) (bool, error) { 79 m.mu.Lock() 80 defer m.mu.Unlock() 81 82 if _, ok := m.topics[topic]; ok { 83 // A handler for this topic already exists. 84 return false, nil 85 } 86 87 ctx, cancel := m.makeContext(ctx) 88 defer cancel() 89 90 errCh, err := m.messageServer.SyncAddHandler(ctx, topic, tpi, fn) 91 if err != nil { 92 return false, errors.Trace(err) 93 } 94 m.topics[topic] = errCh 95 96 return true, nil 97 } 98 99 func (m *messageHandlerManagerImpl) UnregisterHandler(ctx context.Context, topic Topic) (bool, error) { 100 m.mu.Lock() 101 defer m.mu.Unlock() 102 103 if _, ok := m.topics[topic]; !ok { 104 // The handler for this topic does not exist 105 return false, nil 106 } 107 108 ctx, cancel := m.makeContext(ctx) 109 defer cancel() 110 111 if err := m.messageServer.SyncRemoveHandler(ctx, topic); err != nil { 112 return false, errors.Trace(err) 113 } 114 delete(m.topics, topic) 115 116 return true, nil 117 } 118 119 func (m *messageHandlerManagerImpl) CheckError(ctx context.Context) error { 120 m.mu.Lock() 121 defer m.mu.Unlock() 122 123 for topic, errCh := range m.topics { 124 select { 125 case <-ctx.Done(): 126 return errors.Trace(ctx.Err()) 127 case err := <-errCh: 128 if err == nil { 129 continue 130 } 131 log.Warn("handler error received", 132 zap.String("topic", topic)) 133 return errors.Trace(err) 134 default: 135 } 136 } 137 return nil 138 } 139 140 func (m *messageHandlerManagerImpl) Clean(ctx context.Context) error { 141 m.mu.Lock() 142 defer m.mu.Unlock() 143 144 for topic := range m.topics { 145 if err := m.messageServer.SyncRemoveHandler(ctx, topic); err != nil { 146 return errors.Trace(err) 147 } 148 } 149 150 return nil 151 } 152 153 func (m *messageHandlerManagerImpl) SetTimeout(timeout time.Duration) { 154 m.timeout.Store(timeout) 155 } 156 157 func (m *messageHandlerManagerImpl) makeContext(parent context.Context) (context.Context, context.CancelFunc) { 158 timeout := m.timeout.Load() 159 return context.WithTimeout(parent, timeout) 160 }