Skip to content

Commit

Permalink
db: guard against concurrent batch writes during Commit
Browse files Browse the repository at this point in the history
Add a guardrail that ensures a batch isn't in the process of being committed
when applying a mutation to the Batch. We've observed panics during batch
application that suggest concurrent modification of the batch, and these
guardrails will help surface any races more readily.

Informs cockroachdb/cockroach#113268
Resolve cockroachdb#3024.
  • Loading branch information
jbowens committed Nov 1, 2023
1 parent 5f131c1 commit 424019f
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 5 deletions.
27 changes: 22 additions & 5 deletions batch.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,11 +267,6 @@ type batchInternal struct {
// memtable.
flushable *flushableBatch

// ingestedSSTBatch indicates that the batch contains one or more key kinds
// of InternalKeyKindIngestSST. If the batch contains key kinds of IngestSST
// then it will only contain key kinds of IngestSST.
ingestedSSTBatch bool

// minimumFormatMajorVersion indicates the format major version required in
// order to commit this batch. If an operation requires a particular format
// major version, it ratchets the batch's minimumFormatMajorVersion. When
Expand Down Expand Up @@ -299,6 +294,22 @@ type batchInternal struct {
commitStats BatchCommitStats

commitErr error

// Position bools together to reduce the sizeof the struct.

// ingestedSSTBatch indicates that the batch contains one or more key kinds
// of InternalKeyKindIngestSST. If the batch contains key kinds of IngestSST
// then it will only contain key kinds of IngestSST.
ingestedSSTBatch bool

// committing is set to true when a batch begins to commit. It's used to
// ensure the batch is not mutated concurrently. It is not an atomic
// deliberately, so as to avoid the overhead on batch mutations. This is
// okay, because under correct usage this field will never be accessed
// concurrently. It's only under incorrect usage the memory accesses of this
// variable may violate memory safety. Since we don't use atomics here,
// false negatives are possible.
committing bool
}

// BatchCommitStats exposes stats related to committing a batch.
Expand Down Expand Up @@ -560,6 +571,9 @@ func (b *Batch) Get(key []byte) ([]byte, io.Closer, error) {
}

func (b *Batch) prepareDeferredKeyValueRecord(keyLen, valueLen int, kind InternalKeyKind) {
if b.committing {
panic("pebble: batch already committing")
}
if len(b.data) == 0 {
b.init(keyLen + valueLen + 2*binary.MaxVarintLen64 + batchHeaderLen)
}
Expand Down Expand Up @@ -609,6 +623,9 @@ func (b *Batch) prepareDeferredKeyValueRecord(keyLen, valueLen int, kind Interna
}

func (b *Batch) prepareDeferredKeyRecord(keyLen int, kind InternalKeyKind) {
if b.committing {
panic("pebble: batch already committing")
}
if len(b.data) == 0 {
b.init(keyLen + binary.MaxVarintLen64 + batchHeaderLen)
}
Expand Down
4 changes: 4 additions & 0 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,9 @@ func (d *DB) applyInternal(batch *Batch, opts *WriteOptions, noSyncWait bool) er
if err := d.closed.Load(); err != nil {
panic(err)
}
if batch.committing {
panic("pebble: batch already committing")
}
if batch.applied.Load() {
panic("pebble: batch already applied")
}
Expand Down Expand Up @@ -838,6 +841,7 @@ func (d *DB) applyInternal(batch *Batch, opts *WriteOptions, noSyncWait bool) er
}
// TODO(jackson): Assert that all range key operands are suffixless.
}
batch.committing = true

if batch.db == nil {
batch.refreshMemTableSize()
Expand Down

0 comments on commit 424019f

Please sign in to comment.