go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/sync/parallel/buffer.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 parallel 16 17 import ( 18 "container/list" 19 "sync" 20 "sync/atomic" 21 ) 22 23 // A Buffer embeds a Runner, overriding its RunOne method to buffer tasks 24 // indefinitely without blocking. 25 type Buffer struct { 26 Runner 27 28 // lifo, if non-zero-indicates a LIFO task dispatch or, if zero, a FIFO task 29 // dispatch. For more informatio, see SetFIFO. 30 lifo int32 31 32 // initOnce ensures that the Buffer is initialized at most once. 33 initOnce sync.Once 34 // workC receives enqueued tasks for processing. 35 workC chan WorkItem 36 // tasksFinishedC is used to signal Close when our list has finished 37 // dispatching tasks. 38 tasksFinishedC chan struct{} 39 } 40 41 func (b *Buffer) init() { 42 b.initOnce.Do(func() { 43 b.workC = make(chan WorkItem) 44 b.tasksFinishedC = make(chan struct{}) 45 46 go b.process() 47 }) 48 } 49 50 // process enqueues tasks into the Buffer and dispatches them to the underlying 51 // Runner when available. 52 func (b *Buffer) process() { 53 defer close(b.tasksFinishedC) 54 55 // outC is the channel that we send work to. We toggle it between nil and our 56 // Runner's WorkC depending on whether we have work. 57 // 58 // cur is the current work to send. It is only valid if hasWork is true. 59 var outC chan<- WorkItem 60 var cur WorkItem 61 62 // This is our work buffer. If we have unsent work, any additional work will 63 // be written to this buffer. 64 var buf list.List 65 66 // Our main processing loop. 67 inC := b.workC 68 for { 69 select { 70 case work, ok := <-inC: 71 if !ok { 72 // Our work channel has been closed. We aren't accepting any new tasks. 73 if outC == nil && buf.Len() == 0 { 74 // We have no buffered work; exit immediately. 75 return 76 } 77 78 // Mark that we're closed. When all of our work drains, we will exit. 79 inC = nil 80 break 81 } 82 83 // If we have no immediate work, send "work" directly; otherwise, buffer 84 // work for future sending. 85 if outC == nil { 86 cur = work 87 outC = b.Runner.WorkC() 88 } else { 89 buf.PushBack(&work) 90 } 91 92 case outC <- cur: 93 // "cur" has been sent. Dequeue the next work item, or set outC to nil if 94 // there are no more items. 95 switch { 96 case buf.Len() > 0: 97 var e *list.Element 98 if b.isFIFO() { 99 e = buf.Front() 100 } else { 101 e = buf.Back() 102 } 103 104 cur = *(buf.Remove(e).(*WorkItem)) 105 case inC == nil: 106 // There's no more immediate work, no buffered work, and we're closed, 107 // so we're finished. 108 return 109 default: 110 // No more work to send. 111 outC = nil 112 } 113 } 114 } 115 } 116 117 // Run implements the same semantics as Runner's Run. However, if the 118 // dispatch pipeline is full, Run will buffer the work and return immediately 119 // rather than block. 120 func (b *Buffer) Run(gen func(chan<- func() error)) <-chan error { 121 return b.runThen(gen, nil) 122 } 123 124 // Run implements the same semantics as Runner's Run. However, if the 125 // dispatch pipeline is full, Run will buffer the work and return immediately 126 // rather than block. 127 func (b *Buffer) runThen(gen func(chan<- func() error), then func()) <-chan error { 128 b.init() 129 return runImpl(gen, b.workC, then) 130 } 131 132 // RunOne implements the same semantics as Runner's RunOne. However, if the 133 // dispatch pipeline is full, RunOne will buffer the work and return immediately 134 // rather than block. 135 func (b *Buffer) RunOne(f func() error) <-chan error { 136 b.init() 137 138 errC := make(chan error) 139 b.workC <- WorkItem{ 140 F: f, 141 ErrC: errC, 142 After: func() { close(errC) }, 143 } 144 return errC 145 } 146 147 // WorkC implements the same semantics as Runner's WorkC. However, this channel 148 // will not block pending work dispatch. Any tasks written to this channel that 149 // would block are instead buffered pending dispatch availability. 150 func (b *Buffer) WorkC() chan<- WorkItem { 151 b.init() 152 153 return b.workC 154 } 155 156 // Close flushes the remaining tasks in the Buffer and Closes the underlying 157 // Runner. 158 // 159 // Adding new tasks to the Buffer after Close has been invoked will cause a 160 // panic. 161 func (b *Buffer) Close() { 162 b.init() 163 164 close(b.workC) 165 <-b.tasksFinishedC 166 b.Runner.Close() 167 } 168 169 // SetFIFO sets the Buffer's task dispatch order to FIFO (true) or LIFO (false). 170 // This determines the order in which buffered tasks will be dispatched. In 171 // FIFO (first in, first out) mode, the first tasks to be buffered will be 172 // dispatchd first. In LIFO (last in, last out) mode, the last tasks to be 173 // buffered will be dispatched first. 174 func (b *Buffer) SetFIFO(fifo bool) { 175 if fifo { 176 atomic.StoreInt32(&b.lifo, 0) 177 } else { 178 atomic.StoreInt32(&b.lifo, 1) 179 } 180 } 181 182 func (b *Buffer) isFIFO() bool { 183 return atomic.LoadInt32(&b.lifo) == 0 184 }