diff --git a/rbf/db_test.go b/rbf/db_test.go index 039d907f3..d637860e0 100644 --- a/rbf/db_test.go +++ b/rbf/db_test.go @@ -39,6 +39,25 @@ func TestDB_Open(t *testing.T) { } } +func TestDB_WAL(t *testing.T) { + t.Run("ErrTxTooLarge", func(t *testing.T) { + config := rbfcfg.NewDefaultConfig() + config.MaxWALSize = 4 * rbf.PageSize + + db := MustOpenDB(t, config) + defer MustCloseDB(t, db) + + tx := MustBegin(t, db, true) + defer tx.Rollback() + + if err := tx.CreateBitmap("x"); err != nil { + t.Fatal(err) + } else if err := tx.CreateBitmap("y"); err != rbf.ErrTxTooLarge { + t.Fatalf("unexpected error: %v", err) + } + }) +} + func TestDB_Recovery(t *testing.T) { // Ensure a bitmap header written without a bitmap is truncated. t.Run("TruncPartialWALBitmap", func(t *testing.T) { diff --git a/rbf/rbf.go b/rbf/rbf.go index 4dd8d6b43..e3a293a26 100644 --- a/rbf/rbf.go +++ b/rbf/rbf.go @@ -105,6 +105,7 @@ var ( ErrBitmapNameRequired = errors.New("bitmap name required") ErrBitmapNotFound = errors.New("bitmap not found") ErrBitmapExists = errors.New("bitmap already exists") + ErrTxTooLarge = errors.New("rbf tx too large") ) // Debug is just a temporary flag used for debugging. diff --git a/rbf/tx.go b/rbf/tx.go index 6c822f18c..72d041704 100644 --- a/rbf/tx.go +++ b/rbf/tx.go @@ -69,7 +69,12 @@ func (tx *Tx) Writable() bool { // dirty returns true if any pages have been updated in this tx. func (tx *Tx) dirty() bool { - return len(tx.dirtyPages) != 0 || len(tx.dirtyBitmapPages) != 0 + return tx.dirtyN() != 0 +} + +// dirtyN returns the number of dirty pages. +func (tx *Tx) dirtyN() int { + return len(tx.dirtyPages) + len(tx.dirtyBitmapPages) } // PageN returns the number of pages in the database as seen by this transaction. @@ -954,11 +959,18 @@ func (tx *Tx) readPage(pgno uint32) ([]byte, error) { func (tx *Tx) writePage(page []byte) error { tx.dirtyPages[readPageNo(page)] = page - return nil + return tx.checkTxSize() } func (tx *Tx) writeBitmapPage(pgno uint32, page []byte) error { tx.dirtyBitmapPages[pgno] = page + return tx.checkTxSize() +} + +func (tx *Tx) checkTxSize() error { + if (tx.walPageN+tx.dirtyN())*PageSize >= len(tx.db.wal) { + return ErrTxTooLarge + } return nil }