// This software is released into the Public Domain. See copying.txt for details.
package org.openstreetmap.osmosis.pbf2.v0_6.impl;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.openstreetmap.osmosis.core.OsmosisRuntimeException;
import org.openstreetmap.osmosis.core.container.v0_6.EntityContainer;
import org.openstreetmap.osmosis.core.task.v0_6.Sink;
/**
* Decodes all blocks from a PBF stream using worker threads, and passes the
* results to the downstream sink.
*
* @author Brett Henderson
*/
public class PbfDecoder implements Runnable {
private PbfStreamSplitter streamSplitter;
private ExecutorService executorService;
private int maxPendingBlobs;
private Sink sink;
private Lock lock;
private Condition dataWaitCondition;
private Queue<PbfBlobResult> blobResults;
/**
* Creates a new instance.
*
* @param streamSplitter
* The PBF stream splitter providing the source of blobs to be
* decoded.
* @param executorService
* The executor service managing the thread pool.
* @param maxPendingBlobs
* The maximum number of blobs to have in progress at any point
* in time.
* @param sink
* The sink to send all decoded entities to.
*/
public PbfDecoder(PbfStreamSplitter streamSplitter, ExecutorService executorService, int maxPendingBlobs,
Sink sink) {
this.streamSplitter = streamSplitter;
this.executorService = executorService;
this.maxPendingBlobs = maxPendingBlobs;
this.sink = sink;
// Create the thread synchronisation primitives.
lock = new ReentrantLock();
dataWaitCondition = lock.newCondition();
// Create the queue of blobs being decoded.
blobResults = new LinkedList<PbfBlobResult>();
}
/**
* Any thread can call this method when they wish to wait until an update
* has been performed by another thread.
*/
private void waitForUpdate() {
try {
dataWaitCondition.await();
} catch (InterruptedException e) {
throw new OsmosisRuntimeException("Thread was interrupted.", e);
}
}
/**
* Any thread can call this method when they wish to signal another thread
* that an update has occurred.
*/
private void signalUpdate() {
dataWaitCondition.signal();
}
private void sendResultsToSink(int targetQueueSize) {
while (blobResults.size() > targetQueueSize) {
// Get the next result from the queue and wait for it to complete.
PbfBlobResult blobResult = blobResults.remove();
while (!blobResult.isComplete()) {
// The thread hasn't finished processing yet so wait for an
// update from another thread before checking again.
waitForUpdate();
}
if (!blobResult.isSuccess()) {
throw new OsmosisRuntimeException("A PBF decoding worker thread failed, aborting.");
}
// Send the processed entities to the sink. We can release the lock
// for the duration of processing to allow worker threads to post
// their results.
lock.unlock();
try {
for (EntityContainer entity : blobResult.getEntities()) {
sink.process(entity);
}
} finally {
lock.lock();
}
}
}
private void processBlobs() {
// Process until the PBF stream is exhausted.
while (streamSplitter.hasNext()) {
// Obtain the next raw blob from the PBF stream.
PbfRawBlob rawBlob = streamSplitter.next();
// Create the result object to capture the results of the decoded
// blob and add it to the blob results queue.
final PbfBlobResult blobResult = new PbfBlobResult();
blobResults.add(blobResult);
// Create the listener object that will update the blob results
// based on an event fired by the blob decoder.
PbfBlobDecoderListener decoderListener = new PbfBlobDecoderListener() {
@Override
public void error() {
lock.lock();
try {
blobResult.storeFailureResult();
signalUpdate();
} finally {
lock.unlock();
}
}
@Override
public void complete(List<EntityContainer> decodedEntities) {
lock.lock();
try {
blobResult.storeSuccessResult(decodedEntities);
signalUpdate();
} finally {
lock.unlock();
}
}
};
// Create the blob decoder itself and execute it on a worker thread.
PbfBlobDecoder blobDecoder = new PbfBlobDecoder(rawBlob.getType(), rawBlob.getData(), decoderListener);
executorService.execute(blobDecoder);
// If the number of pending blobs has reached capacity we must begin
// sending results to the sink. This method will block until blob
// decoding is complete.
sendResultsToSink(maxPendingBlobs - 1);
}
// There are no more entities available in the PBF stream, so send all remaining data to the sink.
sendResultsToSink(0);
}
@Override
public void run() {
lock.lock();
try {
processBlobs();
} finally {
lock.unlock();
}
}
}