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  }