ipn{,/ipnserver}: delay JSON marshaling of ipn.Notifies

If nobody is connected to the IPN bus, don't burn CPU & waste
allocations (causing more GC) by encoding netmaps for nobody.

This will notably help hello.ipn.dev.

Updates tailscale/corp#1773

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/1922/head
Brad Fitzpatrick 4 years ago committed by Brad Fitzpatrick
parent dc32b4695c
commit 366b3d3f62

@ -6,7 +6,9 @@ package ipnserver
import ( import (
"bufio" "bufio"
"bytes"
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -251,8 +253,7 @@ func (s *server) serveConn(ctx context.Context, c net.Conn, logf logger.Logf) {
return return
} }
defer c.Close() defer c.Close()
serverToClient := func(b []byte) { ipn.WriteMsg(c, b) } bs := ipn.NewBackendServer(logf, nil, jsonNotifier(c, s.logf))
bs := ipn.NewBackendServer(logf, nil, serverToClient)
_, occupied := err.(inUseOtherUserError) _, occupied := err.(inUseOtherUserError)
if occupied { if occupied {
bs.SendInUseOtherUserErrorMessage(err.Error()) bs.SendInUseOtherUserErrorMessage(err.Error())
@ -567,7 +568,9 @@ func (s *server) setServerModeUserLocked() {
} }
} }
func (s *server) writeToClients(b []byte) { var jsonEscapedZero = []byte(`\u0000`)
func (s *server) writeToClients(n ipn.Notify) {
inServerMode := s.b.InServerMode() inServerMode := s.b.InServerMode()
s.mu.Lock() s.mu.Lock()
@ -584,10 +587,19 @@ func (s *server) writeToClients(b []byte) {
} }
} }
if len(s.clients) == 0 {
// Common case (at least on busy servers): nobody
// connected (no GUI, etc), so return before
// serializing JSON.
return
}
if b, ok := marshalNotify(n, s.logf); ok {
for c := range s.clients { for c := range s.clients {
ipn.WriteMsg(c, b) ipn.WriteMsg(c, b)
} }
} }
}
// Run runs a Tailscale backend service. // Run runs a Tailscale backend service.
// The getEngine func is called repeatedly, once per connection, until it returns an engine successfully. // The getEngine func is called repeatedly, once per connection, until it returns an engine successfully.
@ -671,8 +683,7 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
errMsg := err.Error() errMsg := err.Error()
go func() { go func() {
defer c.Close() defer c.Close()
serverToClient := func(b []byte) { ipn.WriteMsg(c, b) } bs := ipn.NewBackendServer(logf, nil, jsonNotifier(c, logf))
bs := ipn.NewBackendServer(logf, nil, serverToClient)
bs.SendErrorMessage(errMsg) bs.SendErrorMessage(errMsg)
time.Sleep(time.Second) time.Sleep(time.Second)
}() }()
@ -962,3 +973,25 @@ func peerPid(entries []netstat.Entry, la, ra netaddr.IPPort) int {
} }
return 0 return 0
} }
// jsonNotifier returns a notify-writer func that writes ipn.Notify
// messages to w.
func jsonNotifier(w io.Writer, logf logger.Logf) func(ipn.Notify) {
return func(n ipn.Notify) {
if b, ok := marshalNotify(n, logf); ok {
ipn.WriteMsg(w, b)
}
}
}
func marshalNotify(n ipn.Notify, logf logger.Logf) (b []byte, ok bool) {
b, err := json.Marshal(n)
if err != nil {
logf("ipnserver: [unexpected] error serializing JSON: %v", err)
return nil, false
}
if bytes.Contains(b, jsonEscapedZero) {
logf("[unexpected] zero byte in BackendServer.send notify message: %q", b)
}
return b, true
}

@ -89,7 +89,7 @@ type Command struct {
type BackendServer struct { type BackendServer struct {
logf logger.Logf logf logger.Logf
b Backend // the Backend we are serving up b Backend // the Backend we are serving up
sendNotifyMsg func(jsonMsg []byte) // send a notification message sendNotifyMsg func(Notify) // send a notification message
GotQuit bool // a Quit command was received GotQuit bool // a Quit command was received
} }
@ -98,7 +98,7 @@ type BackendServer struct {
// If sendNotifyMsg is non-nil, it additionally sets the Backend's // If sendNotifyMsg is non-nil, it additionally sets the Backend's
// notification callback to call the func with ipn.Notify messages in // notification callback to call the func with ipn.Notify messages in
// JSON form. If nil, it does not change the notification callback. // JSON form. If nil, it does not change the notification callback.
func NewBackendServer(logf logger.Logf, b Backend, sendNotifyMsg func(b []byte)) *BackendServer { func NewBackendServer(logf logger.Logf, b Backend, sendNotifyMsg func(Notify)) *BackendServer {
bs := &BackendServer{ bs := &BackendServer{
logf: logf, logf: logf,
b: b, b: b,
@ -115,14 +115,7 @@ func (bs *BackendServer) send(n Notify) {
return return
} }
n.Version = version.Long n.Version = version.Long
b, err := json.Marshal(n) bs.sendNotifyMsg(n)
if err != nil {
log.Fatalf("Failed json.Marshal(notify): %v\n%#v", err, n)
}
if bytes.Contains(b, jsonEscapedZero) {
log.Printf("[unexpected] zero byte in BackendServer.send notify message: %q", b)
}
bs.sendNotifyMsg(b)
} }
func (bs *BackendServer) SendErrorMessage(msg string) { func (bs *BackendServer) SendErrorMessage(msg string) {

@ -7,6 +7,7 @@ package ipn
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/json"
"testing" "testing"
"time" "time"
@ -74,7 +75,11 @@ func TestClientServer(t *testing.T) {
bc.GotNotifyMsg(b) bc.GotNotifyMsg(b)
} }
}() }()
serverToClient := func(b []byte) { serverToClient := func(n Notify) {
b, err := json.Marshal(n)
if err != nil {
panic(err.Error())
}
serverToClientCh <- append([]byte{}, b...) serverToClientCh <- append([]byte{}, b...)
} }
clientToServer := func(b []byte) { clientToServer := func(b []byte) {

Loading…
Cancel
Save