gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/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 "gvisor.dev/gvisor/pkg/context" 22 "gvisor.dev/gvisor/pkg/sentry/contexttest" 23 "gvisor.dev/gvisor/pkg/sentry/limits" 24 "gvisor.dev/gvisor/pkg/sentry/vfs" 25 "gvisor.dev/gvisor/pkg/sync" 26 ) 27 28 const ( 29 // maxFD is the maximum FD to try to create in the map. 30 // 31 // This number of open files has been seen in the wild. 32 maxFD = 2 * 1024 33 ) 34 35 // testFD is a read-only FileDescriptionImpl representing a regular file. 36 type testFD struct { 37 vfsfd vfs.FileDescription 38 vfs.FileDescriptionDefaultImpl 39 vfs.DentryMetadataFileDescriptionImpl 40 vfs.NoLockFD 41 } 42 43 // Release implements FileDescriptionImpl.Release. 44 func (fd *testFD) Release(context.Context) {} 45 46 func newTestFD(ctx context.Context, vfsObj *vfs.VirtualFilesystem) *vfs.FileDescription { 47 vd := vfsObj.NewAnonVirtualDentry("testFD") 48 defer vd.DecRef(ctx) 49 var fd testFD 50 fd.vfsfd.Init(&fd, 0 /* flags */, vd.Mount(), vd.Dentry(), &vfs.FileDescriptionOptions{}) 51 return &fd.vfsfd 52 } 53 54 func runTest(t testing.TB, fn func(ctx context.Context, fdTable *FDTable, fd *vfs.FileDescription, limitSet *limits.LimitSet)) { 55 t.Helper() // Don't show in stacks. 56 57 // Create the limits and context. 58 limitSet := limits.NewLimitSet() 59 limitSet.Set(limits.NumberOfFiles, limits.Limit{maxFD, maxFD}, true) 60 ctx := contexttest.WithLimitSet(contexttest.Context(t), limitSet) 61 62 vfsObj := &vfs.VirtualFilesystem{} 63 if err := vfsObj.Init(ctx); err != nil { 64 t.Fatalf("VFS init: %v", err) 65 } 66 67 fd := newTestFD(ctx, vfsObj) 68 defer fd.DecRef(ctx) 69 70 // Create the table. 71 fdTable := new(FDTable) 72 fdTable.k = &Kernel{} 73 fdTable.k.MaxFDLimit.Store(MaxFdLimit) 74 fdTable.init() 75 76 // Run the test. 77 fn(ctx, fdTable, fd, limitSet) 78 } 79 80 // TestFDTableMany allocates maxFD FDs, i.e. maxes out the FDTable, until there 81 // is no room, then makes sure that NewFDAt works and also that if we remove 82 // one and add one that works too. 83 func TestFDTableMany(t *testing.T) { 84 runTest(t, func(ctx context.Context, fdTable *FDTable, fd *vfs.FileDescription, _ *limits.LimitSet) { 85 for i := 0; i < maxFD; i++ { 86 if _, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err != nil { 87 t.Fatalf("Allocated %v FDs but wanted to allocate %v", i, maxFD) 88 } 89 } 90 91 if _, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err == nil { 92 t.Fatalf("fdTable.NewFDs(0, r) in full map: got nil, wanted error") 93 } 94 95 if df, err := fdTable.NewFDAt(ctx, 1, fd, FDFlags{}); err != nil { 96 t.Fatalf("fdTable.NewFDAt(1, r, FDFlags{}): got %v, wanted nil", err) 97 } else if df != nil { 98 t.Fatalf("fdTable.NewFDAt(1, r, FDFlags{}) displaced FD") 99 } 100 101 i := int32(2) 102 fdTable.Remove(ctx, i) 103 if fds, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err != nil || fds[0] != i { 104 t.Fatalf("Allocated %v FDs but wanted to allocate %v: %v", i, maxFD, err) 105 } 106 }) 107 } 108 109 func TestFDTableOverLimit(t *testing.T) { 110 runTest(t, func(ctx context.Context, fdTable *FDTable, fd *vfs.FileDescription, _ *limits.LimitSet) { 111 if _, err := fdTable.NewFDs(ctx, maxFD, []*vfs.FileDescription{fd}, FDFlags{}); err == nil { 112 t.Fatalf("fdTable.NewFDs(maxFD, f): got nil, wanted error") 113 } 114 115 if _, err := fdTable.NewFDs(ctx, maxFD-2, []*vfs.FileDescription{fd, fd, fd}, FDFlags{}); err == nil { 116 t.Fatalf("fdTable.NewFDs(maxFD-2, {f,f,f}): got nil, wanted error") 117 } 118 119 if fds, err := fdTable.NewFDs(ctx, maxFD-3, []*vfs.FileDescription{fd, fd, fd}, FDFlags{}); err != nil { 120 t.Fatalf("fdTable.NewFDs(maxFD-3, {f,f,f}): got %v, wanted nil", err) 121 } else { 122 for _, fd := range fds { 123 fdTable.Remove(ctx, fd) 124 } 125 } 126 127 if fds, err := fdTable.NewFDs(ctx, maxFD-1, []*vfs.FileDescription{fd}, FDFlags{}); err != nil || fds[0] != maxFD-1 { 128 t.Fatalf("fdTable.NewFDAt(1, r, FDFlags{}): got %v, wanted nil", err) 129 } 130 131 if fds, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd}, 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] != 0 { 134 t.Fatalf("Added an FD to a resized map: got %v, want {1}", fds) 135 } 136 }) 137 } 138 139 // TestFDTable does a set of simple tests to make sure simple adds, removes, 140 // GetRefs, and DecRefs work. The ordering is just weird enough that a 141 // table-driven approach seemed clumsy. 142 func TestFDTable(t *testing.T) { 143 runTest(t, func(ctx context.Context, fdTable *FDTable, fd *vfs.FileDescription, limitSet *limits.LimitSet) { 144 // Cap the limit at one. 145 limitSet.Set(limits.NumberOfFiles, limits.Limit{1, maxFD}, true) 146 147 if _, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err != nil { 148 t.Fatalf("Adding an FD to an empty 1-size map: got %v, want nil", err) 149 } 150 151 if _, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err == nil { 152 t.Fatalf("Adding an FD to a filled 1-size map: got nil, wanted an error") 153 } 154 155 // Remove the previous limit. 156 limitSet.Set(limits.NumberOfFiles, limits.Limit{maxFD, maxFD}, true) 157 158 if fds, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err != nil { 159 t.Fatalf("Adding an FD to a resized map: got %v, want nil", err) 160 } else if len(fds) != 1 || fds[0] != 1 { 161 t.Fatalf("Added an FD to a resized map: got %v, want {1}", fds) 162 } 163 164 if df, err := fdTable.NewFDAt(ctx, 1, fd, FDFlags{}); err != nil { 165 t.Fatalf("Replacing FD 1 via fdTable.NewFDAt(1, r, FDFlags{}): got %v, wanted nil", err) 166 } else if df != nil { 167 t.Fatalf("fdTable.NewFDAt(1, r, FDFlags{}) displaced FD") 168 } 169 170 if _, err := fdTable.NewFDAt(ctx, maxFD+1, fd, FDFlags{}); err == nil { 171 t.Fatalf("Using an FD that was too large via fdTable.NewFDAt(%v, r, FDFlags{}): got nil, wanted an error", maxFD+1) 172 } 173 174 if ref, _ := fdTable.Get(1); ref == nil { 175 t.Fatalf("fdTable.Get(1): got nil, wanted %v", fd) 176 } 177 178 if ref, _ := fdTable.Get(2); ref != nil { 179 t.Fatalf("fdTable.Get(2): got a %v, wanted nil", ref) 180 } 181 182 ref := fdTable.Remove(ctx, 1) 183 if ref == nil { 184 t.Fatalf("fdTable.Remove(1) for an existing FD: failed, want success") 185 } 186 ref.DecRef(ctx) 187 188 if ref := fdTable.Remove(ctx, 1); ref != nil { 189 t.Fatalf("r.Remove(1) for a removed FD: got success, want failure") 190 } 191 }) 192 } 193 194 func TestDescriptorFlags(t *testing.T) { 195 runTest(t, func(ctx context.Context, fdTable *FDTable, fd *vfs.FileDescription, _ *limits.LimitSet) { 196 if df, err := fdTable.NewFDAt(ctx, 2, fd, FDFlags{CloseOnExec: true}); err != nil { 197 t.Fatalf("fdTable.NewFDAt(2, r, FDFlags{}): got %v, wanted nil", err) 198 } else if df != nil { 199 t.Fatalf("fdTable.NewFDAt(2, r, FDFlags{}) displaced FD") 200 } 201 202 newFile, flags := fdTable.Get(2) 203 if newFile == nil { 204 t.Fatalf("fdTable.Get(2): got a %v, wanted nil", newFile) 205 } 206 207 if !flags.CloseOnExec { 208 t.Fatalf("new File flags %v don't match original %d\n", flags, 0) 209 } 210 }) 211 } 212 213 func BenchmarkFDLookupAndDecRef(b *testing.B) { 214 b.StopTimer() // Setup. 215 216 runTest(b, func(ctx context.Context, fdTable *FDTable, fd *vfs.FileDescription, _ *limits.LimitSet) { 217 fds, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd, fd, fd, fd, fd}, FDFlags{}) 218 if err != nil { 219 b.Fatalf("fdTable.NewFDs: got %v, wanted nil", err) 220 } 221 222 b.StartTimer() // Benchmark. 223 for i := 0; i < b.N; i++ { 224 tf, _ := fdTable.Get(fds[i%len(fds)]) 225 tf.DecRef(ctx) 226 } 227 }) 228 } 229 230 func BenchmarkNewFDAt(b *testing.B) { 231 const maxLimit = 1 << 31 232 b.StopTimer() // Setup. 233 234 runTest(b, func(ctx context.Context, fdTable *FDTable, fd *vfs.FileDescription, limitSet *limits.LimitSet) { 235 // Remove the previous limit. 236 limitSet.Set(limits.NumberOfFiles, limits.Limit{maxLimit, maxLimit}, true) 237 238 b.StartTimer() // Benchmark. 239 for i := 0; i < b.N; i++ { 240 _, err := fdTable.NewFDAt(ctx, int32(i%maxLimit), fd, FDFlags{}) 241 if err != nil { 242 b.Fatalf("fdTable.NewFDAt: got %v, wanted nil", err) 243 } 244 } 245 }) 246 } 247 248 func BenchmarkFork(b *testing.B) { 249 b.StopTimer() // Setup. 250 251 runTest(b, func(ctx context.Context, fdTable *FDTable, fd *vfs.FileDescription, limitSet *limits.LimitSet) { 252 for i := 0; i < maxFD; i++ { 253 _, err := fdTable.NewFDAt(ctx, int32(i), fd, FDFlags{}) 254 if err != nil { 255 b.Fatalf("fdTable.NewFDs: got %v, wanted nil", err) 256 } 257 } 258 259 b.StartTimer() // Benchmark. 260 for i := 0; i < b.N; i++ { 261 t := fdTable.Fork(ctx, maxFD) 262 t.DecRef(ctx) 263 } 264 }) 265 } 266 267 func BenchmarkCreateWithMaxFD(b *testing.B) { 268 const maxLimit = 1 << 31 269 runTest(b, func(ctx context.Context, _ *FDTable, fd *vfs.FileDescription, limitSet *limits.LimitSet) { 270 // Remove the previous limit. 271 limitSet.Set(limits.NumberOfFiles, limits.Limit{maxLimit, maxLimit}, true) 272 273 for i := 0; i < b.N; i++ { 274 fdTable := new(FDTable) 275 fdTable.init() 276 _, err := fdTable.NewFDAt(ctx, maxLimit-1, fd, FDFlags{}) 277 if err != nil { 278 b.Fatalf("fdTable.NewFDs: got %v, wanted nil", err) 279 } 280 fdTable.DecRef(ctx) 281 } 282 }) 283 } 284 285 func BenchmarkFDLookupAndDecRefConcurrent(b *testing.B) { 286 b.StopTimer() // Setup. 287 288 runTest(b, func(ctx context.Context, fdTable *FDTable, fd *vfs.FileDescription, _ *limits.LimitSet) { 289 fds, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd, fd, fd, fd, fd}, FDFlags{}) 290 if err != nil { 291 b.Fatalf("fdTable.NewFDs: got %v, wanted nil", err) 292 } 293 294 concurrency := runtime.GOMAXPROCS(0) 295 if concurrency < 4 { 296 concurrency = 4 297 } 298 each := b.N / concurrency 299 300 b.StartTimer() // Benchmark. 301 var wg sync.WaitGroup 302 for i := 0; i < concurrency; i++ { 303 wg.Add(1) 304 go func() { 305 defer wg.Done() 306 for i := 0; i < each; i++ { 307 tf, _ := fdTable.Get(fds[i%len(fds)]) 308 tf.DecRef(ctx) 309 } 310 }() 311 } 312 wg.Wait() 313 }) 314 } 315 316 func TestSetFlagsForRange(t *testing.T) { 317 type testCase struct { 318 name string 319 startFd int32 320 endFd int32 321 wantErr bool 322 } 323 testCases := []testCase{ 324 {"negative ranges", -100, -10, true}, 325 {"inverted positive ranges", 100, 10, true}, 326 {"good range", maxFD / 4, maxFD / 2, false}, 327 } 328 329 for _, test := range testCases { 330 runTest(t, func(ctx context.Context, fdTable *FDTable, fd *vfs.FileDescription, _ *limits.LimitSet) { 331 for i := 0; i < maxFD; i++ { 332 if _, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err != nil { 333 t.Fatalf("testCase: %v\nfdTable.NewFDs(_, 0, %+v, FDFlags{}): %d, want: nil", test, []*vfs.FileDescription{fd}, err) 334 } 335 } 336 337 newFlags := FDFlags{CloseOnExec: true} 338 if err := fdTable.SetFlagsForRange(ctx, test.startFd, test.endFd, newFlags); (err == nil) == test.wantErr { 339 t.Fatalf("testCase: %v\nfdTable.SetFlagsForRange(_, %d, %d, %v): %v, waf: %t", test, test.startFd, test.endFd, newFlags, err, test.wantErr) 340 } 341 342 if test.wantErr { 343 return 344 } 345 346 testRangeFlags := func(start int32, end int32, expected FDFlags) { 347 for i := start; i <= end; i++ { 348 file, flags := fdTable.Get(i) 349 if file == nil || flags != expected { 350 t.Fatalf("testCase: %v\nfdTable.Get(%d): (%v, %v), wanted (non-nil, %v)", test, i, file, flags, expected) 351 } 352 } 353 } 354 testRangeFlags(0, test.startFd-1, FDFlags{}) 355 testRangeFlags(test.startFd, test.endFd, newFlags) 356 testRangeFlags(test.endFd+1, maxFD-1, FDFlags{}) 357 }) 358 } 359 }