You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by st...@locus.apache.org on 2000/08/18 00:34:14 UTC

cvs commit: xml-cocoon/src/org/apache/cocoon Engine.java

stefano     00/08/17 15:34:13

  Modified:    src/org/apache/cocoon Engine.java
  Log:
  added concurrency control
  
  Revision  Changes    Path
  1.33      +239 -121  xml-cocoon/src/org/apache/cocoon/Engine.java
  
  Index: Engine.java
  ===================================================================
  RCS file: /home/cvs/xml-cocoon/src/org/apache/cocoon/Engine.java,v
  retrieving revision 1.32
  retrieving revision 1.33
  diff -u -r1.32 -r1.33
  --- Engine.java	2000/06/22 19:08:28	1.32
  +++ Engine.java	2000/08/17 22:34:13	1.33
  @@ -1,4 +1,4 @@
  -/*-- $Id: Engine.java,v 1.32 2000/06/22 19:08:28 stefano Exp $ --
  +/*-- $Id: Engine.java,v 1.33 2000/08/17 22:34:13 stefano Exp $ --
   
    ============================================================================
                      The Apache Software License, Version 1.1
  @@ -48,6 +48,7 @@
    Software Foundation, please see <http://www.apache.org/>.
   
    */
  + 
   package org.apache.cocoon;
   
   import java.io.*;
  @@ -73,11 +74,13 @@
    * This class implements the engine that does all the document processing.
    *
    * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
  - * @version $Revision: 1.32 $ $Date: 2000/06/22 19:08:28 $
  + * @version $Revision: 1.33 $ $Date: 2000/08/17 22:34:13 $
    */
   
   public class Engine implements Defaults {
   
  +    private Block blocker = new Block();
  +    
       private static Hashtable engineInstances = new Hashtable(1, 0.90f);
   
       Configurations configurations;
  @@ -261,129 +264,164 @@
           }
   
           Page page = null;
  -
  -        // ask if the cache contains the page requested and if it's
  -        // a valid instance (no changeable points have changed)
  -        if (CACHE) page = cache.getPage(request);
  -
  -        // the page was not found in the cache or the cache was
  -        // disabled, we need to process it
  -        if (page == null) {
  -
  -            if (LOG) logger.log(this, "Creating page", Logger.DEBUG);
  -
  -            // continue until the page is done.
  -            for (int i = 0; i < LOOPS; i++) {
  -                // catch if any OutOfMemoryError is thrown
  -                try {
  -                    // create the Page wrapper
  -                    page = new Page();
  -
  -                    // get the document producer
  -                    Producer producer = producers.getProducer(request);
  -
  -                    // set the producer as a page changeable point
  -                    page.setChangeable(producer);
  -
  -                    // pass the produced stream to the parser
  -                    Document document = producer.getDocument(request);
  -
  -                    if (LOG) logger.log(this, "Document produced", Logger.DEBUG);
  -
  -                    // pass needed parameters to the processor pipeline
  -                    Hashtable environment = new Hashtable();
  -                    environment.put("path", producer.getPath(request));
  -                    environment.put("browser", browsers.map(agent));
  -                    environment.put("request", request);
  -                    environment.put("response", response);
  -                    if (servletContext != null) environment.put("context", servletContext);
  -
  -                    // process the document through the document processors
  -                    while (true) {
  -                        Processor processor = processors.getProcessor(document);
  -                        if (processor == null) break;
  -                        document = processor.process(document, environment);
  -                        page.setChangeable(processor);
  -                        if (LOG) logger.log(this, "Document processed", Logger.DEBUG);
  +        
  +        boolean lock = false;
  +        
  +        String encodedRequest = Utils.encode( request );
  +        
  +        try {
  +            if ( CACHE ) {
  +                // ask if the cache contains the page requested and if it's
  +                // a valid instance (no changeable points have changed)
  +                page = cache.getPage(request);
  +
  +                // if the page isn't in cache block any furhter access to this page
  +                // until it get's put into cache
  +                if ( page == null ) {
  +                    
  +                    // lock this page while we build it and put it in cache
  +                    lock = this.blocker.lock( encodedRequest );
  +                    
  +                    if ( ! lock ) {
  +                        // we were blocked so by another thread processing this page
  +                        // so maybe it's in cache now
  +                        page = cache.getPage(request);
                       }
  -
  -                    // get the right formatter for the page
  -                    Formatter formatter = formatters.getFormatter(document);
  -
  -                    // FIXME: I know it sucks to encapsulate a nice stream into
  -                    // a long String to push it into the cache. In the future,
  -                    // we'll find a smarter way to do it.
  -
  -                    // format the page
  -                    StringWriter writer = new StringWriter();
  -                    formatter.format(document, writer, environment);
  -
  -                    if (LOG) logger.log(this, "Document formatted", Logger.DEBUG);
  -
  -                    // fill the page bean with content
  -                    page.setContent(writer.toString());
  -
  -                    // set content type together with encoding if appropriate
  -                    String encoding = formatter.getEncoding();
  -                    if (encoding != null) {
  -                        page.setContentType(formatter.getMIMEType() + "; charset=" + encoding);
  -                    } else {
  -                        page.setContentType(formatter.getMIMEType());
  -                    }                    
  -
  -                    // page is done without memory errors so exit the loop
  -                    break;
  -                } catch (OutOfMemoryError e) {
  -                    if (LOG) logger.log(this, "Triggered OutOfMemory", Logger.WARNING);
  -                    // force the cache to free some of its content.
  -                    cache.flush();
  -                    // reset the page to signal the error
  -                    page = null;
                   }
               }
  -        }
  -
  -        if (page == null) {
  -            if (LOG) logger.log(this, "System is out of memory", Logger.EMERGENCY);
  -            throw new Exception("FATAL ERROR: the system ran out of memory when"
  -                + " processing the request. Increase your JVM memory.");
  -        }
  -
  -        if (DEBUG) {
  -            // send the debug message and restore the streams
  -            Frontend.print(response, "Debugging " + request.getRequestURI(), debugStream.toString());
  -            System.setOut(System.out);
  -            System.setErr(System.err);
  -        } else {
  -            // set the response content type
  -            response.setContentType(page.getContentType());
  -
  -            // get the output writer
  -            PrintWriter out = response.getWriter();
  -
  -            // send the page
  -            out.println(page.getContent());
  -
  -            // if verbose mode is on the the output type allows it
  -			         // and the HTTP request isn't a HEAD
  -            // print some processing info as a comment
  -            if (VERBOSE && (page.isText()) && !"HEAD".equals(request.getMethod())) {
  -                time = System.currentTimeMillis() - time;
  -                out.println("<!-- This page was served "
  -                    + (page.isCached() ? "from cache " : "")
  -                    + "in " + time + " milliseconds by "
  -                    + Cocoon.version() + " -->");
  -                //out.println("<!-- free memory: " + Runtime.getRuntime().freeMemory() + " -->");
  +  
  +            // the page was not found in the cache or the cache was
  +            // disabled, we need to process it
  +            
  +            
  +            if (page == null) {
  +                
  +                synchronized( this ) {
  +    
  +                    if (LOG) logger.log(this, "Creating page", Logger.DEBUG);
  +        
  +                    // continue until the page is done.
  +                    for (int i = 0; i < LOOPS; i++) {
  +                        // catch if any OutOfMemoryError is thrown
  +                        try {
  +                            // create the Page wrapper
  +                            page = new Page();
  +        
  +                            // get the document producer
  +                            Producer producer = producers.getProducer(request);
  +        
  +                            // set the producer as a page changeable point
  +                            page.setChangeable(producer);
  +        
  +                            // pass the produced stream to the parser
  +                            Document document = producer.getDocument(request);
  +        
  +                            if (LOG) logger.log(this, "Document produced", Logger.DEBUG);
  +        
  +                            // pass needed parameters to the processor pipeline
  +                            Hashtable environment = new Hashtable();
  +                            environment.put("path", producer.getPath(request));
  +                            environment.put("browser", browsers.map(agent));
  +                            environment.put("request", request);
  +                            environment.put("response", response);
  +                            if (servletContext != null) environment.put("context", servletContext);
  +        
  +                            // process the document through the document processors
  +                            while (true) {
  +                                Processor processor = processors.getProcessor(document);
  +                                if (processor == null) break;
  +                                document = processor.process(document, environment);
  +                                page.setChangeable(processor);
  +                                if (LOG) logger.log(this, "Document processed", Logger.DEBUG);
  +                            }
  +        
  +                            // get the right formatter for the page
  +                            Formatter formatter = formatters.getFormatter(document);
  +        
  +                            // FIXME: I know it sucks to encapsulate a nice stream into
  +                            // a long String to push it into the cache. In the future,
  +                            // we'll find a smarter way to do it.
  +        
  +                            // format the page
  +                            StringWriter writer = new StringWriter();
  +                            formatter.format(document, writer, environment);
  +        
  +                            if (LOG) logger.log(this, "Document formatted", Logger.DEBUG);
  +        
  +                            // fill the page bean with content
  +                            page.setContent(writer.toString());
  +        
  +                            // set content type together with encoding if appropriate
  +                            String encoding = formatter.getEncoding();
  +                            if (encoding != null) {
  +                                page.setContentType(formatter.getMIMEType() + "; charset=" + encoding);
  +                            } else {
  +                                page.setContentType(formatter.getMIMEType());
  +                            }                    
  +        
  +                            // page is done without memory errors so exit the loop
  +                            break;
  +                        } catch (OutOfMemoryError e) {
  +                            if (LOG) logger.log(this, "Triggered OutOfMemory", Logger.WARNING);
  +                            // force the cache to free some of its content.
  +                            cache.flush();
  +                            // reset the page to signal the error
  +                            page = null;
  +                        }
  +                    }
  +                }
               }
  -
  -            // send all content so that client doesn't wait while caching.
  -            out.flush();
  +    
  +            if (page == null) {
  +                if (LOG) logger.log(this, "System is out of memory", Logger.EMERGENCY);
  +                throw new Exception("FATAL ERROR: the system ran out of memory when"
  +                    + " processing the request. Increase your JVM memory.");
  +            }
  +    
  +            if (DEBUG) {
  +                // send the debug message and restore the streams
  +                Frontend.print(response, "Debugging " + request.getRequestURI(), debugStream.toString());
  +                System.setOut(System.out);
  +                System.setErr(System.err);
  +            } else {
  +                // set the response content type
  +                response.setContentType(page.getContentType());
  +    
  +                // get the output writer
  +                PrintWriter out = response.getWriter();
  +    
  +                // send the page
  +                out.println(page.getContent());
  +    
  +                // if verbose mode is on the the output type allows it
  +                         // and the HTTP request isn't a HEAD
  +                // print some processing info as a comment
  +                if (VERBOSE && (page.isText()) && !"HEAD".equals(request.getMethod())) {
  +                    time = System.currentTimeMillis() - time;
  +                    out.println("<!-- This page was served "
  +                        + (page.isCached() ? "from cache " : "")
  +                        + "in " + time + " milliseconds by "
  +                        + Cocoon.version() + " -->");
  +                    //out.println("<!-- free memory: " + Runtime.getRuntime().freeMemory() + " -->");
  +                }
  +    
  +                // send all content so that client doesn't wait while caching.
  +                out.flush();
  +            }
  +    
  +            if (LOG) logger.log(this, "response sent to client", Logger.WARNING);
  +    
  +            // cache the created page.
  +            cache.setPage(page, request);
  +        
  +        } finally {
  +            // if there is a lock make sure it is released,
  +            // otherwise this page could never be served
  +            if ( lock ) {
  +                this.blocker.unlock( encodedRequest );
  +            }
           }
  -
  -        if (LOG) logger.log(this, "response sent to client", Logger.WARNING);
  -
  -        // cache the created page.
  -        cache.setPage(page, request);
  +        
       }
   
       /**
  @@ -418,4 +456,84 @@
           }
           return table;
       }
  -}
  +    
  +    /**
  +     * LAF: August 2, 2000
  +     *
  +     * Problem: For large pages that require huge amounts of time and CPU usage
  +     * if the page is not in cache and multiple requests are made for that page
  +     * all requests will attempt to build the page thereby puting undue stress on
  +     * the system.
  +     *
  +     * Solution: If caching is on block requests to build a page if another request
  +     * is currently building that same page, i.e. subsequent requests wait until the
  +     * page gets put into cache.  Also, if the first attempt to build the page fails
  +     * to put the page in cache allow the next queued request to build the page.
  +     * See PersistantCache and PersistantStore for further enhancments to large pages.
  +     */
  +    private class Block
  +    {
  +        // holds the locked objects
  +        private Hashtable blocks = new Hashtable();
  +    
  +        
  +        
  +        /**
  +         * Checks if a key is being blocked, and if so blocks this request until notified.
  +         * If the key is not locked then it becomes locked, so any subsequent calls will block.
  +         *
  +         * @param key - The object to be locked.
  +         * @return boolean - ture = a lock was obtained, false = the caller was blocked.
  +         */
  +        public boolean lock( Object key )
  +        {
  +            boolean locked = false;
  +            
  +            if ( ! this.blocks.containsKey(key) ) {
  +                // flag the key as locked
  +                System.err.println( "Locking: " + key );
  +                this.blocks.put( key, key );
  +                locked = true;
  +                
  +            } else {
  +                // block the call
  +                System.err.println( "Blocking: " + key );
  +
  +                try {
  +                    // synchronization is required to be an owner of this objects monitor
  +                    synchronized( this ) {
  +                        // wait until the block is released by the blocking thread
  +                        while( this.blocks.containsKey(key) ) {
  +                            wait();
  +                        }
  +                    }
  +                } catch( InterruptedException ie ) {
  +                    System.err.println( "Wait for '" + key + "' was interrupted" );
  +                }
  +            }
  +            
  +            System.err.println( "Total locks for all pages: " + this.blocks.size() );
  +            return locked;
  +            
  +        } // lock(Object)
  +        
  +        
  +        /**
  +         * @param key - The object who's lock is to be removed, all waiting threads are notified.
  +         */
  +        public void unlock( Object key )
  +        {
  +            // notify all waiting threads if there was a lock on this key
  +            if ( this.blocks.remove(key) != null ) {
  +                System.err.println( "Releasing lock on: " + key );
  +
  +                // synchronization is required to be an owner of this objects monitor
  +                synchronized( this ) {
  +                    notifyAll();
  +                }
  +            }
  +        } // unlock(Object)
  +        
  +    } // inner class Block
  +
  +} // class Engine