diff --git a/Makefile b/Makefile index 1ff13c133..9ee21cae4 100644 --- a/Makefile +++ b/Makefile @@ -42,14 +42,14 @@ lint: .PHONY: test test: @echo "hashmap freelist test" - TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout ${TESTFLAGS_TIMEOUT} - TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} ./internal/... - TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} ./cmd/bbolt + BBOLT_VERIFY=all TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout ${TESTFLAGS_TIMEOUT} + BBOLT_VERIFY=all TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} ./internal/... + BBOLT_VERIFY=all TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} ./cmd/bbolt @echo "array freelist test" - TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout ${TESTFLAGS_TIMEOUT} - TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} ./internal/... - TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} ./cmd/bbolt + BBOLT_VERIFY=all TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout ${TESTFLAGS_TIMEOUT} + BBOLT_VERIFY=all TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} ./internal/... + BBOLT_VERIFY=all TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} ./cmd/bbolt .PHONY: coverage coverage: @@ -76,8 +76,8 @@ install-gofail: .PHONY: test-failpoint test-failpoint: @echo "[failpoint] hashmap freelist test" - TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint + BBOLT_VERIFY=all TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint @echo "[failpoint] array freelist test" - TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint + BBOLT_VERIFY=all TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint diff --git a/db.go b/db.go index 93a3bb329..65ef209df 100644 --- a/db.go +++ b/db.go @@ -15,9 +15,6 @@ import ( "go.etcd.io/bbolt/internal/common" ) -// When enabled, the database will perform assert function to check the slow-path code -var assertVerify = os.Getenv("BBOLT_VERIFY") == "true" - // The time elapsed between consecutive file locking attempts. const flockRetryTimeout = 50 * time.Millisecond @@ -1312,9 +1309,3 @@ type Info struct { Data uintptr PageSize int } - -func _assertVerify(conditionFunc func() bool, msg string, v ...interface{}) { - if assertVerify && !conditionFunc() { - panic(fmt.Sprintf("assertion failed: "+msg, v...)) - } -} diff --git a/freelist_hmap.go b/freelist_hmap.go index 39f75eed4..aa425e2db 100644 --- a/freelist_hmap.go +++ b/freelist_hmap.go @@ -8,7 +8,11 @@ import ( // hashmapFreeCount returns count of free pages(hashmap version) func (f *freelist) hashmapFreeCount() int { - _assertVerify(func() bool { return int(f.freePagesCount) == f.hashmapFreeCountSlow() }, "freePagesCount is out of sync with free pages map") + common.Verify(func() { + slow := f.hashmapFreeCountSlow() + common.Assert(int(f.freePagesCount) == slow, + "freePagesCount (%d) is out of sync with free pages map (%d)", f.freePagesCount, slow) + }) return int(f.freePagesCount) } diff --git a/freelist_test.go b/freelist_test.go index 7297055b4..9ece5098e 100644 --- a/freelist_test.go +++ b/freelist_test.go @@ -435,6 +435,11 @@ func newTestFreelist() *freelist { } func Test_freelist_hashmapGetFreePageIDs(t *testing.T) { + // Because we pre-create the freelist here, we'd fail the assertion that + // the computed count matches the freePagesCount. + reenable := common.DisableVerifications() + defer reenable() + f := newTestFreelist() if f.freelistType == FreelistArrayType { t.Skip() diff --git a/internal/common/utils.go b/internal/common/utils.go index c94e5c6bf..bdf82a7b0 100644 --- a/internal/common/utils.go +++ b/internal/common/utils.go @@ -7,13 +7,6 @@ import ( "unsafe" ) -// Assert will panic with a given formatted message if the given condition is false. -func Assert(condition bool, msg string, v ...interface{}) { - if !condition { - panic(fmt.Sprintf("assertion failed: "+msg, v...)) - } -} - func LoadBucket(buf []byte) *InBucket { return (*InBucket)(unsafe.Pointer(&buf[0])) } diff --git a/internal/common/verify.go b/internal/common/verify.go new file mode 100644 index 000000000..322e962a8 --- /dev/null +++ b/internal/common/verify.go @@ -0,0 +1,80 @@ +// Copyright 2022 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "fmt" + "os" + "strings" +) + +const ENV_VERIFY = "BBOLT_VERIFY" + +type VerificationType string + +const ( + ENV_VERIFY_VALUE_ALL VerificationType = "all" + ENV_VERIFY_VALUE_ASSERT VerificationType = "assert" +) + +func getEnvVerify() string { + return strings.ToLower(os.Getenv(ENV_VERIFY)) +} + +func IsVerificationEnabled(verification VerificationType) bool { + env := getEnvVerify() + return env == string(ENV_VERIFY_VALUE_ALL) || env == strings.ToLower(string(verification)) +} + +// EnableVerifications sets `ENV_VERIFY` and returns a function that +// can be used to bring the original settings. +func EnableVerifications(verification VerificationType) func() { + previousEnv := getEnvVerify() + os.Setenv(ENV_VERIFY, string(verification)) + return func() { + os.Setenv(ENV_VERIFY, previousEnv) + } +} + +// EnableAllVerifications enables verification and returns a function +// that can be used to bring the original settings. +func EnableAllVerifications() func() { + return EnableVerifications(ENV_VERIFY_VALUE_ALL) +} + +// DisableVerifications unsets `ENV_VERIFY` and returns a function that +// can be used to bring the original settings. +func DisableVerifications() func() { + previousEnv := getEnvVerify() + os.Unsetenv(ENV_VERIFY) + return func() { + os.Setenv(ENV_VERIFY, previousEnv) + } +} + +// Verify performs verification if the assertions are enabled. +// In the default setup running in tests and skipped in the production code. +func Verify(f func()) { + if IsVerificationEnabled(ENV_VERIFY_VALUE_ASSERT) { + f() + } +} + +// Assert will panic with a given formatted message if the given condition is false. +func Assert(condition bool, msg string, v ...any) { + if !condition { + panic(fmt.Sprintf("assertion failed: "+msg, v...)) + } +}