0

Upgrade versions of vendored libs

This commit is contained in:
eleksir
2023-05-18 23:55:10 +03:00
parent 4869c146fe
commit 7958db9c0d
555 changed files with 36170 additions and 15565 deletions

View File

@@ -133,7 +133,8 @@ func (q *syncQueue) load() (head, tail, realLength uint32) {
return head, tail, realLength
}
func (q *syncQueue) pop(head, tail uint32, err error) error {
// REQUIRES: queueSemChan is non-nil.
func (q *syncQueue) pop(head, tail uint32, err error, queueSemChan chan struct{}) error {
if tail == head {
// Queue is empty.
return nil
@@ -153,6 +154,10 @@ func (q *syncQueue) pop(head, tail uint32, err error) error {
// will try to enqueue before we've "freed" space in the queue.
atomic.AddUint64(&q.headTail, 1)
wg.Done()
// Is always non-nil in production.
if queueSemChan != nil {
<-queueSemChan
}
}
return nil
@@ -291,12 +296,20 @@ type LogWriter struct {
// used for min-sync-interval. In normal operation this points to
// time.AfterFunc.
afterFunc func(d time.Duration, f func()) syncTimer
// See the comment for LogWriterConfig.QueueSemChan.
queueSemChan chan struct{}
}
// LogWriterConfig is a struct used for configuring new LogWriters
type LogWriterConfig struct {
WALMinSyncInterval durationFunc
WALFsyncLatency prometheus.Histogram
// QueueSemChan is an optional channel to pop from when popping from
// LogWriter.flusher.syncQueue. It functions as a semaphore that prevents
// the syncQueue from overflowing (which will cause a panic). All production
// code ensures this is non-nil.
QueueSemChan chan struct{}
}
// CapAllocatedBlocks is the maximum number of blocks allocated by the
@@ -319,6 +332,7 @@ func NewLogWriter(w io.Writer, logNum base.FileNum, logWriterConfig LogWriterCon
afterFunc: func(d time.Duration, f func()) syncTimer {
return time.AfterFunc(d, f)
},
queueSemChan: logWriterConfig.QueueSemChan,
}
r.free.cond.L = &r.free.Mutex
r.free.blocks = make([]*block, 0, CapAllocatedBlocks)
@@ -441,7 +455,7 @@ func (w *LogWriter) flushLoop(context.Context) {
// If flusher has an error, we propagate it to waiters. Note in spite of
// error we consume the pending list above to free blocks for writers.
if f.err != nil {
f.syncQ.pop(head, tail, f.err)
f.syncQ.pop(head, tail, f.err, w.queueSemChan)
// Update the idleStartTime if work could not be done, so that we don't
// include the duration we tried to do work as idle. We don't bother
// with the rest of the accounting, which means we will undercount.
@@ -518,7 +532,7 @@ func (w *LogWriter) flushPending(
syncLatency, err = w.syncWithLatency()
}
f := &w.flusher
if popErr := f.syncQ.pop(head, tail, err); popErr != nil {
if popErr := f.syncQ.pop(head, tail, err, w.queueSemChan); popErr != nil {
return synced, syncLatency, bytesWritten, popErr
}
}
@@ -548,7 +562,7 @@ func (w *LogWriter) flushBlock(b *block) error {
// queueBlock queues the current block for writing to the underlying writer,
// allocates a new block and reserves space for the next header.
func (w *LogWriter) queueBlock() {
func (w *LogWriter) queueBlock() (waitDuration time.Duration) {
// Allocate a new block, blocking until one is available. We do this first
// because w.block is protected by w.flusher.Mutex.
w.free.Lock()
@@ -557,9 +571,11 @@ func (w *LogWriter) queueBlock() {
w.free.allocated++
w.free.blocks = append(w.free.blocks, &block{})
} else {
now := time.Now()
for len(w.free.blocks) == 0 {
w.free.cond.Wait()
}
waitDuration = time.Since(now)
}
}
nextBlock := w.free.blocks[len(w.free.blocks)-1]
@@ -575,6 +591,28 @@ func (w *LogWriter) queueBlock() {
f.Unlock()
w.blockNum++
return waitDuration
}
// ReserveAllFreeBlocksForTesting is used to only for testing.
func (w *LogWriter) ReserveAllFreeBlocksForTesting() (releaseFunc func()) {
w.free.Lock()
defer w.free.Unlock()
free := w.free.blocks
w.free.blocks = nil
return func() {
w.free.Lock()
defer w.free.Unlock()
// It is possible that someone has pushed a free block and w.free.blocks
// is no longer nil. That is harmless. Also, the waiter loops on the
// condition len(w.free.blocks) == 0, so to actually unblock it we need to
// give it a free block.
if len(free) == 0 {
free = append(free, &block{})
}
w.free.blocks = free
w.free.cond.Broadcast()
}
}
// Close flushes and syncs any unwritten data and closes the writer.
@@ -626,7 +664,8 @@ func (w *LogWriter) Close() error {
// of the record.
// External synchronisation provided by commitPipeline.mu.
func (w *LogWriter) WriteRecord(p []byte) (int64, error) {
return w.SyncRecord(p, nil, nil)
logSize, _, err := w.SyncRecord(p, nil, nil)
return logSize, err
}
// SyncRecord writes a complete record. If wg!= nil the record will be
@@ -634,9 +673,11 @@ func (w *LogWriter) WriteRecord(p []byte) (int64, error) {
// the wait group upon completion. Returns the offset just past the end of the
// record.
// External synchronisation provided by commitPipeline.mu.
func (w *LogWriter) SyncRecord(p []byte, wg *sync.WaitGroup, err *error) (int64, error) {
func (w *LogWriter) SyncRecord(
p []byte, wg *sync.WaitGroup, err *error,
) (logSize int64, waitDuration time.Duration, err2 error) {
if w.err != nil {
return -1, w.err
return -1, 0, w.err
}
// The `i == 0` condition ensures we handle empty records. Such records can
@@ -644,7 +685,9 @@ func (w *LogWriter) SyncRecord(p []byte, wg *sync.WaitGroup, err *error) (int64,
// MANIFEST is currently written using Writer, it is good to support the same
// semantics with LogWriter.
for i := 0; i == 0 || len(p) > 0; i++ {
p = w.emitFragment(i, p)
var wd time.Duration
p, wd = w.emitFragment(i, p)
waitDuration += wd
}
if wg != nil {
@@ -663,7 +706,7 @@ func (w *LogWriter) SyncRecord(p []byte, wg *sync.WaitGroup, err *error) (int64,
// race with our read. That's ok because the only error we could be seeing is
// one to syncing for which the caller can receive notification of by passing
// in a non-nil err argument.
return offset, nil
return offset, waitDuration, nil
}
// Size returns the current size of the file.
@@ -684,7 +727,7 @@ func (w *LogWriter) emitEOFTrailer() {
atomic.StoreInt32(&b.written, i+int32(recyclableHeaderSize))
}
func (w *LogWriter) emitFragment(n int, p []byte) []byte {
func (w *LogWriter) emitFragment(n int, p []byte) (remainingP []byte, waitDuration time.Duration) {
b := w.block
i := b.written
first := n == 0
@@ -718,9 +761,9 @@ func (w *LogWriter) emitFragment(n int, p []byte) []byte {
for i := b.written; i < blockSize; i++ {
b.buf[i] = 0
}
w.queueBlock()
waitDuration = w.queueBlock()
}
return p[r:]
return p[r:], waitDuration
}
// Metrics must be called after Close. The callee will no longer modify the

View File

@@ -19,6 +19,7 @@
// Neither Readers or Writers are safe to use concurrently.
//
// Example code:
//
// func read(r io.Reader) ([]string, error) {
// var ss []string
// records := record.NewReader(r)
@@ -65,9 +66,9 @@
// A record maps to one or more chunks. There are two chunk formats: legacy and
// recyclable. The legacy chunk format:
//
// +----------+-----------+-----------+--- ... ---+
// | CRC (4B) | Size (2B) | Type (1B) | Payload |
// +----------+-----------+-----------+--- ... ---+
// +----------+-----------+-----------+--- ... ---+
// | CRC (4B) | Size (2B) | Type (1B) | Payload |
// +----------+-----------+-----------+--- ... ---+
//
// CRC is computed over the type and payload
// Size is the length of the payload in bytes
@@ -84,9 +85,9 @@
// metadata. Additionally, recycling log files is a prequisite for using direct
// IO with log writing. The recyclyable format is:
//
// +----------+-----------+-----------+----------------+--- ... ---+
// | CRC (4B) | Size (2B) | Type (1B) | Log number (4B)| Payload |
// +----------+-----------+-----------+----------------+--- ... ---+
// +----------+-----------+-----------+----------------+--- ... ---+
// | CRC (4B) | Size (2B) | Type (1B) | Log number (4B)| Payload |
// +----------+-----------+-----------+----------------+--- ... ---+
//
// Recyclable chunks are distinguished from legacy chunks by the addition of 4
// extra "recyclable" chunk types that map directly to the legacy chunk types
@@ -247,6 +248,7 @@ func (r *Reader) nextChunk(wantFirst bool) error {
r.begin = r.end + headerSize
r.end = r.begin + int(length)
if r.end > r.n {
// The chunk straddles a 32KB boundary (or the end of file).
if r.recovering {
r.recover()
continue

View File

@@ -0,0 +1,82 @@
// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
// of this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
package record
// RotationHelper is a type used to inform the decision of rotating a record log
// file.
//
// The assumption is that multiple records can be coalesced into a single record
// (called a snapshot). Starting a new file, where the first record is a
// snapshot of the current state is referred to as "rotating" the log.
//
// Normally we rotate files when a certain file size is reached. But in certain
// cases (e.g. contents become very large), this can result in too frequent
// rotation. This helper contains logic to impose extra conditions on the
// rotation.
//
// The rotation helper uses "size" as a unit-less estimation that is correlated
// with the on-disk size of a record or snapshot.
type RotationHelper struct {
// lastSnapshotSize is the size of the last snapshot.
lastSnapshotSize int64
// sizeSinceLastSnapshot is the sum of sizes of records applied since the last
// snapshot.
sizeSinceLastSnapshot int64
lastRecordSize int64
}
// AddRecord makes the rotation helper aware of a new record.
func (rh *RotationHelper) AddRecord(recordSize int64) {
rh.sizeSinceLastSnapshot += recordSize
rh.lastRecordSize = recordSize
}
// ShouldRotate returns whether we should start a new log file (with a snapshot).
// Does not need to be called if other rotation factors (log file size) are not
// satisfied.
func (rh *RotationHelper) ShouldRotate(nextSnapshotSize int64) bool {
// The primary goal is to ensure that when reopening a log file, the number of
// edits that need to be replayed on top of the snapshot is "sane" while
// keeping the rotation frequency as low as possible.
//
// For the purposes of this description, we assume that the log is mainly
// storing a collection of "entries", with edits adding or removing entries.
// Consider the following cases:
//
// - The number of live entries is roughly stable: after writing the snapshot
// (with S entries), we require that there be enough edits such that the
// cumulative number of entries in those edits, E, be greater than S. This
// will ensure that at most 50% of data written out is due to rotation.
//
// - The number of live entries K in the DB is shrinking drastically, say from
// S to S/10: After this shrinking, E = 0.9S, and so if we used the previous
// snapshot entry count, S, as the threshold that needs to be exceeded, we
// will further delay the snapshot writing. Which means on reopen we will
// need to replay 0.9S edits to get to a version with 0.1S entries. It would
// be better to create a new snapshot when E exceeds the number of entries in
// the current version.
//
// - The number of live entries L in the DB is growing; say the last snapshot
// had S entries, and now we have 10S entries, so E = 9S. If we required
// that E is at least the current number of entries, we would further delay
// writing a new snapshot (which is not desirable).
//
// The logic below uses the min of the last snapshot size count and the size
// count in the current version.
return rh.sizeSinceLastSnapshot > rh.lastSnapshotSize || rh.sizeSinceLastSnapshot > nextSnapshotSize
}
// Rotate makes the rotation helper aware that we are rotating to a new snapshot
// (to which we will apply the latest edit).
func (rh *RotationHelper) Rotate(snapshotSize int64) {
rh.lastSnapshotSize = snapshotSize
rh.sizeSinceLastSnapshot = rh.lastRecordSize
}
// DebugInfo returns the last snapshot size and size of the edits since the last
// snapshot; used for testing and debugging.
func (rh *RotationHelper) DebugInfo() (lastSnapshotSize int64, sizeSinceLastSnapshot int64) {
return rh.lastSnapshotSize, rh.sizeSinceLastSnapshot
}