github.com/filecoin-project/bacalhau@v0.3.23-0.20230228154132-45c989550ace/pkg/localdb/inmemory/inmemory.go (about) 1 package inmemory 2 3 import ( 4 "context" 5 "sort" 6 "time" 7 8 sync "github.com/bacalhau-project/golang-mutex-tracer" 9 "github.com/rs/zerolog/log" 10 "golang.org/x/exp/maps" 11 "golang.org/x/exp/slices" 12 13 "github.com/filecoin-project/bacalhau/pkg/bacerrors" 14 jobutils "github.com/filecoin-project/bacalhau/pkg/job" 15 "github.com/filecoin-project/bacalhau/pkg/localdb" 16 "github.com/filecoin-project/bacalhau/pkg/localdb/shared" 17 model "github.com/filecoin-project/bacalhau/pkg/model/v1beta1" 18 "github.com/filecoin-project/bacalhau/pkg/system" 19 ) 20 21 type InMemoryDatastore struct { 22 // we keep pointers to these things because we will update them partially 23 jobs map[string]*model.Job 24 states map[string]*model.JobState 25 events map[string][]model.JobEvent 26 localEvents map[string][]model.JobLocalEvent 27 mtx sync.RWMutex 28 } 29 30 func NewInMemoryDatastore() (*InMemoryDatastore, error) { 31 res := &InMemoryDatastore{ 32 jobs: map[string]*model.Job{}, 33 states: map[string]*model.JobState{}, 34 events: map[string][]model.JobEvent{}, 35 localEvents: map[string][]model.JobLocalEvent{}, 36 } 37 res.mtx.EnableTracerWithOpts(sync.Opts{ 38 Threshold: 10 * time.Millisecond, 39 Id: "InMemoryDatastore.mtx", 40 }) 41 return res, nil 42 } 43 44 // Gets a job from the datastore. 45 // 46 // Errors: 47 // 48 // - error-job-not-found -- if the job is not found 49 func (d *InMemoryDatastore) GetJob(ctx context.Context, id string) (*model.Job, error) { 50 _, span := system.NewSpan(ctx, system.GetTracer(), "pkg/localdb/inmemory.InMemoryDatastore.GetJob") 51 defer span.End() 52 53 d.mtx.RLock() 54 defer d.mtx.RUnlock() 55 return d.getJob(id) 56 } 57 58 // Get Job Events from a job ID 59 // 60 // Errors: 61 // 62 // - error-job-not-found -- if the job is not found 63 func (d *InMemoryDatastore) GetJobEvents(ctx context.Context, id string) ([]model.JobEvent, error) { 64 _, span := system.NewSpan(ctx, system.GetTracer(), "pkg/localdb/inmemory.InMemoryDatastore.GetJobEvents") 65 defer span.End() 66 67 d.mtx.RLock() 68 defer d.mtx.RUnlock() 69 _, ok := d.jobs[id] 70 if !ok { 71 return []model.JobEvent{}, bacerrors.NewJobNotFound(id) 72 } 73 result, ok := d.events[id] 74 if !ok { 75 result = []model.JobEvent{} 76 } 77 return result, nil 78 } 79 80 func (d *InMemoryDatastore) GetJobLocalEvents(ctx context.Context, id string) ([]model.JobLocalEvent, error) { 81 _, span := system.NewSpan(ctx, system.GetTracer(), "pkg/localdb/inmemory.InMemoryDatastore.GetJobLocalEvents") 82 defer span.End() 83 84 d.mtx.RLock() 85 defer d.mtx.RUnlock() 86 _, ok := d.jobs[id] 87 if !ok { 88 return []model.JobLocalEvent{}, bacerrors.NewJobNotFound(id) 89 } 90 result, ok := d.localEvents[id] 91 if !ok { 92 result = []model.JobLocalEvent{} 93 } 94 return result, nil 95 } 96 97 func (d *InMemoryDatastore) GetJobs(ctx context.Context, query localdb.JobQuery) ([]*model.Job, error) { 98 ctx, span := system.NewSpan(ctx, system.GetTracer(), "pkg/localdb/inmemory.InMemoryDatastore.GetJobs") 99 defer span.End() 100 101 d.mtx.RLock() 102 defer d.mtx.RUnlock() 103 result := []*model.Job{} 104 105 if query.ID != "" { 106 log.Ctx(ctx).Trace().Msgf("querying for single job %s", query.ID) 107 j, err := d.getJob(query.ID) 108 if err != nil { 109 return nil, err 110 } 111 return []*model.Job{j}, nil 112 } 113 114 for _, j := range maps.Values(d.jobs) { 115 if query.Limit > 0 && len(result) == query.Limit { 116 break 117 } 118 119 if !query.ReturnAll && query.ClientID != "" && query.ClientID != j.Metadata.ClientID { 120 // Job is not for the requesting client, so ignore it. 121 continue 122 } 123 124 // If we are not using include tags, by default every job is included. 125 // If a job is specifically included, that overrides it being excluded. 126 included := len(query.IncludeTags) == 0 127 for _, tag := range j.Spec.Annotations { 128 if slices.Contains(query.IncludeTags, model.IncludedTag(tag)) { 129 included = true 130 break 131 } 132 if slices.Contains(query.ExcludeTags, model.ExcludedTag(tag)) { 133 included = false 134 break 135 } 136 } 137 138 if !included { 139 continue 140 } 141 142 result = append(result, j) 143 } 144 145 listSorter := func(i, j int) bool { 146 switch query.SortBy { 147 case "id": 148 if query.SortReverse { 149 // what does it mean to sort by ID? 150 return result[i].Metadata.ID > result[j].Metadata.ID 151 } else { 152 return result[i].Metadata.ID < result[j].Metadata.ID 153 } 154 case "created_at": 155 if query.SortReverse { 156 return result[i].Metadata.CreatedAt.UTC().Unix() > result[j].Metadata.CreatedAt.UTC().Unix() 157 } else { 158 return result[i].Metadata.CreatedAt.UTC().Unix() < result[j].Metadata.CreatedAt.UTC().Unix() 159 } 160 default: 161 return false 162 } 163 } 164 sort.Slice(result, listSorter) 165 return result, nil 166 } 167 168 func (d *InMemoryDatastore) GetJobsCount(ctx context.Context, query localdb.JobQuery) (int, error) { 169 useQuery := query 170 useQuery.Limit = 0 171 useQuery.Offset = 0 172 jobs, err := d.GetJobs(ctx, useQuery) 173 if err != nil { 174 return 0, err 175 } 176 return len(jobs), nil 177 } 178 179 func (d *InMemoryDatastore) HasLocalEvent(ctx context.Context, jobID string, eventFilter localdb.LocalEventFilter) (bool, error) { 180 jobLocalEvents, err := d.GetJobLocalEvents(ctx, jobID) 181 if err != nil { 182 return false, err 183 } 184 hasEvent := false 185 for _, localEvent := range jobLocalEvents { 186 if eventFilter(localEvent) { 187 hasEvent = true 188 break 189 } 190 } 191 return hasEvent, nil 192 } 193 194 func (d *InMemoryDatastore) AddJob(ctx context.Context, j *model.Job) error { 195 //nolint:ineffassign,staticcheck 196 ctx, span := system.NewSpan(ctx, system.GetTracer(), "pkg/localdb/inmemory.InMemoryDatastore.AddJob") 197 defer span.End() 198 199 d.mtx.Lock() 200 defer d.mtx.Unlock() 201 existingJob, ok := d.jobs[j.Metadata.ID] 202 if ok { 203 if len(j.Status.Requester.RequesterPublicKey) > 0 { 204 existingJob.Status.Requester.RequesterPublicKey = j.Status.Requester.RequesterPublicKey 205 } 206 return nil 207 } 208 d.jobs[j.Metadata.ID] = j 209 return nil 210 } 211 212 func (d *InMemoryDatastore) AddEvent(ctx context.Context, jobID string, ev model.JobEvent) error { 213 //nolint:ineffassign,staticcheck 214 ctx, span := system.NewSpan(ctx, system.GetTracer(), "pkg/localdb/inmemory.InMemoryDatastore.AddEvent") 215 defer span.End() 216 217 d.mtx.Lock() 218 defer d.mtx.Unlock() 219 _, ok := d.jobs[jobID] 220 if !ok { 221 return bacerrors.NewJobNotFound(jobID) 222 } 223 eventArr, ok := d.events[jobID] 224 if !ok { 225 eventArr = []model.JobEvent{} 226 } 227 eventArr = append(eventArr, ev) 228 d.events[jobID] = eventArr 229 return nil 230 } 231 232 func (d *InMemoryDatastore) AddLocalEvent(ctx context.Context, jobID string, ev model.JobLocalEvent) error { 233 //nolint:ineffassign,staticcheck 234 ctx, span := system.NewSpan(ctx, system.GetTracer(), "pkg/localdb/inmemory.InMemoryDatastore.AddLocalEvent") 235 defer span.End() 236 237 d.mtx.Lock() 238 defer d.mtx.Unlock() 239 _, ok := d.jobs[jobID] 240 if !ok { 241 return bacerrors.NewJobNotFound(jobID) 242 } 243 eventArr, ok := d.localEvents[jobID] 244 if !ok { 245 eventArr = []model.JobLocalEvent{} 246 } 247 eventArr = append(eventArr, ev) 248 d.localEvents[jobID] = eventArr 249 return nil 250 } 251 252 func (d *InMemoryDatastore) UpdateJobDeal(ctx context.Context, jobID string, deal model.Deal) error { 253 _, span := system.NewSpan(ctx, system.GetTracer(), "pkg/localdb/inmemory.InMemoryDatastore.UpdateJobDeal") 254 defer span.End() 255 256 d.mtx.Lock() 257 defer d.mtx.Unlock() 258 job, ok := d.jobs[jobID] 259 if !ok { 260 return bacerrors.NewJobNotFound(jobID) 261 } 262 job.Spec.Deal = deal 263 return nil 264 } 265 266 func (d *InMemoryDatastore) GetJobState(ctx context.Context, jobID string) (model.JobState, error) { 267 _, span := system.NewSpan(ctx, system.GetTracer(), "pkg/localdb/inmemory.InMemoryDatastore.GetJobState") 268 defer span.End() 269 270 d.mtx.RLock() 271 defer d.mtx.RUnlock() 272 _, ok := d.jobs[jobID] 273 if !ok { 274 return model.JobState{}, bacerrors.NewJobNotFound(jobID) 275 } 276 state, ok := d.states[jobID] 277 if !ok { 278 return model.JobState{}, nil 279 } 280 // return a copy so we remain within the mutex of the localdb 281 // in terms of accessing d.states 282 return *state, nil 283 } 284 285 func (d *InMemoryDatastore) UpdateShardState( 286 ctx context.Context, 287 jobID, nodeID string, 288 shardIndex int, 289 update model.JobShardState, 290 ) error { 291 _, span := system.NewSpan(ctx, system.GetTracer(), "pkg/localdb/inmemory.InMemoryDatastore.UpdateShardState") 292 defer span.End() 293 294 d.mtx.Lock() 295 defer d.mtx.Unlock() 296 _, ok := d.jobs[jobID] 297 if !ok { 298 return bacerrors.NewJobNotFound(jobID) 299 } 300 jobState, ok := d.states[jobID] 301 if !ok { 302 jobState = &model.JobState{ 303 Nodes: map[string]model.JobNodeState{}, 304 } 305 } 306 err := shared.UpdateShardState(nodeID, shardIndex, jobState, update) 307 if err != nil { 308 return err 309 } 310 d.states[jobID] = jobState 311 return nil 312 } 313 314 // helper method to read a single job from memory. This is used by both GetJob and GetJobs. 315 // It is important that we don't attempt to acquire a lock inside this method to avoid deadlocks since 316 // the callers are expected to be holding a lock, and golang doesn't support reentrant locks. 317 func (d *InMemoryDatastore) getJob(id string) (*model.Job, error) { 318 if len(id) < model.ShortIDLength { 319 return nil, bacerrors.NewJobNotFound(id) 320 } 321 322 // support for short job IDs 323 if jobutils.ShortID(id) == id { 324 // passed in a short id, need to resolve the long id first 325 for k := range d.jobs { 326 if jobutils.ShortID(k) == id { 327 id = k 328 break 329 } 330 } 331 } 332 333 j, ok := d.jobs[id] 334 if !ok { 335 returnError := bacerrors.NewJobNotFound(id) 336 return nil, returnError 337 } 338 339 return j, nil 340 } 341 342 // Static check to ensure that Transport implements Transport: 343 var _ localdb.LocalDB = (*InMemoryDatastore)(nil)