Skip to content

Commit 063492a

Browse files
committed
Make f(await(completedFuture)) execute f synchronously
A worthy optimization, suggested by @danarmak. Closes #73
1 parent c0d7115 commit 063492a

File tree

4 files changed

+85
-35
lines changed

4 files changed

+85
-35
lines changed

src/main/scala/scala/async/internal/ExprBuilder.scala

+42-34
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ trait ExprBuilder {
2727

2828
def nextStates: List[Int]
2929

30-
def mkHandlerCaseForState: CaseDef
30+
def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef
3131

3232
def mkOnCompleteHandler[T: WeakTypeTag]: Option[CaseDef] = None
3333

@@ -51,7 +51,7 @@ trait ExprBuilder {
5151
def nextStates: List[Int] =
5252
List(nextState)
5353

54-
def mkHandlerCaseForState: CaseDef =
54+
def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef =
5555
mkHandlerCase(state, stats :+ mkStateTree(nextState, symLookup))
5656

5757
override val toString: String =
@@ -62,7 +62,7 @@ trait ExprBuilder {
6262
* a branch of an `if` or a `match`.
6363
*/
6464
final class AsyncStateWithoutAwait(var stats: List[Tree], val state: Int, val nextStates: List[Int]) extends AsyncState {
65-
override def mkHandlerCaseForState: CaseDef =
65+
override def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef =
6666
mkHandlerCase(state, stats)
6767

6868
override val toString: String =
@@ -79,39 +79,47 @@ trait ExprBuilder {
7979
def nextStates: List[Int] =
8080
List(nextState)
8181

82-
override def mkHandlerCaseForState: CaseDef = {
83-
val callOnComplete = futureSystemOps.onComplete(Expr(awaitable.expr),
84-
Expr(This(tpnme.EMPTY)), Expr(Ident(name.execContext))).tree
85-
mkHandlerCase(state, stats ++ List(mkStateTree(onCompleteState, symLookup), callOnComplete, Return(literalUnit)))
82+
override def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef = {
83+
val fun = This(tpnme.EMPTY)
84+
val callOnComplete = futureSystemOps.onComplete[Any, Unit](Expr[futureSystem.Fut[Any]](awaitable.expr),
85+
Expr[futureSystem.Tryy[Any] => Unit](fun), Expr[futureSystem.ExecContext](Ident(name.execContext))).tree
86+
val tryGetOrCallOnComplete =
87+
if (futureSystemOps.continueCompletedFutureOnSameThread)
88+
If(futureSystemOps.isCompleted(Expr[futureSystem.Fut[_]](awaitable.expr)).tree,
89+
Block(ifIsFailureTree[T](futureSystemOps.getCompleted[Any](Expr[futureSystem.Fut[Any]](awaitable.expr)).tree) :: Nil, literalUnit),
90+
Block(callOnComplete :: Nil, Return(literalUnit)))
91+
else
92+
Block(callOnComplete :: Nil, Return(literalUnit))
93+
mkHandlerCase(state, stats ++ List(mkStateTree(onCompleteState, symLookup), tryGetOrCallOnComplete))
8694
}
8795

96+
private def tryGetTree(tryReference: => Tree) =
97+
Assign(
98+
Ident(awaitable.resultName),
99+
TypeApply(Select(futureSystemOps.tryyGet[Any](Expr[futureSystem.Tryy[Any]](tryReference)).tree, newTermName("asInstanceOf")), List(TypeTree(awaitable.resultType)))
100+
)
101+
102+
/* if (tr.isFailure)
103+
* result.complete(tr.asInstanceOf[Try[T]])
104+
* else {
105+
* <resultName> = tr.get.asInstanceOf[<resultType>]
106+
* <nextState>
107+
* <mkResumeApply>
108+
* }
109+
*/
110+
def ifIsFailureTree[T: WeakTypeTag](tryReference: => Tree) =
111+
If(futureSystemOps.tryyIsFailure(Expr[futureSystem.Tryy[T]](tryReference)).tree,
112+
Block(futureSystemOps.completeProm[T](
113+
Expr[futureSystem.Prom[T]](symLookup.memberRef(name.result)),
114+
Expr[futureSystem.Tryy[T]](
115+
TypeApply(Select(tryReference, newTermName("asInstanceOf")),
116+
List(TypeTree(futureSystemOps.tryType[T]))))).tree :: Nil,
117+
Return(literalUnit)),
118+
Block(List(tryGetTree(tryReference)), mkStateTree(nextState, symLookup))
119+
)
120+
88121
override def mkOnCompleteHandler[T: WeakTypeTag]: Option[CaseDef] = {
89-
val tryGetTree =
90-
Assign(
91-
Ident(awaitable.resultName),
92-
TypeApply(Select(futureSystemOps.tryyGet[T](Expr[futureSystem.Tryy[T]](Ident(symLookup.applyTrParam))).tree, newTermName("asInstanceOf")), List(TypeTree(awaitable.resultType)))
93-
)
94-
95-
/* if (tr.isFailure)
96-
* result.complete(tr.asInstanceOf[Try[T]])
97-
* else {
98-
* <resultName> = tr.get.asInstanceOf[<resultType>]
99-
* <nextState>
100-
* <mkResumeApply>
101-
* }
102-
*/
103-
val ifIsFailureTree =
104-
If(futureSystemOps.tryyIsFailure(Expr[futureSystem.Tryy[T]](Ident(symLookup.applyTrParam))).tree,
105-
Block(futureSystemOps.completeProm[T](
106-
Expr[futureSystem.Prom[T]](symLookup.memberRef(name.result)),
107-
Expr[futureSystem.Tryy[T]](
108-
TypeApply(Select(Ident(symLookup.applyTrParam), newTermName("asInstanceOf")),
109-
List(TypeTree(futureSystemOps.tryType[T]))))).tree :: Nil,
110-
Return(literalUnit)),
111-
Block(List(tryGetTree), mkStateTree(nextState, symLookup))
112-
)
113-
114-
Some(mkHandlerCase(onCompleteState, List(ifIsFailureTree)))
122+
Some(mkHandlerCase(onCompleteState, List(ifIsFailureTree[T](Ident(symLookup.applyTrParam)))))
115123
}
116124

117125
override val toString: String =
@@ -337,7 +345,7 @@ trait ExprBuilder {
337345
case s :: Nil =>
338346
List(caseForLastState)
339347
case _ =>
340-
val initCases = for (state <- asyncStates.toList.init) yield state.mkHandlerCaseForState
348+
val initCases = for (state <- asyncStates.toList.init) yield state.mkHandlerCaseForState[T]
341349
initCases :+ caseForLastState
342350
}
343351
}

src/main/scala/scala/async/internal/FutureSystem.scala

+15
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ trait FutureSystem {
4949
def onComplete[A, U](future: Expr[Fut[A]], fun: Expr[Tryy[A] => U],
5050
execContext: Expr[ExecContext]): Expr[Unit]
5151

52+
def continueCompletedFutureOnSameThread = false
53+
def isCompleted(future: Expr[Fut[_]]): Expr[Boolean] =
54+
throw new UnsupportedOperationException("isCompleted not supported by this FutureSystem")
55+
def getCompleted[A: WeakTypeTag](future: Expr[Fut[A]]): Expr[Tryy[A]] =
56+
throw new UnsupportedOperationException("getCompleted not supported by this FutureSystem")
57+
5258
/** Complete a promise with a value */
5359
def completeProm[A](prom: Expr[Prom[A]], value: Expr[Tryy[A]]): Expr[Unit]
5460

@@ -103,6 +109,15 @@ object ScalaConcurrentFutureSystem extends FutureSystem {
103109
future.splice.onComplete(fun.splice)(execContext.splice)
104110
}
105111

112+
override def continueCompletedFutureOnSameThread: Boolean = true
113+
114+
override def isCompleted(future: Expr[Fut[_]]): Expr[Boolean] = reify {
115+
future.splice.isCompleted
116+
}
117+
override def getCompleted[A: WeakTypeTag](future: Expr[Fut[A]]): Expr[Tryy[A]] = reify {
118+
future.splice.value.get
119+
}
120+
106121
def completeProm[A](prom: Expr[Prom[A]], value: Expr[scala.util.Try[A]]): Expr[Unit] = reify {
107122
prom.splice.complete(value.splice)
108123
Expr[Unit](Literal(Constant(()))).splice
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package scala.async.run
2+
3+
import org.junit.Test
4+
import scala.async.Async._
5+
import scala.concurrent._
6+
import scala.concurrent.duration._
7+
import ExecutionContext.Implicits._
8+
9+
class SyncOptimizationSpec {
10+
@Test
11+
def awaitOnCompletedFutureRunsOnSameThread: Unit = {
12+
13+
def stackDepth = Thread.currentThread().getStackTrace.size
14+
15+
val future = async {
16+
val thread1 = Thread.currentThread
17+
val stackDepth1 = stackDepth
18+
19+
val f = await(Future.successful(1))
20+
val thread2 = Thread.currentThread
21+
val stackDepth2 = stackDepth
22+
assert(thread1 == thread2)
23+
assert(stackDepth1 == stackDepth2)
24+
}
25+
Await.result(future, 10.seconds)
26+
}
27+
28+
}

src/test/scala/scala/async/run/futures/FutureSpec.scala

-1
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,6 @@ class FutureSpec {
538538
val f = async { await(future(5)) / 0 }
539539
Await.ready(f, defaultTimeout).value.get.toString mustBe expected.toString
540540
}
541-
542541
}
543542

544543

0 commit comments

Comments
 (0)