mirror of
https://github.com/rclone/rclone.git
synced 2025-08-19 09:52:05 +02:00
Switch to using the dep tool and update all the dependencies
This commit is contained in:
258
vendor/google.golang.org/api/support/bundler/bundler.go
generated
vendored
Normal file
258
vendor/google.golang.org/api/support/bundler/bundler.go
generated
vendored
Normal file
@@ -0,0 +1,258 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package bundler supports bundling (batching) of items. Bundling amortizes an
|
||||
// action with fixed costs over multiple items. For example, if an API provides
|
||||
// an RPC that accepts a list of items as input, but clients would prefer
|
||||
// adding items one at a time, then a Bundler can accept individual items from
|
||||
// the client and bundle many of them into a single RPC.
|
||||
//
|
||||
// This package is experimental and subject to change without notice.
|
||||
package bundler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultDelayThreshold = time.Second
|
||||
DefaultBundleCountThreshold = 10
|
||||
DefaultBundleByteThreshold = 1e6 // 1M
|
||||
DefaultBufferedByteLimit = 1e9 // 1G
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrOverflow indicates that Bundler's stored bytes exceeds its BufferedByteLimit.
|
||||
ErrOverflow = errors.New("bundler reached buffered byte limit")
|
||||
|
||||
// ErrOversizedItem indicates that an item's size exceeds the maximum bundle size.
|
||||
ErrOversizedItem = errors.New("item size exceeds bundle byte limit")
|
||||
)
|
||||
|
||||
// A Bundler collects items added to it into a bundle until the bundle
|
||||
// exceeds a given size, then calls a user-provided function to handle the bundle.
|
||||
type Bundler struct {
|
||||
// Starting from the time that the first message is added to a bundle, once
|
||||
// this delay has passed, handle the bundle. The default is DefaultDelayThreshold.
|
||||
DelayThreshold time.Duration
|
||||
|
||||
// Once a bundle has this many items, handle the bundle. Since only one
|
||||
// item at a time is added to a bundle, no bundle will exceed this
|
||||
// threshold, so it also serves as a limit. The default is
|
||||
// DefaultBundleCountThreshold.
|
||||
BundleCountThreshold int
|
||||
|
||||
// Once the number of bytes in current bundle reaches this threshold, handle
|
||||
// the bundle. The default is DefaultBundleByteThreshold. This triggers handling,
|
||||
// but does not cap the total size of a bundle.
|
||||
BundleByteThreshold int
|
||||
|
||||
// The maximum size of a bundle, in bytes. Zero means unlimited.
|
||||
BundleByteLimit int
|
||||
|
||||
// The maximum number of bytes that the Bundler will keep in memory before
|
||||
// returning ErrOverflow. The default is DefaultBufferedByteLimit.
|
||||
BufferedByteLimit int
|
||||
|
||||
handler func(interface{}) // called to handle a bundle
|
||||
itemSliceZero reflect.Value // nil (zero value) for slice of items
|
||||
flushTimer *time.Timer // implements DelayThreshold
|
||||
|
||||
mu sync.Mutex
|
||||
sem *semaphore.Weighted // enforces BufferedByteLimit
|
||||
semOnce sync.Once
|
||||
curBundle bundle // incoming items added to this bundle
|
||||
handlingc <-chan struct{} // set to non-nil while a handler is running; closed when it returns
|
||||
}
|
||||
|
||||
type bundle struct {
|
||||
items reflect.Value // slice of item type
|
||||
size int // size in bytes of all items
|
||||
}
|
||||
|
||||
// NewBundler creates a new Bundler.
|
||||
//
|
||||
// itemExample is a value of the type that will be bundled. For example, if you
|
||||
// want to create bundles of *Entry, you could pass &Entry{} for itemExample.
|
||||
//
|
||||
// handler is a function that will be called on each bundle. If itemExample is
|
||||
// of type T, the argument to handler is of type []T. handler is always called
|
||||
// sequentially for each bundle, and never in parallel.
|
||||
//
|
||||
// Configure the Bundler by setting its thresholds and limits before calling
|
||||
// any of its methods.
|
||||
func NewBundler(itemExample interface{}, handler func(interface{})) *Bundler {
|
||||
b := &Bundler{
|
||||
DelayThreshold: DefaultDelayThreshold,
|
||||
BundleCountThreshold: DefaultBundleCountThreshold,
|
||||
BundleByteThreshold: DefaultBundleByteThreshold,
|
||||
BufferedByteLimit: DefaultBufferedByteLimit,
|
||||
|
||||
handler: handler,
|
||||
itemSliceZero: reflect.Zero(reflect.SliceOf(reflect.TypeOf(itemExample))),
|
||||
}
|
||||
b.curBundle.items = b.itemSliceZero
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Bundler) sema() *semaphore.Weighted {
|
||||
// Create the semaphore lazily, because the user may set BufferedByteLimit
|
||||
// after NewBundler.
|
||||
b.semOnce.Do(func() {
|
||||
b.sem = semaphore.NewWeighted(int64(b.BufferedByteLimit))
|
||||
})
|
||||
return b.sem
|
||||
}
|
||||
|
||||
// Add adds item to the current bundle. It marks the bundle for handling and
|
||||
// starts a new one if any of the thresholds or limits are exceeded.
|
||||
//
|
||||
// If the item's size exceeds the maximum bundle size (Bundler.BundleByteLimit), then
|
||||
// the item can never be handled. Add returns ErrOversizedItem in this case.
|
||||
//
|
||||
// If adding the item would exceed the maximum memory allowed
|
||||
// (Bundler.BufferedByteLimit) or an AddWait call is blocked waiting for
|
||||
// memory, Add returns ErrOverflow.
|
||||
//
|
||||
// Add never blocks.
|
||||
func (b *Bundler) Add(item interface{}, size int) error {
|
||||
// If this item exceeds the maximum size of a bundle,
|
||||
// we can never send it.
|
||||
if b.BundleByteLimit > 0 && size > b.BundleByteLimit {
|
||||
return ErrOversizedItem
|
||||
}
|
||||
// If adding this item would exceed our allotted memory
|
||||
// footprint, we can't accept it.
|
||||
// (TryAcquire also returns false if anything is waiting on the semaphore,
|
||||
// so calls to Add and AddWait shouldn't be mixed.)
|
||||
if !b.sema().TryAcquire(int64(size)) {
|
||||
return ErrOverflow
|
||||
}
|
||||
b.add(item, size)
|
||||
return nil
|
||||
}
|
||||
|
||||
// add adds item to the current bundle. It marks the bundle for handling and
|
||||
// starts a new one if any of the thresholds or limits are exceeded.
|
||||
func (b *Bundler) add(item interface{}, size int) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
// If adding this item to the current bundle would cause it to exceed the
|
||||
// maximum bundle size, close the current bundle and start a new one.
|
||||
if b.BundleByteLimit > 0 && b.curBundle.size+size > b.BundleByteLimit {
|
||||
b.startFlushLocked()
|
||||
}
|
||||
// Add the item.
|
||||
b.curBundle.items = reflect.Append(b.curBundle.items, reflect.ValueOf(item))
|
||||
b.curBundle.size += size
|
||||
|
||||
// Start a timer to flush the item if one isn't already running.
|
||||
// startFlushLocked clears the timer and closes the bundle at the same time,
|
||||
// so we only allocate a new timer for the first item in each bundle.
|
||||
// (We could try to call Reset on the timer instead, but that would add a lot
|
||||
// of complexity to the code just to save one small allocation.)
|
||||
if b.flushTimer == nil {
|
||||
b.flushTimer = time.AfterFunc(b.DelayThreshold, b.Flush)
|
||||
}
|
||||
|
||||
// If the current bundle equals the count threshold, close it.
|
||||
if b.curBundle.items.Len() == b.BundleCountThreshold {
|
||||
b.startFlushLocked()
|
||||
}
|
||||
// If the current bundle equals or exceeds the byte threshold, close it.
|
||||
if b.curBundle.size >= b.BundleByteThreshold {
|
||||
b.startFlushLocked()
|
||||
}
|
||||
}
|
||||
|
||||
// AddWait adds item to the current bundle. It marks the bundle for handling and
|
||||
// starts a new one if any of the thresholds or limits are exceeded.
|
||||
//
|
||||
// If the item's size exceeds the maximum bundle size (Bundler.BundleByteLimit), then
|
||||
// the item can never be handled. AddWait returns ErrOversizedItem in this case.
|
||||
//
|
||||
// If adding the item would exceed the maximum memory allowed (Bundler.BufferedByteLimit),
|
||||
// AddWait blocks until space is available or ctx is done.
|
||||
//
|
||||
// Calls to Add and AddWait should not be mixed on the same Bundler.
|
||||
func (b *Bundler) AddWait(ctx context.Context, item interface{}, size int) error {
|
||||
// If this item exceeds the maximum size of a bundle,
|
||||
// we can never send it.
|
||||
if b.BundleByteLimit > 0 && size > b.BundleByteLimit {
|
||||
return ErrOversizedItem
|
||||
}
|
||||
// If adding this item would exceed our allotted memory footprint, block
|
||||
// until space is available. The semaphore is FIFO, so there will be no
|
||||
// starvation.
|
||||
if err := b.sema().Acquire(ctx, int64(size)); err != nil {
|
||||
return err
|
||||
}
|
||||
// Here, we've reserved space for item. Other goroutines can call AddWait
|
||||
// and even acquire space, but no one can take away our reservation
|
||||
// (assuming sem.Release is used correctly). So there is no race condition
|
||||
// resulting from locking the mutex after sem.Acquire returns.
|
||||
b.add(item, size)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush invokes the handler for all remaining items in the Bundler and waits
|
||||
// for it to return.
|
||||
func (b *Bundler) Flush() {
|
||||
b.mu.Lock()
|
||||
b.startFlushLocked()
|
||||
done := b.handlingc
|
||||
b.mu.Unlock()
|
||||
|
||||
if done != nil {
|
||||
<-done
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bundler) startFlushLocked() {
|
||||
if b.flushTimer != nil {
|
||||
b.flushTimer.Stop()
|
||||
b.flushTimer = nil
|
||||
}
|
||||
|
||||
if b.curBundle.items.Len() == 0 {
|
||||
return
|
||||
}
|
||||
bun := b.curBundle
|
||||
b.curBundle = bundle{items: b.itemSliceZero}
|
||||
|
||||
done := make(chan struct{})
|
||||
var running <-chan struct{}
|
||||
running, b.handlingc = b.handlingc, done
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
b.sem.Release(int64(bun.size))
|
||||
close(done)
|
||||
}()
|
||||
|
||||
if running != nil {
|
||||
// Wait for our turn to call the handler.
|
||||
<-running
|
||||
}
|
||||
|
||||
b.handler(bun.items.Interface())
|
||||
}()
|
||||
}
|
285
vendor/google.golang.org/api/support/bundler/bundler_test.go
generated
vendored
Normal file
285
vendor/google.golang.org/api/support/bundler/bundler_test.go
generated
vendored
Normal file
@@ -0,0 +1,285 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package bundler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestBundlerCount1(t *testing.T) {
|
||||
// Unbundled case: one item per bundle.
|
||||
handler := &testHandler{}
|
||||
b := NewBundler(int(0), handler.handle)
|
||||
b.BundleCountThreshold = 1
|
||||
b.DelayThreshold = time.Second
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
if err := b.Add(i, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
b.Flush()
|
||||
got := handler.bundles()
|
||||
want := [][]int{{0}, {1}, {2}}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("bundles: got %v, want %v", got, want)
|
||||
}
|
||||
// All bundles should have been handled "immediately": much less
|
||||
// than the delay threshold of 1s.
|
||||
tgot := quantizeTimes(handler.times(), 100*time.Millisecond)
|
||||
twant := []int{0, 0, 0}
|
||||
if !reflect.DeepEqual(tgot, twant) {
|
||||
t.Errorf("times: got %v, want %v", tgot, twant)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBundlerCount3(t *testing.T) {
|
||||
handler := &testHandler{}
|
||||
b := NewBundler(int(0), handler.handle)
|
||||
b.BundleCountThreshold = 3
|
||||
b.DelayThreshold = 100 * time.Millisecond
|
||||
// Add 8 items.
|
||||
// The first two bundles of 3 should both be handled quickly.
|
||||
// The third bundle of 2 should not be handled for about DelayThreshold ms.
|
||||
for i := 0; i < 8; i++ {
|
||||
if err := b.Add(i, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
time.Sleep(5 * b.DelayThreshold)
|
||||
// We should not need to close the bundler.
|
||||
|
||||
bgot := handler.bundles()
|
||||
bwant := [][]int{{0, 1, 2}, {3, 4, 5}, {6, 7}}
|
||||
if !reflect.DeepEqual(bgot, bwant) {
|
||||
t.Errorf("bundles: got %v, want %v", bgot, bwant)
|
||||
}
|
||||
|
||||
tgot := quantizeTimes(handler.times(), b.DelayThreshold)
|
||||
if len(tgot) != 3 || tgot[0] != 0 || tgot[1] != 0 || tgot[2] == 0 {
|
||||
t.Errorf("times: got %v, want [0, 0, non-zero]", tgot)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBundlerByteThreshold(t *testing.T) {
|
||||
handler := &testHandler{}
|
||||
b := NewBundler(int(0), handler.handle)
|
||||
b.BundleCountThreshold = 10
|
||||
b.BundleByteThreshold = 3
|
||||
add := func(i interface{}, s int) {
|
||||
if err := b.Add(i, s); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
add(1, 1)
|
||||
add(2, 2)
|
||||
// Hit byte threshold: bundle = 1, 2
|
||||
add(3, 1)
|
||||
add(4, 1)
|
||||
add(5, 2)
|
||||
// Passed byte threshold, but not limit: bundle = 3, 4, 5
|
||||
add(6, 1)
|
||||
b.Flush()
|
||||
bgot := handler.bundles()
|
||||
bwant := [][]int{{1, 2}, {3, 4, 5}, {6}}
|
||||
if !reflect.DeepEqual(bgot, bwant) {
|
||||
t.Errorf("bundles: got %v, want %v", bgot, bwant)
|
||||
}
|
||||
tgot := quantizeTimes(handler.times(), b.DelayThreshold)
|
||||
twant := []int{0, 0, 0}
|
||||
if !reflect.DeepEqual(tgot, twant) {
|
||||
t.Errorf("times: got %v, want %v", tgot, twant)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBundlerLimit(t *testing.T) {
|
||||
handler := &testHandler{}
|
||||
b := NewBundler(int(0), handler.handle)
|
||||
b.BundleCountThreshold = 10
|
||||
b.BundleByteLimit = 3
|
||||
add := func(i interface{}, s int) {
|
||||
if err := b.Add(i, s); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
add(1, 1)
|
||||
add(2, 2)
|
||||
// Hit byte limit: bundle = 1, 2
|
||||
add(3, 1)
|
||||
add(4, 1)
|
||||
add(5, 2)
|
||||
// Exceeded byte limit: bundle = 3, 4
|
||||
add(6, 2)
|
||||
// Exceeded byte limit: bundle = 5
|
||||
b.Flush()
|
||||
bgot := handler.bundles()
|
||||
bwant := [][]int{{1, 2}, {3, 4}, {5}, {6}}
|
||||
if !reflect.DeepEqual(bgot, bwant) {
|
||||
t.Errorf("bundles: got %v, want %v", bgot, bwant)
|
||||
}
|
||||
tgot := quantizeTimes(handler.times(), b.DelayThreshold)
|
||||
twant := []int{0, 0, 0, 0}
|
||||
if !reflect.DeepEqual(tgot, twant) {
|
||||
t.Errorf("times: got %v, want %v", tgot, twant)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddWait(t *testing.T) {
|
||||
var (
|
||||
mu sync.Mutex
|
||||
events []string
|
||||
)
|
||||
event := func(s string) {
|
||||
mu.Lock()
|
||||
events = append(events, s)
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
handlec := make(chan int)
|
||||
done := make(chan struct{})
|
||||
b := NewBundler(int(0), func(interface{}) {
|
||||
<-handlec
|
||||
event("handle")
|
||||
})
|
||||
b.BufferedByteLimit = 3
|
||||
addw := func(sz int) {
|
||||
if err := b.AddWait(context.Background(), 0, sz); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
event(fmt.Sprintf("addw(%d)", sz))
|
||||
}
|
||||
|
||||
addw(2)
|
||||
go func() {
|
||||
addw(3) // blocks until first bundle is handled
|
||||
close(done)
|
||||
}()
|
||||
// Give addw(3) a chance to finish
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
handlec <- 1 // handle the first bundle
|
||||
select {
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timed out")
|
||||
case <-done:
|
||||
}
|
||||
want := []string{"addw(2)", "handle", "addw(3)"}
|
||||
if !reflect.DeepEqual(events, want) {
|
||||
t.Errorf("got %v\nwant%v", events, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddWaitCancel(t *testing.T) {
|
||||
b := NewBundler(int(0), func(interface{}) {})
|
||||
b.BufferedByteLimit = 3
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
cancel()
|
||||
}()
|
||||
err := b.AddWait(ctx, 0, 4)
|
||||
if want := context.Canceled; err != want {
|
||||
t.Fatalf("got %v, want %v", err, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBundlerErrors(t *testing.T) {
|
||||
// Use a handler that blocks forever, to force the bundler to run out of
|
||||
// memory.
|
||||
b := NewBundler(int(0), func(interface{}) { select {} })
|
||||
b.BundleByteLimit = 3
|
||||
b.BufferedByteLimit = 10
|
||||
|
||||
if got, want := b.Add(1, 4), ErrOversizedItem; got != want {
|
||||
t.Fatalf("got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
if err := b.Add(i, 2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if got, want := b.Add(5, 1), ErrOverflow; got != want {
|
||||
t.Fatalf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
type testHandler struct {
|
||||
mu sync.Mutex
|
||||
b [][]int
|
||||
t []time.Time
|
||||
}
|
||||
|
||||
func (t *testHandler) bundles() [][]int {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
return t.b
|
||||
}
|
||||
|
||||
func (t *testHandler) times() []time.Time {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
return t.t
|
||||
}
|
||||
|
||||
func (t *testHandler) handle(b interface{}) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.b = append(t.b, b.([]int))
|
||||
t.t = append(t.t, time.Now())
|
||||
}
|
||||
|
||||
// Round times to the nearest q and express them as the number of q
|
||||
// since the first time.
|
||||
// E.g. if q is 100ms, then a time within 50ms of the first time
|
||||
// will be represented as 0, a time 150 to 250ms of the first time
|
||||
// we be represented as 1, etc.
|
||||
func quantizeTimes(times []time.Time, q time.Duration) []int {
|
||||
var rs []int
|
||||
for _, t := range times {
|
||||
d := t.Sub(times[0])
|
||||
r := int((d + q/2) / q)
|
||||
rs = append(rs, r)
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
func TestQuantizeTimes(t *testing.T) {
|
||||
quantum := 100 * time.Millisecond
|
||||
for _, test := range []struct {
|
||||
millis []int // times in milliseconds
|
||||
want []int
|
||||
}{
|
||||
{[]int{10, 20, 30}, []int{0, 0, 0}},
|
||||
{[]int{0, 49, 50, 90}, []int{0, 0, 1, 1}},
|
||||
{[]int{0, 95, 170, 315}, []int{0, 1, 2, 3}},
|
||||
} {
|
||||
var times []time.Time
|
||||
for _, ms := range test.millis {
|
||||
times = append(times, time.Unix(0, int64(ms*1e6)))
|
||||
}
|
||||
got := quantizeTimes(times, quantum)
|
||||
if !reflect.DeepEqual(got, test.want) {
|
||||
t.Errorf("%v: got %v, want %v", test.millis, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user