fix(input): correct lon mapping
This commit is contained in:
parent
19557f3a62
commit
84d1664b29
20 changed files with 1197 additions and 137 deletions
|
|
@ -161,6 +161,57 @@ func (m *Manager) SelectFor(t time.Time, lat, lng float64) weather.WindField {
|
|||
return nil
|
||||
}
|
||||
|
||||
// FieldFor returns the global dataset whose epoch is epoch, loading it from
|
||||
// storage on demand when it is stored but not currently active.
|
||||
//
|
||||
// Being stored is enough. A caller asking for a specific run — an archival
|
||||
// forecast, a reproducible comparison — must not have to make it the service's
|
||||
// active dataset first, and asking for it must not repoint the service: Load
|
||||
// appends, and Active keeps returning the first global it finds.
|
||||
//
|
||||
// Errors when no stored dataset carries that epoch, so a request naming a
|
||||
// dataset the service cannot serve is refused rather than quietly answered from
|
||||
// a different one.
|
||||
//
|
||||
// ponytail: loaded datasets are never evicted, so a long-lived service asked for
|
||||
// many distinct epochs accumulates one mmap and fd each. Add LRU eviction here
|
||||
// if that becomes real; the files are mmap-backed, so the cost is address space
|
||||
// and descriptors, not resident memory.
|
||||
func (m *Manager) FieldFor(ctx context.Context, epoch time.Time) (weather.WindField, error) {
|
||||
if f := m.activeFieldAt(epoch); f != nil {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
stored, err := m.store.List()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list stored datasets: %w", err)
|
||||
}
|
||||
for _, id := range stored {
|
||||
if !id.Subset.IsGlobal() || !id.Epoch.Equal(epoch) {
|
||||
continue
|
||||
}
|
||||
if err := m.Load(ctx, id); err != nil {
|
||||
return nil, fmt.Errorf("load %s: %w", id.Filename(), err)
|
||||
}
|
||||
if f := m.activeFieldAt(epoch); f != nil {
|
||||
return f, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("dataset %s is not stored", epoch.UTC().Format(time.RFC3339))
|
||||
}
|
||||
|
||||
// activeFieldAt returns the loaded global field with exactly this epoch, or nil.
|
||||
func (m *Manager) activeFieldAt(epoch time.Time) weather.WindField {
|
||||
m.activeMu.RLock()
|
||||
defer m.activeMu.RUnlock()
|
||||
for _, d := range m.active {
|
||||
if d.ID.Subset.IsGlobal() && d.ID.Epoch.Equal(epoch) {
|
||||
return d.Field
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadedDatasets returns snapshots of every currently-loaded dataset.
|
||||
func (m *Manager) LoadedDatasets() []LoadedDatasetInfo {
|
||||
m.activeMu.RLock()
|
||||
|
|
@ -309,36 +360,48 @@ func (m *Manager) Load(ctx context.Context, id DatasetID) error {
|
|||
//
|
||||
// Returns the JobID started, or empty string when nothing was scheduled.
|
||||
func (m *Manager) Refresh(ctx context.Context, freshnessTTL time.Duration) (string, error) {
|
||||
if a := m.activeGlobal(); a != nil && time.Since(a.ID.Epoch) < freshnessTTL {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if datasets, err := m.store.List(); err == nil {
|
||||
for _, id := range datasets {
|
||||
if !id.Subset.IsGlobal() {
|
||||
continue
|
||||
}
|
||||
if time.Since(id.Epoch) > freshnessTTL {
|
||||
continue
|
||||
}
|
||||
if a := m.activeGlobal(); a != nil && a.ID.Equals(id) {
|
||||
return "", nil
|
||||
}
|
||||
if err := m.Load(ctx, id); err == nil {
|
||||
return "", nil
|
||||
// Get something usable loaded first, whatever its age.
|
||||
//
|
||||
// freshnessTTL answers one question only: go fetch something newer? It must
|
||||
// never decide whether data already on disk may be read. While one check
|
||||
// served both, a stored run older than the TTL was skipped even when it was
|
||||
// the only dataset present — active stayed empty, every prediction answered
|
||||
// "no dataset loaded" with gigabytes of usable wind on disk, and with no
|
||||
// reachable origin that state was permanent.
|
||||
if m.activeGlobal() == nil {
|
||||
if stored, err := m.store.List(); err == nil {
|
||||
for _, id := range stored { // newest first
|
||||
if !id.Subset.IsGlobal() {
|
||||
continue
|
||||
}
|
||||
if err := m.Load(ctx, id); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
active := m.activeGlobal()
|
||||
if active != nil && time.Since(active.ID.Epoch) < freshnessTTL {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
latest, err := m.src.LatestEpoch(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("latest epoch: %w", err)
|
||||
}
|
||||
id := DatasetID{Epoch: latest}
|
||||
if a := m.activeGlobal(); a != nil && !latest.After(a.ID.Epoch) {
|
||||
if active != nil && !latest.After(active.ID.Epoch) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Another replica sharing this volume may have committed it already.
|
||||
id := DatasetID{Epoch: latest}
|
||||
if m.store.Exists(id) {
|
||||
if err := m.Load(ctx, id); err == nil {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
jobID := m.Download(id)
|
||||
go m.loadAfterCompletion(jobID, id)
|
||||
return jobID, nil
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue