File tree Expand file tree Collapse file tree 2 files changed +50
-5
lines changed Expand file tree Collapse file tree 2 files changed +50
-5
lines changed Original file line number Diff line number Diff line change @@ -15,6 +15,7 @@ import java.util.Objects
1515
1616import scala .util .{Failure , Success , Try }
1717import scala .concurrent .{ExecutionContext , Future , Promise }
18+ import scala .util .control .NonFatal
1819
1920/** The base class for state machines generated by the `scala.async.Async.async` macro.
2021 * Not intended to be directly extended in user-written code.
@@ -34,12 +35,14 @@ abstract class FutureStateMachine(execContext: ExecutionContext) extends Functio
3435 /** Assign `i` to the state variable */
3536 protected def state_= (s : Int ): Unit = state$async = s
3637
38+ NonFatal // eagerly classloading NonFatal to reduce the chance of a cascading StackOverflowError in `completeFailure`
39+
3740 /** Complete the state machine with the given failure. */
38- // scala-async accidentally started catching NonFatal exceptions in:
39- // https://github.com/scala/scala-async/commit/e3ff0382ae4e015fc69da8335450718951714982#diff-136ab0b6ecaee5d240cd109e2b17ccb2R411
40- // This follows the new behaviour but should we fix the regression?
41- protected def completeFailure ( t : Throwable ) : Unit = {
42- result$async.complete( Failure (t))
41+ protected def completeFailure ( t : Throwable ) : Unit = t match {
42+ case NonFatal (t) =>
43+ result$async.complete( Failure (t))
44+ case _ =>
45+ throw t
4346 }
4447
4548 /** Complete the state machine with the given value. */
Original file line number Diff line number Diff line change 1+ package scala .async
2+
3+ import java .util .concurrent .atomic .AtomicReference
4+
5+ import org .junit .{Assert , Ignore , Test }
6+
7+ import scala .async .Async .{async , await }
8+ import scala .concurrent .{ExecutionContext , Future , _ }
9+ import scala .language .postfixOps
10+ import scala .util .control .NonFatal
11+
12+ class ExceptionalTest {
13+ @ Test
14+ def nonFatalNotCaughtFutureCombinators (): Unit = {
15+ check { implicit ec =>
16+ Future .successful(42 ).map(x => (x, throw fatal))
17+ }
18+ }
19+
20+ @ Test
21+ def nonFatalNotCaughtAsync (): Unit = {
22+ check { implicit ec =>
23+ async {
24+ (await(Future .successful(42 )), throw fatal)
25+ }
26+ }
27+ }
28+
29+ def check (f : ExecutionContext => Future [Any ]): Unit = {
30+ val lastUncaught = new AtomicReference [Throwable ]()
31+ implicit val executor : ExecutionContextExecutor = ExecutionContext .fromExecutor(null , lastUncaught.set(_))
32+ val future = f(executor)
33+ Thread .sleep(100 )
34+ Assert .assertSame(fatal, lastUncaught.get())
35+ }
36+
37+ private val fatal : Throwable = {
38+ val t = new VirtualMachineError () {}
39+ Assert .assertTrue(NonFatal .unapply(t).isEmpty)
40+ t
41+ }
42+ }
You can’t perform that action at this time.
0 commit comments