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)