diff --git a/internal/meshmapper/channels.go b/internal/meshmapper/channels.go index 934af8a..4ead380 100644 --- a/internal/meshmapper/channels.go +++ b/internal/meshmapper/channels.go @@ -65,7 +65,7 @@ type Channels struct { func NewChannels(ctx context.Context, cfg config.MeshMapperChannelsConfig, store ChannelStore, dir *Directory, keys ChannelKeys) (*Channels, error) { c := &Channels{store: store, keys: keys, dir: dir, channelsURL: channelsEndpoint, interval: cfg.Interval(), seen: map[string]bool{}, client: &http.Client{ - Timeout: 10 * time.Second, + Timeout: requestTimeout, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }, }} if !cfg.Enabled { @@ -130,7 +130,7 @@ func (c *Channels) Refresh(ctx context.Context) (err error) { err = nil } }() - ctx, cancel := context.WithTimeout(ctx, 15*time.Second) + ctx, cancel := context.WithTimeout(ctx, refreshTimeout) defer cancel() now := time.Now().UTC() iatas, err := c.store.ListKnownIATAs(ctx) diff --git a/internal/meshmapper/directory.go b/internal/meshmapper/directory.go index 4c1fedd..20f7503 100644 --- a/internal/meshmapper/directory.go +++ b/internal/meshmapper/directory.go @@ -64,7 +64,7 @@ type Directory struct { func NewDirectory(store DirectoryStore) *Directory { return &Directory{store: store, listURL: ZonesURL, lists: map[string]*zoneList{}, client: &http.Client{ - Timeout: 30 * time.Second, + Timeout: requestTimeout, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }, }} } diff --git a/internal/meshmapper/scopes.go b/internal/meshmapper/scopes.go index a4171be..3ecdcbe 100644 --- a/internal/meshmapper/scopes.go +++ b/internal/meshmapper/scopes.go @@ -79,7 +79,7 @@ func (i *Importer) SetCacheInvalidator(fn func(context.Context)) { i.onChange = func New(ctx context.Context, cfg config.MeshMapperScopesConfig, store Store, dir *Directory, scopes *scopestore.ScopeStore, manual []scopestore.Entry) (*Importer, error) { i := &Importer{store: store, scopes: scopes, manual: manual, dir: dir, scopesURL: scopesEndpoint, seen: map[string]bool{}, interval: cfg.Interval(), client: &http.Client{ - Timeout: 10 * time.Second, + Timeout: requestTimeout, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }, }} if !cfg.Enabled { @@ -142,7 +142,7 @@ func (i *Importer) Refresh(ctx context.Context) (err error) { err = nil } }() - ctx, cancel := context.WithTimeout(ctx, 15*time.Second) + ctx, cancel := context.WithTimeout(ctx, refreshTimeout) defer cancel() now := time.Now().UTC() iatas, err := i.store.ListKnownIATAs(ctx) diff --git a/internal/meshmapper/zones.go b/internal/meshmapper/zones.go index 20b99ff..1310b71 100644 --- a/internal/meshmapper/zones.go +++ b/internal/meshmapper/zones.go @@ -26,6 +26,10 @@ const ( MaxZoneList = 1 << 20 MaxBoundaryBody = 8 << 20 // outlines are never simplified + // MeshMapper asks clients to allow 60-120s; a request given up on may still use the day's call. + requestTimeout = 60 * time.Second + refreshTimeout = requestTimeout + 30*time.Second // room for the DB write after a slow request + zoneListFresh = config.MinZonesRefresh // get_zones.php allows one request per country per 23.5h zoneFailureRetry = config.MinZonesRefresh // failed requests count too ) @@ -82,7 +86,7 @@ type Zones struct { func NewZones(cfg config.MeshMapperZonesConfig, store ZoneStore, dir *Directory) *Zones { return &Zones{store: store, enabled: cfg.Enabled, importGroups: cfg.Enabled && cfg.ImportGroups, seen: map[string]bool{}, interval: cfg.Interval(), dir: dir, boundsURL: boundaryEndpoint, client: &http.Client{ - Timeout: 30 * time.Second, + Timeout: requestTimeout, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }, }} } @@ -165,7 +169,7 @@ func (z *Zones) Refresh(ctx context.Context) (err error) { err = nil } }() - ctx, cancel := context.WithTimeout(ctx, time.Minute) + ctx, cancel := context.WithTimeout(ctx, refreshTimeout) defer cancel() now := time.Now().UTC() // IATAs are created from traffic, so pick up new ones every tick. diff --git a/internal/meshmapper/zones_test.go b/internal/meshmapper/zones_test.go index dc27274..bdf4589 100644 --- a/internal/meshmapper/zones_test.go +++ b/internal/meshmapper/zones_test.go @@ -15,6 +15,8 @@ import ( "time" "github.com/MeshCore-Beacon/beacon-server/internal/config" + "github.com/MeshCore-Beacon/beacon-server/internal/keystore" + "github.com/MeshCore-Beacon/beacon-server/internal/scopestore" ) const square = `{"type":"Polygon","coordinates":[[[-76,45],[-75,45],[-75,46],[-76,46],[-76,45]]]}` @@ -465,3 +467,28 @@ func TestDecodeBoundaryRejectsUnusableOutlines(t *testing.T) { t.Fatal("case-insensitive code rejected", err) } } + +func TestClientsAllowMeshMapperTimeout(t *testing.T) { + ctx := context.Background() + scopes, err := New(ctx, config.MeshMapperScopesConfig{}, nil, NewDirectory(nil), scopestore.New(), nil) + if err != nil { + t.Fatal(err) + } + channels, err := NewChannels(ctx, config.MeshMapperChannelsConfig{}, newChannelMemoryStore(), NewDirectory(nil), keystore.NewMapKeyStore(nil)) + if err != nil { + t.Fatal(err) + } + for name, c := range map[string]*http.Client{ + "directory": NewDirectory(nil).client, + "zones": NewZones(config.MeshMapperZonesConfig{}, nil, nil).client, + "scopes": scopes.client, + "channels": channels.client, + } { + if c.Timeout < 60*time.Second { + t.Errorf("%s client gives up after %v; MeshMapper asks for 60-120s", name, c.Timeout) + } + } + if refreshTimeout <= requestTimeout { + t.Fatal("a refresh must outlast its request") + } +}