summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/kernel/futex
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/sentry/kernel/futex')
-rw-r--r--pkg/sentry/kernel/futex/BUILD57
-rwxr-xr-xpkg/sentry/kernel/futex/atomicptr_bucket_unsafe.go37
-rwxr-xr-xpkg/sentry/kernel/futex/futex_state_autogen.go79
-rw-r--r--pkg/sentry/kernel/futex/futex_test.go530
-rwxr-xr-xpkg/sentry/kernel/futex/waiter_list.go193
5 files changed, 309 insertions, 587 deletions
diff --git a/pkg/sentry/kernel/futex/BUILD b/pkg/sentry/kernel/futex/BUILD
deleted file mode 100644
index c5021f2db..000000000
--- a/pkg/sentry/kernel/futex/BUILD
+++ /dev/null
@@ -1,57 +0,0 @@
-load("//tools:defs.bzl", "go_library", "go_test")
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-
-package(licenses = ["notice"])
-
-go_template_instance(
- name = "atomicptr_bucket",
- out = "atomicptr_bucket_unsafe.go",
- package = "futex",
- suffix = "Bucket",
- template = "//pkg/sync:generic_atomicptr",
- types = {
- "Value": "bucket",
- },
-)
-
-go_template_instance(
- name = "waiter_list",
- out = "waiter_list.go",
- package = "futex",
- prefix = "waiter",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*Waiter",
- "Linker": "*Waiter",
- },
-)
-
-go_library(
- name = "futex",
- srcs = [
- "atomicptr_bucket_unsafe.go",
- "futex.go",
- "waiter_list.go",
- ],
- visibility = ["//pkg/sentry:internal"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/context",
- "//pkg/log",
- "//pkg/sentry/memmap",
- "//pkg/sync",
- "//pkg/syserror",
- "//pkg/usermem",
- ],
-)
-
-go_test(
- name = "futex_test",
- size = "small",
- srcs = ["futex_test.go"],
- library = ":futex",
- deps = [
- "//pkg/sync",
- "//pkg/usermem",
- ],
-)
diff --git a/pkg/sentry/kernel/futex/atomicptr_bucket_unsafe.go b/pkg/sentry/kernel/futex/atomicptr_bucket_unsafe.go
new file mode 100755
index 000000000..d3fdf09b0
--- /dev/null
+++ b/pkg/sentry/kernel/futex/atomicptr_bucket_unsafe.go
@@ -0,0 +1,37 @@
+package futex
+
+import (
+ "sync/atomic"
+ "unsafe"
+)
+
+// An AtomicPtr is a pointer to a value of type Value that can be atomically
+// loaded and stored. The zero value of an AtomicPtr represents nil.
+//
+// Note that copying AtomicPtr by value performs a non-atomic read of the
+// stored pointer, which is unsafe if Store() can be called concurrently; in
+// this case, do `dst.Store(src.Load())` instead.
+//
+// +stateify savable
+type AtomicPtrBucket struct {
+ ptr unsafe.Pointer `state:".(*bucket)"`
+}
+
+func (p *AtomicPtrBucket) savePtr() *bucket {
+ return p.Load()
+}
+
+func (p *AtomicPtrBucket) loadPtr(v *bucket) {
+ p.Store(v)
+}
+
+// Load returns the value set by the most recent Store. It returns nil if there
+// has been no previous call to Store.
+func (p *AtomicPtrBucket) Load() *bucket {
+ return (*bucket)(atomic.LoadPointer(&p.ptr))
+}
+
+// Store sets the value returned by Load to x.
+func (p *AtomicPtrBucket) Store(x *bucket) {
+ atomic.StorePointer(&p.ptr, (unsafe.Pointer)(x))
+}
diff --git a/pkg/sentry/kernel/futex/futex_state_autogen.go b/pkg/sentry/kernel/futex/futex_state_autogen.go
new file mode 100755
index 000000000..68aa69daa
--- /dev/null
+++ b/pkg/sentry/kernel/futex/futex_state_autogen.go
@@ -0,0 +1,79 @@
+// automatically generated by stateify.
+
+package futex
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (x *AtomicPtrBucket) beforeSave() {}
+func (x *AtomicPtrBucket) save(m state.Map) {
+ x.beforeSave()
+ var ptr *bucket = x.savePtr()
+ m.SaveValue("ptr", ptr)
+}
+
+func (x *AtomicPtrBucket) afterLoad() {}
+func (x *AtomicPtrBucket) load(m state.Map) {
+ m.LoadValue("ptr", new(*bucket), func(y interface{}) { x.loadPtr(y.(*bucket)) })
+}
+
+func (x *bucket) beforeSave() {}
+func (x *bucket) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(&x.waiters) {
+ m.Failf("waiters is %#v, expected zero", &x.waiters)
+ }
+}
+
+func (x *bucket) afterLoad() {}
+func (x *bucket) load(m state.Map) {
+}
+
+func (x *Manager) beforeSave() {}
+func (x *Manager) save(m state.Map) {
+ x.beforeSave()
+ if !state.IsZeroValue(&x.privateBuckets) {
+ m.Failf("privateBuckets is %#v, expected zero", &x.privateBuckets)
+ }
+ m.Save("sharedBucket", &x.sharedBucket)
+}
+
+func (x *Manager) afterLoad() {}
+func (x *Manager) load(m state.Map) {
+ m.Load("sharedBucket", &x.sharedBucket)
+}
+
+func (x *waiterList) beforeSave() {}
+func (x *waiterList) save(m state.Map) {
+ x.beforeSave()
+ m.Save("head", &x.head)
+ m.Save("tail", &x.tail)
+}
+
+func (x *waiterList) afterLoad() {}
+func (x *waiterList) load(m state.Map) {
+ m.Load("head", &x.head)
+ m.Load("tail", &x.tail)
+}
+
+func (x *waiterEntry) beforeSave() {}
+func (x *waiterEntry) save(m state.Map) {
+ x.beforeSave()
+ m.Save("next", &x.next)
+ m.Save("prev", &x.prev)
+}
+
+func (x *waiterEntry) afterLoad() {}
+func (x *waiterEntry) load(m state.Map) {
+ m.Load("next", &x.next)
+ m.Load("prev", &x.prev)
+}
+
+func init() {
+ state.Register("pkg/sentry/kernel/futex.AtomicPtrBucket", (*AtomicPtrBucket)(nil), state.Fns{Save: (*AtomicPtrBucket).save, Load: (*AtomicPtrBucket).load})
+ state.Register("pkg/sentry/kernel/futex.bucket", (*bucket)(nil), state.Fns{Save: (*bucket).save, Load: (*bucket).load})
+ state.Register("pkg/sentry/kernel/futex.Manager", (*Manager)(nil), state.Fns{Save: (*Manager).save, Load: (*Manager).load})
+ state.Register("pkg/sentry/kernel/futex.waiterList", (*waiterList)(nil), state.Fns{Save: (*waiterList).save, Load: (*waiterList).load})
+ state.Register("pkg/sentry/kernel/futex.waiterEntry", (*waiterEntry)(nil), state.Fns{Save: (*waiterEntry).save, Load: (*waiterEntry).load})
+}
diff --git a/pkg/sentry/kernel/futex/futex_test.go b/pkg/sentry/kernel/futex/futex_test.go
deleted file mode 100644
index 7c5c7665b..000000000
--- a/pkg/sentry/kernel/futex/futex_test.go
+++ /dev/null
@@ -1,530 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package futex
-
-import (
- "math"
- "runtime"
- "sync/atomic"
- "syscall"
- "testing"
- "unsafe"
-
- "gvisor.dev/gvisor/pkg/sync"
- "gvisor.dev/gvisor/pkg/usermem"
-)
-
-// testData implements the Target interface, and allows us to
-// treat the address passed for futex operations as an index in
-// a byte slice for testing simplicity.
-type testData []byte
-
-const sizeofInt32 = 4
-
-func newTestData(size uint) testData {
- return make([]byte, size)
-}
-
-func (t testData) SwapUint32(addr usermem.Addr, new uint32) (uint32, error) {
- val := atomic.SwapUint32((*uint32)(unsafe.Pointer(&t[addr])), new)
- return val, nil
-}
-
-func (t testData) CompareAndSwapUint32(addr usermem.Addr, old, new uint32) (uint32, error) {
- if atomic.CompareAndSwapUint32((*uint32)(unsafe.Pointer(&t[addr])), old, new) {
- return old, nil
- }
- return atomic.LoadUint32((*uint32)(unsafe.Pointer(&t[addr]))), nil
-}
-
-func (t testData) LoadUint32(addr usermem.Addr) (uint32, error) {
- return atomic.LoadUint32((*uint32)(unsafe.Pointer(&t[addr]))), nil
-}
-
-func (t testData) GetSharedKey(addr usermem.Addr) (Key, error) {
- return Key{
- Kind: KindSharedMappable,
- Offset: uint64(addr),
- }, nil
-}
-
-func futexKind(private bool) string {
- if private {
- return "private"
- }
- return "shared"
-}
-
-func newPreparedTestWaiter(t *testing.T, m *Manager, ta Target, addr usermem.Addr, private bool, val uint32, bitmask uint32) *Waiter {
- w := NewWaiter()
- if err := m.WaitPrepare(w, ta, addr, private, val, bitmask); err != nil {
- t.Fatalf("WaitPrepare failed: %v", err)
- }
- return w
-}
-
-func TestFutexWake(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(sizeofInt32)
-
- // Start waiting for wakeup.
- w := newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(w)
-
- // Perform a wakeup.
- if n, err := m.Wake(d, 0, private, ^uint32(0), 1); err != nil || n != 1 {
- t.Errorf("Wake: got (%d, %v), wanted (1, nil)", n, err)
- }
-
- // Expect the waiter to have been woken.
- if !w.woken() {
- t.Error("waiter not woken")
- }
- })
- }
-}
-
-func TestFutexWakeBitmask(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(sizeofInt32)
-
- // Start waiting for wakeup.
- w := newPreparedTestWaiter(t, m, d, 0, private, 0, 0x0000ffff)
- defer m.WaitComplete(w)
-
- // Perform a wakeup using the wrong bitmask.
- if n, err := m.Wake(d, 0, private, 0xffff0000, 1); err != nil || n != 0 {
- t.Errorf("Wake with non-matching bitmask: got (%d, %v), wanted (0, nil)", n, err)
- }
-
- // Expect the waiter to still be waiting.
- if w.woken() {
- t.Error("waiter woken unexpectedly")
- }
-
- // Perform a wakeup using the right bitmask.
- if n, err := m.Wake(d, 0, private, 0x00000001, 1); err != nil || n != 1 {
- t.Errorf("Wake with matching bitmask: got (%d, %v), wanted (1, nil)", n, err)
- }
-
- // Expect that the waiter was woken.
- if !w.woken() {
- t.Error("waiter not woken")
- }
- })
- }
-}
-
-func TestFutexWakeTwo(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(sizeofInt32)
-
- // Start three waiters waiting for wakeup.
- var ws [3]*Waiter
- for i := range ws {
- ws[i] = newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(ws[i])
- }
-
- // Perform two wakeups.
- const wakeups = 2
- if n, err := m.Wake(d, 0, private, ^uint32(0), 2); err != nil || n != wakeups {
- t.Errorf("Wake: got (%d, %v), wanted (%d, nil)", n, err, wakeups)
- }
-
- // Expect that exactly two waiters were woken.
- // We don't get guarantees about exactly which two,
- // (although we expect them to be w1 and w2).
- awake := 0
- for i := range ws {
- if ws[i].woken() {
- awake++
- }
- }
- if awake != wakeups {
- t.Errorf("got %d woken waiters, wanted %d", awake, wakeups)
- }
- })
- }
-}
-
-func TestFutexWakeUnrelated(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(2 * sizeofInt32)
-
- // Start two waiters waiting for wakeup on different addresses.
- w1 := newPreparedTestWaiter(t, m, d, 0*sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w1)
- w2 := newPreparedTestWaiter(t, m, d, 1*sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w2)
-
- // Perform two wakeups on the second address.
- if n, err := m.Wake(d, 1*sizeofInt32, private, ^uint32(0), 2); err != nil || n != 1 {
- t.Errorf("Wake: got (%d, %v), wanted (1, nil)", n, err)
- }
-
- // Expect that only the second waiter was woken.
- if w1.woken() {
- t.Error("w1 woken unexpectedly")
- }
- if !w2.woken() {
- t.Error("w2 not woken")
- }
- })
- }
-}
-
-func TestWakeOpEmpty(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(2 * sizeofInt32)
-
- // Perform wakeups with no waiters.
- if n, err := m.WakeOp(d, 0, sizeofInt32, private, 10, 10, 0); err != nil || n != 0 {
- t.Fatalf("WakeOp: got (%d, %v), wanted (0, nil)", n, err)
- }
- })
- }
-}
-
-func TestWakeOpFirstNonEmpty(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(8)
-
- // Add two waiters on address 0.
- w1 := newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(w1)
- w2 := newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(w2)
-
- // Perform 10 wakeups on address 0.
- if n, err := m.WakeOp(d, 0, sizeofInt32, private, 10, 0, 0); err != nil || n != 2 {
- t.Errorf("WakeOp: got (%d, %v), wanted (2, nil)", n, err)
- }
-
- // Expect that both waiters were woken.
- if !w1.woken() {
- t.Error("w1 not woken")
- }
- if !w2.woken() {
- t.Error("w2 not woken")
- }
- })
- }
-}
-
-func TestWakeOpSecondNonEmpty(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(8)
-
- // Add two waiters on address sizeofInt32.
- w1 := newPreparedTestWaiter(t, m, d, sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w1)
- w2 := newPreparedTestWaiter(t, m, d, sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w2)
-
- // Perform 10 wakeups on address sizeofInt32 (contingent on
- // d.Op(0), which should succeed).
- if n, err := m.WakeOp(d, 0, sizeofInt32, private, 0, 10, 0); err != nil || n != 2 {
- t.Errorf("WakeOp: got (%d, %v), wanted (2, nil)", n, err)
- }
-
- // Expect that both waiters were woken.
- if !w1.woken() {
- t.Error("w1 not woken")
- }
- if !w2.woken() {
- t.Error("w2 not woken")
- }
- })
- }
-}
-
-func TestWakeOpSecondNonEmptyFailingOp(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(8)
-
- // Add two waiters on address sizeofInt32.
- w1 := newPreparedTestWaiter(t, m, d, sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w1)
- w2 := newPreparedTestWaiter(t, m, d, sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w2)
-
- // Perform 10 wakeups on address sizeofInt32 (contingent on
- // d.Op(1), which should fail).
- if n, err := m.WakeOp(d, 0, sizeofInt32, private, 0, 10, 1); err != nil || n != 0 {
- t.Errorf("WakeOp: got (%d, %v), wanted (0, nil)", n, err)
- }
-
- // Expect that neither waiter was woken.
- if w1.woken() {
- t.Error("w1 woken unexpectedly")
- }
- if w2.woken() {
- t.Error("w2 woken unexpectedly")
- }
- })
- }
-}
-
-func TestWakeOpAllNonEmpty(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(8)
-
- // Add two waiters on address 0.
- w1 := newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(w1)
- w2 := newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(w2)
-
- // Add two waiters on address sizeofInt32.
- w3 := newPreparedTestWaiter(t, m, d, sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w3)
- w4 := newPreparedTestWaiter(t, m, d, sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w4)
-
- // Perform 10 wakeups on address 0 (unconditionally), and 10
- // wakeups on address sizeofInt32 (contingent on d.Op(0), which
- // should succeed).
- if n, err := m.WakeOp(d, 0, sizeofInt32, private, 10, 10, 0); err != nil || n != 4 {
- t.Errorf("WakeOp: got (%d, %v), wanted (4, nil)", n, err)
- }
-
- // Expect that all waiters were woken.
- if !w1.woken() {
- t.Error("w1 not woken")
- }
- if !w2.woken() {
- t.Error("w2 not woken")
- }
- if !w3.woken() {
- t.Error("w3 not woken")
- }
- if !w4.woken() {
- t.Error("w4 not woken")
- }
- })
- }
-}
-
-func TestWakeOpAllNonEmptyFailingOp(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(8)
-
- // Add two waiters on address 0.
- w1 := newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(w1)
- w2 := newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(w2)
-
- // Add two waiters on address sizeofInt32.
- w3 := newPreparedTestWaiter(t, m, d, sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w3)
- w4 := newPreparedTestWaiter(t, m, d, sizeofInt32, private, 0, ^uint32(0))
- defer m.WaitComplete(w4)
-
- // Perform 10 wakeups on address 0 (unconditionally), and 10
- // wakeups on address sizeofInt32 (contingent on d.Op(1), which
- // should fail).
- if n, err := m.WakeOp(d, 0, sizeofInt32, private, 10, 10, 1); err != nil || n != 2 {
- t.Errorf("WakeOp: got (%d, %v), wanted (2, nil)", n, err)
- }
-
- // Expect that only the first two waiters were woken.
- if !w1.woken() {
- t.Error("w1 not woken")
- }
- if !w2.woken() {
- t.Error("w2 not woken")
- }
- if w3.woken() {
- t.Error("w3 woken unexpectedly")
- }
- if w4.woken() {
- t.Error("w4 woken unexpectedly")
- }
- })
- }
-}
-
-func TestWakeOpSameAddress(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(8)
-
- // Add four waiters on address 0.
- var ws [4]*Waiter
- for i := range ws {
- ws[i] = newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(ws[i])
- }
-
- // Perform 1 wakeup on address 0 (unconditionally), and 1 wakeup
- // on address 0 (contingent on d.Op(0), which should succeed).
- const wakeups = 2
- if n, err := m.WakeOp(d, 0, 0, private, 1, 1, 0); err != nil || n != wakeups {
- t.Errorf("WakeOp: got (%d, %v), wanted (%d, nil)", n, err, wakeups)
- }
-
- // Expect that exactly two waiters were woken.
- awake := 0
- for i := range ws {
- if ws[i].woken() {
- awake++
- }
- }
- if awake != wakeups {
- t.Errorf("got %d woken waiters, wanted %d", awake, wakeups)
- }
- })
- }
-}
-
-func TestWakeOpSameAddressFailingOp(t *testing.T) {
- for _, private := range []bool{false, true} {
- t.Run(futexKind(private), func(t *testing.T) {
- m := NewManager()
- d := newTestData(8)
-
- // Add four waiters on address 0.
- var ws [4]*Waiter
- for i := range ws {
- ws[i] = newPreparedTestWaiter(t, m, d, 0, private, 0, ^uint32(0))
- defer m.WaitComplete(ws[i])
- }
-
- // Perform 1 wakeup on address 0 (unconditionally), and 1 wakeup
- // on address 0 (contingent on d.Op(1), which should fail).
- const wakeups = 1
- if n, err := m.WakeOp(d, 0, 0, private, 1, 1, 1); err != nil || n != wakeups {
- t.Errorf("WakeOp: got (%d, %v), wanted (%d, nil)", n, err, wakeups)
- }
-
- // Expect that exactly one waiter was woken.
- awake := 0
- for i := range ws {
- if ws[i].woken() {
- awake++
- }
- }
- if awake != wakeups {
- t.Errorf("got %d woken waiters, wanted %d", awake, wakeups)
- }
- })
- }
-}
-
-const (
- testMutexSize = sizeofInt32
- testMutexLocked uint32 = 1
- testMutexUnlocked uint32 = 0
-)
-
-// testMutex ties together a testData slice, an address, and a
-// futex manager in order to implement the sync.Locker interface.
-// Beyond being used as a Locker, this is a simple mechanism for
-// changing the underlying values for simpler tests.
-type testMutex struct {
- a usermem.Addr
- d testData
- m *Manager
-}
-
-func newTestMutex(addr usermem.Addr, d testData, m *Manager) *testMutex {
- return &testMutex{a: addr, d: d, m: m}
-}
-
-// Lock acquires the testMutex.
-// This may wait for it to be available via the futex manager.
-func (t *testMutex) Lock() {
- for {
- // Attempt to grab the lock.
- if atomic.CompareAndSwapUint32(
- (*uint32)(unsafe.Pointer(&t.d[t.a])),
- testMutexUnlocked,
- testMutexLocked) {
- // Lock held.
- return
- }
-
- // Wait for it to be "not locked".
- w := NewWaiter()
- err := t.m.WaitPrepare(w, t.d, t.a, true, testMutexLocked, ^uint32(0))
- if err == syscall.EAGAIN {
- continue
- }
- if err != nil {
- // Should never happen.
- panic("WaitPrepare returned unexpected error: " + err.Error())
- }
- <-w.C
- t.m.WaitComplete(w)
- }
-}
-
-// Unlock releases the testMutex.
-// This will notify any waiters via the futex manager.
-func (t *testMutex) Unlock() {
- // Unlock.
- atomic.StoreUint32((*uint32)(unsafe.Pointer(&t.d[t.a])), testMutexUnlocked)
-
- // Notify all waiters.
- t.m.Wake(t.d, t.a, true, ^uint32(0), math.MaxInt32)
-}
-
-// This function was shamelessly stolen from mutex_test.go.
-func HammerMutex(l sync.Locker, loops int, cdone chan bool) {
- for i := 0; i < loops; i++ {
- l.Lock()
- runtime.Gosched()
- l.Unlock()
- }
- cdone <- true
-}
-
-func TestMutexStress(t *testing.T) {
- m := NewManager()
- d := newTestData(testMutexSize)
- tm := newTestMutex(0*testMutexSize, d, m)
- c := make(chan bool)
-
- for i := 0; i < 10; i++ {
- go HammerMutex(tm, 1000, c)
- }
-
- for i := 0; i < 10; i++ {
- <-c
- }
-}
diff --git a/pkg/sentry/kernel/futex/waiter_list.go b/pkg/sentry/kernel/futex/waiter_list.go
new file mode 100755
index 000000000..1b7a92b62
--- /dev/null
+++ b/pkg/sentry/kernel/futex/waiter_list.go
@@ -0,0 +1,193 @@
+package futex
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type waiterElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (waiterElementMapper) linkerFor(elem *Waiter) *Waiter { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type waiterList struct {
+ head *Waiter
+ tail *Waiter
+}
+
+// Reset resets list l to the empty state.
+func (l *waiterList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *waiterList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *waiterList) Front() *Waiter {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *waiterList) Back() *Waiter {
+ return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+func (l *waiterList) Len() (count int) {
+ for e := l.Front(); e != nil; e = e.Next() {
+ count++
+ }
+ return count
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *waiterList) PushFront(e *Waiter) {
+ linker := waiterElementMapper{}.linkerFor(e)
+ linker.SetNext(l.head)
+ linker.SetPrev(nil)
+ if l.head != nil {
+ waiterElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *waiterList) PushBack(e *Waiter) {
+ linker := waiterElementMapper{}.linkerFor(e)
+ linker.SetNext(nil)
+ linker.SetPrev(l.tail)
+ if l.tail != nil {
+ waiterElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *waiterList) PushBackList(m *waiterList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ waiterElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ waiterElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *waiterList) InsertAfter(b, e *Waiter) {
+ bLinker := waiterElementMapper{}.linkerFor(b)
+ eLinker := waiterElementMapper{}.linkerFor(e)
+
+ a := bLinker.Next()
+
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ bLinker.SetNext(e)
+
+ if a != nil {
+ waiterElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *waiterList) InsertBefore(a, e *Waiter) {
+ aLinker := waiterElementMapper{}.linkerFor(a)
+ eLinker := waiterElementMapper{}.linkerFor(e)
+
+ b := aLinker.Prev()
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ aLinker.SetPrev(e)
+
+ if b != nil {
+ waiterElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *waiterList) Remove(e *Waiter) {
+ linker := waiterElementMapper{}.linkerFor(e)
+ prev := linker.Prev()
+ next := linker.Next()
+
+ if prev != nil {
+ waiterElementMapper{}.linkerFor(prev).SetNext(next)
+ } else {
+ l.head = next
+ }
+
+ if next != nil {
+ waiterElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else {
+ l.tail = prev
+ }
+
+ linker.SetNext(nil)
+ linker.SetPrev(nil)
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type waiterEntry struct {
+ next *Waiter
+ prev *Waiter
+}
+
+// Next returns the entry that follows e in the list.
+func (e *waiterEntry) Next() *Waiter {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *waiterEntry) Prev() *Waiter {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *waiterEntry) SetNext(elem *Waiter) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *waiterEntry) SetPrev(elem *Waiter) {
+ e.prev = elem
+}