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: 2 additions & 0 deletions pkg/logservice/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func NewService(
return nil, err
}
if err := store.loadMetadata(); err != nil {
_ = store.close()
return nil, err
}
startCtx, cancelStart := context.WithCancel(context.Background())
Expand All @@ -148,6 +149,7 @@ func NewService(
}
defer cancelStart()
if err := store.startReplicas(startCtx); err != nil {
_ = store.close()
return nil, err
}
pool := &sync.Pool{}
Expand Down
73 changes: 73 additions & 0 deletions pkg/logservice/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package logservice

import (
"context"
"errors"
"fmt"
"math"
"runtime/debug"
Expand All @@ -37,6 +38,19 @@ import (
"github.com/stretchr/testify/require"
)

type failOpenFS struct {
vfs.FS
path string
err error
}

func (f *failOpenFS) Open(name string, opts ...vfs.OpenOption) (vfs.File, error) {
if name == f.path {
return nil, f.err
}
return f.FS.Open(name, opts...)
}

func runServiceTest(t *testing.T,
hakeeper bool, startReplica bool, fn func(*testing.T, *Service)) {
defer leaktest.AfterTest(t)()
Expand Down Expand Up @@ -105,6 +119,65 @@ func TestNewService(t *testing.T) {
assert.NoError(t, service.Close())
}

func TestNewServiceClosesStoreOnMetadataFailure(t *testing.T) {
defer leaktest.AfterTest(t)()
cfg := getServiceTestConfig()
fs := cfg.FS
defer vfs.ReportLeakedFD(fs, t)

md := metadata.LogStore{UUID: cfg.UUID}
require.NoError(t, createMetadataFile(cfg.DataDir, logMetadataFilename, &md, fs))

injectedErr := errors.New("injected metadata open failure")
cfg.FS = &failOpenFS{
FS: fs,
path: fs.PathJoin(cfg.DataDir, logMetadataFilename),
err: injectedErr,
}
service, err := NewService(cfg, newFS(), nil)
require.Nil(t, service)
require.ErrorIs(t, err, injectedErr)

// Reopening the same NodeHost proves that constructor failure released its
// directory lock, network listeners, and background workers.
cfg.FS = fs
service, err = NewService(cfg, newFS(), nil)
require.NoError(t, err)
require.NoError(t, service.Close())
}

func TestNewServiceClosesStoreOnReplicaStartFailure(t *testing.T) {
defer leaktest.AfterTest(t)()
cfg := getServiceTestConfig()
defer vfs.ReportLeakedFD(cfg.FS, t)

service, err := NewService(cfg, newFS(), nil)
require.NoError(t, err)
members := map[uint64]dragonboat.Target{1: service.ID()}
require.NoError(t, service.store.startReplica(1, 1, members, false))
require.NoError(t, service.Close())

record := metadata.LogShard{
LogShardRecord: metadata.LogShardRecord{ShardID: 1},
ReplicaID: 1,
}
md := metadata.LogStore{
UUID: cfg.UUID,
Shards: []metadata.LogShard{record, record},
}
require.NoError(t, createMetadataFile(cfg.DataDir, logMetadataFilename, &md, cfg.FS))

service, err = NewService(cfg, newFS(), nil)
require.Nil(t, service)
require.ErrorIs(t, err, dragonboat.ErrShardAlreadyExist)

md.Shards = md.Shards[:1]
require.NoError(t, createMetadataFile(cfg.DataDir, logMetadataFilename, &md, cfg.FS))
service, err = NewService(cfg, newFS(), nil)
require.NoError(t, err)
require.NoError(t, service.Close())
}

func TestNewServiceRetry(t *testing.T) {
defer leaktest.AfterTest(t)()
cfg0 := getServiceTestConfig()
Expand Down
Loading