/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.appjet.oui;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import scala.collection.mutable.{HashSet, SynchronizedSet};
import java.util.concurrent.ConcurrentHashMap;
import net.appjet.bodylock.{BodyLock, JSCompileException};
object ScopeReuseManager {
// reset handling.
// val filesToWatch = new ConcurrentHashMap[CachedFile, Unit];
// def watch(libs: DiskLibrary*) {
// for(lib <- libs) {
// filesToWatch.put(lib,());
// }
// }
val t = new java.util.TimerTask {
def run() {
try {
// val t1 = System.currentTimeMillis;
// var doReset = false;
// val libIter = filesToWatch.keySet.iterator;
// while (libIter.hasNext) {
// if (libIter.next.hasBeenModified) {
// doReset = true;
// }
// }
// val t2 = System.currentTimeMillis;
// val elapsedMs = (t2 -t1).toInt;
// if (elapsedMs >= 500) {
// eventlog(Map(
// "type" -> "event",
// "event" -> "scopereusefilewatcher-slowtask",
// "elapsedMs" -> elapsedMs
// ));
// }
if (FileCache.testFiles()) {
reset();
}
} catch {
case e => e.printStackTrace();
}
}
}
val timerPeriod = if (! config.devMode) 5000 else 500;
val timer = new java.util.Timer(true);
timer.schedule(t, timerPeriod, timerPeriod);
// scope handling
val mainLib = new VariableDiskLibrary("main.js");
val preambleLib = new FixedDiskLibrary(new SpecialJarOrNotFile(config.ajstdlibHome, "preamble.js"));
val postambleLib = new FixedDiskLibrary(new SpecialJarOrNotFile(config.ajstdlibHome, "postamble.js"));
def mainExecutable = mainLib.executable;
def preambleExecutable = preambleLib.executable;
def postambleExecutable = postambleLib.executable;
val mainGlobalScope = BodyLock.newScope;
val nextId = new AtomicLong(0);
val freeRunners = new ConcurrentLinkedQueue[Runner]();
var lastReset = new AtomicLong(0);
val resetLock = new ReentrantReadWriteLock(true);
def readLocked[E](block: => E): E = {
resetLock.readLock().lock();
try {
block;
} finally {
resetLock.readLock().unlock();
}
}
def writeLocked[E](block: => E): E = {
resetLock.writeLock().lock();
try {
block;
} finally {
resetLock.writeLock().unlock();
}
}
case class Runner(val globalScope: org.mozilla.javascript.Scriptable) {
var count = 0;
val created = timekeeper.time;
val id = nextId.incrementAndGet();
val mainScope = BodyLock.subScope(globalScope);
var reuseOk = true;
var trace: Option[Array[StackTraceElement]] = None;
override def finalize() {
trace.foreach(t => eventlog(Map(
"type" -> "error",
"error" -> "unreleased runner",
"runnerId" -> id,
"trace" -> t.mkString("\n"))));
super.finalize();
}
val attributes = new scala.collection.mutable.HashMap[String, Object];
}
def newRunner = {
// watch(mainLib, preambleLib, postambleLib);
val startTime = System.currentTimeMillis();
val scope = BodyLock.subScope(mainGlobalScope);
val r = Runner(scope);
ExecutionContextUtils.withContext(ExecutionContext(null, null, r)) {
// scope.put("_appjetcontext_", scope, );
preambleExecutable.execute(scope);
mainExecutable.execute(r.mainScope);
postambleExecutable.execute(scope);
val endTime = System.currentTimeMillis();
eventlog(Map(
"type" -> "event",
"event" -> "runner-created",
"latency" -> (endTime - startTime).toString(),
"runnerId" -> r.id));
}
r;
}
def getRunner = readLocked {
val runner = freeRunners.poll();
if (runner == null) {
newRunner;
} else {
if (config.devMode) {
runner.trace = Some(Thread.currentThread().getStackTrace());
}
runner;
}
}
def getEmpty(block: Runner => Unit): Runner = readLocked {
// watch(preambleLib, postambleLib);
val scope = BodyLock.newScope;
val r = Runner(scope);
// scope.put("_appjetcontext_", scope, ExecutionContext(null, null, r));
ExecutionContextUtils.withContext(ExecutionContext(null, null, r)) {
preambleExecutable.execute(scope);
block(r);
postambleExecutable.execute(scope);
}
r;
}
def getEmpty: Runner = getEmpty(r => {});
def freeRunner(r: Runner) {
r.trace = None;
if (r.reuseOk && r.created > lastReset.get()) {
freeRunners.offer(r);
} else {
if (r.reuseOk) {
eventlog(Map(
"type" -> "event",
"event" -> "runner-discarded",
"runnerId" -> r.id));
} else {
eventlog(Map(
"type" -> "event",
"event" -> "runner-retired",
"runnerId" -> r.id));
}
}
}
lazy val resetExecutable = (new FixedDiskLibrary(new SpecialJarOrNotFile(config.ajstdlibHome, "onreset.js"))).executable;
def runOnReset() {
execution.runOutOfBand(resetExecutable, "Reset", None, { error =>
error match {
case e: JSCompileException => { }
case e: Throwable => { exceptionlog(e); }
case (sc: Int, msg: String) => { exceptionlog("Reset failed: "+msg); }
case x => exceptionlog("Reset failed: "+String.valueOf(x));
}
});
}
def reset() = writeLocked {
eventlog(Map(
"type" -> "event",
"event" -> "files-reset"));
// filesToWatch.clear();
lastReset.set(timekeeper.time);
freeRunners.clear();
runOnReset();
}
eventlog(Map(
"type" -> "event",
"event" -> "server-restart"));
}