snapshot: sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1

Содержимое пина, зафиксированного в go.mod sing-box-lx, одним коммитом
без истории. Полная история SagerNet/gvisor — 1.45 ГБ и клонируется в
каждой CI-джобе; наша дельта — одна вставка в одну функцию, история для
неё не нужна.

Module path github.com/sagernet/gvisor сохранён намеренно: на него
опирается replace-директива суперпроекта.

Патч поверх — отдельным коммитом, чтобы дельта читалась одним git show
и переносилась на новый пин копированием.

SPECS/TASKS/048-GVISOR_HANDSHAKE_NIL_CRASH
This commit is contained in:
Leadaxe 2026-08-04 15:50:08 +03:00
commit 2c4ae3b0a4
712 changed files with 185689 additions and 0 deletions

View file

@ -0,0 +1,220 @@
// Copyright 2020 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 syncevent
import (
"github.com/sagernet/gvisor/pkg/sync"
)
// Broadcaster is an implementation of Source that supports any number of
// subscribed Receivers.
//
// The zero value of Broadcaster is valid and has no subscribed Receivers.
// Broadcaster is not copyable by value.
//
// All Broadcaster methods may be called concurrently from multiple goroutines.
type Broadcaster struct {
// Broadcaster is implemented as a hash table where keys are assigned by
// the Broadcaster and returned as SubscriptionIDs, making it safe to use
// the identity function for hashing. The hash table resolves collisions
// using linear probing and features Robin Hood insertion and backward
// shift deletion in order to support a relatively high load factor
// efficiently, which matters since the cost of Broadcast is linear in the
// size of the table.
// mu protects the following fields.
mu sync.Mutex
// Invariants: len(table) is 0 or a power of 2.
table []broadcasterSlot
// load is the number of entries in table with receiver != nil.
load int
lastID SubscriptionID
}
type broadcasterSlot struct {
// Invariants: If receiver == nil, then filter == NoEvents and id == 0.
// Otherwise, id != 0.
receiver *Receiver
filter Set
id SubscriptionID
}
const (
broadcasterMinNonZeroTableSize = 2 // must be a power of 2 > 1
broadcasterMaxLoadNum = 13
broadcasterMaxLoadDen = 16
)
// SubscribeEvents implements Source.SubscribeEvents.
func (b *Broadcaster) SubscribeEvents(r *Receiver, filter Set) SubscriptionID {
b.mu.Lock()
// Assign an ID for this subscription.
b.lastID++
id := b.lastID
// Expand the table if over the maximum load factor:
//
// load / len(b.table) > broadcasterMaxLoadNum / broadcasterMaxLoadDen
// load * broadcasterMaxLoadDen > broadcasterMaxLoadNum * len(b.table)
b.load++
if (b.load * broadcasterMaxLoadDen) > (broadcasterMaxLoadNum * len(b.table)) {
// Double the number of slots in the new table.
newlen := broadcasterMinNonZeroTableSize
if len(b.table) != 0 {
newlen = 2 * len(b.table)
}
if newlen <= cap(b.table) {
// Reuse excess capacity in the current table, moving entries not
// already in their first-probed positions to better ones.
newtable := b.table[:newlen]
newmask := uint64(newlen - 1)
for i := range b.table {
if b.table[i].receiver != nil && uint64(b.table[i].id)&newmask != uint64(i) {
entry := b.table[i]
b.table[i] = broadcasterSlot{}
broadcasterTableInsert(newtable, entry.id, entry.receiver, entry.filter)
}
}
b.table = newtable
} else {
newtable := make([]broadcasterSlot, newlen)
// Copy existing entries to the new table.
for i := range b.table {
if b.table[i].receiver != nil {
broadcasterTableInsert(newtable, b.table[i].id, b.table[i].receiver, b.table[i].filter)
}
}
// Switch to the new table.
b.table = newtable
}
}
broadcasterTableInsert(b.table, id, r, filter)
b.mu.Unlock()
return id
}
// Preconditions:
// - table must not be full.
// - len(table) is a power of 2.
func broadcasterTableInsert(table []broadcasterSlot, id SubscriptionID, r *Receiver, filter Set) {
entry := broadcasterSlot{
receiver: r,
filter: filter,
id: id,
}
mask := uint64(len(table) - 1)
i := uint64(id) & mask
disp := uint64(0)
for {
if table[i].receiver == nil {
table[i] = entry
return
}
// If we've been displaced farther from our first-probed slot than the
// element stored in this one, swap elements and switch to inserting
// the replaced one. (This is Robin Hood insertion.)
slotDisp := (i - uint64(table[i].id)) & mask
if disp > slotDisp {
table[i], entry = entry, table[i]
disp = slotDisp
}
i = (i + 1) & mask
disp++
}
}
// UnsubscribeEvents implements Source.UnsubscribeEvents.
func (b *Broadcaster) UnsubscribeEvents(id SubscriptionID) {
b.mu.Lock()
mask := uint64(len(b.table) - 1)
i := uint64(id) & mask
for {
if b.table[i].id == id {
// Found the element to remove. Move all subsequent elements
// backward until we either find an empty slot, or an element that
// is already in its first-probed slot. (This is backward shift
// deletion.)
for {
next := (i + 1) & mask
if b.table[next].receiver == nil {
break
}
if uint64(b.table[next].id)&mask == next {
break
}
b.table[i] = b.table[next]
i = next
}
b.table[i] = broadcasterSlot{}
break
}
i = (i + 1) & mask
}
// If a table 1/4 of the current size would still be at or under the
// maximum load factor (i.e. the current table size is at least two
// expansions bigger than necessary), halve the size of the table to reduce
// the cost of Broadcast. Since we are concerned with iteration time and
// not memory usage, reuse the existing slice to reduce future allocations
// from table re-expansion.
b.load--
if len(b.table) > broadcasterMinNonZeroTableSize && (b.load*(4*broadcasterMaxLoadDen)) <= (broadcasterMaxLoadNum*len(b.table)) {
newlen := len(b.table) / 2
newtable := b.table[:newlen]
for i := newlen; i < len(b.table); i++ {
if b.table[i].receiver != nil {
broadcasterTableInsert(newtable, b.table[i].id, b.table[i].receiver, b.table[i].filter)
b.table[i] = broadcasterSlot{}
}
}
b.table = newtable
}
b.mu.Unlock()
}
// Broadcast notifies all Receivers subscribed to the Broadcaster of the subset
// of events to which they subscribed. The order in which Receivers are
// notified is unspecified.
func (b *Broadcaster) Broadcast(events Set) {
b.mu.Lock()
for i := range b.table {
if intersection := events & b.table[i].filter; intersection != 0 {
// We don't need to check if broadcasterSlot.receiver is nil, since
// if it is then broadcasterSlot.filter is 0.
b.table[i].receiver.Notify(intersection)
}
}
b.mu.Unlock()
}
// FilteredEvents returns the set of events for which Broadcast will notify at
// least one Receiver, i.e. the union of filters for all subscribed Receivers.
func (b *Broadcaster) FilteredEvents() Set {
var es Set
b.mu.Lock()
for i := range b.table {
es |= b.table[i].filter
}
b.mu.Unlock()
return es
}

