usbip: make ExportHost.Close stop event goroutines and Start transactional

ExportHost.Events spawned goroutines tied to a caller-supplied ctx, and
neither host.Close nor the per-error cleanup in ServerService.Start
cancelled that ctx. A bind failure on port 3240 after Events succeeded
leaked ueventLoop, the netlink fd, and the darwin watcher goroutine.

The host now owns its background lifecycle: Start derives runCtx from
the caller's ctx, Close cancels it, Events binds goroutines to it.
ServerService.Start switches to a deferred cleanup that cancels and
closes the host on any error, so future failure steps cannot forget it.
This commit is contained in:
世界
2026-05-16 17:42:49 +08:00
parent a53abedcfa
commit d30035ccb0
4 changed files with 43 additions and 15 deletions
+5 -4
View File
@@ -19,10 +19,11 @@ type ExportHost interface {
Reconcile(ctx context.Context, isReserved func(busid string) bool) (snapshot map[string]Export, released []string, err error)
FinishImport(ctx context.Context, busid string) (released bool, err error)
// Events returns a coalescing channel that signals topology
// changes; the channel is closed when ctx is cancelled. A non-nil
// error means the host could not subscribe and the server must not
// continue.
Events(ctx context.Context) (<-chan struct{}, error)
// changes. The channel is closed when Close() is called, which
// also stops the host's background goroutines. Events MUST be
// called after Start succeeds. A non-nil error means the host
// could not subscribe and the server must not continue.
Events() (<-chan struct{}, error)
}
type ImportHost interface {
+12 -2
View File
@@ -34,6 +34,9 @@ type darwinExportHost struct {
logger log.ContextLogger
matches []option.USBIPDeviceMatch
runCtx context.Context
runCancel context.CancelFunc
access sync.Mutex
exports map[string]*darwinExport
watcher *darwinUSBHostDeviceWatcher
@@ -48,10 +51,14 @@ func newDarwinExportHost(logger log.ContextLogger, matches []option.USBIPDeviceM
}
func (h *darwinExportHost) Start(ctx context.Context) error {
h.runCtx, h.runCancel = context.WithCancel(ctx)
return nil
}
func (h *darwinExportHost) Close() error {
if h.runCancel != nil {
h.runCancel()
}
h.access.Lock()
watcher := h.watcher
h.watcher = nil
@@ -69,7 +76,10 @@ func (h *darwinExportHost) Close() error {
return nil
}
func (h *darwinExportHost) Events(ctx context.Context) (<-chan struct{}, error) {
func (h *darwinExportHost) Events() (<-chan struct{}, error) {
if h.runCtx == nil {
return nil, E.New("usbip host: Events called before Start")
}
ch := make(chan struct{}, 1)
signal := func() {
select {
@@ -86,7 +96,7 @@ func (h *darwinExportHost) Events(ctx context.Context) (<-chan struct{}, error)
h.watcher = watcher
h.access.Unlock()
go func() {
<-ctx.Done()
<-h.runCtx.Done()
h.access.Lock()
w := h.watcher
h.watcher = nil
+17 -3
View File
@@ -139,6 +139,9 @@ type linuxExportHost struct {
logger log.ContextLogger
matches []option.USBIPDeviceMatch
runCtx context.Context
runCancel context.CancelFunc
access sync.Mutex
exports map[string]*linuxExport
}
@@ -159,10 +162,18 @@ func newLinuxExportHost(logger log.ContextLogger, matches []option.USBIPDeviceMa
}
func (h *linuxExportHost) Start(ctx context.Context) error {
return ensureKernelPath(sysUsbipHostDriver, "usbip-host", "usbip-host driver")
err := ensureKernelPath(sysUsbipHostDriver, "usbip-host", "usbip-host driver")
if err != nil {
return err
}
h.runCtx, h.runCancel = context.WithCancel(ctx)
return nil
}
func (h *linuxExportHost) Close() error {
if h.runCancel != nil {
h.runCancel()
}
h.access.Lock()
exports := h.exports
h.exports = make(map[string]*linuxExport)
@@ -176,9 +187,12 @@ func (h *linuxExportHost) Close() error {
return nil
}
func (h *linuxExportHost) Events(ctx context.Context) (<-chan struct{}, error) {
func (h *linuxExportHost) Events() (<-chan struct{}, error) {
if h.runCtx == nil {
return nil, E.New("usbip host: Events called before Start")
}
ch := make(chan struct{}, 1)
go h.ueventLoop(ctx, ch)
go h.ueventLoop(h.runCtx, ch)
return ch, nil
}
+9 -6
View File
@@ -73,27 +73,30 @@ func NewServerService(ctx context.Context, logger log.ContextLogger, tag string,
}, nil
}
func (s *ServerService) Start(stage adapter.StartStage) error {
func (s *ServerService) Start(stage adapter.StartStage) (err error) {
if stage != adapter.StartStateStart {
return nil
}
err := s.host.Start(s.ctx)
defer func() {
if err != nil {
s.cancel()
_ = s.host.Close()
}
}()
err = s.host.Start(s.ctx)
if err != nil {
return err
}
events, err := s.host.Events(s.ctx)
events, err := s.host.Events()
if err != nil {
_ = s.host.Close()
return E.Cause(err, "subscribe topology events")
}
err = s.reconcileAndBroadcast(false)
if err != nil {
_ = s.host.Close()
return err
}
tcpListener, err := s.listener.ListenTCP()
if err != nil {
_ = s.host.Close()
return err
}
go s.acceptLoop(tcpListener)