Skip to content

Commit a85b887

Browse files
ahrtrElbehery
authored andcommitted
Reimplement the feature of support setting data file size limit
Signed-off-by: Benjamin Wang <benjamin.ahrtr@gmail.com>
1 parent 443486e commit a85b887

3 files changed

Lines changed: 50 additions & 27 deletions

File tree

bolt_windows.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -68,19 +68,6 @@ func mmap(db *DB, sz int) error {
6868
var sizelo, sizehi uint32
6969

7070
if !db.readOnly {
71-
if db.MaxSize > 0 && sz > db.MaxSize {
72-
// The max size only limits future writes; however, we don’t block opening
73-
// and mapping the database if it already exceeds the limit.
74-
fileSize, err := db.fileSize()
75-
if err != nil {
76-
return fmt.Errorf("could not check existing db file size: %s", err)
77-
}
78-
79-
if sz > fileSize {
80-
return errors.ErrMaxSizeReached
81-
}
82-
}
83-
8471
// Truncate the database to the size of the mmap.
8572
if err := db.file.Truncate(int64(sz)); err != nil {
8673
return fmt.Errorf("truncate: %s", err)

db.go

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,23 @@ func (db *DB) mmap(minsz int) (err error) {
476476
return err
477477
}
478478

479+
if db.MaxSize > 0 && size > db.MaxSize {
480+
// On Windows, the data file is expanded to the full mapped size during mmap,
481+
// so we must reject any mmap size larger than the configured max size.
482+
//
483+
// On other platforms, mmap itself does not grow the file immediately, so the
484+
// mapped size may exceed the max size temporarily. The file size limit is
485+
// enforced later when the file actually grows.
486+
//
487+
// In practice, this check mainly applies when opening a database with a large
488+
// InitialMmapSize. In all other cases, file growth is already guarded during
489+
// page allocation.
490+
if size > fileSize && runtime.GOOS == "windows" {
491+
db.Logger().Errorf("[GOOS: %s, GOARCH: %s] maximum db size reached, size: %d, db.MaxSize: %d", runtime.GOOS, runtime.GOARCH, size, db.MaxSize)
492+
return berrors.ErrMaxSizeReached
493+
}
494+
}
495+
479496
if db.Mlock {
480497
// Unlock db memory
481498
if err := db.munlock(fileSize); err != nil {
@@ -1165,6 +1182,26 @@ func (db *DB) allocate(txid common.Txid, count int) (*common.Page, error) {
11651182
// Resize mmap() if we're at the end.
11661183
p.SetId(db.rwtx.meta.Pgid())
11671184
var minsz = int((p.Id()+common.Pgid(count))+1) * db.pageSize
1185+
if db.MaxSize > 0 {
1186+
nextAllocSize := minsz
1187+
nextMmapSize, err := db.mmapSize(minsz)
1188+
if err != nil {
1189+
return nil, fmt.Errorf("mmap size calculation error: %w", err)
1190+
}
1191+
if runtime.GOOS == "windows" {
1192+
// nextAllocSize may not exactly match nextMmapSize.
1193+
// On Windows, this mismatch may cause the file size to slightly exceed maxSize,
1194+
// while it is harmless on other platforms.
1195+
nextAllocSize = nextMmapSize
1196+
} else {
1197+
// On non-Windows platforms, the database file is only grown explicitly in grow calls.
1198+
nextAllocSize = db.growSize(nextMmapSize, nextAllocSize)
1199+
}
1200+
if nextAllocSize > db.MaxSize {
1201+
db.Logger().Errorf("[GOOS: %s, GOARCH: %s] maximum db size reached, minSize: %d (allocSize: %d), db.MaxSize: %d", runtime.GOOS, runtime.GOARCH, minsz, nextAllocSize, db.MaxSize)
1202+
return nil, berrors.ErrMaxSizeReached
1203+
}
1204+
}
11681205
if minsz >= db.datasz {
11691206
if err := db.mmap(minsz); err != nil {
11701207
if err == berrors.ErrMaxSizeReached {
@@ -1195,18 +1232,7 @@ func (db *DB) grow(sz int) error {
11951232
return nil
11961233
}
11971234

1198-
// If the data is smaller than the alloc size then only allocate what's needed.
1199-
// Once it goes over the allocation size then allocate in chunks.
1200-
if db.datasz <= db.AllocSize {
1201-
sz = db.datasz
1202-
} else {
1203-
sz += db.AllocSize
1204-
}
1205-
1206-
if !db.readOnly && db.MaxSize > 0 && sz > db.MaxSize {
1207-
lg.Errorf("[GOOS: %s, GOARCH: %s] maximum db size reached, size: %d, db.MaxSize: %d", runtime.GOOS, runtime.GOARCH, sz, db.MaxSize)
1208-
return berrors.ErrMaxSizeReached
1209-
}
1235+
sz = db.growSize(db.datasz, sz)
12101236

12111237
// Truncate and fsync to ensure file size metadata is flushed.
12121238
// https://github.com/boltdb/bolt/issues/284
@@ -1234,6 +1260,16 @@ func (db *DB) grow(sz int) error {
12341260
return nil
12351261
}
12361262

1263+
func (db *DB) growSize(mmapSize, growSize int) int {
1264+
// If the data is smaller than the alloc size then only allocate what's needed.
1265+
// Once it goes over the allocation size then allocate in chunks.
1266+
if mmapSize <= db.AllocSize {
1267+
return mmapSize
1268+
} else {
1269+
return growSize + db.AllocSize
1270+
}
1271+
}
1272+
12371273
func (db *DB) IsReadOnly() bool {
12381274
return db.readOnly
12391275
}
@@ -1333,7 +1369,7 @@ type Options struct {
13331369
// PageSize overrides the default OS page size.
13341370
PageSize int
13351371

1336-
// MaxSize sets the maximum size of the data file. <=0 means no maximum.
1372+
// MaxSize sets the maximum size of the data file. A value <= 0 means no limit.
13371373
MaxSize int
13381374

13391375
// NoSync sets the initial value of DB.NoSync. Normally this can just be

db_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1688,7 +1688,7 @@ func TestDB_MaxSizeExceededDoesNotGrow(t *testing.T) {
16881688
maxSize int // 0 means unset
16891689
// expected file size after re-open (0 means "same as baseSize")
16901690
wantSize int64
1691-
wantOpenErr error // nil means open must succeed
1691+
wantOpenErr error
16921692
}{
16931693
{
16941694
// On Windows, mmap expands the file to the mapped size immediately.

0 commit comments

Comments
 (0)