Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ if(POLICY CMP0135)
endif()

project(foundationdb
VERSION 7.3.66
VERSION 7.3.69
DESCRIPTION "FoundationDB is a scalable, fault-tolerant, ordered key-value store with full ACID transactions."
HOMEPAGE_URL "http://www.foundationdb.org/"
LANGUAGES C CXX ASM)
Expand Down
1 change: 1 addition & 0 deletions bindings/go/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ set(SRCS
src/fdb/transaction.go
src/fdb/directory/directoryLayer.go
src/fdb/errors.go
src/fdb/errors_test.go
src/fdb/keyselector.go
src/fdb/tuple/tuple.go
src/fdb/cluster.go
Expand Down
84 changes: 61 additions & 23 deletions bindings/go/src/fdb/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ import (
"runtime"
)

// ErrMultiVersionClientUnavailable is returned when the multi-version client API is unavailable.
// Client status information is available only when such API is enabled.
var ErrMultiVersionClientUnavailable = errors.New("multi-version client API is unavailable")

// Database is a handle to a FoundationDB database. Database is a lightweight
// object that may be efficiently copied, and is safe for concurrent use by
// multiple goroutines.
Expand Down Expand Up @@ -61,15 +65,21 @@ type DatabaseOptions struct {
}

// Close will close the Database and clean up all resources.
// You have to ensure that you're not resuing this database.
func (d *Database) Close() {
// It must be called exactly once for each created database.
// You have to ensure that you're not reusing this database
// after it has been closed.
func (d Database) Close() {
// Remove database object from the cached databases
if d.isCached {
delete(openDatabases, d.clusterFile)
}

if d.ptr == nil {
return
}

// Destroy the database
d.destroy()
C.fdb_database_destroy(d.ptr)
}

func (opt DatabaseOptions) setOpt(code int, param []byte) error {
Expand All @@ -78,14 +88,6 @@ func (opt DatabaseOptions) setOpt(code int, param []byte) error {
}, param)
}

func (d *database) destroy() {
if d.ptr == nil {
return
}

C.fdb_database_destroy(d.ptr)
}

// CreateTransaction returns a new FoundationDB transaction. It is generally
// preferable to use the (Database).Transact method, which handles
// automatically creating and committing a transaction with appropriate retry
Expand All @@ -98,6 +100,9 @@ func (d Database) CreateTransaction() (Transaction, error) {
}

t := &transaction{outt, d}
// transactions cannot be destroyed explicitly if any future is still potentially used
// thus the GC is used to figure out when all Go wrapper objects for futures have gone out of scope,
// making the transaction ready to be garbage-collected.
runtime.SetFinalizer(t, (*transaction).destroy)

return Transaction{t}, nil
Expand All @@ -110,13 +115,16 @@ func (d Database) CreateTransaction() (Transaction, error) {
// process address is the form of IP:Port pair.
func (d Database) RebootWorker(address string, checkFile bool, suspendDuration int) error {
t := &futureInt64{
future: newFuture(C.fdb_database_reboot_worker(
d.ptr,
byteSliceToPtr([]byte(address)),
C.int(len(address)),
C.fdb_bool_t(boolToInt(checkFile)),
C.int(suspendDuration),
),
future: newFutureWithDb(
d.database,
nil,
C.fdb_database_reboot_worker(
d.ptr,
byteSliceToPtr([]byte(address)),
C.int(len(address)),
C.fdb_bool_t(boolToInt(checkFile)),
C.int(suspendDuration),
),
),
}

Expand All @@ -129,6 +137,29 @@ func (d Database) RebootWorker(address string, checkFile bool, suspendDuration i
return err
}

// GetClientStatus returns a JSON byte slice containing database client-side status information.
// At the top level the report describes the status of the Multi-Version Client database - its initialization state, the protocol version, the available client versions; it also embeds the status of the actual version-specific database and within it the addresses of various FDB server roles the client is aware of and their connection status.
// NOTE: ErrMultiVersionClientUnavailable will be returned if the Multi-Version client API was not enabled.
func (d Database) GetClientStatus() ([]byte, error) {
if apiVersion == 0 {
return nil, errAPIVersionUnset
}

st := &futureByteSlice{
future: newFutureWithDb(d.database, nil, C.fdb_database_get_client_status(d.ptr)),
}

b, err := st.Get()
if err != nil {
return nil, err
}
if len(b) == 0 {
return nil, ErrMultiVersionClientUnavailable
}

return b, nil
}

func retryable(wrapped func() (interface{}, error), onError func(Error) FutureNil) (ret interface{}, e error) {
for {
ret, e = wrapped()
Expand All @@ -142,7 +173,13 @@ func retryable(wrapped func() (interface{}, error), onError func(Error) FutureNi
// fdb.Error
var ep Error
if errors.As(e, &ep) {
e = onError(ep).Get()
processedErr := onError(ep).Get()
var newEp Error
if !errors.As(processedErr, &newEp) || newEp.Code != ep.Code {
// override original error only if not an Error or code changed
// fdb_transaction_on_error(), called by OnError, will currently almost always return same error as the original one
e = processedErr
}
}

// If OnError returns an error, then it's not
Expand Down Expand Up @@ -209,6 +246,8 @@ func (d Database) Transact(f func(Transaction) (interface{}, error)) (interface{
//
// The transaction is retried if the error is or wraps a retryable Error.
// The error is unwrapped.
// Read transactions are never committed and destroyed automatically via GC,
// once all their futures go out of scope.
//
// Do not return Future objects from the function provided to ReadTransact. The
// Transaction created by ReadTransact may be finalized at any point after
Expand All @@ -220,8 +259,8 @@ func (d Database) Transact(f func(Transaction) (interface{}, error)) (interface{
// Transaction, Snapshot and Database objects.
func (d Database) ReadTransact(f func(ReadTransaction) (interface{}, error)) (interface{}, error) {
tr, e := d.CreateTransaction()
// Any error here is non-retryable
if e != nil {
// Any error here is non-retryable
return nil, e
}

Expand All @@ -230,9 +269,8 @@ func (d Database) ReadTransact(f func(ReadTransaction) (interface{}, error)) (in

ret, e = f(tr)

if e == nil {
e = tr.Commit().Get()
}
// read-only transactions are not committed and will be destroyed automatically via GC,
// once all the futures go out of scope

return
}
Expand Down
4 changes: 3 additions & 1 deletion bindings/go/src/fdb/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ func (e Error) Error() string {
// SOMEDAY: these (along with others) should be coming from fdb.options?

var (
errNetworkNotSetup = Error{2008}
errNetworkNotSetup = Error{2008}
errNetworkAlreadySetup = Error{2009} // currently unused
errNetworkCannotBeRestarted = Error{2025} // currently unused

errAPIVersionUnset = Error{2200}
errAPIVersionAlreadySet = Error{2201}
Expand Down
62 changes: 62 additions & 0 deletions bindings/go/src/fdb/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* errors_test.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2024 Apple Inc. and the FoundationDB project 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.
*/

// FoundationDB Go API

package fdb

import (
"errors"
"fmt"
"testing"
)

const API_VERSION int = 740

func TestErrorWrapping(t *testing.T) {
MustAPIVersion(API_VERSION)
db := MustOpenDefault()

testCases := []error{
nil,
&Error{
Code: 2007,
},
fmt.Errorf("wrapped error: %w", &Error{
Code: 2007,
}),
Error{
Code: 2007,
},
fmt.Errorf("wrapped error: %w", Error{
Code: 2007,
}),
errors.New("custom error"),
}

for _, inputError := range testCases {
_, outputError := db.ReadTransact(func(rtr ReadTransaction) (interface{}, error) {
return nil, inputError
})
if inputError != outputError {
t.Errorf("expected error %v to be the same as %v", outputError, inputError)
}
}
}
Loading