Files
Andrei VaginandgVisor bot 2baf142905 kernel: periodically preempt tasks when platform max concurrency is exceeded
Add a new method `ConcurrencyCount()` to the `platform.Platform` interface. This
method returns the maximum number of tasks that can run in parallel on the
platform. For most platforms, this is effectively infinite (math.MaxInt). For
the KVM platform, this is the number of available vCPUs.

Introduce a new method `Preempt()` to the `platform.Context` interface. The CPU
clock ticker now checks if the number of running tasks is greater than
Platform.ConcurrencyCount(). If it is, it calls `t.p.Preempt()` on each running
task to trigger a preemption.

This mechanism ensures that on platforms with a fixed number of execution units,
like KVM with a limited number of vCPUs, tasks are periodically preempted to
allow other runnable tasks to be scheduled. This prevents any single task from
monopolizing a vCPU and improves overall fairness and responsiveness.

The `Preempt()` implementation in most platforms is a no-op. In the KVM
platform, `Preempt()` is implemented by leveraging the existing interrupt
mechanism, effectively signaling the vCPU to exit.

PiperOrigin-RevId: 805926296
2025-09-11 11:48:08 -07:00

111 lines
2.9 KiB
Go

// 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 interrupt provides an interrupt helper.
package interrupt
import (
"fmt"
"gvisor.dev/gvisor/pkg/sync"
)
// Receiver receives interrupt notifications from a Forwarder.
type Receiver interface {
// NotifyInterrupt is called when the Receiver receives an interrupt.
NotifyInterrupt()
}
// Forwarder is a helper for delivering delayed signal interruptions.
//
// This helps platform implementations with Interrupt semantics.
type Forwarder struct {
// mu protects the below.
mu sync.Mutex
// dst is the function to be called when NotifyInterrupt() is called. If
// dst is nil, pending will be set instead, causing the next call to
// Enable() to return false.
dst Receiver
pending bool
}
// Enable attempts to enable interrupt forwarding to r. If f has already
// received an interrupt, Enable does nothing and returns false. Otherwise,
// future calls to f.NotifyInterrupt() cause r.NotifyInterrupt() to be called,
// and Enable returns true.
//
// Usage:
//
// if !f.Enable(r) {
// // There was an interrupt.
// return
// }
//
// defer f.Disable()
//
// Preconditions:
// - r must not be nil.
// - f must not already be forwarding interrupts to a Receiver.
func (f *Forwarder) Enable(r Receiver) bool {
if r == nil {
panic("nil Receiver")
}
f.mu.Lock()
if f.dst != nil {
f.mu.Unlock()
panic(fmt.Sprintf("already forwarding interrupts to %+v", f.dst))
}
if f.pending {
f.pending = false
f.mu.Unlock()
return false
}
f.dst = r
f.mu.Unlock()
return true
}
// Disable stops interrupt forwarding. If interrupt forwarding is already
// disabled, Disable is a no-op.
func (f *Forwarder) Disable() {
f.mu.Lock()
f.dst = nil
f.mu.Unlock()
}
// NotifyInterrupt implements Receiver.NotifyInterrupt. If interrupt forwarding
// is enabled, the configured Receiver will be notified. Otherwise the
// interrupt will be delivered to the next call to Enable.
func (f *Forwarder) NotifyInterrupt() {
f.mu.Lock()
if f.dst != nil {
f.dst.NotifyInterrupt()
} else {
f.pending = true
}
f.mu.Unlock()
}
// Preempt preempts the running context. Preempt is a weaker version of
// NotifyInterrupt, it doesn't set the pending flag which is set when a context
// isn't actually running at this moment.
func (f *Forwarder) Preempt() {
f.mu.Lock()
if f.dst != nil {
f.dst.NotifyInterrupt()
}
f.mu.Unlock()
}