/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
* Copyright (c) Ericsson AB, 2004-2008. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.ericsson.ssa.sip.dialog;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.sip.SipApplicationSession;
import org.jvnet.glassfish.comms.util.LogUtil;
import com.ericsson.ssa.container.sim.ApplicationDispatcher;
import com.ericsson.ssa.sip.DialogFragment;
import com.ericsson.ssa.sip.DialogFragmentManager;
import com.ericsson.ssa.sip.DialogSet;
import com.ericsson.ssa.sip.PathNode;
import com.ericsson.ssa.sip.RemoteLockRuntimeException;
import com.ericsson.ssa.sip.SipApplicationSessionImpl;
import com.ericsson.ssa.sip.SipSessionBase;
import com.ericsson.ssa.sip.SipSessionDialogImpl;
import com.ericsson.ssa.sip.SipSessionManagerBase;
import com.ericsson.ssa.sip.timer.GeneralTimer;
import com.ericsson.ssa.sip.timer.GeneralTimerListener;
import com.ericsson.ssa.sip.timer.TimerServiceImpl;
import com.ericsson.ssa.sip.transaction.TransactionManager;
/**
* This class handles cleanup of dialogs.
* - Triggers finalization of dialog life cycle activities
* - (future) handles removal of obsolete DialogSets and dialog structures that have been created for non-dialog requests.
*/
public class DialogCleaner {
private static final long MAX_UOW_DURATION;
private static final long CLEANING_INTERVAL;
private static final long DS_SCAVENGE_INTERVAL;
private static final long TR_SCAVENGE_INTERVAL;
public static final long NO_DIALOG_TIMEOUT = 5000; // 5 second
static {
{
long interval = 10; // In seconds
try {
interval = Integer.parseInt(System.getProperty("sipContainer.dc.dialogCleaningInterval", "" + interval));
} catch (Throwable t) {
// swallow...
}
CLEANING_INTERVAL = interval * 1000;
}
{
long duration = (TransactionManager.getInstance().getTimerT1() * 64 + 10000)/1000; // In seconds
try {
duration = Integer.parseInt(System.getProperty("sipContainer.dc.maxUowActivityDuration", "" + duration));
} catch (Throwable t) {
// swallow...
}
MAX_UOW_DURATION = duration*1000;
}
{
long dsi = 60; // In seconds
try {
dsi = Integer.parseInt(System.getProperty("sipContainer.dc.dsScavengeInterval", "" + dsi));
} catch (Throwable t) {
// swallow...
}
DS_SCAVENGE_INTERVAL = dsi*1000;
}
{
long tsi = 60*10; // In seconds
try {
tsi = Integer.parseInt(System.getProperty("sipContainer.dc.transactionScavengeInterval", "" + tsi));
} catch (Throwable t) {
// swallow...
}
TR_SCAVENGE_INTERVAL = tsi*1000;
}
}
private static volatile DialogCleaner instance;
private static final Logger logger = LogUtil.SIP_LOGGER.getLogger();
// Use a linked hash map to ensure FIFO order when iterating over it!
private LinkedHashMap<Cleanable, CleanupRecord> supervisedLifeCycles = new LinkedHashMap<Cleanable, CleanupRecord>();
private long nextDsScavenge;
private long nextTrScavenge;
private long nextDump;
private class TimerListener implements GeneralTimerListener {
public void timeout(GeneralTimer timer) {
onTimeout();
TimerServiceImpl.getInstance().createTimer(new TimerListener(), CLEANING_INTERVAL, null);
}
}
private class CleanupRecord {
private long expirationTime;
private Cleanable cleanable;
public CleanupRecord(Cleanable cleanable, long expirationTime) {
this.cleanable = cleanable;
this.expirationTime = expirationTime;
}
}
private DialogCleaner() {
TimerServiceImpl.getInstance().createTimer(new TimerListener(), CLEANING_INTERVAL, null);
}
/**
* Gets the singleton instance.
* @return
*/
public static DialogCleaner getInstance() {
if (instance == null) {
synchronized (DialogCleaner.class) {
if (instance == null)
instance = new DialogCleaner();
}
}
return instance;
}
/**
* Sets the instance. Only for unit test purposes.
* @param dc
*/
static void setInstance(DialogCleaner dc) {
instance = dc;
}
/**
* Register the Cleanable for supervision. To trigger finalization of its current activity.
* @param cleanable
*/
synchronized public void registerForSupervision(Cleanable cleanable) {
registerForSupervision(cleanable, MAX_UOW_DURATION);
}
/**
* Register the Cleanable for supervision. To trigger finalization of its current activity.
* @param cleanable
* @param duration max duration before timeout in milliseconds
*/
synchronized public void registerForSupervision(Cleanable cleanable, long duration) {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "Register object for supervision: " + cleanable);
}
supervisedLifeCycles.put(cleanable, new CleanupRecord(cleanable, System.currentTimeMillis() + duration));
}
/**
* Unregister the DialogLifeCycle from supervision.
* @param dlc
*/
synchronized void unregisterFromSupervision(DialogLifeCycle dlc) {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "Un-register dialog for supervision: " + dlc.getDialogFragment().getFragmentId());
}
supervisedLifeCycles.remove(dlc);
}
private void onTimeout() {
long now = System.currentTimeMillis();
if (logger.isLoggable(Level.FINEST)) {
StringBuffer sb = new StringBuffer();
try {
if (now > nextDump) {
nextDump = now + 180000; // Only log every 3rd minute.
logger.log(Level.FINEST, "Perform dialog clean-up");
logger.log(Level.FINEST, "Ongoing transactions in TransactionManager: " + TransactionManager.getInstance().getOngoingTransactions());
ApplicationDispatcher.getInstance().logActiveCaches();
Iterable<DialogFragment> dialogs = DialogFragmentManager.getInstance().getDialogs();
for (DialogFragment df : dialogs) {
sb.append("dlc={").append(df.getDialogLifeCycle().toString());
sb.append("; df.isValid=").append(df.isValid());
sb.append("; locks: df=").append(df.isForegroundLocked());
for (Iterator<PathNode> pnIt = df.getCaller2CalleePath(); pnIt.hasNext();) {
PathNode pn = pnIt.next();
sb.append("; ss (").append(pn.getSipSessionId()).append(")=");
try {
SipSessionBase ss;
if ((ss = pn.getSipSession()) != null) {
sb.append(((SipSessionDialogImpl) ss).isForegroundLocked());
SipApplicationSession sas;
if ((sas = ss.getApplicationSession()) != null) {
sb.append("; sas (").append(sas.getId()).append(")=").append(((SipApplicationSessionImpl) sas).isForegroundLocked());
} else {
sb.append("sas=null");
}
} else {
sb.append("null");
}
} catch (Throwable e) {
sb.append("exception at reading");
}
}
sb.append("}\n");
}
}
} catch (Throwable t) {
logger.log(Level.FINEST, "Exception when logging active transaction, dialogs, sessions and timers; ignored");
} finally {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "Dialogs: [" + sb.toString() + "]");
}
}
}
scavengeCleanables(now);
scavengeDialogSets(now);
scavengeTransactions(now);
}
private void scavengeDialogSets(long now) {
if (now > nextDsScavenge) {
nextDsScavenge = now + DS_SCAVENGE_INTERVAL;
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Do dialog set scavenging");
}
DialogSet.doScavenge();
}
}
private void scavengeTransactions(long now) {
if (now > nextTrScavenge) {
nextTrScavenge = now + TR_SCAVENGE_INTERVAL;
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINEST, "Do transaction scavenging");
}
TransactionManager.getInstance().doScavenge();
}
}
private void scavengeCleanables(long now) {
List<CleanupRecord> expired = new ArrayList<CleanupRecord>();
synchronized (this) {
for (Iterator<CleanupRecord> it = supervisedLifeCycles.values().iterator(); it.hasNext();) {
CleanupRecord cleanupRecord = it.next();
if (now >= cleanupRecord.expirationTime) {
it.remove();
expired.add(cleanupRecord);
}
}
}
for (Iterator<CleanupRecord> it = expired.iterator(); it.hasNext();) {
CleanupRecord dlcRecord = it.next();
try {
dlcRecord.cleanable.doCleanup();
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "Cleaned registered: " + dlcRecord.cleanable);
}
} catch (Throwable t) {
if (logger.isLoggable(Level.WARNING)) {
logger.log(Level.WARNING, "Exception when cleaning up: " + dlcRecord.cleanable, t);
}
}
}
}
}