You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by vg...@apache.org on 2008/02/22 18:23:47 UTC

svn commit: r630256 - in /cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src: changes/ main/java/org/apache/cocoon/components/flow/ main/java/org/apache/cocoon/components/flow/javascript/fom/

Author: vgritsenko
Date: Fri Feb 22 09:23:41 2008
New Revision: 630256

URL: http://svn.apache.org/viewvc?rev=630256&view=rev
Log:
      <action dev="vgritsenko" type="update">
        Reduce thred contention in flowscript interpreter. Do not synchronize
        execution of scripts within current thread scope on global map of
        compiled scripts.
      </action>


Modified:
    cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/changes/changes.xml
    cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/main/java/org/apache/cocoon/components/flow/CompilingInterpreter.java
    cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/main/java/org/apache/cocoon/components/flow/javascript/fom/FOM_Cocoon.java
    cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/main/java/org/apache/cocoon/components/flow/javascript/fom/FOM_JavaScriptInterpreter.java

Modified: cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/changes/changes.xml
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/changes/changes.xml?rev=630256&r1=630255&r2=630256&view=diff
==============================================================================
--- cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/changes/changes.xml (original)
+++ cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/changes/changes.xml Fri Feb 22 09:23:41 2008
@@ -27,6 +27,11 @@
   </properties>
   <body>
     <release version="1.0.0" date="2008-??-??" description="unreleased">
+      <action dev="vgritsenko" type="update">
+        Reduce thred contention in flowscript interpreter. Do not synchronize
+        execution of scripts within current thread scope on global map of
+        compiled scripts.
+      </action>
       <action dev="vgritsenko" type="fix">
         Fix flow scripts reload check logic.
       </action>

Modified: cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/main/java/org/apache/cocoon/components/flow/CompilingInterpreter.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/main/java/org/apache/cocoon/components/flow/CompilingInterpreter.java?rev=630256&r1=630255&r2=630256&view=diff
==============================================================================
--- cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/main/java/org/apache/cocoon/components/flow/CompilingInterpreter.java (original)
+++ cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/main/java/org/apache/cocoon/components/flow/CompilingInterpreter.java Fri Feb 22 09:23:41 2008
@@ -33,8 +33,7 @@
  *
  * @version $Id$
  */
