aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--bucket_test.go25
-rw-r--r--cursor.go2
-rw-r--r--db.go17
-rw-r--r--freelist.go43
-rw-r--r--freelist_test.go17
-rw-r--r--page.go6
7 files changed, 65 insertions, 47 deletions
diff --git a/Makefile b/Makefile
index 2c0dc97..67635ae 100644
--- a/Makefile
+++ b/Makefile
@@ -19,7 +19,7 @@ cover: fmt
cpuprofile: fmt
@go test -c
- @./bolt.test -test.v -test.run="^X" -test.bench=$(BENCH) -test.cpuprofile cpu.prof
+ @./bolt.test -test.v -test.run=$(TEST) -test.cpuprofile cpu.prof
# go get github.com/kisielk/errcheck
errcheck:
diff --git a/bucket_test.go b/bucket_test.go
index f6bd13f..16172a5 100644
--- a/bucket_test.go
+++ b/bucket_test.go
@@ -899,30 +899,21 @@ func TestBucket_Delete_Quick(t *testing.T) {
assert.NoError(t, err)
// Remove items one at a time and check consistency.
- for i, item := range items {
+ for _, item := range items {
err := db.Update(func(tx *Tx) error {
return tx.Bucket([]byte("widgets")).Delete(item.Key)
})
assert.NoError(t, err)
+ }
- // Anything before our deletion index should be nil.
- db.View(func(tx *Tx) error {
- b := tx.Bucket([]byte("widgets"))
- for j, exp := range items {
- value := b.Get(exp.Key)
- if j > i {
- if !assert.Equal(t, exp.Value, value) {
- t.FailNow()
- }
- } else {
- if !assert.Nil(t, value) {
- t.FailNow()
- }
- }
- }
+ // Anything before our deletion index should be nil.
+ db.View(func(tx *Tx) error {
+ tx.Bucket([]byte("widgets")).ForEach(func(k, v []byte) error {
+ t.Fatalf("bucket should be empty; found: %06x", trunc(k, 3))
return nil
})
- }
+ return nil
+ })
})
return true
}
diff --git a/cursor.go b/cursor.go
index 31a3d5f..1c14d05 100644
--- a/cursor.go
+++ b/cursor.go
@@ -193,7 +193,7 @@ func (c *Cursor) last() {
func (c *Cursor) search(key []byte, pgid pgid) {
p, n := c.bucket.pageNode(pgid)
if p != nil {
- _assert((p.flags&(branchPageFlag|leafPageFlag)) != 0, "invalid page type: %d: %s", p.id, p.typ())
+ _assert((p.flags&(branchPageFlag|leafPageFlag)) != 0, "invalid page type: %d: %x", p.id, p.flags)
}
e := elemRef{page: p, node: n}
c.stack = append(c.stack, e)
diff --git a/db.go b/db.go
index c768ce3..ed0f2cd 100644
--- a/db.go
+++ b/db.go
@@ -351,7 +351,6 @@ func (db *DB) beginTx() (*Tx, error) {
// Lock the meta pages while we initialize the transaction.
db.metalock.Lock()
- defer db.metalock.Unlock()
// Exit if the database is not open yet.
if !db.opened {
@@ -365,6 +364,16 @@ func (db *DB) beginTx() (*Tx, error) {
// Keep track of transaction until it closes.
db.txs = append(db.txs, t)
+ n := len(db.txs)
+
+ // Unlock the meta pages.
+ db.metalock.Unlock()
+
+ // Update the transaction stats.
+ db.statlock.Lock()
+ db.stats.TxN++
+ db.stats.OpenTxN = n
+ db.statlock.Unlock()
return t, nil
}
@@ -418,12 +427,14 @@ func (db *DB) removeTx(tx *Tx) {
break
}
}
+ n := len(db.txs)
// Unlock the meta pages.
db.metalock.Unlock()
// Merge statistics.
db.statlock.Lock()
+ db.stats.OpenTxN = n
db.stats.TxStats.add(&tx.stats)
db.statlock.Unlock()
}
@@ -612,6 +623,10 @@ func (db *DB) allocate(count int) (*page, error) {
// Stats represents statistics about the database.
type Stats struct {
+ // Transaction stats
+ TxN int // total number of completed read transactions
+ OpenTxN int // number of currently open read transactions
+
TxStats TxStats // global, ongoing stats.
}
diff --git a/freelist.go b/freelist.go
index ebe2810..0d79bb4 100644
--- a/freelist.go
+++ b/freelist.go
@@ -26,30 +26,42 @@ func (f *freelist) all() []pgid {
ids = append(ids, list...)
}
- sort.Sort(reverseSortedPgids(ids))
+ sort.Sort(pgids(ids))
return ids
}
// allocate returns the starting page id of a contiguous list of pages of a given size.
// If a contiguous block cannot be found then 0 is returned.
func (f *freelist) allocate(n int) pgid {
- var count int
- var previd pgid
+ if len(f.ids) == 0 {
+ return 0
+ }
+
+ var initial, previd pgid
for i, id := range f.ids {
- // Reset count if this is not contiguous.
- if previd == 0 || previd-id != 1 {
- count = 1
+ _assert(id > 1, "invalid page allocation: %d", id)
+
+ // Reset initial page if this is not contiguous.
+ if previd == 0 || id-previd != 1 {
+ initial = id
}
// If we found a contiguous block then remove it and return it.
- if count == n {
- f.ids = append(f.ids[:i-(n-1)], f.ids[i+1:]...)
- _assert(id > 1, "cannot allocate page 0 or 1: %d", id)
- return id
+ if (id-initial)+1 == pgid(n) {
+ // If we're allocating off the beginning then take the fast path
+ // and just adjust the existing slice. This will use extra memory
+ // temporarily but the append() in free() will realloc the slice
+ // as is necessary.
+ if (i + 1) == n {
+ f.ids = f.ids[i+1:]
+ } else {
+ copy(f.ids[i-1:], f.ids[i+n-1:])
+ f.ids = f.ids[:len(f.ids)-n]
+ }
+ return initial
}
previd = id
- count++
}
return 0
}
@@ -74,7 +86,7 @@ func (f *freelist) release(txid txid) {
delete(f.pending, tid)
}
}
- sort.Sort(reverseSortedPgids(f.ids))
+ sort.Sort(pgids(f.ids))
}
// isFree returns whether a given page is in the free list.
@@ -99,6 +111,7 @@ func (f *freelist) read(p *page) {
ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0:p.count]
f.ids = make([]pgid, len(ids))
copy(f.ids, ids)
+ sort.Sort(pgids(ids))
}
// write writes the page ids onto a freelist page. All free and pending ids are
@@ -133,9 +146,3 @@ func (f *freelist) check() {
}
}
*/
-
-type reverseSortedPgids []pgid
-
-func (s reverseSortedPgids) Len() int { return len(s) }
-func (s reverseSortedPgids) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
-func (s reverseSortedPgids) Less(i, j int) bool { return s[i] > s[j] }
diff --git a/freelist_test.go b/freelist_test.go
index 2b321a4..00c71cf 100644
--- a/freelist_test.go
+++ b/freelist_test.go
@@ -36,15 +36,14 @@ func TestFreelist_release(t *testing.T) {
// Ensure that a freelist can find contiguous blocks of pages.
func TestFreelist_allocate(t *testing.T) {
- f := &freelist{ids: []pgid{18, 13, 12, 9, 7, 6, 5, 4, 3}}
- assert.Equal(t, f.allocate(2), pgid(12))
- assert.Equal(t, f.allocate(1), pgid(18))
- assert.Equal(t, f.allocate(3), pgid(5))
- assert.Equal(t, f.allocate(3), pgid(0))
- assert.Equal(t, f.allocate(2), pgid(3))
- assert.Equal(t, f.allocate(1), pgid(9))
- assert.Equal(t, f.allocate(0), pgid(0))
- assert.Equal(t, f.ids, []pgid{})
+ f := &freelist{ids: []pgid{3, 4, 5, 6, 7, 9, 12, 13, 18}}
+ assert.Equal(t, 3, int(f.allocate(3)))
+ assert.Equal(t, 6, int(f.allocate(1)))
+ assert.Equal(t, 0, int(f.allocate(3)))
+ assert.Equal(t, 12, int(f.allocate(2)))
+ assert.Equal(t, 7, int(f.allocate(1)))
+ assert.Equal(t, 0, int(f.allocate(0)))
+ assert.Equal(t, []pgid{9, 18}, f.ids)
}
// Ensure that a freelist can deserialize from a freelist page.
diff --git a/page.go b/page.go
index 56cf064..cd213a4 100644
--- a/page.go
+++ b/page.go
@@ -128,3 +128,9 @@ type PageInfo struct {
Count int
OverflowCount int
}
+
+type pgids []pgid
+
+func (s pgids) Len() int { return len(s) }
+func (s pgids) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+func (s pgids) Less(i, j int) bool { return s[i] > s[j] }