mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
ipn: add a WatchIPNBus option bit to subscribe to EngineStatus changes
So GUI clients don't need to poll for it. We still poll internally (for now!) but that's still cheaper. And will get much cheaper later, without having to modify clients once they start sending this bit. Change-Id: I36647b701c8d1fe197677e5eb76f6894e8ff79f7 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
f45106d47c
commit
5676d201d6
@ -990,13 +990,6 @@ func (lc *LocalClient) DebugDERPRegion(ctx context.Context, regionIDOrCode strin
|
||||
return decodeJSON[*ipnstate.DebugDERPRegionReport](body)
|
||||
}
|
||||
|
||||
// WatchIPNMask are filtering options for LocalClient.WatchIPNBus.
|
||||
//
|
||||
// The zero value is a valid WatchOpt that means to watch everything.
|
||||
//
|
||||
// TODO(bradfitz): flesh out.
|
||||
type WatchIPNMask uint64
|
||||
|
||||
// WatchIPNBus subscribes to the IPN notification bus. It returns a watcher
|
||||
// once the bus is connected successfully.
|
||||
//
|
||||
@ -1005,7 +998,9 @@ func (lc *LocalClient) DebugDERPRegion(ctx context.Context, regionIDOrCode strin
|
||||
//
|
||||
// The returned IPNBusWatcher's Close method must be called when done to release
|
||||
// resources.
|
||||
func (lc *LocalClient) WatchIPNBus(ctx context.Context, mask WatchIPNMask) (*IPNBusWatcher, error) {
|
||||
//
|
||||
// A default set of ipn.Notify messages are returned but the set can be modified by mask.
|
||||
func (lc *LocalClient) WatchIPNBus(ctx context.Context, mask ipn.NotifyWatchOpt) (*IPNBusWatcher, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET",
|
||||
"http://"+apitype.LocalAPIHost+"/localapi/v0/watch-ipn-bus?mask="+fmt.Sprint(mask),
|
||||
nil)
|
||||
|
@ -52,6 +52,17 @@ type EngineStatus struct {
|
||||
LivePeers map[key.NodePublic]ipnstate.PeerStatusLite
|
||||
}
|
||||
|
||||
// NotifyWatchOpt is a bitmask of options about what type of Notify messages
|
||||
// to subscribe to.
|
||||
type NotifyWatchOpt uint64
|
||||
|
||||
const (
|
||||
// NotifyWatchEngineUpdates, if set, causes Engine updates to be sent to the
|
||||
// client either regularly or when they change, without having to ask for
|
||||
// each one via RequestEngineStatus.
|
||||
NotifyWatchEngineUpdates NotifyWatchOpt = 1 << iota
|
||||
)
|
||||
|
||||
// Notify is a communication from a backend (e.g. tailscaled) to a frontend
|
||||
// (cmd/tailscale, iOS, macOS, Win Tasktray).
|
||||
// In any given notification, any or all of these may be nil, meaning
|
||||
|
@ -1702,7 +1702,7 @@ func (b *LocalBackend) readPoller() {
|
||||
// Failure to consume many notifications in a row will result in dropped
|
||||
// notifications. There is currently (2022-11-22) no mechanism provided to
|
||||
// detect when a message has been dropped.
|
||||
func (b *LocalBackend) WatchNotifications(ctx context.Context, fn func(roNotify *ipn.Notify) (keepGoing bool)) {
|
||||
func (b *LocalBackend) WatchNotifications(ctx context.Context, mask ipn.NotifyWatchOpt, fn func(roNotify *ipn.Notify) (keepGoing bool)) {
|
||||
handle := new(mapSetHandle)
|
||||
ch := make(chan *ipn.Notify, 128)
|
||||
|
||||
@ -1715,6 +1715,21 @@ func (b *LocalBackend) WatchNotifications(ctx context.Context, fn func(roNotify
|
||||
b.mu.Unlock()
|
||||
}()
|
||||
|
||||
// The GUI clients want to know when peers become active or inactive.
|
||||
// They've historically got this information by polling for it, which is
|
||||
// wasteful. As a step towards making it efficient, they now set this
|
||||
// NotifyWatchEngineUpdates bit to ask for us to send it to them only on
|
||||
// change. That's not yet (as of 2022-11-26) plumbed everywhere in
|
||||
// tailscaled yet, so just do the polling here. This ends up causing all IPN
|
||||
// bus watchers to get the notification every 2 seconds instead of just the
|
||||
// GUI client's bus watcher, but in practice there's only 1 total connection
|
||||
// anyway. And if we're polling, at least the client isn't making a new HTTP
|
||||
// request every 2 seconds.
|
||||
// TODO(bradfitz): plumb this further and only send a Notify on change.
|
||||
if mask&ipn.NotifyWatchEngineUpdates != 0 {
|
||||
go b.pollRequestEngineStatus(ctx)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@ -1727,6 +1742,21 @@ func (b *LocalBackend) WatchNotifications(ctx context.Context, fn func(roNotify
|
||||
}
|
||||
}
|
||||
|
||||
// pollRequestEngineStatus calls b.RequestEngineStatus every 2 seconds until ctx
|
||||
// is done.
|
||||
func (b *LocalBackend) pollRequestEngineStatus(ctx context.Context) {
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
b.RequestEngineStatus()
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// send delivers n to the connected frontend and any API watchers from
|
||||
// LocalBackend.WatchNotifications (via the LocalAPI).
|
||||
//
|
||||
|
@ -602,8 +602,17 @@ func (h *Handler) serveWatchIPNBus(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
f.Flush()
|
||||
|
||||
var mask ipn.NotifyWatchOpt
|
||||
if s := r.FormValue("mask"); s != "" {
|
||||
v, err := strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
http.Error(w, "bad mask", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
mask = ipn.NotifyWatchOpt(v)
|
||||
}
|
||||
ctx := r.Context()
|
||||
h.b.WatchNotifications(ctx, func(roNotify *ipn.Notify) (keepGoing bool) {
|
||||
h.b.WatchNotifications(ctx, mask, func(roNotify *ipn.Notify) (keepGoing bool) {
|
||||
js, err := json.Marshal(roNotify)
|
||||
if err != nil {
|
||||
h.logf("json.Marshal: %v", err)
|
||||
|
Loading…
Reference in New Issue
Block a user