Skip to content

Commit 5238027

Browse files
vkostyukovjenkins
authored and
jenkins
committed
finagle-core: Share RetryBudget within a single StackClient
Problem It mostly affects clients built with MethodBuilder but the fix is also beneficial for vanilla stack clients should be put more RetryBudget-dependent stack modules into them in the future. We're very inconsistent when it comes to RetryBudgets in MethodBuilders. Here is the current state of things: - If a user configures their own RetryBudget, MethodBuilder will re-use it for all its methods (and within every method between its filters, retries and backups). - If a user doesn't configure their own RetryBudget, MethoddBiulder will create a new RetryBudget for each method. There is a 50% chance that the underlying client will share its RetryBudget with the *first* method created with MethodBuilder. Solution Always (unless already configured by a user) pre-configure a fresh copy of a RetryBudget for every new client. Differential Revision: https://phabricator.twitter.biz/D979325
1 parent 129a5ce commit 5238027

File tree

7 files changed

+45
-17
lines changed

7 files changed

+45
-17
lines changed

build.sbt

+2-1
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,8 @@ lazy val finagleIntegration = Project(
360360
finagleNetty4Http,
361361
finagleRedis % "test",
362362
finagleThrift,
363-
finagleThriftMux % "test->compile;test->test"
363+
finagleThriftMux % "test->compile;test->test",
364+
finaglePostgresql % "test->compile;test->test"
364365
)
365366

