Merge pull request #220 from benbjohnson/handle-panic

Recover from panics appropriately.
master
Ben Johnson 2014-07-11 10:04:28 -06:00
commit ba4adce39b
3 changed files with 80 additions and 0 deletions

14
db.go
View File

@ -440,6 +440,13 @@ func (db *DB) Update(fn func(*Tx) error) error {
return err return err
} }
// Make sure the transaction rolls back in the event of a panic.
defer func() {
if t.db != nil {
t.rollback()
}
}()
// Mark as a managed tx so that the inner function cannot manually commit. // Mark as a managed tx so that the inner function cannot manually commit.
t.managed = true t.managed = true
@ -464,6 +471,13 @@ func (db *DB) View(fn func(*Tx) error) error {
return err return err
} }
// Make sure the transaction rolls back in the event of a panic.
defer func() {
if t.db != nil {
t.rollback()
}
}()
// Mark as a managed tx so that the inner function cannot manually rollback. // Mark as a managed tx so that the inner function cannot manually rollback.
t.managed = true t.managed = true

View File

@ -262,6 +262,37 @@ func TestDB_Update_ManualCommitAndRollback(t *testing.T) {
}) })
} }
// Ensure a write transaction that panics does not hold open locks.
func TestDB_Update_Panic(t *testing.T) {
withOpenDB(func(db *DB, path string) {
func() {
defer func() {
if r := recover(); r != nil {
warn("recover: update", r)
}
}()
db.Update(func(tx *Tx) error {
tx.CreateBucket([]byte("widgets"))
panic("omg")
return nil
})
}()
// Verify we can update again.
err := db.Update(func(tx *Tx) error {
_, err := tx.CreateBucket([]byte("widgets"))
return err
})
assert.NoError(t, err)
// Verify that our change persisted.
err = db.Update(func(tx *Tx) error {
assert.NotNil(t, tx.Bucket([]byte("widgets")))
return nil
})
})
}
// Ensure a database can return an error through a read-only transactional block. // Ensure a database can return an error through a read-only transactional block.
func TestDB_View_Error(t *testing.T) { func TestDB_View_Error(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
@ -272,6 +303,35 @@ func TestDB_View_Error(t *testing.T) {
}) })
} }
// Ensure a read transaction that panics does not hold open locks.
func TestDB_View_Panic(t *testing.T) {
withOpenDB(func(db *DB, path string) {
db.Update(func(tx *Tx) error {
tx.CreateBucket([]byte("widgets"))
return nil
})
func() {
defer func() {
if r := recover(); r != nil {
warn("recover: view", r)
}
}()
db.View(func(tx *Tx) error {
assert.NotNil(t, tx.Bucket([]byte("widgets")))
panic("omg")
return nil
})
}()
// Verify that we can still use read transactions.
db.View(func(tx *Tx) error {
assert.NotNil(t, tx.Bucket([]byte("widgets")))
return nil
})
})
}
// Ensure that an error is returned when a database write fails. // Ensure that an error is returned when a database write fails.
func TestDB_Commit_WriteFail(t *testing.T) { func TestDB_Commit_WriteFail(t *testing.T) {
t.Skip("pending") // TODO(benbjohnson) t.Skip("pending") // TODO(benbjohnson)

6
tx.go
View File

@ -212,6 +212,9 @@ func (tx *Tx) Rollback() error {
} }
func (tx *Tx) rollback() { func (tx *Tx) rollback() {
if tx.db == nil {
return
}
if tx.writable { if tx.writable {
tx.db.freelist.rollback(tx.id()) tx.db.freelist.rollback(tx.id())
tx.db.freelist.reload(tx.db.page(tx.db.meta().freelist)) tx.db.freelist.reload(tx.db.page(tx.db.meta().freelist))
@ -220,6 +223,9 @@ func (tx *Tx) rollback() {
} }
func (tx *Tx) close() { func (tx *Tx) close() {
if tx.db == nil {
return
}
if tx.writable { if tx.writable {
// Grab freelist stats. // Grab freelist stats.
var freelistFreeN = tx.db.freelist.free_count() var freelistFreeN = tx.db.freelist.free_count()