kvdb: move channeldb/kvdb to top level
This commit is contained in:
143 files changed
+119
-119
No files matched your search
@@ -0,0 +1,150 @@
|
||||
// +build kvdb_etcd
|
||||
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// commitQueueSize is the maximum number of commits we let to queue up. All
|
||||
// remaining commits will block on commitQueue.Add().
|
||||
const commitQueueSize = 100
|
||||
|
||||
// commitQueue is a simple execution queue to manage conflicts for transactions
|
||||
// and thereby reduce the number of times conflicting transactions need to be
|
||||
// retried. When a new transaction is added to the queue, we first upgrade the
|
||||
// read/write counts in the queue's own accounting to decide whether the new
|
||||
// transaction has any conflicting dependencies. If the transaction does not
|
||||
// conflict with any other, then it is comitted immediately, otherwise it'll be
|
||||
// queued up for later exection.
|
||||
// The algorithm is described in: http://www.cs.umd.edu/~abadi/papers/vll-vldb13.pdf
|
||||
type commitQueue struct {
|
||||
ctx context.Context
|
||||
mx sync.Mutex
|
||||
readerMap map[string]int
|
||||
writerMap map[string]int
|
||||
|
||||
commitMutex sync.RWMutex
|
||||
queue chan (func())
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewCommitQueue creates a new commit queue, with the passed abort context.
|
||||
func NewCommitQueue(ctx context.Context) *commitQueue {
|
||||
q := &commitQueue{
|
||||
ctx: ctx,
|
||||
readerMap: make(map[string]int),
|
||||
writerMap: make(map[string]int),
|
||||
queue: make(chan func(), commitQueueSize),
|
||||
}
|
||||
|
||||
// Start the queue consumer loop.
|
||||
q.wg.Add(1)
|
||||
go q.mainLoop()
|
||||
|
||||
return q
|
||||
}
|
||||
|
||||
// Wait waits for the queue to stop (after the queue context has been canceled).
|
||||
func (c *commitQueue) Wait() {
|
||||
c.wg.Wait()
|
||||
}
|
||||
|
||||
// Add increases lock counts and queues up tx commit closure for execution.
|
||||
// Transactions that don't have any conflicts are executed immediately by
|
||||
// "downgrading" the count mutex to allow concurrency.
|
||||
func (c *commitQueue) Add(commitLoop func(), rset readSet, wset writeSet) {
|
||||
c.mx.Lock()
|
||||
blocked := false
|
||||
|
||||
// Mark as blocked if there's any writer changing any of the keys in
|
||||
// the read set. Do not increment the reader counts yet as we'll need to
|
||||
// use the original reader counts when scanning through the write set.
|
||||
for key := range rset {
|
||||
if c.writerMap[key] > 0 {
|
||||
blocked = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Mark as blocked if there's any writer or reader for any of the keys
|
||||
// in the write set.
|
||||
for key := range wset {
|
||||
blocked = blocked || c.readerMap[key] > 0 || c.writerMap[key] > 0
|
||||
|
||||
// Increment the writer count.
|
||||
c.writerMap[key] += 1
|
||||
}
|
||||
|
||||
// Finally we can increment the reader counts for keys in the read set.
|
||||
for key := range rset {
|
||||
c.readerMap[key] += 1
|
||||
}
|
||||
|
||||
if blocked {
|
||||
// Add the transaction to the queue if conflicts with an already
|
||||
// queued one.
|
||||
c.mx.Unlock()
|
||||
|
||||
select {
|
||||
case c.queue <- commitLoop:
|
||||
case <-c.ctx.Done():
|
||||
}
|
||||
} else {
|
||||
// To make sure we don't add a new tx to the queue that depends
|
||||
// on this "unblocked" tx, grab the commitMutex before lifting
|
||||
// the mutex guarding the lock maps.
|
||||
c.commitMutex.RLock()
|
||||
c.mx.Unlock()
|
||||
|
||||
// At this point we're safe to execute the "unblocked" tx, as
|
||||
// we cannot execute blocked tx that may have been read from the
|
||||
// queue until the commitMutex is held.
|
||||
commitLoop()
|
||||
|
||||
c.commitMutex.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Done decreases lock counts of the keys in the read/write sets.
|
||||
func (c *commitQueue) Done(rset readSet, wset writeSet) {
|
||||
c.mx.Lock()
|
||||
defer c.mx.Unlock()
|
||||
|
||||
for key := range rset {
|
||||
c.readerMap[key] -= 1
|
||||
if c.readerMap[key] == 0 {
|
||||
delete(c.readerMap, key)
|
||||
}
|
||||
}
|
||||
|
||||
for key := range wset {
|
||||
c.writerMap[key] -= 1
|
||||
if c.writerMap[key] == 0 {
|
||||
delete(c.writerMap, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mainLoop executes queued transaction commits for transactions that have
|
||||
// dependencies. The queue ensures that the top element doesn't conflict with
|
||||
// any other transactions and therefore can be executed freely.
|
||||
func (c *commitQueue) mainLoop() {
|
||||
defer c.wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case top := <-c.queue:
|
||||
// Execute the next blocked transaction. As it is
|
||||
// the top element in the queue it means that it doesn't
|
||||
// depend on any other transactions anymore.
|
||||
c.commitMutex.Lock()
|
||||
top()
|
||||
c.commitMutex.Unlock()
|
||||
|
||||
case <-c.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user