aboutsummaryrefslogtreecommitdiff
path: root/libpod/lock/in_memory_locks.go
blob: f00f01032da4f0c7bdc20a8f5eb20a09ec201a80 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package lock

import (
	"errors"
	"fmt"
	"sync"
)

// Mutex holds a single mutex and whether it has been allocated.
type Mutex struct {
	id        uint32
	lock      sync.Mutex
	allocated bool
}

// ID retrieves the ID of the mutex
func (m *Mutex) ID() uint32 {
	return m.id
}

// Lock locks the mutex
func (m *Mutex) Lock() {
	m.lock.Lock()
}

// Unlock unlocks the mutex
func (m *Mutex) Unlock() {
	m.lock.Unlock()
}

// Free deallocates the mutex to allow its reuse
func (m *Mutex) Free() error {
	m.allocated = false

	return nil
}

// InMemoryManager is a lock manager that allocates and retrieves local-only
// locks - that is, they are not multiprocess. This lock manager is intended
// purely for unit and integration testing and should not be used in production
// deployments.
type InMemoryManager struct {
	locks     []*Mutex
	numLocks  uint32
	localLock sync.Mutex
}

// NewInMemoryManager creates a new in-memory lock manager with the given number
// of locks.
func NewInMemoryManager(numLocks uint32) (Manager, error) {
	if numLocks == 0 {
		return nil, errors.New("must provide a non-zero number of locks")
	}

	manager := new(InMemoryManager)
	manager.numLocks = numLocks
	manager.locks = make([]*Mutex, numLocks)

	var i uint32
	for i = 0; i < numLocks; i++ {
		lock := new(Mutex)
		lock.id = i
		manager.locks[i] = lock
	}

	return manager, nil
}

// AllocateLock allocates a lock from the manager.
func (m *InMemoryManager) AllocateLock() (Locker, error) {
	m.localLock.Lock()
	defer m.localLock.Unlock()

	for _, lock := range m.locks {
		if !lock.allocated {
			lock.allocated = true
			return lock, nil
		}
	}

	return nil, errors.New("all locks have been allocated")
}

// RetrieveLock retrieves a lock from the manager.
func (m *InMemoryManager) RetrieveLock(id uint32) (Locker, error) {
	if id >= m.numLocks {
		return nil, fmt.Errorf("given lock ID %d is too large - this manager only supports lock indexes up to %d", id, m.numLocks-1)
	}

	return m.locks[id], nil
}

// AllocateAndRetrieveLock allocates a lock with the given ID (if not already in
// use) and returns it.
func (m *InMemoryManager) AllocateAndRetrieveLock(id uint32) (Locker, error) {
	if id >= m.numLocks {
		return nil, fmt.Errorf("given lock ID %d is too large - this manager only supports lock indexes up to %d", id, m.numLocks)
	}

	if m.locks[id].allocated {
		return nil, fmt.Errorf("given lock ID %d is already in use, cannot reallocate", id)
	}

	m.locks[id].allocated = true

	return m.locks[id], nil
}

// FreeAllLocks frees all locks.
// This function is DANGEROUS. Please read the full comment in locks.go before
// trying to use it.
func (m *InMemoryManager) FreeAllLocks() error {
	for _, lock := range m.locks {
		lock.allocated = false
	}

	return nil
}