Обновление снапшота с v0.0.0-20250811.0 на пин, которого требует sing-box после мержа 235 коммитов (upstream d620bbbf2 "Update gvisor to 20260727.0"). Прежний снапшот был взят 2026-08-04 ровно с той версии, на которой тогда стоял апстрим; разрыв возник 2026-08-05 вместе с его бампом. За год апстрим-gvisor изменил ~14 000 строк в 292 файлах. Значимое для нас — сетевой стек: tcp/connect.go (PMTU-discovery + исправление начального RTT/RTO: раньше задержка ACK внутри стека завышала стартовый таймаут на несколько RTT), tcp/snd.go, tcp/rcv.go, stack/conntrack.go, stack/packet_buffer.go. Всего 30 файлов в TCP и 37 в stack. Баг SPEC 048 апстрим НЕ исправил — проверено по коду новой версии: handleConnecting по-прежнему проверяет состояние endpoint'а, но не ep.h, а performHandshake так же зануляет h и отпускает мьютекс до Close(). Поэтому guard перенесён (12 строк) вместе со своим тестом (45 строк). Red/green проверен на новой базе: без guard'а тест падает с той же nil-паникой, что в полевом крашдампе; с ним зелёный.
152 lines
4.5 KiB
Go
152 lines
4.5 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 sync
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"sync/atomic"
|
|
"unsafe"
|
|
|
|
"github.com/sagernet/gvisor/pkg/gohacks"
|
|
)
|
|
|
|
// Gate is a synchronization primitive that allows concurrent goroutines to
|
|
// "enter" it as long as it hasn't been closed yet. Once it's been closed,
|
|
// goroutines cannot enter it anymore, but are allowed to leave, and the closer
|
|
// will be informed when all goroutines have left.
|
|
//
|
|
// Gate is similar to WaitGroup:
|
|
//
|
|
// - Gate.Enter() is analogous to WaitGroup.Add(1), but may be called even if
|
|
// the Gate counter is 0 and fails if Gate.Close() has been called.
|
|
//
|
|
// - Gate.Leave() is equivalent to WaitGroup.Done().
|
|
//
|
|
// - Gate.Close() is analogous to WaitGroup.Wait(), but also causes future
|
|
//
|
|
// calls to Gate.Enter() to fail and may only be called once, from a single
|
|
// goroutine.
|
|
//
|
|
// This is useful, for example, in cases when a goroutine is trying to clean up
|
|
// an object for which multiple goroutines have pointers. In such a case, users
|
|
// would be required to enter and leave the Gate, and the cleaner would wait
|
|
// until all users are gone (and no new ones are allowed) before proceeding.
|
|
//
|
|
// Users:
|
|
//
|
|
// if !g.Enter() {
|
|
// // Gate is closed, we can't use the object.
|
|
// return
|
|
// }
|
|
//
|
|
// // Do something with object.
|
|
// [...]
|
|
//
|
|
// g.Leave()
|
|
//
|
|
// Closer:
|
|
//
|
|
// // Prevent new users from using the object, and wait for the existing
|
|
// // ones to complete.
|
|
// g.Close()
|
|
//
|
|
// // Clean up the object.
|
|
// [...]
|
|
type Gate struct {
|
|
userCount int32
|
|
closingG uintptr
|
|
}
|
|
|
|
const preparingG = 1
|
|
|
|
// Enter tries to enter the gate. It will succeed if it hasn't been closed yet,
|
|
// in which case the caller must eventually call Leave().
|
|
//
|
|
// This function is thread-safe.
|
|
func (g *Gate) Enter() bool {
|
|
if atomic.AddInt32(&g.userCount, 1) > 0 {
|
|
return true
|
|
}
|
|
g.leaveAfterFailedEnter()
|
|
return false
|
|
}
|
|
|
|
// leaveAfterFailedEnter is identical to Leave, but is marked noinline to
|
|
// prevent it from being inlined into Enter, since as of this writing inlining
|
|
// Leave into Enter prevents Enter from being inlined into its callers.
|
|
//
|
|
//go:noinline
|
|
func (g *Gate) leaveAfterFailedEnter() {
|
|
if atomic.AddInt32(&g.userCount, -1) == math.MinInt32 {
|
|
g.leaveClosed()
|
|
}
|
|
}
|
|
|
|
// Leave leaves the gate. This must only be called after a successful call to
|
|
// Enter(). If the gate has been closed and this is the last one inside the
|
|
// gate, it will notify the closer that the gate is done.
|
|
//
|
|
// This function is thread-safe.
|
|
func (g *Gate) Leave() {
|
|
if atomic.AddInt32(&g.userCount, -1) == math.MinInt32 {
|
|
g.leaveClosed()
|
|
}
|
|
}
|
|
|
|
func (g *Gate) leaveClosed() {
|
|
if atomic.LoadUintptr(&g.closingG) == 0 {
|
|
return
|
|
}
|
|
if cG := atomic.SwapUintptr(&g.closingG, 0); cG > preparingG {
|
|
goready(cG, 0)
|
|
}
|
|
}
|
|
|
|
// Close closes the gate, causing future calls to Enter to fail, and waits
|
|
// until all goroutines that are currently inside the gate leave before
|
|
// returning.
|
|
//
|
|
// Only one goroutine can call this function.
|
|
func (g *Gate) Close() {
|
|
if atomic.LoadInt32(&g.userCount) == math.MinInt32 {
|
|
// The gate is already closed, with no goroutines inside. For legacy
|
|
// reasons, we have to allow Close to be called again in this case.
|
|
return
|
|
}
|
|
if v := atomic.AddInt32(&g.userCount, math.MinInt32); v == math.MinInt32 {
|
|
// userCount was already 0.
|
|
return
|
|
} else if v >= 0 {
|
|
panic("concurrent Close of sync.Gate")
|
|
}
|
|
|
|
if cG := atomic.SwapUintptr(&g.closingG, preparingG); cG != 0 {
|
|
panic(fmt.Sprintf("invalid sync.Gate.closingG during Close: %#x", cG))
|
|
}
|
|
if atomic.LoadInt32(&g.userCount) == math.MinInt32 {
|
|
// The last call to Leave arrived while we were setting up closingG.
|
|
return
|
|
}
|
|
// WaitReasonSemacquire/TraceBlockSync are consistent with WaitGroup.
|
|
gopark(gateCommit, gohacks.Noescape(unsafe.Pointer(&g.closingG)), WaitReasonSemacquire, TraceBlockSync, 0)
|
|
RaceAcquire(unsafe.Pointer(&g.closingG))
|
|
}
|
|
|
|
//go:norace
|
|
//go:nosplit
|
|
func gateCommit(g uintptr, closingG unsafe.Pointer) bool {
|
|
return RaceUncheckedAtomicCompareAndSwapUintptr((*uintptr)(closingG), preparingG, g)
|
|
}
|