Skip to content

Commit 05f5618

Browse files
Shashikanth Reddy Potujenkins
Shashikanth Reddy Potu
authored and
jenkins
committed
finagle-core: ExponentialJitteredBackoff random zero bugfix
**Problem:** `ExponentialJitteredBackoff.next` throws an exception occasionally. ``` java.lang.IllegalArgumentException: at java.base/java.util.concurrent.ThreadLocalRandom.nextLong(ThreadLocalRandom.java:361) at com.twitter.finagle.util.Rng$$anon$2.nextLong(Rng.scala:71) ERR at com.twitter.finagle.Backoff$ExponentialJittered.next(Backoff.scala:331) ``` **Bug:** `Rng.nextLong(n)` generates a number from 0 (inclusive) to n (exclusive). When it generates 0 as random number. The next occurrence of backoff.next would try fetching `Rng.nextLong(0)` which is illegal argument. **Fix:** Add 1 to the generated random, so that `Rng.nextLong(_)` can accept the number. Since the random generated is exclusive on the `n` provided, this doesn't cause overflow as well. JIRA Issues: STOR-8734 Differential Revision: https://phabricator.twitter.biz/D1178528
1 parent df4c740 commit 05f5618

File tree

3 files changed

+25
-7
lines changed

3 files changed

+25
-7
lines changed

CHANGELOG.rst

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Bug Fixes
3131

3232
* finagle-memcached: Fixed support for running memcached tests with external memcached. Added README with
3333
instructions under finagle/finagle-memcached. ``PHAB_ID=D1120240``
34+
* finagle-core: Fixed a bug in `ExponentialJitteredBackoff` where `rng` can be 0. ``PHAB_ID=D1178528``
3435

3536

3637
Breaking API Changes

finagle-core/src/main/scala/com/twitter/finagle/Backoff.scala

+3-2
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,9 @@ object Backoff {
328328
if (maxBackoff == maximum) {
329329
new Const(maximum)
330330
} else {
331-
val random = Duration.fromNanoseconds(rng.nextLong(maxBackoff.inNanoseconds))
332-
new ExponentialJittered(random, maximum, attempt + 1, rng)
331+
// to avoid the case of random being 0
332+
val random = 1 + rng.nextLong(maxBackoff.inNanoseconds)
333+
new ExponentialJittered(Duration.fromNanoseconds(random), maximum, attempt + 1, rng)
333334
}
334335
}
335336

finagle-core/src/test/scala/com/twitter/finagle/BackoffTest.scala

+21-5
Original file line numberDiff line numberDiff line change
@@ -147,23 +147,29 @@ class BackoffTest extends AnyFunSuite with ScalaCheckDrivenPropertyChecks {
147147
forAll(exponentialGen) {
148148
case (startMs: Long, maxMs: Long, seed: Long) =>
149149
val rng = Rng(seed)
150-
val backoff: Backoff =
151-
new ExponentialJittered(startMs.millis, maxMs.millis, 1, Rng(seed))
152-
val result: ArrayBuffer[Duration] = new ArrayBuffer[Duration]()
153150
var start = startMs.millis
151+
val max = maxMs.millis
152+
val backoff: Backoff = new ExponentialJittered(start, max, 1, Rng(seed))
153+
val result: ArrayBuffer[Duration] = new ArrayBuffer[Duration]()
154154
for (attempt <- 1 to 7) {
155155
result.append(start)
156-
start = nextStart(start, maxMs.millis, rng, attempt)
156+
start = nextStart(start, max, rng, attempt)
157157
}
158158
verifyBackoff(backoff, result.toSeq, exhausted = false)
159+
160+
// Verify case where Rng returns 0.
161+
val zeroRng = getZeroRng(rng)
162+
val zeroRngBackoff: Backoff = new ExponentialJittered(start, max, 1, zeroRng)
163+
val expectedResults = Seq(start, nextStart(start, max, zeroRng, 1))
164+
verifyBackoff(zeroRngBackoff, expectedResults, exhausted = false)
159165
}
160166

161167
def nextStart(start: Duration, max: Duration, rng: Rng, attempt: Int): Duration = {
162168
val shift = 1L << attempt
163169
// to avoid Long overflow
164170
val maxBackoff = if (start >= max / shift) max else start * shift
165171
if (maxBackoff == max) max
166-
else Duration.fromNanoseconds(rng.nextLong(maxBackoff.inNanoseconds))
172+
else Duration.fromNanoseconds(1 + rng.nextLong(maxBackoff.inNanoseconds))
167173
}
168174
}
169175

@@ -331,4 +337,14 @@ class BackoffTest extends AnyFunSuite with ScalaCheckDrivenPropertyChecks {
331337
}
332338
assert(actualBackoff.isExhausted == exhausted)
333339
}
340+
341+
private[this] def getZeroRng(rng: Rng): Rng = new Rng {
342+
override def nextDouble(): Double = rng.nextDouble()
343+
344+
override def nextInt(n: Int): Int = rng.nextInt(n)
345+
346+
override def nextInt(): Int = rng.nextInt()
347+
348+
override def nextLong(n: Long): Long = 0L
349+
}
334350
}

0 commit comments

Comments
 (0)