carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package geo
2
3import (
4 "net"
5 "sync"
6 "time"
7)
8
9// Snapshot is one answer from one reader: the result, that reader's source,
10// and whether any reader was loaded at all (geoip spec §5.4, codex R2-4).
11// Headers, analytics and attribution all come from one Snapshot, so a swap
12// between the lookup and the response can never relabel the data.
13type Snapshot struct {
14 Result Result
15 Found bool
16 Source string // empty only when Loaded is false
17 Loaded bool
18}
19
20// Swappable is a reader that can be replaced while lookups run. The old
21// mapping is closed only after the write lock has been taken and released,
22// so no lookup is ever in flight on an unmapped file. There is deliberately
23// no Source() method: a source read apart from its lookup is the race this
24// type exists to prevent.
25type Swappable struct {
26 mu sync.RWMutex
27 cur *MMDB
28}
29
30func NewSwappable() *Swappable { return &Swappable{} }
31
32// Lookup answers under one read lock.
33func (s *Swappable) Lookup(ip net.IP) Snapshot {
34 s.mu.RLock()
35 defer s.mu.RUnlock()
36 if s.cur == nil {
37 return Snapshot{}
38 }
39 r, ok := s.cur.Lookup(ip)
40 return Snapshot{Result: r, Found: ok, Source: s.cur.Source(), Loaded: true}
41}
42
43// Locate makes Swappable a Locator, so the console's region guess can hold
44// it without ever seeing the *MMDB underneath (codex R3-2).
45func (s *Swappable) Locate(ip net.IP) (Point, bool) {
46 s.mu.RLock()
47 defer s.mu.RUnlock()
48 if s.cur == nil {
49 return Point{}, false
50 }
51 return s.cur.Locate(ip)
52}
53
54// Loaded reports the current reader's source and build time.
55func (s *Swappable) Loaded() (source string, build time.Time, ok bool) {
56 s.mu.RLock()
57 defer s.mu.RUnlock()
58 if s.cur == nil {
59 return "", time.Time{}, false
60 }
61 return s.cur.Source(), s.cur.BuildEpoch(), true
62}
63
64// Swap installs next (nil = none) and closes the previous reader after
65// every lookup that could see it has finished. Go's RWMutex blocks new
66// readers while a writer waits, so lookups cannot starve a swap.
67func (s *Swappable) Swap(next *MMDB) {
68 s.mu.Lock()
69 old := s.cur
70 s.cur = next
71 s.mu.Unlock()
72 if old != nil && old != next {
73 old.Close()
74 }
75}
76
77// LocateFrom is Locate plus the answering reader's source, under one read
78// lock, so a credit shown beside a guess always names the database that
79// made it.
80func (s *Swappable) LocateFrom(ip net.IP) (Point, bool, string) {
81 s.mu.RLock()
82 defer s.mu.RUnlock()
83 if s.cur == nil {
84 return Point{}, false, ""
85 }
86 p, ok := s.cur.Locate(ip)
87 return p, ok, s.cur.Source()
88}
89