database: Prevent write transaction with low disk space

Currently the user is not informed of issues that are due to low disk
space (such as block processing failures and resulting orphaning
blocks).  Instead, check for available disk space before starting a
write transaction to preventively fail and inform the user of the
problem.
This commit is contained in:
Steven Roose 2018-04-08 02:21:57 +02:00
parent 2be2f12b35
commit 203a9130fc
5 changed files with 89 additions and 0 deletions

View file

@ -62,6 +62,11 @@ const (
// the database was attempted against a read-only transaction.
ErrTxNotWritable
// ErrAvailableDiskSpace indicates that the user is running out of
// disk space. The database preventively decided to not allow the
// transaction to prevent causing hard-to-detect problems.
ErrAvailableDiskSpace
// **************************************
// Errors related to metadata operations.
// **************************************
@ -143,6 +148,7 @@ var errorCodeStrings = map[ErrorCode]string{
ErrCorruption: "ErrCorruption",
ErrTxClosed: "ErrTxClosed",
ErrTxNotWritable: "ErrTxNotWritable",
ErrAvailableDiskSpace: "ErrAvailableDiskSpace",
ErrBucketNotFound: "ErrBucketNotFound",
ErrBucketExists: "ErrBucketExists",
ErrBucketNameRequired: "ErrBucketNameRequired",

View file

@ -27,6 +27,7 @@ func TestErrorCodeStringer(t *testing.T) {
{database.ErrCorruption, "ErrCorruption"},
{database.ErrTxClosed, "ErrTxClosed"},
{database.ErrTxNotWritable, "ErrTxNotWritable"},
{database.ErrAvailableDiskSpace, "ErrAvailableDiskSpace"},
{database.ErrBucketNotFound, "ErrBucketNotFound"},
{database.ErrBucketExists, "ErrBucketExists"},
{database.ErrBucketNameRequired, "ErrBucketNameRequired"},

View file

@ -43,6 +43,13 @@ const (
// The serialized block index row format is:
// <blocklocation><blockheader>
blockHdrOffset = blockLocSize
// bytesMiB is the number of bytes in a mebibyte.
bytesMiB = 1024 * 1024
// minAvailableSpaceUpdate is the minimum space available (in bytes) to
// allow a write transaction. The value is 25 MiB.
minAvailableSpaceUpdate = 25 * bytesMiB
)
var (
@ -1752,6 +1759,22 @@ func (db *db) Type() string {
// which is used by the managed transaction code while the database method
// returns the interface.
func (db *db) begin(writable bool) (*transaction, error) {
// Make sure there is enough available disk space so we can inform the
// user of the problem instead of causing a db failure.
if writable {
freeSpace, err := getAvailableDiskSpace()
if err != nil {
return nil, makeDbErr(database.ErrDriverSpecific,
"failed to inspect available disk space", err)
}
if freeSpace < minAvailableSpaceUpdate {
errMsg := fmt.Sprintf("available disk space too low: "+
"%.1f MiB", float64(freeSpace)/float64(bytesMiB))
return nil, makeDbErr(database.ErrAvailableDiskSpace,
errMsg, nil)
}
}
// Whenever a new writable transaction is started, grab the write lock
// to ensure only a single write transaction can be active at the same
// time. This lock will not be released until the transaction is

View file

@ -0,0 +1,26 @@
// +build !windows
// Copyright (c) 2013-2018 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package ffldb
import (
"os"
"syscall"
)
// getAvailableDiskSpace returns the number of bytes of available disk space.
func getAvailableDiskSpace() (uint64, error) {
var stat syscall.Statfs_t
wd, err := os.Getwd()
if err != nil {
return 0, err
}
syscall.Statfs(wd, &stat)
return stat.Bavail * uint64(stat.Bsize), nil
}

View file

@ -0,0 +1,33 @@
// +build windows
// Copyright (c) 2013-2018 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package ffldb
import (
"os"
"syscall"
"unsafe"
)
// getAvailableDiskSpace returns the number of bytes of available disk space.
func getAvailableDiskSpace() (uint64, error) {
wd, err := os.Getwd()
if err != nil {
return 0, err
}
h := syscall.MustLoadDLL("kernel32.dll")
c := h.MustFindProc("GetDiskFreeSpaceExW")
var freeBytes int64
_, _, err := c.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(wd))),
uintptr(unsafe.Pointer(&freeBytes)), nil, nil)
if err != nil {
return 0, err
}
return uint64(freeBytes), nil
}