Skip to content

Commit

Permalink
Merge pull request #1376 from mtrmac/recursive-remove
Browse files Browse the repository at this point in the history
Remove lockfile.Locker.RecursiveLock (alternative to #1344)
  • Loading branch information
vrothberg committed Oct 11, 2022
2 parents 7de6744 + 082a816 commit 925647d
Show file tree
Hide file tree
Showing 7 changed files with 6 additions and 236 deletions.
4 changes: 0 additions & 4 deletions containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -623,10 +623,6 @@ func (r *containerStore) Lock() {
r.lockfile.Lock()
}

func (r *containerStore) RecursiveLock() {
r.lockfile.RecursiveLock()
}

func (r *containerStore) RLock() {
r.lockfile.RLock()
}
Expand Down
4 changes: 0 additions & 4 deletions images.go
Original file line number Diff line number Diff line change
Expand Up @@ -804,10 +804,6 @@ func (r *imageStore) Lock() {
r.lockfile.Lock()
}

func (r *imageStore) RecursiveLock() {
r.lockfile.RecursiveLock()
}

func (r *imageStore) RLock() {
r.lockfile.RLock()
}
Expand Down
4 changes: 0 additions & 4 deletions layers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1920,10 +1920,6 @@ func (r *layerStore) Lock() {
r.lockfile.Lock()
}

func (r *layerStore) RecursiveLock() {
r.lockfile.RecursiveLock()
}

func (r *layerStore) RLock() {
r.lockfile.RLock()
}
Expand Down
4 changes: 0 additions & 4 deletions pkg/lockfile/lockfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ type Locker interface {
// - tried to lock a read-only lock-file
Lock()

// Acquire a writer lock recursively, allowing for recursive acquisitions
// within the same process space.
RecursiveLock()

// Unlock the lock.
// The default unix implementation panics if:
// - unlocking an unlocked lock
Expand Down
190 changes: 0 additions & 190 deletions pkg/lockfile/lockfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,45 +107,6 @@ func subLock(l *namedLocker) (io.WriteCloser, io.ReadCloser, error) {
return wc, rc, nil
}

// subRecursiveLockMain is a child process which opens the lock file, closes
// stdout to indicate that it has acquired the lock, waits for stdin to get
// closed, and then unlocks the file.
func subRecursiveLockMain() {
if len(os.Args) != 2 {
logrus.Fatalf("expected two args, got %d", len(os.Args))
}
tf, err := GetLockfile(os.Args[1])
if err != nil {
logrus.Fatalf("error opening lock file %q: %v", os.Args[1], err)
}
tf.RecursiveLock()
os.Stdout.Close()
io.Copy(io.Discard, os.Stdin)
tf.Unlock()
}

// subRecursiveLock starts a child process. If it doesn't return an error, the
// caller should wait for the first ReadCloser by reading it until it receives
// an EOF. At that point, the child will have acquired the lock. It can then
// signal that the child should release the lock by closing the WriteCloser.
func subRecursiveLock(l *namedLocker) (io.WriteCloser, io.ReadCloser, error) {
cmd := reexec.Command("subRecursiveLock", l.name)
wc, err := cmd.StdinPipe()
if err != nil {
return nil, nil, err
}
rc, err := cmd.StdoutPipe()
if err != nil {
return nil, nil, err
}
go func() {
if err = cmd.Run(); err != nil {
logrus.Errorf("Running subLock: %v", err)
}
}()
return wc, rc, nil
}

// subRLockMain is a child process which opens the lock file, closes stdout to
// indicate that it has acquired the read lock, waits for stdin to get closed,
// and then unlocks the file.
Expand Down Expand Up @@ -188,7 +149,6 @@ func subRLock(l *namedLocker) (io.WriteCloser, io.ReadCloser, error) {
func init() {
reexec.Register("subTouch", subTouchMain)
reexec.Register("subRLock", subRLockMain)
reexec.Register("subRecursiveLock", subRecursiveLockMain)
reexec.Register("subLock", subLockMain)
}

Expand Down Expand Up @@ -276,18 +236,6 @@ func TestLockfileWrite(t *testing.T) {
l.Unlock()
}

func TestRecursiveLockfileWrite(t *testing.T) {
l, err := getTempLockfile()
require.Nil(t, err, "error getting temporary lock file")
defer os.Remove(l.name)

l.RecursiveLock()
assert.True(t, l.Locked(), "Locked() said we didn't have a write lock")
l.RecursiveLock()
l.Unlock()
l.Unlock()
}

func TestROLockfileWrite(t *testing.T) {
l, err := getTempROLockfile()
require.Nil(t, err, "error getting temporary lock file")
Expand Down Expand Up @@ -403,44 +351,6 @@ func TestLockfileReadConcurrent(t *testing.T) {
}
}

func TestLockfileRecursiveWrite(t *testing.T) {
// NOTE: given we're in the same process space, it's effectively the same as
// reader lock.

l, err := getTempLockfile()
require.Nil(t, err, "error getting temporary lock file")
defer os.Remove(l.name)

// the test below is inspired by the stdlib's rwmutex tests
numReaders := 1000
locked := make(chan bool)
unlocked := make(chan bool)
done := make(chan bool)

for i := 0; i < numReaders; i++ {
go func() {
l.RecursiveLock()
locked <- true
<-unlocked
l.Unlock()
done <- true
}()
}

// Wait for all parallel locks to succeed
for i := 0; i < numReaders; i++ {
<-locked
}
// Instruct all parallel locks to unlock
for i := 0; i < numReaders; i++ {
unlocked <- true
}
// Wait for all parallel locks to be unlocked
for i := 0; i < numReaders; i++ {
<-done
}
}

func TestLockfileMixedConcurrent(t *testing.T) {
l, err := getTempLockfile()
require.Nil(t, err, "error getting temporary lock file")
Expand Down Expand Up @@ -495,63 +405,6 @@ func TestLockfileMixedConcurrent(t *testing.T) {
}
}

func TestLockfileMixedConcurrentRecursiveWriters(t *testing.T) {
// It's effectively the same tests as with mixed readers & writers but calling
// RecursiveLocks() instead.

l, err := getTempLockfile()
require.Nil(t, err, "error getting temporary lock file")
defer os.Remove(l.name)

counter := int32(0)
diff := int32(10000)
numIterations := 10
numReaders := 100
numWriters := 50

done := make(chan bool)

// A writer always adds `diff` to the counter. Hence, `diff` is the
// only valid value in the critical section.
writer := func(c *int32) {
for i := 0; i < numIterations; i++ {
l.Lock()
tmp := atomic.AddInt32(c, diff)
assert.True(t, tmp == diff, "counter should be %d but instead is %d", diff, tmp)
time.Sleep(100 * time.Millisecond)
atomic.AddInt32(c, diff*(-1))
l.Unlock()
}
done <- true
}

// A reader always adds `1` to the counter. Hence,
// [1,`numReaders*numIterations`] are valid values.
reader := func(c *int32) {
for i := 0; i < numIterations; i++ {
l.RecursiveLock()
tmp := atomic.AddInt32(c, 1)
assert.True(t, tmp >= 1 && tmp < diff)
time.Sleep(100 * time.Millisecond)
atomic.AddInt32(c, -1)
l.Unlock()
}
done <- true
}

for i := 0; i < numReaders; i++ {
go reader(&counter)
// schedule a writer every 2nd iteration
if i%2 == 1 {
go writer(&counter)
}
}

for i := 0; i < numReaders+numWriters; i++ {
<-done
}
}

func TestLockfileMultiprocessRead(t *testing.T) {
l, err := getTempLockfile()
require.Nil(t, err, "error getting temporary lock file")
Expand Down Expand Up @@ -638,49 +491,6 @@ func TestLockfileMultiprocessWrite(t *testing.T) {
assert.True(t, whighest == 1, "expected to have no more than one writer lock active at a time, had %d", whighest)
}

func TestLockfileMultiprocessRecursiveWrite(t *testing.T) {
l, err := getTempLockfile()
require.Nil(t, err, "error getting temporary lock file")
defer os.Remove(l.name)
var wg sync.WaitGroup
var wcounter, whighest int64
var highestMutex sync.Mutex
subs := make([]struct {
stdin io.WriteCloser
stdout io.ReadCloser
}, 10)
for i := range subs {
stdin, stdout, err := subRecursiveLock(l)
require.Nil(t, err, "error starting subprocess %d to take a write lock", i+1)
subs[i].stdin = stdin
subs[i].stdout = stdout
}
for i := range subs {
wg.Add(1)
go func(i int) {
io.Copy(io.Discard, subs[i].stdout)
if testing.Verbose() {
t.Logf("\tchild %4d acquired the recursive write lock\n", i+1)
}
workingWcounter := atomic.AddInt64(&wcounter, 1)
highestMutex.Lock()
if workingWcounter > whighest {
whighest = workingWcounter
}
highestMutex.Unlock()
time.Sleep(1 * time.Second)
atomic.AddInt64(&wcounter, -1)
if testing.Verbose() {
t.Logf("\ttelling child %4d to release the recursive write lock\n", i+1)
}
subs[i].stdin.Close()
wg.Done()
}(i)
}
wg.Wait()
assert.True(t, whighest == 1, "expected to have no more than one writer lock active at a time, had %d", whighest)
}

func TestLockfileMultiprocessMixed(t *testing.T) {
l, err := getTempLockfile()
require.Nil(t, err, "error getting temporary lock file")
Expand Down
29 changes: 5 additions & 24 deletions pkg/lockfile/lockfile_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ type lockfile struct {
locktype int16
locked bool
ro bool
recursive bool
}

const lastWriterIDSize = 64 // This must be the same as len(stringid.GenerateRandomID)
Expand Down Expand Up @@ -131,7 +130,7 @@ func createLockerForPath(path string, ro bool) (Locker, error) {

// lock locks the lockfile via FCTNL(2) based on the specified type and
// command.
func (l *lockfile) lock(lType int16, recursive bool) {
func (l *lockfile) lock(lType int16) {
lk := unix.Flock_t{
Type: lType,
Whence: int16(os.SEEK_SET),
Expand All @@ -142,13 +141,7 @@ func (l *lockfile) lock(lType int16, recursive bool) {
case unix.F_RDLCK:
l.rwMutex.RLock()
case unix.F_WRLCK:
if recursive {
// NOTE: that's okay as recursive is only set in RecursiveLock(), so
// there's no need to protect against hypothetical RDLCK cases.
l.rwMutex.RLock()
} else {
l.rwMutex.Lock()
}
l.rwMutex.Lock()
default:
panic(fmt.Sprintf("attempted to acquire a file lock of unrecognized type %d", lType))
}
Expand All @@ -171,7 +164,6 @@ func (l *lockfile) lock(lType int16, recursive bool) {
}
l.locktype = lType
l.locked = true
l.recursive = recursive
l.counter++
}

Expand All @@ -180,24 +172,13 @@ func (l *lockfile) Lock() {
if l.ro {
panic("can't take write lock on read-only lock file")
} else {
l.lock(unix.F_WRLCK, false)
}
}

// RecursiveLock locks the lockfile as a writer but allows for recursive
// acquisitions within the same process space. Note that RLock() will be called
// if it's a lockTypReader lock.
func (l *lockfile) RecursiveLock() {
if l.ro {
l.RLock()
} else {
l.lock(unix.F_WRLCK, true)
l.lock(unix.F_WRLCK)
}
}

// LockRead locks the lockfile as a reader.
func (l *lockfile) RLock() {
l.lock(unix.F_RDLCK, false)
l.lock(unix.F_RDLCK)
}

// Unlock unlocks the lockfile.
Expand All @@ -224,7 +205,7 @@ func (l *lockfile) Unlock() {
// file lock.
unix.Close(int(l.fd))
}
if l.locktype == unix.F_RDLCK || l.recursive {
if l.locktype == unix.F_RDLCK {
l.rwMutex.RUnlock()
} else {
l.rwMutex.Unlock()
Expand Down
7 changes: 1 addition & 6 deletions pkg/lockfile/lockfile_windows.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build windows
// +build windows

package lockfile
Expand Down Expand Up @@ -36,12 +37,6 @@ func (l *lockfile) Lock() {
l.locked = true
}

func (l *lockfile) RecursiveLock() {
// We don't support Windows but a recursive writer-lock in one process-space
// is really a writer lock, so just panic.
panic("not supported")
}

func (l *lockfile) RLock() {
l.mu.Lock()
l.locked = true
Expand Down

0 comments on commit 925647d

Please sign in to comment.