/*
* 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()));
}
}
}