Skip to content

Commit 624ddf8

Browse files
Paul E. Murphypmur
Paul E. Murphy
authored andcommitted
Add aix directory synchronization support
AIX doesn't support a proper flock like linux, but it seems to have enough support for process level file locking using fcntl. For #2035
1 parent 93b5d4e commit 624ddf8

File tree

2 files changed

+159
-2
lines changed

2 files changed

+159
-2
lines changed

dir_aix.go

+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
//go:build aix
2+
// +build aix
3+
4+
/*
5+
* Copyright 2024 Dgraph Labs, Inc. and Contributors
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
package badger
21+
22+
import (
23+
"fmt"
24+
"os"
25+
"path/filepath"
26+
"sync"
27+
28+
"golang.org/x/sys/unix"
29+
30+
"github.com/dgraph-io/badger/v4/y"
31+
)
32+
33+
type directoryLockGuard struct {
34+
// The absolute path to our pid/lock file.
35+
path string
36+
// Was this a shared lock for a read-only database?
37+
readOnly bool
38+
}
39+
40+
// AIX flocking is file x process, not fd x file x process like linux. We can
41+
// only hold one descriptor with a lock open at any given time.
42+
type aixFlock struct {
43+
file *os.File
44+
count int
45+
readOnly bool
46+
}
47+
48+
// Keep a map of locks synchronized by a mutex.
49+
var aixFlockMap = map[string]*aixFlock{}
50+
var aixFlockMapLock sync.Mutex
51+
52+
// acquireDirectoryLock gets a lock on the directory by creating the file
53+
// dirPath/pidFileName. A registry of directory locks is maintained to
54+
// avoid multiple open file descriptors of the directory lock file in the
55+
// same process.
56+
func acquireDirectoryLock(dirPath string, pidFileName string, readOnly bool) (
57+
*directoryLockGuard, error) {
58+
59+
// Convert to absolute path so that Release still works even if we do an unbalanced
60+
// chdir in the meantime.
61+
absPidFilePath, err := filepath.Abs(filepath.Join(dirPath, pidFileName))
62+
if err != nil {
63+
return nil, y.Wrapf(err, "cannot get absolute path for pid lock file")
64+
}
65+
66+
aixFlockMapLock.Lock()
67+
defer aixFlockMapLock.Unlock()
68+
69+
lg := &directoryLockGuard{absPidFilePath, readOnly}
70+
71+
if lock, fnd := aixFlockMap[absPidFilePath]; fnd {
72+
if !readOnly || lock.readOnly != readOnly {
73+
fmt.Printf("error\n")
74+
return nil, fmt.Errorf(
75+
"Cannot acquire directory lock on %q. Another process is using this Badger database.", dirPath)
76+
}
77+
lock.count++
78+
} else {
79+
// This is the first acquirer, set up a lock file and register it.
80+
f, err := os.OpenFile(absPidFilePath, os.O_RDWR|os.O_CREATE, 0666)
81+
if err != nil {
82+
return nil, y.Wrapf(err, "cannot create/open pid file %q", absPidFilePath)
83+
}
84+
85+
opts := unix.F_WRLCK
86+
if readOnly {
87+
opts = unix.F_RDLCK
88+
}
89+
90+
flckt := unix.Flock_t{int16(opts), 0, 0, 0, 0, 0, 0}
91+
err = unix.FcntlFlock(uintptr(f.Fd()), unix.F_SETLK, &flckt)
92+
if err != nil {
93+
f.Close()
94+
return nil, y.Wrapf(err,
95+
"Cannot acquire directory lock on %q. Another process is using this Badger database.", dirPath)
96+
}
97+
98+
if !readOnly {
99+
f.Truncate(0)
100+
// Write our pid to the file.
101+
_, err = f.Write([]byte(fmt.Sprintf("%d\n", os.Getpid())))
102+
if err != nil {
103+
f.Close()
104+
return nil, y.Wrapf(err,
105+
"Cannot write pid file %q", absPidFilePath)
106+
}
107+
}
108+
aixFlockMap[absPidFilePath] = &aixFlock{f, 1, readOnly}
109+
}
110+
return lg, nil
111+
}
112+
113+
// Release deletes the pid file and releases our lock on the directory.
114+
func (guard *directoryLockGuard) release() error {
115+
var err error
116+
117+
aixFlockMapLock.Lock()
118+
defer aixFlockMapLock.Unlock()
119+
120+
lock := aixFlockMap[guard.path]
121+
lock.count--
122+
if lock.count == 0 {
123+
if !lock.readOnly {
124+
// Try to clear the PID if we succeed.
125+
lock.file.Truncate(0)
126+
}
127+
128+
if closeErr := lock.file.Close(); err == nil {
129+
err = closeErr
130+
}
131+
delete(aixFlockMap, guard.path)
132+
guard.path = ""
133+
}
134+
135+
return err
136+
}
137+
138+
// openDir opens a directory for syncing.
139+
func openDir(path string) (*os.File, error) { return os.Open(path) }
140+
141+
// When you create or delete a file, you have to ensure the directory entry for the file is synced
142+
// in order to guarantee the file is visible (if the system crashes). (See the man page for fsync,
143+
// or see https://github.com/coreos/etcd/issues/6368 for an example.)
144+
func syncDir(dir string) error {
145+
f, err := openDir(dir)
146+
if err != nil {
147+
return y.Wrapf(err, "While opening directory: %s.", dir)
148+
}
149+
150+
err = f.Sync()
151+
closeErr := f.Close()
152+
if err != nil {
153+
// TODO: ignore the failure on AIX. It complains with invalid argument.
154+
// return y.Wrapf(err, "While syncing directory: %s.", dir)
155+
}
156+
return y.Wrapf(closeErr, "While closing directory: %s.", dir)
157+
}

dir_unix.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
//go:build !windows && !plan9
2-
// +build !windows,!plan9
1+
//go:build !windows && !plan9 && !aix
2+
// +build !windows,!plan9,!aix
33

44
/*
55
* Copyright 2017 Dgraph Labs, Inc. and Contributors

0 commit comments

Comments
 (0)