go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/sync/dispatcher/buffer/full_behavior.go (about) 1 // Copyright 2019 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 buffer 16 17 import ( 18 "go.chromium.org/luci/common/errors" 19 ) 20 21 // FullBehavior allows you to customize the Buffer's behavior when it gets too 22 // full. 23 // 24 // Generally you'll pick one of DropOldestBatch or BlockNewItems. 25 type FullBehavior interface { 26 // Check inspects Options to see if it's compatible with this FullBehavior 27 // implementation. 28 // 29 // Called exactly once during Buffer creation before any other methods are 30 // used. 31 Check(Options) error 32 33 // ComputeState evaluates the state of the Buffer (via Stats) and returns: 34 // 35 // * okToInsert - User can add an item without blocking. 36 // * dropBatch - Buffer needs to drop the oldest batch if the user does 37 // insert data. 38 // 39 // Called after Check. 40 ComputeState(stats Stats) (okToInsert, dropBatch bool) 41 42 // NeedsItemSize should return true iff this FullBehavior requires item sizes 43 // to effectively apply its policy. 44 // 45 // Called after Check. 46 NeedsItemSize() bool 47 } 48 49 // BlockNewItems prevents the Buffer from accepting any new items as long as it 50 // it has MaxItems worth of items. 51 // 52 // This will never drop batches, but will block inserts. 53 type BlockNewItems struct { 54 // The maximum number of items that this Buffer is allowed to house (including 55 // both leased and unleased items). 56 // 57 // Default: -1 if BatchItemsMax != -1 else max(1000, BatchItemsMax) 58 // Required: Must be >= BatchItemsMax or -1 (only MaxSize applies) 59 MaxItems int 60 61 // The maximum* number of 'size units' that this Buffer is allowed to house 62 // (including both leased and unleased items). 63 // 64 // NOTE(*): This only blocks addition of new items once the buffer is 65 // at-or-past capacitiy. e.g. if the buffer has a MaxSize of 1000 and 66 // a BatchSizeMax of 100, and you insert items worth 999 units, this will 67 // still allow the addition of another item (of up to 100 units) before 68 // claiming the buffer is full. 69 // 70 // NOTE: This may only be set if BatchSizeMax is > 0; Otherwise buffer will 71 // not enforce item sizes, and so BlockNewItems will not be able to enforce 72 // this policy. 73 // 74 // Default: -1 if BatchSizeMax != -1 else BatchSizeMax * 5 75 // Required: Must be >= BatchSizeMax or -1 (only MaxItems applies) 76 MaxSize int 77 } 78 79 var _ FullBehavior = (*BlockNewItems)(nil) 80 81 // ComputeState implements FullBehavior.ComputeState. 82 func (b *BlockNewItems) ComputeState(stats Stats) (okToInsert, dropBatch bool) { 83 itemsOK := b.MaxItems == -1 || stats.Total() < b.MaxItems 84 sizeOK := b.MaxSize == -1 || stats.TotalSize() < b.MaxSize 85 okToInsert = itemsOK && sizeOK 86 return 87 } 88 89 // Check implements FullBehavior.Check. 90 func (b *BlockNewItems) Check(opts Options) (err error) { 91 if b.MaxItems == 0 { 92 b.MaxItems = negOneOrMax(opts.BatchItemsMax, 1000) 93 } 94 if b.MaxSize == 0 { 95 b.MaxSize = negOneOrMult(opts.BatchSizeMax, 5) 96 } 97 98 switch { 99 case b.MaxItems == -1: 100 case b.MaxItems < opts.BatchItemsMax: 101 return errors.Reason("BlockNewItems.MaxItems must be >= BatchItemsMax[%d]: got %d", 102 opts.BatchItemsMax, b.MaxItems).Err() 103 } 104 105 switch { 106 case b.MaxSize == -1: 107 case b.MaxSize < opts.BatchSizeMax: 108 return errors.Reason("BlockNewItems.MaxSize must be >= BatchSizeMax[%d]: got %d", 109 opts.BatchSizeMax, b.MaxSize).Err() 110 case opts.BatchSizeMax == -1: 111 return errors.Reason("BlockNewItems.MaxSize may only be set with BatchSizeMax[%d] > 0", 112 opts.BatchSizeMax).Err() 113 } 114 115 if b.MaxItems == -1 && b.MaxSize == -1 { 116 return errors.Reason("BlockNewItems must have one of MaxItems or MaxSize > 0").Err() 117 } 118 119 return 120 } 121 122 // NeedsItemSize implements FullBehavior.NeedsItemSize. 123 func (b *BlockNewItems) NeedsItemSize() bool { 124 return b.MaxSize > 0 125 } 126 127 // DropOldestBatch will drop buffered data whenever the number of unleased items 128 // plus leased items would grow beyond MaxLiveItems. 129 // 130 // This will never block inserts, but will drop batches. 131 type DropOldestBatch struct { 132 // The maximum number of leased and unleased items that the Buffer may have 133 // before dropping data. 134 // 135 // Once a batch is dropped, it no longer counts against MaxLiveItems, but it 136 // may still be in memory if the dropped batch was currently leased. 137 // 138 // NOTE: The maximum Stats.Total number of items the Buffer could have at 139 // a given time is: 140 // 141 // MaxLiveItems + (BatchItemsMax * MaxLeases) 142 // 143 // Default: -1 if BatchItemsMax == -1 else max(1000, BatchItemsMax) 144 // Required: Must be >= BatchItemsMax or -1 (only MaxLiveSize applies) 145 MaxLiveItems int 146 147 // The maximum number of leased and unleased size units that the Buffer may 148 // have before dropping data. 149 // 150 // Once a batch is dropped, it no longer counts against MaxLiveSize, but it 151 // may still be in memory if the dropped batch was currently leased at the 152 // time it was dropped. 153 // 154 // NOTE: The maximum Stats.TotalSize the Buffer could have at a given 155 // time is: 156 // 157 // MaxLiveSize + (BatchSizeMax * MaxLeases) 158 // 159 // NOTE: This may only be set if BatchSizeMax is > 0; Otherwise buffer will 160 // not size inserted items, and so DropOldestBatch will not be able to enforce 161 // this policy. 162 // 163 // Default: -1 if BatchSizeMax == -1 else BatchSizeMax * 5 164 // Required: Must be >= BatchSizeMax or -1 (only MaxLiveItems applies) 165 MaxLiveSize int 166 } 167 168 var _ FullBehavior = (*DropOldestBatch)(nil) 169 170 // ComputeState implements FullBehavior.ComputeState. 171 func (d *DropOldestBatch) ComputeState(stats Stats) (okToInsert, dropBatch bool) { 172 okToInsert = true 173 itemsDrop := d.MaxLiveItems > -1 && stats.Total() >= d.MaxLiveItems 174 sizeDrop := d.MaxLiveSize > -1 && stats.TotalSize() >= d.MaxLiveSize 175 dropBatch = itemsDrop || sizeDrop 176 return 177 } 178 179 // Check implements FullBehavior.Check. 180 func (d *DropOldestBatch) Check(opts Options) (err error) { 181 if d.MaxLiveItems == 0 { 182 d.MaxLiveItems = negOneOrMax(opts.BatchItemsMax, 1000) 183 } 184 if d.MaxLiveSize == 0 { 185 d.MaxLiveSize = negOneOrMult(opts.BatchSizeMax, 5) 186 } 187 188 switch { 189 case d.MaxLiveItems == -1: 190 case d.MaxLiveItems < opts.BatchItemsMax: 191 return errors.Reason("DropOldestBatch.MaxLiveItems must be >= BatchItemsMax[%d]: got %d", 192 opts.BatchItemsMax, d.MaxLiveItems).Err() 193 } 194 195 switch { 196 case d.MaxLiveSize == -1: 197 case d.MaxLiveSize < opts.BatchSizeMax: 198 return errors.Reason("DropOldestBatch.MaxLiveSize must be >= BatchSizeMax[%d]: got %d", 199 opts.BatchSizeMax, d.MaxLiveSize).Err() 200 case opts.BatchSizeMax == -1: 201 return errors.Reason("DropOldestBatch.MaxLiveSize may only be set with BatchSizeMax[%d] > 0", 202 opts.BatchSizeMax).Err() 203 } 204 205 if d.MaxLiveItems == -1 && d.MaxLiveSize == -1 { 206 return errors.Reason("DropOldestBatch must have one of MaxLiveItems or MaxLiveSize > 0").Err() 207 } 208 209 return 210 } 211 212 // NeedsItemSize implements FullBehavior.NeedsItemSize. 213 func (d *DropOldestBatch) NeedsItemSize() bool { 214 return d.MaxLiveSize > 0 215 } 216 217 // InfiniteGrowth will not drop data or block new items. It just grows until 218 // your computer runs out of memory. 219 // 220 // This will never block inserts, and will not drop batches. 221 type InfiniteGrowth struct{} 222 223 var _ FullBehavior = InfiniteGrowth{} 224 225 // ComputeState implements FullBehavior.ComputeState. 226 func (i InfiniteGrowth) ComputeState(Stats) (okToInsert, dropBatch bool) { return true, false } 227 228 // Check implements FullBehavior.Check. 229 func (i InfiniteGrowth) Check(opts Options) (err error) { return nil } 230 231 // NeedsItemSize implements FullBehavior.NeedsItemSize. 232 func (i InfiniteGrowth) NeedsItemSize() bool { return false } 233 234 func negOneOrMult(a, b int) int { 235 if a == -1 { 236 return -1 237 } 238 return a * b 239 } 240 241 func negOneOrMax(a, b int) int { 242 if a == -1 { 243 return -1 244 } 245 return intMax(a, b) 246 }