101
pkg/syncevent/receiver.go Normal file
View file

@ -0,0 +1,101 @@
// Copyright 2020 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 syncevent
import (
"github.com/sagernet/gvisor/pkg/atomicbitops"
)
// Receiver is an event sink that holds pending events and invokes a callback
// whenever new events become pending. Receiver's methods may be called
// concurrently from multiple goroutines.
//
// Receiver.Init() must be called before first use.
type Receiver struct {
// pending is the set of pending events. pending is accessed using atomic
// memory operations.
pending atomicbitops.Uint64
// cb is notified when new events become pending. cb is immutable after
// Init().
cb ReceiverCallback
}
// ReceiverCallback receives callbacks from a Receiver.
type ReceiverCallback interface {
// NotifyPending is called when the corresponding Receiver has new pending
// events.
//
// NotifyPending is called synchronously from Receiver.Notify(), so
// implementations must not take locks that may be held by callers of
// Receiver.Notify(). NotifyPending may be called concurrently from
// multiple goroutines.
NotifyPending()
}
// Init must be called before first use of r.
func (r *Receiver) Init(cb ReceiverCallback) {
r.cb = cb
}
// Pending returns the set of pending events.
func (r *Receiver) Pending() Set {
return Set(r.pending.Load())
}
// Notify sets the given events as pending.
func (r *Receiver) Notify(es Set) {
p := Set(r.pending.Load())
// Optimization: Skip the atomic CAS on r.pending if all events are
// already pending.
if p&es == es {
return
}
// When this is uncontended (the common case), CAS is faster than
// atomic-OR because the former is inlined and the latter (which we
// implement in assembly ourselves) is not.
if !r.pending.CompareAndSwap(uint64(p), uint64(p|es)) {
// If the CAS fails, fall back to atomic-OR.
atomicbitops.OrUint64(&r.pending, uint64(es))
}
r.cb.NotifyPending()
}
// Ack unsets the given events as pending.
func (r *Receiver) Ack(es Set) {
p := Set(r.pending.Load())
// Optimization: Skip the atomic CAS on r.pending if all events are
// already not pending.
if p&es == 0 {
return
}
// When this is uncontended (the common case), CAS is faster than
// atomic-AND because the former is inlined and the latter (which we
// implement in assembly ourselves) is not.
if !r.pending.CompareAndSwap(uint64(p), uint64(p&^es)) {
// If the CAS fails, fall back to atomic-AND.
atomicbitops.AndUint64(&r.pending, ^uint64(es))
}
}
// PendingAndAckAll unsets all events as pending and returns the set of
// previously-pending events.
//
// PendingAndAckAll should only be used in preference to a call to Pending
// followed by a conditional call to Ack when the caller expects events to be
// pending (e.g. after a call to ReceiverCallback.NotifyPending()).
func (r *Receiver) PendingAndAckAll() Set {
return Set(r.pending.Swap(0))
}

