9c8147f234
- Route every reserved-state mutation through withInventoryWrite; the lease insert path now broadcasts so extended subscribers see the busy transition immediately instead of waiting for the next mutation. Folds cleanupExpiredLocked changes into the broadcast decision on every IssueLease early-reject path. Rename the field to inventory and force read/write/write-quiet sites through dedicated accessors so future callers cannot bypass the invariant. - Mirror the linux clone-then-swap pattern in darwinExportHost.Reconcile via cloneDarwinExport, so the ledger's unlocked Snapshot / LeaseCheck reads never observe a half-mutated stale flag. Documented as docs/adr/0001-export-pointer-immutability.md and on the Export interface; both hosts now share the applyStaleClones helper. - Rewrite 27 if (_, )?err := …; err != nil sites to assign-then-check per .claude/rules/go-syntax.md. - Delete parse / builder / tautological tests forbidden by .claude/rules/code-test.md (option/usbip_test.go, iso_scheduler_test.go, usbhost_darwin_status_test.go). - Tag nine more usbip files with linux || (darwin && cgo); fixes pre-existing windows / android lint failures because the protocol types were only consumed by tagged code.
82 lines
1.7 KiB
Go
82 lines
1.7 KiB
Go
//go:build linux || (darwin && cgo)
|
|
|
|
package usbip
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net"
|
|
"time"
|
|
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
)
|
|
|
|
func (s *ServerService) acceptLoop(ln net.Listener) {
|
|
for {
|
|
conn, err := ln.Accept()
|
|
if err != nil {
|
|
select {
|
|
case <-s.ctx.Done():
|
|
return
|
|
default:
|
|
}
|
|
if E.IsClosed(err) {
|
|
return
|
|
}
|
|
//nolint:staticcheck // net.Error.Temporary predates net.ErrClosed; replacement needs a separate audit.
|
|
if netError, isNetError := err.(net.Error); isNetError && netError.Temporary() {
|
|
s.logger.Error("accept: ", err)
|
|
if !sleepCtx(s.ctx, 200*time.Millisecond) {
|
|
return
|
|
}
|
|
continue
|
|
}
|
|
s.logger.Error("accept: ", err)
|
|
return
|
|
}
|
|
go s.dispatchConn(conn)
|
|
}
|
|
}
|
|
|
|
func (s *ServerService) dispatchConn(conn net.Conn) {
|
|
var prefix [controlPrefaceSize]byte
|
|
_, err := io.ReadFull(conn, prefix[:])
|
|
if err != nil {
|
|
s.logger.Debug("read connection preface: ", err)
|
|
_ = conn.Close()
|
|
return
|
|
}
|
|
if bytes.Equal(prefix[:], controlPreface[:]) {
|
|
s.handleControlConn(conn)
|
|
return
|
|
}
|
|
s.handleStandardConn(conn, ParseOpHeader(prefix[:]))
|
|
}
|
|
|
|
func (s *ServerService) readControlConn(sub *exportSubscriber, done chan<- struct{}) {
|
|
defer close(done)
|
|
var reader controlReader
|
|
for {
|
|
message, err := reader.read(sub.conn)
|
|
if err != nil {
|
|
return
|
|
}
|
|
frame := message.Frame
|
|
switch frame.Type {
|
|
case controlFramePing:
|
|
s.ledger.enqueueFrame(sub, controlFrame{
|
|
Type: controlFramePong,
|
|
Version: controlProtocolVersion,
|
|
})
|
|
case controlFrameLeaseRequest:
|
|
if supportsControlExtensions(sub.capabilities) {
|
|
s.ledger.HandleControlLeaseRequest(s.ctx, sub, message.Payload)
|
|
continue
|
|
}
|
|
return
|
|
default:
|
|
return
|
|
}
|
|
}
|
|
}
|