ipn/ipnlocal: add mutex to webClient struct
Adds a new sync.Mutex field to the webClient struct, rather than using the general LocalBackend mutex. Since webClientGetOrInit (previously WebClientInit) gets called on every connection, we want to avoid holding the lock on LocalBackend just to check if the server is initialized. Moves all web_client.go funcs over to using the webClient.mu field. Updates tailscale/corp#14335 Signed-off-by: Sonia Appasamy <sonia@tailscale.com>
This commit is contained in:
parent
7d4221c295
commit
055394f3be
|
@ -656,7 +656,7 @@ func (b *LocalBackend) Shutdown() {
|
||||||
b.debugSink = nil
|
b.debugSink = nil
|
||||||
}
|
}
|
||||||
b.mu.Unlock()
|
b.mu.Unlock()
|
||||||
b.WebClientShutdown()
|
b.webClientShutdown()
|
||||||
|
|
||||||
if b.sockstatLogger != nil {
|
if b.sockstatLogger != nil {
|
||||||
b.sockstatLogger.Shutdown()
|
b.sockstatLogger.Shutdown()
|
||||||
|
@ -4252,7 +4252,7 @@ func (b *LocalBackend) setWebClientAtomicBoolLocked(nm *netmap.NetworkMap, prefs
|
||||||
shouldRun := prefs.Valid() && prefs.RunWebClient()
|
shouldRun := prefs.Valid() && prefs.RunWebClient()
|
||||||
wasRunning := b.webClientAtomicBool.Swap(shouldRun)
|
wasRunning := b.webClientAtomicBool.Swap(shouldRun)
|
||||||
if wasRunning && !shouldRun {
|
if wasRunning && !shouldRun {
|
||||||
go b.WebClientShutdown() // stop web client
|
go b.webClientShutdown() // stop web client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tailscale.com/client/tailscale"
|
"tailscale.com/client/tailscale"
|
||||||
|
@ -24,10 +25,12 @@ import (
|
||||||
|
|
||||||
const webClientPort = web.ListenPort
|
const webClientPort = web.ListenPort
|
||||||
|
|
||||||
// webClient holds state for the web interface for managing
|
// webClient holds state for the web interface for managing this
|
||||||
// this tailscale instance. The web interface is not used by
|
// tailscale instance. The web interface is not used by default,
|
||||||
// default, but initialized by calling LocalBackend.WebClientInit.
|
// but initialized by calling LocalBackend.WebClientGetOrInit.
|
||||||
type webClient struct {
|
type webClient struct {
|
||||||
|
mu sync.Mutex // protects webClient fields
|
||||||
|
|
||||||
server *web.Server // or nil, initialized lazily
|
server *web.Server // or nil, initialized lazily
|
||||||
|
|
||||||
// lc optionally specifies a LocalClient to use to connect
|
// lc optionally specifies a LocalClient to use to connect
|
||||||
|
@ -40,51 +43,54 @@ type webClient struct {
|
||||||
// Specifially, it sets b.web.lc to the provided LocalClient.
|
// Specifially, it sets b.web.lc to the provided LocalClient.
|
||||||
// If provided as nil, b.web.lc is cleared out.
|
// If provided as nil, b.web.lc is cleared out.
|
||||||
func (b *LocalBackend) ConfigureWebClient(lc *tailscale.LocalClient) {
|
func (b *LocalBackend) ConfigureWebClient(lc *tailscale.LocalClient) {
|
||||||
b.mu.Lock()
|
b.webClient.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.webClient.mu.Unlock()
|
||||||
b.webClient.lc = lc
|
b.webClient.lc = lc
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebClientInit initializes the web interface for managing this
|
// webClientGetOrInit gets or initializes the web server for managing
|
||||||
// tailscaled instance.
|
// this tailscaled instance.
|
||||||
// If the web interface is already running, WebClientInit is a no-op.
|
// s is always non-nil if err is empty.
|
||||||
func (b *LocalBackend) WebClientInit() (err error) {
|
func (b *LocalBackend) webClientGetOrInit() (s *web.Server, err error) {
|
||||||
if !b.ShouldRunWebClient() {
|
if !b.ShouldRunWebClient() {
|
||||||
return errors.New("web client not enabled for this device")
|
return nil, errors.New("web client not enabled for this device")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.mu.Lock()
|
b.webClient.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.webClient.mu.Unlock()
|
||||||
if b.webClient.server != nil {
|
if b.webClient.server != nil {
|
||||||
return nil
|
return b.webClient.server, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
b.logf("WebClientInit: initializing web ui")
|
b.logf("webClientGetOrInit: initializing web ui")
|
||||||
if b.webClient.server, err = web.NewServer(web.ServerOpts{
|
if b.webClient.server, err = web.NewServer(web.ServerOpts{
|
||||||
Mode: web.ManageServerMode,
|
Mode: web.ManageServerMode,
|
||||||
LocalClient: b.webClient.lc,
|
LocalClient: b.webClient.lc,
|
||||||
Logf: b.logf,
|
Logf: b.logf,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return fmt.Errorf("web.NewServer: %w", err)
|
return nil, fmt.Errorf("web.NewServer: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.logf("WebClientInit: started web ui")
|
b.logf("webClientGetOrInit: started web ui")
|
||||||
return nil
|
return b.webClient.server, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebClientShutdown shuts down any running b.webClient servers and
|
// WebClientShutdown shuts down any running b.webClient servers and
|
||||||
// clears out b.webClient state (besides the b.webClient.lc field,
|
// clears out b.webClient state (besides the b.webClient.lc field,
|
||||||
// which is left untouched because required for future web startups).
|
// which is left untouched because required for future web startups).
|
||||||
// WebClientShutdown obtains the b.mu lock.
|
// WebClientShutdown obtains the b.mu lock.
|
||||||
func (b *LocalBackend) WebClientShutdown() {
|
func (b *LocalBackend) webClientShutdown() {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
server := b.webClient.server
|
|
||||||
b.webClient.server = nil
|
|
||||||
for ap, ln := range b.webClientListeners {
|
for ap, ln := range b.webClientListeners {
|
||||||
ln.Close()
|
ln.Close()
|
||||||
delete(b.webClientListeners, ap)
|
delete(b.webClientListeners, ap)
|
||||||
}
|
}
|
||||||
b.mu.Unlock() // release lock before shutdown
|
b.mu.Unlock()
|
||||||
|
|
||||||
|
b.webClient.mu.Lock() // webClient struct uses its own mutext
|
||||||
|
server := b.webClient.server
|
||||||
|
b.webClient.server = nil
|
||||||
|
b.webClient.mu.Unlock() // release lock before shutdown
|
||||||
if server != nil {
|
if server != nil {
|
||||||
server.Shutdown()
|
server.Shutdown()
|
||||||
b.logf("WebClientShutdown: shut down web ui")
|
b.logf("WebClientShutdown: shut down web ui")
|
||||||
|
@ -93,10 +99,11 @@ func (b *LocalBackend) WebClientShutdown() {
|
||||||
|
|
||||||
// handleWebClientConn serves web client requests.
|
// handleWebClientConn serves web client requests.
|
||||||
func (b *LocalBackend) handleWebClientConn(c net.Conn) error {
|
func (b *LocalBackend) handleWebClientConn(c net.Conn) error {
|
||||||
if err := b.WebClientInit(); err != nil {
|
webServer, err := b.webClientGetOrInit()
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s := http.Server{Handler: b.webClient.server}
|
s := http.Server{Handler: webServer}
|
||||||
return s.Serve(netutil.NewOneConnListener(c, nil))
|
return s.Serve(netutil.NewOneConnListener(c, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,11 +18,11 @@ type webClient struct{}
|
||||||
|
|
||||||
func (b *LocalBackend) ConfigureWebClient(lc *tailscale.LocalClient) {}
|
func (b *LocalBackend) ConfigureWebClient(lc *tailscale.LocalClient) {}
|
||||||
|
|
||||||
func (b *LocalBackend) WebClientInit() error {
|
func (b *LocalBackend) webClientGetOrInit() error {
|
||||||
return errors.New("not implemented")
|
return errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *LocalBackend) WebClientShutdown() {}
|
func (b *LocalBackend) webClientShutdown() {}
|
||||||
|
|
||||||
func (b *LocalBackend) handleWebClientConn(c net.Conn) error {
|
func (b *LocalBackend) handleWebClientConn(c net.Conn) error {
|
||||||
return errors.New("not implemented")
|
return errors.New("not implemented")
|
||||||
|
|
Loading…
Reference in New Issue