Advertisement
mitrakov

NonFatal in sync/async code

Aug 19th, 2018
251
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Scala 6.66 KB | None | 0 0
  1. // Let's test NonFatal in sync and async code!
  2. // 1. InterruptedException in try/catch-all pattern:
  3. package ru.mitrakov.self.sandbox
  4.  
  5. import java.time.LocalDateTime
  6. import scala.concurrent.ExecutionContext.Implicits.global
  7. import scala.concurrent.Future
  8. import scala.io.StdIn
  9. import scala.language.postfixOps
  10.  
  11. object Main extends App {
  12.   def print(x: Any): Unit = {Thread.sleep(50); println(s"${LocalDateTime.now()}: $x")}
  13.  
  14.   def dangerousMethod(): Unit = {
  15.     print("Dangerous method started")
  16.     Thread.sleep(3000)
  17.     throw new InterruptedException("Disaster happened!")
  18.     print("Dangerous method finished")
  19.   }
  20.  
  21.   def test(): Unit = {
  22.     print("Test started")
  23.     try {
  24.       dangerousMethod()
  25.     } catch {
  26.       case th: Throwable => print(s"Throwable: $th")
  27.     }
  28.     print("Test finished")
  29.   }
  30.  
  31.   test()
  32.  
  33.   print("Press ENTER to finish")
  34.   StdIn.readLine()
  35. }
  36.         // Output:
  37.         // 2018-08-19T18:51:14.145: Test started
  38.         // 2018-08-19T18:51:14.198: Dangerous method started
  39.         // 2018-08-19T18:51:17.248: Throwable: java.lang.InterruptedException: Disaster happened!
  40.         // 2018-08-19T18:51:17.298: Test finished
  41.         // 2018-08-19T18:51:17.348: Press ENTER to finish
  42.         //
  43.         // Process finished with exit code 0
  44.         // Conclusion: it's OK, Throwable matches all exceptions
  45.  
  46. // 2. InterruptedException in try/catch-nonfatal pattern:
  47. case NonFatal(e) => print(s"Throwable: $e")
  48.         // Output:
  49.         // 2018-08-19T18:52:14.530: Test started
  50.         // 2018-08-19T18:52:14.584: Dangerous method started
  51.         // Exception in thread "main" java.lang.InterruptedException: Disaster happened!
  52.         //  at ru.mitrakov.self.sandbox.Main$.dangerousMethod(Main.scala:25)
  53.         //  at ru.mitrakov.self.sandbox.Main$.test(Main.scala:32)
  54.         //  at ru.mitrakov.self.sandbox.Main$.delayedEndpoint$ru$mitrakov$self$sandbox$Main$1(Main.scala:39)
  55.         //  at ru.mitrakov.self.sandbox.Main$delayedInit$body.apply(Main.scala:12)
  56.         //  at scala.Function0.apply$mcV$sp(Function0.scala:34)
  57.         //  at scala.Function0.apply$mcV$sp$(Function0.scala:34)
  58.         //  at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
  59.         // Conclusion: it's OK, NonFatal doesn't catch fatal errors
  60.  
  61. // 3. Now let's convert method to async:
  62. def dangerousMethod() = Future { ...
  63.  
  64.         // Output (Enter pressed):
  65.         // 2018-08-19T18:53:52.009: Test started
  66.         // 2018-08-19T18:53:52.163: Dangerous method started
  67.         // 2018-08-19T18:53:52.163: Test finished
  68.         // 2018-08-19T18:53:52.213: Press ENTER to finish
  69.         //
  70.         // Process finished with exit code 0
  71.         // Conclusion: it's OK, program had been finished before the error was thrown
  72.        
  73.         // Output (Enter NOT pressed):
  74.         // 2018-08-19T18:54:13.302: Test started
  75.         // 2018-08-19T18:54:13.457: Dangerous method started
  76.         // 2018-08-19T18:54:13.457: Test finished
  77.         // 2018-08-19T18:54:13.507: Press ENTER to finish
  78.         // java.lang.InterruptedException: Disaster happened!
  79.         //  at ru.mitrakov.self.sandbox.Main$.$anonfun$dangerousMethod$1(Main.scala:25)
  80.         //  at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12)
  81.         //  at scala.concurrent.Future$.$anonfun$apply$1(Future.scala:654)
  82.         //  at scala.util.Success.$anonfun$map$1(Try.scala:251)
  83.         //  at scala.util.Success.map(Try.scala:209)
  84.         //  at scala.concurrent.Future.$anonfun$map$1(Future.scala:288)
  85.         // Conclusion: it's interesting: we got stack trace even though we used try/catch instead of recover
  86.        
  87. // 4. OK, let's use recover pattern for async code:
  88. dangerousMethod().recover {
  89.   case NonFatal(e) => print(s"Throwable: $e")
  90. }
  91.  
  92.         // Output:
  93.         // 2018-08-19T19:06:22.646: Test started
  94.         // 2018-08-19T19:06:22.843: Dangerous method started
  95.         // 2018-08-19T19:06:22.845: Test finished
  96.         // 2018-08-19T19:06:22.895: Press ENTER to finish
  97.         // java.lang.InterruptedException: Disaster happened!
  98.         //  at ru.mitrakov.self.sandbox.Main$.$anonfun$dangerousMethod$1(Main.scala:25)
  99.         //  at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12)
  100.         //  at scala.concurrent.Future$.$anonfun$apply$1(Future.scala:654)
  101.         //  at scala.util.Success.$anonfun$map$1(Try.scala:251)
  102.         //  at scala.util.Success.map(Try.scala:209)
  103.         //  at scala.concurrent.Future.$anonfun$map$1(Future.scala:288)
  104.         // Conclusion: it's OK, NonFatal doesn't catch fatal errors
  105.        
  106. // 5. Let's recover ALL exceptions:
  107. dangerousMethod().recover {
  108.   case th: Throwable => print(s"Throwable: $th")
  109. }
  110.  
  111.         // Output:
  112.         // 2018-08-19T18:57:00.851: Test started
  113.         // 2018-08-19T18:57:01.004: Dangerous method started
  114.         // 2018-08-19T18:57:01.005: Test finished
  115.         // 2018-08-19T18:57:01.055: Press ENTER to finish
  116.         // java.lang.InterruptedException: Disaster happened!
  117.         //  at ru.mitrakov.self.sandbox.Main$.$anonfun$dangerousMethod$1(Main.scala:25)
  118.         //  at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12)
  119.         //  at scala.concurrent.Future$.$anonfun$apply$1(Future.scala:654)
  120.         //  at scala.util.Success.$anonfun$map$1(Try.scala:251)
  121.         //  at scala.util.Success.map(Try.scala:209)
  122.         //  at scala.concurrent.Future.$anonfun$map$1(Future.scala:288)
  123.         // Conclusion: WTF?!?!? Throwable doesn't catch fatal errors either!!!
  124.        
  125. // 6. WTF? Maybe recover doesn't work? Let's change an exception type:
  126. throw new RuntimeException("Exception happened!")
  127.  
  128.         // Output:
  129.         // 2018-08-19T18:57:45.040: Test started
  130.         // 2018-08-19T18:57:45.195: Dangerous method started
  131.         // 2018-08-19T18:57:45.196: Test finished
  132.         // 2018-08-19T18:57:45.246: Press ENTER to finish
  133.         // 2018-08-19T18:57:48.258: Throwable: java.lang.RuntimeException: Exception happened!
  134.         // Conclusion: now it's OK, exception was catched
  135.        
  136. // Summary:
  137. // 1) "case th: Throwable" catches ALL exceptions in sync code
  138. // 2) "case th: Throwable" does NOT catch all exceptions in async code
  139. // 3) use "NonFatal" in sync code with try/catch pattern: all fatal errors must be propagated outside
  140. // 4) use "NonFatal" in async code with "recover" pattern too
  141. // 5) no need to recover using "case th: Throwable" => it will NOT catch fatal errors!
  142. // 6) if a fatal error in ASYNC code is thrown in try/catch pattern => it will still be propagated, when the time comes up!
  143. //    but be careful: it will happen "in background", whereas the main execution flow may be anywhere!
  144. // 7) don't throw fatal errors manually: leave them to JVM
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement