Skip to content

Commit c67876f

Browse files
committed
Merge pull request #12 from drob/syntax-sugar
Allow invoking retries with less punctuation boilerplate.
2 parents 7227b38 + a3dfeb3 commit c67876f

File tree

3 files changed

+54
-8
lines changed

3 files changed

+54
-8
lines changed

build.sbt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ libraryDependencies ++= Seq(
1212
"me.lessis" %% "odelay-core" % "0.1.0",
1313
"org.scalatest" %% "scalatest" % "2.2.0" % "test")
1414

15+
scalacOptions += "-feature"
16+
1517
scalaVersion := crossScalaVersions.value.last
1618

1719
licenses :=

src/main/scala/Policy.scala

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,29 @@ package retry
33
import odelay.{ Delay, Timer }
44
import scala.concurrent.{ Future, ExecutionContext }
55
import scala.concurrent.duration.{ Duration, FiniteDuration }
6+
import scala.language.implicitConversions
67
import scala.util.control.NonFatal
78
import java.util.concurrent.TimeUnit
89

10+
// This case class and its implicit conversions allow us to accept both
11+
// `() => Future[T]` and `Future[T]`-by-name as Policy.apply arguments.
12+
// Note that these two types are the same after erasure.
13+
case class PromiseWrapper[T](
14+
promise: () => Future[T]
15+
)
16+
17+
object PromiseWrapper {
18+
implicit def fromFuture[T](promise: () => Future[T]): PromiseWrapper[T] = PromiseWrapper(promise)
19+
implicit def toFuture[T](pw: PromiseWrapper[T]): () => Future[T] = pw.promise
20+
}
21+
922
object Directly {
1023

1124
/** Retry immediately after failure forever */
1225
def forever: Policy =
1326
new Policy {
1427
def apply[T]
15-
(promise: () => Future[T])
28+
(promise: PromiseWrapper[T])
1629
(implicit success: Success[T],
1730
executor: ExecutionContext): Future[T] =
1831
retry(promise, promise)
@@ -22,7 +35,7 @@ object Directly {
2235
def apply(max: Int = 3): Policy =
2336
new CountingPolicy {
2437
def apply[T]
25-
(promise: () => Future[T])
38+
(promise: PromiseWrapper[T])
2639
(implicit success: Success[T],
2740
executor: ExecutionContext): Future[T] = {
2841
def run(max: Int): Future[T] = countdown(max, promise, run)
@@ -39,7 +52,7 @@ object Pause {
3952
(implicit timer: Timer): Policy =
4053
new Policy { self =>
4154
def apply[T]
42-
(promise: () => Future[T])
55+
(promise: PromiseWrapper[T])
4356
(implicit success: Success[T],
4457
executor: ExecutionContext): Future[T] =
4558
retry(promise, { () =>
@@ -52,7 +65,7 @@ object Pause {
5265
(implicit timer: Timer): Policy =
5366
new CountingPolicy {
5467
def apply[T]
55-
(promise: () => Future[T])
68+
(promise: PromiseWrapper[T])
5669
(implicit success: Success[T],
5770
executor: ExecutionContext): Future[T] = {
5871
def run(max: Int): Future[T] = countdown(
@@ -70,7 +83,7 @@ object Backoff {
7083
(implicit timer: Timer): Policy =
7184
new Policy {
7285
def apply[T]
73-
(promise: () => Future[T])
86+
(promise: PromiseWrapper[T])
7487
(implicit success: Success[T],
7588
executor: ExecutionContext): Future[T] = {
7689
def run(delay: FiniteDuration): Future[T] = retry(promise, { () =>
@@ -90,7 +103,7 @@ object Backoff {
90103
(implicit timer: Timer): Policy =
91104
new CountingPolicy {
92105
def apply[T]
93-
(promise: () => Future[T])
106+
(promise: PromiseWrapper[T])
94107
(implicit success: Success[T],
95108
executor: ExecutionContext): Future[T] = {
96109
def run(max: Int, delay: FiniteDuration): Future[T] = countdown(
@@ -121,7 +134,7 @@ object When {
121134
type Depends = PartialFunction[Any, Policy]
122135
def apply(depends: Depends): Policy =
123136
new Policy {
124-
def apply[T](promise: () => Future[T])
137+
def apply[T](promise: PromiseWrapper[T])
125138
(implicit success: Success[T],
126139
executor: ExecutionContext): Future[T] = {
127140
val fut = promise()
@@ -157,10 +170,16 @@ trait CountingPolicy extends Policy {
157170
* specific to implementations
158171
*/
159172
trait Policy {
160-
def apply[T](promise: () => Future[T])
173+
174+
def apply[T](pw: PromiseWrapper[T])
161175
(implicit success: Success[T],
162176
executor: ExecutionContext): Future[T]
163177

178+
def apply[T](promise: => Future[T])
179+
(implicit success: Success[T],
180+
executor: ExecutionContext): Future[T] =
181+
apply { () => promise }
182+
164183
protected def retry[T](
165184
promise: () => Future[T],
166185
orElse: () => Future[T],

src/test/scala/PolicySpec.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,31 @@ class PolicySpec extends FunSpec with BeforeAndAfterAll {
5757
Await.ready(future, Duration.Inf)
5858
assert(counter.get() === 4)
5959
}
60+
61+
it ("should accept a future in reduced syntax format") {
62+
implicit val success = Success.always
63+
val counter = new AtomicInteger()
64+
val future = retry.Directly(1) {
65+
counter.incrementAndGet()
66+
Future.failed(new RuntimeException("always failing"))
67+
}
68+
Await.ready(future, Duration.Inf)
69+
assert(counter.get() === 2)
70+
}
71+
72+
it ("should retry futures passed by-name instead of caching results") {
73+
implicit val success = Success.always
74+
val counter = new AtomicInteger()
75+
val future = retry.Directly(1) {
76+
counter.getAndIncrement() match {
77+
case 1 => Future.successful("yay!")
78+
case _ => Future.failed(new RuntimeException("failed"))
79+
}
80+
}
81+
val result: String = Await.result(future, Duration.Inf)
82+
assert(counter.get() == 2)
83+
assert(result == "yay!")
84+
}
6085
}
6186

6287
describe("retry.Pause") {

0 commit comments

Comments
 (0)