/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. 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.sun.enterprise.ee.web.sessmgmt;
import com.sun.logging.LogDomains;
import java.io.IOException;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jxta.document.MimeMediaType;
import net.jxta.document.XMLDocument;
import net.jxta.endpoint.Message;
import net.jxta.endpoint.TextDocumentMessageElement;
import net.jxta.impl.endpoint.router.EndpointRouter;
import net.jxta.impl.pipe.BlockingWireOutputPipe;
import net.jxta.peer.PeerID;
import net.jxta.pipe.OutputPipe;
import net.jxta.protocol.RouteAdvertisement;
/*
* Utility methods for using Jxta unicast pipes.
*
* The purpose of this class is to provide a pool of unicast pipes to each
* instance.
*
* The number of pipes here is a little misleading -- each Jxta pipe is
* physically connected to the same socket (for a given instance). So
* no matter how many pipes there are, there is only one socket. This
* means that while it makes sense to have a few so that they can setup and
* multiplex in the appserver, we'll get more contention at the JXTA layer
* as we add pipes. We'll have to test to get to the optimal number but
* it is likely small.
*
* Hence, the pipes are put in a Deque (stack) so that we will end up creating
* a minimal number of them anyway, with an absolute upper bound based on the
* number of pipes configured. Note that if we cant get a pipe here, the JXTA
* layer is all clogged up anyway, so making more pipes doesn't help.
*
* FIXME: Should we just fail the call without waiting in that case?
*
*/
public class JxtaUnicastPipeUtil {
private static HashMap<String, LinkedBlockingDeque<JxtaUnicastPipeWrapper>> pipeMap =
new HashMap<String, LinkedBlockingDeque<JxtaUnicastPipeWrapper>>();
private static int numPipes = ReplicationUtil.getNumberOfOutputPipes();
private final static Level TRACE_LEVEL = Level.FINE;
public final static String LOGGER_MEM_REP
= ReplicationState.LOGGER_MEM_REP;
private static final Logger _logger
= Logger.getLogger(LOGGER_MEM_REP);
private static final Logger _pipelogger = ReplicationUtil.getPipeLogger();
private static AtomicLong _unicastMessageFailures = new AtomicLong(0);
private static int NUMBER_OF_PIPES = 1; // FIXME
static {
// FIXME
// See comments above about this number; we'll test it to determine
// the optimal value; this will probably not need to be configurable
String s = System.getProperty("jxtaUnicastPipeWrappers");
if (s != null) {
try {
NUMBER_OF_PIPES = Integer.parseInt(s);
} catch (Exception e) {
NUMBER_OF_PIPES = 5;
}
}
}
/**** PUBLIC METHODS ****/
public static void initializeUnicastOutputPipe(String instanceName) {
synchronized(pipeMap) {
// It won't be in the map unless we missed an event, but still...
removeUnicastOutputPipe(instanceName);
getDeque(instanceName, true);
}
}
public static void removeUnicastOutputPipe(String instanceName) {
LinkedBlockingDeque<JxtaUnicastPipeWrapper> deck = null;
synchronized(pipeMap) {
deck = pipeMap.remove(instanceName);
}
if (deck != null) {
for (JxtaUnicastPipeWrapper pipeWrapper = deck.unlinkFirst(); pipeWrapper != null;
pipeWrapper = deck.unlinkFirst()) {
if (!pipeWrapper.closed) {
pipeWrapper.close();
}
}
deck.close();
}
}
public static void sendOverPropagatedPipe(ReplicationState state,
String instanceName, boolean isResponse) {
if (_logger.isLoggable(Level.FINE)) {
_logger.fine("JxtaUnicastPipeUtil>>sendOverPropagatedPipe:toInstance=" + instanceName);
}
//use this to create our outputPipe
RouteAdvertisement routeAdv = state.getRouteAdvertisement();
Message msg = ReplicationState.createBroadcastMessage(state, isResponse, instanceName);
//add routeAdvertisement element for return route (self-addressed stamped envelope)
if (!isResponse) {
JxtaUtil.addRoute(msg);
}
sendOverUnicastPipe(routeAdv, instanceName, (String) state.getId(),
state.getCommand(), msg);
}
public static long getFailures() {
return _unicastMessageFailures.get();
}
public static boolean sendOverUnicastPipe(RouteAdvertisement ra,
String instanceName, String id, String command, Message msg) {
boolean sendResult = false;
final int MAX_RETRY = 6;
JxtaUnicastPipeWrapper pw = null;
for (int retryCount = 0; retryCount < MAX_RETRY; retryCount++) {
try {
pw = getPipe(instanceName, ra);
if (pw == null) {
if (_pipelogger.isLoggable(Level.FINE)) {
_pipelogger.log(Level.FINE,
"Aborting attempt to send " + command +
" to unavailable instance " + instanceName);
}
return false;
}
if (_pipelogger.isLoggable(Level.FINEST)) {
_pipelogger.log(Level.FINEST, "sending over unicast pipe for sessionid=" + id + " pipe= " + pw);
}
sendResult = pw.send(msg);
if (sendResult) {
break;
}
else {
// If we got here, either
// 1) we are congested.
// 2) The remote side is down.
//
// If we don't wait for a small time, we will still
// be congested, but we can't wait too long for a down
// machine.
try { Thread.sleep(10); } catch (InterruptedException ie) {}
if (_pipelogger.isLoggable(Level.FINE)) {
_pipelogger.fine("send over unicast pipe failed for sessionid=" + id + " pipe=" + pw);
}
}
} catch (InterruptedException ie) {
// Either we are very backed up, or the remote host is down
// In either case, no use retrying
return false;
} catch (IOException ex) {
if (_pipelogger.isLoggable(Level.FINE)) {
_pipelogger.log(Level.FINE, "IOException sending unicast message over " + pw, ex);
}
if (pw != null)
pw.error(ex);
if (retryCount < MAX_RETRY) {
if (_pipelogger.isLoggable(Level.FINE)) {
_pipelogger.fine("unicast pipe send retrying");
}
}
} finally {
returnPipe(instanceName, pw);
}
}
if (sendResult == false) {
_unicastMessageFailures.incrementAndGet();
if (_pipelogger.isLoggable(Level.INFO)) {
_pipelogger.info("failed to send message over unicast pipe for sessionid=" + id + " cmd=" + command + " to instance " + instanceName);
}
}
return sendResult;
}
/**** PRIVATE METHODS ****/
private JxtaUnicastPipeUtil() {
throw new IllegalArgumentException("Don't instantiate UnicastPipeUtil");
}
private static JxtaUnicastPipeWrapper getPipe(
String instance, RouteAdvertisement ra) throws IOException, InterruptedException {
LinkedBlockingDeque<JxtaUnicastPipeWrapper> deck;
deck = getDeque(instance, false);
if (deck == null) {
return null;
}
// We will only ever block here if the pool size is 1 and the remote
// system is down.
JxtaUnicastPipeWrapper pw = deck.pollFirst(5, TimeUnit.SECONDS);
if (pw == null) {
if (!deck.isClosed()) {
_logger.warning("Waited 5 seconds for free JXTA unicast pipe to " + instance);
}
throw new InterruptedException("Timeout waiting for JXTA unicast pipe " + instance);
}
return check(pw, instance, ra);
}
private static void returnPipe(String instance, JxtaUnicastPipeWrapper pw) {
if (pw == null)
return;
if (pw.closed) {
pw.initPipeWrapper(null, null, null);
}
LinkedBlockingDeque<JxtaUnicastPipeWrapper> deck = getDeque(instance, false);
if (deck != null) {
boolean b = deck.offerFirst(pw);
}
// if deck == null, it means that someone is returning a pipe on an
// instance that has been remove via GMS notification. Just ignore that.
}
private static void makePipe(JxtaUnicastPipeWrapper pw,
String instanceName, RouteAdvertisement ra) throws IOException {
PeerID peerID = JxtaStarter.getPeerID(instanceName);
OutputPipe p = new BlockingWireOutputPipe(
JxtaUtil.getNetPeerGroup(),
JxtaUtil.getPropagatedPipeAdvertisement(), peerID, ra);
if (_pipelogger.isLoggable(Level.FINE)) {
_pipelogger.fine("create PropagatedPipe instance=" +
instanceName + " peerId="+ peerID + " routeAdv=" + ra);
}
pw.initPipeWrapper(p, ra, instanceName);
}
private static JxtaUnicastPipeWrapper check(
JxtaUnicastPipeWrapper pw,
String instanceName, RouteAdvertisement ra) throws IOException {
if (pw.closed || pw.pipe == null || pw.pipe.isClosed()) {
makePipe(pw, instanceName, ra);
} else if (ra != null && pw.routeAdv != null && !ra.equals(pw.routeAdv)) {
if (_pipelogger.isLoggable(Level.FINER)) {
_pipelogger.finer(
"detected a stale propagated pipe for instance=" +
instanceName + " cached routeAdv=" +
pw.routeAdv + " current routeAdv=" + ra);
}
// close stale pipe
pw.close();
makePipe(pw, instanceName, ra);
} else if (_pipelogger.isLoggable(Level.FINEST)) {
_pipelogger.finest(
"cache hit for propagated pipe to instance=" +
instanceName + " routeAdv=" + ra);
}
return pw;
}
private static LinkedBlockingDeque<JxtaUnicastPipeWrapper>
getDeque(String instance, boolean create) {
LinkedBlockingDeque<JxtaUnicastPipeWrapper> deck;
synchronized(pipeMap) {
deck = pipeMap.get(instance);
if (deck == null && create) {
deck = new LinkedBlockingDeque<JxtaUnicastPipeWrapper>(NUMBER_OF_PIPES);
pipeMap.put(instance, deck);
for (int i = 0; i < NUMBER_OF_PIPES; i++) {
deck.offerFirst(new JxtaUnicastPipeWrapper());
}
}
}
return deck;
}
private static class JxtaUnicastPipeWrapper {
private OutputPipe pipe;
private RouteAdvertisement routeAdv;
private boolean closed = false;
private String myInstance;
public boolean send(Message m) throws IOException {
// We set closed so that if the send fails for any reason, we know
// not to reuse the pipe
closed = true;
try {
boolean b = pipe.send(m);
closed = false;
return b;
} finally {
if (closed) {
close();
}
}
}
public void initPipeWrapper(OutputPipe p,
RouteAdvertisement ra, String s) {
pipe = p;
routeAdv = ra;
myInstance = s;
}
public void error(Throwable t) {
try {
close();
} catch (Throwable ignore) {
// ignore
}
}
public void close() {
if (pipe != null)
pipe.close();
closed = true;
}
public String toString() {
return "JXTA Unicast Pipe Wrapper #" +
System.identityHashCode(this) +
" connected to " + myInstance;
}
}
/*
* This class is a subset of the JDK 6 LinkedBlockingDeque class. When
* Sailfin no longer supports JDK 5, this class should be removed and
* the JDK 6 class used in its place -- except note that we have added
* the isClosed functionality, so it will still need to be subclassed.
*/
private static final class LinkedBlockingDeque<E> {
private static final class Node<E> {
E item;
Node<E> prev;
Node<E> next;
Node(E x, Node<E> p, Node<E> n) {
item = x;
prev = p;
next = n;
}
}
private volatile boolean isClosed = false;
private transient Node<E> first;
private transient Node<E> last;
private transient int count;
private final int capacity;
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
public LinkedBlockingDeque(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
}
public E pollFirst(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
lock.lockInterruptibly();
try {
for (;;) {
E x = unlinkFirst();
if (x != null)
return x;
if (isClosed)
return null;
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
} finally {
lock.unlock();
}
}
public boolean offerFirst(E e) {
if (e == null) throw new NullPointerException();
lock.lock();
try {
return linkFirst(e);
} finally {
lock.unlock();
}
}
public void close() {
lock.lock();
try {
isClosed = true;
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
public boolean isClosed() {
return isClosed;
}
private boolean linkFirst(E e) {
if (count >= capacity) {
return false;
}
++count;
Node<E> f = first;
Node<E> x = new Node<E>(e, null, f);
first = x;
if (last == null)
last = x;
else
f.prev = x;
notEmpty.signal();
return true;
}
private E unlinkFirst() {
Node<E> f = first;
if (f == null)
return null;
Node<E> n = f.next;
first = n;
if (n == null)
last = null;
else n.prev = null;
--count;
return f.item;
}
}
}