diff options
-rw-r--r-- | src/compiler/scala/tools/nsc/ScriptRunner.scala | 10 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/util/Origins.scala | 105 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/util/package.scala | 11 | ||||
-rw-r--r-- | src/library/scala/util/DynamicVariable.scala | 65 | ||||
-rw-r--r-- | test/files/run/origins.check | 6 | ||||
-rw-r--r-- | test/files/run/origins.scala | 21 |
6 files changed, 169 insertions, 49 deletions
diff --git a/src/compiler/scala/tools/nsc/ScriptRunner.scala b/src/compiler/scala/tools/nsc/ScriptRunner.scala index 6f5f2936a8..c432e1c9f0 100644 --- a/src/compiler/scala/tools/nsc/ScriptRunner.scala +++ b/src/compiler/scala/tools/nsc/ScriptRunner.scala @@ -16,7 +16,7 @@ import io.{ Directory, File, Path, PlainFile } import java.net.URL import java.util.jar.{ JarEntry, JarOutputStream } -import util.waitingForThreads +import util.{ waitingForThreads, addShutdownHook } import scala.tools.util.PathResolver import scala.tools.nsc.reporters.{Reporter,ConsoleReporter} @@ -56,14 +56,6 @@ object ScriptRunner { /** Default name to use for the wrapped script */ val defaultScriptMain = "Main" - /** Must be a daemon thread else scripts won't shut down: ticket #3678 */ - private def addShutdownHook(body: => Unit) = - Runtime.getRuntime addShutdownHook { - val t = new Thread { override def run { body } } - t setDaemon true - t - } - /** Pick a main object name from the specified settings */ def scriptMain(settings: Settings) = settings.script.value match { case "" => defaultScriptMain diff --git a/src/compiler/scala/tools/nsc/util/Origins.scala b/src/compiler/scala/tools/nsc/util/Origins.scala new file mode 100644 index 0000000000..a95558272d --- /dev/null +++ b/src/compiler/scala/tools/nsc/util/Origins.scala @@ -0,0 +1,105 @@ +/* NSC -- new scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package util + +/** A debugging class for logging from whence a method is being called. + * Say you wanted to discover who was calling phase_= in SymbolTable. + * You could do this: + * + * {{{ + * private lazy val origins = Origins[SymbolTable]("phase_=") + * // Commented out original enclosed for contrast + * // final def phase_=(p: Phase): Unit = { + * final def phase_=(p: Phase): Unit = origins { + * }}} + * + * And that's it. When the JVM exits it would issue a report something like this: + {{{ + >> Origins scala.tools.nsc.symtab.SymbolTable.phase_= logged 145585 calls from 51 distinguished sources. + + 71114 scala.tools.nsc.symtab.Symbols$Symbol.unsafeTypeParams(Symbols.scala:862) + 16584 scala.tools.nsc.symtab.Symbols$Symbol.rawInfo(Symbols.scala:757) + 15411 scala.tools.nsc.symtab.Symbols$Symbol.unsafeTypeParams(Symbols.scala:869) + 11507 scala.tools.nsc.symtab.Symbols$Symbol.rawInfo(Symbols.scala:770) + 10285 scala.tools.nsc.symtab.Symbols$Symbol.unsafeTypeParams(Symbols.scala:864) + 6860 scala.tools.nsc.transform.SpecializeTypes.specializedTypeVars(SpecializeTypes.scala:304) + ... + }}} + * + */ + +import scala.collection.{ mutable, immutable } +import Origins._ + +abstract class Origins { + type Rep + def newRep(xs: StackSlice): Rep + def repString(rep: Rep): String + def originClass: String + + private var _tag: String = null + def tag: String = _tag + def setTag(tag: String): this.type = { + _tag = tag + this + } + + private val origins = new mutable.HashMap[Rep, Int] withDefaultValue 0 + private def add(xs: Rep) = origins(xs) += 1 + private def total = origins.values.foldLeft(0L)(_ + _) + + // We find the right line by dropping any from around here and any + // from the method's origin class. + private def dropStackElement(cn: String) = + (cn startsWith OriginsName) || (cn startsWith originClass) + + // Create a stack and whittle it down to the interesting part. + private def readStack(): Array[StackTraceElement] = + (new Throwable).getStackTrace dropWhile (el => dropStackElement(el.getClassName)) + + def apply[T](body: => T): T = { + add(newRep(readStack())) + body + } + def clear() = origins.clear() + def show() = { + println("\n>> Origins %s.%s logged %s calls from %s distinguished sources.\n".format(originClass, tag, total, origins.keys.size)) + origins.toList sortBy (-_._2) foreach { + case (k, v) => println("%7s %s".format(v, repString(k))) + } + } + def purge() = { + show() + clear() + } +} + +object Origins { + private type StackSlice = Array[StackTraceElement] + private val OriginsName = classOf[Origins].getName + private val counters = new mutable.HashSet[Origins] + + { + // Console.println("\nOrigins loaded: registering shutdown hook to display results.") + addShutdownHook(counters foreach (_.purge())) + } + + def apply[T: Manifest](tag: String): Origins = apply(tag, manifest[T].erasure) + def apply(tag: String, clazz: Class[_]): Origins = apply(tag, new OneLine(clazz)) + def apply(tag: String, orElse: => Origins): Origins = { + counters find (_.tag == tag) getOrElse { + returning(orElse setTag tag)(counters += _) + } + } + + class OneLine(clazz: Class[_]) extends Origins { + type Rep = StackTraceElement + val originClass = clazz.getName stripSuffix "$" + def newRep(xs: StackSlice): Rep = xs(0) + def repString(rep: Rep) = " " + rep + } +} diff --git a/src/compiler/scala/tools/nsc/util/package.scala b/src/compiler/scala/tools/nsc/util/package.scala index c38b2c5031..427e385aca 100644 --- a/src/compiler/scala/tools/nsc/util/package.scala +++ b/src/compiler/scala/tools/nsc/util/package.scala @@ -11,6 +11,17 @@ package object util { /** Apply a function and return the passed value */ def returning[T](x: T)(f: T => Unit): T = { f(x) ; x } + /** Register a shutdown hook to be run when the jvm exits. + * Marks it as daemon so it doesn't interfere with shutdown, + * but the thread is returned so it can be modified. + */ + def addShutdownHook(body: => Unit) = { + returning(new Thread { override def run { body } }) { t => + t setDaemon true + Runtime.getRuntime addShutdownHook t + } + } + /** All living threads. */ def allThreads(): List[Thread] = { val num = Thread.activeCount() diff --git a/src/library/scala/util/DynamicVariable.scala b/src/library/scala/util/DynamicVariable.scala index 52d1763123..c42c3baaf8 100644 --- a/src/library/scala/util/DynamicVariable.scala +++ b/src/library/scala/util/DynamicVariable.scala @@ -6,45 +6,32 @@ ** |/ ** \* */ - - package scala.util - import java.lang.InheritableThreadLocal -/** <p> - * DynamicVariables provide a binding mechanism where the current - * value is found through <em>dynamic scope</em>, but where - * access to the variable itself is resolved through <em>static - * scope</em>. - * </p> - * <p> - * The current value can be retrieved with the - * <code>value</code> method. New values should be - * pushed using the <code>withValue</code> method. - * Values pushed via <code>withValue</code> only - * stay valid while the <code>withValue</code>'s - * <em>second</em> argument, a parameterless closure, - * executes. When the second argument finishes, - * the variable reverts to the previous value. - * </p> - * <p> - * Usage of <code>withValue</code> looks like this: - * </p> - * <blockquote><pre> +/** DynamicVariables provide a binding mechanism where the current + * value is found through dynamic scope, but where access to the + * variable itself is resolved through static scope. + * + * The current value can be retrieved with the value method. New values + * should be pushed using the withValue method. Values pushed via + * withValue only stay valid while the withValue's second argument, a + * parameterless closure, executes. When the second argument finishes, + * the variable reverts to the previous value. + * + * {{{ * someDynamicVariable.withValue(newValue) { * // ... code called in here that calls value ... * // ... will be given back the newValue ... * } - * </pre></blockquote> - * <p> - * Each thread gets its own stack of bindings. When a - * new thread is created, the DynamicVariable gets a copy - * of the stack of bindings from the parent thread, and - * from then on the bindings for the new thread - * are independent of those for the original thread. - * </p> + * }}} + * + * Each thread gets its own stack of bindings. When a + * new thread is created, the DynamicVariable gets a copy + * of the stack of bindings from the parent thread, and + * from then on the bindings for the new thread + * are independent of those for the original thread. * * @author Lex Spoon * @version 1.1, 2007-5-21 @@ -57,26 +44,24 @@ class DynamicVariable[T](init: T) { /** Retrieve the current value */ def value: T = tl.get.asInstanceOf[T] - /** Set the value of the variable while executing the specified * thunk. * * @param newval The value to which to set the variable * @param thunk The code to evaluate under the new setting */ - def withValue[S](newval: T)(thunk: =>S): S = { + def withValue[S](newval: T)(thunk: => S): S = { val oldval = value - tl.set(newval) + tl set newval - try { thunk } finally { - tl.set(oldval) - } + try thunk + finally tl set oldval } /** Change the currently bound value, discarding the old value. - * Usually <code>withValue()</code> gives better semantics. + * Usually withValue() gives better semantics. */ - def value_=(newval: T) = { tl.set(newval) } + def value_=(newval: T) = tl set newval - override def toString: String = "DynamicVariable(" + value +")" + override def toString: String = "DynamicVariable(" + value + ")" } diff --git a/test/files/run/origins.check b/test/files/run/origins.check new file mode 100644 index 0000000000..c088ea05c2 --- /dev/null +++ b/test/files/run/origins.check @@ -0,0 +1,6 @@ + +>> Origins goxbox.Socks.boop logged 65 calls from 3 distinguished sources. + + 50 Test$$anonfun$f3$1.apply$mcII$sp(origins.scala:16) + 10 Test$$anonfun$f2$1.apply$mcII$sp(origins.scala:15) + 5 Test$$anonfun$f1$1.apply$mcII$sp(origins.scala:14) diff --git a/test/files/run/origins.scala b/test/files/run/origins.scala new file mode 100644 index 0000000000..4c98e7a66c --- /dev/null +++ b/test/files/run/origins.scala @@ -0,0 +1,21 @@ +import scala.tools.nsc.util.Origins + +package goxbox { + object Socks { + val origins = Origins[Socks.type]("boop") + + def boop(x: Int): Int = origins { 5 } + } +} + +object Test { + import goxbox.Socks.boop + + def f1() = 1 to 5 map boop + def f2() = 1 to 10 map boop + def f3() = 1 to 50 map boop + + def main(args: Array[String]): Unit = { + f1() ; f2() ; f3() + } +} |