github.com/looshlee/beatles@v0.0.0-20220727174639-742810ab631c/pkg/kafka/correlation_cache.go (about) 1 // Copyright 2018 Authors of Cilium 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 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package kafka 16 17 import ( 18 "time" 19 20 "github.com/cilium/cilium/pkg/lock" 21 ) 22 23 var ( 24 // RequestLifetime specifies the maximum time a request can stay in the 25 // correlation cache without getting correlated. After this time has 26 // passed, the request will be removed from the cache 27 RequestLifetime = 5 * time.Minute 28 ) 29 30 type requestsCache map[CorrelationID]*correlationEntry 31 32 // FinishFunc is the function called when a request has been correlated with 33 // its response 34 type FinishFunc func(req *RequestMessage) 35 36 // correlationEntry is the structure used to store requests in the correlation 37 // cache 38 type correlationEntry struct { 39 request *RequestMessage 40 41 // created is the timestamp when the request was created in the cache 42 created time.Time 43 44 // finishFunc is called when the request has been correlated with a 45 // response or when the request has been expired from the cache 46 finishFunc FinishFunc 47 48 // origCorrelationID is the original correlation ID as present in the 49 // request. It will be used to restore the correlation ID in the 50 // response heading back to the client. 51 origCorrelationID CorrelationID 52 } 53 54 // CorrelationCache is a cache used to correlate requests with responses 55 // 56 // It consists of two main functions: 57 // 58 // cache.HandleRequest(request) 59 // 60 // Must be called when a request is forwarded to the broker, will keep track 61 // of the request and rewrite the correlation ID inside of the request to 62 // a sequence number. This sequence number is guaranteed to be unique within 63 // the connection covered by the cache. 64 // 65 // cache.CorrelateResponse(response) 66 // 67 // Must be called when a response is received from the broker. Will return 68 // the original request that corresponds to the response and will restore the 69 // correlation ID in the response to the value that was found in the original 70 // request. 71 // 72 // A garbage collector will run frequently and expire requests which have not 73 // been correlated for the period of `RequestLifetime` 74 type CorrelationCache struct { 75 // mutex protects the cache and numExpired 76 mutex lock.RWMutex 77 78 // cache is a list of all Kafka requests currently waiting to be 79 // correlated with a response 80 cache requestsCache 81 82 // numExpired is the number of expired entries 83 numExpired uint64 84 85 // NumGcRuns counts the number of garbage collector runs 86 numGcRuns uint64 87 88 // nextSequenceNumber is the next sequence number to be used as 89 // correlation ID 90 nextSequenceNumber CorrelationID 91 92 // stopGc is closed when the garbage collector must exit 93 stopGc chan struct{} 94 } 95 96 // NewCorrelationCache returns a new correlation cache 97 func NewCorrelationCache() *CorrelationCache { 98 cc := &CorrelationCache{ 99 cache: requestsCache{}, 100 nextSequenceNumber: 1, 101 stopGc: make(chan struct{}), 102 } 103 104 go cc.garbageCollector() 105 106 return cc 107 } 108 109 // DeleteCache releases the cache and stops the garbage collector. This 110 // function must be called when the cache is no longer required, otherwise go 111 // routines are leaked. 112 func (cc *CorrelationCache) DeleteCache() { 113 close(cc.stopGc) 114 } 115 116 // HandleRequest must be called when a request is forwarded to the broker, will 117 // keep track of the request and rewrite the correlation ID inside of the 118 // request to a sequence number. This sequence number is guaranteed to be 119 // unique within the connection covered by the cache. 120 func (cc *CorrelationCache) HandleRequest(req *RequestMessage, finishFunc FinishFunc) { 121 cc.mutex.Lock() 122 defer cc.mutex.Unlock() 123 124 // save the original correlation ID 125 origCorrelationID := req.GetCorrelationID() 126 127 // Use a sequence number to generate a correlation ID that is 128 // guaranteed to be unique 129 newCorrelationID := cc.nextSequenceNumber 130 cc.nextSequenceNumber++ 131 132 // Overwrite the correlation ID in the request to allow correlating the 133 // response later on. The original correlation ID will be restored when 134 // forwarding the response 135 req.SetCorrelationID(newCorrelationID) 136 137 if _, ok := cc.cache[newCorrelationID]; ok { 138 log.Warning("BUG: Overwriting Kafka request message in correlation cache") 139 } 140 141 cc.cache[newCorrelationID] = &correlationEntry{ 142 request: req, 143 created: time.Now(), 144 origCorrelationID: origCorrelationID, 145 finishFunc: finishFunc, 146 } 147 } 148 149 // correlate returns the request message with the matching correlation ID 150 func (cc *CorrelationCache) correlate(id CorrelationID) *correlationEntry { 151 cc.mutex.RLock() 152 defer cc.mutex.RUnlock() 153 154 entry := cc.cache[id] 155 return entry 156 } 157 158 // CorrelateResponse extracts the correlation ID from the response message, 159 // correlates the corresponding request, restores the original correlation ID 160 // in the response and returns the original request 161 func (cc *CorrelationCache) CorrelateResponse(res *ResponseMessage) *RequestMessage { 162 cc.mutex.Lock() 163 defer cc.mutex.Unlock() 164 165 correlationID := res.GetCorrelationID() 166 if entry := cc.cache[correlationID]; entry != nil { 167 res.SetCorrelationID(entry.origCorrelationID) 168 169 if entry.finishFunc != nil { 170 entry.finishFunc(entry.request) 171 } 172 173 delete(cc.cache, correlationID) 174 return entry.request 175 } 176 177 return nil 178 } 179 180 func (cc *CorrelationCache) garbageCollector() { 181 for { 182 select { 183 case <-cc.stopGc: 184 return 185 default: 186 } 187 188 // calculate the creation time for expiration, entries created 189 // prior to this timestamp must be expired 190 expiryCreationTime := time.Now().Add(-RequestLifetime) 191 192 log.WithField("expiryCreationTime", expiryCreationTime). 193 Debug("Running Kafka correlation cache garbage collector") 194 195 cc.mutex.Lock() 196 for correlationID, entry := range cc.cache { 197 if entry.created.Before(expiryCreationTime) { 198 log.WithField(fieldRequest, entry.request).Debug("Request expired in cache, removing") 199 delete(cc.cache, correlationID) 200 cc.numExpired++ 201 202 if entry.finishFunc != nil { 203 entry.finishFunc(entry.request) 204 } 205 } 206 } 207 208 cc.numGcRuns++ 209 cc.mutex.Unlock() 210 211 time.Sleep(RequestLifetime) 212 } 213 }