tailscale/portlist/poller.go

102 lines
2.0 KiB
Go

// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package portlist
import (
"context"
"errors"
"time"
"tailscale.com/version"
)
// Poller scans the systems for listening ports periodically and sends
// the results to C.
type Poller struct {
// C received the list of ports periodically. It's closed when
// Run completes, after which Err can be checked.
C <-chan List
c chan List
// Err is the error from the final GetList call. It is only
// valid to read once C has been closed. Err is nil if Close
// is called or the context is canceled.
Err error
quitCh chan struct{} // close this to force exit
prev List // most recent data
}
// NewPoller returns a new portlist Poller. It returns an error
// if the portlist couldn't be obtained.
func NewPoller() (*Poller, error) {
if version.OS() == "iOS" {
return nil, errors.New("not available on iOS")
}
p := &Poller{
c: make(chan List),
quitCh: make(chan struct{}),
}
p.C = p.c
// Do one initial poll synchronously so we can return an error
// early.
var err error
p.prev, err = GetList(nil)
if err != nil {
return nil, err
}
return p, nil
}
func (p *Poller) Close() error {
select {
case <-p.quitCh:
return nil
default:
}
close(p.quitCh)
<-p.C
return nil
}
// Run runs the Poller periodically until either the context
// is done, or the Close is called.
func (p *Poller) Run(ctx context.Context) error {
defer close(p.c)
tick := time.NewTicker(pollInterval)
defer tick.Stop()
// Send out the pre-generated initial value
p.c <- p.prev
for {
select {
case <-tick.C:
pl, err := GetList(p.prev)
if err != nil {
p.Err = err
return err
}
if pl.SameInodes(p.prev) {
continue
}
p.prev = pl
select {
case p.c <- pl:
case <-ctx.Done():
return ctx.Err()
case <-p.quitCh:
return nil
}
case <-ctx.Done():
return ctx.Err()
case <-p.quitCh:
return nil
}
}
}