ipn/ipnlocal, drive: stop using netmap.NetworkMap in Taildrive too
This applies the same treatment from PR #20162 (netlog) and PR #20171 (wglog) to the local Taildrive filesystem wiring, ending the per-netmap-update O(n) rebuild of the drive remotes list. This moves the O(n peers) taildrive-remote list rebuild from every peer change (which previously happened regardless of whether you were even using taildrive) to instead happen only as needed. That running on every netmap update and was a contributor to the broader quadratic behavior we want to eliminate when a single peer is added or removed. Instead, this introduces drive.RemoteSource, a small interface the Taildrive filesystem pulls from lazily on incoming WebDAV requests, and caches by a generation counter. ipn/ipnlocal installs a driveRemoteSource once at NewLocalBackend time and bumps LocalBackend.driveGen on the three events that can actually flip the drive-capable peer set: full netmap installs (domain + self caps), UpdateNetmapDelta (peer add/remove or per-peer address changes), and updatePacketFilter (since PeerCapability values are derived from the packet filter rules, not from peer.CapMap). The hook itself is kept but narrowed: it no longer takes a *netmap.NetworkMap and its only remaining job is to re-notify IPN bus listeners of the current local shares list on full installs. This is a dependency to removing the netmap.NetworkMap type from upstream callers, like wgengine.Engine in general. (Also add a bunch more tests) Updates #12542 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com> Change-Id: I7e3d2f5b4a9c8e1d6f0a3b7c9e2d4f8a1b6c5e9d
This commit is contained in:
committed by
Brad Fitzpatrick
parent
988b0905bb
commit
1d69894084
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"iter"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -456,10 +457,48 @@ func (r *remote) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
type system struct {
|
||||
t *testing.T
|
||||
local *local
|
||||
client *gowebdav.Client
|
||||
t *testing.T
|
||||
local *local
|
||||
client *gowebdav.Client
|
||||
transport http.RoundTripper
|
||||
|
||||
mu sync.Mutex
|
||||
remotes map[string]*remote
|
||||
gen uint64
|
||||
}
|
||||
|
||||
// Domain implements [drive.RemoteSource].
|
||||
func (s *system) Domain() string { return domain }
|
||||
|
||||
// Transport implements [drive.RemoteSource].
|
||||
func (s *system) Transport() http.RoundTripper { return s.transport }
|
||||
|
||||
// Remotes implements [drive.RemoteSource].
|
||||
func (s *system) Remotes() iter.Seq[*drive.Remote] {
|
||||
s.mu.Lock()
|
||||
rs := make([]*drive.Remote, 0, len(s.remotes))
|
||||
for name, r := range s.remotes {
|
||||
url := fmt.Sprintf("http://%s", r.ln.Addr())
|
||||
rs = append(rs, &drive.Remote{
|
||||
Name: name,
|
||||
URL: func() string { return url },
|
||||
})
|
||||
}
|
||||
s.mu.Unlock()
|
||||
return func(yield func(*drive.Remote) bool) {
|
||||
for _, r := range rs {
|
||||
if !yield(r) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generation implements [drive.RemoteSource].
|
||||
func (s *system) Generation() uint64 {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.gen
|
||||
}
|
||||
|
||||
func newSystem(t *testing.T) *system {
|
||||
@@ -486,11 +525,16 @@ func newSystem(t *testing.T) *system {
|
||||
client := gowebdav.NewAuthClient(fmt.Sprintf("http://%s", ln.Addr()), &noopAuthorizer{})
|
||||
client.SetTransport(&http.Transport{DisableKeepAlives: true})
|
||||
s := &system{
|
||||
t: t,
|
||||
local: &local{ln: ln, fs: fs},
|
||||
t: t,
|
||||
local: &local{ln: ln, fs: fs},
|
||||
transport: &http.Transport{
|
||||
DisableKeepAlives: true,
|
||||
ResponseHeaderTimeout: 5 * time.Second,
|
||||
},
|
||||
client: client,
|
||||
remotes: make(map[string]*remote),
|
||||
}
|
||||
fs.SetRemoteSource(s)
|
||||
t.Cleanup(s.stop)
|
||||
return s
|
||||
}
|
||||
@@ -518,22 +562,11 @@ func (s *system) addRemote(name string) string {
|
||||
}
|
||||
r.fs.SetFileServerAddr(fileServer.Addr())
|
||||
go http.Serve(ln, r)
|
||||
s.remotes[name] = r
|
||||
|
||||
remotes := make([]*drive.Remote, 0, len(s.remotes))
|
||||
for name, r := range s.remotes {
|
||||
remotes = append(remotes, &drive.Remote{
|
||||
Name: name,
|
||||
URL: func() string { return fmt.Sprintf("http://%s", r.ln.Addr()) },
|
||||
})
|
||||
}
|
||||
s.local.fs.SetRemotes(
|
||||
domain,
|
||||
remotes,
|
||||
&http.Transport{
|
||||
DisableKeepAlives: true,
|
||||
ResponseHeaderTimeout: 5 * time.Second,
|
||||
})
|
||||
s.mu.Lock()
|
||||
s.remotes[name] = r
|
||||
s.gen++
|
||||
s.mu.Unlock()
|
||||
|
||||
return fileServer.Addr()
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tailscale.com/drive"
|
||||
@@ -52,10 +53,18 @@ type FileSystemForLocal struct {
|
||||
logf logger.Logf
|
||||
h *compositedav.Handler
|
||||
listener *connListener
|
||||
|
||||
// sourceMu guards source and cachedGen. It also serializes the
|
||||
// rebuild path so concurrent requests don't race to replace
|
||||
// children with stale data.
|
||||
sourceMu sync.Mutex
|
||||
source drive.RemoteSource
|
||||
cachedGen uint64
|
||||
haveGen bool // true once cachedGen reflects an actual source.Generation call
|
||||
}
|
||||
|
||||
func (s *FileSystemForLocal) startServing() {
|
||||
hs := &http.Server{Handler: s.h}
|
||||
hs := &http.Server{Handler: http.HandlerFunc(s.serveHTTP)}
|
||||
go func() {
|
||||
err := hs.Serve(s.listener)
|
||||
if err != nil {
|
||||
@@ -65,17 +74,35 @@ func (s *FileSystemForLocal) startServing() {
|
||||
}()
|
||||
}
|
||||
|
||||
// HandleConn handles connections from local WebDAV clients
|
||||
func (s *FileSystemForLocal) HandleConn(conn net.Conn, remoteAddr net.Addr) error {
|
||||
return s.listener.HandleConn(conn, remoteAddr)
|
||||
// serveHTTP refreshes the underlying compositedav children from the
|
||||
// remote source if its generation has changed, then delegates to the
|
||||
// composite handler. The refresh path is skipped entirely when the
|
||||
// generation is unchanged, which is the common case.
|
||||
func (s *FileSystemForLocal) serveHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.refresh()
|
||||
s.h.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// SetRemotes sets the complete set of remotes on the given tailnet domain
|
||||
// using a map of name -> url. If transport is specified, that transport
|
||||
// will be used to connect to these remotes.
|
||||
func (s *FileSystemForLocal) SetRemotes(domain string, remotes []*drive.Remote, transport http.RoundTripper) {
|
||||
children := make([]*compositedav.Child, 0, len(remotes))
|
||||
for _, remote := range remotes {
|
||||
// refresh rebuilds the compositedav children from the current source
|
||||
// if its generation has changed since the last refresh. It is a no-op
|
||||
// when no source is set or when the generation matches the cached
|
||||
// value.
|
||||
func (s *FileSystemForLocal) refresh() {
|
||||
s.sourceMu.Lock()
|
||||
defer s.sourceMu.Unlock()
|
||||
|
||||
source := s.source
|
||||
if source == nil {
|
||||
return
|
||||
}
|
||||
gen := source.Generation()
|
||||
if s.haveGen && gen == s.cachedGen {
|
||||
return
|
||||
}
|
||||
|
||||
transport := source.Transport()
|
||||
var children []*compositedav.Child
|
||||
for remote := range source.Remotes() {
|
||||
children = append(children, &compositedav.Child{
|
||||
Child: &dirfs.Child{
|
||||
Name: remote.Name,
|
||||
@@ -85,8 +112,25 @@ func (s *FileSystemForLocal) SetRemotes(domain string, remotes []*drive.Remote,
|
||||
Transport: transport,
|
||||
})
|
||||
}
|
||||
s.h.SetChildren(source.Domain(), children...)
|
||||
s.cachedGen = gen
|
||||
s.haveGen = true
|
||||
}
|
||||
|
||||
s.h.SetChildren(domain, children...)
|
||||
// HandleConn handles connections from local WebDAV clients
|
||||
func (s *FileSystemForLocal) HandleConn(conn net.Conn, remoteAddr net.Addr) error {
|
||||
return s.listener.HandleConn(conn, remoteAddr)
|
||||
}
|
||||
|
||||
// SetRemoteSource sets the source from which the filesystem reads the
|
||||
// current set of remotes. It replaces any previously set source and
|
||||
// forces a rebuild on the next incoming request.
|
||||
func (s *FileSystemForLocal) SetRemoteSource(source drive.RemoteSource) {
|
||||
s.sourceMu.Lock()
|
||||
s.source = source
|
||||
s.cachedGen = 0
|
||||
s.haveGen = false
|
||||
s.sourceMu.Unlock()
|
||||
}
|
||||
|
||||
// Close() stops serving the WebDAV content
|
||||
|
||||
Reference in New Issue
Block a user