
uk.ac.starlink.table.ProgressRowSplittable Maven / Gradle / Ivy
package uk.ac.starlink.table;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.LongSupplier;
/**
* RowSplittable wrapper that can manage progress reporting,
* as well as force termination of iteration.
*
* Note that it is important to close instances of this splittable
* for correct progress reporting.
*
* @author Mark Taylor
* @since 12 Oct 2020
*/
public class ProgressRowSplittable implements RowSplittable {
private final RowSplittable base_;
private final Tracker tracker_;
private final int trackBlock_;
private long itrack_;
/**
* Public constructor.
*
* @param base splittable which this one is to monitor
* @param target object to which progress events are reported
*/
public ProgressRowSplittable( RowSplittable base, Target target ) {
this( base, new Tracker( target ) );
}
/**
* Constructor used internally.
*
* @param base splittable which this one is to monitor
* @param tracker internal object to which progress events are reported
*/
private ProgressRowSplittable( RowSplittable base, Tracker tracker ) {
base_ = base;
tracker_ = tracker;
trackBlock_ = 10_000;
tracker_.addWorker( this );
}
public boolean next() throws IOException {
if ( base_.next() ) {
trackNext();
return true;
}
else {
return false;
}
}
public ProgressRowSplittable split() {
RowSplittable split0 = base_.split();
return split0 == null ? null
: new ProgressRowSplittable( split0, tracker_ );
}
public Object getCell( int icol ) throws IOException {
return base_.getCell( icol );
}
public Object[] getRow() throws IOException {
return base_.getRow();
}
public LongSupplier rowIndex() {
return base_.rowIndex();
}
public long splittableSize() {
return base_.splittableSize();
}
public void close() throws IOException {
if ( itrack_ > 0 ) {
try {
tracker_.addCount( itrack_ );
}
catch ( IOException e ) {
// swallow this exception, which probably indicates interruption
}
itrack_ = 0;
}
tracker_.removeWorker( this );
base_.close();
}
/**
* Records an iteration for potential reporting to the tracker.
* Such reports are only made after a significant number of iterations,
* for reasons of efficiency.
*/
private void trackNext() throws IOException {
if ( ++itrack_ >= trackBlock_ ) {
tracker_.addCount( itrack_ );
itrack_ = 0;
if ( Thread.interrupted() ) {
throw new IOException( "Sequence interrupted" );
}
}
}
/**
* Callback interface for objects that will be informed about iteration
* progress, and also given the opportunity to terminate iteration.
* Implementations should be thread-safe; there is no
* guarantee about what threads they may be used from.
*/
public interface Target {
/**
* Reports an updated figure for the progress.
*
*
Implementations may throw an IOException from this method;
* the exception will be thrown during iteration from the
* {@link ProgressRowSplittable#next next} method of this
* ProgressRowSplittable
and thus halt its iteration.
*
* @param count number of iterations so far
* @throws IOException to interrupt execution
*/
void updateCount( long count ) throws IOException;
/**
* Reports that progress has finished.
*
* @param count final number of iterations
*/
void done( long count );
}
/**
* Object used internally to keep track of progress.
*/
private static class Tracker {
private final Target target_;
private final Set workers_;
private final AtomicLong count_;
/**
* Constructor.
*
* @param target user-supplied messaging callback
*/
Tracker( Target target ) {
target_ = target;
workers_ = new HashSet();
count_ = new AtomicLong();
}
/**
* Reports an increment in the number of rows processed.
*
* @param inc number of newly processed rows
*/
void addCount( long inc ) throws IOException {
target_.updateCount( count_.addAndGet( inc ) );
}
/**
* Registers a new splittable that will report to this tracker.
*
* @param worker new worker
*/
void addWorker( ProgressRowSplittable worker ) {
synchronized ( workers_ ) {
workers_.add( worker );
}
}
/**
* Unregisters an existing splittable that will no longer report
* to this tracker.
*
* @param worker worker to remove
*/
void removeWorker( ProgressRowSplittable worker ) {
boolean isFinal;
synchronized ( workers_ ) {
isFinal = workers_.remove( worker ) && workers_.isEmpty();
}
if ( isFinal ) {
target_.done( count_.longValue() );
}
}
}
}