366367
lazy val finagleToggle = Project(

finagle-core/src/main/scala/com/twitter/finagle/client/MethodBuilder.scala

+2-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import com.twitter.finagle.client.MethodBuilderTimeout.TunableDuration
55
import com.twitter.finagle.service.Filterable
66
import com.twitter.finagle.service.ResponseClass
77
import com.twitter.finagle.service.ResponseClassifier
8-
import com.twitter.finagle.service.Retries
98
import com.twitter.finagle.service.TimeoutFilter
109
import com.twitter.finagle.stats.LazyStatsReceiver
1110
import com.twitter.finagle.stats.StatsReceiver
@@ -56,6 +55,7 @@ object MethodBuilder {
5655
*/
5756
def from[Req, Rep](dest: Name, stackClient: StackClient[Req, Rep]): MethodBuilder[Req, Rep] = {
5857
val stack = modifiedStack(stackClient.stack)
58+
5959
new MethodBuilder(
6060
new MethodPool[Req, Rep](stackClient.withStack(stack), dest, param.Label.Default),
6161
dest,
@@ -397,10 +397,7 @@ final class MethodBuilder[Req, Rep] private[finagle] (
397397
methodPool,
398398
dest,
399399
stack,
400-
// If the RetryBudget is not configured, BackupRequestFilter and RetryFilter will each
401-
// get a new instance of the default budget. Since we want them to share the same
402-
// client retry budget, insert the budget into the params.
403-
params + params[Retries.Budget],
400+
params,
404401
config.copy(backup = brfParam)
405402
)
406403

finagle-core/src/main/scala/com/twitter/finagle/client/StackClient.scala

+3-1
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,9 @@ object StackClient {
491491
Stack.Params.empty +
492492
Stats(ClientStatsReceiver) +
493493
LoadBalancerFactory.HostStats(LoadedHostStatsReceiver) +
494-
MetricBuilders(Some(new CoreMetricsRegistry()))
494+
MetricBuilders(Some(new CoreMetricsRegistry())) +
495+
// RetryBudget needs to be shared across one physical client.
496+
Retries.Budget.default
495497

496498
/**
497499
* A set of StackTransformers for transforming client stacks.

finagle-core/src/test/scala/com/twitter/finagle/client/MethodBuilderTest.scala

+8-8
Original file line numberDiff line numberDiff line change
@@ -1120,8 +1120,8 @@ class MethodBuilderTest
11201120
assert(client.params[Retries.Budget].retryBudget eq retryBudget)
11211121
}
11221122

1123-
test("idempotent adds retry budget to params") {
1124-
val params = Stack.Params.empty
1123+
test("shares RetryBudget between methods") {
1124+
val params = StackClient.defaultParams
11251125

11261126
val svc: Service[Int, Int] = Service.const(Future.value(1))
11271127

@@ -1131,16 +1131,16 @@ class MethodBuilderTest
11311131
val stackClient = TestStackClient(stack, params)
11321132
val client = MethodBuilder.from("mb", stackClient)
11331133

1134-
// there's no budget to start with; if we get the budget it's a new default one each time
1135-
assert(client.params[Retries.Budget].retryBudget ne client.params[Retries.Budget].retryBudget)
1134+
val method1 =
1135+
client.idempotent(1.percent, sendInterrupts = true, ResponseClassifier.Default)
11361136

1137-
val idempotentClient =
1137+
val method2 =
11381138
client.idempotent(1.percent, sendInterrupts = true, ResponseClassifier.Default)
11391139

1140-
// now that the budget has been added (a new default one), we get the same one each time
1140+
// each method should share the same retryBudget
11411141
assert(
1142-
idempotentClient.params[Retries.Budget].retryBudget eq
1143-
idempotentClient.params[Retries.Budget].retryBudget
1142+
method1.params[Retries.Budget].retryBudget eq
1143+
method2.params[Retries.Budget].retryBudget
11441144
)
11451145
}
11461146

finagle-integration/src/test/scala/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ junit_tests(
2222
"finagle/finagle-mysql/src/main/scala",
2323
"finagle/finagle-mysql/src/test/scala/com/twitter/finagle/mysql/harness",
2424
"finagle/finagle-netty4-http",
25+
"finagle/finagle-postgresql/src/main/scala/com/twitter/finagle",
2526
"finagle/finagle-redis",
2627
"finagle/finagle-thrift",
2728
"finagle/finagle-thriftmux",

finagle-integration/src/test/scala/com/twitter/finagle/integration/StackClientTest.scala

+28-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
package com.twitter.finagle.integration
22

3+
import com.twitter.finagle.service.Retries
34
import com.twitter.finagle.service.StatsFilter
4-
import com.twitter.finagle.{Http, Memcached, Mysql, Redis, Thrift, ThriftMux}
5+
import com.twitter.finagle.Http
6+
import com.twitter.finagle.Memcached
7+
import com.twitter.finagle.Mysql
8+
import com.twitter.finagle.PostgreSql
9+
import com.twitter.finagle.Redis
10+
import com.twitter.finagle.Thrift
11+
import com.twitter.finagle.ThriftMux
12+
import com.twitter.finagle.client.StackBasedClient
513
import org.scalatest.funsuite.AnyFunSuite
614

715
class StackClientTest extends AnyFunSuite {
@@ -44,4 +52,23 @@ class StackClientTest extends AnyFunSuite {
4452
assert(!c.stack.contains(role))
4553
}
4654

55+
def testThatRetryBudgetIsShared(newClient: () => StackBasedClient[_, _], protocol: String): Unit =
56+
test(s"$protocol clients shipped with retry-budget included") {
57+
// every new clients gets new retry budget
58+
assert(
59+
newClient().params[Retries.Budget].retryBudget ne newClient()
60+
.params[Retries.Budget].retryBudget)
61+
62+
// single client shares the retry budget between it's modules
63+
val client = newClient()
64+
assert(client.params[Retries.Budget].retryBudget eq client.params[Retries.Budget].retryBudget)
65+
}
66+
67+
testThatRetryBudgetIsShared(Thrift.client _, "Thrift")
68+
testThatRetryBudgetIsShared(ThriftMux.client _, "ThriftMux")
69+
testThatRetryBudgetIsShared(Http.client _, "Http")
70+
testThatRetryBudgetIsShared(Memcached.client _, "Memcached")
71+
testThatRetryBudgetIsShared(Redis.client _, "Redis")
72+
testThatRetryBudgetIsShared(Mysql.client _, "Mysql")
73+
testThatRetryBudgetIsShared(PostgreSql.client _, "PostgreSql")
4774
}

finagle-postgresql/src/main/scala/com/twitter/finagle/PostgreSql.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ object PostgreSql {
4040
NackAdmissionFilter.module[Request, Response]
4141
)
4242

43-
val defaultParams: Stack.Params = StackClient.defaultParams +
43+
def defaultParams: Stack.Params = StackClient.defaultParams +
4444
// Keep NackAdmissionFilter disabled by default for backwards compatibility.
4545
NackAdmissionFilter.Disabled
4646

0 commit comments

Comments
 (0)