Skip to content

fix(envd): port scanner ScanAndBroadcast uses time.Sleep instead of select+ticker, delaying shutdown #3356

Description

@AdaAibaby

Summary

ScanAndBroadcast() in the envd port scanner uses a bare time.Sleep to pace scans. Because Destroy() closes the scanExit channel but the goroutine checks it before sleeping (not after), a Destroy() call that arrives during the sleep is not noticed until the sleep completes.

Location: packages/envd/internal/port/scan.go:44

func (s *Scanner) ScanAndBroadcast() {
    for {
        processes, _ := net.Connections("tcp")
        for _, sub := range s.subs.Items() {
            sub.Signal(processes)
        }
        select {
        case <-s.scanExit:
            return
        default:
            time.Sleep(s.period)  // ← shutdown waits up to `period` here
        }
    }
}

Root cause

The select only tests scanExit at the top of each iteration, before the sleep. Once time.Sleep starts, a concurrent Destroy() closes scanExit but the goroutine doesn't wake up — it sleeps the full period before looping back and seeing the closed channel.

If period is long (the default is not specified here, but envd calls NewScanner(portScannerInterval) — if that's on the order of seconds) shutdown is delayed unnecessarily.

Proposed fix

Replace the time.Sleep with a time.After inside the select so Destroy() can interrupt the sleep immediately:

func (s *Scanner) ScanAndBroadcast() {
    for {
        processes, _ := net.Connections("tcp")
        for _, sub := range s.subs.Items() {
            sub.Signal(processes)
        }
        select {
        case <-s.scanExit:
            return
        case <-time.After(s.period):
        }
    }
}

Or use a time.Ticker (more efficient — avoids a new timer allocation per iteration):

func (s *Scanner) ScanAndBroadcast() {
    ticker := time.NewTicker(s.period)
    defer ticker.Stop()
    for {
        processes, _ := net.Connections("tcp")
        for _, sub := range s.subs.Items() {
            sub.Signal(processes)
        }
        select {
        case <-s.scanExit:
            return
        case <-ticker.C:
        }
    }
}

Impact

With the current code, every envd shutdown is delayed by up to portScannerInterval waiting for the port scanner goroutine to wake from its sleep and notice the exit signal. The fix makes shutdown instantaneous for this goroutine.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions