blob: f00b44ef61d6375835f08bd9566bbb2988cf2f09 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2013 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.compiler;
import java.lang.reflect.InvocationTargetException;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
@SuppressWarnings({"rawtypes", "unchecked"})
public class ReadManager implements Runnable {
ICompilationUnit[] units;
int nextFileToRead;
ICompilationUnit[] filesRead;
char[][] contentsRead;
int readyToReadPosition;
int nextAvailablePosition;
Thread[] readingThreads;
char[] readInProcessMarker = new char[0];
int sleepingThreadCount;
private Throwable caughtException;
static final int START_CUSHION = 5;
public static final int THRESHOLD = 10;
static final int CACHE_SIZE = 15; // do not waste memory by keeping too many files in memory
public ReadManager(ICompilationUnit[] files, int length) {
// start the background threads to read the file's contents
int threadCount = 0;
try {
Class runtime = Class.forName("java.lang.Runtime"); //$NON-NLS-1$
java.lang.reflect.Method m = runtime.getDeclaredMethod("availableProcessors", new Class[0]); //$NON-NLS-1$
if (m != null) {
Integer result = (Integer) m.invoke(Runtime.getRuntime(), (Object[]) null);
threadCount = result.intValue() + 1;
if (threadCount < 2)
threadCount = 0;
else if (threadCount > CACHE_SIZE)
threadCount = CACHE_SIZE;
}
} catch (IllegalAccessException | ClassNotFoundException | SecurityException | NoSuchMethodException | IllegalArgumentException | InvocationTargetException e) { // ignored
}
if (threadCount > 0) {
synchronized (this) {
this.units = new ICompilationUnit[length];
System.arraycopy(files, 0, this.units, 0, length);
this.nextFileToRead = START_CUSHION; // skip some files to reduce the number of times we have to wait
this.filesRead = new ICompilationUnit[CACHE_SIZE];
this.contentsRead = new char[CACHE_SIZE][];
this.readyToReadPosition = 0;
this.nextAvailablePosition = 0;
this.sleepingThreadCount = 0;
this.readingThreads = new Thread[threadCount];
for (int i = threadCount; --i >= 0;) {
this.readingThreads[i] = new Thread(this, "Compiler Source File Reader"); //$NON-NLS-1$
this.readingThreads[i].setDaemon(true);
this.readingThreads[i].start();
}
}
}
}
public char[] getContents(ICompilationUnit unit) throws Error {
Thread[] rThreads = this.readingThreads;
if (rThreads == null || this.units.length == 0) {
if (this.caughtException != null) {
// rethrow the caught exception from the readingThreads in the main compiler thread
if (this.caughtException instanceof Error)
throw (Error) this.caughtException;
throw (RuntimeException) this.caughtException;
}
return unit.getContents();
}
boolean yield = this.sleepingThreadCount == rThreads.length;
char[] result = null;
synchronized (this) {
if (unit == this.filesRead[this.readyToReadPosition]) {
result = this.contentsRead[this.readyToReadPosition];
while (result == this.readInProcessMarker || result == null) {
// let the readingThread know we're waiting
//System.out.print('|');
this.contentsRead[this.readyToReadPosition] = null;
try {
wait(250);
} catch (InterruptedException ignore) { // ignore
}
if (this.caughtException != null) {
// rethrow the caught exception from the readingThreads in the main compiler thread
if (this.caughtException instanceof Error)
throw (Error) this.caughtException;
throw (RuntimeException) this.caughtException;
}
result = this.contentsRead[this.readyToReadPosition];
}
// free spot for next file
this.filesRead[this.readyToReadPosition] = null;
this.contentsRead[this.readyToReadPosition] = null;
if (++this.readyToReadPosition >= this.contentsRead.length)
this.readyToReadPosition = 0;
if (this.sleepingThreadCount > 0) {
//System.out.print('+');
//System.out.print(this.nextFileToRead);
notify();
}
} else {
// must make sure we're reading ahead of the unit
int unitIndex = 0;
for (int l = this.units.length; unitIndex < l; unitIndex++)
if (this.units[unitIndex] == unit) break;
if (unitIndex == this.units.length) {
// attempting to read a unit that was not included in the initial files - should not happen
this.units = new ICompilationUnit[0]; // stop looking for more
} else if (unitIndex >= this.nextFileToRead) {
// start over
//System.out.println(unitIndex + " vs " + this.nextFileToRead);
this.nextFileToRead = unitIndex + START_CUSHION;
this.readyToReadPosition = 0;
this.nextAvailablePosition = 0;
this.filesRead = new ICompilationUnit[CACHE_SIZE];
this.contentsRead = new char[CACHE_SIZE][];
notifyAll();
}
}
}
if (yield)
Thread.yield(); // ensure other threads get a chance
if (result != null)
return result;
//System.out.print('-');
return unit.getContents();
}
@Override
public void run() {
try {
while (this.readingThreads != null && this.nextFileToRead < this.units.length) {
ICompilationUnit unit = null;
int position = -1;
synchronized (this) {
if (this.readingThreads == null) return;
while (this.filesRead[this.nextAvailablePosition] != null) {
this.sleepingThreadCount++;
try {
wait(250); // wait until a spot in contents is available
} catch (InterruptedException e) { // ignore
}
this.sleepingThreadCount--;
if (this.readingThreads == null) return;
}
if (this.nextFileToRead >= this.units.length) return;
unit = this.units[this.nextFileToRead++];
position = this.nextAvailablePosition;
if (++this.nextAvailablePosition >= this.contentsRead.length)
this.nextAvailablePosition = 0;
this.filesRead[position] = unit;
this.contentsRead[position] = this.readInProcessMarker; // mark the spot so we know its being read
}
char[] result = unit.getContents();
synchronized (this) {
if (this.filesRead[position] == unit) {
if (this.contentsRead[position] == null) // wake up main thread which is waiting for this file
notifyAll();
this.contentsRead[position] = result;
}
}
}
} catch (Error | RuntimeException e) {
synchronized (this) {
this.caughtException = e;
shutdown();
}
return;
}
}
public synchronized void shutdown() {
this.readingThreads = null; // mark the read manager as shutting down so that the reading threads stop
notifyAll();
}
}