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 }