61
pkg/syncevent/source.go Normal file
View file

@ -0,0 +1,61 @@
// Copyright 2020 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 syncevent
// Source represents an event source.
type Source interface {
// SubscribeEvents causes the Source to notify the given Receiver of the
// given subset of events.
//
// Preconditions:
// * r != nil.
// * The ReceiverCallback for r must not take locks that are ordered
// prior to the Source; for example, it cannot call any Source
// methods.
SubscribeEvents(r *Receiver, filter Set) SubscriptionID
// UnsubscribeEvents causes the Source to stop notifying the Receiver
// subscribed by a previous call to SubscribeEvents that returned the given
// SubscriptionID.
//
// Preconditions: UnsubscribeEvents may be called at most once for any
// given SubscriptionID.
UnsubscribeEvents(id SubscriptionID)
}
// SubscriptionID identifies a call to Source.SubscribeEvents.
type SubscriptionID uint64
// UnsubscribeAndAck is a convenience function that unsubscribes r from the
// given events from src and also clears them from r.
func UnsubscribeAndAck(src Source, r *Receiver, filter Set, id SubscriptionID) {
src.UnsubscribeEvents(id)
r.Ack(filter)
}
// NoopSource implements Source by never sending events to subscribed
// Receivers.
type NoopSource struct{}
// SubscribeEvents implements Source.SubscribeEvents.
func (NoopSource) SubscribeEvents(*Receiver, Set) SubscriptionID {
return 0
}
// UnsubscribeEvents implements Source.UnsubscribeEvents.
func (NoopSource) UnsubscribeEvents(SubscriptionID) {
}
// See Broadcaster for a non-noop implementations of Source.

View file

@ -0,0 +1,32 @@
// Copyright 2020 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 syncevent provides efficient primitives for goroutine
// synchronization based on event bitmasks.
package syncevent
// Set is a bitmask where each bit represents a distinct user-defined event.
// The event package does not treat any bits in Set specially.
type Set uint64
const (
// NoEvents is a Set containing no events.
NoEvents = Set(0)
// AllEvents is a Set containing all possible events.
AllEvents = ^Set(0)
// MaxEvents is the number of distinct events that can be represented by a Set.
MaxEvents = 64
)

View file

@ -0,0 +1,3 @@
// automatically generated by stateify.
package syncevent

View file

@ -0,0 +1,3 @@
// automatically generated by stateify.
package syncevent

View file

