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