Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Optimization to log an exception at most once if the same exception i…

…s being

(re-)thrown several times

This used to be an ad-hoc optimization available only in
``ScalaCompilationUnit``.  However, I believe this can be useful in general,
and the optimization belongs to ``EclipseLoggger``.

The former implementation was thread-safe because ``lastCrash`` was always
accessed within the ``PresentationCompilerThread`` (thread-safety was achieved
via thread-confinement). However, it was a weak form of thread-safety, as it
was potentially very easy to break if you didn't understand the thread policy
of the class (which is not documented).  Implementing this optimization in
``EclipseLogger`` forced us to have a stronger form of thread-safety, because
``lastCrash`` can be accessed from any thread.

Documented thread safety guarantees of ``EclipseLogger`` and also described its
synchronization policy for mantainers
  • Loading branch information...
commit 5b3fc47f2ca8503c3e46d50a5f9dcf4023d10d19 1 parent 836bde2
Mirco Dotta authored July 17, 2012
16  org.scala-ide.sdt.core/src/scala/tools/eclipse/javaelements/ScalaCompilationUnit.scala
@@ -41,8 +41,6 @@ trait ScalaCompilationUnit extends Openable with env.ICompilationUnit with Scala
41 41
   val project = ScalaPlugin.plugin.getScalaProject(getJavaProject.getProject)
42 42
 
43 43
   val file : AbstractFile
44  
-  
45  
-  private var lastCrash: Throwable = null
46 44
 
47 45
   def doWithSourceFile(op : (SourceFile, ScalaPresentationCompiler) => Unit) {
48 46
     project.withSourceFile(this)(op)(())
@@ -91,7 +89,7 @@ trait ScalaCompilationUnit extends Openable with env.ICompilationUnit with Scala
91 89
           false
92 90
           
93 91
         case ex => 
94  
-          handleCrash("Compiler crash while building structure for %s".format(file), ex)
  92
+          logger.error("Compiler crash while building structure for %s".format(file), ex)
95 93
           false
96 94
       }
97 95
     }) (false)
@@ -109,14 +107,6 @@ trait ScalaCompilationUnit extends Openable with env.ICompilationUnit with Scala
109 107
     r.set()
110 108
     r
111 109
   }
112  
-
113  
-  /** Log an error at most once for this source file. */
114  
-  private def handleCrash(msg: String, ex: Throwable) {
115  
-    if (lastCrash != ex) {
116  
-      lastCrash = ex
117  
-      eclipseLog.error(msg, ex)
118  
-    }
119  
-  }
120 110
   
