Skip to content

Commit c2e588a

Browse files
committed
db: refactor directory locking
Refactor the acquisition of directory locks, moving it outside the logic of Open and embedding the directory locks within the resolvedDirs struct. This better encapsulates the locks and removes some subtle error handling within Open.
1 parent 77c0947 commit c2e588a

File tree

8 files changed

+62
-77
lines changed

8 files changed

+62
-77
lines changed

db.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,8 +1554,6 @@ func (d *DB) Close() error {
15541554
// is still valid for the checks below.
15551555
err = firstError(err, d.mu.versions.close())
15561556

1557-
err = firstError(err, d.dirs.Close())
1558-
15591557
d.readState.val.unrefLocked()
15601558

15611559
current := d.mu.versions.currentVersion()
@@ -1628,6 +1626,7 @@ func (d *DB) Close() error {
16281626
if v := d.mu.snapshots.count(); v > 0 {
16291627
err = firstError(err, errors.Errorf("leaked snapshots: %d open snapshots on DB %p", v, d))
16301628
}
1629+
err = firstError(err, d.dirs.Close())
16311630

16321631
if d.iterTracker != nil {
16331632
d.iterTracker.Close()

internal/base/directory_lock.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
package base
66

77
import (
8+
"fmt"
89
"io"
910
"os"
11+
"strings"
1012
"sync/atomic"
1113

1214
"github.com/cockroachdb/errors"
@@ -19,6 +21,19 @@ type DirLockSet struct {
1921
acquired []*DirLock
2022
}
2123

24+
func (s *DirLockSet) String() string {
25+
var buf strings.Builder
26+
buf.WriteString("{")
27+
for i, l := range s.acquired {
28+
if i > 0 {
29+
buf.WriteString(", ")
30+
}
31+
fmt.Fprint(&buf, l.dirname)
32+
}
33+
buf.WriteString("}")
34+
return buf.String()
35+
}
36+
2237
// Close releases all of the directory locks in the set.
2338
func (s *DirLockSet) Close() error {
2439
var err error

open.go

Lines changed: 33 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,14 @@ func Open(dirname string, opts *Options) (db *DB, err error) {
103103
}
104104

105105
// Open the database and WAL directories first.
106-
dirs, err := prepareAndOpenDirs(dirname, opts)
106+
dirs, err := prepareOpenAndLockDirs(dirname, opts)
107107
if err != nil {
108-
return nil, errors.Wrapf(err, "error opening database at %q", dirname)
108+
err = errors.Wrapf(err, "error opening database at %q", dirname)
109+
err = errors.CombineErrors(err, dirs.Close())
110+
return nil, err
109111
}
110112
defer maybeCleanUp(dirs.Close)
111113

112-
var dirLocks base.DirLockSet
113-
114114
rs, err := recoverState(opts, dirname)
115115
if err != nil {
116116
return nil, err
@@ -294,8 +294,8 @@ func Open(dirname string, opts *Options) (db *DB, err error) {
294294
})
295295

296296
walOpts := wal.Options{
297-
Primary: wal.Dir{FS: opts.FS, Dirname: dirs.WALPrimary.Dirname},
298-
Secondary: wal.Dir{},
297+
Primary: dirs.WALPrimary,
298+
Secondary: dirs.WALSecondary,
299299
MinUnflushedWALNum: wal.NumWAL(d.mu.versions.minUnflushedLogNum),
300300
MaxNumRecyclableLogs: opts.MemTableStopWritesThreshold + 1,
301301
NoSyncOnClose: opts.NoSyncOnClose,
@@ -308,45 +308,7 @@ func Open(dirname string, opts *Options) (db *DB, err error) {
308308
EventListener: walEventListenerAdaptor{l: opts.EventListener},
309309
WriteWALSyncOffsets: func() bool { return d.FormatMajorVersion() >= FormatWALSyncChunks },
310310
}
311-
// Ensure we release the WAL directory locks if we fail to open the
312-
// database. If we fail before initializing the WAL manager, this defer is
313-
// responsible for releasing the locks. If we fail after initializing the
314-
// WAL manager, closing the WAL manager will release the locks.
315-
//
316-
// TODO(jackson): Open's cleanup error handling logic is convoluted; can we
317-
// simplify it?
318-
defer maybeCleanUp(func() (err error) {
319-
if d.mu.log.manager == nil {
320-
if walOpts.Primary.Lock != nil {
321-
err = errors.CombineErrors(err, walOpts.Primary.Lock.Close())
322-
}
323-
if walOpts.Secondary.Lock != nil {
324-
err = errors.CombineErrors(err, walOpts.Secondary.Lock.Close())
325-
}
326-
return err
327-
}
328-
return nil
329-
})
330-
331-
// Lock the dedicated WAL directory, if configured.
332-
if dirs.WALPrimary.Dirname != dirname {
333-
walOpts.Primary.Lock, err = dirLocks.AcquireOrValidate(opts.WALDirLock, dirs.WALPrimary.Dirname, opts.FS)
334-
if err != nil {
335-
return nil, err
336-
}
337-
}
338311
if !opts.ReadOnly && opts.WALFailover != nil {
339-
walOpts.Secondary = opts.WALFailover.Secondary
340-
// Lock the secondary WAL directory, if distinct from the data directory
341-
// and primary WAL directory.
342-
if dirs.WALSecondary.Dirname != dirname && dirs.WALSecondary.Dirname != dirs.WALPrimary.Dirname {
343-
walOpts.Secondary.Lock, err = dirLocks.AcquireOrValidate(
344-
opts.WALFailover.Secondary.Lock, dirs.WALSecondary.Dirname, opts.WALFailover.Secondary.FS)
345-
if err != nil {
346-
return nil, err
347-
}
348-
}
349-
walOpts.Secondary.Dirname = dirs.WALSecondary.Dirname
350312
walOpts.FailoverOptions = opts.WALFailover.FailoverOptions
351313
walOpts.FailoverWriteAndSyncLatency = prometheus.NewHistogram(prometheus.HistogramOpts{
352314
Buckets: FsyncLatencyBuckets,
@@ -617,20 +579,23 @@ type resolvedDirs struct {
617579

618580
// Close closes the data directory and the directory locks.
619581
func (d *resolvedDirs) Close() error {
620-
err := errors.CombineErrors(
621-
d.DataDir.Close(),
622-
d.DirLocks.Close(),
623-
)
582+
var err error
583+
if d.DataDir != nil {
584+
err = errors.CombineErrors(err, d.DataDir.Close())
585+
}
586+
err = errors.CombineErrors(err, d.DirLocks.Close())
624587
*d = resolvedDirs{}
625588
return err
626589
}
627590

628-
// prepareAndOpenDirs resolves the various directory paths indicated within
591+
// prepareOpenAndLockDirs resolves the various directory paths indicated within
629592
// Options (substituting {store_path} relative paths as necessary), creates the
630-
// directories if they don't exist, and locks the database directory.
593+
// directories if they don't exist, and acquires directory locks as necessary.
631594
//
632-
// Returns an error if ReadOnly is set and the directories don't exist.
633-
func prepareAndOpenDirs(dirname string, opts *Options) (dirs *resolvedDirs, err error) {
595+
// Returns an error if ReadOnly is set and the directories don't exist. Always
596+
// returns a non-nil resolvedDirs that may be closed to release all resources
597+
// acquired before any error was encountered.
598+
func prepareOpenAndLockDirs(dirname string, opts *Options) (dirs *resolvedDirs, err error) {
634599
dirs = &resolvedDirs{}
635600
dirs.WALPrimary.Dirname = dirname
636601
dirs.WALPrimary.FS = opts.FS
@@ -676,7 +641,6 @@ func prepareAndOpenDirs(dirname string, opts *Options) (dirs *resolvedDirs, err
676641
// Check that the wal dir exists.
677642
walDir, err := opts.FS.OpenDir(dirs.WALPrimary.Dirname)
678643
if err != nil {
679-
dirs.DataDir.Close()
680644
return dirs, err
681645
}
682646
walDir.Close()
@@ -685,10 +649,24 @@ func prepareAndOpenDirs(dirname string, opts *Options) (dirs *resolvedDirs, err
685649
// Lock the database directory.
686650
_, err = dirs.DirLocks.AcquireOrValidate(opts.Lock, dirname, opts.FS)
687651
if err != nil {
688-
dirs.DataDir.Close()
689652
return dirs, err
690653
}
691-
654+
// Lock the dedicated WAL directory, if configured.
655+
if dirs.WALPrimary.Dirname != dirname {
656+
dirs.WALPrimary.Lock, err = dirs.DirLocks.AcquireOrValidate(opts.WALDirLock, dirs.WALPrimary.Dirname, opts.FS)
657+
if err != nil {
658+
return dirs, err
659+
}
660+
}
661+
// Lock the secondary WAL directory, if distinct from the data directory
662+
// and primary WAL directory.
663+
if opts.WALFailover != nil && dirs.WALSecondary.Dirname != dirname && dirs.WALSecondary.Dirname != dirs.WALPrimary.Dirname {
664+
dirs.WALSecondary.Lock, err = dirs.DirLocks.AcquireOrValidate(
665+
opts.WALFailover.Secondary.Lock, dirs.WALSecondary.Dirname, dirs.WALSecondary.FS)
666+
if err != nil {
667+
return dirs, err
668+
}
669+
}
692670
return dirs, nil
693671
}
694672

open_test.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -501,13 +501,15 @@ func TestOpenAlreadyLocked(t *testing.T) {
501501
// Run tests for different filesystems.
502502
t.Run("memfs", func(t *testing.T) {
503503
for _, tc := range testCases {
504-
mem := vfs.NewMem()
505-
var tmpDirs [4]string
506-
for i := range tmpDirs {
507-
tmpDirs[i] = mem.PathJoin("dir", fmt.Sprintf("%d", i))
508-
require.NoError(t, mem.MkdirAll(tmpDirs[i], 0755), "Failed to create temp dir %s", tmpDirs[i])
509-
}
510-
runTest(t, tmpDirs, tc.setupLocks, mem)
504+
t.Run(tc.name, func(t *testing.T) {
505+
tmpDirs := [4]string{"dir/0", "dir/1", "dir/2", "dir/3"}
506+
mem := vfs.NewMem()
507+
for i := range tmpDirs {
508+
require.NoError(t, mem.MkdirAll(tmpDirs[i], 0755),
509+
"Failed to create temp dir %s", tmpDirs[i])
510+
}
511+
runTest(t, tmpDirs, tc.setupLocks, mem)
512+
})
511513
}
512514
})
513515

testdata/cleaner

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ open-dir: db_wal
1515
close: db_wal
1616
open-dir: db
1717
lock: db/LOCK
18+
lock: db_wal/LOCK
1819
open-dir: db
1920
open-dir: db
2021
open-dir: db
@@ -24,7 +25,6 @@ create: db/marker.manifest.000001.MANIFEST-000001
2425
sync: db/marker.manifest.000001.MANIFEST-000001
2526
close: db/marker.manifest.000001.MANIFEST-000001
2627
sync: db
27-
lock: db_wal/LOCK
2828
open-dir: db_wal
2929
create: db/temporary.000002.dbtmp
3030
sync: db/temporary.000002.dbtmp
@@ -148,6 +148,7 @@ open-dir: db1_wal
148148
close: db1_wal
149149
open-dir: db1
150150
lock: db1/LOCK
151+
lock: db1_wal/LOCK
151152
open-dir: db1
152153
open-dir: db1
153154
open-dir: db1
@@ -157,7 +158,6 @@ create: db1/marker.manifest.000001.MANIFEST-000001
157158
sync: db1/marker.manifest.000001.MANIFEST-000001
158159
close: db1/marker.manifest.000001.MANIFEST-000001
159160
sync: db1
160-
lock: db1_wal/LOCK
161161
open-dir: db1_wal
162162
create: db1/temporary.000002.dbtmp
163163
sync: db1/temporary.000002.dbtmp
@@ -242,12 +242,12 @@ open-dir: db1_wal
242242
close: db1_wal
243243
open-dir: db1
244244
lock: db1/LOCK
245+
lock: db1_wal/LOCK
245246
open-dir: db1
246247
open-dir: db1
247248
open-dir: db1
248249
open: db1/MANIFEST-000001
249250
close: db1/MANIFEST-000001
250-
lock: db1_wal/LOCK
251251
remove: db1_wal/000003.log
252252
open-dir: db1_wal
253253
open: db1/OPTIONS-000002

testdata/event_listener

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ open-dir: wal
1414
close: wal
1515
open-dir: db
1616
lock: db/LOCK
17+
lock: wal/LOCK
1718
open-dir: db
1819
open-dir: db
1920
open-dir: db
@@ -24,7 +25,6 @@ sync: db/marker.manifest.000001.MANIFEST-000001
2425
close: db/marker.manifest.000001.MANIFEST-000001
2526
sync: db
2627
[JOB 1] MANIFEST created 000001
27-
lock: wal/LOCK
2828
open-dir: wal
2929
create: db/temporary.000002.dbtmp
3030
sync: db/temporary.000002.dbtmp

wal/failover_manager.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -746,12 +746,6 @@ func (wm *failoverManager) Close() error {
746746
for _, f := range wm.dirHandles {
747747
err = firstError(err, f.Close())
748748
}
749-
if wm.opts.Primary.Lock != nil {
750-
err = firstError(err, wm.opts.Primary.Lock.Close())
751-
}
752-
if wm.opts.Secondary.Lock != nil {
753-
err = firstError(err, wm.opts.Secondary.Lock.Close())
754-
}
755749
return err
756750
}
757751

wal/standalone_manager.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,6 @@ func (m *StandaloneManager) Close() error {
251251
_, err = m.w.Close()
252252
}
253253
err = firstError(err, m.walDir.Close())
254-
if m.o.Primary.Lock != nil {
255-
err = firstError(err, m.o.Primary.Lock.Close())
256-
}
257254
return err
258255
}
259256

0 commit comments

Comments
 (0)