github.com/safing/portbase@v0.19.5/database/storage/hashmap/map.go (about) 1 package hashmap 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "sync" 8 "time" 9 10 "github.com/safing/portbase/database/iterator" 11 "github.com/safing/portbase/database/query" 12 "github.com/safing/portbase/database/record" 13 "github.com/safing/portbase/database/storage" 14 ) 15 16 // HashMap storage. 17 type HashMap struct { 18 name string 19 db map[string]record.Record 20 dbLock sync.RWMutex 21 } 22 23 func init() { 24 _ = storage.Register("hashmap", NewHashMap) 25 } 26 27 // NewHashMap creates a hashmap database. 28 func NewHashMap(name, location string) (storage.Interface, error) { 29 return &HashMap{ 30 name: name, 31 db: make(map[string]record.Record), 32 }, nil 33 } 34 35 // Get returns a database record. 36 func (hm *HashMap) Get(key string) (record.Record, error) { 37 hm.dbLock.RLock() 38 defer hm.dbLock.RUnlock() 39 40 r, ok := hm.db[key] 41 if !ok { 42 return nil, storage.ErrNotFound 43 } 44 return r, nil 45 } 46 47 // GetMeta returns the metadata of a database record. 48 func (hm *HashMap) GetMeta(key string) (*record.Meta, error) { 49 // TODO: Replace with more performant variant. 50 51 r, err := hm.Get(key) 52 if err != nil { 53 return nil, err 54 } 55 56 return r.Meta(), nil 57 } 58 59 // Put stores a record in the database. 60 func (hm *HashMap) Put(r record.Record) (record.Record, error) { 61 hm.dbLock.Lock() 62 defer hm.dbLock.Unlock() 63 64 hm.db[r.DatabaseKey()] = r 65 return r, nil 66 } 67 68 // PutMany stores many records in the database. 69 func (hm *HashMap) PutMany(shadowDelete bool) (chan<- record.Record, <-chan error) { 70 hm.dbLock.Lock() 71 defer hm.dbLock.Unlock() 72 // we could lock for every record, but we want to have the same behaviour 73 // as the other storage backends, especially for testing. 74 75 batch := make(chan record.Record, 100) 76 errs := make(chan error, 1) 77 78 // start handler 79 go func() { 80 for r := range batch { 81 hm.batchPutOrDelete(shadowDelete, r) 82 } 83 errs <- nil 84 }() 85 86 return batch, errs 87 } 88 89 func (hm *HashMap) batchPutOrDelete(shadowDelete bool, r record.Record) { 90 r.Lock() 91 defer r.Unlock() 92 93 hm.dbLock.Lock() 94 defer hm.dbLock.Unlock() 95 96 if !shadowDelete && r.Meta().IsDeleted() { 97 delete(hm.db, r.DatabaseKey()) 98 } else { 99 hm.db[r.DatabaseKey()] = r 100 } 101 } 102 103 // Delete deletes a record from the database. 104 func (hm *HashMap) Delete(key string) error { 105 hm.dbLock.Lock() 106 defer hm.dbLock.Unlock() 107 108 delete(hm.db, key) 109 return nil 110 } 111 112 // Query returns a an iterator for the supplied query. 113 func (hm *HashMap) Query(q *query.Query, local, internal bool) (*iterator.Iterator, error) { 114 _, err := q.Check() 115 if err != nil { 116 return nil, fmt.Errorf("invalid query: %w", err) 117 } 118 119 queryIter := iterator.New() 120 121 go hm.queryExecutor(queryIter, q, local, internal) 122 return queryIter, nil 123 } 124 125 func (hm *HashMap) queryExecutor(queryIter *iterator.Iterator, q *query.Query, local, internal bool) { 126 hm.dbLock.RLock() 127 defer hm.dbLock.RUnlock() 128 129 var err error 130 131 mapLoop: 132 for key, record := range hm.db { 133 record.Lock() 134 if !q.MatchesKey(key) || 135 !q.MatchesRecord(record) || 136 !record.Meta().CheckValidity() || 137 !record.Meta().CheckPermission(local, internal) { 138 139 record.Unlock() 140 continue 141 } 142 record.Unlock() 143 144 select { 145 case <-queryIter.Done: 146 break mapLoop 147 case queryIter.Next <- record: 148 default: 149 select { 150 case <-queryIter.Done: 151 break mapLoop 152 case queryIter.Next <- record: 153 case <-time.After(1 * time.Second): 154 err = errors.New("query timeout") 155 break mapLoop 156 } 157 } 158 159 } 160 161 queryIter.Finish(err) 162 } 163 164 // ReadOnly returns whether the database is read only. 165 func (hm *HashMap) ReadOnly() bool { 166 return false 167 } 168 169 // Injected returns whether the database is injected. 170 func (hm *HashMap) Injected() bool { 171 return false 172 } 173 174 // MaintainRecordStates maintains records states in the database. 175 func (hm *HashMap) MaintainRecordStates(ctx context.Context, purgeDeletedBefore time.Time, shadowDelete bool) error { 176 hm.dbLock.Lock() 177 defer hm.dbLock.Unlock() 178 179 now := time.Now().Unix() 180 purgeThreshold := purgeDeletedBefore.Unix() 181 182 for key, record := range hm.db { 183 // check if context is cancelled 184 select { 185 case <-ctx.Done(): 186 return nil 187 default: 188 } 189 190 meta := record.Meta() 191 switch { 192 case meta.Deleted == 0 && meta.Expires > 0 && meta.Expires < now: 193 if shadowDelete { 194 // mark as deleted 195 record.Lock() 196 meta.Deleted = meta.Expires 197 record.Unlock() 198 199 continue 200 } 201 202 // Immediately delete expired entries if shadowDelete is disabled. 203 fallthrough 204 case meta.Deleted > 0 && (!shadowDelete || meta.Deleted < purgeThreshold): 205 // delete from storage 206 delete(hm.db, key) 207 } 208 } 209 210 return nil 211 } 212 213 // Shutdown shuts down the database. 214 func (hm *HashMap) Shutdown() error { 215 return nil 216 }