/*
* Copyright 2007 Sun Microsystems, Inc.
*
* This file is part of jVoiceBridge.
*
* jVoiceBridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation and distributed hereunder
* to you.
*
* jVoiceBridge 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Sun designates this particular file as subject to the "Classpath"
* exception as provided by Sun in the License file that accompanied this
* code.
*/
package com.sun.voip.server;
import com.sun.voip.Logger;
import com.sun.voip.RtpPacket;
import com.sun.voip.Ticker;
import com.sun.voip.TickerException;
import com.sun.voip.TickerFactory;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.NoSuchElementException;
/**
* Send data to conference members
*/
public class ConferenceSender extends Thread {
/*
* Tuneable parameters (mostly for debugging)
*/
private static int senderThreads;
private ArrayList conferenceList;
private boolean done = false;
/* Statistics */
private int packetsSent = 0;
private long totalSendTime;
private static double averageSendTime;
private static double lastMaxSendTime;
private static long startTime;
private static double timeBetweenSends;
Ticker ticker;
private static ArrayList<SenderCallbackListener> senderCallbackList =
new ArrayList<SenderCallbackListener>();
public ConferenceSender(ArrayList conferenceList) {
this.conferenceList = conferenceList;
setName("TheLoneSender");
initialize();
}
public ConferenceSender(ConferenceManager conferenceManager) {
conferenceList = new ArrayList();
conferenceList.add(conferenceManager);
setName("Sender-" + conferenceManager.getId());
initialize();
}
private void initialize() {
senderThreads = Runtime.getRuntime().availableProcessors();
setPriority(Thread.MAX_PRIORITY);
start();
}
public static void addSenderCallbackListener(SenderCallbackListener listener) {
synchronized (senderCallbackList) {
senderCallbackList.add(listener);
}
}
/**
* The job of the conference sender is to send a voice data packet
* to each conference member every 20 ms.
*/
public void run() {
String tickerClassName = System.getProperty("com.sun.voip.TICKER");
try {
TickerFactory tickerFactory = TickerFactory.getInstance();
ticker = tickerFactory.createTicker(tickerClassName, getName());
} catch (TickerException e) {
Logger.println(e.getMessage());
end();
return;
}
/*
* Pump out data every <timeBetweenPackets> ms to each member.
*/
ticker.arm(RtpPacket.PACKET_PERIOD, RtpPacket.PACKET_PERIOD);
long sendTime = 0;
long maxSendTime = 0;
while (!done) {
long startTime = System.nanoTime();
for (SenderCallbackListener listener : senderCallbackList) {
try {
listener.senderCallback();
} catch (Exception e) {
e.printStackTrace();
Logger.println("Sender callback failed! "
+ e.getMessage());
}
}
sendDataToConferences();
int elapsed = (int) (System.nanoTime() - startTime);
if (elapsed > maxSendTime) {
maxSendTime = elapsed;
}
totalSendTime += elapsed;
sendTime += elapsed;
try {
ticker.tick();
} catch (TickerException e) {
Logger.println(getName() + " tick() failed! " + e.getMessage());
end();
break;
}
if (ConferenceManager.getTotalMembers() == 0) {
resetStatistics();
sendTime = 0;
maxSendTime = 0;
continue;
}
packetsSent++;
if ((packetsSent % 250) == 0) {
averageSendTime = sendTime / 1000000000. / 250.;
lastMaxSendTime = maxSendTime / 1000000000.;
String s = getName()
+ " time to send a packet to " + ConferenceManager.getTotalMembers()
+ " members in last 5 seconds is " + (sendTime / 1000000000.)
+ " seconds, average time " + averageSendTime + " seconds "
+ ", maxSendTime " + lastMaxSendTime
+ ", members speaking " + CallHandler.getTotalSpeaking();
if (Logger.logLevel >= Logger.LOG_DETAIL) {
Logger.println(s);
} else {
Logger.writeFile(s);
}
if (packetsSent > 0) {
timeBetweenSends = (System.nanoTime() - startTime) /
1000000000. / 250.;
}
startTime = System.nanoTime();
maxSendTime = 0;
sendTime = 0;
}
}
ticker.disarm();
}
public static double getAverageSendTime() {
return averageSendTime;
}
public static double getMaxSendTime() {
return lastMaxSendTime;
}
public static double getTimeBetweenSends() {
return timeBetweenSends;
}
private void sendDataToConferences() {
/*
* Build a memberList containing the members of all conferences.
*/
ArrayList memberList = new ArrayList();
for (int i = 0; i < conferenceList.size(); i++) {
ConferenceManager conferenceManager = (ConferenceManager) conferenceList.get(i);
//ArrayList ml = (ArrayList) conferenceManager.getMemberList();
//memberList.addAll(ml);
/*
* Take a snapshot of member data
*/
//for (int j = 0; j < ml.size(); j++) {
// ConferenceMember member = (ConferenceMember) ml.get(j);
// member.saveCurrentContribution();
//}
/*
* Take a snapshot of all members and all whisper groups
* in the conference.
*/
synchronized (conferenceManager) {
WGManager wgManager = conferenceManager.getWGManager();
if (wgManager == null) {
continue; // not initialized yet
}
ArrayList whisperGroups = wgManager.getWhisperGroups();
synchronized(whisperGroups) {
for (int j = 0; j < whisperGroups.size(); j++) {
WhisperGroup whisperGroup = (WhisperGroup)
whisperGroups.get(j);
ArrayList ml = whisperGroup.getMembers();
for (int k = 0; k < ml.size(); k++) {
ConferenceMember member = (ConferenceMember)
ml.get(k);
if (member.getWhisperGroup() == whisperGroup) {
/*
* Member is whispering in this whisper group
*/
try {
member.saveCurrentContribution();
} catch (Exception e) {
e.printStackTrace();
Logger.println(
"conf " + getName() + ": "
+ " can't save contribution for "
+ "member " + member);
member.getCallHandler().cancelRequest(
"Unexpected Exception");
continue;
}
memberList.add(member);
}
}
/*
* At this point, the whisper group has the data
* from each whisperer mixed in a buffer.
*/
try {
whisperGroup.saveCurrentContribution();
} catch (Exception e) {
e.printStackTrace();
Logger.println("conf " + getName() + ": "
+ " can't save contribution for whisper group "
+ whisperGroup);
}
}
}
}
}
/*
* Send data to each member in every conference.
*/
if (memberList.size() == 0) {
return;
}
sendDataToMembers(memberList);
for (int i = 0; i < memberList.size(); i++) {
ConferenceMember member = (ConferenceMember) memberList.get(i);
member.invalidateCurrentContribution();
}
}
private ArrayList workerThreads = new ArrayList();
private ConcurrentLinkedQueue workToDo = new ConcurrentLinkedQueue();
private void sendDataToMembers(ArrayList memberList) {
if (Logger.logLevel == -55) {
for (int i = 0; i < memberList.size(); i++) {
ConferenceMember m = (ConferenceMember) memberList.get(i);
Logger.println("conf " + getName() + ": " + m);
}
Logger.println("wt size " + workerThreads.size()
+ " sender threads " + senderThreads);
}
if (workerThreads.size() > 1 && workerThreads.size() != senderThreads) {
/*
* Stop old threads
* XXX We could just stop the extra threads or add new ones
* rather than stopping all of them.
*/
if (Logger.logLevel == -55) {
Logger.println("Stopping sender worker threads "
+ workerThreads.size());
}
for (int i = 0; i < workerThreads.size(); i++) {
((WorkerThread) workerThreads.get(i)).done();
}
workerThreads.clear();
}
if (senderThreads <= 1) {
singleThreadSendDataToMembers(memberList);
return;
}
CountDownLatch doneSignal = new CountDownLatch(workerThreads.size());
if (workerThreads.size() != senderThreads) {
/*
* Start new threads
*/
for (int i = 0; i < senderThreads; i++) {
workerThreads.add(new WorkerThread(i, doneSignal));
}
Logger.println("Started " + senderThreads + " sender threads");
}
workToDo.clear();
for (int i = 0; i < memberList.size(); i++) {
ConferenceMember member = (ConferenceMember) memberList.get(i);
if (member.getMemberSender().memberIsReadyForSenderData()) {
workToDo.add(member);
}
}
/*
* Start all of the worker threads
*/
for (int i = 0; i < workerThreads.size(); i++) {
WorkerThread workerThread = (WorkerThread) workerThreads.get(i);
workerThread.setLatch(doneSignal);
synchronized (workerThread) {
workerThread.notify();
}
}
if (!done) {
try {
doneSignal.await(); // wait for all to finish
} catch (InterruptedException e) {
}
}
}
private void singleThreadSendDataToMembers(ArrayList memberList) {
for (int i = 0; i < memberList.size(); i++) {
ConferenceMember member = (ConferenceMember)
memberList.get(i);
if (!member.getMemberSender().memberIsReadyForSenderData()) {
continue;
}
long start = 0;
if (Logger.logLevel == -33) {
start = System.nanoTime();
}
try {
member.sendData();
} catch (Exception e) {
e.printStackTrace();
Logger.println("Can't send data to " + member + " "
+ e.getMessage());
member.getCallHandler().cancelRequest("Unexpected Exception");
}
if (Logger.logLevel == -33) {
Logger.println("Sender sendDataToOneMember time "
+ member + " "
+ ((System.nanoTime() - start) / 1000000000.) + " seconds");
Logger.logLevel = 3;
}
}
}
class WorkerThread extends Thread {
private boolean done;
private CountDownLatch doneSignal;
public WorkerThread(int i, CountDownLatch doneSignal) {
this.doneSignal = doneSignal;
setName("Sender-WorkerThread-" + i + "-" + getName());
setPriority(Thread.MAX_PRIORITY);
start();
}
public void setLatch(CountDownLatch doneSignal) {
this.doneSignal = doneSignal;
}
public void done() {
done = true;
interrupt();
}
public void run() {
while (!done) {
try {
ConferenceMember member = (ConferenceMember)
workToDo.remove();
try {
member.sendData();
} catch (Exception e) {
e.printStackTrace();
Logger.println("Can't send data to " + member
+ " " + e.getMessage());
member.getCallHandler().cancelRequest(
"Unexpected Exception");
}
} catch (NoSuchElementException e) {
synchronized (this) {
doneSignal.countDown();
if (done) {
break; // done
}
try {
wait();
} catch (InterruptedException ie) {
break;
}
}
}
}
}
}
public void end() {
done = true;
printStatistics();
this.interrupt();
synchronized (workerThreads) {
workToDo.clear();
workerThreads.notifyAll();
}
for (int i = 0; i < workerThreads.size(); i++) {
((WorkerThread) workerThreads.get(i)).done();
}
}
public void printStatistics() {
Logger.println(getName() + " " + packetsSent + " packets sent");
if (packetsSent > 0) {
Logger.println(getName()
+ " average time to send a packet to every member "
+ (totalSendTime / 1000000000. / packetsSent) + " seconds ");
}
ticker.printStatistics();
}
private void resetStatistics() {
packetsSent = 0;
totalSendTime = 0;
}
/*
* Tuneable parameters
*/
public static void setSenderThreads(int senderThreads) {
if (senderThreads < 1) {
senderThreads = 1;
} else if (senderThreads > Runtime.getRuntime().availableProcessors()) {
senderThreads = Runtime.getRuntime().availableProcessors();
}
ConferenceSender.senderThreads = senderThreads;
}
public static int getSenderThreads() {
return senderThreads;
}
public String toString() {
return getName();
}
}