-public abstract class CompilingInterpreter
-        extends AbstractInterpreter {
+public abstract class CompilingInterpreter extends AbstractInterpreter {
 
     /**
      * A source resolver
@@ -44,10 +43,11 @@
     /**
      * Mapping of String objects (source uri's) to ScriptSourceEntry's
      */
-    protected Map compiledScripts = new HashMap();
+    protected final Map compiledScripts = new HashMap();
 
-    /* (non-Javadoc)
-     * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
+    
+    /**
+     * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
      */
     public void service(ServiceManager manager) throws ServiceException {
         super.service(manager);
@@ -58,33 +58,32 @@
      * @see org.apache.avalon.framework.activity.Disposable#dispose()
      */
     public void dispose() {
-        if (this.compiledScripts != null) {
-            Iterator i = this.compiledScripts.values().iterator();
-            while (i.hasNext()) {
-                ScriptSourceEntry current = (ScriptSourceEntry)i.next();
-                this.sourceresolver.release(current.getSource());
-            }
-            this.compiledScripts = null;
+        for (Iterator i = this.compiledScripts.values().iterator(); i.hasNext(); ) {
+            ScriptSourceEntry entry = (ScriptSourceEntry) i.next();
+            this.sourceresolver.release(entry.getSource());
         }
+        this.compiledScripts.clear();
+
         if (this.manager != null) {
             this.manager.release(this.sourceresolver);
             this.sourceresolver = null;
         }
+
         super.dispose();
     }
 
     /**
      * TODO - This is a little bit strange, the interpreter calls
-     * get Script on the ScriptSourceEntry which in turn
-     * calls compileScript on the interpreter. I think we
-     * need more refactoring here.
+     * {@link ScriptSourceEntry#compile} which in turn calls this
+     * compileScript mthod on the interpreter. I think we need
+     * more refactoring here.
      */
     protected abstract Script compileScript(Context context,
                                             Scriptable scope,
                                             Source source) throws Exception;
 
     protected class ScriptSourceEntry {
-        final private Source source;
+        private final Source source;
         private Script script;
         private long compileTime;
 
@@ -92,31 +91,29 @@
             this.source = source;
         }
 
-        public ScriptSourceEntry(Source source, Script script, long t) {
-            this.source = source;
-            this.script = script;
-            this.compileTime = t;
-        }
-
         public Source getSource() {
             return source;
         }
 
+        public Script getScript() {
+            return script;
+        }
+
         public long getCompileTime() {
             return compileTime;
         }
 
-        public Script getScript(Context context, Scriptable scope,
-                                boolean refresh, CompilingInterpreter interpreter)
+        public void compile(Context context, Scriptable scope)
         throws Exception {
-            if (refresh) {
+            // If not first compile() call, refresh the source.
+            if (script != null) {
                 source.refresh();
             }
-            if (script == null || (refresh && compileTime < source.getLastModified())) {
-                script = interpreter.compileScript(context, scope, source);
+            // Compile script if this is first compile() call or source was modified.
+            if (script == null || compileTime < source.getLastModified()) {
+                script = CompilingInterpreter.this.compileScript(context, scope, source);
                 compileTime = source.getLastModified();
             }
-            return script;
         }
     }
 }

Modified: cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/main/java/org/apache/cocoon/components/flow/javascript/fom/FOM_Cocoon.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/main/java/org/apache/cocoon/components/flow/javascript/fom/FOM_Cocoon.java?rev=630256&r1=630255&r2=630256&view=diff
==============================================================================
--- cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/main/java/org/apache/cocoon/components/flow/javascript/fom/FOM_Cocoon.java (original)
+++ cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/main/java/org/apache/cocoon/components/flow/javascript/fom/FOM_Cocoon.java Fri Feb 22 09:23:41 2008
@@ -341,13 +341,13 @@
      * @return an <code>Object</code> value
      * @exception JavaScriptException if an error occurs
      */
-    public Object jsFunction_load( String filename )
-        throws Exception {
+    public Object jsFunction_load(String filename) throws Exception {
         org.mozilla.javascript.Context cx =
-            org.mozilla.javascript.Context.getCurrentContext();
+                org.mozilla.javascript.Context.getCurrentContext();
         Scriptable scope = getParentScope();
+
         Script script = getInterpreter().compileScript(cx, filename);
-        return script.exec( cx, scope );
+        return script.exec(cx, scope);
     }
 
     /**

Modified: cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/main/java/org/apache/cocoon/components/flow/javascript/fom/FOM_JavaScriptInterpreter.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/main/java/org/apache/cocoon/components/flow/javascript/fom/FOM_JavaScriptInterpreter.java?rev=630256&r1=630255&r2=630256&view=diff
==============================================================================
--- cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/main/java/org/apache/cocoon/components/flow/javascript/fom/FOM_JavaScriptInterpreter.java (original)
+++ cocoon/trunk/blocks/cocoon-flowscript/cocoon-flowscript-impl/src/main/java/org/apache/cocoon/components/flow/javascript/fom/FOM_JavaScriptInterpreter.java Fri Feb 22 09:23:41 2008
@@ -25,15 +25,21 @@
 import java.io.PushbackInputStream;
 import java.io.Reader;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-
 import javax.servlet.http.HttpSession;
 
 import org.apache.avalon.framework.activity.Initializable;
 import org.apache.avalon.framework.configuration.Configuration;
 import org.apache.avalon.framework.configuration.ConfigurationException;
-import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.commons.jxpath.JXPathIntrospector;
+import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
+import org.apache.excalibur.source.Source;
+import org.apache.regexp.RE;
+import org.apache.regexp.RECompiler;
+import org.apache.regexp.REProgram;
+
 import org.apache.cocoon.ResourceNotFoundException;
 import org.apache.cocoon.components.flow.CompilingInterpreter;
 import org.apache.cocoon.components.flow.Interpreter;
@@ -47,12 +53,7 @@
 import org.apache.cocoon.environment.ObjectModelHelper;
 import org.apache.cocoon.environment.Redirector;
 import org.apache.cocoon.environment.Request;
-import org.apache.commons.jxpath.JXPathIntrospector;
-import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
-import org.apache.excalibur.source.Source;
-import org.apache.regexp.RE;
-import org.apache.regexp.RECompiler;
-import org.apache.regexp.REProgram;
+
 import org.mozilla.javascript.Context;
 import org.mozilla.javascript.EcmaError;
 import org.mozilla.javascript.Function;
@@ -98,7 +99,8 @@
 
     /**
      * When was the last time we checked for script modifications. Used
-     * only if {@link #reloadScripts} is true.
+     * only if {@link #reloadScripts} is true. Access is synchronized by
+     * {@link #compiledScripts}.
      */
     private long lastReloadCheckTime;
 
@@ -115,14 +117,7 @@
 
     private boolean enableDebugger;
 
-    /**
-     * Needed to get things working with JDK 1.3. Can be removed once we
-     * don't support that platform any more.
-     */
-    protected ServiceManager getServiceManager() {
-        return manager;
-    }
-
+    
     /**
      * JavaScript debugger: there's only one of these: it can debug multiple
      * threads executing JS code.
@@ -150,6 +145,7 @@
         return debugger;
     }
 
+
     public void configure(Configuration config) throws ConfigurationException {
         super.configure(config);
 
@@ -184,7 +180,6 @@
             FOM_Cocoon.init(scope);
         } catch (Exception e) {
             Context.exit();
-            e.printStackTrace();
             throw e;
         }
     }
@@ -247,14 +242,16 @@
     }
 
     public static class ThreadScope extends ScriptableObject {
-        private static final String[] BUILTIN_PACKAGES = {"javax", "org", "com"};
+        private static final String[] BUILTIN_PACKAGES = { "javax", "org", "com" };
+        private static final String[] BUILTIN_FUNCTIONS = { "importClass" };
 
         private ClassLoader classLoader;
 
-        /* true if this scope has assigned any global vars */
+        /** true if this scope has assigned any global vars */
         boolean useSession;
 
-        boolean locked = false;
+        /** true if this scope is locked for implicit variable declarations */
+        boolean locked;
 
         /**
          * Initializes new top-level scope.
@@ -262,8 +259,7 @@
         public ThreadScope(Global scope) throws Exception {
             final Context context = Context.getCurrentContext();
 
-            final String[] names = { "importClass" };
-            defineFunctionProperties(names,
+            defineFunctionProperties(BUILTIN_FUNCTIONS,
                                      ThreadScope.class,
                                      ScriptableObject.DONTENUM);
 
@@ -318,7 +314,7 @@
             super.put(index, start, value);
         }
 
-        // Invoked after script execution
+        /** Invoked after script execution */
         void onExec() {
             this.useSession = false;
             super.put(LAST_EXEC_TIME, this, new Long(System.currentTimeMillis()));
@@ -369,7 +365,7 @@
     }
 
     /**
-     * Returns a new Scriptable object to be used as the global scope
+     * Sets up a ThreadScope object to be used as the global scope
      * when running the JavaScript scripts in the context of a request.
      *
      * <p>If you want to maintain the state of global variables across
@@ -385,80 +381,96 @@
     private void setupContext(Redirector redirector, Context context,
                               ThreadScope thrScope)
     throws Exception {
-        // Try to retrieve the scope object from the session instance. If
-        // no scope is found, we create a new one, but don't place it in
-        // the session.
-        //
-        // When a user script "creates" a session using
-        // cocoon.createSession() in JavaScript, the thrScope is placed in
-        // the session object, where it's later retrieved from here. This
-        // behaviour allows multiple JavaScript functions to share the
-        // same global scope.
-
-        FOM_Cocoon cocoon = (FOM_Cocoon) thrScope.get("cocoon", thrScope);
-        long lastExecTime = ((Long) thrScope.get(LAST_EXEC_TIME,
-                                                 thrScope)).longValue();
-        boolean needsRefresh = false;
-        if (reloadScripts) {
-            long now = System.currentTimeMillis();
-            if (now >= lastReloadCheckTime + checkTime) {
-                needsRefresh = true;
-                lastReloadCheckTime = now;
-            }
-        }
 
         // We need to setup the FOM_Cocoon object according to the current
         // request. Everything else remains the same.
         ClassLoader contextClassloader = Thread.currentThread().getContextClassLoader();
         thrScope.setupPackages(contextClassloader);
+
+        FOM_Cocoon cocoon = (FOM_Cocoon) thrScope.get("cocoon", thrScope);
         cocoon.pushCallContext(this, redirector, avalonContext, null);
 
-        // Check if we need to compile and/or execute scripts
+        // Time when scripts were last executed, in this thread scope
+        final long lastExecuted = ((Long) thrScope.get(LAST_EXEC_TIME,
+                                                       thrScope)).longValue();
+
+        // List of scripts (ScriptSourceEntry objects) which might have to be
+        // executed in this thread scope.
+        List execList = new ArrayList();
+
+        // Check if we need to (re)compile any of the scripts
         synchronized (compiledScripts) {
-            List execList = new ArrayList();
-            // If we've never executed scripts in this scope or
-            // if reload-scripts is true and the check interval has expired
-            // or if new scripts have been specified in the sitemap,
-            // then create a list of scripts to compile/execute
-            if (lastExecTime == 0 || needsRefresh || needResolve.size() > 0) {
-                topLevelScripts.addAll(needResolve);
-                if (lastExecTime != 0 && !needsRefresh) {
-                    execList.addAll(needResolve);
-                } else {
-                    execList.addAll(topLevelScripts);
+            // Determine if refresh is needed.
+            boolean needsRefresh = false;
+            if (reloadScripts) {
+                long now = System.currentTimeMillis();
+                if (now >= lastReloadCheckTime + checkTime) {
+                    needsRefresh = true;
+                    lastReloadCheckTime = now;
                 }
+            }
+
+            // List of script URIs to resolve
+            List resolveList = new ArrayList();
+            // If reloadScripts is true, recompile all top level scripts
+            if (needsRefresh) {
+                resolveList.addAll(topLevelScripts);
+            }
+            // If new scripts has been specified in sitemap, load and compile them
+            if (needResolve.size() > 0) {
+                topLevelScripts.addAll(needResolve);
+                resolveList.addAll(needResolve);
                 needResolve.clear();
             }
+
             // Compile all the scripts first. That way you can set breakpoints
             // in the debugger before they execute.
-            for (int i = 0, size = execList.size(); i < size; i++) {
-                String sourceURI = (String)execList.get(i);
+            for (int i = 0, size = resolveList.size(); i < size; i++) {
+                String sourceURI = (String) resolveList.get(i);
                 ScriptSourceEntry entry =
-                    (ScriptSourceEntry)compiledScripts.get(sourceURI);
+                        (ScriptSourceEntry) compiledScripts.get(sourceURI);
                 if (entry == null) {
                     Source src = this.sourceresolver.resolveURI(sourceURI);
                     entry = new ScriptSourceEntry(src);
                     compiledScripts.put(sourceURI, entry);
                 }
-                // Compile the script if necessary
-                entry.getScript(context, this.scope, needsRefresh, this);
-            }
-            // Execute the scripts if necessary
-            for (int i = 0, size = execList.size(); i < size; i++) {
-                String sourceURI = (String) execList.get(i);
-                ScriptSourceEntry entry =
-                    (ScriptSourceEntry) compiledScripts.get(sourceURI);
-                long lastMod = 0;
-                if (reloadScripts && lastExecTime != 0) {
-                    lastMod = entry.getSource().getLastModified();
+                entry.compile(context, this.scope);
+                // If top level scripts were executed in this thread scope,
+                // collect only newly added scripts for execution.
+                if (lastExecuted != 0) {
+                    execList.add(entry);
                 }
-                Script script = entry.getScript(context, this.scope, false, this);
-                if (lastExecTime == 0 || lastMod > lastExecTime) {
-                    script.exec(context, thrScope);
-                    thrScope.onExec();
+            }
+
+            // If scripts were never executed in this thread scope yet,
+            // then collect all top level scripts for execution.
+            if (lastExecuted == 0) {
+                for (int i = 0, size = topLevelScripts.size(); i < size; i++) {
+                    String sourceURI = (String) resolveList.get(i);
+                    ScriptSourceEntry entry =
+                            (ScriptSourceEntry) compiledScripts.get(sourceURI);
+                    if (entry != null) {
+                        execList.add(entry);
+                    }
                 }
             }
         }
+
+        // Execute the scripts identified above, as necessary
+        boolean executed = false;
+        for (int i = 0, size = execList.size(); i < size; i++) {
+            ScriptSourceEntry entry = (ScriptSourceEntry) execList.get(i);
+            if (lastExecuted == 0 || entry.getCompileTime() > lastExecuted) {
+                entry.getScript().exec(context, thrScope);
+                executed = true;
+            }
+        }
+
+        // If any of the scripts has been executed, inform ThreadScope,
+        // which will update last execution timestamp.
+        if (executed) {
+            thrScope.onExec();
+        }
     }
 
     /**
@@ -470,24 +482,23 @@
      */
     Script compileScript(Context cx, String fileName) throws Exception {
         Source src = this.sourceresolver.resolveURI(fileName);
-        if (src != null) {
-            synchronized (compiledScripts) {
-                ScriptSourceEntry entry =
-                    (ScriptSourceEntry)compiledScripts.get(src.getURI());
-                Script compiledScript;
-                if (entry == null) {
-                    compiledScripts.put(src.getURI(),
-                            entry = new ScriptSourceEntry(src));
-                } else {
-                    this.sourceresolver.release(src);
-                }
-                boolean needsRefresh = reloadScripts &&
-                       (entry.getCompileTime() + checkTime < System.currentTimeMillis());
-                compiledScript = entry.getScript(cx, this.scope, needsRefresh, this);
-                return compiledScript;
+        synchronized (compiledScripts) {
+            ScriptSourceEntry entry =
+                    (ScriptSourceEntry) compiledScripts.get(src.getURI());
+            if (entry == null) {
+                compiledScripts.put(src.getURI(),
+                                    entry = new ScriptSourceEntry(src));
+            } else {
+                this.sourceresolver.release(src);
+            }
+
+            long compileTime = entry.getCompileTime();
+            if (compileTime == 0 || reloadScripts && (compileTime + checkTime < System.currentTimeMillis())) {
+                entry.compile(cx, this.scope);
             }
+
+            return entry.getScript();
         }
-        throw new ResourceNotFoundException(fileName + ": not found");
     }
 
     protected Script compileScript(Context cx, Scriptable scope, Source src)
@@ -497,9 +508,7 @@
             String encoding = findEncoding(is);
             Reader reader = encoding == null ? new InputStreamReader(is) : new InputStreamReader(is, encoding);
             reader = new BufferedReader(reader);
-            Script compiledScript = cx.compileReader(reader,
-                                                     src.getURI(), 1, null);
-            return compiledScript;
+            return cx.compileReader(reader, src.getURI(), 1, null);
         } finally {
             is.close();
         }
@@ -509,7 +518,7 @@
     // (see http://www.iana.org/assignments/character-sets). So reading 100 bytes should be more than enough.
     private final static int ENCODING_BUF_SIZE = 100;
     // Match 'encoding = xxxx' on the first line
-    REProgram encodingRE = new RECompiler().compile("^.*encoding\\s*=\\s*([^\\s]*)");
+    private final static REProgram encodingRE = new RECompiler().compile("encoding\\s*=\\s*([^\\s]*)");
 
     /**
      * Find the encoding of the stream, or null if not specified
@@ -555,7 +564,17 @@
             context.setDebugger(locationTracker, null);
         }
 
+        // Try to retrieve the scope object from the session instance. If
+        // no scope is found, we create a new one, but don't place it in
+        // the session.
+        //
+        // When a user script "creates" a session using
+        // cocoon.createSession() in JavaScript, the thrScope is placed in
+        // the session object, where it's later retrieved from here. This
+        // behaviour allows multiple JavaScript functions to share the
+        // same global scope.
         ThreadScope thrScope = getSessionScope();
+
         synchronized (thrScope) {
             ClassLoader savedClassLoader =
                 Thread.currentThread().getContextClassLoader();
@@ -569,8 +588,8 @@
                     FOM_JavaScriptFlowHelper.setFOM_FlowScope(cocoon.getObjectModel(), thrScope);
 
                     if (enableDebugger) {
+                        // only raise the debugger window if it isn't already visible
                         if (!getDebugger().isVisible()) {
-                            // only raise the debugger window if it isn't already visible
                             getDebugger().setVisible(true);
                         }
                     }
@@ -643,8 +662,9 @@
         // Obtain the continuation object from it, and setup the
         // FOM_Cocoon object associated in the dynamic scope of the saved
         // continuation with the environment and context objects.
-        Continuation k = (Continuation)wk.getContinuation();
-        ThreadScope kScope = (ThreadScope)k.getParentScope();
+        Continuation k = (Continuation) wk.getContinuation();
+        ThreadScope kScope = (ThreadScope) k.getParentScope();
+
         synchronized (kScope) {
             ClassLoader savedClassLoader =
                 Thread.currentThread().getContextClassLoader();