go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/service/taskqueue/interface.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 taskqueue
    16  
    17  import (
    18  	"context"
    19  	"time"
    20  
    21  	"go.chromium.org/luci/common/errors"
    22  	"go.chromium.org/luci/common/sync/parallel"
    23  )
    24  
    25  // Add adds the specified task(s) to the specified task queue.
    26  //
    27  // If only one task is provided its error will be returned directly. If more
    28  // than one task is provided, an errors.MultiError will be returned in the
    29  // event of an error, with a given error index corresponding to the error
    30  // encountered when processing the task at that index.
    31  //
    32  // If the number of tasks is beyond the limits of the underlying implementation,
    33  // splits the batch into multiple ones.
    34  func Add(c context.Context, queueName string, tasks ...*Task) error {
    35  	return addRaw(Raw(c), queueName, tasks)
    36  }
    37  
    38  func makeBatches(tasks []*Task, limit int) [][]*Task {
    39  	if limit <= 0 {
    40  		return [][]*Task{tasks}
    41  	}
    42  	batches := make([][]*Task, 0, len(tasks)/limit+1)
    43  	for len(tasks) > 0 {
    44  		batch := tasks
    45  		if len(batch) > limit {
    46  			batch = batch[:limit]
    47  		}
    48  		batches = append(batches, batch)
    49  		tasks = tasks[len(batch):]
    50  	}
    51  	return batches
    52  }
    53  
    54  func addRaw(raw RawInterface, queueName string, tasks []*Task) error {
    55  	lme := errors.NewLazyMultiError(len(tasks))
    56  	err := parallel.FanOutIn(func(work chan<- func() error) {
    57  		offset := 0
    58  		for _, batch := range makeBatches(tasks, raw.Constraints().MaxAddSize) {
    59  			batch := batch
    60  			i := offset
    61  			offset += len(batch)
    62  			work <- func() error {
    63  				return raw.AddMulti(batch, queueName, func(t *Task, err error) {
    64  					if !lme.Assign(i, err) {
    65  						*tasks[i] = *t
    66  					}
    67  					i++
    68  				})
    69  			}
    70  		}
    71  	})
    72  	if err != nil {
    73  		return err
    74  	}
    75  	err = lme.Get()
    76  	if len(tasks) == 1 {
    77  		err = errors.SingleError(err)
    78  	}
    79  	return err
    80  }
    81  
    82  // Delete deletes a task from the task queue.
    83  //
    84  // If only one task is provided its error will be returned directly. If more
    85  // than one task is provided, an errors.MultiError will be returned in the
    86  // event of an error, with a given error index corresponding to the error
    87  // encountered when processing the task at that index.
    88  //
    89  // If the number of tasks is beyond the limits of the underlying implementation,
    90  // splits the batch into multiple ones.
    91  func Delete(c context.Context, queueName string, tasks ...*Task) error {
    92  	raw := Raw(c)
    93  	lme := errors.NewLazyMultiError(len(tasks))
    94  	err := parallel.FanOutIn(func(work chan<- func() error) {
    95  		offset := 0
    96  		for _, batch := range makeBatches(tasks, raw.Constraints().MaxDeleteSize) {
    97  			batch := batch
    98  			localOffset := offset
    99  			offset += len(batch)
   100  			work <- func() error {
   101  				return raw.DeleteMulti(batch, queueName, func(i int, err error) {
   102  					lme.Assign(localOffset+i, err)
   103  				})
   104  			}
   105  		}
   106  	})
   107  	if err != nil {
   108  		return err
   109  	}
   110  	err = lme.Get()
   111  	if len(tasks) == 1 {
   112  		err = errors.SingleError(err)
   113  	}
   114  	return err
   115  }
   116  
   117  // NOTE(riannucci): Pull task queues API can be extended to support automatic
   118  // lease management.
   119  //
   120  // The theory is that a good lease API might look like:
   121  //
   122  //   func Lease(queueName, tag string, batchSize int, duration time.Time, cb func(*Task, error<-))
   123  //
   124  // Which blocks and calls cb for each task obtained. Lease would then do all
   125  // necessary backoff negotiation with the backend. The callback could execute
   126  // synchronously (stuffing an error into the chan or panicing if it fails), or
   127  // asynchronously (dispatching a goroutine which will then populate the error
   128  // channel if needed). If it operates asynchronously, it has the option of
   129  // processing multiple work items at a time.
   130  //
   131  // Lease would also take care of calling ModifyLease as necessary to ensure
   132  // that each call to cb would have 'duration' amount of time to work on the
   133  // task, as well as releasing as many leased tasks as it can on a failure.
   134  
   135  // Lease leases tasks from a queue.
   136  //
   137  // leaseTime has seconds precision. The number of tasks fetched will be at most
   138  // maxTasks.
   139  func Lease(c context.Context, maxTasks int, queueName string, leaseTime time.Duration) ([]*Task, error) {
   140  	return Raw(c).Lease(maxTasks, queueName, leaseTime)
   141  }
   142  
   143  // LeaseByTag leases tasks from a queue, grouped by tag.
   144  //
   145  // If tag is empty, then the returned tasks are grouped by the tag of the task
   146  // with earliest ETA.
   147  //
   148  // leaseTime has seconds precision. The number of tasks fetched will be at most
   149  // maxTasks.
   150  func LeaseByTag(c context.Context, maxTasks int, queueName string, leaseTime time.Duration, tag string) ([]*Task, error) {
   151  	return Raw(c).LeaseByTag(maxTasks, queueName, leaseTime, tag)
   152  }
   153  
   154  // ModifyLease modifies the lease of a task.
   155  //
   156  // Used to request more processing time, or to abandon processing. leaseTime has
   157  // seconds precision and must not be negative.
   158  //
   159  // On success, modifies task's ETA field in-place with updated lease expiration
   160  // time.
   161  func ModifyLease(c context.Context, task *Task, queueName string, leaseTime time.Duration) error {
   162  	return Raw(c).ModifyLease(task, queueName, leaseTime)
   163  }
   164  
   165  // Purge purges all tasks form the named queue.
   166  func Purge(c context.Context, queueName string) error {
   167  	return Raw(c).Purge(queueName)
   168  }
   169  
   170  // Stats returns Statistics instances for each of the named task queues.
   171  //
   172  // If only one task is provided its error will be returned directly. If more
   173  // than one task is provided, an errors.MultiError will be returned in the
   174  // event of an error, with a given error index corresponding to the error
   175  // encountered when processing the task at that index.
   176  func Stats(c context.Context, queueNames ...string) ([]Statistics, error) {
   177  	ret := make([]Statistics, len(queueNames))
   178  	lme := errors.NewLazyMultiError(len(queueNames))
   179  	i := 0
   180  	err := Raw(c).Stats(queueNames, func(s *Statistics, err error) {
   181  		if !lme.Assign(i, err) {
   182  			ret[i] = *s
   183  		}
   184  		i++
   185  	})
   186  	if err == nil {
   187  		err = lme.Get()
   188  		if len(queueNames) == 1 {
   189  			err = errors.SingleError(err)
   190  		}
   191  	}
   192  	return ret, err
   193  }
   194  
   195  // GetTestable returns a Testable for the current task queue service in c, or
   196  // nil if it does not offer one.
   197  func GetTestable(c context.Context) Testable {
   198  	return Raw(c).GetTestable()
   199  }