mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
`runsc` creates some FDs that are slow to release (`AF_PACKET` sockets for
networking, KVM FDs for the KVM platform).
By releasing them asynchronously, we free up the sandbox process memory
earlier during teardown, allowing better density of sandboxes to exist.
Also allows faster sandbox recycling for systems like Substrate which spawn
a new sandbox as soon as one exits.
This uses an `io_uring` hack to move the release work to a kworker thread.
The ring is held by the gofer (not the Sentry), is disabled
(`IORING_SETUP_R_DISABLED`), and is unusable anyway due to seccomp filters.
Includes a test to verify this kernel implementation detail.
Benchmarks:
```
$ sudo runsc do --platform=kvm --network=sandbox
│ before │ after │
│ sec/op │ sec/op vs base │
RunscDo 459.5m ± 6% 432.5m ± 4% -5.88% (p=0.001 n=30)
│ before │ after │
│ sec/op │ sec/op vs base │
OCICreate 155.8m ± 5% 155.0m ± 7% ~ (p=0.623 n=30)
OCIDelete 8.838m ± 4% 7.713m ± 9% -12.73% (p=0.000 n=30)
OCIExit 80.511m ± 6% 2.015m ± 7% -97.50% (p=0.000 n=30)
OCIReady 170.6m ± 6% 171.0m ± 7% ~ (p=0.708 n=30)
OCIStart 15.51m ± 3% 15.97m ± 5% ~ (p=0.223 n=30)
OCITotal 261.0m ± 5% 180.8m ± 6% -30.73% (p=0.000 n=30)
```
153 lines
4.6 KiB
Go
153 lines
4.6 KiB
Go
// Copyright 2022 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 donation tracks files that are being donated to a child process and
|
|
// using flags to notified the child process where the FDs are.
|
|
package donation
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
|
|
"gvisor.dev/gvisor/pkg/log"
|
|
"gvisor.dev/gvisor/runsc/specutils"
|
|
)
|
|
|
|
// LogDonations logs the FDs we are donating in the command.
|
|
func LogDonations(cmd *exec.Cmd) {
|
|
for i, f := range cmd.ExtraFiles {
|
|
log.Debugf("Donating FD %d: %q", i+3, f.Name())
|
|
}
|
|
}
|
|
|
|
const multipleFDTransferred = -42
|
|
|
|
// Agency keeps track of files that need to be donated to a child process.
|
|
type Agency struct {
|
|
donations []donation
|
|
closePending []*os.File
|
|
// transferred maps flag names to FD numbers.
|
|
// The special value `multipleFDTransferred` means this flag was used
|
|
// multiple times, so it cannot be mapped to a single FD.
|
|
transferred map[string]int
|
|
}
|
|
|
|
type donation struct {
|
|
flag string
|
|
files []*os.File
|
|
}
|
|
|
|
// Donate sets up the given files to be donated to another process. The FD
|
|
// in which the new file will appear in the child process is added as a flag to
|
|
// the child process, e.g. --flag=3. In case the file is nil, -1 is used for the
|
|
// flag value and no file is donated to the next process.
|
|
func (f *Agency) Donate(flag string, files ...*os.File) {
|
|
f.donations = append(f.donations, donation{flag: flag, files: files})
|
|
}
|
|
|
|
// DonateAndClose does the same as Donate, but takes ownership of the files
|
|
// passed in.
|
|
func (f *Agency) DonateAndClose(flag string, files ...*os.File) {
|
|
f.Donate(flag, files...)
|
|
f.closePending = append(f.closePending, files...)
|
|
}
|
|
|
|
// DonateLogFile is similar to DonateAndClose but handles the opening of
|
|
// the file using specutils.OpenLogFile() for convenience. It's a noop, if
|
|
// path is empty.
|
|
func (f *Agency) DonateLogFile(flag, logPattern string, flags int, opts log.FileOpts) error {
|
|
if len(logPattern) == 0 {
|
|
return nil
|
|
}
|
|
file, err := log.OpenFile(logPattern, flags, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f.DonateAndClose(flag, file)
|
|
return nil
|
|
}
|
|
|
|
// DonateDebugLogFile is similar to DonateAndClose but handles the opening of
|
|
// the file using specutils.OpenDebugLogFile() for convenience. It's a noop, if
|
|
// path is empty.
|
|
func (f *Agency) DonateDebugLogFile(flag, logPattern string, opts log.FileOpts) error {
|
|
if len(logPattern) == 0 {
|
|
return nil
|
|
}
|
|
file, err := specutils.OpenDebugLogFile(logPattern, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f.DonateAndClose(flag, file)
|
|
return nil
|
|
}
|
|
|
|
// Transfer sets up all files and flags to cmd. It can be called multiple times
|
|
// to partially transfer files to cmd.
|
|
func (f *Agency) Transfer(cmd *exec.Cmd, nextFD int) int {
|
|
for _, d := range f.donations {
|
|
for _, file := range d.files {
|
|
fd := -1
|
|
if file != nil {
|
|
cmd.ExtraFiles = append(cmd.ExtraFiles, file)
|
|
fd = nextFD
|
|
nextFD++
|
|
}
|
|
cmd.Args = append(cmd.Args, fmt.Sprintf("--%s=%d", d.flag, fd))
|
|
if f.transferred == nil {
|
|
f.transferred = make(map[string]int)
|
|
}
|
|
if _, alreadyExist := f.transferred[d.flag]; alreadyExist {
|
|
f.transferred[d.flag] = multipleFDTransferred
|
|
} else {
|
|
f.transferred[d.flag] = fd
|
|
}
|
|
}
|
|
}
|
|
// Reset donations made so far in case more transfers are needed.
|
|
f.donations = nil
|
|
return nextFD
|
|
}
|
|
|
|
// TransferredFD returns the FD number that the file donated under flag was
|
|
// given in the child, or -1 if nothing or multiple FDs were donated with it.
|
|
func (f *Agency) TransferredFD(flag string) int {
|
|
if fd, ok := f.transferred[flag]; ok && fd != multipleFDTransferred {
|
|
return fd
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// DonateAndTransferCustomFiles sets up the flags for passing file descriptors from the
|
|
// host to the sandbox. Making use of the agency is not necessary,
|
|
func DonateAndTransferCustomFiles(cmd *exec.Cmd, nextFD int, files map[int]*os.File) int {
|
|
for fd, file := range files {
|
|
cmd.Args = append(cmd.Args, fmt.Sprintf("--pass-fd=%d:%d", nextFD, fd))
|
|
cmd.ExtraFiles = append(cmd.ExtraFiles, file)
|
|
nextFD++
|
|
}
|
|
return nextFD
|
|
}
|
|
|
|
// Close closes any files the agency has taken ownership over.
|
|
func (f *Agency) Close() {
|
|
for _, file := range f.closePending {
|
|
if file != nil {
|
|
_ = file.Close()
|
|
}
|
|
}
|
|
f.closePending = nil
|
|
}
|