Package freenet.support.compress

Source Code of freenet.support.compress.DecompressorThreadManager$DecompressorThread

/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.support.compress;

import static java.util.concurrent.TimeUnit.MINUTES;

import freenet.support.LogThresholdCallback;
import freenet.support.TimeUtil;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.List;
import java.util.ArrayDeque;
import java.util.Queue;

import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
import freenet.support.io.Closer;

/** Creates and manages decompressor threads. This class is
* given all decompressors which should be applied to an
* InputStream via addDecompressor. The decompressors will be
* strung together and executed when the execute method is called.
* This class also stores any errors which may arise.
* @author sajack
*/
public class DecompressorThreadManager {

  final Queue<DecompressorThread> threads;
  PipedInputStream input;
  PipedOutputStream output = new PipedOutputStream();
  final long maxLen;
  private boolean finished = false;
  private Throwable error = null;

        private static volatile boolean logMINOR;
  static {
    Logger.registerLogThresholdCallback(new LogThresholdCallback(){
      @Override
      public void shouldUpdate(){
        logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
      }
    });
  }

  /** Creates a new DecompressorThreadManager
   * @param inputStream The stream that will be decompressed, if compressed
   * @param maxLen The maximum number of bytes to extract
   */
  public DecompressorThreadManager(PipedInputStream inputStream, List<? extends Compressor> decompressors, long maxLen) throws IOException {
    threads = new ArrayDeque<DecompressorThread>(decompressors.size());
    this.maxLen = maxLen;
    if(inputStream == null) {
      IOException e = new IOException("Input stream may not be null");
      onFailure(e);
      throw e;
    }
    input = inputStream;
    while(!decompressors.isEmpty()) {
      Compressor compressor = decompressors.remove(decompressors.size()-1);
      if(logMINOR) Logger.minor(this, "Decompressing with "+compressor);
      DecompressorThread thread = new DecompressorThread(compressor, this, input, output, maxLen);
      threads.add(thread);
      input = new PipedInputStream(output);
      output = new PipedOutputStream();
    }
  }

  /** Creates and executes a new thread for each decompressor,
   * chaining the output of the previous to the next.
   * @return An InputStream from which uncompressed data may be read from
   */
  public synchronized PipedInputStream execute() throws Throwable {
    if(error != null) throw error;
    if(threads.isEmpty()) {
      onFinish();
      return input;
    }
    try {
      int count = 0;
      while(!threads.isEmpty()){
        if(getError() != null) throw getError();
        DecompressorThread threadRunnable = threads.remove();
        if(threads.isEmpty()) threadRunnable.setLast();
        Thread t = new Thread(threadRunnable, "DecompressorThread"+count);
        t.start();
        if(logMINOR) Logger.minor(this, "Started decompressor thread "+t);
        count++;
      }
      output.close();
    } catch(Throwable t) {
      onFailure(t);
      throw t;
    } finally {
      Closer.close(output);
    }
    return input;
   
  }

  /** Informs the manager that a nonrecoverable exception has occured in the
   * decompression threads
   * @param e The thrown exception
   */
  public synchronized void onFailure(Throwable t) {
    error = t;
    onFinish();
  }

  /** Marks that the decompression of the stream has finished and wakes
   * threads blocking on completion */
  public synchronized void onFinish() {
    finished = true;
    notifyAll();
  }

  /** Blocks until all threads have finished executing and cleaning up.*/
  public synchronized void waitFinished() throws Throwable {
    long start = System.currentTimeMillis();
    while(!finished) {
      try {
        // FIXME remove the timeout here.
        // Something wierd is happening...
        //wait(0)
        wait(MINUTES.toMillis(20));
        long time = System.currentTimeMillis()-start;
        if(time > MINUTES.toMillis(20))
          Logger.error(this, "Still waiting for decompressor chain after "+TimeUtil.formatTime(time));
      } catch(InterruptedException e) {
        //Do nothing
      }
    }
    if(error != null) throw error;
  }

  /** Returns an exception which was thrown during decompression
   * @return Returns an exception which was caught during the decompression
   */
  public synchronized Throwable getError() {
    return error;
  }

  /**Represents a thread which invokes a decompressor upon an
   * input stream. These threads should be instantiated by a
   * <code>DecompressorThreadManager</code>
   * @author sajack
   */
  class DecompressorThread implements Runnable {

    /**The compressor whose decompress method will be invoked*/
    final Compressor compressor;
    /**The stream compressed data will be read from*/
    private InputStream input;
    /**The stream decompressed data will be written*/
    private OutputStream output;
    /**A upper limit to how much data may be decompressed. This is passed to the decompressor*/
    final long maxLen;
    /**The manager which created the thread*/
    final DecompressorThreadManager manager;
    /**Whether or not this thread should signal the manager that decompression has finished*/
    boolean isLast = false;

    public DecompressorThread(Compressor compressor, DecompressorThreadManager manager, InputStream input, PipedOutputStream output, long maxLen) {
      this.compressor = compressor;
      this.input = new BufferedInputStream(input);
      this.output = new BufferedOutputStream(output);
      this.maxLen = maxLen;
      this.manager = manager;
    }

    /**Begins the decompression */
    @Override
    public void run() {
      if(logMINOR) Logger.minor(this, "Decompressing...");
      try {
        if(manager.getError() == null) {
          compressor.decompress(input, output, maxLen, maxLen * 4);
          input.close();
          output.close();
          // Avoid relatively expensive repeated close on normal completion
          input = null;
          output = null;
          if(isLast) manager.onFinish();
        }
        if(logMINOR) Logger.minor(this, "Finished decompressing...");
      } catch (Exception e) {
        manager.onFailure(e);
      } finally {
        Closer.close(input);
        Closer.close(output);
      }
    }

    /** Should be called before executing the thread when there
     * are no further decompressors pending*/
    public void setLast() {
      isLast = true;
    }
  }
}
TOP

Related Classes of freenet.support.compress.DecompressorThreadManager$DecompressorThread

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.