@ -0,0 +1,197 @@
// Copyright 2020 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 syncevent
import (
"sync/atomic"
"unsafe"
"github.com/sagernet/gvisor/pkg/sync"
)
// Waiter allows a goroutine to block on pending events received by a Receiver.
//
// Waiter.Init() must be called before first use.
type Waiter struct {
r Receiver
// g is one of:
//
// - 0: No goroutine is blocking in Wait.
//
// - preparingG: A goroutine is in Wait preparing to sleep, but hasn't yet
// completed waiterUnlock(). Thus the wait can only be interrupted by
// replacing the value of g with 0 (the G may not be in state Gwaiting yet,
// so we can't call goready.)
//
// - Otherwise: g is a pointer to the runtime.g in state Gwaiting for the
// goroutine blocked in Wait, which can only be woken by calling goready.
g uintptr `state:"zerovalue"`
}
const preparingG = 1
// Init must be called before first use of w.
func (w *Waiter) Init() {
w.r.Init(w)
}
// Receiver returns the Receiver that receives events that unblock calls to
// w.Wait().
func (w *Waiter) Receiver() *Receiver {
return &w.r
}
// Pending returns the set of pending events.
func (w *Waiter) Pending() Set {
return w.r.Pending()
}
// Wait blocks until at least one event is pending, then returns the set of
// pending events. It does not affect the set of pending events; callers must
// call w.Ack() to do so, or use w.WaitAndAck() instead.
//
// Precondition: Only one goroutine may call any Wait* method at a time.
func (w *Waiter) Wait() Set {
return w.WaitFor(AllEvents)
}
// WaitFor blocks until at least one event in es is pending, then returns the
// set of pending events (including those not in es). It does not affect the
// set of pending events; callers must call w.Ack() to do so.
//
// Precondition: Only one goroutine may call any Wait* method at a time.
func (w *Waiter) WaitFor(es Set) Set {
for {
// Optimization: Skip the atomic store to w.g if an event is already
// pending.
if p := w.r.Pending(); p&es != NoEvents {
return p
}
// Indicate that we're preparing to go to sleep.
atomic.StoreUintptr(&w.g, preparingG)
// If an event is pending, abort the sleep.
if p := w.r.Pending(); p&es != NoEvents {
atomic.StoreUintptr(&w.g, 0)
return p
}
// If w.g is still preparingG (i.e. w.NotifyPending() has not been
// called or has not reached atomic.SwapUintptr()), go to sleep until
// w.NotifyPending() => goready().
sync.Gopark(waiterCommit, unsafe.Pointer(&w.g), sync.WaitReasonSelect, sync.TraceBlockSelect, 0)
}
}
//go:norace
//go:nosplit
func waiterCommit(g uintptr, wg unsafe.Pointer) bool {
// The only way this CAS can fail is if a call to Waiter.NotifyPending()
// has replaced *wg with nil, in which case we should not sleep.
return sync.RaceUncheckedAtomicCompareAndSwapUintptr((*uintptr)(wg), preparingG, g)
}
// Ack marks the given events as not pending.
func (w *Waiter) Ack(es Set) {
w.r.Ack(es)
}
// WaitAndAckAll blocks until at least one event is pending, then marks all
// events as not pending and returns the set of previously-pending events.
//
// Precondition: Only one goroutine may call any Wait* method at a time.
func (w *Waiter) WaitAndAckAll() Set {
// Optimization: Skip the atomic store to w.g if an event is already
// pending. Call Pending() first since, in the common case that events are
// not yet pending, this skips an atomic swap on w.r.pending.
if w.r.Pending() != NoEvents {
if p := w.r.PendingAndAckAll(); p != NoEvents {
return p
}
}
for {
// Indicate that we're preparing to go to sleep.
atomic.StoreUintptr(&w.g, preparingG)
// If an event is pending, abort the sleep.
if w.r.Pending() != NoEvents {
if p := w.r.PendingAndAckAll(); p != NoEvents {
atomic.StoreUintptr(&w.g, 0)
return p
}
}
// If w.g is still preparingG (i.e. w.NotifyPending() has not been
// called or has not reached atomic.SwapUintptr()), go to sleep until
// w.NotifyPending() => goready().
sync.Gopark(waiterCommit, unsafe.Pointer(&w.g), sync.WaitReasonSelect, sync.TraceBlockSelect, 0)
// Check for pending events. We call PendingAndAckAll() directly now since
// we only expect to be woken after events become pending.
if p := w.r.PendingAndAckAll(); p != NoEvents {
return p
}
}
}
// Notify marks the given events as pending, possibly unblocking concurrent
// calls to w.Wait() or w.WaitFor().
func (w *Waiter) Notify(es Set) {
w.r.Notify(es)
}
// NotifyPending implements ReceiverCallback.NotifyPending. Users of Waiter
// should not call NotifyPending.
func (w *Waiter) NotifyPending() {
// Optimization: Skip the atomic swap on w.g if there is no sleeping
// goroutine. NotifyPending is called after w.r.Pending() is updated, so
// concurrent and future calls to w.Wait() will observe pending events and
// abort sleeping.
if atomic.LoadUintptr(&w.g) == 0 {
return
}
// Wake a sleeping G, or prevent a G that is preparing to sleep from doing
// so. Swap is needed here to ensure that only one call to NotifyPending
// calls goready.
if g := atomic.SwapUintptr(&w.g, 0); g > preparingG {
sync.Goready(g, 0, true /* wakep */)
}
}
var waiterPool = sync.Pool{
New: func() any {
w := &Waiter{}
w.Init()
return w
},
}
// GetWaiter returns an unused Waiter. PutWaiter should be called to release
// the Waiter once it is no longer needed.
//
// Where possible, users should prefer to associate each goroutine that calls
// Waiter.Wait() with a distinct pre-allocated Waiter to avoid allocation of
// Waiters in hot paths.
func GetWaiter() *Waiter {
return waiterPool.Get().(*Waiter)
}
// PutWaiter releases an unused Waiter previously returned by GetWaiter.
func PutWaiter(w *Waiter) {
waiterPool.Put(w)
}