pacer: factor call stack searching into its own package
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
// Package caller contains functions to examine the call stack.
|
||||||
|
package caller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Present looks for functionName in the call stack and return true if found
|
||||||
|
//
|
||||||
|
// Note that this ignores the caller.
|
||||||
|
func Present(functionName string) bool {
|
||||||
|
var pcs [48]uintptr
|
||||||
|
n := runtime.Callers(3, pcs[:]) // skip runtime.Callers, Present and caller
|
||||||
|
frames := runtime.CallersFrames(pcs[:n])
|
||||||
|
for {
|
||||||
|
f, more := frames.Next()
|
||||||
|
if strings.HasSuffix(f.Function, functionName) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !more {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package caller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPresent(t *testing.T) {
|
||||||
|
assert.False(t, Present("NotFound"))
|
||||||
|
assert.False(t, Present("TestPresent"))
|
||||||
|
f := func() {
|
||||||
|
assert.True(t, Present("TestPresent"))
|
||||||
|
}
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkPresent(b *testing.B) {
|
||||||
|
for b.Loop() {
|
||||||
|
_ = Present("NotFound")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkPresent100(b *testing.B) {
|
||||||
|
var fn func(level int)
|
||||||
|
fn = func(level int) {
|
||||||
|
if level > 0 {
|
||||||
|
fn(level - 1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for b.Loop() {
|
||||||
|
_ = Present("NotFound")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
fn(100)
|
||||||
|
}
|
||||||
+5
-21
@@ -4,11 +4,10 @@ package pacer
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/rclone/rclone/lib/caller"
|
||||||
liberrors "github.com/rclone/rclone/lib/errors"
|
liberrors "github.com/rclone/rclone/lib/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -193,7 +192,9 @@ func (p *Pacer) endCall(retry bool, err error, limitConnections bool) {
|
|||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect the pacer being called reentrantly.
|
// call implements Call but with settable retries
|
||||||
|
//
|
||||||
|
// This detects the pacer being called reentrantly.
|
||||||
//
|
//
|
||||||
// This looks for Pacer.call in the call stack and returns true if it
|
// This looks for Pacer.call in the call stack and returns true if it
|
||||||
// is found.
|
// is found.
|
||||||
@@ -204,27 +205,10 @@ func (p *Pacer) endCall(retry bool, err error, limitConnections bool) {
|
|||||||
// This is only needed when p.maxConnections > 0 which isn't a common
|
// This is only needed when p.maxConnections > 0 which isn't a common
|
||||||
// configuration so adding a bit of extra slowdown here is not a
|
// configuration so adding a bit of extra slowdown here is not a
|
||||||
// problem.
|
// problem.
|
||||||
func pacerReentered() bool {
|
|
||||||
var pcs [48]uintptr
|
|
||||||
n := runtime.Callers(3, pcs[:]) // skip runtime.Callers, pacerReentered and call
|
|
||||||
frames := runtime.CallersFrames(pcs[:n])
|
|
||||||
for {
|
|
||||||
f, more := frames.Next()
|
|
||||||
if strings.HasSuffix(f.Function, "(*Pacer).call") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if !more {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// call implements Call but with settable retries
|
|
||||||
func (p *Pacer) call(fn Paced, retries int) (err error) {
|
func (p *Pacer) call(fn Paced, retries int) (err error) {
|
||||||
var retry bool
|
var retry bool
|
||||||
limitConnections := false
|
limitConnections := false
|
||||||
if p.maxConnections > 0 && !pacerReentered() {
|
if p.maxConnections > 0 && !caller.Present("(*Pacer).call") {
|
||||||
limitConnections = true
|
limitConnections = true
|
||||||
}
|
}
|
||||||
for i := 1; i <= retries; i++ {
|
for i := 1; i <= retries; i++ {
|
||||||
|
|||||||
@@ -353,27 +353,6 @@ func TestCallParallel(t *testing.T) {
|
|||||||
wait.Broadcast()
|
wait.Broadcast()
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkPacerReentered(b *testing.B) {
|
|
||||||
for b.Loop() {
|
|
||||||
_ = pacerReentered()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkPacerReentered100(b *testing.B) {
|
|
||||||
var fn func(level int)
|
|
||||||
fn = func(level int) {
|
|
||||||
if level > 0 {
|
|
||||||
fn(level - 1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for b.Loop() {
|
|
||||||
_ = pacerReentered()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
fn(100)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCallMaxConnectionsRecursiveDeadlock(t *testing.T) {
|
func TestCallMaxConnectionsRecursiveDeadlock(t *testing.T) {
|
||||||
p := New(CalculatorOption(NewDefault(MinSleep(1*time.Millisecond), MaxSleep(2*time.Millisecond))))
|
p := New(CalculatorOption(NewDefault(MinSleep(1*time.Millisecond), MaxSleep(2*time.Millisecond))))
|
||||||
p.SetMaxConnections(1)
|
p.SetMaxConnections(1)
|
||||||
|
|||||||
Reference in New Issue
Block a user