mirror of
https://github.com/rclone/rclone.git
synced 2025-12-06 00:03:32 +00:00
pacer: factor call stack searching into its own package
This commit is contained in:
26
lib/caller/caller.go
Normal file
26
lib/caller/caller.go
Normal file
@@ -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
|
||||
}
|
||||
37
lib/caller/caller_test.go
Normal file
37
lib/caller/caller_test.go
Normal file
@@ -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)
|
||||
}
|
||||
@@ -4,11 +4,10 @@ package pacer
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rclone/rclone/lib/caller"
|
||||
liberrors "github.com/rclone/rclone/lib/errors"
|
||||
)
|
||||
|
||||
@@ -193,7 +192,9 @@ func (p *Pacer) endCall(retry bool, err error, limitConnections bool) {
|
||||
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
|
||||
// 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
|
||||
// configuration so adding a bit of extra slowdown here is not a
|
||||
// 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) {
|
||||
var retry bool
|
||||
limitConnections := false
|
||||
if p.maxConnections > 0 && !pacerReentered() {
|
||||
if p.maxConnections > 0 && !caller.Present("(*Pacer).call") {
|
||||
limitConnections = true
|
||||
}
|
||||
for i := 1; i <= retries; i++ {
|
||||
|
||||
@@ -353,27 +353,6 @@ func TestCallParallel(t *testing.T) {
|
||||
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) {
|
||||
p := New(CalculatorOption(NewDefault(MinSleep(1*time.Millisecond), MaxSleep(2*time.Millisecond))))
|
||||
p.SetMaxConnections(1)
|
||||
|
||||
Reference in New Issue
Block a user