go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/common/storage/memory/memory.go (about) 1 // Copyright 2015 The LUCI Authors. 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 memory implements in-memory Storage structures. 16 // 17 // It is designed for testing, and hasn't been optimized for any productio use. 18 package memory 19 20 import ( 21 "context" 22 "errors" 23 "sync" 24 25 "google.golang.org/protobuf/proto" 26 27 "go.chromium.org/luci/logdog/api/logpb" 28 "go.chromium.org/luci/logdog/common/storage" 29 "go.chromium.org/luci/logdog/common/types" 30 ) 31 32 type logStream struct { 33 logs map[types.MessageIndex][]byte 34 latestIndex types.MessageIndex 35 } 36 37 type rec struct { 38 index types.MessageIndex 39 data []byte 40 } 41 42 type streamKey struct { 43 project string 44 path types.StreamPath 45 } 46 47 // Storage is an implementation of the storage.Storage interface that stores 48 // data in memory. 49 // 50 // This is intended for testing, and not intended to be performant. 51 type Storage struct { 52 // MaxGetCount, if not zero, is the maximum number of records to retrieve from 53 // a single Get request. 54 MaxGetCount int 55 56 stateMu sync.Mutex 57 streams map[streamKey]*logStream 58 closed bool 59 err error 60 } 61 62 var _ storage.Storage = (*Storage)(nil) 63 64 // Close implements storage.Storage. 65 func (s *Storage) Close() { 66 s.run(func() error { 67 s.closed = true 68 return nil 69 }) 70 } 71 72 // ResetClose resets the storage instance, allowing it to be used another time. 73 // The data stored in this instance is not changed. 74 func (s *Storage) ResetClose() { 75 s.stateMu.Lock() 76 defer s.stateMu.Unlock() 77 78 s.closed = false 79 } 80 81 // Put implements storage.Storage. 82 func (s *Storage) Put(c context.Context, req storage.PutRequest) error { 83 return s.run(func() error { 84 ls := s.getLogStreamLocked(req.Project, req.Path, true) 85 86 for i, v := range req.Values { 87 index := req.Index + types.MessageIndex(i) 88 if _, ok := ls.logs[index]; ok { 89 return storage.ErrExists 90 } 91 92 clone := make([]byte, len(v)) 93 copy(clone, v) 94 ls.logs[index] = clone 95 if index > ls.latestIndex { 96 ls.latestIndex = index 97 } 98 } 99 return nil 100 }) 101 } 102 103 // Expunge implements storage.Storage. 104 func (s *Storage) Expunge(c context.Context, req storage.ExpungeRequest) error { 105 return s.run(func() error { 106 ls := s.getLogStreamLocked(req.Project, req.Path, true) 107 ls.logs = nil 108 return nil 109 }) 110 } 111 112 // Get implements storage.Storage. 113 func (s *Storage) Get(c context.Context, req storage.GetRequest, cb storage.GetCallback) error { 114 recs := []*rec(nil) 115 err := s.run(func() error { 116 ls := s.getLogStreamLocked(req.Project, req.Path, false) 117 if ls == nil { 118 return storage.ErrDoesNotExist 119 } 120 121 limit := len(ls.logs) 122 if req.Limit > 0 && req.Limit < limit { 123 limit = req.Limit 124 } 125 if s.MaxGetCount > 0 && s.MaxGetCount < limit { 126 limit = s.MaxGetCount 127 } 128 129 // Grab all records starting from our start index. 130 for idx := req.Index; idx <= ls.latestIndex; idx++ { 131 if le, ok := ls.logs[idx]; ok { 132 recs = append(recs, &rec{ 133 index: idx, 134 data: le, 135 }) 136 } 137 138 if len(recs) >= limit { 139 break 140 } 141 } 142 return nil 143 }) 144 if err != nil { 145 return err 146 } 147 148 // Punt all of the records upstream. We copy the data to prevent the 149 // callback from accidentally mutating it. We reuse the data buffer to try 150 // and catch errors when the callback retains the data. 151 for _, r := range recs { 152 var dataCopy []byte 153 if !req.KeysOnly { 154 dataCopy = make([]byte, len(r.data)) 155 copy(dataCopy, r.data) 156 } 157 if !cb(storage.MakeEntry(dataCopy, r.index)) { 158 break 159 } 160 } 161 162 return nil 163 } 164 165 // Tail implements storage.Storage. 166 func (s *Storage) Tail(c context.Context, project string, path types.StreamPath) (*storage.Entry, error) { 167 var r *rec 168 169 // Find the latest log, then return it. 170 err := s.run(func() error { 171 ls := s.getLogStreamLocked(project, path, false) 172 if ls == nil { 173 return storage.ErrDoesNotExist 174 } 175 176 r = &rec{ 177 index: ls.latestIndex, 178 data: ls.logs[ls.latestIndex], 179 } 180 return nil 181 }) 182 if err != nil { 183 return nil, err 184 } 185 return storage.MakeEntry(r.data, r.index), nil 186 } 187 188 // Count returns the number of log records for the given stream. 189 func (s *Storage) Count(project string, path types.StreamPath) (c int) { 190 s.run(func() error { 191 if st := s.getLogStreamLocked(project, path, false); st != nil { 192 c = len(st.logs) 193 } 194 return nil 195 }) 196 return 197 } 198 199 // SetErr sets the storage's error value. If not nil, all operations will fail 200 // with this error. 201 func (s *Storage) SetErr(err error) { 202 s.stateMu.Lock() 203 defer s.stateMu.Unlock() 204 s.err = err 205 } 206 207 func (s *Storage) run(f func() error) error { 208 s.stateMu.Lock() 209 defer s.stateMu.Unlock() 210 211 if s.err != nil { 212 return s.err 213 } 214 if s.closed { 215 return errors.New("storage is closed") 216 } 217 return f() 218 } 219 220 func (s *Storage) getLogStreamLocked(project string, path types.StreamPath, create bool) *logStream { 221 key := streamKey{ 222 project: project, 223 path: path, 224 } 225 226 ls := s.streams[key] 227 if ls == nil && create { 228 ls = &logStream{ 229 logs: map[types.MessageIndex][]byte{}, 230 latestIndex: -1, 231 } 232 233 if s.streams == nil { 234 s.streams = map[streamKey]*logStream{} 235 } 236 s.streams[key] = ls 237 } 238 239 return ls 240 } 241 242 // PutEntries is a convenience method for ingesting logpb.Entry's into this 243 // Storage object. 244 func (s *Storage) PutEntries(ctx context.Context, project string, path types.StreamPath, entries ...*logpb.LogEntry) { 245 for _, ent := range entries { 246 value, err := proto.Marshal(ent) 247 if err != nil { 248 panic(err) 249 } 250 s.Put(ctx, storage.PutRequest{ 251 Project: project, 252 Path: path, 253 Index: types.MessageIndex(ent.StreamIndex), 254 Values: [][]byte{value}, 255 }) 256 } 257 }