github.com/creachadair/ffs@v0.17.3/storage/monitor/monitor.go (about) 1 // Copyright 2019 Michael J. Fromberger. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package monitor implements common plumbing for implementations of the 16 // [blob.Store] interface based on storage with a flat key space. 17 // 18 // # Overview 19 // 20 // The [M] type implements shared plumbing for the methods of a [blob.Store]. 21 // It is intended to be embedded in another type to provide the required 22 // methods of the interface. The monitor is parameterized by a storage handle 23 // (DB) and an implementation of the [blob.KV] interface using that storage. 24 // 25 // Keyspaces managed by the monitor are partitioned by adding a key prefix. 26 // The prefix is derived by hashing the path of (sub)space and keyspace names 27 // from the root of the store (see [dbkey]). This ensures the prefix for a 28 // given path is stable without explicitly persisting the mapping of names. 29 // 30 // To construct an [M], the caller must provide, at minimum: 31 // 32 // - A storage handle (typically a database or storage client). 33 // - An implementation of the [blob.KV] interface based on that storage. 34 // - A constructor to create new instances of that KV implementation. 35 // 36 // The caller may also optionally provide a constructor to derive new substore 37 // instances. This is not necessary unless the storage handle requires state to 38 // track its own subspace organization, and is typically omitted. 39 package monitor 40 41 import ( 42 "context" 43 "iter" 44 "sync" 45 46 "github.com/creachadair/ffs/blob" 47 "github.com/creachadair/ffs/storage/dbkey" 48 ) 49 50 // Config carries settings for construction of an [M]. 51 // At minimum, the DB and NewKV fields must be populated. 52 type Config[DB any, KV blob.KV] struct { 53 // DB represents the initial state of the monitor. 54 // It must be safe to copy DB, so if the state contains locks or other 55 // values that cannot be safely copied, use a pointer type. 56 DB DB 57 58 // Prefix gives the initial storage prefix of the root. Empty is a valid, 59 // safe default. 60 Prefix dbkey.Prefix 61 62 // NewKV construts a KV instance from the current state, where db is the 63 // store state, pfx the derived prefix for the new KV, and name is the name 64 // passed to the KV call. 65 NewKV func(ctx context.Context, db DB, pfx dbkey.Prefix, name string) (KV, error) 66 67 // NewSub constructs a sub-DB state from the current state, where db is the 68 // store state, pfx the derived prefix for the new subspace, and name is the 69 // name passed to the Sub call. If NewSub is nil, the existing state is 70 // copied without change. 71 NewSub func(ctx context.Context, db DB, pfx dbkey.Prefix, name string) (DB, error) 72 } 73 74 // A M value manages keyspace and substore allocations for the specified 75 // database and KV implementations. The resulting value implements [blob.Store]. 76 type M[DB any, KV blob.KV] struct { 77 DB DB 78 prefix dbkey.Prefix 79 newKV func(context.Context, DB, dbkey.Prefix, string) (KV, error) 80 newSub func(context.Context, DB, dbkey.Prefix, string) (DB, error) 81 82 μ sync.Mutex 83 subs map[string]*M[DB, KV] 84 kvs map[string]KV 85 } 86 87 // New constructs a new empty store using the specified database, prefix, and 88 // KV constructor function. New will panic if cfg.NewKV is nil. 89 func New[DB any, KV blob.KV](cfg Config[DB, KV]) *M[DB, KV] { 90 if cfg.NewKV == nil { 91 panic("KV constructor is nil") 92 } 93 if cfg.NewSub == nil { 94 cfg.NewSub = func(_ context.Context, old DB, _ dbkey.Prefix, _ string) (DB, error) { 95 return old, nil 96 } 97 } 98 return &M[DB, KV]{ 99 DB: cfg.DB, 100 prefix: cfg.Prefix, 101 newKV: cfg.NewKV, 102 newSub: cfg.NewSub, 103 subs: make(map[string]*M[DB, KV]), 104 kvs: make(map[string]KV), 105 } 106 } 107 108 // KV implements a method of [blob.Store]. A successful result has concrete 109 // type [KV]. Any error reported by this method is from the NewKV callback 110 // provided at construction. 111 func (d *M[DB, KV]) KV(ctx context.Context, name string) (blob.KV, error) { 112 d.μ.Lock() 113 defer d.μ.Unlock() 114 115 kv, ok := d.kvs[name] 116 if !ok { 117 var err error 118 kv, err = d.newKV(ctx, d.DB, d.prefix.Keyspace(name), name) 119 if err != nil { 120 return nil, err 121 } 122 d.kvs[name] = kv 123 } 124 return kv, nil 125 } 126 127 // CAS implements a method of [blob.Store]. This implementation uses the 128 // default [blob.CASFromKV] construction. 129 func (d *M[DB, KV]) CAS(ctx context.Context, name string) (blob.CAS, error) { 130 return blob.CASFromKVError(d.KV(ctx, name)) 131 } 132 133 // Sub implements a method of [blob.Store]. Any error reported by this method 134 // is from the NewSub callback provided at construction. If no such callback 135 // was provided, it will always succeed. 136 func (d *M[DB, KV]) Sub(ctx context.Context, name string) (blob.Store, error) { 137 d.μ.Lock() 138 defer d.μ.Unlock() 139 140 sub, ok := d.subs[name] 141 if !ok { 142 npfx := d.prefix.Sub(name) 143 ndb, err := d.newSub(ctx, d.DB, npfx, name) 144 if err != nil { 145 return nil, err 146 } 147 sub = New(Config[DB, KV]{ 148 DB: ndb, 149 Prefix: npfx, 150 NewKV: d.newKV, 151 NewSub: d.newSub, 152 }) 153 d.subs[name] = sub 154 } 155 return sub, nil 156 } 157 158 // AllKV implements an iterator over all the KV values defined by d. 159 func (d *M[DB, KV]) AllKV() iter.Seq[KV] { 160 return func(yield func(KV) bool) { 161 d.μ.Lock() 162 defer d.μ.Unlock() 163 164 for _, sub := range d.subs { 165 for kv := range sub.AllKV() { 166 if !yield(kv) { 167 return 168 } 169 } 170 } 171 for _, kv := range d.kvs { 172 if !yield(kv) { 173 return 174 } 175 } 176 } 177 }