performance improvement: setup signal notify in a new go routine

There is a big loop(at least 65 times) in `signal.Notify`, it costs as much
time as `runc init`, so we can call it in parallel ro reduce the container
start time. In a general test, it can be reduced about 38.70%.

Signed-off-by: lifubang <lifubang@acmcoder.com>
(cyphar: move signal channel definition inside goroutine)
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
This commit is contained in:
lifubang authored and Aleksa Sarai committed 2025-03-01 23:46:25 +11:00
1 parent 202ca99fae
commit d92dd22611
2 files changed
+20 -11

No files matched your search

+18 -10
View File
@@ -18,22 +18,30 @@ const signalBufferSize = 2048
// while still forwarding all other signals to the process. // while still forwarding all other signals to the process.
// If notifySocket is present, use it to read systemd notifications from the container and // If notifySocket is present, use it to read systemd notifications from the container and
// forward them to notifySocketHost. // forward them to notifySocketHost.
func newSignalHandler(enableSubreaper bool, notifySocket *notifySocket) *signalHandler { func newSignalHandler(enableSubreaper bool, notifySocket *notifySocket) chan *signalHandler {
if enableSubreaper { if enableSubreaper {
// set us as the subreaper before registering the signal handler for the container // set us as the subreaper before registering the signal handler for the container
if err := system.SetSubreaper(1); err != nil { if err := system.SetSubreaper(1); err != nil {
logrus.Warn(err) logrus.Warn(err)
} }
} }
// ensure that we have a large buffer size so that we do not miss any signals handler := make(chan *signalHandler)
// in case we are not processing them fast enough. // signal.Notify is actually quite expensive, as it has to configure the
s := make(chan os.Signal, signalBufferSize) // signal mask and add signal handlers for all signals (all ~65 of them).
// handle all signals for the process. // So, defer this to a background thread while doing the rest of the io/tty
signal.Notify(s) // setup.
return &signalHandler{ go func() {
signals: s, // ensure that we have a large buffer size so that we do not miss any
notifySocket: notifySocket, // signals in case we are not processing them fast enough.
} s := make(chan os.Signal, signalBufferSize)
// handle all signals for the process.
signal.Notify(s)
handler <- &signalHandler{
signals: s,
notifySocket: notifySocket,
}
}()
return handler
} }
// exit models a process exit status with the pid and // exit models a process exit status with the pid and
+2 -1
View File
@@ -246,7 +246,7 @@ func (r *runner) run(config *specs.Process) (int, error) {
// Setting up IO is a two stage process. We need to modify process to deal // Setting up IO is a two stage process. We need to modify process to deal
// with detaching containers, and then we get a tty after the container has // with detaching containers, and then we get a tty after the container has
// started. // started.
handler := newSignalHandler(r.enableSubreaper, r.notifySocket) handlerCh := newSignalHandler(r.enableSubreaper, r.notifySocket)
tty, err := setupIO(process, r.container, config.Terminal, detach, r.consoleSocket) tty, err := setupIO(process, r.container, config.Terminal, detach, r.consoleSocket)
if err != nil { if err != nil {
return -1, err return -1, err
@@ -285,6 +285,7 @@ func (r *runner) run(config *specs.Process) (int, error) {
return -1, err return -1, err
} }
} }
handler := <-handlerCh
status, err := handler.forward(process, tty, detach) status, err := handler.forward(process, tty, detach)
if err != nil { if err != nil {
r.terminate(process) r.terminate(process)