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  //------------------------------------------------------------------------------