/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * BasicAsynchronousFileWriter.java * Created: Jan 28, 2005 * By: Bo Ilic */ package org.openquark.cal.services; import java.io.FileNotFoundException; import java.io.IOException; import java.util.LinkedList; import org.openquark.cal.compiler.CompilerMessage; import org.openquark.cal.compiler.CompilerMessageLogger; import org.openquark.cal.compiler.MessageKind; import org.openquark.cal.machine.AsynchronousFileWriter; import org.openquark.cal.machine.ProgramResourceLocator; import org.openquark.cal.machine.ProgramResourceRepository; /** * Helper class to allow serialization of the files generated by runtime to occur on a separate * thread. * * Currently this is used for class files only, but it works for any type of file. * There is 1 ClassFileWriter object created per CodeGenerator i.e. 2 threads are involved in * generating code for a *list* of modules: 1 for code generation and 1 for serialization. * The thread is started on the first call to addFileToWrite(). * * Synchronized blocks are used to ensure that only one thread can modify or check the state * at once. The class may call wait() while fetching an object from the list and sleep until * either addfileToWrite() or stopAcceptingFiles() is called. These calls will modify the * state and call notify() to wakeup the thread. * * The maximum number of bytes for asynchronous saving can be controlled with the MAX_BYTES_FOR_ASYNCHRONOUS_SAVING constant. * If the BasicAsynchronousFileWriter is using more bytes then this, calling addFileToWrite will block * until it is using less. * * @author Bo Ilic */ final class BasicAsynchronousFileWriter extends AsynchronousFileWriter { /** The list of files to write out to disk */ private final LinkedList<FileData> filesToWrite; /** whether this ClassFileWriter will continue to accept new class files to write */ volatile private boolean acceptFiles; /** * total number of bytes that this AsynchronousFileWriter has outstanding to commit to disk. * This field is designed to limit the amount of memory that the AsynchronousFileWriter can use. */ private long bytesPending; /** * the maximum number of bytes that this AsynchronousFileWriter will allow to have pending for * asynchronous saving. Beyond this, the call to addFilesToWrite will block. */ private static final long MAX_BYTES_FOR_ASYNCHRONOUS_SAVING = 3000000L; //3MB max /** If true, then calculate some statistics for BasicAsynchronousFileWriter and dump to the console. */ private static final boolean DEBUG = false; /** the maximimum number of files held by the AsynchronousFileWriter. Only computed if DEBUG = true. */ private int maxFilesPending; /** the maximum number of bytes actually held by this AsynchronousFileWriter. Only computed if DEBUG = true. */ private long maxBytesPending; /** Whether the file writer thread has been started. */ private boolean started = false; /** The repository for program resources. */ private final ProgramResourceRepository resourceRepository; /** The thread which does the background writing. */ private final WriterThread writerThread; /** Encapsulates the exception that was caught on the background thread. */ private WriteThreadException writeThreadException = null; /** * Encapsulates the exception that was caught on the background thread. * * @author Joseph Wong */ private static final class WriteThreadException extends Exception { private static final long serialVersionUID = 3709770477995247585L; /** The file locator for the offending file. */ private final ProgramResourceLocator.File fileLocator; /** * @param fileLocator the file locator for hte offending file. * @param cause the exception that was caught on the background thread. */ private WriteThreadException(ProgramResourceLocator.File fileLocator, Exception cause) { super(cause); this.fileLocator = fileLocator; } } /** * The thread which does the background writing. * @author Edward Lam */ private final class WriterThread extends Thread { /* (non-Javadoc) * @see java.lang.Runnable#run() */ @Override public void run() { try { while (acceptFiles) { writeFiles(); } //one last time to make sure all files get written out. writeFiles(); } catch (WriteThreadException e) { synchronized (this) { writeThreadException = e; } } if (DEBUG) { System.out.println("AsynchronousFileWriter.maxFilesPending = " + maxFilesPending); System.out.println("AsynchronousFileWriter.maxBytesPending = " + maxBytesPending); } } private void writeFiles() throws WriteThreadException { AsynchronousFileWriter.FileData fileToWrite; while ((fileToWrite = getFileToWrite()) != null) { ProgramResourceLocator.File fileLocator = fileToWrite.getFileLocator(); try { writeFile(fileToWrite, fileLocator); } catch (Exception e) { // we capture the exception and throw a wrapper WriteThreadException so // as to remember the file locator throw new WriteThreadException(fileLocator, e); } } } } BasicAsynchronousFileWriter(ProgramResourceRepository resourceRepository) { this.resourceRepository = resourceRepository; if (resourceRepository == null) { throw new NullPointerException("Argument 'resourceRepository' must not be null."); } this.writerThread = new WriterThread(); acceptFiles = true; filesToWrite = new LinkedList<FileData>(); } /** * The compilation thread must call this method after it finishes with all calls to addFileToWrite. */ @Override public synchronized void stopAcceptingFiles () { acceptFiles = false; notify(); } /** * The compilation thread calls this method to add a file to write. This will return (almost) immediately if * the AsynchronousFileWriter has bytesPending < MAX_BYTES_FOR_ASYNCHRONOUS_SAVING. Otherwise, this method will write the files * on the calling thread until bytesPending < MAX_BYTES_FOR_ASYNCHRONOUS_SAVING and then notify the asynchronous saving thread. * * The file writer thread will be started the first time this is called. * * @param fileData cannot be null. */ @Override public synchronized void addFileToWrite(AsynchronousFileWriter.FileData fileData, CompilerMessageLogger logger) { if (fileData == null) { throw new NullPointerException(); } if (!acceptFiles) { throw new IllegalStateException(); } bytesPending += fileData.getSize(); filesToWrite.add(fileData); if (DEBUG) { if (bytesPending > maxBytesPending) { maxBytesPending = bytesPending; } int nFiles = filesToWrite.size(); if (nFiles > maxFilesPending) { maxFilesPending = nFiles; } } if (!started) { writerThread.start(); started = true; } while (bytesPending > MAX_BYTES_FOR_ASYNCHRONOUS_SAVING) { AsynchronousFileWriter.FileData fileToWrite = filesToWrite.removeFirst(); bytesPending -= fileToWrite.getSize(); ProgramResourceLocator.File fileLocator = fileToWrite.getFileLocator(); try { writeFile(fileToWrite, fileLocator); } catch (FileNotFoundException e) { logger.logMessage( new CompilerMessage( new MessageKind.Fatal.CodeGenerationAbortedDueToInternalCodingError(fileLocator.getModuleName()), e)); } catch (IOException e) { logger.logMessage( new CompilerMessage( new MessageKind.Fatal.CodeGenerationAbortedDueToInternalCodingError(fileLocator.getModuleName()), e)); } } notify(); } private synchronized AsynchronousFileWriter.FileData getFileToWrite() { // Block until a file is available and we're still accepting files. When woken up the code will // continue only if a new object has been added to the list or acceptFiles has been set to false. while (filesToWrite.isEmpty() && acceptFiles) { try { wait(); } catch (InterruptedException e) { // This is unexpected, but won't cause any problems. // We'll just loop around and check the list and acceptFiles flag again. } } // Return the first file if one is available if (filesToWrite.size() > 0) { AsynchronousFileWriter.FileData fileToWrite = filesToWrite.removeFirst(); bytesPending -= fileToWrite.getSize(); return fileToWrite; } return null; } /** * Writes the given file out. * @param fileData the file data. * @param fileLocator the file locator. * @throws IOException */ private void writeFile(AsynchronousFileWriter.FileData fileData, ProgramResourceLocator.File fileLocator) throws IOException { resourceRepository.setContents(fileLocator, fileData.getInputStream()); } /** * Wait for the file writer to meet the following conditions: * - stopAcceptingFiles() has been called * - all files have been written * @throws InterruptedException if the caller is interrupted while waiting. */ @Override public void waitForFilesToBeWritten(CompilerMessageLogger logger) throws InterruptedException { writerThread.join(); // Check to see if the thread had caught an exception. WriteThreadException exceptionFromThread; synchronized (writerThread) { exceptionFromThread = writeThreadException; writeThreadException = null; // once we check it, we can clear it (so that we don't re-log the error) } // If the thread had caught the exception, we log an error about it. if (exceptionFromThread != null) { logger.logMessage( new CompilerMessage( new MessageKind.Fatal.CodeGenerationAbortedDueToInternalCodingError(exceptionFromThread.fileLocator.getModuleName()), (Exception)exceptionFromThread.getCause())); } } }