spoon.processing.AbstractParallelProcessor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spoon-core Show documentation
Show all versions of spoon-core Show documentation
Spoon is a tool for meta-programming, analysis and transformation of Java programs.
/*
* SPDX-License-Identifier: (MIT OR CECILL-C)
*
* Copyright (C) 2006-2023 INRIA and contributors
*
* Spoon is available either under the terms of the MIT License (see LICENSE-MIT.txt) or the Cecill-C License (see LICENSE-CECILL-C.txt). You as the user are entitled to choose the terms under which to adopt Spoon.
*/
package spoon.processing;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import java.util.stream.StreamSupport;
import spoon.SpoonException;
import spoon.reflect.declaration.CtElement;
/**
* AbstractParallelProcessor allows using multiple threads for concurrent
* processing with {@link AbstractProcessor}.
*
* This class should only be used if all processors do the same.
* Otherwise the result may vary from the expected result. All processors
* must synchronize shared fields like Collections by themselves. Multiple
* constructors exist for different approaches creating this. You can create
* this processor with either a Iterable of processors or a Consumer.
*
* For creating and managing threads a {@link Executors#newFixedThreadPool()} is
* used. Creating more threads then cores can harm the performance. Using a
* different thread pool could increase the performance, but this class should
* be general usage. If you need better performance you may want to use an own
* class with different parallel approach.
*/
public abstract class AbstractParallelProcessor extends AbstractProcessor {
private ExecutorService service;
private ArrayBlockingQueue> processorQueue;
// Maps each processor to its last submitted job to be able to wait for all processors to finish
private final Map, Future>> lastSubmittedJobs;
/**
* Creates a new AbstractParallelProcessor from given iterable. The iterable is
* fully consumed. Giving an endless iterable of processors will result in
* errors. The processors must follow the guidelines given in the class
* description.
*
* @param processors iterable of processors.
* @throws IllegalArgumentException if size of iterable is less than 1.
*
*/
public AbstractParallelProcessor(Iterable> processors) {
// added cast because constructors need int
int processorNumber = (int) StreamSupport.stream(processors.spliterator(), false).count();
processorQueue = new ArrayBlockingQueue<>(processorNumber);
processors.forEach(processorQueue::add);
service = Executors.newFixedThreadPool(processorNumber);
lastSubmittedJobs = new IdentityHashMap<>();
}
/**
* Creates a new AbstractParallelProcessor from given iterable. The processors
* must follow the guidelines given in the class description.
*
* @param processors iterable of processors.
* @param numberOfProcessors number consumed from the iterable added to the
* active processors.
* @throws SpoonException if iterable has less values then
* numberOfProcessors.
* @throws IllegalArgumentException if numberOfProcessors is less than 1.
*
*/
public AbstractParallelProcessor(Iterable> processors, int numberOfProcessors) {
processorQueue = new ArrayBlockingQueue<>(numberOfProcessors);
service = Executors.newFixedThreadPool(numberOfProcessors);
Iterator> it = processors.iterator();
for (int i = 0; i < numberOfProcessors; i++) {
if (!it.hasNext()) {
throw new SpoonException("not enough elements provided, iterable is already empty");
}
processorQueue.add(it.next());
}
lastSubmittedJobs = new IdentityHashMap<>();
}
/**
* Creates a new AbstractParallelProcessor from given consumer. The processors
* must follow the guidelines given in the class description.
*
* @param processFunction Represents an operation that accepts a single
* element E and returns no result.
* @param numberOfProcessors number of concurrent running processors.
* @throws IllegalArgumentException if numberOfProcessors is less than 1.
*/
public AbstractParallelProcessor(Consumer processFunction, int numberOfProcessors) {
processorQueue = new ArrayBlockingQueue<>(numberOfProcessors);
for (int i = 0; i < numberOfProcessors; i++) {
processorQueue.add(new AbstractProcessor() {
@Override
public void process(E element) {
processFunction.accept(element);
}
});
}
service = Executors.newFixedThreadPool(numberOfProcessors);
lastSubmittedJobs = new IdentityHashMap<>();
}
@Override
public final void process(E element) {
try {
Processor currentProcessor = processorQueue.take();
Future> job = service.submit(() -> {
try {
currentProcessor.process(element);
processorQueue.put(currentProcessor);
} catch (InterruptedException e) {
// because rethrow is not possible here.
Thread.currentThread().interrupt();
e.printStackTrace();
processorQueue.add(currentProcessor);
} catch (Exception e) {
// allows throwing exception, but keeping the processor in the queue
processorQueue.add(currentProcessor);
throw e;
}
});
lastSubmittedJobs.put(currentProcessor, job);
} catch (InterruptedException e) {
// because rethrow is not possible here.
awaitJobCompletion();
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
/**
* Cleans the threadpool after processing.
*/
@Override
public void processingDone() {
// await termination of the latest jobs
awaitJobCompletion();
service.shutdown();
super.processingDone();
}
private void awaitJobCompletion() {
for (Future> job : lastSubmittedJobs.values()) {
try {
job.get();
} catch (InterruptedException | ExecutionException e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
throw new SpoonException("failed to wait for parallel processor to finish", e);
}
}
}
}