github.com/Jeffail/benthos/v3@v3.65.0/lib/buffer/single/mmap_buffer.go (about) 1 //go:build !wasm 2 // +build !wasm 3 4 package single 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/Jeffail/benthos/v3/lib/log" 11 "github.com/Jeffail/benthos/v3/lib/message" 12 "github.com/Jeffail/benthos/v3/lib/metrics" 13 "github.com/Jeffail/benthos/v3/lib/types" 14 ) 15 16 //------------------------------------------------------------------------------ 17 18 // MmapBufferConfig is config options for a memory-map based buffer reader. 19 type MmapBufferConfig MmapCacheConfig 20 21 // NewMmapBufferConfig creates a MmapBufferConfig oject with default values. 22 func NewMmapBufferConfig() MmapBufferConfig { 23 return MmapBufferConfig(NewMmapCacheConfig()) 24 } 25 26 // MmapBuffer is a buffer implemented around rotated memory mapped files. 27 type MmapBuffer struct { 28 config MmapBufferConfig 29 cache *MmapCache 30 31 logger log.Modular 32 stats metrics.Type 33 34 retryPeriod time.Duration 35 36 mCacheErr metrics.StatCounter 37 38 readFrom int 39 readIndex int 40 41 writtenTo int 42 writeIndex int 43 44 closed bool 45 } 46 47 // NewMmapBuffer creates a memory-map based buffer. 48 func NewMmapBuffer(config MmapBufferConfig, log log.Modular, stats metrics.Type) (*MmapBuffer, error) { 49 cache, err := NewMmapCache(MmapCacheConfig(config), log, stats) 50 if err != nil { 51 return nil, fmt.Errorf("MMAP Cache: %v", err) 52 } 53 cache.L.Lock() 54 defer cache.L.Unlock() 55 56 f := &MmapBuffer{ 57 config: config, 58 cache: cache, 59 logger: log, 60 stats: stats, 61 mCacheErr: stats.GetCounter("open.error"), 62 readFrom: 0, 63 readIndex: 0, 64 writtenTo: 0, 65 writeIndex: 0, 66 closed: false, 67 } 68 69 if tout := config.RetryPeriod; len(tout) > 0 { 70 var err error 71 if f.retryPeriod, err = time.ParseDuration(tout); err != nil { 72 return nil, fmt.Errorf("failed to parse retry period string: %v", err) 73 } 74 } 75 76 f.readTracker() 77 78 f.logger.Infof("Storing messages to file in: %s\n", f.config.Path) 79 80 // Try to ensure both the starting write and read indexes are cached 81 if err = cache.EnsureCached(f.readIndex); err != nil { 82 return nil, fmt.Errorf("MMAP index read: %v", err) 83 } 84 if err = cache.EnsureCached(f.writeIndex); err != nil { 85 log.Errorf("MMAP index write: %v, benthos will block writes until this is resolved.\n", err) 86 } 87 88 go f.cacheManagerLoop(&f.writeIndex) 89 go f.cacheManagerLoop(&f.readIndex) 90 91 return f, nil 92 } 93 94 //------------------------------------------------------------------------------ 95 96 // readTracker reads our cached values from the tracker file for recording 97 // reader/writer indexes. 98 func (f *MmapBuffer) readTracker() { 99 if !f.closed { 100 trackerBlock := f.cache.GetTracker() 101 102 f.writeIndex = readMessageSize(trackerBlock, 0) 103 f.writtenTo = readMessageSize(trackerBlock, 4) 104 f.readIndex = readMessageSize(trackerBlock, 8) 105 f.readFrom = readMessageSize(trackerBlock, 12) 106 } 107 } 108 109 // writeTracker writes our current state to the tracker. 110 func (f *MmapBuffer) writeTracker() { 111 if !f.closed { 112 trackerBlock := f.cache.GetTracker() 113 114 writeMessageSize(trackerBlock, 0, f.writeIndex) 115 writeMessageSize(trackerBlock, 4, f.writtenTo) 116 writeMessageSize(trackerBlock, 8, f.readIndex) 117 writeMessageSize(trackerBlock, 12, f.readFrom) 118 } 119 } 120 121 //------------------------------------------------------------------------------ 122 123 // cacheManagerLoop continuously checks whether the cache contains maps of our 124 // next indexes. 125 func (f *MmapBuffer) cacheManagerLoop(indexPtr *int) { 126 bootstrapped := false 127 128 loop := func() bool { 129 f.cache.L.Lock() 130 defer f.cache.L.Unlock() 131 132 if f.closed { 133 return false 134 } 135 136 targetIndex := *indexPtr 137 if bootstrapped { 138 targetIndex++ 139 } 140 141 if err := f.cache.EnsureCached(targetIndex); err != nil { 142 // Failed to read, log the error and wait before trying again. 143 f.logger.Errorf("Failed to cache mmap file for index %v: %v\n", targetIndex, err) 144 f.mCacheErr.Incr(1) 145 146 f.cache.L.Unlock() 147 <-time.After(f.retryPeriod) 148 f.cache.L.Lock() 149 } else if !bootstrapped { 150 bootstrapped = true 151 } else if *indexPtr < targetIndex { 152 // NOTE: It's possible that while we were waiting for ensure target 153 // was indexed the actual index caught up with us, in which case we 154 // should loop straight back into ensuring the new index rather than 155 // waiting. 156 f.cache.Wait() 157 } 158 return true 159 } 160 for loop() { 161 } 162 } 163 164 //------------------------------------------------------------------------------ 165 166 // backlog reads the current backlog of messages stored. 167 func (f *MmapBuffer) backlog() int { 168 // NOTE: For speed, the following calculation assumes that all mmap files 169 // are the size of limit. 170 return ((f.writeIndex - f.readIndex) * f.config.FileSize) + f.writtenTo - f.readFrom 171 } 172 173 //------------------------------------------------------------------------------ 174 175 // CloseOnceEmpty closes the mmap buffer once the backlog reaches 0. 176 func (f *MmapBuffer) CloseOnceEmpty() { 177 defer func() { 178 f.cache.L.Unlock() 179 f.Close() 180 }() 181 f.cache.L.Lock() 182 183 // Until the backlog is cleared. 184 for f.backlog() > 0 { 185 // Wait for a broadcast from our reader. 186 f.cache.Wait() 187 } 188 } 189 190 // Close unblocks any blocked calls and prevents further writing to the block. 191 func (f *MmapBuffer) Close() { 192 f.cache.L.Lock() 193 f.closed = true 194 f.cache.Broadcast() 195 f.cache.L.Unlock() 196 197 f.cache.L.Lock() 198 f.cache.RemoveAll() 199 f.cache.L.Unlock() 200 } 201 202 // ShiftMessage removes the last message. Returns the backlog count. 203 func (f *MmapBuffer) ShiftMessage() (int, error) { 204 f.cache.L.Lock() 205 defer func() { 206 f.writeTracker() 207 f.cache.Broadcast() 208 f.cache.L.Unlock() 209 }() 210 211 if !f.closed && f.cache.IsCached(f.readIndex) { 212 msgSize := readMessageSize(f.cache.Get(f.readIndex), f.readFrom) 213 f.readFrom = f.readFrom + msgSize + 4 214 } 215 return f.backlog(), nil 216 } 217 218 // NextMessage reads the next message, blocks until there's something to read. 219 func (f *MmapBuffer) NextMessage() (types.Message, error) { 220 f.cache.L.Lock() 221 defer func() { 222 f.writeTracker() 223 f.cache.Broadcast() 224 f.cache.L.Unlock() 225 }() 226 227 // If reader is the same position as the writer then we wait. 228 for f.writeIndex == f.readIndex && f.readFrom == f.writtenTo && !f.closed { 229 f.cache.Wait() 230 } 231 if f.closed { 232 return nil, types.ErrTypeClosed 233 } 234 235 index := f.readFrom 236 block := f.cache.Get(f.readIndex) 237 238 msgSize := readMessageSize(block, index) 239 240 // Messages are written in a contiguous array of bytes, therefore when the 241 // writer reaches the end it will zero the next four bytes (zero size 242 // message) to indicate to the reader that it should move onto the next 243 // file. 244 for msgSize <= 0 { 245 // If we need to switch 246 for !f.cache.IsCached(f.readIndex+1) && !f.closed { 247 // Block until the next file is ready to read. 248 f.cache.Wait() 249 } 250 if f.closed { 251 return nil, types.ErrTypeClosed 252 } 253 254 // If we are meant to delete files as we are done with them 255 if f.config.CleanUp { 256 // The delete is done asynchronously as it has no impact on the 257 // reader 258 go func(prevIndex int) { 259 f.cache.L.Lock() 260 defer f.cache.L.Unlock() 261 262 // Remove and delete the previous index 263 f.cache.Remove(prevIndex) 264 f.cache.Delete(prevIndex) 265 }(f.readIndex) 266 } 267 268 f.readIndex++ 269 f.readFrom = 0 270 271 block = f.cache.Get(f.readIndex) 272 index = 0 273 274 f.cache.Broadcast() 275 276 // If reader is the same position as the writer then we wait. 277 for f.writeIndex == f.readIndex && f.readFrom == f.writtenTo && !f.closed { 278 f.cache.Wait() 279 } 280 if f.closed { 281 return nil, types.ErrTypeClosed 282 } 283 284 // Read the next message. 285 msgSize = readMessageSize(block, index) 286 } 287 288 index += 4 289 if index+msgSize > len(block) { 290 return nil, types.ErrBlockCorrupted 291 } 292 293 return message.FromBytes(block[index : index+msgSize]) 294 } 295 296 // PushMessage pushes a new message, returns the backlog count. 297 func (f *MmapBuffer) PushMessage(msg types.Message) (int, error) { 298 f.cache.L.Lock() 299 defer func() { 300 f.writeTracker() 301 f.cache.Broadcast() 302 f.cache.L.Unlock() 303 }() 304 305 blob := message.ToBytes(msg) 306 index := f.writtenTo 307 308 if len(blob)+4 > f.config.FileSize { 309 return 0, types.ErrMessageTooLarge 310 } 311 312 for !f.cache.IsCached(f.writeIndex) && !f.closed { 313 f.cache.Wait() 314 } 315 if f.closed { 316 return 0, types.ErrTypeClosed 317 } 318 319 block := f.cache.Get(f.writeIndex) 320 321 // If we can't fit our next message in the remainder of the buffer we will 322 // move onto the next file. In order to prevent the reader from reading 323 // garbage we set the next message size to 0, which tells the reader to loop 324 // back to index 0. 325 for len(blob)+4+index > len(block) { 326 // Write zeroes into remainder of the block. 327 for i := index; i < len(block) && i < index+4; i++ { 328 block[i] = byte(0) 329 } 330 331 // Wait until our next file is ready. 332 for !f.cache.IsCached(f.writeIndex+1) && !f.closed { 333 f.cache.Wait() 334 } 335 if f.closed { 336 return 0, types.ErrTypeClosed 337 } 338 339 // If the read index is behind then don't keep our writer block cached. 340 if f.readIndex < f.writeIndex-1 { 341 // But do not block while doing so. 342 go func(prevIndex int) { 343 f.cache.L.Lock() 344 defer f.cache.L.Unlock() 345 346 // Remove the previous index from cache. 347 f.cache.Remove(prevIndex) 348 }(f.writeIndex) 349 } 350 351 // Set counters 352 f.writeIndex++ 353 f.writtenTo = 0 354 355 block = f.cache.Get(f.writeIndex) 356 index = 0 357 358 f.cache.Broadcast() 359 } 360 361 writeMessageSize(block, index, len(blob)) 362 copy(block[index+4:], blob) 363 364 // Move writtenTo ahead. 365 f.writtenTo = (index + len(blob) + 4) 366 367 return f.backlog(), nil 368 } 369 370 //------------------------------------------------------------------------------