121 111
   /** Index this source file, but only if the project has the Scala nature.
122 112
    * 
@@ -130,7 +120,7 @@ trait ScalaCompilationUnit extends Openable with env.ICompilationUnit with Scala
130 120
           new compiler.IndexBuilderTraverser(indexer).traverse(tree)
131 121
         }
132 122
       } catch {
133  
-        case ex: Throwable => handleCrash("Compiler crash during indexing of %s".format(getResource()), ex)
  123
+        case ex: Throwable => logger.error("Compiler crash during indexing of %s".format(getResource()), ex)
134 124
       }
135 125
     }
136 126
   }
@@ -235,7 +225,7 @@ trait ScalaCompilationUnit extends Openable with env.ICompilationUnit with Scala
235 225
           }
236 226
         } catch {
237 227
           case ex =>
238  
-            handleCrash("Exception thrown while creating override indicators for %s".format(sourceFile), ex)
  228
+           logger.error("Exception thrown while creating override indicators for %s".format(sourceFile), ex)
239 229
         }
240 230
       }
241 231
   }
54  org.scala-ide.sdt.core/src/scala/tools/eclipse/logging/EclipseLogger.scala
@@ -5,9 +5,28 @@ import scala.tools.eclipse.ScalaPlugin
5 5
 import scala.tools.eclipse.util.SWTUtils
6 6
 import org.eclipse.core.runtime.{ ILog, IStatus }
7 7
 import scala.util.control.ControlThrowable
  8
+import java.util.concurrent.atomic.AtomicReference
8 9
 
  10
+/** Use the `EclipseLogger` when you want to communicate with the user. Messages logged
  11
+ *  through the `EclipseLogger` are persisted in the Error Log.
  12
+ *
  13
+ *  This class is meant to be thread-safe, but it isn't because of a possible race condition existing 
  14
+ *  in the Eclipse Logger. Indeed, the call to `ScalaPlugin.plugin.getLog()` isn't thread-safe, but 
  15
+ *  there is really nothing we can do about it. The issue is that the `logs` map accessed in 
  16
+ *  [[org.eclipse.core.internal.runtime.InternalPlatform.getLog(bundle)]] is not synchronized.
  17
+ *  
  18
+ *  Mantainers should evolve this class by keeping in mind that the `EclipseLogger` will be 
  19
+ *  accessed by different threads at the same time, so it is important to keep this class lock-free, 
  20
+ *  and thread-safe (or maybe a better wording would be: as much thread-safe as it can be).
  21
+ *  And that is actually the main motivation for using an [[java.util.concurrent.atomic.AtomicReference]] 
  22
+ *  for `lastCrash`. Also, note that declaring `lastCrash` as volatile (instead of using a 
  23
+ *  [[java.util.concurrent.atomic.AtomicReference]]) would have made this class less correct (because 
  24
+ *  volatile fields don't offer atomic operations such as `getAndSet`).
  25
+ */
9 26
 private[logging] object EclipseLogger extends Logger {
10  
-  private final val pluginLogger: ILog = ScalaPlugin.plugin.getLog
  27
+  private val pluginLogger: ILog = ScalaPlugin.plugin.getLog()
  28
+
  29
+  private val lastCrash: AtomicReference[Throwable] = new AtomicReference
11 30
 
12 31
   def debug(message: => Any) {
13 32
     info(message)
@@ -48,23 +67,28 @@ private[logging] object EclipseLogger extends Logger {
48 67
   def fatal(message: => Any, t: Throwable) {
49 68
     error(message, t)
50 69
   }
51  
-  
  70
+
52 71
   private def log(severity: Int, message: => Any, t: Throwable = null) {
53  
-    // Because of a potential deadlock in the Eclipse internals (look at #1000914), the log action need to be executed in the UI thread.
54  
-    def log(status: Status) = 
55  
-      if (ScalaPlugin.plugin.headlessMode) pluginLogger.log(status)
56  
-      else SWTUtils.asyncExec { pluginLogger.log(status) }
57  
-    
58  
-    log(createStatus(severity, message, t))
59  
-    t match {
60  
-      // `ControlThrowable` should never (ever!) be caught by user code. If that happens, generate extra noise. 
61  
-      case ce: ControlThrowable =>
62  
-        log(createStatus(IStatus.ERROR, "Incorrectly logged ControlThrowable: " + ce.getClass.getSimpleName + "(" + ce.getMessage + ")", t))
63  
-      case _ => () // do nothing
  72
+    if (t == null) logInUiThread(severity, message, t)
  73
+    else {
  74
+      // this is an optimization to log the exception at most once if the same exception is being re-thrown several times.
  75
+      val oldValue = lastCrash.getAndSet(t)
  76
+      if (oldValue != t) {
  77
+        logInUiThread(severity, message, t)
  78
+        t match {
  79
+          // `ControlThrowable` should never (ever!) be caught by user code. If that happens, generate extra noise. 
  80
+          case ce: ControlThrowable =>
  81
+            logInUiThread(IStatus.ERROR, "Incorrectly logged ControlThrowable: " + ce.getClass.getSimpleName + "(" + ce.getMessage + ")", t)
  82
+          case _ => () // do nothing
  83
+        }
  84
+      }
64 85
     }
65 86
   }
66 87
 
67  
-  private def createStatus(severity: Int, message: Any, exception: Throwable): Status = {
68  
-    new Status(severity, ScalaPlugin.plugin.getBundle.getSymbolicName, message.toString, exception)
  88
+  // Because of a potential deadlock in the Eclipse internals (look at #1000914), the log action need to be executed in the UI thread.
  89
+  private def logInUiThread(severity: Int, message: Any, exception: Throwable): Unit = {
  90
+    val status = new Status(severity, ScalaPlugin.plugin.getBundle.getSymbolicName, message.toString, exception)
  91
+    if (ScalaPlugin.plugin.headlessMode) pluginLogger.log(status)
  92
+    else SWTUtils.asyncExec { pluginLogger.log(status) }
69 93
   }
70 94
 }

0 notes on commit 5b3fc47

Please sign in to comment.
Something went wrong with that request. Please try again.