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:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user