/*
* Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
* All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package javaFlacEncoder;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.Map;
import java.util.HashMap;
import java.util.Collections;
import java.util.concurrent.locks.ReentrantLock;
import java.lang.Thread.UncaughtExceptionHandler;
/**
* BlockThreadManager is used by FLACEncoder(when encoding with threads), to
* dispatch BlockEncodeRequests to ThreadFrames which do the actual encode.
* @author Preston Lacey
*/
public class BlockThreadManager implements Runnable,UncaughtExceptionHandler {
/* queue: These are requests that are actively being processed */
LinkedBlockingQueue<Thread> activeFrames = null;
/* inactiveFrames: FrameThreads which may be used to encode(not busy yet) */
LinkedBlockingQueue<FrameThread> inactiveFrames = null;
/* unassignedEncodeRequests: Requests waiting to be assigned a thread */
LinkedBlockingQueue<BlockEncodeRequest> unassignedEncodeRequests = null;
/* frameThreadMap: Keep track of which thread is handling which frame */
Map<Thread, FrameThread> frameThreadMap = null;
/* Thread which watches for finished encodes and alerts FLACEncoder of their
* finished state. This thread will die when there is no data to encode, but
* should remain valid and unchanged so long as blocks are encoding or
* queued. It may therefore be used to monitor/interrupt, an encode process.
*/
volatile Thread managerThread = null;
/* This lock must be locked whenever there is data remaining to encode;
* nothing may exist in activeFrames or unassignedEncodeRequests if unlocked
*/
ReentrantLock encodingLock = null;
volatile FLACEncoder encoder = null;
/**
* Constructor. Must supply a valid FLACEncoder object which will be alerted
* when a block is finished encoding.
* @param encoder FLACEncoder to use in encoding process.
*/
public BlockThreadManager(FLACEncoder encoder) {
this.encoder = encoder;
activeFrames = new LinkedBlockingQueue<Thread>();
inactiveFrames = new LinkedBlockingQueue<FrameThread>();
unassignedEncodeRequests = new LinkedBlockingQueue<BlockEncodeRequest>();
frameThreadMap = Collections.synchronizedMap(new HashMap<Thread, FrameThread>());
managerThread = null;
encodingLock = new ReentrantLock();
}
synchronized public int getTotalManagedCount() {
return activeFrames.size()+inactiveFrames.size()+
unassignedEncodeRequests.size();
}
/**
* This function is used to help control flow of blockEncodeRequests into
* this manager. It will block so long as their is at least as many
* unprocessed blocks waiting to be encoded as the value given.
* @param count
*/
public void blockWhileQueueExceeds(int count) {
int waitingCount = unassignedEncodeRequests.size();
while(waitingCount > count) {
Thread temp = activeFrames.peek();
try {
temp.join();
waitingCount = unassignedEncodeRequests.size();
}
catch(InterruptedException e) {
}
}
}
/**
* Add a Frame to this manager, which it will use to encode a block. Each
* Frame added allows one more thread to be used for encoding. At least one
* Frame must be added for this manager to encode.
* @param frame Frame to use for encoding.
* @return boolean false if there was an error adding the frame, true
* otherwise.
*/
synchronized public boolean addFrameThread(Frame frame) {
FrameThread ft = new FrameThread(frame);
boolean r = true;
try {
inactiveFrames.put(ft);
if (managerThread == null)
restartManager();
}catch(InterruptedException e) {
r = false;
}
return r;
}
/**
* Assign a request to an inactive FrameThread, and start encoding.
* @return boolean true if new thread was started, false otherwise. False
* will occur if no frames or requests are available.
*/
synchronized private boolean assignRequest() {
boolean result = false;
BlockEncodeRequest request = unassignedEncodeRequests.peek();
FrameThread frame = inactiveFrames.peek();
if (request != null && frame != null) {
request = unassignedEncodeRequests.poll();
frame = inactiveFrames.poll();
frame.prepareToEncodeFrame(request);
Thread thread = new Thread(frame);
thread.setUncaughtExceptionHandler(this);
frameThreadMap.put(thread, frame);
try {
activeFrames.put(thread);
thread.start();
result = true;
}catch(InterruptedException e) {
System.err.println("assignRequest: Error! Interrupted");
}
}
return result;
}
/**
* Attempt to restart the managerThread if possible/needed. This is the only
* function that should *ever* call managerThread.start().
*/
synchronized private void restartManager() {
//System.err.println("restartManager: inactiveFramesCount : "+inactiveFrames.size());
if(managerThread == null && (unassignedEncodeRequests.size() > 0 ||
activeFrames.size() > 0 ) ) {
managerThread = new Thread(this);
managerThread.start();
//System.err.println("New Manager Thread #: "+managerThread.getName());
}
else {
}
}
/**
* Add a BlockEncodeRequest to the manager. This will immediately attempt
* to assign a request to an encoding thread(which may not occur if no
* threads are currently available)
* @param ber Block request to encode
* @return boolean true if block added, false if an error occured.
*/
synchronized public boolean addRequest(BlockEncodeRequest ber) {
//add request to the manager(requests are automatically removed when complete)
//if manager is dead, hire new manager
boolean r = true;
try {
unassignedEncodeRequests.put(ber);
assignRequest();
if(managerThread == null)
restartManager();
}catch(InterruptedException e) {
r = false;
}
return r;
}
/**
* Manager's run method. Repeatedly joins on the oldest encode thread, sends
* results to the FLACEncoder object, starts a new thread(if possible), and
* waits again. Dies when no more encode requests are active, or able to be
* started. This should *only* be started by the restartManager() method.
*/
public void run() {
//GET_TOP: We'll join on the top thread in FIFO
// if FIFO is empty, destroy managerThread
//if (frame is valid)
//Write to output
//dump request and thread
//add framethread back to inactive queue.
//attempt to start a new frame
//jump to GET_TOP
//encodingLock.lock();
//System.err.println("Locking encodeLock");
Thread topThread = null;
synchronized(this) {
topThread = activeFrames.poll();
//FrameThread readyFrame = inactiveFrames.peek();
// while(assignRequest());
}
while(topThread != null) {
try {
//topThread.start();
//System.err.println("Joining: "+topThread.getName());
topThread.join();
synchronized (this) {
FrameThread frame = frameThreadMap.get(topThread);
frameThreadMap.remove(topThread);
//TELL ENCODER FRAME IS DONE!
BlockEncodeRequest ber = frame.ber;
// finalizer.add(ber);
frame.ber = null;
inactiveFrames.put(frame);
assignRequest();
//encoder.blockFinished(frame.ber);
encoder.blockFinished(ber);
//frame.ber = null;
/*inactiveFrames.put(frame);
assignRequest();*/
topThread = activeFrames.poll();
if(topThread == null)
managerThread = null;
}
}
catch(InterruptedException e) {
//silently try again, pretend nothing happened on join.
System.err.println("thread interrupted: ");
//topThread = null;
}
finally {
}
}
synchronized(this) {
managerThread = null;
//System.err.println("Unlocking encodeLock");
//encodingLock.unlock();
restartManager();
}
}
/**
* Get the current encoding manager thread. This thread should stay active
* so long as there are available Frame's to encode with, and availabe
* requests to encode.
* @return Thread used by this manager
*/
synchronized public Thread getEncodingThread() {
return managerThread;
}
/**
* WOn't likely keep this method
* @param t
* @param e
*/
public void uncaughtException(Thread t, Throwable e) {
System.err.println("Exception in thread: "+t.toString());
e.printStackTrace();
}
}