github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/kernel/fd_table_test.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package kernel 16 17 import ( 18 "runtime" 19 "testing" 20 21 "github.com/SagerNet/gvisor/pkg/context" 22 "github.com/SagerNet/gvisor/pkg/sentry/contexttest" 23 "github.com/SagerNet/gvisor/pkg/sentry/fs" 24 "github.com/SagerNet/gvisor/pkg/sentry/fs/filetest" 25 "github.com/SagerNet/gvisor/pkg/sentry/limits" 26 "github.com/SagerNet/gvisor/pkg/sync" 27 ) 28 29 const ( 30 // maxFD is the maximum FD to try to create in the map. 31 // 32 // This number of open files has been seen in the wild. 33 maxFD = 2 * 1024 34 ) 35 36 func runTest(t testing.TB, fn func(ctx context.Context, fdTable *FDTable, file *fs.File, limitSet *limits.LimitSet)) { 37 t.Helper() // Don't show in stacks. 38 39 // Create the limits and context. 40 limitSet := limits.NewLimitSet() 41 limitSet.Set(limits.NumberOfFiles, limits.Limit{maxFD, maxFD}, true) 42 ctx := contexttest.WithLimitSet(contexttest.Context(t), limitSet) 43 44 // Create a test file.; 45 file := filetest.NewTestFile(t) 46 47 // Create the table. 48 fdTable := new(FDTable) 49 fdTable.init() 50 51 // Run the test. 52 fn(ctx, fdTable, file, limitSet) 53 } 54 55 // TestFDTableMany allocates maxFD FDs, i.e. maxes out the FDTable, until there 56 // is no room, then makes sure that NewFDAt works and also that if we remove 57 // one and add one that works too. 58 func TestFDTableMany(t *testing.T) { 59 runTest(t, func(ctx context.Context, fdTable *FDTable, file *fs.File, _ *limits.LimitSet) { 60 for i := 0; i < maxFD; i++ { 61 if _, err := fdTable.NewFDs(ctx, 0, []*fs.File{file}, FDFlags{}); err != nil { 62 t.Fatalf("Allocated %v FDs but wanted to allocate %v", i, maxFD) 63 } 64 } 65 66 if _, err := fdTable.NewFDs(ctx, 0, []*fs.File{file}, FDFlags{}); err == nil { 67 t.Fatalf("fdTable.NewFDs(0, r) in full map: got nil, wanted error") 68 } 69 70 if err := fdTable.NewFDAt(ctx, 1, file, FDFlags{}); err != nil { 71 t.Fatalf("fdTable.NewFDAt(1, r, FDFlags{}): got %v, wanted nil", err) 72 } 73 74 i := int32(2) 75 fdTable.Remove(ctx, i) 76 if fds, err := fdTable.NewFDs(ctx, 0, []*fs.File{file}, FDFlags{}); err != nil || fds[0] != i { 77 t.Fatalf("Allocated %v FDs but wanted to allocate %v: %v", i, maxFD, err) 78 } 79 }) 80 } 81 82 func TestFDTableOverLimit(t *testing.T) { 83 runTest(t, func(ctx context.Context, fdTable *FDTable, file *fs.File, _ *limits.LimitSet) { 84 if _, err := fdTable.NewFDs(ctx, maxFD, []*fs.File{file}, FDFlags{}); err == nil { 85 t.Fatalf("fdTable.NewFDs(maxFD, f): got nil, wanted error") 86 } 87 88 if _, err := fdTable.NewFDs(ctx, maxFD-2, []*fs.File{file, file, file}, FDFlags{}); err == nil { 89 t.Fatalf("fdTable.NewFDs(maxFD-2, {f,f,f}): got nil, wanted error") 90 } 91 92 if fds, err := fdTable.NewFDs(ctx, maxFD-3, []*fs.File{file, file, file}, FDFlags{}); err != nil { 93 t.Fatalf("fdTable.NewFDs(maxFD-3, {f,f,f}): got %v, wanted nil", err) 94 } else { 95 for _, fd := range fds { 96 fdTable.Remove(ctx, fd) 97 } 98 } 99 100 if fds, err := fdTable.NewFDs(ctx, maxFD-1, []*fs.File{file}, FDFlags{}); err != nil || fds[0] != maxFD-1 { 101 t.Fatalf("fdTable.NewFDAt(1, r, FDFlags{}): got %v, wanted nil", err) 102 } 103 104 if fds, err := fdTable.NewFDs(ctx, 0, []*fs.File{file}, FDFlags{}); err != nil { 105 t.Fatalf("Adding an FD to a resized map: got %v, want nil", err) 106 } else if len(fds) != 1 || fds[0] != 0 { 107 t.Fatalf("Added an FD to a resized map: got %v, want {1}", fds) 108 } 109 }) 110 } 111 112 // TestFDTable does a set of simple tests to make sure simple adds, removes, 113 // GetRefs, and DecRefs work. The ordering is just weird enough that a 114 // table-driven approach seemed clumsy. 115 func TestFDTable(t *testing.T) { 116 runTest(t, func(ctx context.Context, fdTable *FDTable, file *fs.File, limitSet *limits.LimitSet) { 117 // Cap the limit at one. 118 limitSet.Set(limits.NumberOfFiles, limits.Limit{1, maxFD}, true) 119 120 if _, err := fdTable.NewFDs(ctx, 0, []*fs.File{file}, FDFlags{}); err != nil { 121 t.Fatalf("Adding an FD to an empty 1-size map: got %v, want nil", err) 122 } 123 124 if _, err := fdTable.NewFDs(ctx, 0, []*fs.File{file}, FDFlags{}); err == nil { 125 t.Fatalf("Adding an FD to a filled 1-size map: got nil, wanted an error") 126 } 127 128 // Remove the previous limit. 129 limitSet.Set(limits.NumberOfFiles, limits.Limit{maxFD, maxFD}, true) 130 131 if fds, err := fdTable.NewFDs(ctx, 0, []*fs.File{file}, FDFlags{}); err != nil { 132 t.Fatalf("Adding an FD to a resized map: got %v, want nil", err) 133 } else if len(fds) != 1 || fds[0] != 1 { 134 t.Fatalf("Added an FD to a resized map: got %v, want {1}", fds) 135 } 136 137 if err := fdTable.NewFDAt(ctx, 1, file, FDFlags{}); err != nil { 138 t.Fatalf("Replacing FD 1 via fdTable.NewFDAt(1, r, FDFlags{}): got %v, wanted nil", err) 139 } 140 141 if err := fdTable.NewFDAt(ctx, maxFD+1, file, FDFlags{}); err == nil { 142 t.Fatalf("Using an FD that was too large via fdTable.NewFDAt(%v, r, FDFlags{}): got nil, wanted an error", maxFD+1) 143 } 144 145 if ref, _ := fdTable.Get(1); ref == nil { 146 t.Fatalf("fdTable.Get(1): got nil, wanted %v", file) 147 } 148 149 if ref, _ := fdTable.Get(2); ref != nil { 150 t.Fatalf("fdTable.Get(2): got a %v, wanted nil", ref) 151 } 152 153 ref, _ := fdTable.Remove(ctx, 1) 154 if ref == nil { 155 t.Fatalf("fdTable.Remove(1) for an existing FD: failed, want success") 156 } 157 ref.DecRef(ctx) 158 159 if ref, _ := fdTable.Remove(ctx, 1); ref != nil { 160 t.Fatalf("r.Remove(1) for a removed FD: got success, want failure") 161 } 162 }) 163 } 164 165 func TestDescriptorFlags(t *testing.T) { 166 runTest(t, func(ctx context.Context, fdTable *FDTable, file *fs.File, _ *limits.LimitSet) { 167 if err := fdTable.NewFDAt(ctx, 2, file, FDFlags{CloseOnExec: true}); err != nil { 168 t.Fatalf("fdTable.NewFDAt(2, r, FDFlags{}): got %v, wanted nil", err) 169 } 170 171 newFile, flags := fdTable.Get(2) 172 if newFile == nil { 173 t.Fatalf("fdTable.Get(2): got a %v, wanted nil", newFile) 174 } 175 176 if !flags.CloseOnExec { 177 t.Fatalf("new File flags %v don't match original %d\n", flags, 0) 178 } 179 }) 180 } 181 182 func BenchmarkFDLookupAndDecRef(b *testing.B) { 183 b.StopTimer() // Setup. 184 185 runTest(b, func(ctx context.Context, fdTable *FDTable, file *fs.File, _ *limits.LimitSet) { 186 fds, err := fdTable.NewFDs(ctx, 0, []*fs.File{file, file, file, file, file}, FDFlags{}) 187 if err != nil { 188 b.Fatalf("fdTable.NewFDs: got %v, wanted nil", err) 189 } 190 191 b.StartTimer() // Benchmark. 192 for i := 0; i < b.N; i++ { 193 tf, _ := fdTable.Get(fds[i%len(fds)]) 194 tf.DecRef(ctx) 195 } 196 }) 197 } 198 199 func BenchmarkFDLookupAndDecRefConcurrent(b *testing.B) { 200 b.StopTimer() // Setup. 201 202 runTest(b, func(ctx context.Context, fdTable *FDTable, file *fs.File, _ *limits.LimitSet) { 203 fds, err := fdTable.NewFDs(ctx, 0, []*fs.File{file, file, file, file, file}, FDFlags{}) 204 if err != nil { 205 b.Fatalf("fdTable.NewFDs: got %v, wanted nil", err) 206 } 207 208 concurrency := runtime.GOMAXPROCS(0) 209 if concurrency < 4 { 210 concurrency = 4 211 } 212 each := b.N / concurrency 213 214 b.StartTimer() // Benchmark. 215 var wg sync.WaitGroup 216 for i := 0; i < concurrency; i++ { 217 wg.Add(1) 218 go func() { 219 defer wg.Done() 220 for i := 0; i < each; i++ { 221 tf, _ := fdTable.Get(fds[i%len(fds)]) 222 tf.DecRef(ctx) 223 } 224 }() 225 } 226 wg.Wait() 227 }) 228 }