github.com/qiniu/go-sdk/v7@v7.27.0/storagev2/objects/batch.go (about) 1 package objects 2 3 import ( 4 "context" 5 "encoding/hex" 6 "errors" 7 "fmt" 8 "net" 9 "net/url" 10 "os" 11 "sort" 12 "sync" 13 "time" 14 15 clientv1 "github.com/qiniu/go-sdk/v7/client" 16 internal_context "github.com/qiniu/go-sdk/v7/internal/context" 17 "github.com/qiniu/go-sdk/v7/internal/pkg/toposort" 18 "github.com/qiniu/go-sdk/v7/storagev2/apis" 19 "github.com/qiniu/go-sdk/v7/storagev2/apis/batch_ops" 20 "github.com/qiniu/go-sdk/v7/storagev2/apis/stat_object" 21 "github.com/qiniu/go-sdk/v7/storagev2/retrier" 22 ) 23 24 type ( 25 // 批处理执行器 26 BatchOpsExecutor interface { 27 ExecuteBatchOps(context.Context, []Operation, *apis.Storage) error 28 } 29 30 // 串行批处理执行器选项 31 SerialBatchOpsExecutorOptions struct { 32 RetryMax uint // 最大重试次数,默认为 10 33 BatchSize uint // 批次大小,默认为 1000 34 } 35 36 serialBatchOpsExecutor struct { 37 options *SerialBatchOpsExecutorOptions 38 } 39 40 // 并行批处理执行器选项 41 ConcurrentBatchOpsExecutorOptions struct { 42 RetryMax uint // 最大重试次数,默认为 10 43 InitBatchSize uint // 初始批次大小,默认为 250 44 MaxBatchSize uint // 最大批次大小,默认为 250 45 MinBatchSize uint // 最小批次大小,默认为 50 46 DoublingFactor uint // 批次大小翻倍系数,默认为 2 47 DoublingInterval time.Duration // 翻倍时间间隔,默认为 1 分钟 48 InitWorkers uint // 初始化并发数,默认为 20 49 MaxWorkers uint // 最大并发数,默认为 20 50 MinWorkers uint // 最小并发数,默认为 1 51 AddWorkerInterval time.Duration // 增加并发数时间间隔,默认为 1 分钟 52 } 53 54 concurrentBatchOpsExecutor struct { 55 options *ConcurrentBatchOpsExecutorOptions 56 } 57 58 operation struct { 59 Operation 60 tries uint 61 } 62 63 requestsManager struct { 64 storage *apis.Storage 65 lock sync.Mutex 66 operations [][]*operation 67 batchSize, minBatchSize, maxBatchSize, doublingFactor, maxTries uint 68 doublingInterval time.Duration 69 ticker *time.Ticker 70 resetTicker chan struct{} 71 cancelTicker internal_context.CancelCauseFunc 72 lastDecreaseBatchSizeTime time.Time 73 lastDecreaseBatchSizeTimeMutex sync.Mutex 74 waitGroup sync.WaitGroup 75 } 76 77 workersManager struct { 78 lock sync.Mutex 79 parentCtx internal_context.Context 80 parentCancelFunc internal_context.CancelCauseFunc 81 cancels []internal_context.CancelCauseFunc 82 requestsManager *requestsManager 83 maxWorkers, minWorkers uint 84 addWorkerInterval time.Duration 85 ticker *time.Ticker 86 resetTicker chan struct{} 87 cancelTickerFunc internal_context.CancelCauseFunc 88 lastResetTickerTime time.Time 89 lastResetTickerTimeMutex sync.Mutex 90 timerWaitGroup, asyncWorkersWaitGroup sync.WaitGroup 91 } 92 ) 93 94 // 创建串型批处理执行器 95 func NewSerialBatchOpsExecutor(options *SerialBatchOpsExecutorOptions) BatchOpsExecutor { 96 if options == nil { 97 options = &SerialBatchOpsExecutorOptions{} 98 } 99 return &serialBatchOpsExecutor{options} 100 } 101 102 func (executor *serialBatchOpsExecutor) ExecuteBatchOps(ctx context.Context, operations []Operation, storage *apis.Storage) error { 103 ops := make([]*operation, len(operations)) 104 for i, op := range operations { 105 ops[i] = &operation{Operation: op} 106 } 107 _, err := doOperations(ctx, ops, storage, executor.options.BatchSize, executor.options.RetryMax) 108 return err 109 } 110 111 // 创建并行批处理执行器 112 func NewConcurrentBatchOpsExecutor(options *ConcurrentBatchOpsExecutorOptions) BatchOpsExecutor { 113 if options == nil { 114 options = &ConcurrentBatchOpsExecutorOptions{} 115 } 116 return &concurrentBatchOpsExecutor{options} 117 } 118 119 func (executor *concurrentBatchOpsExecutor) ExecuteBatchOps(ctx context.Context, operations []Operation, storage *apis.Storage) error { 120 rm, err := newRequestsManager( 121 storage, 122 executor.options.InitBatchSize, 123 executor.options.MinBatchSize, 124 executor.options.MaxBatchSize, 125 executor.options.DoublingFactor, 126 executor.options.RetryMax, 127 executor.options.DoublingInterval, 128 operations, 129 ) 130 if err != nil { 131 return err 132 } 133 defer rm.done() 134 wm := newWorkersManager( 135 ctx, 136 executor.options.InitWorkers, 137 executor.options.MinWorkers, 138 executor.options.MaxWorkers, 139 executor.options.AddWorkerInterval, 140 rm, 141 ) 142 return wm.wait() 143 } 144 145 func newRequestsManager(storage *apis.Storage, initBatchSize, minBatchSize, maxBatchSize, doublingFactor, maxTries uint, doublingInterval time.Duration, operations []Operation) (*requestsManager, error) { 146 if initBatchSize == 0 { 147 initBatchSize = 250 148 } 149 if minBatchSize == 0 { 150 minBatchSize = 50 151 } 152 if maxBatchSize == 0 { 153 maxBatchSize = 250 154 } 155 if maxBatchSize < minBatchSize { 156 maxBatchSize = minBatchSize 157 } 158 if initBatchSize < minBatchSize { 159 initBatchSize = minBatchSize 160 } 161 if initBatchSize > maxBatchSize { 162 initBatchSize = maxBatchSize 163 } 164 if doublingFactor < 2 { 165 doublingFactor = 2 166 } 167 if doublingInterval == 0 { 168 doublingInterval = 1 * time.Minute 169 } 170 if maxTries == 0 { 171 maxTries = 10 172 } 173 174 sortedOperations, err := topoSort(operations) 175 if err != nil { 176 return nil, err 177 } 178 179 ctx, cancelFunc := internal_context.WithCancelCause(internal_context.Background()) 180 rm := requestsManager{ 181 storage: storage, 182 operations: wrapOperations(filterOperations(sortedOperations)), 183 batchSize: initBatchSize, 184 minBatchSize: minBatchSize, 185 maxBatchSize: maxBatchSize, 186 doublingFactor: doublingFactor, 187 doublingInterval: doublingInterval, 188 ticker: time.NewTicker(doublingInterval), 189 resetTicker: make(chan struct{}, 1024), 190 cancelTicker: cancelFunc, 191 } 192 sortOperations(rm.operations) 193 194 rm.waitGroup.Add(1) 195 go rm.asyncLoop(ctx) 196 return &rm, nil 197 } 198 199 func (rm *requestsManager) asyncLoop(ctx internal_context.Context) { 200 defer rm.waitGroup.Done() 201 202 for { 203 select { 204 case <-rm.resetTicker: 205 // do nothing 206 case <-rm.ticker.C: 207 rm.increaseBatchSize() 208 case <-ctx.Done(): 209 return 210 } 211 } 212 } 213 214 func (rm *requestsManager) done() { 215 rm.cancelTicker(nil) 216 rm.ticker.Stop() 217 rm.waitGroup.Wait() 218 } 219 220 func (rm *requestsManager) takeOperations() []*operation { 221 rm.lock.Lock() 222 defer rm.lock.Unlock() 223 224 needed := int(rm.batchSize) 225 got := make([]*operation, 0, needed) 226 for needed > 0 && len(rm.operations) > 0 { 227 foundOperations, restOperations := findBestMatches(needed, rm.operations) 228 if len(foundOperations) == 0 { 229 break 230 } 231 rm.operations = restOperations 232 got = append(got, foundOperations...) 233 needed -= len(foundOperations) 234 } 235 if len(got) == 0 && needed > 0 && len(rm.operations) > 0 { // 一个都没获得,但依然有剩余的操作还没能获取,说明锁有剩余的操作组的大小都大于 batchSize 236 got = rm.operations[0] 237 rm.operations = rm.operations[1:] 238 } 239 return got 240 } 241 242 func (rm *requestsManager) putBackOperations(operations []*operation) { 243 if len(operations) == 0 { 244 return 245 } 246 247 rm.lock.Lock() 248 defer rm.lock.Unlock() 249 250 rm.operations = append(rm.operations, operations) 251 sortOperations(rm.operations) 252 } 253 254 func (rm *requestsManager) isOperationsEmpty() bool { 255 rm.lock.Lock() 256 defer rm.lock.Unlock() 257 258 return len(rm.operations) == 0 259 } 260 261 func (rm *requestsManager) handleTimeoutError() { 262 rm.lastDecreaseBatchSizeTimeMutex.Lock() 263 defer rm.lastDecreaseBatchSizeTimeMutex.Unlock() 264 265 canDecrease := time.Since(rm.lastDecreaseBatchSizeTime) > time.Second 266 if canDecrease { 267 rm.decreaseBatchSize() 268 rm.lastDecreaseBatchSizeTime = time.Now() 269 } 270 } 271 272 func (rm *requestsManager) decreaseBatchSize() { 273 rm.lock.Lock() 274 defer rm.lock.Unlock() 275 276 batchSize := rm.batchSize / rm.doublingFactor 277 if batchSize < rm.minBatchSize { 278 batchSize = rm.minBatchSize 279 } 280 rm.batchSize = batchSize 281 rm.ticker.Stop() 282 rm.ticker = time.NewTicker(rm.doublingInterval) 283 rm.resetTicker <- struct{}{} 284 } 285 286 func (rm *requestsManager) increaseBatchSize() { 287 rm.lock.Lock() 288 defer rm.lock.Unlock() 289 290 batchSize := rm.batchSize * rm.doublingFactor 291 if batchSize > rm.maxBatchSize { 292 batchSize = rm.maxBatchSize 293 } 294 rm.batchSize = batchSize 295 } 296 297 func newWorkersManager(ctx internal_context.Context, initWorkers, minWorkers, maxWorkers uint, addWorkerInterval time.Duration, requestsManager *requestsManager) *workersManager { 298 if initWorkers == 0 { 299 initWorkers = 20 300 } 301 if minWorkers == 0 { 302 minWorkers = 1 303 } 304 if maxWorkers == 0 { 305 maxWorkers = 20 306 } 307 if maxWorkers < minWorkers { 308 maxWorkers = minWorkers 309 } 310 if initWorkers < minWorkers { 311 initWorkers = minWorkers 312 } 313 if initWorkers > maxWorkers { 314 initWorkers = maxWorkers 315 } 316 if addWorkerInterval == 0 { 317 addWorkerInterval = 1 * time.Minute 318 } 319 wm := new(workersManager) 320 wm.parentCtx, wm.parentCancelFunc = internal_context.WithCancelCause(ctx) 321 wm.requestsManager = requestsManager 322 wm.cancels = make([]internal_context.CancelCauseFunc, initWorkers) 323 wm.maxWorkers = maxWorkers 324 wm.minWorkers = minWorkers 325 wm.addWorkerInterval = addWorkerInterval 326 wm.ticker = time.NewTicker(addWorkerInterval) 327 wm.resetTicker = make(chan struct{}, 1024) 328 329 var timerTickerCtx internal_context.Context 330 timerTickerCtx, wm.cancelTickerFunc = internal_context.WithCancelCause(wm.parentCtx) 331 332 wm.timerWaitGroup.Add(1) 333 go wm.asyncAddWorkersLoop(timerTickerCtx) 334 335 for i := uint(0); i < initWorkers; i++ { 336 workerCtx, workerCancelFunc := internal_context.WithCancelCause(wm.parentCtx) 337 wm.cancels[i] = workerCancelFunc 338 wm.asyncWorkersWaitGroup.Add(1) 339 go wm.asyncWorker(workerCtx, i) 340 } 341 return wm 342 } 343 344 func (wm *workersManager) asyncAddWorkersLoop(ctx internal_context.Context) { 345 defer wm.timerWaitGroup.Done() 346 347 for { 348 select { 349 case <-wm.resetTicker: 350 // do nothing 351 case _, ok := <-wm.ticker.C: 352 if !ok { 353 return 354 } 355 if wm.getWorkersCount() < wm.maxWorkers { 356 wm.spawnWorker() 357 } 358 case <-ctx.Done(): 359 return 360 } 361 } 362 } 363 364 func (wm *workersManager) wait() error { 365 wm.asyncWorkersWaitGroup.Wait() 366 wm.ticker.Stop() 367 wm.cancelTickerFunc(nil) 368 wm.timerWaitGroup.Wait() 369 if wm.requestsManager.isOperationsEmpty() { 370 return wm.parentCtx.Err() 371 } 372 return wm.doOperationsSync() 373 } 374 375 func (wm *workersManager) doOperationsSync() error { 376 for { 377 if err := getCtxError(wm.parentCtx); err != nil { 378 return err 379 } 380 if operations := wm.requestsManager.takeOperations(); len(operations) > 0 { 381 if operations, err := wm.doOperations(wm.parentCtx, operations); err != nil { 382 wm.requestsManager.putBackOperations(operations) 383 if isTimeoutError(err) { 384 wm.requestsManager.handleTimeoutError() 385 } else { 386 wm.setError(err) 387 return err 388 } 389 } 390 } else { 391 return nil 392 } 393 } 394 } 395 396 func (wm *workersManager) asyncWorker(ctx internal_context.Context, id uint) { 397 defer wm.asyncWorkersWaitGroup.Done() 398 for getCtxError(wm.parentCtx) == nil { 399 if operations := wm.requestsManager.takeOperations(); len(operations) > 0 { 400 if operations, err := wm.doOperations(ctx, operations); err != nil { 401 // 确定错误是否可以重试 402 wm.requestsManager.putBackOperations(operations) 403 if isTimeoutError(err) { // 超时,说明 batchSize 过大 404 wm.requestsManager.handleTimeoutError() 405 } else if isOutOfQuotaError(err) { 406 // 并发度过高,自杀减少并发度 407 if wm.handleOutOfQuotaError(id, err) { 408 return 409 } 410 } else { 411 wm.setError(err) 412 return 413 } 414 } 415 } else { 416 break 417 } 418 } 419 } 420 421 func (wm *workersManager) handleOutOfQuotaError(id uint, err error) bool { 422 wm.lastResetTickerTimeMutex.Lock() 423 defer wm.lastResetTickerTimeMutex.Unlock() 424 425 canReset := time.Since(wm.lastResetTickerTime) > time.Second 426 if canReset { // 这里禁止并行杀死 worker,防止杀死速度过快 427 wm.ticker.Stop() 428 wm.ticker = time.NewTicker(wm.addWorkerInterval) 429 wm.resetTicker <- struct{}{} 430 wm.killWorker(id, err) 431 wm.lastResetTickerTime = time.Now() 432 } 433 return canReset 434 } 435 436 func (wm *workersManager) doOperations(ctx internal_context.Context, operations []*operation) ([]*operation, error) { 437 return doOperations(ctx, operations, wm.requestsManager.storage, wm.requestsManager.batchSize, wm.requestsManager.maxTries) 438 } 439 440 func (wm *workersManager) setError(err error) { 441 wm.parentCancelFunc(err) 442 } 443 444 func (wm *workersManager) getWorkersCount() (count uint) { 445 wm.lock.Lock() 446 defer wm.lock.Unlock() 447 448 for _, c := range wm.cancels { 449 if c != nil { 450 count += 1 451 } 452 } 453 return 454 } 455 456 func (wm *workersManager) killWorker(id uint, err error) { 457 wm.lock.Lock() 458 defer wm.lock.Unlock() 459 460 cancelFunc := wm.cancels[id] 461 cancelFunc(err) 462 wm.cancels[id] = nil 463 } 464 465 func (wm *workersManager) spawnWorker() { 466 wm.lock.Lock() 467 defer wm.lock.Unlock() 468 469 workerCtx, workerCancelFunc := internal_context.WithCancelCause(wm.parentCtx) 470 for id := range wm.cancels { 471 if wm.cancels[id] == nil { 472 wm.cancels[id] = workerCancelFunc 473 go wm.asyncWorker(workerCtx, uint(id)) 474 return 475 } 476 } 477 wm.cancels = append(wm.cancels, workerCancelFunc) 478 go wm.asyncWorker(workerCtx, uint(len(wm.cancels)-1)) 479 } 480 481 func doOperations(ctx internal_context.Context, operations []*operation, storage *apis.Storage, batchSize, maxTries uint) ([]*operation, error) { 482 if batchSize == 0 { 483 batchSize = 1000 484 } 485 if maxTries == 0 { 486 maxTries = 10 487 } 488 for len(operations) > 0 { 489 thisBatchSize := batchSize 490 if thisBatchSize > uint(len(operations)) { 491 thisBatchSize = uint(len(operations)) 492 } 493 toDoThisLoop := operations[:thisBatchSize] 494 willDoNextLoop := make([]*operation, 0, thisBatchSize) 495 bucketName := toDoThisLoop[0].relatedEntries()[0].bucketName 496 497 operationsStrings := make([]string, len(toDoThisLoop)) 498 for i, operation := range toDoThisLoop { 499 operationsStrings[i] = operation.String() 500 } 501 502 response, err := storage.BatchOps(ctx, &apis.BatchOpsRequest{ 503 Operations: operationsStrings, 504 }, &apis.Options{ 505 OverwrittenBucketName: bucketName, 506 }) 507 if err != nil { 508 return operations, err 509 } 510 for i, operationResponse := range response.OperationResponses { 511 operation := toDoThisLoop[i] 512 if operationResponse.Code == 200 { 513 var object ObjectDetails 514 if err = object.fromOperationResponseData(operation.relatedEntries()[0].objectName, &operationResponse.Data); err != nil { 515 operation.handleResponse(nil, err) 516 continue 517 } 518 operation.handleResponse(&object, nil) 519 } else { 520 operation.handleResponse(nil, errors.New(operationResponse.Data.Error)) 521 operation.tries += 1 522 if retrier.IsStatusCodeRetryable(int(operationResponse.Code)) && operation.tries < maxTries { 523 willDoNextLoop = append(willDoNextLoop, operation) 524 } 525 } 526 } 527 if thisBatchSize >= batchSize { 528 willDoNextLoop = append(willDoNextLoop, operations[thisBatchSize:]...) 529 } 530 operations = willDoNextLoop 531 } 532 return nil, nil 533 } 534 535 func (object *ObjectDetails) fromOperationResponseData(key string, data *batch_ops.OperationResponseData) error { 536 var ( 537 md5 []byte 538 err error 539 ) 540 object.Name = key 541 object.UploadedAt = time.Unix(data.PutTime/1e7, (data.PutTime%1e7)*1e2) 542 object.ETag = data.Hash 543 object.Size = data.Size 544 object.MimeType = data.MimeType 545 object.StorageClass = StorageClass(data.Type) 546 object.EndUser = data.EndUser 547 object.Status = Status(data.Status) 548 object.RestoreStatus = RestoreStatus(data.RestoringStatus) 549 object.Metadata = data.Metadata 550 if data.Md5 != "" { 551 md5, err = hex.DecodeString(data.Md5) 552 if err != nil { 553 return err 554 } 555 } 556 if len(md5) > 0 { 557 copy(object.MD5[:], md5) 558 } 559 if len(data.Parts) > 0 { 560 object.Parts = append(make(stat_object.PartSizes, 0, len(data.Parts)), data.Parts...) 561 } 562 if data.TransitionToIaTime > 0 { 563 transitionToIA := time.Unix(data.TransitionToIaTime, 0) 564 object.TransitionToIA = &transitionToIA 565 } 566 if data.TransitionToArchiveIrTime > 0 { 567 transitionToArchiveIR := time.Unix(data.TransitionToArchiveIrTime, 0) 568 object.TransitionToArchiveIR = &transitionToArchiveIR 569 } 570 if data.TransitionToArchiveTime > 0 { 571 transitionToArchive := time.Unix(data.TransitionToArchiveTime, 0) 572 object.TransitionToArchive = &transitionToArchive 573 } 574 if data.TransitionToDeepArchiveTime > 0 { 575 transitionToDeepArchive := time.Unix(data.TransitionToDeepArchiveTime, 0) 576 object.TransitionToDeepArchive = &transitionToDeepArchive 577 } 578 if data.ExpirationTime > 0 { 579 expireAt := time.Unix(data.ExpirationTime, 0) 580 object.ExpireAt = &expireAt 581 } 582 return nil 583 } 584 585 type groups struct { 586 keyGroups []map[string]struct{} 587 keyIndexes map[string]int 588 emptyGroupIndexes []int 589 } 590 591 func findKeyFromGroups(g *groups, key string) (int, bool) { 592 idx, ok := g.keyIndexes[key] 593 return idx, ok 594 } 595 596 func addKeyToGroup(g *groups, key string) { 597 if _, ok := findKeyFromGroups(g, key); ok { 598 return 599 } 600 appendKeyToGroup(g, key) 601 } 602 603 func appendKeyToGroup(g *groups, keys ...string) { 604 foundIndex := -1 605 if len(g.emptyGroupIndexes) > 0 { 606 lastIdx := len(g.emptyGroupIndexes) - 1 607 foundIndex = g.emptyGroupIndexes[lastIdx] 608 g.emptyGroupIndexes = g.emptyGroupIndexes[:lastIdx] 609 } 610 newKeyGroup := make(map[string]struct{}, len(keys)) 611 for _, key := range keys { 612 newKeyGroup[key] = struct{}{} 613 } 614 615 if foundIndex < 0 { 616 foundIndex = len(g.keyGroups) 617 g.keyGroups = append(g.keyGroups, newKeyGroup) 618 } else { 619 g.keyGroups[foundIndex] = newKeyGroup 620 } 621 for _, key := range keys { 622 g.keyIndexes[key] = foundIndex 623 } 624 } 625 626 func connectGroup(g *groups, key1, key2 string) { 627 k1, ok := findKeyFromGroups(g, key1) 628 if !ok { 629 k1 = -1 630 } 631 k2, ok := findKeyFromGroups(g, key2) 632 if !ok { 633 k2 = -1 634 } 635 if k1 == k2 { 636 if k1 < 0 { 637 appendKeyToGroup(g, key1, key2) 638 } 639 } else if k1 < 0 { 640 g.keyGroups[k2][key1] = struct{}{} 641 g.keyIndexes[key1] = k2 642 } else if k2 < 0 { 643 g.keyGroups[k1][key2] = struct{}{} 644 g.keyIndexes[key2] = k1 645 } else { 646 for k := range g.keyGroups[k2] { 647 g.keyGroups[k1][k] = struct{}{} 648 g.keyIndexes[k] = k1 649 } 650 g.keyGroups[k2] = nil 651 g.emptyGroupIndexes = append(g.emptyGroupIndexes, k2) 652 } 653 } 654 655 func topoSort(operations []Operation) ([][]Operation, error) { 656 var ( 657 edges = make([]toposort.Edge, 0, len(operations)) 658 rootNodesMap = make(map[string]int, len(operations)*2) 659 g = groups{keyIndexes: make(map[string]int)} 660 ) 661 662 for operationId, operation := range operations { 663 if operation == nil { 664 continue 665 } 666 edges = append(edges, toposort.Edge{nil, operationId}) 667 668 var firstKey string 669 for _, relatedEntry := range operation.relatedEntries() { 670 key := relatedEntry.String() 671 if oldRootOperationId, ok := rootNodesMap[key]; ok { 672 edges = append(edges, toposort.Edge{oldRootOperationId, operationId}) 673 } 674 rootNodesMap[key] = operationId 675 676 addKeyToGroup(&g, key) 677 if firstKey == "" { 678 firstKey = key 679 } else { 680 connectGroup(&g, firstKey, key) 681 } 682 } 683 } 684 685 sortedOperationIds, err := toposort.Toposort(edges) 686 if err != nil { 687 return nil, err 688 } 689 690 groupedOperations := make([][]Operation, len(g.keyGroups)) 691 for _, sortedOperationId := range sortedOperationIds { 692 operation := operations[sortedOperationId.(int)] 693 operationKey := operation.relatedEntries()[0].String() 694 index, ok := findKeyFromGroups(&g, operationKey) 695 if !ok { 696 panic(fmt.Sprintf("failed to find key `%s`, which is unexpected", operationKey)) 697 } 698 groupedOperations[index] = append(groupedOperations[index], operation) 699 } 700 return groupedOperations, nil 701 } 702 703 func sortOperations(operations [][]*operation) { 704 sort.Slice(operations, func(i, j int) bool { 705 return len(operations[i]) < len(operations[j]) 706 }) 707 } 708 709 func filterOperations(operationsGroups [][]Operation) [][]Operation { 710 results := make([][]Operation, 0, len(operationsGroups)) 711 for _, operationsGroup := range operationsGroups { 712 if len(operationsGroup) == 0 { 713 continue 714 } 715 results = append(results, operationsGroup) 716 } 717 return results 718 } 719 720 func wrapOperations(operationsGroups [][]Operation) [][]*operation { 721 results := make([][]*operation, len(operationsGroups)) 722 for groupId, operationsGroup := range operationsGroups { 723 groupResults := make([]*operation, len(operationsGroup)) 724 for opId, op := range operationsGroup { 725 groupResults[opId] = &operation{Operation: op} 726 } 727 results[groupId] = groupResults 728 } 729 return results 730 } 731 732 func findBestMatches(size int, operations [][]*operation) ([]*operation, [][]*operation) { 733 var lastIdx int = -1 734 sort.Search(len(operations), func(idx int) bool { 735 if len(operations[idx]) <= size { 736 lastIdx = idx 737 } 738 return size <= len(operations[idx]) 739 }) 740 if lastIdx < 0 { 741 return nil, operations 742 } 743 bestMatches := operations[lastIdx] 744 if len(bestMatches) > size { 745 return nil, operations 746 } 747 return bestMatches, append(operations[:lastIdx], operations[lastIdx+1:]...) 748 } 749 750 func getCtxError(ctx internal_context.Context) error { 751 select { 752 case <-ctx.Done(): 753 return ctx.Err() 754 default: 755 return nil 756 } 757 } 758 759 func isTimeoutError(err error) bool { 760 if err == context.DeadlineExceeded { 761 return false 762 } else if os.IsTimeout(err) { 763 return true 764 } else if clientErr, ok := unwrapUnderlyingError(err).(*clientv1.ErrorInfo); ok { 765 if clientErr.Code == 504 { 766 return true 767 } 768 } 769 return false 770 } 771 772 func isOutOfQuotaError(err error) bool { 773 if clientErr, ok := unwrapUnderlyingError(err).(*clientv1.ErrorInfo); ok { 774 if clientErr.Code == 573 { 775 return true 776 } 777 } 778 return false 779 } 780 781 func tryToUnwrapUnderlyingError(err error) (error, bool) { 782 switch err := err.(type) { 783 case *os.PathError: 784 return err.Err, true 785 case *os.LinkError: 786 return err.Err, true 787 case *os.SyscallError: 788 return err.Err, true 789 case *url.Error: 790 return err.Err, true 791 case *net.OpError: 792 return err.Err, true 793 } 794 return err, false 795 } 796 797 func unwrapUnderlyingError(err error) error { 798 ok := true 799 for ok { 800 err, ok = tryToUnwrapUnderlyingError(err) 801 } 802 return err 803 }