diff -Nru libjgroups-java-2.7.0.GA/bin/clusterperformancetest.sh libjgroups-java-2.12.2.Final/bin/clusterperformancetest.sh --- libjgroups-java-2.7.0.GA/bin/clusterperformancetest.sh 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/clusterperformancetest.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,90 +0,0 @@ -#!/bin/bash -#Script file used for automated performance tests. Prior to running this script -#user should use org.jgroups.tests.perf.PerformanceTestGenerator to generate -#performance tests input files. -# -#For a succesful performance test running user should ensure the following: -# - can log into all machines listed in CLUSTER_NODES variable -# - CLASSPATH variable points to existing lib files -# - CONFIG_FILES is initialized to proper performance tests input files -# - JGROUPS_CONFIG_FILE is initialized to an existing JGroups stack conf file - -#lists all the computer nodes used for performance tests -CLUSTER_NODES=( cluster01.qa.atl.jboss.com cluster02.qa.atl.jboss.com cluster03.qa.atl.jboss.com cluster04.qa.atl.jboss.com cluster05.qa.atl.jboss.com cluster06.qa.atl.jboss.com cluster07.qa.atl.jboss.com cluster08.qa.atl.jboss.com ) - -USERID=bela - - -#classpath for performance tests -CLASSPATH='commons-logging.jar:log4j-1.2.6.jar:concurrent.jar:jgroups-all.jar' - -#finds test configuration files -#note the running directory of this script and make sure that find command can -#actually find configuration files -CONFIG_FILES=`find . -name 'config_*.txt'` - -#JGroups configuration stack used in performance tests -JGROUPS_CONFIG_FILE="/home/${USERID}/udp.xml" -#JGROUPS_CONFIG_FILE="/home/${USERID}/tcp-nio.xml" - -#sleeptime between performance test rounds (should be big enough to prevent test -#overlapping) -SLEEP_TIME=30 - -LOGIN_COMMAND='ssh -i rgreathouse@jboss.com.id_dsa vblagojevic@' - -JVM_COMMAND='/opt/jdk1.5.0_06/bin/java -Djava.net.preferIPv4Stack=true' - -LOGIN_COMMAND="${USERID}@" - -JVM_COMMAND='java -Djava.net.preferIPv4Stack=true' - -JVM_PARAM="-Xmx500M -Xms500M -XX:NewRatio=1 -XX:+AggressiveHeap -XX:+DisableExplicitGC -XX:CompileThreshold=100 -Dbind.address=\${MYTESTIP_1}" - -#verify that we found configuration files -config_file_count=${#CONFIG_FILES[*]} -if [ $config_file_count -le "0" ] ; then - echo Did not find performance test configuration files! - exit -fi - -echo "starting performance tests..." -node_count=${#CLUSTER_NODES[@]} -for file in $CONFIG_FILES; -do - num_senders_line=`grep num_senders $file` - num_senders=${num_senders_line:12} - - num_members_line=`grep num_members $file` - num_members=${num_members_line:12} - - sender_count=1 - sender_or_receiver=" -sender " - echo "starting performance test round for $file..." - for (( i = 0 ; i < node_count ; i++ )) - do - node=${CLUSTER_NODES[$i]} - if [ $sender_count -le $num_senders ] ; then - let "sender_count++" - else - sender_or_receiver=" -receiver " - fi - let j=$i+1 - if [ $j -eq $node_count ] ; then - SSH_CMD="ssh" - output_file="-f result-${file#.*/}" - else - SSH_CMD="ssh -f" - output_file="" - fi - args="-config $file -props $JGROUPS_CONFIG_FILE $sender_or_receiver $output_file" - final_command="$SSH_CMD $LOGIN_COMMAND$node $JVM_COMMAND $JVM_PARAM org.jgroups.tests.perf.Test $args > /dev/null" - echo starting $final_command on $node - $final_command - sleep 5 - done - echo "Tests round is now running, waiting $SLEEP_TIME seconds for this test round to finish..." - sleep $SLEEP_TIME -done - - diff -Nru libjgroups-java-2.7.0.GA/bin/concurrent.sh libjgroups-java-2.12.2.Final/bin/concurrent.sh --- libjgroups-java-2.7.0.GA/bin/concurrent.sh 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/concurrent.sh 2011-10-18 11:22:35.000000000 +0000 @@ -1,37 +1,12 @@ #!/bin/bash -BIN=`dirname $0` - -LIB=$BIN/../lib - -LIBS=$LIB/commons-logging.jar - -CLASSPATH=$BIN/../classes:$CLASSPATH:$LIBS - -# OS specific support (must be 'true' or 'false'). -cygwin=false; -case "`uname`" in - CYGWIN*) - cygwin=true - ;; -esac - -if [ $cygwin = "true" ]; then - CP=`cygpath -wp $CLASSPATH` -else - CP=$CLASSPATH -fi - -#java -classpath $CP -Dbind.address=192.168.2.5 org.jgroups.demos.Draw -props /home/bela/udp.xml & -#sleep 5 - count=0 while [ $count -lt 20 ] do echo "Starting Draw instance #$count" # change the IP address to your system - java -classpath $CP -Dbind.address=192.168.1.5 org.jgroups.demos.Draw -props /home/bela/udp.xml & + jgroups.sh org.jgroups.demos.Draw -props /home/bela/udp.xml -name $count & # sleep 1 count=$(($count+1)) done \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/bin/draw.bat libjgroups-java-2.12.2.Final/bin/draw.bat --- libjgroups-java-2.7.0.GA/bin/draw.bat 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/draw.bat 2011-10-18 11:22:35.000000000 +0000 @@ -1,7 +1,7 @@ @rem Convenience launcher for the Draw demo (contributed by Laran Evans lc278@cornell.edu) @echo off -set CPATH=../classes;../conf;../lib/commons-logging.jar;../lib/log4j-1.2.6.jar;../lib/concurrent.jar +set CPATH=../classes;../conf; set JAVA_OPTS= if -debug==%1 set JAVA_OPTS=-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_shmem,server=y,suspend=y,address=jgc1 @@ -15,7 +15,7 @@ set PROPS=%PROPS%:UNICAST(timeout=600,1200,2400,4800) set PROPS=%PROPS%:pbcast.STABLE(desired_avg_gossip=20000) set PROPS=%PROPS%:FRAG(frag_size=8096;down_thread=false;up_thread=false) -set PROPS=%PROPS%:pbcast.GMS(join_timeout=5000;shun=false;print_local_addr=true) +set PROPS=%PROPS%:pbcast.GMS(join_timeout=5000;print_local_addr=true) @echo on java -classpath %CPATH% %JAVA_OPTS% org.jgroups.demos.Draw -props %PROPS% diff -Nru libjgroups-java-2.7.0.GA/bin/drawnio.bat libjgroups-java-2.12.2.Final/bin/drawnio.bat --- libjgroups-java-2.7.0.GA/bin/drawnio.bat 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/drawnio.bat 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ -@rem Convenience launcher for the Draw demo (contributed by Laran Evans lc278@cornell.edu) -@echo off - -set CPATH=../classes;../conf;../lib/commons-logging.jar;../lib/log4j.jar;../lib/log4j-1.2.6.jar;../lib/concurrent.jar;../conf/log4j.properties - -set JAVA_OPTS= -if -debug==%1 set JAVA_OPTS=-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_shmem,server=y,suspend=y,address=jgc1 - -if "%LOCALHOSTIP%"=="" echo Warning: You should set environment variable 'LOCALHOSTIP' to your local ip address before running this script. -if "%LOCALHOSTIP%"=="" set LOCALHOSTIP=192.168.1.103 - -@echo on -java -classpath %CPATH% %JAVA_OPTS% -Djgroups.bind_addr=%LOCALHOSTIP% -Djgroups.tcpping.initial_hosts=%LOCALHOSTIP%[7800],%LOCALHOSTIP%[7801] org.jgroups.demos.Draw -props c:\jboss\JGroups\conf\tcp-nio.xml \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/bin/draw.sh libjgroups-java-2.12.2.Final/bin/draw.sh --- libjgroups-java-2.7.0.GA/bin/draw.sh 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/draw.sh 2011-10-18 11:22:35.000000000 +0000 @@ -24,7 +24,7 @@ done -CLASSPATH="$relpath/../classes$SEP$relpath/../conf$SEP$relpath/../lib/commons-logging.jar$SEP$relpath/../lib/log4j-1.2.6.jar$SEP$relpath/../lib/concurrent.jar" +CLASSPATH="$relpath/../classes$SEP$relpath/../conf$SEP" if [ "$debug" = "true" ]; then JAVA_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_shmem,server=y,suspend=y,address=jgc1" @@ -39,7 +39,7 @@ UNICAST(timeout=600,1200,2400,4800):\ pbcast.STABLE(desired_avg_gossip=20000):\ FRAG(frag_size=8096;down_thread=false;up_thread=false):\ -pbcast.GMS(join_timeout=5000;shun=false;print_local_addr=true)" +pbcast.GMS(join_timeout=5000;print_local_addr=true)" if [ "$cygwin" = "true" ]; then diff -Nru libjgroups-java-2.7.0.GA/bin/frag_size.bat libjgroups-java-2.12.2.Final/bin/frag_size.bat --- libjgroups-java-2.7.0.GA/bin/frag_size.bat 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/frag_size.bat 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ -@echo off - -REM Determines the fragmentation size of your system - -set CLASSPATH=..\classes - -set CP=%CLASSPATH% -set LOG4J=etc/log4j.xml -set L4J=%LOG4J% - - - -java -cp %CP% org.jgroups.tests.DetermineFragSize diff -Nru libjgroups-java-2.7.0.GA/bin/frag_size.sh libjgroups-java-2.12.2.Final/bin/frag_size.sh --- libjgroups-java-2.7.0.GA/bin/frag_size.sh 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/frag_size.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,24 +0,0 @@ -#!/bin/sh - -# Determines the fragmentation size of your system - -BIN=`dirname $0` - -CLASSPATH=$BIN/../classes - -# OS specific support (must be 'true' or 'false'). -cygwin=false; -case "`uname`" in - CYGWIN*) - cygwin=true - ;; -esac - -if [ $cygwin = "true" ]; then - CP=`cygpath -wp $CLASSPATH` -else - CP=$CLASSPATH -fi - - -java -cp $CP org.jgroups.tests.DetermineFragSize diff -Nru libjgroups-java-2.7.0.GA/bin/gaps.sh libjgroups-java-2.12.2.Final/bin/gaps.sh --- libjgroups-java-2.7.0.GA/bin/gaps.sh 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/gaps.sh 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,2 @@ +#!/bin/bash +java org.jgroups.tests.CheckGaps $* diff -Nru libjgroups-java-2.7.0.GA/bin/get_interfaces.bat libjgroups-java-2.12.2.Final/bin/get_interfaces.bat --- libjgroups-java-2.7.0.GA/bin/get_interfaces.bat 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/get_interfaces.bat 1970-01-01 00:00:00.000000000 +0000 @@ -1,9 +0,0 @@ -@echo off - -REM Determines the interfaces on a machine - -set CLASSPATH=..\classes - -set CP=%CLASSPATH% - -java -cp %CP% org.jgroups.util.GetNetworkInterfaces diff -Nru libjgroups-java-2.7.0.GA/bin/get_interfaces.sh libjgroups-java-2.12.2.Final/bin/get_interfaces.sh --- libjgroups-java-2.7.0.GA/bin/get_interfaces.sh 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/get_interfaces.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,24 +0,0 @@ -#!/bin/sh - -# Determines the network interfaces on a machine - -BIN=`dirname $0` - -CLASSPATH=$BIN/../classes - -# OS specific support (must be 'true' or 'false'). -cygwin=false; -case "`uname`" in - CYGWIN*) - cygwin=true - ;; -esac - -if [ $cygwin = "true" ]; then - CP=`cygpath -wp $CLASSPATH` -else - CP=$CLASSPATH -fi - - -java -cp $CP org.jgroups.util.GetNetworkInterfaces diff -Nru libjgroups-java-2.7.0.GA/bin/gossiprouter.sh libjgroups-java-2.12.2.Final/bin/gossiprouter.sh --- libjgroups-java-2.7.0.GA/bin/gossiprouter.sh 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/gossiprouter.sh 2011-10-18 11:22:35.000000000 +0000 @@ -9,5 +9,8 @@ OPTS="-Dlog4j.configuration=file:$HOME/log4j.properties -Djava.net.preferIPv4Stack=true" OPTS="$OPTS -Dcom.sun.management.jmxremote" -java $OPTS -classpath $CLASSPATH $JAVA_OPTS org.jgroups.stack.GossipRouter -port 12001 $* +## Uncomment for remote JMX access. Also modify JAVA_HOME/jre/lib/management/jmxremote.passwords +# OPTS="$OPTS -Dcom.sun.management.jmxremote.port=7000 -Dcom.sun.management.jmxremote.ssl=false" + +java $OPTS -classpath $CP $JAVA_OPTS org.jgroups.stack.GossipRouter -port 12001 $* diff -Nru libjgroups-java-2.7.0.GA/bin/jg libjgroups-java-2.12.2.Final/bin/jg --- libjgroups-java-2.7.0.GA/bin/jg 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/jg 2011-10-18 11:22:35.000000000 +0000 @@ -1,6 +1,5 @@ # Author: Bela Ban -# Version: $Id: jg,v 1.3 2008/09/26 10:53:20 belaban Exp $ #!/bin/bash diff -Nru libjgroups-java-2.7.0.GA/bin/jg-2.6 libjgroups-java-2.12.2.Final/bin/jg-2.6 --- libjgroups-java-2.7.0.GA/bin/jg-2.6 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/jg-2.6 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ - -# Author: Bela Ban -# Version: $Id$ - - -#!/bin/bash - -JG=$HOME/JGroups-2.6-branch - -jgroups.sh org.jgroups.demos.$* diff -Nru libjgroups-java-2.7.0.GA/bin/jgroups.bat libjgroups-java-2.12.2.Final/bin/jgroups.bat --- libjgroups-java-2.7.0.GA/bin/jgroups.bat 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/jgroups.bat 2011-10-18 11:22:35.000000000 +0000 @@ -7,7 +7,7 @@ set JG=. set LIB=%JG% -set CP=%JG%\classes\;%JG%\conf\;%LIB%\jgroups-all.jar\;%LIB%\commons-logging.jar\;%LIB%\concurrent.jar\;%LIB%\jmxri.jar\;%LIB%\log4j.jar\;%JG%\keystore +set CP=%JG%\classes\;%JG%\conf\;%LIB%\jgroups-all.jar\;%LIB%\log4j.jar\;%JG%\keystore set VMFLAGS=-Xmx500M -Xms500M -XX:NewRatio=1 -XX:+AggressiveHeap -verbose:gc -XX:+DisableExplicitGC -XX:ThreadStackSize=32 -XX:CompileThreshold=100 diff -Nru libjgroups-java-2.7.0.GA/bin/jgroups.sh libjgroups-java-2.12.2.Final/bin/jgroups.sh --- libjgroups-java-2.7.0.GA/bin/jgroups.sh 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/jgroups.sh 2011-10-18 11:22:35.000000000 +0000 @@ -1,6 +1,4 @@ - # Author: Bela Ban -# version: $Id: jgroups.sh,v 1.7 2008/10/28 12:19:28 belaban Exp $ #!/bin/bash @@ -10,19 +8,38 @@ CP=$JG/classes:$JG/conf +# If this is a bin dist, JARs are in the $JG directory. +if [ ! -d $LIB ]; then + LIB=$JG +fi; + for i in $LIB/*.jar do CP=$CP:$i done -LOG="-Dlog4j.configuration=file:$HOME/log4j.properties" +if [ -f $HOME/log4j.properties ]; then + LOG="-Dlog4j.configuration=file:$HOME/log4j.properties" +fi; + JG_FLAGS="-Dresolve.dns=false -Djgroups.bind_addr=$IP_ADDR -Djboss.tcpping.initial_hosts=$IP_ADDR[7800]" -FLAGS="-server -Xmx600M -Xms600M -Xmn500M" -FLAGS="$FLAGS -XX:CompileThreshold=10000 -XX:+AggressiveHeap -XX:ThreadStackSize=64 -XX:SurvivorRatio=8" +JG_FLAGS="$JG_FLAGS -Djava.net.preferIPv4Stack=true -Djgroups.timer.num_threads=4" +FLAGS="-server -Xmx600M -Xms600M -Xmn500M -Xss128K" +FLAGS="$FLAGS -XX:CompileThreshold=10000 -XX:+AggressiveHeap -XX:ThreadStackSize=64K -XX:SurvivorRatio=8" FLAGS="$FLAGS -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=31" -FLAGS="$FLAGS -Djava.net.preferIPv4Stack=true -Djgroups.timer.num_threads=4" -FLAGS="$FLAGS -Xshare:off -XX:+UseBiasedLocking" +FLAGS="$FLAGS -Xshare:off" JMX="-Dcom.sun.management.jmxremote" -EXPERIMENTAL="-XX:+UseFastAccessorMethods -XX:+UseTLAB -XX:+DoEscapeAnalysis" +#EXPERIMENTAL="-XX:+UseFastAccessorMethods -XX:+UseTLAB" + +#EXPERIMENTAL="$EXPERIMENTAL -XX:+DoEscapeAnalysis -XX:+EliminateLocks -XX:+UseBiasedLocking" +EXPERIMENTAL="$EXPERIMENTAL -XX:+EliminateLocks -XX:+UseBiasedLocking" + +#EXPERIMENTAL="$EXPERIMENTAL -XX:+AggressiveOpts -XX:+DoEscapeAnalysis -XX:+EliminateLocks -XX:+UseBiasedLocking -XX:+UseCompressedOops" +#EXPERIMENTAL="$EXPERIMENTAL -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC" + +#java -Xrunhprof:cpu=samples,monitor=y,interval=5,lineno=y,thread=y -classpath $CP $LOG $JG_FLAGS $FLAGS $EXPERIMENTAL $JMX $* + +#DEBUG="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5000" + +java -classpath $CP $DEBUG $LOG $JG_FLAGS $FLAGS $EXPERIMENTAL $JMX $* -java -classpath $CP $LOG $JG_FLAGS $FLAGS $EXPERIMENTAL $JMX $* diff -Nru libjgroups-java-2.7.0.GA/bin/jt libjgroups-java-2.12.2.Final/bin/jt --- libjgroups-java-2.7.0.GA/bin/jt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/jt 2011-10-18 11:22:35.000000000 +0000 @@ -1,5 +1,4 @@ # Author: Bela Ban -# Version: $Id: jt,v 1.4 2008/09/26 10:52:57 belaban Exp $ #!/bin/bash diff -Nru libjgroups-java-2.7.0.GA/bin/jt-2.6 libjgroups-java-2.12.2.Final/bin/jt-2.6 --- libjgroups-java-2.7.0.GA/bin/jt-2.6 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/jt-2.6 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ - -# Author: Bela Ban -# Version: $Id$ - - -#!/bin/bash - -JG=$HOME/JGroups-2.6-branch - -jgroups.sh org.jgroups.tests.$* diff -Nru libjgroups-java-2.7.0.GA/bin/probe.bat libjgroups-java-2.12.2.Final/bin/probe.bat --- libjgroups-java-2.7.0.GA/bin/probe.bat 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/probe.bat 2011-10-18 11:22:35.000000000 +0000 @@ -8,7 +8,7 @@ set LIB=..\lib -set LIBS=%LIB%\log4j-1.2.6.jar;%LIB%\commons-logging.jar;%LIB%\concurrent.jar +set LIBS=%LIB%\log4j.jar; set CP=%CLASSPATH%;%LIBS% diff -Nru libjgroups-java-2.7.0.GA/bin/probe.sh libjgroups-java-2.12.2.Final/bin/probe.sh --- libjgroups-java-2.7.0.GA/bin/probe.sh 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/probe.sh 2011-10-18 11:22:35.000000000 +0000 @@ -8,7 +8,7 @@ LIB=$BIN/../lib -LIBS=$LIB/log4j-1.2.6.jar:$LIB/commons-logging.jar:$LIB/concurrent.jar +LIBS=$LIB/log4j.jar #echo $CLASSPATH diff -Nru libjgroups-java-2.7.0.GA/bin/release_to_local_repo.sh libjgroups-java-2.12.2.Final/bin/release_to_local_repo.sh --- libjgroups-java-2.7.0.GA/bin/release_to_local_repo.sh 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/release_to_local_repo.sh 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,27 @@ +#!/bin/bash + + +# Uploads the artifacts in ./dist (JAR and src JAR) to the local repo ($HOME/.ms/jboss-repository) +# so we can do local testing before uploading to the Nexus maven repo + + +# Author: Bela Ban + + +DIST=../dist +POM=../pom.xml + +JAR=`find $DIST -name "jgroups-*.jar" | grep -v source` +SRC_JAR=`find $DIST -name "jgroups-*.jar" | grep source` + +REPO=file:$HOME/.m2/jboss-repository +FLAGS="-Dpackaging=jar -DrepositoryId=jboss-releases-repository" + + +echo "Deploying $JAR to $REPO" +mvn deploy:deploy-file -Dfile=$JAR -Durl=$REPO -DpomFile=$POM $FLAGS + + +echo "Deploying $SRC_JAR to $REPO" +mvn deploy:deploy-file -Dfile=$SRC_JAR -Durl=$REPO -DpomFile=$POM -Dclassifier=sources $FLAGS + diff -Nru libjgroups-java-2.7.0.GA/bin/release_to_nexus.sh libjgroups-java-2.12.2.Final/bin/release_to_nexus.sh --- libjgroups-java-2.7.0.GA/bin/release_to_nexus.sh 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/release_to_nexus.sh 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,28 @@ +#!/bin/bash + + +# Uploads the artifacts in ./dist (JAR and src JAR) to the Nexus Maven repo at repository.jboss.org/nexus +# The artifacts will be in the staging repo, go to repository.jboss.org/nexus and promote them to the releases repo in +# the next step + +# Author: Bela Ban + + +DIST=../dist +POM=../pom.xml + +JAR=`find $DIST -name "jgroups-*.jar" | grep -v source` +SRC_JAR=`find $DIST -name "jgroups-*.jar" | grep source` + +REPO=https://repository.jboss.org/nexus/service/local/staging/deploy/maven2 +FLAGS="-Dpackaging=jar -DrepositoryId=jboss-releases-repository" + + +echo "Deploying $JAR to $REPO" +mvn deploy:deploy-file -Dfile=$JAR -Durl=$REPO -DpomFile=$POM $FLAGS + + +echo "Deploying $SRC_JAR to $REPO" +mvn deploy:deploy-file -Dfile=$SRC_JAR -Durl=$REPO -DpomFile=$POM -Dclassifier=sources $FLAGS + + diff -Nru libjgroups-java-2.7.0.GA/bin/runtest.sh libjgroups-java-2.12.2.Final/bin/runtest.sh --- libjgroups-java-2.7.0.GA/bin/runtest.sh 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/runtest.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,49 +0,0 @@ -#!/bin/sh - -# Runs a single test class from command line, circumventing ant altogether. Useful for -# quick debugging. - -TESTCLASS=org.jgroups.tests.stack.RouterTest - -reldir=`dirname $0` - -# OS specific support (must be 'true' or 'false'). -cygwin=false; -case "`uname`" in - CYGWIN*) - cygwin=true - ;; -esac - -if [ $cygwin = true ]; then - SEP=";" -else - SEP=":" -fi - -while [ "$1" != "" ]; do - if [ "$1" = "-debug" ]; then - if [ $cygwin = false ]; then - JAVA_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=12348" - else - JAVA_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_shmem,server=y,suspend=y,address=jgroups" - fi - fi - shift -done - - -CLASSPATH="$reldir/../classes${SEP}\ -$reldir/../conf${SEP}\ -$reldir/../lib/junit.jar${SEP}\ -$reldir/../lib/log4j-1.2.6.jar${SEP}\ -$reldir/../lib/commons-logging.jar" - -#if [ $cygwin = "true" ]; then -# CP=`cygpath -wp $CLASSPATH` -#else -# CP=$CLASSPATH -#fi - -#echo $CLASSPATH -java $JAVA_OPTS -cp $CLASSPATH junit.textui.TestRunner $TESTCLASS diff -Nru libjgroups-java-2.7.0.GA/bin/upload_javadocs.sh libjgroups-java-2.12.2.Final/bin/upload_javadocs.sh --- libjgroups-java-2.7.0.GA/bin/upload_javadocs.sh 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/upload_javadocs.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ -#!/bin/sh -# -# Uploads the javadoc to SourceForge - -scp -r ../dist/javadoc/* belaban,javagroups@web.sourceforge.net:/home/groups/j/ja/javagroups/htdocs/javagroupsnew/docs/javadoc/ diff -Nru libjgroups-java-2.7.0.GA/bin/upload_manual.sh libjgroups-java-2.12.2.Final/bin/upload_manual.sh --- libjgroups-java-2.7.0.GA/bin/upload_manual.sh 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/bin/upload_manual.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -#!/bin/sh -# -# Uploads the manual to SourceForge - -scp -r ../doc/manual/build/en/* belaban,javagroups@web.sourceforge.net:/home/groups/j/ja/javagroups/htdocs/javagroupsnew/docs/manual -scp -r ../doc/tutorial/build/en/* belaban,javagroups@web.sourceforge.net:/home/groups/j/ja/javagroups/htdocs/javagroupsnew/docs/tutorial diff -Nru libjgroups-java-2.7.0.GA/build.bat libjgroups-java-2.12.2.Final/build.bat --- libjgroups-java-2.7.0.GA/build.bat 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/build.bat 2011-10-18 11:22:35.000000000 +0000 @@ -8,7 +8,7 @@ set LIB=lib -set LIBS=%LIB%\log4j-1.2.6.jar;%LIB%\commons-logging.jar;%LIB%\concurrent.jar +set LIBS=%LIB%\log4j.jar set LIBS=%LIB%\ant.jar;%LIB%\ant-junit.jar;%LIB%\ant-launcher.jar;%LIB%\junit.jar;%LIB%\xalan.jar;%LIB%\serializer.jar; REM echo LIBS=%LIBS% diff -Nru libjgroups-java-2.7.0.GA/build.properties libjgroups-java-2.12.2.Final/build.properties --- libjgroups-java-2.7.0.GA/build.properties 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/build.properties 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -# add your own properties in here - -# the network interface (NIC) which will be used by the unit tests, change this to -# the one you want to use. Note that 'localhost' usually resolved to 127.0.0.1, -# which may not work on Linux unless you have a multicast route set up for loopback -jgroups.bind_addr=localhost -jgroups.tcpping.initial_hosts=localhost[7800] -jgroups.udp.mcast_addr=232.10.10.10 -jgroups.udp.mcast_port=45588 -jgroups.udp.ip_ttl=5 \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/build.properties.template libjgroups-java-2.12.2.Final/build.properties.template --- libjgroups-java-2.7.0.GA/build.properties.template 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/build.properties.template 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,12 @@ # the one you want to use. Note that 'localhost' usually resolved to 127.0.0.1, # which may not work on Linux unless you have a multicast route set up for loopback jgroups.bind_addr=localhost -jgroups.tcpping.initial_hosts=localhost[7800] +jgroups.tcpping.initial_hosts=localhost[7800],localhost[7801] +jgroups.tunnel.gossip_router_hosts=localhost[12001] jgroups.udp.mcast_addr=232.10.10.10 jgroups.udp.mcast_port=45588 -jgroups.udp.ip_ttl=5 \ No newline at end of file +jgroups.udp.ip_ttl=5 +# use true for IPv6 and false for IPv4 +jgroups.useIPv6=false + + diff -Nru libjgroups-java-2.7.0.GA/build.properties.template.ipv6 libjgroups-java-2.12.2.Final/build.properties.template.ipv6 --- libjgroups-java-2.7.0.GA/build.properties.template.ipv6 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/build.properties.template.ipv6 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,16 @@ +# Example of IPv6 based build.properties +# Replace the IPv6 addresses with your own + +# the network interface (NIC) which will be used by the unit tests, change this to +# the one you want to use. Note that 'localhost' usually resolved to 127.0.0.1, +# which may not work on Linux unless you have a multicast route set up for loopback +jgroups.bind_addr=fe80::21b:21ff:fe07:a3b0%3 +jgroups.tcpping.initial_hosts=fe80::21b:21ff:fe07:a3b0%3[7800],fe80::21b:21ff:fe07:a3b0%3[7801] +jgroups.tunnel.gossip_router_hosts=fe80::21b:21ff:fe07:a3b0%3[12001] +jgroups.udp.mcast_addr=ff0e::5:6:7 +jgroups.udp.mcast_port=45588 +jgroups.udp.ip_ttl=5 +# use true for IPv6 and false for IPv4 +jgroups.useIPv6=true + + diff -Nru libjgroups-java-2.7.0.GA/build.sh libjgroups-java-2.12.2.Final/build.sh --- libjgroups-java-2.7.0.GA/build.sh 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/build.sh 2011-10-18 11:22:35.000000000 +0000 @@ -20,12 +20,6 @@ LIB=lib -#if [ "$cygwin" = "true" ]; then -# CP=${LIB}/ant.jar\;${LIB}/ant-launcher.jar\;${LIB}/ant-junit.jar\;${LIB}/xalan.jar\;${LIB}/junit.jar -#else -# CP=${LIB}/ant.jar:${LIB}/ant-launcher.jar:${LIB}/ant-junit.jar:${LIB}/xalan.jar:${LIB}/junit.jar -#fi - if [ "$cygwin" = "true" ]; then for i in ${LIB}/*.jar diff -Nru libjgroups-java-2.7.0.GA/build.xml libjgroups-java-2.12.2.Final/build.xml --- libjgroups-java-2.7.0.GA/build.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/build.xml 2011-10-18 11:22:35.000000000 +0000 @@ -1,13 +1,12 @@ - build.xml file for JGroups. Needs Ant (jakarta.apache.org) to run - + @@ -16,6 +15,7 @@ + @@ -32,19 +32,17 @@ - + + + - + - - - - @@ -54,6 +52,17 @@ + + + + + + + + + + + @@ -62,10 +71,6 @@ - - - - @@ -80,17 +85,16 @@ - - - + + description="Compiles all Java files"> + + + + + + + + + + + depends="jgroups.jar,jgroups-sources.jar"> + + - - - - + + + + + + + + + + + + + + - - - - - - @@ -147,103 +167,17 @@ + includes="org/jgroups/**"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -260,28 +194,69 @@ + + + + - - + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + - - - - + + + + + + + + + + + + + + + + @@ -290,10 +265,11 @@ + - + @@ -306,7 +282,7 @@ - + @@ -315,6 +291,7 @@ + @@ -342,7 +319,7 @@ - + @@ -352,23 +329,30 @@ - - + + - + - + - + + + + + + - - + + + @@ -379,13 +363,30 @@ usedefaultlisteners="false" outputdir="${tmp.dir}/test-results/xml" listeners="org.jgroups.util.JUnitXMLReporter" - threadcount="20" + threadcount="10" parallel="methods" > + + + + + + + + + + + - - - - - + @@ -422,28 +412,48 @@ - + - + + + + + + + + + + @@ -453,12 +463,14 @@ @@ -486,14 +498,16 @@ - + + + @@ -502,18 +516,32 @@ + + + - + + + + + + + + + + + + - + - diff -Nru libjgroups-java-2.7.0.GA/.classpath libjgroups-java-2.12.2.Final/.classpath --- libjgroups-java-2.7.0.GA/.classpath 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/.classpath 2011-10-18 11:22:35.000000000 +0000 @@ -1,19 +1,19 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff -Nru libjgroups-java-2.7.0.GA/conf/auth_fixedlist.xml libjgroups-java-2.12.2.Final/conf/auth_fixedlist.xml --- libjgroups-java-2.7.0.GA/conf/auth_fixedlist.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/auth_fixedlist.xml 2011-10-18 11:22:35.000000000 +0000 @@ -1,9 +1,8 @@ - + /> + max_bytes="4m"/> - + /> \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/conf/auth_md5.xml libjgroups-java-2.12.2.Final/conf/auth_md5.xml --- libjgroups-java-2.7.0.GA/conf/auth_md5.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/auth_md5.xml 2011-10-18 11:22:35.000000000 +0000 @@ -2,7 +2,6 @@ + /> + max_bytes="4m"/> - + /> \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/conf/auth_regex.xml libjgroups-java-2.12.2.Final/conf/auth_regex.xml --- libjgroups-java-2.7.0.GA/conf/auth_regex.xml 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/auth_regex.xml 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru libjgroups-java-2.7.0.GA/conf/auth_simple.xml libjgroups-java-2.12.2.Final/conf/auth_simple.xml --- libjgroups-java-2.7.0.GA/conf/auth_simple.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/auth_simple.xml 2011-10-18 11:22:35.000000000 +0000 @@ -1,16 +1,16 @@ - + + /> - - + max_bytes="4m"/> + + /> \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/conf/auth_X509.xml libjgroups-java-2.12.2.Final/conf/auth_X509.xml --- libjgroups-java-2.7.0.GA/conf/auth_X509.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/auth_X509.xml 2011-10-18 11:22:35.000000000 +0000 @@ -2,7 +2,6 @@ + /> + max_bytes="4m"/> - + /> diff -Nru libjgroups-java-2.7.0.GA/conf/bare-bones.xml libjgroups-java-2.12.2.Final/conf/bare-bones.xml --- libjgroups-java-2.7.0.GA/conf/bare-bones.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/bare-bones.xml 2011-10-18 11:22:35.000000000 +0000 @@ -12,9 +12,10 @@ done on the application level in this case --> - + diff -Nru libjgroups-java-2.7.0.GA/conf/bsh.xml libjgroups-java-2.12.2.Final/conf/bsh.xml --- libjgroups-java-2.7.0.GA/conf/bsh.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/bsh.xml 2011-10-18 11:22:35.000000000 +0000 @@ -2,19 +2,20 @@ - + - + - + + desired_avg_gossip="20000" max_bytes="4m"/> - + \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/conf/causal.xml libjgroups-java-2.12.2.Final/conf/causal.xml --- libjgroups-java-2.7.0.GA/conf/causal.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/causal.xml 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/conf/compress.xml libjgroups-java-2.12.2.Final/conf/compress.xml --- libjgroups-java-2.7.0.GA/conf/compress.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/compress.xml 2011-10-18 11:22:35.000000000 +0000 @@ -2,20 +2,21 @@ - + - + - + - - + diff -Nru libjgroups-java-2.7.0.GA/conf/config.txt libjgroups-java-2.12.2.Final/conf/config.txt --- libjgroups-java-2.7.0.GA/conf/config.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/config.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,6 +1,5 @@ # # This file contains configuration for the JGroups performance tests (org.jgroups.tests.perf package) -# $Id: config.txt,v 1.12 2006/12/11 10:16:54 belaban Exp $ # # Class implementing the org.jgroups.tests.perf.Transport interface diff -Nru libjgroups-java-2.7.0.GA/conf/discard.xml libjgroups-java-2.12.2.Final/conf/discard.xml --- libjgroups-java-2.7.0.GA/conf/discard.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/discard.xml 1970-01-01 00:00:00.000000000 +0000 @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/conf/encrypt-entire-message.xml libjgroups-java-2.12.2.Final/conf/encrypt-entire-message.xml --- libjgroups-java-2.7.0.GA/conf/encrypt-entire-message.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/encrypt-entire-message.xml 2011-10-18 11:22:35.000000000 +0000 @@ -2,18 +2,19 @@ - + - + - + - + \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/conf/EncryptKeyStore.xml libjgroups-java-2.12.2.Final/conf/EncryptKeyStore.xml --- libjgroups-java-2.7.0.GA/conf/EncryptKeyStore.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/EncryptKeyStore.xml 2011-10-18 11:22:35.000000000 +0000 @@ -2,9 +2,10 @@ - + diff -Nru libjgroups-java-2.7.0.GA/conf/EncryptNoKeyStore.xml libjgroups-java-2.12.2.Final/conf/EncryptNoKeyStore.xml --- libjgroups-java-2.7.0.GA/conf/EncryptNoKeyStore.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/EncryptNoKeyStore.xml 2011-10-18 11:22:35.000000000 +0000 @@ -2,18 +2,20 @@ - + + loopback="true" ucast_send_buf_size="32000" ip_ttl="32"/> - + - + - + diff -Nru libjgroups-java-2.7.0.GA/conf/encrypt.xml libjgroups-java-2.12.2.Final/conf/encrypt.xml --- libjgroups-java-2.7.0.GA/conf/encrypt.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/encrypt.xml 2011-10-18 11:22:35.000000000 +0000 @@ -2,18 +2,19 @@ - + - + - + - + \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/conf/execution-service.xml libjgroups-java-2.12.2.Final/conf/execution-service.xml --- libjgroups-java-2.7.0.GA/conf/execution-service.xml 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/execution-service.xml 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + diff -Nru libjgroups-java-2.7.0.GA/conf/fast-local.xml libjgroups-java-2.12.2.Final/conf/fast-local.xml --- libjgroups-java-2.7.0.GA/conf/fast-local.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/fast-local.xml 2011-10-18 11:22:35.000000000 +0000 @@ -5,12 +5,12 @@ Therefore, this configuration will NOT work to cluster members residing on different hosts ! Author: Bela Ban - Version: $Id: fast-local.xml,v 1.1 2008/09/26 16:01:28 belaban Exp $ --> - + + oob_thread_pool.rejection_policy="discard"/> @@ -52,19 +58,20 @@ - + max_bytes="8m"/> - + + diff -Nru libjgroups-java-2.7.0.GA/conf/flush-tcp-nio.xml libjgroups-java-2.12.2.Final/conf/flush-tcp-nio.xml --- libjgroups-java-2.7.0.GA/conf/flush-tcp-nio.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/flush-tcp-nio.xml 2011-10-18 11:22:35.000000000 +0000 @@ -5,21 +5,21 @@ Note that TCP.bind_addr and TCPPING.initial_hosts should be set, possibly via system properties, e.g. -Djgroups.bind_addr=192.168.5.2 and -Djgroups.tcpping.initial_hosts=192.168.5.2[7800]". author: Bela Ban - version: $Id: flush-tcp-nio.xml,v 1.7 2008/09/25 14:20:45 belaban Exp $ --> - + - + - + max_bytes="4m"/> - + + diff -Nru libjgroups-java-2.7.0.GA/conf/flush-tcp.xml libjgroups-java-2.12.2.Final/conf/flush-tcp.xml --- libjgroups-java-2.7.0.GA/conf/flush-tcp.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/flush-tcp.xml 2011-10-18 11:22:35.000000000 +0000 @@ -5,18 +5,19 @@ -Djgroups.bind_addr=192.168.5.2 and -Djgroups.tcpping.initial_hosts=192.168.5.2[7800]" --> - + - + - + + max_bytes="4m"/> - + view_bundling="true"/> + + - \ No newline at end of file + diff -Nru libjgroups-java-2.7.0.GA/conf/flush-udp.xml libjgroups-java-2.12.2.Final/conf/flush-udp.xml --- libjgroups-java-2.7.0.GA/conf/flush-udp.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/flush-udp.xml 2011-10-18 11:22:35.000000000 +0000 @@ -3,16 +3,17 @@ Default flush stack using IP multicasting. --> - + - + @@ -46,14 +47,16 @@ - - + max_bytes="4m"/> + + + diff -Nru libjgroups-java-2.7.0.GA/conf/jboss-service.xml libjgroups-java-2.12.2.Final/conf/jboss-service.xml --- libjgroups-java-2.7.0.GA/conf/jboss-service.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/jboss-service.xml 2011-10-18 11:22:35.000000000 +0000 @@ -6,7 +6,6 @@ jgroups:name=DemoChannel @@ -22,22 +21,22 @@ - + loopback="true"/> - + - + + print_local_addr="true"/> diff -Nru libjgroups-java-2.7.0.GA/conf/jg-magic-map.xml libjgroups-java-2.12.2.Final/conf/jg-magic-map.xml --- libjgroups-java-2.7.0.GA/conf/jg-magic-map.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/jg-magic-map.xml 2011-10-18 11:22:35.000000000 +0000 @@ -2,7 +2,6 @@ - @@ -18,24 +17,43 @@ - - + - + - + + + + + + + + + + + + + + + + + + + + + diff -Nru libjgroups-java-2.7.0.GA/conf/jg-protocol-ids.xml libjgroups-java-2.12.2.Final/conf/jg-protocol-ids.xml --- libjgroups-java-2.7.0.GA/conf/jg-protocol-ids.xml 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/jg-protocol-ids.xml 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru libjgroups-java-2.7.0.GA/conf/log4j.properties libjgroups-java-2.12.2.Final/conf/log4j.properties --- libjgroups-java-2.7.0.GA/conf/log4j.properties 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/log4j.properties 2011-10-18 11:22:35.000000000 +0000 @@ -23,7 +23,8 @@ log4j.logger.org.jgroups=warn -#log4j.logger.org.jgroups.protocols.pbcast.FLUSH=WARN +log4j.logger.org.jgroups.protocols.pbcast.FLUSH=DEBUG +log4j.logger.org.jgroups.protocols.MERGE2=DEBUG #log4j.logger.org.jgroups.protocols.pbcast.GMS=WARN #log4j.additivity.org.jgroups.protocols.pbcast.STABLE=false diff -Nru libjgroups-java-2.7.0.GA/conf/manifest.mf libjgroups-java-2.12.2.Final/conf/manifest.mf --- libjgroups-java-2.7.0.GA/conf/manifest.mf 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/manifest.mf 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,4 @@ Manifest-Version: 1.0 -Created-By: Apache Ant 1.5Beta2 +Created-By: Apache Ant 1.6.5 Main-Class: org.jgroups.Version -Implementation-Version: 2.7.0.GA \ No newline at end of file +Implementation-Version: 2.12.2.Final \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/conf/mping.xml libjgroups-java-2.12.2.Final/conf/mping.xml --- libjgroups-java-2.7.0.GA/conf/mping.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/mping.xml 2011-10-18 11:22:35.000000000 +0000 @@ -2,18 +2,17 @@ - - + - + max_bytes="4m"/> + /> + + + + + + diff -Nru libjgroups-java-2.7.0.GA/conf/multiplexer-service.xml libjgroups-java-2.12.2.Final/conf/multiplexer-service.xml --- libjgroups-java-2.7.0.GA/conf/multiplexer-service.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/multiplexer-service.xml 2011-10-18 11:22:35.000000000 +0000 @@ -1,23 +1,22 @@ - - - - - - - - - - jgroups.mux - stacks.xml - true - true - - - - + + + + + + + + + + jgroups.mux + stacks.xml + true + true + + + + diff -Nru libjgroups-java-2.7.0.GA/conf/multiplexer-xmbean.xml libjgroups-java-2.12.2.Final/conf/multiplexer-xmbean.xml --- libjgroups-java-2.7.0.GA/conf/multiplexer-xmbean.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/multiplexer-xmbean.xml 2011-10-18 11:22:35.000000000 +0000 @@ -1,140 +1,139 @@ - - - - - - - - - - JGroups Multiplexer - org.jgroups.jmx.JChannelFactory - - - - The default constructor - JChannelFactory - - - - - The domain which is used as prefix for all channels and protocols exposed via JMX - Domain - java.lang.String - - - - - - - The file used for configuration. If this is not an absolute pathname, it will need to be found on the classpath - MultiplexerConfig - java.lang.String - - - - - - - Whether or not to expose channels via JMX - ExposeChannels - boolean - - - - - - - Whether or not to expose protocols via JMX (if true, will set ExposeChannels to true too) - ExposeProtocols - boolean - - - - - - - - - The create() life cycle operation - create - - - - The start lifecycle operation. - start - - - - The stop lifecycle operation. - stop - - - - The destroy() life cycle operation - destroy - - - - - Dumps the channels - dumpChannels - java.lang.String - - - - Dumps the configuration - dumpConfiguration - java.lang.String - - - - - createMultiplexerChannel - - The name of the stack, as defined e.g. in stacks.xml - stack_name - java.lang.String - - - The application ID, all IDs need to be unique across a Multiplexer - id - java.lang.String - - - Whether this application wants to register for state transfer, getState() will only return when *all* registered listeners called it - register_for_state_transfer - boolean - - - The ID of the substate to be retrieved (or null) - substate_id - java.lang.String - - org.jgroups.Channel - - - - - createMultiplexerChannel - - The name of the stack, as defined e.g. in stacks.xml - stack_name - java.lang.String - - - The application ID, all IDs need to be unique across a Multiplexer - id - java.lang.String - - org.jgroups.Channel - - - - + + + + + + + + + + JGroups Multiplexer + org.jgroups.jmx.JChannelFactory + + + + The default constructor + JChannelFactory + + + + + The domain which is used as prefix for all channels and protocols exposed via JMX + Domain + java.lang.String + + + + + + + The file used for configuration. If this is not an absolute pathname, it will need to be found on the classpath + MultiplexerConfig + java.lang.String + + + + + + + Whether or not to expose channels via JMX + ExposeChannels + boolean + + + + + + + Whether or not to expose protocols via JMX (if true, will set ExposeChannels to true too) + ExposeProtocols + boolean + + + + + + + + + The create() life cycle operation + create + + + + The start lifecycle operation. + start + + + + The stop lifecycle operation. + stop + + + + The destroy() life cycle operation + destroy + + + + + Dumps the channels + dumpChannels + java.lang.String + + + + Dumps the configuration + dumpConfiguration + java.lang.String + + + + + createMultiplexerChannel + + The name of the stack, as defined e.g. in stacks.xml + stack_name + java.lang.String + + + The application ID, all IDs need to be unique across a Multiplexer + id + java.lang.String + + + Whether this application wants to register for state transfer, getState() will only return when *all* registered listeners called it + register_for_state_transfer + boolean + + + The ID of the substate to be retrieved (or null) + substate_id + java.lang.String + + org.jgroups.Channel + + + + + createMultiplexerChannel + + The name of the stack, as defined e.g. in stacks.xml + stack_name + java.lang.String + + + The application ID, all IDs need to be unique across a Multiplexer + id + java.lang.String + + org.jgroups.Channel + + + + diff -Nru libjgroups-java-2.7.0.GA/conf/pbcast.xml libjgroups-java-2.12.2.Final/conf/pbcast.xml --- libjgroups-java-2.7.0.GA/conf/pbcast.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/pbcast.xml 1970-01-01 00:00:00.000000000 +0000 @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - diff -Nru libjgroups-java-2.7.0.GA/conf/pulltheplug.xml libjgroups-java-2.12.2.Final/conf/pulltheplug.xml --- libjgroups-java-2.7.0.GA/conf/pulltheplug.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/pulltheplug.xml 1970-01-01 00:00:00.000000000 +0000 @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - diff -Nru libjgroups-java-2.7.0.GA/conf/sequencer.xml libjgroups-java-2.12.2.Final/conf/sequencer.xml --- libjgroups-java-2.7.0.GA/conf/sequencer.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/sequencer.xml 2011-10-18 11:22:35.000000000 +0000 @@ -3,19 +3,19 @@ Default stack using IP multicasting. It is similar to the "udp" stack in stacks.xml, but doesn't use streaming state transfer and flushing author: Bela Ban - version: $Id: sequencer.xml,v 1.11 2008/11/03 07:43:27 belaban Exp $ --> - + - + max_bytes="4m"/> - + + diff -Nru libjgroups-java-2.7.0.GA/conf/settings.xml libjgroups-java-2.12.2.Final/conf/settings.xml --- libjgroups-java-2.7.0.GA/conf/settings.xml 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/settings.xml 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,86 @@ + + + + + + /home/bela/.m2/jboss-repository + + + + jboss-releases-repository + USERNAME + PASSWORD + + + + + + + + jboss-public-repository + + + jboss-public-repository-group + JBoss Public Maven Repository Group + https://repository.jboss.org/nexus/content/groups/public/ + default + + true + never + + + true + never + + + + + + jboss-public-repository-group + JBoss Public Maven Repository Group + https://repository.jboss.org/nexus/content/groups/public/ + default + + true + never + + + true + never + + + + + + + + jboss-deprecated-repository + + + jboss-deprecated-repository + JBoss Deprecated Maven Repository + https://repository.jboss.org/nexus/content/repositories/deprecated/ + default + + true + never + + + false + never + + + + + + + + + + jboss-public-repository + + + + diff -Nru libjgroups-java-2.7.0.GA/conf/sfc.xml libjgroups-java-2.12.2.Final/conf/sfc.xml --- libjgroups-java-2.7.0.GA/conf/sfc.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/sfc.xml 2011-10-18 11:22:35.000000000 +0000 @@ -2,19 +2,19 @@ - + - + - + max_bytes="4m" /> + view_bundling="true" /> diff -Nru libjgroups-java-2.7.0.GA/conf/smack_tunnel.xml libjgroups-java-2.12.2.Final/conf/smack_tunnel.xml --- libjgroups-java-2.7.0.GA/conf/smack_tunnel.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/smack_tunnel.xml 2011-10-18 11:22:35.000000000 +0000 @@ -4,9 +4,11 @@ - + - + diff -Nru libjgroups-java-2.7.0.GA/conf/smack.xml libjgroups-java-2.12.2.Final/conf/smack.xml --- libjgroups-java-2.7.0.GA/conf/smack.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/smack.xml 2011-10-18 11:22:35.000000000 +0000 @@ -9,9 +9,11 @@ acks (sender sends message, receiver sends ack, if sender doesn't receive ack within timeout, message will be retransmitted). Compared to smack.xml, this stack uses TUNNEL as transport--> - - + + - + diff -Nru libjgroups-java-2.7.0.GA/conf/stacks.xml libjgroups-java-2.12.2.Final/conf/stacks.xml --- libjgroups-java-2.7.0.GA/conf/stacks.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/stacks.xml 2011-10-18 11:22:35.000000000 +0000 @@ -2,21 +2,19 @@ - + + max_bytes="4m"/> - + + @@ -73,14 +73,13 @@ run out of memory"> - + + max_bytes="4m"/> @@ -141,9 +140,8 @@ max_bundle_size="64000" max_bundle_timeout="30" enable_bundling="true" - use_send_queues="false" + use_send_queues="true" sock_conn_timeout="300" - skip_suspected_members="true" thread_pool.enabled="true" thread_pool.min_threads="1" @@ -168,7 +166,7 @@ - + + max_bytes="4m"/> - + + @@ -204,10 +204,9 @@ max_bundle_size="64000" max_bundle_timeout="30" enable_bundling="true" - use_send_queues="false" + use_send_queues="true" sock_conn_timeout="300" - skip_suspected_members="true" - + thread_pool.enabled="true" thread_pool.min_threads="1" thread_pool.max_threads="25" @@ -231,7 +230,7 @@ - + + max_bytes="4m"/> @@ -259,14 +258,14 @@ - + + max_bytes="4m"/> - + + @@ -312,14 +313,14 @@ - + + max_bytes="4m"/> @@ -364,7 +365,7 @@ gossip_port="12001"/> - + + max_bytes="4m"/> - + + diff -Nru libjgroups-java-2.7.0.GA/conf/tcpgossip.xml libjgroups-java-2.12.2.Final/conf/tcpgossip.xml --- libjgroups-java-2.7.0.GA/conf/tcpgossip.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/tcpgossip.xml 2011-10-18 11:22:35.000000000 +0000 @@ -1,17 +1,59 @@ - - - - - - - - - - + + + + + + + + + + + - - - + + + + + diff -Nru libjgroups-java-2.7.0.GA/conf/tcp_nio.xml libjgroups-java-2.12.2.Final/conf/tcp_nio.xml --- libjgroups-java-2.7.0.GA/conf/tcp_nio.xml 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/tcp_nio.xml 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + diff -Nru libjgroups-java-2.7.0.GA/conf/tcp-nio.xml libjgroups-java-2.12.2.Final/conf/tcp-nio.xml --- libjgroups-java-2.7.0.GA/conf/tcp-nio.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/tcp-nio.xml 2011-10-18 11:22:35.000000000 +0000 @@ -5,9 +5,10 @@ Note that TCP.bind_addr and TCPPING.initial_hosts should be set, possibly via system properties, e.g. -Djgroups.bind_addr=192.168.5.2 and -Djgroups.tcpping.initial_hosts=192.168.5.2[7800]". author: Bela Ban - version: $Id: tcp-nio.xml,v 1.14 2008/09/25 14:20:44 belaban Exp $ --> - + - + - + max_bytes="4m"/> - + + diff -Nru libjgroups-java-2.7.0.GA/conf/tcp.xml libjgroups-java-2.12.2.Final/conf/tcp.xml --- libjgroups-java-2.7.0.GA/conf/tcp.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/tcp.xml 2011-10-18 11:22:35.000000000 +0000 @@ -4,28 +4,34 @@ Note that TCP.bind_addr and TCPPING.initial_hosts should be set, possibly via system properties, e.g. -Djgroups.bind_addr=192.168.5.2 and -Djgroups.tcpping.initial_hosts=192.168.5.2[7800] author: Bela Ban - version: $Id: tcp.xml,v 1.25 2008/09/25 14:26:07 belaban Exp $ --> - + + oob_thread_pool.rejection_policy="discard"/> - + - + - + max_bytes="4M"/> - - + + + \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/conf/testng/stack-independent.xml libjgroups-java-2.12.2.Final/conf/testng/stack-independent.xml --- libjgroups-java-2.7.0.GA/conf/testng/stack-independent.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/testng/stack-independent.xml 2011-10-18 11:22:35.000000000 +0000 @@ -9,6 +9,7 @@ + diff -Nru libjgroups-java-2.7.0.GA/conf/testng/testng-single.xml libjgroups-java-2.12.2.Final/conf/testng/testng-single.xml --- libjgroups-java-2.7.0.GA/conf/testng/testng-single.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/testng/testng-single.xml 1970-01-01 00:00:00.000000000 +0000 @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff -Nru libjgroups-java-2.7.0.GA/conf/testng/testng-tcp-flush.xml libjgroups-java-2.12.2.Final/conf/testng/testng-tcp-flush.xml --- libjgroups-java-2.7.0.GA/conf/testng/testng-tcp-flush.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/testng/testng-tcp-flush.xml 2011-10-18 11:22:35.000000000 +0000 @@ -1,8 +1,7 @@ + thread-count="5"> diff -Nru libjgroups-java-2.7.0.GA/conf/testng/testng-tcp-stress.xml libjgroups-java-2.12.2.Final/conf/testng/testng-tcp-stress.xml --- libjgroups-java-2.7.0.GA/conf/testng/testng-tcp-stress.xml 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/testng/testng-tcp-stress.xml 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + diff -Nru libjgroups-java-2.7.0.GA/conf/testng/testng-tcp.xml libjgroups-java-2.12.2.Final/conf/testng/testng-tcp.xml --- libjgroups-java-2.7.0.GA/conf/testng/testng-tcp.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/testng/testng-tcp.xml 2011-10-18 11:22:35.000000000 +0000 @@ -1,7 +1,7 @@ @@ -13,7 +13,8 @@ - + + diff -Nru libjgroups-java-2.7.0.GA/conf/testng/testng-udp-flush.xml libjgroups-java-2.12.2.Final/conf/testng/testng-udp-flush.xml --- libjgroups-java-2.7.0.GA/conf/testng/testng-udp-flush.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/testng/testng-udp-flush.xml 2011-10-18 11:22:35.000000000 +0000 @@ -1,8 +1,7 @@ + thread-count="5"> diff -Nru libjgroups-java-2.7.0.GA/conf/testng/testng-udp.xml libjgroups-java-2.12.2.Final/conf/testng/testng-udp.xml --- libjgroups-java-2.7.0.GA/conf/testng/testng-udp.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/testng/testng-udp.xml 2011-10-18 11:22:35.000000000 +0000 @@ -1,7 +1,7 @@ diff -Nru libjgroups-java-2.7.0.GA/conf/testng/time-sensitive.xml libjgroups-java-2.12.2.Final/conf/testng/time-sensitive.xml --- libjgroups-java-2.7.0.GA/conf/testng/time-sensitive.xml 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/testng/time-sensitive.xml 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff -Nru libjgroups-java-2.7.0.GA/conf/tunnel.xml libjgroups-java-2.12.2.Final/conf/tunnel.xml --- libjgroups-java-2.7.0.GA/conf/tunnel.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/tunnel.xml 2011-10-18 11:22:35.000000000 +0000 @@ -4,17 +4,15 @@ - - - + + + + - + + max_bytes="4m"/> - + + diff -Nru libjgroups-java-2.7.0.GA/conf/udp-largecluster.xml libjgroups-java-2.12.2.Final/conf/udp-largecluster.xml --- libjgroups-java-2.7.0.GA/conf/udp-largecluster.xml 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/udp-largecluster.xml 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru libjgroups-java-2.7.0.GA/conf/udp.xml libjgroups-java-2.12.2.Final/conf/udp.xml --- libjgroups-java-2.7.0.GA/conf/udp.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/conf/udp.xml 2011-10-18 11:22:35.000000000 +0000 @@ -3,27 +3,33 @@ Default stack using IP multicasting. It is similar to the "udp" stack in stacks.xml, but doesn't use streaming state transfer and flushing author: Bela Ban - version: $Id: udp.xml,v 1.29 2008/09/26 15:59:33 belaban Exp $ --> - + - + max_bytes="4M"/> - - + + + diff -Nru libjgroups-java-2.7.0.GA/debian/changelog libjgroups-java-2.12.2.Final/debian/changelog --- libjgroups-java-2.7.0.GA/debian/changelog 2011-10-24 03:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/debian/changelog 2011-12-27 23:06:24.000000000 +0000 @@ -1,3 +1,19 @@ +libjgroups-java (2.12.2.Final-2) unstable; urgency=low + + * Upload to unstable. + + -- Brian Thomason Tue, 27 Dec 2011 22:36:12 +0100 + +libjgroups-java (2.12.2.Final-1) experimental; urgency=low + + * New upstream release + * Added Red Hat to copyright list + * Patch no longer required + [ Steffen Moeller ] + * Added Brian to uploaders. + + -- Brian Thomason Tue, 06 Dec 2011 22:43:17 +0000 + libjgroups-java (2.7.0.GA-4) unstable; urgency=low * Team upload. diff -Nru libjgroups-java-2.7.0.GA/debian/compat libjgroups-java-2.12.2.Final/debian/compat --- libjgroups-java-2.7.0.GA/debian/compat 2007-10-27 15:10:52.000000000 +0000 +++ libjgroups-java-2.12.2.Final/debian/compat 2011-12-22 01:00:28.000000000 +0000 @@ -1 +1 @@ -5 +7 diff -Nru libjgroups-java-2.7.0.GA/debian/control libjgroups-java-2.12.2.Final/debian/control --- libjgroups-java-2.7.0.GA/debian/control 2011-10-22 22:04:44.000000000 +0000 +++ libjgroups-java-2.12.2.Final/debian/control 2011-12-22 22:48:08.000000000 +0000 @@ -2,7 +2,9 @@ Section: java Priority: optional Maintainer: Debian Java Maintainers -Uploaders: Varun Hiremath , Torsten Werner +Uploaders: Varun Hiremath , + Torsten Werner , + Brian Thomason Build-Depends: cdbs, debhelper (>= 5) Build-Depends-Indep: ant, default-jdk, glassfish-javaee, bsh, junit, libxalan2-java, libbcprov-java, libcommons-logging-java, @@ -11,10 +13,11 @@ Homepage: http://www.jgroups.org/javagroupsnew/docs/index.html Vcs-Svn: svn://svn.debian.org/svn/pkg-java/trunk/libjgroups-java Vcs-Browser: http://svn.debian.org/wsvn/pkg-java/trunk/libjgroups-java/ +DM-Upload-Allowed: yes Package: libjgroups-java Architecture: all -Depends: ${shlibs:Depends}, ${misc:Depends}, openjdk-6-jre-headless | java5-runtime +Depends: ${shlibs:Depends}, ${misc:Depends} Description: Toolkit for Reliable Multicast Communication JGroups is a toolkit for reliable multicast communication. (Note that this doesn't necessarily mean IP Multicast, JGroups can also use diff -Nru libjgroups-java-2.7.0.GA/debian/copyright libjgroups-java-2.12.2.Final/debian/copyright --- libjgroups-java-2.7.0.GA/debian/copyright 2008-08-16 21:38:07.000000000 +0000 +++ libjgroups-java-2.12.2.Final/debian/copyright 2011-12-22 01:00:28.000000000 +0000 @@ -20,8 +20,9 @@ Chris Mills Copyright: -(C) 2005 JBoss Inc., Chris Mills -(C) 2002 Filip Hanik and Bela Ban +Copyright 2006 Red Hat, Inc. +Copyright 2005 JBoss Inc., Chris Mills +Copyright 2002 Filip Hanik and Bela Ban License: This library is free software; you can redistribute it and/or @@ -42,10 +43,10 @@ src/org/jgroups/annotations/GuardedBy.java src/org/jgroups/annotations/Immutable.java -Copyright: (c) 2008 Torsten Werner +Copyright: 2008 Torsten Werner License: same license as jgroups (LGPL, see above) ------- -The Debian packaging is (C) 2007, Varun Hiremath and +The Debian packaging is Copyright 2007, Varun Hiremath and is licensed under the GPL, see `/usr/share/common-licenses/GPL'. diff -Nru libjgroups-java-2.7.0.GA/debian/orig-tar.sh libjgroups-java-2.12.2.Final/debian/orig-tar.sh --- libjgroups-java-2.7.0.GA/debian/orig-tar.sh 2008-08-16 21:38:07.000000000 +0000 +++ libjgroups-java-2.12.2.Final/debian/orig-tar.sh 2011-12-22 01:00:28.000000000 +0000 @@ -1,17 +1,29 @@ #!/bin/sh -e # called by uscan with '--upstream-version' -TAR=libjgroups-java_$2.orig.tar.gz -DIR=libjgroups-java-$2.orig +VER=`echo $2|sed -e 's/\_/\./g'` +TAR=../libjgroups-java_$VER.orig.tar.gz +DIR=libjgroups-java-$VER.orig # clean up the upstream tarball -unzip $3 -mv JGroups-* $DIR +if [ -r "$3" ]; then + echo "Found existing tarball - presumably from get-orig-soruce/uscan - fixing it" + tar xfz $3 +fi +if find . -type d -a -name "belaban*"; then + echo "Renaming belaban* to '$DIR'" + mv belaban* $DIR +fi # replace CC licensed files by my own simple implementation cp -f debian/annotations/* $DIR/src/org/jgroups/annotations/ -tar -c -z -f $TAR --exclude '*.jar' --exclude '*/out/*' --exclude '*/lib/*' $DIR +if GZIP="-9n" tar -c -z -f $TAR --exclude '*.jar' --exclude '*/out/*' --exclude '*/lib/*' $DIR; then + echo "Created tar file in '$TAR'." +else + echo "Error packing directory '$DIR' to '$TAR'" + exit +fi rm -rf $3 $DIR # move to directory 'tarballs' @@ -20,3 +32,6 @@ mv $TAR $origDir echo "moved $TAR to $origDir" fi + + +echo "[OK]" diff -Nru libjgroups-java-2.7.0.GA/debian/patches/0001-define-jg.classpath-before-using-it.patch libjgroups-java-2.12.2.Final/debian/patches/0001-define-jg.classpath-before-using-it.patch --- libjgroups-java-2.7.0.GA/debian/patches/0001-define-jg.classpath-before-using-it.patch 2010-02-28 10:17:05.000000000 +0000 +++ libjgroups-java-2.12.2.Final/debian/patches/0001-define-jg.classpath-before-using-it.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,35 +0,0 @@ -From: Torsten Werner -Date: Sun, 28 Feb 2010 11:09:15 +0100 -Subject: [PATCH] define jg.classpath before using it - ---- - build.xml | 8 ++++---- - 1 files changed, 4 insertions(+), 4 deletions(-) - -diff --git a/build.xml b/build.xml -index 596e909..20baae7 100644 ---- a/build.xml -+++ b/build.xml -@@ -41,10 +41,6 @@ - - - -- -- -- -- - - - -@@ -54,6 +50,10 @@ - - - -+ -+ -+ -+ - - - --- diff -Nru libjgroups-java-2.7.0.GA/debian/patches/series libjgroups-java-2.12.2.Final/debian/patches/series --- libjgroups-java-2.7.0.GA/debian/patches/series 2010-02-28 10:17:05.000000000 +0000 +++ libjgroups-java-2.12.2.Final/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -0001-define-jg.classpath-before-using-it.patch diff -Nru libjgroups-java-2.7.0.GA/debian/rules libjgroups-java-2.12.2.Final/debian/rules --- libjgroups-java-2.7.0.GA/debian/rules 2011-10-22 22:04:44.000000000 +0000 +++ libjgroups-java-2.12.2.Final/debian/rules 2011-12-22 01:00:28.000000000 +0000 @@ -10,13 +10,15 @@ #DEB_ANT_CHECK_TARGET := -Djgroups.bind_addr=`hostname` all-tests DEB_JARS := xalan2 serializer junit log4j-1.2 bsh commons-logging bcprov glassfish-javaee ant-junit \ testng -ANT_OPTS := -Dant.build.javac.source=1.5 -Dant.build.javac.target=1.5 +ANT_OPTS := -Dant.build.javac.source=1.6 -Dant.build.javac.target=1.6 install/libjgroups-java:: - mh_installpom -plibjgroups-java jgroups-pom.xml - mh_installjar -plibjgroups-java jgroups-pom.xml -l dist/jgroups-core.jar - install -m 644 -D dist/jgroups-all.jar $(DEB_DESTDIR)/usr/share/java/jgroups-all-$(DEB_UPSTREAM_VERSION).jar - dh_link /usr/share/java/jgroups-all-$(DEB_UPSTREAM_VERSION).jar /usr/share/java/jgroups-all.jar + mh_installpom -plibjgroups-java pom.xml + mh_installjar -plibjgroups-java pom.xml -l dist/jgroups-2.12.2.Final.jar get-orig-source: - uscan --download-version $(DEB_UPSTREAM_VERSION) --force-download --rename + uscan --debug --download-version `echo $(DEB_UPSTREAM_VERSION) | sed -e 's/\./_/g'` --force-download --rename + +clean:: + rm -rf debian/.mh + rm -f build.properties diff -Nru libjgroups-java-2.7.0.GA/doc/Announcement-2.4.txt libjgroups-java-2.12.2.Final/doc/Announcement-2.4.txt --- libjgroups-java-2.7.0.GA/doc/Announcement-2.4.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/Announcement-2.4.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,165 +0,0 @@ - -// $Id: Announcement-2.4.txt,v 1.2 2006/10/31 12:44:45 belaban Exp $ - - -Finally, after almost 5 months, JGroups 2.4 is here ! - -There are some cool features that I'll describe in more detail -below. Over 80 JIRA issues were resolved in 2.4, mostly bug fixes and -new functionality. - -The good news is that 2.4 is API-backward compatible with all previous -versions down to and including 2.2.7. So, for those folks who are -using JBoss 4.0.x, which ships with JGroups 2.2.7 by default, this -means that they can simply replace their JGroups JAR file with the one -from 2.4 and benefit from the performance enhancements and bug fixes -that went into 2.4. For details on the JBoss/JGroups combinations see -http://labs.jboss.com/portal/jbosscache/compatibility/index.html. - -I'll now describe the new features briefly, check out the -documentation (URL below) for a full discussion. - - -FLUSH ------ - -Flush is a feature that - whenever the group membership is to be -changed, or a state to be transferred - we tell every node in the -cluster to stop sending messages and then join/leave a node, or -transfer the state. When done, we tell all members to resume message -sending. - -Why is this needed ? - -In 2 cases: (1) when we use a Multiplexer (see below) and have -multiple services sharing the same channel that require state transfer -and (2) when we want virtual synchrony (see below). - -So, if you don't use the Multiplexer or don't need virtual synchrony, -you don't need the FLUSH protocol in your configuration. FLUSH is -quite expensive because it uses multiple rounds of multicasts across -the cluster, so remove it if you don't need it. Note that JBoss 5 -requires FLUSH because it uses the Multiplexer: all cluster services -share one JGroups channel. - - -Multiplexer ------------ - -The Multiplexer was mainly developed to accommodate multiple services -running on top of the *same* channel. JGroups channel are quite -expensive in their use of resources (mainly threads) and sharing a -channel amortizes a channel over multiple services. - -This is beneficial in JBoss where we had 5 clustered services (in -4.0.x), each using its own channel. In JBoss 5, we switched to the -Multiplexer, and all 5 services use the same shared channel. Startup -time in JBoss 5 ('all' configuration) was 43s on my laptop before the -change, and 23s afterwards ! - -If multiple services sharing a channel require state transfer, we run -into the problems described in -JGroups/doc/design/PartialStateTransfer.txt. FLUSH is required to -prevent those problems. - -The Multiplexer is described in chapter 6.3 of the documentation (see -below). - - -Streaming state transfer ------------------------- - -So far, state has always been transferred using a byte[] buffer. This -forced a user to serialize the entire state into a byte[] buffer at -the state provider and unserialize the byte[] buffer into the -application state at the state requester. If the state is 2GB or more, -this might likely result in memory exhaustion. - -Streaming state transfer uses input and output streams, so users can -stream their state to the output stream in *chunks* and don't need to -have the entire state in memory as required by a byte[] buffer. On the -receiving side, an input stream is provided from which the user can -read the entire state and set the application state to it. - -Streaming state transfer is essential for large states ! - - -Partial state transfer ----------------------- - -This allows a programmer to transfer a subset of the entire state, -identified by an ID. We use this (via JBossCache) in JBoss HTTP -session replication/buddy replication, where only the state -represented by a buddy is transferred. - - -Virtual Synchrony ------------------ - -Virtual Synchrony is a model of group communication, developed by Ken -Birman at Cornell, which has the following properties: -#1 All non-faulty members see the same set of messages between views -#2 A message M sent by P in view V must be received by P in V, unless -P crashes -#3 All non-faulty members receive the same sequence of views - -With FLUSH, we re-implemented the old virtual synchrony implementation -of JGroups (vsync.xml), which I wrote in 1998/1999, but which has -never been tested rigorously. We will phase out the old implementation -in 3.0, along with other reorganizations of the protocol stack -packages. - -Note that we have not yet *fully* implemented virtual synchrony as -flushing only flushes out messages *sent* by member P, but not those -*received* by P. Therefore, if member A sends a message M to {A,B,C}, -and crashes immediately afterwards, and only B received M, then C will -*not* receive M (violating rule #1 above). We will fully implement -this in JGroups 2.5 (http://jira.jboss.com/jira/browse/JGRP-341). - - -View bundling -------------- - -When a large number of nodes join or leave a cluster at about the same -time, we can collect all JOIN/LEAVE requests and create only 1 -view. View installation is quite costly, especially if FLUSH is used, -which requires some round trips across the cluster, and so we minimize -them. - - -Failure detection ------------------ - -We have 2 new protocols: FD_PING and FD_ICMP which allow for scripts -to be run in order to check the health of a node (FD_PING) and ICMP to -ping a machine (FD_ICMP). These can of course be combined with other -failure detection protocols, such as FD or FD_SOCK. - - -Updated documentation ---------------------- - -The new features have been added to the documentation at -http://www.jgroups.org/javagroupsnew/docs/manual/html/index.html - - -JIRA issues ------------ - -The issues that went into 2.4 can be found at -http://jira.jboss.com/jira/browse/JGRP. - - -Acknowledgments ---------------- - -I'd like to thank Vladimir Blagojevic for writing the FLUSH and -streaming/partial state transfer features, and for testing them -thoroughly ! - - - -Enjoy ! - -Bela Ban, Red Hat Inc -Kreuzlingen, Oct 31 2006 \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/doc/bugs/Digest.txt libjgroups-java-2.12.2.Final/doc/bugs/Digest.txt --- libjgroups-java-2.7.0.GA/doc/bugs/Digest.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/bugs/Digest.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,28 +0,0 @@ - -Digest on view changes -====================== - -Author: Bela Ban -Version: $Id: Digest.txt,v 1.1 2006/01/26 09:23:16 belaban Exp $ - - -With STATE_TRANSFER we disable message garbage collection with SUSPEND_STABLE for the duration of the state transfer -and later resume it with RESUME_STABLE. However, we don't do this for regular view changes, e.g. when a new member joins. - -The problem is: -- Member A is in the group -- Member B joins -- Member A (as coord) sent 5 messages and therefore returns 5 as digest (assuming it has received all 5 messages yet) -- Member B receives the JoinRsp and installs the digest of A:5 -- In the meantime, member A sent another 10 messages, which triggered garbage collection of messages - A:0 - A:13 (just as example), so A has only messages 13-15 in its sent_table -- A sends another message (A:16) -- Upon reception of A:16, B requests retransmission of A:6 - A:16 -- A will not be able to serve that request because it garbage collected messages up to 13 - - -SOLUTION: -- Same as with STATE_TRANSFER: -- On JOIN request, send down a SUSPEND_STABLE -- When JOIN request handling has processed (e.g. after receiving all VIEW_ACKs ?), send a RESUME_STABLE down -- Does this need to be done only on JOIN or also on LEAVE ? \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/doc/bugs/MergeProblem.txt libjgroups-java-2.12.2.Final/doc/bugs/MergeProblem.txt --- libjgroups-java-2.7.0.GA/doc/bugs/MergeProblem.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/bugs/MergeProblem.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,79 +0,0 @@ - - -Merge problem in GMS/MERGE2 -=========================== - -Author: Bela Ban -Version: $Id: MergeProblem.txt,v 1.1 2006/01/27 14:46:11 belaban Exp $ - -Symptom: first merge works, subsequent merges don't work -Unit test: MergeStressTest - -The problem is that when many members join, we increment the VID (ViewID) every time we multicast a new view. -Say we have subgroups {A,B,C} and {D,E}. The merge is now started by MERGE2, detecting 2 coordinators (A and D). -The VID are 4 for {A,B,C} and 6 for {D,E}. -The merge coordinator (A) therefore picks VID=7 for the MergeView. It now sends a request to both A and D, to multicast -the new MergeView to their respective subgroups. -However, let's assume that in the meantime, other members have joined one of the 2 subgroups, so that the VIDs are now: - -{A,B,C}: VID=9 -{D,E}: VID=5 - -When the MergeView is received, it will be rejected by {A,B,C} because its own VID of 9 is greater than the -MergeView's VID of 7. However, subgroup {D,E} will accept the MergeView because its own VID of 5 is smaller than the -MergeView's VID of 7 ! - -So now the -A, B and C have a membership of {A,B,C} with VID=9 -but -D and E have a membership of {A,B,C,D,E} with VID=7 ! - -One of the consequences of this is that D will cease sending out MERGE2 requests, because it thinks it is not -the coordinator anymore ! This means we will not get any MERGE2 events into the GMS layer anymore, which would -cause another merge, this time with a higher VID for the MergeView. - -Possible SOLUTIONs: - -#1 Pick a VID for MergeViews that is high enough to be accepted by all subgroups -#2 Suspend generating view changes while a merge is in progress -#3 All JOIN, LEAVE and MERGE requests need to be handled by ViewBroadcaster (rename this!), check out - ViewHandling.txt for details - -Comments: - -- #1 doesn't work well because then all previously generated views will be discarded, but clients already - *have* the views ! - -- We have to process VIEWS and MERGE requests in order, possibly we need to flush the ViewBroadcaster and suspend - view generation until the merge has been handled, then resume view generation - -- Therefore #2 looks more promising: on merge() we flush the ViewBroadcaster (send out all pending views) on *all* - coordinators involved. Then we process the merge, generate and mcast the new MergeView, then resume - ViewBroadcaster. Probably JOIN and LEAVE requests need to be handled by ViewBroadcaster too - - -Design: -------- - -On MERGE_REQ: -- suspendViewHandler() - -On CANCEL_MERGE: -- resumeViewHandler() - -On firing of MergeCanceller: -- If MergeCancellers merge_id == current merge_id: - - resumeViewHandler() - -On INSTALL_MERGE_VIEW: -- Install view -- resumeViewHandler() - -suspendViewHandler(): -- Complete current task, remove all other requests from queue -- Make view handler discard all new requests from now on -- Start MergeCanceller (if not started yet) - -resumeViewHandler(): -- Stop MergeCanceller -- Make view handler accept all requests from now on \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/doc/CONFIGURATIONS libjgroups-java-2.12.2.Final/doc/CONFIGURATIONS --- libjgroups-java-2.7.0.GA/doc/CONFIGURATIONS 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/CONFIGURATIONS 1970-01-01 00:00:00.000000000 +0000 @@ -1,133 +0,0 @@ -## $Id: CONFIGURATIONS,v 1.2 2007/11/19 16:07:53 belaban Exp $ - - - Frequently used protocol stack specifications - ============================================= - - - - -Virtual Synchrony & State Transfer Protocol Stack -------------------------------------------------- - -UDP: -PING(num_initial_members=2;timeout=3000): -FD: -STABLE: -NAKACK: -UNICAST: -FRAG: -FLUSH: -GMS: -VIEW_ENFORCER: -STATE_TRANSFER: -QUEUE - - -Properties: - -Uses Virtual Synchrony. All messages sent in a view V1 are delivered in -that view. The FLUSH protocol makes sure that, when a new view V2 is to -be installed, that all messages sent in V1 will be seen by all members -of V1 before V2 is installed. The FLUSH protocol stops all sending -until V2 has been installed successfully at all members. Messages sent -after the block has been received and before V2 is installed will be -sent in V2. - - -Protocols: - -UDP: uses UDP/IP multicast as transport - -PING: discovers initial set of members, determines coordinator to - which the join-request will be sent - -FD: failure detection based on periodic pinging of member 'to the - right' in the membership ring - -STABLE: garbage collection of messages seen by all members - -NAKACK: guarantees lossless 1-m message delivery, uses negative acks - to retransmit lost messages - -UNICAST: guarantees lossless 1-1 message delivery, uses positive acks - to retransmit lost messages - -FRAG: fragments large messages into smaller ones and reassembles them - at the receiving side - -FLUSH: flushes all pending multicasts out of the system before - transitioning to a new view. Ensures that all members of view - V1 agree on the set of messages they delivered in V1. - -GMS: group membership service. Takes care of joining/leaving members - -VIEW_ENFORCER: only accepts messages from senders in the same - view. Stores messages for future view, discards messages - sent in previous view. - -STATE_TRANSFER: allows any member to fetch the state from any other - member (usually done immediately after joining) - -QUEUE: queues messages sent during a view transition. When the new - view is installed, the stored messages will be sent. - - - - - - -Pbcast-based Protocol Stack ---------------------------- - -UDP(mcast_addr=228.1.2.3;mcast_port=45566;ip_ttl=0): -PING(timeout=5000;num_initial_members=6): -FD_SOCK: -VERIFY_SUSPECT(timeout=1500): -pbcast.STABLE(desired_avg_gossip=10000): -pbcast.NAKACK(gc_lag=5;retransmit_timeout=3000;trace=true): -UNICAST(timeout=5000;min_wait_time=2000): -FRAG(down_thread=false;up_thread=false): -pbcast.GMS(join_timeout=5000;shun=false;print_local_addr=true) - - -Properties: - -Defines a stack with weaker reliability semantics than the Virtual Synchrony protocol -stack. The biggest difference is that there is no guarantee that the set of messages -sent between views is the same (no FLUSH protocol). Essentially defines a reliable 1-m -protocol, where views are just regular messages and have no special semantics. - - -Protocols: - -UDP: uses UDP/IP multicast as transport. Uses a time-to-live of 0 - -PING: discovers initial set of members, determines coordinator to - which the join-request will be sent. Will wait for 5 seconds or 6 members to - respond (whichever is first) - -FD_SOCK: failure detection based on TCP socket connection from each member to the - member 'to the right' in the membership ring. When connection breaks, member - is suspected. Compared to FD, there are no periodic ping messages sent - -VERIFY_SUSPECT: reduces false suspicions. Verifies that a member P that is suspected - is really dead by sending a ping message to P. - -pbcast.STABLE: garbage collection of messages seen by all members using gossips. A - gossip is multicast every 10 seconds on average by each member. - -pbcast.NAKACK: guarantees lossless 1-m message delivery, uses negative acks - to retransmit lost messages - -UNICAST: guarantees lossless 1-1 message delivery, uses positive acks - to retransmit lost messages - -FRAG: fragments large messages into smaller ones and reassembles them - at the receiving side - -pbcast.GMS: group membership service. Takes care of joining/leaving members - - - - diff -Nru libjgroups-java-2.7.0.GA/doc/design/AddressTranslation.txt libjgroups-java-2.12.2.Final/doc/design/AddressTranslation.txt --- libjgroups-java-2.7.0.GA/doc/design/AddressTranslation.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/AddressTranslation.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: AddressTranslation.txt,v 1.1 2006/01/27 14:46:12 belaban Exp $ On multi-homed systems, the identity of a member is bound to a NIC (either chosen by the OS, or by the user through bind_addr): Address. When a message is sent, the msg contains this address as the sender's diff -Nru libjgroups-java-2.7.0.GA/doc/design/AUTH.txt libjgroups-java-2.12.2.Final/doc/design/AUTH.txt --- libjgroups-java-2.7.0.GA/doc/design/AUTH.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/AUTH.txt 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ ======================================== Author: Roland Raez, Bela Ban, Chris Mills -Version: $Id: AUTH.txt,v 1.1 2006/01/27 14:46:12 belaban Exp $ Goal: to prevent random members from joining a group. Members have to pass authentication to join a group, otherwise they will be rejected diff -Nru libjgroups-java-2.7.0.GA/doc/design/Bundling.txt libjgroups-java-2.12.2.Final/doc/design/Bundling.txt --- libjgroups-java-2.7.0.GA/doc/design/Bundling.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/Bundling.txt 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ Message bundling ================ -// Version: $Id: Bundling.txt,v 1.1 2006/01/27 14:46:12 belaban Exp $ // Author: Bela Ban Init: diff -Nru libjgroups-java-2.7.0.GA/doc/design/CLOUD_TCP.txt libjgroups-java-2.12.2.Final/doc/design/CLOUD_TCP.txt --- libjgroups-java-2.7.0.GA/doc/design/CLOUD_TCP.txt 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/CLOUD_TCP.txt 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,43 @@ + + +Changes to TCP to make it scale better in a cloud +================================================= + +Author: Bela Ban + + +Problem: +-------- + +In many cloud services (EC2, rackspace, GAE), IP multicasting is not allowed, therefore users of JGroups have to switch +to TCPGOSSIP:TCP in conjunction with a GossipRouter for initial lookup. + +With TCP, we send a cluster wide message to N-1 nodes (with UDP, we send it only once). This doesn't scale with +increasing clusters. Even if we parallelize this, all the traffic from us to each node has to go through the switch, +taxing our full-duplex line to the switch. + +Solution: +--------- + +Eliminate the N-1 problem by sending a cluster-wide message only to our neighbor. The neighbor then sends it to its +neighbor and so on. Each message has a header with a TTL, which is equal to the cluster size. When a (cluster wide) +message is received, we decrement the TTL in the header and forward it to our neighbor (unless the TTL is 0, then we +discard it). + +For retransmissions, we send an XMIT-REQ to the original sender the first time, then to its neighbor, then to the +next neighbor and so on. + + +Implementation: +--------------- + +This functionality can probably be done in a subclass of TCP (or maybe even be integrated into TP !). + +Update: IMO a better approach is to create a new protocol layered on top of the transport. This way, we can use this +functionality with other transports as well. + +Take a look at Ron Levy's EPFL paper [1] for a similar approach. + + +[1] http://infoscience.epfl.ch/getfile.py?docid=7701&name=paper&format=pdf&version=1 +[2] infoscience.epfl.ch/record/149218/files/paper.pdf \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/doc/design/ConcurrentConnectionEstablishment.txt libjgroups-java-2.12.2.Final/doc/design/ConcurrentConnectionEstablishment.txt --- libjgroups-java-2.7.0.GA/doc/design/ConcurrentConnectionEstablishment.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/ConcurrentConnectionEstablishment.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,6 +1,5 @@ -// Version: $Id: ConcurrentConnectionEstablishment.txt,v 1.3 2007/09/19 12:42:43 belaban Exp $ // Author: Bela Ban diff -Nru libjgroups-java-2.7.0.GA/doc/design/ConcurrentStack.txt libjgroups-java-2.12.2.Final/doc/design/ConcurrentStack.txt --- libjgroups-java-2.7.0.GA/doc/design/ConcurrentStack.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/ConcurrentStack.txt 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ ================ Author: Bela Ban -Version: $Id: ConcurrentStack.txt,v 1.1 2006/12/27 10:02:04 belaban Exp $ JIRAs: http://jira.jboss.com/jira/browse/JGRP-180 (harden stack) http://jira.jboss.com/jira/browse/JGRP-181 (concurrent stack) diff -Nru libjgroups-java-2.7.0.GA/doc/design/ConcurrentStartupTest.txt libjgroups-java-2.12.2.Final/doc/design/ConcurrentStartupTest.txt --- libjgroups-java-2.7.0.GA/doc/design/ConcurrentStartupTest.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/ConcurrentStartupTest.txt 2011-10-18 11:22:35.000000000 +0000 @@ -3,7 +3,6 @@ ===================================== // Author: Bela Ban -// Version: $Id: ConcurrentStartupTest.txt,v 1.2 2006/05/20 22:02:12 belaban Exp $ The unit test org.jgroups.tests.ConcurrentStartupTest simulates multiple nodes in a cluster being started at the same diff -Nru libjgroups-java-2.7.0.GA/doc/design/DataCenterReplication.txt libjgroups-java-2.12.2.Final/doc/design/DataCenterReplication.txt --- libjgroups-java-2.7.0.GA/doc/design/DataCenterReplication.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/DataCenterReplication.txt 2011-10-18 11:22:35.000000000 +0000 @@ -3,7 +3,6 @@ ================================ Author: Bela Ban -Version: $Id: DataCenterReplication.txt,v 1.9 2008/07/02 11:24:37 belaban Exp $ We have data centers in New York (NYC) and San Francisco (SFO). The idea is to replicate traffic from NYC to SFO asynchronously. In case of a site failure of NYC, all clients can be switched over to SFO and continue working with @@ -100,4 +99,8 @@ No, doesn't work ! The relay would have to be active on every node ! This is not feasible as this would require every node to have a TCP connection to NYC ! We still need to do it on reception not sending. However, messages -cannot exclude the sender \ No newline at end of file +cannot exclude the sender + +***************************************************** +*** UPDATE: this design is continued in RELAY.txt *** +***************************************************** diff -Nru libjgroups-java-2.7.0.GA/doc/design/FILE_PING.txt libjgroups-java-2.12.2.Final/doc/design/FILE_PING.txt --- libjgroups-java-2.7.0.GA/doc/design/FILE_PING.txt 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/FILE_PING.txt 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,41 @@ + + +Design of FILE_PING +=================== + +Author: Bela Ban + + +Goal +---- + +FILE_PING is a shared storage based discovery; during the discovery process, +new nodes read the addresses of existing members from a shared store. + +Members write their addresses to the store and remove them on leaving the cluster. + + +Design +------ + +FILE_PING takes as property a 'location' which is the location of a directory in a shared store, e.g. /share/jgroups. +Each cluster X creates a subdirectory X, where all files for cluster X reside. + +A member writes its local address (plus physical address mappings) to a file which is named after the local address. +Example: /share/jgroups/DemoCluster/linux-433234.dat. (Maybe we should use the UUID !) + +A new member reads all files in DemoCluster and sends the discovery request to all addresses resulting from this. + +When a member leaves, it removes its file from DemoCluster. We could use File.removeOnExit() to do this automatically. +(However, kill -9 doesn't remove the file). + +We still need to periodically clean up members who crashed (kill -9). This could be done via an age out cache. + + +Notes +----- + +In 2.6.x, we don't have logical addresses, therefore we don't need the discovery messages to ship logical-physical +address mappings around. As an optimization, we could read all files and see if we have an element tagged as +coordinator. If so, we could directly send a JOIN request to the coord, rather than sending discovery messages. +If there is no coordinator, we go through the regular discovery message sending process. \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/doc/design/FlowControl.txt libjgroups-java-2.12.2.Final/doc/design/FlowControl.txt --- libjgroups-java-2.7.0.GA/doc/design/FlowControl.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/FlowControl.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// Version: $Id: FlowControl.txt,v 1.1 2006/01/27 14:38:35 belaban Exp $ // Author: Bela Ban diff -Nru libjgroups-java-2.7.0.GA/doc/design/FLUSH2.txt libjgroups-java-2.12.2.Final/doc/design/FLUSH2.txt --- libjgroups-java-2.7.0.GA/doc/design/FLUSH2.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/FLUSH2.txt 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ ============= Author: Bela Ban -Version: $Id: FLUSH2.txt,v 1.5 2007/10/22 19:46:52 belaban Exp $ Prerequisites: - A flush is always started and stopped by the *same* member, both for joins and state transfer @@ -82,4 +81,63 @@ Reconciliation phase (in NAKACK): - Sends XMIT requests for message not in local digest, but in digest received from REBROADCAST_MSGS - Wait until local digest (updated when missing messages are received) is same as digest received from REBROADCAST_MSGS -- Takes suspects into account \ No newline at end of file +- Takes suspects into account + + + + +Concurrent flushes +------------------ + +- Concurrent flushes to overlapping member sets will cause only 1 flush to succeed, and all others to fail + +- A failed flush set the flushInProgress flag back to false on all members on which it successfully set it + +- Algorithm: + - Send a START-FLUSH to all members in the target set + - Every member sets the flushInProgress flag to true + - If this succeeds, the member sends back an OK + - Else a FAIL is sent back + - If we received OKs from all members, startFlush() succeeded and returns true + - If 1 FAIL is received: + - Send an ABORT-FLUSH to all members which took part in the flush, causes flushInProgress to be set to false + - startFlush() failed and returns false + + +Concurrent total flushing (members={A,B,C,D}) +--------------------------------------------- + +- In general, concurrent total flushes are not allowed + +- Concurrent flushes on the same member: + - The first thread to call Channel.startFlush() runs the flush phase + - Subsequent threads to call Channel.startFlush() block until the current flush phase has completed + (Channel.stopFlush()) + +- Concurrent flushes on different members: + - A.startFlush() and B.startFlush() are called concurrently + - A sends a START-FLUSH to all members, and so does B + - When a START-FLUSH is received, a flag 'flushing-in-progress' is set + - If flushing-in-progress cannot be set (because another flush protocol is running), we send back a + START-FLUSH-FAIL. (We might add a small timeout to block until flushing-in-progress can be acquired) + - On reception of a START-FLUSH-FAIL, we 'roll back' the flush, setting all of our acquired 'flushing-in-progess' + flags back to false + - SUMMARY: either A fails, or B fails, or both A and B fail in a concurrent flush phase (similar to concurrent + transactions) + + +Concurrent partial flushing (members={A,B,C,D}) +----------------------------------------------- + +- In general, concurrent partial flushes are not allowed (same as for concurrent total flushes) + + + + + + + + + + + diff -Nru libjgroups-java-2.7.0.GA/doc/design/FLUSH.txt libjgroups-java-2.12.2.Final/doc/design/FLUSH.txt --- libjgroups-java-2.7.0.GA/doc/design/FLUSH.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/FLUSH.txt 2011-10-18 11:22:35.000000000 +0000 @@ -3,7 +3,6 @@ ================================ Author: Bela Ban -Version: $Id: FLUSH.txt,v 1.11 2007/11/08 14:34:52 vlada Exp $ See http://jira.jboss.com/jira/browse/JGRP-82 Overview diff -Nru libjgroups-java-2.7.0.GA/doc/design/GossipRouterChanges-1.8.txt libjgroups-java-2.12.2.Final/doc/design/GossipRouterChanges-1.8.txt --- libjgroups-java-2.7.0.GA/doc/design/GossipRouterChanges-1.8.txt 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/GossipRouterChanges-1.8.txt 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,108 @@ + + +Changes to GossipRouter in 2.8 +============================== +Author: Bela Ban + + +Motivation +---------- + +The changes are mainly due to logical addresses and shared transport. + +TCPGOSSIP currently doesn't work, as the GossipRouter doesn't return logical addresses *and* their associated physical +addresses, but only returns logical addresses (UUIDs) [1]. + +In TUNNEL, RouterStubs are conceptually associated with a single channel, and cannot process traffic from multiple +channels on top of a shared transport. + +[1] https://jira.jboss.org/jira/browse/JGRP-1005 +[2] https://jira.jboss.org/jira/browse/JGRP-924 + + +Design overview +--------------- + +A RouterStub will simply establish a TCP connection with a GossipRouter, but doesn't send the CONNECT message on +socket establishment. This is typically done in init() of TUNNEL (or TCPGOSSIP). + +On channel connection, a CONNECT(groupname, logical_addr, logical_name, physical_addresses) is sent to the +GossipRouter. + +On a GET-MEMBERS(groupname), the GR returns a list of elements. + +On a ROUTE(groupname, logical_addr, message), the GR pick the single destination (if logical-addr is not null) and +routes the message. If logical_addr is null, then the GR routes the message to all members keyed by groupname. + +The GossipRouter maintains the following data structures: + +- RoutingTable: + - A hashmap with groupnames as keys and hashmaps as values + - The 2nd level hashmaps have logical addresses as keys and ConnectionHandlers as values + - A ConnectionHandler represent a physical connection to a client. It has a thread listening for messages on the + input stream, and it also has a list of logical_addrs from which it received CONNECT messages. + - When the socket in the ConnectionHandler is closed by the peer, we remove all entries associated with all + logical_addrs of ConnectionHandler from the other data structures. + +- AddressMappings: + - Maintains a hashmap with logical_addrs as keys and the physical_addrs as value + - This is used to map logical addresses to their physical addresses, and is only used by TCPGOSSIP + + +Implementation +-------------- + +RouterStub +---------- +- When started, the RouterStub establishes a socket to the GossipRouter's address:port given +- When the connection is closed by the GR, the stub goes into reconnecting mode. When reconnected, it issues + a notification so registered listeners can send a CONNECT (see below) to the GR again +- When the CONNECT event is received by TUNNEL / TCPGOSSIP, RouterStub.connect() is called with + - the groupname + - the logical address + - the logical name + - a list of physical addresses (might be null, only used by TCPGOSSIP) +- This generates a CONNECT message which is sent to the GossipRouter + + +GossipRouter +------------ + +- On accept(): + - Create a new ConnectionHandler on a new thread (from the thread pool) + - This stores the client socket, input and output stream, then listens on the input stream + [- We might add the ConnectionHandler to a separate list, just to keep track of open connections] + +- On peer (RouterStub) closing the socket: + - The ConnectionHandler gets an exception when listening on the input stream + - We remove the entries in connection-list from all tables + +- On CONNECT(groupname, logical_addr, logical_name, physical_addrs) [received by ConnectionHandler.run()]: + - The ConnectionHandler adds the logical address to its connection-list + - An entry is added to RoutingTable under groupname, and the value (hashmap) is updated: + - key is the logical addr, value the ConnectionHandler (this) + - If CONNECT ships with non-null physical_addrs, then an entry is added to AddressMappings + + +- On DISCONNECT(logical_addr): + - Remove the entry for logical_addr from all tables (RoutingTable, AddressMappings) + - Do *not* close the socket in ConnectionHandler: others may still be connected through the same connection ! + + +- On GET_MEMBERS(groupname): + - Grabs the members for groupname (from RoutingTable and AddressMappings) and passes them back, for each member: + - The logical address + - The logical name + - (if available) a list of physical addresses + +- On ROUTE(groupname, dest, message): + - If dest == null (multicast): + - Grab all ConnectionHandler's from RoutingTable for groupname and send the message to all + - Else (unicast) + - Find the ConnectionHandler keyed by 'dest' and send the message to it + +TUNNEL +------ + +- On stub reconnect: + - Send a CONNECT message. With a shared transort, this has to be done for all channels sharing the transport \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/doc/design/JDBC_PING.txt libjgroups-java-2.12.2.Final/doc/design/JDBC_PING.txt --- libjgroups-java-2.7.0.GA/doc/design/JDBC_PING.txt 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/JDBC_PING.txt 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,71 @@ + + +Design of JDBC_PING +=================== + +Author: Sanne Ginovero + + +Goal +---- + +JDBC_PING is a discovery protocol making use of a shared database; during the discovery process, +new nodes read the addresses of existing members from a JDBC connection. + +The design is derived from FILE_PING, and the implementation extends it as there are many similarities. +As with FILE_PING, also with JDBC_PING members write their addresses to the store and remove them on leaving the cluster. + + +Usage +------ + +A database is needed, and a single table will be used. Something like the following is expected: + +CREATE TABLE JGROUPSPING ( + own_addr varchar(200) NOT NULL, + cluster_name varchar(200) NOT NULL, + ping_data varbinary(5000) DEFAULT NULL, + PRIMARY KEY (own_addr, cluster_name) + ) + +It's possible to change the table definitions, make sure to update the three SQL statements accordingly using the appropriate +configuration properties: + + initialize_sql - to customize the table creation SQL instructions + insert_single_sql - to insert a new row + delete_single_sql - to delete a single row + select_all_pingdata_sql - to load all ping_data having a specific cluster_name + +Connection properties to be set are either: + +1) JDBC direct connection + + connection_url + connection_username + connection_password + connection_driver + +2) via a JNDI registered DataSource + + datasource_jndi_name + + +Design +------ + +Each node connects to the same database, and reads all Addresses related to his same cluster to find it's peers, +and adds himself to the list. + +At stop(), a node attempts to cleanup the table by removing himself. + +Members who crashed should be removed by the same strategy of FILE_PING, actually it should inherit each eventual +improvement. + +Warning: +While the node stores the PingData instance of himself, to be retrieved by other nodes, it will nullify the View, +as there's no interest in restoring the View instance and this could potentially not fit in the fixed column dimension +allocated for a serialized PingData, or at least this variable is not fixed so storing View might make it hard to +select a proper size. +So consider that a restored PingData will have lost the information about the original View. + + diff -Nru libjgroups-java-2.7.0.GA/doc/design/LargeClusters.txt libjgroups-java-2.12.2.Final/doc/design/LargeClusters.txt --- libjgroups-java-2.7.0.GA/doc/design/LargeClusters.txt 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/LargeClusters.txt 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,38 @@ + + +Considerations for large clusters +================================= + +Author: Bela Ban +JIRA: https://jira.jboss.org/browse/JGRP-100 + + +Discovery +--------- + + + +ConcurrentHashMap +----------------- + +CCHMs have a default initial capacity (16), load factor (0.75) and concurrency level (16). These are OK for most +scenarios, but we have to investigate whether these values are sufficient for 1000 node clusters. + +When for example 1000 threads from different senders access the same CCHM, we need to make sure we don't have high +contention, ie. by spreading a 1000 senders over 16 buckets. + +Investigate whether we should add CCHM initial sizes, load factors and concurrency levels as properties. + +With ergonomics [1], we could for example set bucket sizes for CCHMs dynamically, e.g. based on cluster size. + + + +[1] https://jira.jboss.org/jira/browse/JGRP-1037 + + +Misc +---- + +- MERGE2: num_initial_members in Discovery should be large, so that we detect partitions sooner +- GMS: view bundling should be enabled, and max_bundling_time should be a bit larger than the default, as this + will allow for more concurrent joins and leaves before starting the view installation process \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/doc/design/LargeMessages.txt libjgroups-java-2.12.2.Final/doc/design/LargeMessages.txt --- libjgroups-java-2.7.0.GA/doc/design/LargeMessages.txt 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/LargeMessages.txt 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,53 @@ + +Large messages in large clusters +================================ + +Author: Bela Ban + +Requirements +------------ +- Large cluster +- 1 sender, many receivers +- Sender sends messages between 10MB and 200MB, in bursts + +Goals: +------ +- Receivers discard message (purge it from memory) as soon as it has been delivered to the application +- Sender discards message as soon as it has received acks from all receivers or a timeout has elapsed +--> We don't want to keep 200MB messages in buffers longer than necessary +- Don't allow single receiver to bog down the whole cluster (in times and memory foot print) + + +Issues with existing protocols: +------------------------------- +- We cannot use STABLE for agreement, so that a sender can purge a message, because one or more slow receivers could + prevent stability messages to be sent. This is because STABLE requires agreement from all group members, and slow + members won't be able to agree, at least not fast enough. +- We therefore wait for agreement from all members, but if a timeout elapses, the sender will purge the message anyway +- Receivers are not guaranteed to receive all messages: if the sender purged a message after not having received acks + from all members, the receiver will not receive that message +- However, even with message loss, all *received* messages will be delivered in order +- We cannot use SMACK, which uses positive acks for each message, because that would lead to too many acks. If we for + example send a 200MB message and it is fragmented into 2000 fragments, we don't want to send an ack / fragment, but + we only want to send an ack per message, so after the entire 200MB message has been received + +Design: +------- +- A new protocol ACK, layered above FRAG2 (or any other fragmentation protocol) +- When sending a message, ACK creates a list of members from which it needs to receive acks and starts a timer +- When ACK receives a message it sends an ack back to the sender +- When all acks have been received, ACK sends down a STABLE event, which will cause the sender's NAKACK protocol to + purge the message +- When the timer kicks in, it returns if all acks have been received or (if not) sends a stability message around, + which causes all receivers to ask for retransmission of messages they haven't received yet. The timer cancels itself + after N attempts or when all acks have been received +- The receivers also start a timer when the first retransmission of a message occurs +- If a message has not been received after a timeout, the receivers will flag that message as not-received and stop + retransmission requests for it. When the special not-received message is removed, it won't be passed up + + +Flow control: +------------- + +- We cannot have a sender block on a credit missing from a slow member +- Solution: use max_block_times in FC, which is already implemented diff -Nru libjgroups-java-2.7.0.GA/doc/design/LogicalAddresses.txt libjgroups-java-2.12.2.Final/doc/design/LogicalAddresses.txt --- libjgroups-java-2.7.0.GA/doc/design/LogicalAddresses.txt 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/LogicalAddresses.txt 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,243 @@ + + +Logical addresses +================= + +Author: Bela Ban +JIRA: https://jira.jboss.org/jira/browse/JGRP-129 + +The address chosen by each node is essentially the IP address and port of the receiver socket. However, for the +following reasons, this is not good enough: + +- Reincarnation: if we use fixed ports (bind_port is set), then a restarted (or shunned) node will have the same + address. If other nodes in the cluster don't clear their states before the reincarnated node comes up again, we'll + have issues (see JGRP-130 for details) + +- NIC failover: a NIC goes down, we want to continue sending/receiving on a different NIC + +- The sender sends on all available NICs (send_on_all_interfaces="true"). This means that -if we take the receiver's + datagram packet's address to be the identity of the sender - we get N different identities; 1 for each interface + the message is sent on + +- Network Address Translation: the sender's address might get changed by the NAT + +DESIGN: + +- A logical address consists of a unique identifier (UUID) and a logical name. The name is passed to JGroups when a + channel is created (new JChannel(String logical_name, String props)). If logical_name is null, JGroups picks a + logical name (which is not guaranteed to be unique though). The logical name stays with the channel until the latter + is destroyed + +- A UUID is represented by org.jgroups.util.UUID, which is a subclass of Address and consists of the least and + most significant bits variables copied from java.util.UUID. All other instance variables are omitted. + - The isMulticastAddress() method uses 1 bit out of the 128 bits of UUID + (Maybe we can remove this method and always represent multicast addresses as nulls, so UUIDs would only be used + to represent non multicast addresses) + +- All UUIDs have a reference to a static table which contains the mappings between logical names and UUIDs + (classloader issues ?) + +- The logical name is used in UUID.toString(), and the least and most significant bits are used for equals() and + hashCode() + +- A UUID is created on channel connect, deleted on channel disconnect and re-created on channel connect. + Since it is re-created on every connect(), it will prevent reincarnation issues + +Transport (TP) +-------------- + +TP maintains a cache of mappings between UUIDs and physical addresses. Whenever a message is sent, the physical address +of the receiver is looked up from the cache. + +A UUID can have more than 1 physical address. We envisage providing a pluggable strategy which picks a physical +address given a UUID. For example, a plugin could load balance between physical addresses. + +To exchange cache information, we'll use the existing Discovery protocol (see below). + +When a physical address for a given UUID is not present, a sender discards (or queues, TBD) the message and broadcasts +a WHO-HAS message. Receivers then broadcast (or unicast) the UUID-physical address mapping. + +The discovery phase needs to return logical *and* physical addresses: on reception of a discovery request, we return +our logical address (UUID) and the physical address(es) associated with it, plus the logical name. On reception +of a discovery response, the transport places the physical addresses returned into its cache (if not yet present). +See the scenarios below for details as to why this is needed. + + +UDP startup +----------- +- The joiner multicasts a discovery request with its UUID, logical name and physical address(es) +- The receivers' transports add this information into their caches +- Each receiver unicasts a discovery response, containing the coordinator's address, plus its own UUID, logical + name and physical address(es) +- On reception of a discovery response, the transport adds this information to its cache if not yet there + + +TCPPING:TCP startup +------------------- +- The problem is that TCPPING has as initial_hosts the *physical* (not logical) addresses listed ! +- The joiner sends a discovery request to all physical addresses listed in initial_hosts +- The dest_addr field of a discovery request message is the physical address +- The transport usually expects UUIDs as addresses and finds the associated physical address in the cache +- However, in this case, the transport re-uses the physical address (dest_addr) in the message, bypasses the + translation UUID --> physical address, and nulls dest_addr in Message +- The destination address is now *null*. This works because Discovery/PING/TCPPING don't check the messages's + dest_addr field ! (Otherwise 'null' would be interpreted as a multicast destination !) +- On the receiver side, the destination is not used for discovery +- The response is sent back to the sender (Message.getSrc()), this is a UUID and will be translated back + into a physical address + + +TCPGOSSIP:TCP startup +--------------------- +- The joiner asks the GossipRouter for a list of nodes for a given cluster name +- The information returned contains a list of nodes, for each node: + - The logical address (UUID) + - The logical name + - The physical address(es) associated with the UUID +- The joiner's transport adds this information to its cache +- Then each node of the initial membership is sent a discovery request +- The rest is the same as for UDP + + +TCPGOSSIP:TUNNEL startup +------------------------ +- Same as for TCPGOSSIP:TCP, but here we don't really need the physical addresses, because we send every request + to the GossipRouter anyway (via our TCP connection to it) +- The physical address will simply be ignored + + +ARP-like functionality +---------------------- +- This is handled by Discovery +- We'll add WHO-HAS, I-HAVE and INVALIDATE message handling to Discovery +- The Discovery and transport protocols communicate via events +- When the transport wants to send a message whose destination UUID is not in its cache, it sends a (non-blocking) + WHO-HAS up the stack which is handled by Discovery. Meanwhile the message is queued (bounded queue). +- Discovery sends a WHO-HAS(UUID) message (multicast if PING, sent to initial_hosts in TCPPING, or sent to the + GossipRouter if TCPGOSSIP) +- On reception of I-HAVE, Discovery sends the result down the stack via an I-HAVE event +- When the transport receives an I-HAVE event, it updates its local cache and then tries to send the queued messages +- A discovery request also ships the logical name and physical address(es) +- A discovery response also contains these items +- When a discovery request or response is received, the cache is updated if necessary +- When a channel is closed, we send an INVALIDATE(UUID) message around, which removes the UUID from UUID.cache and + the transport's cache in all cluster nodes + + + +Runtime scenarios +----------------- + +Startup +------- +- The transport stores the logical name (generated if user didn't pass one to the JChannel() constructor) +- On connect: + - The UUID is generated (local address) and associated with the logical name (in UUID.cache) + - The local socket is created and associated with the UUID in the transport's cache +- On disconnect: the local_addr (UUID) is nulled and removed from the transport's cache and UUID.cache +- On close: an INVALIDATE(UUID) message is broadcast so every node can remove UUID from the transport's cache + and from UUID.cache + +Discovery +--------- +- Discovery fetches the local_addr (UUID), logical name and physical address(es) from the transport via a + GET_PHYSICAL_ADDRESS +- A discovery request containing this info is sent out (multicast, unicast via TCP or sent to the GossipRouter) +- The receivers (Discovery protocols) fetch this info from the message and send it down to the transport + (via a SET_PHYSICAL_ADDRESS), which adds it to its cache and to UUID.cache +- The receivers then fetch their own local information from the transport and send it along with the discovery + response +- On reception of the discovery response, the requester extracts this info from the message and sends it + down to its transport, which adds it to its own local cache + +Sending of a message with no physical address available for UUID +---------------------------------------------------------------- +- The transport queues the message +- The transport sends a GET_PHYSICAL_ADDRESS *event* up the stack +- The Discovery protocol sends out a GET_MBRS_REQ *message* (via multicast, TCP unicast, or to the GossipRouter) +- The receivers fetch their local information from the transport (exception: the GossipRouter has this information + in its local loookup cache), and return it with a GET_MBRS_RSP message +- On reception of the GET_MBRS_RSP message, a SET_PHYSICAL_ADDRESS event is sent down the stack to the transport +- The transport updates its local cache from the information +- The transport sends all queued messages if the UUID are now available + + +On view change +-------------- +- The coordinator's Discovery protocol broadcasts an INVALIDATE(UUID) message for each node which left the cluster +- On reception, every receiver sends down a REMOVE_PHYSICAL_ADDRESS(UUID) +- The transport then removes the mapping for the given UUID + + +IDs instead of UUIDs +-------------------- +- Implemented in TP or a separate protocol (ID ?) +- The coordinator dishes out IDs (shorts) for new nodes +- The IDs are always increasing +- Every new ID is broadcast across the cluster, so everyone knows the highest IDs +- An ID is associated with a UUID, and UUIDs are also associated with physical addresses (2 tables) +- When we send a message to dest UUID, we lookup the ID associated with UUID. If found, we send the ID (a short) + rather than the UUID +- On reception, if an ID is found, we create an IdAddress, which work on the short field for equals() and hashCode() +- IdAddress.equals() and IdAddress.compareTo() can compare to both IdAddress *and* UUIDs: in the latter case, they + fetch the ID from the table given the UUID as key and do the comparison. UUIDs can also compare to IdAddresses +- We should probably either add a PhysicalAddress interface, which inherits from Address, and have physical addresses + implement PhysicalAddress (so we can do an instanceof PhysicalAddress), or have an isPhysicalAddress() method +- ID canonicalization should be configurable: we can enable or disable it +- This could be used by an ID protocol (sitting on top of the transport), which maintains a UUID-ID table and *replaces* + dest and src addresses for messages coming in and going out +- This protocol would replace an UUID dest with an IdAddress and provide the physical address as well (?) + + +Shared transport and UUIDs +-------------------------- +- With a shared transport, every channel has a local_addr: the transport itself cannot have a local_addr anymore. The + reason is that a channel could get shunned, leaves the cluster and then reconnects. However, because the address + of the shared transport hasn't changed, the rejoined member has the same address, thus chances of reincarnation are + higher +- When a channel connects, it creates a new local_addr (UUID) and sends it down via SET_LOCAL_ADDRESS. The transport + then adds the UUID/physical address mapping to its cache +- When we send a multicast message (dest == null), but don't have a multicast capable transport (e.g. UDP.ip_mcast=false) + or TCP/TCP_NIO, then we simply send it to all *physical* addresses in the transport's UUID cache. + If we don't currently have all physical addresses, that's fine because MERGE2 will eventually fetch all physical + addresses in the cluster + + +TODOs +----- +- Multicast messages should always be null, so Address.isMulticastAddress() should not be needed anymore +- GossipRouter: we need to register logical *and* physical addresses, plus the logical name +- Find all uses of IpAddress and change them to SocketAddress (if possible), or try to use Address rather than + IpAddress +- UUID: generate a nice name from UUID if the logical name is not defined. Alternative: don't use the UUID to generate + the logical name, but maybe the host name and a random short +- Util.writeAddress()/writeAddresses(): currently optimized for IpAddress, change to UUID. + Example: View.writeTo()/readFrom() +- How are we going to associate logical names to channels when a shared transport is used ? +- Marshalled size of UUID: 18 bytes, IpAddress: 9 bytes. Size in memory (without superclass, additional_data, on + 64 bit machine): 16 bytes for UUID, 32 bytes for IpAddress ! So while marshalling takes more space for UUID, + it is quite compact in memory ! + If it turns out that UUIDs generate too much overhead on the wire (bandwidth), we should think about canonicalizing + UUIDs to shorts: run an agreement protocol which assigns cluster-wide unique shorts to UUIDs, and send the shorts + rather than the UUIDs around ! +- UDP.initial_hosts also needs to send to physical addresses +- Implement getPhysicalAddress() in all transports +- BasicTCP.handleDownEvent(): view has to be handled in ProtocolAdapter and connections have to be closed there, too + --> The semantics of retainAll() have to be inspected: do we handle UUIDs or PhysicalAddresses ? + --> Should we switch to a model where ConnectionTable never reaps connections based on view changes, but based on + idle time ? Ie. reap a connection that hasn't been used for more than 30 seconds +- TCPPING.initial_hosts="A,B": how will we find out about C, D and E ? + + +- Shared transport + - Move setSourceAddress() from TP to JChannel ? + - Check whether thread names in a shared transport are still correct. + - Do thread names use the logical or UUID name of a channel ? + - Is Global.DUMMY still needed when we set the local address top down ? + - The 'members' variable in TP is a union of all memberships in the shared transport case + - In BasicTCP, we need to change retainAll() to use physical addresses rather than UUIDs stored ib TP.members + - Dito for suspected_mbrs + - In general, change all addresses in TCP to PhysicalAddresses. Or should we use logical addresses ? + + + diff -Nru libjgroups-java-2.7.0.GA/doc/design/MarshalingFormat.txt libjgroups-java-2.12.2.Final/doc/design/MarshalingFormat.txt --- libjgroups-java-2.7.0.GA/doc/design/MarshalingFormat.txt 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/MarshalingFormat.txt 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,60 @@ + +// Author: Bela Ban + + +Binary format for marshalled messages +===================================== + +An org.jgroups.Message is marshalled to a byte stream as follows. + +- version ID: short (2 bytes). Version.encode() encodes the major, minor and patch version into the short. +- flags (byte): + - single message or list of messages (LIST) + If single message --> SingleMessage, else MessageList + - multicast (MULTICAST) or unicast message (for optimizations) + +SingleMessage: +- leading: byte. Has bits set for null src and dest addresses, buf and headers +- flags: byte +- src address: Address +- [length of buf]: int +- [buf]: byte[] array +- [Headers]: list of headers --> Headers + + +MessageList: +- length: int. Number of messages in the list +- src address: Address +- 1-m Messages: --> SingleMessage, but with no src and dest addresses + + + +Headers: +- length: int. Number of headers +- For each Header: + - Key: UTF-8 string + - Header + + +Header: +- magic_number (short) +- if magic number == -1 (not present): + - no-op +- else + - UTF-8 string (class name) +- size in bytes (short) +- contents (header-specific) + + +UTF-8 strings: +- All strings start with a short that is the length of the string (DataOutputStream.writeUTF(String)) + + +Notes: + +- In most cases, we don't need to send the dest address, because the sender knows whether the message + was received on the unicast or multicast socket, and can thus set the dest address in an incoming + message to its own local address, or multicast address + +- This is currently as used by UDP. Once we move to Transport (e.g. including TCP), this needs to be + revisited. Currently (2.2.8), TCP uses externalization, *not* Streamable. \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/doc/design/MERGE4.txt libjgroups-java-2.12.2.Final/doc/design/MERGE4.txt --- libjgroups-java-2.7.0.GA/doc/design/MERGE4.txt 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/MERGE4.txt 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,48 @@ + +MERGE4 +------ + +Author: Bela Ban +JIRA: https://jira.jboss.org/jira/browse/JGRP-937 + +Goal +---- + +To merge asymmetric partitions, e.g. A: {A,B,C}, B: {A,B}, C: {A,C}. The current merge algorithm wouldn't merge these +partitions because B and C are not coordinators and therefore don't participate in the merge. + +The implementation involves creating a new protocol (MERGE4) and modifying GMS and CoordGmsImpl. + +MERGE4 +------ +- Periodically runs a discovery +- The Discovery protocol is changed such that each participant additionally returns its view +- If the consolidated discovery responses result in more than 1 view, send up a MERGE event with a + list of all (different) views + +GMS / CoordGmsImpl +------------------ +- On MERGE(V1,V2,V3,...): + - Determine all coordinators, e.g. A for V1 and V2, D for V3 + - Determine the membership for each coord, e.g. {A,B,C} for A and {D,E,F} for D + - Send a MERGE-REQ to A and D + - The MERGE-REQ for A includes {A,B,C}, the MERGE-REQ for D includes {D,E,F} + - A and D fetch digest information from {A,B,C} (A) and {D,E,F} (D) respectively + - This information is consolidated in A (merge leader) and installed in both partitions + +- Example: + - A: V3 {A,B,C} + - B: V1 {A,B} + - C: V2 {A,C} + - D: V7 {D,E,F} + - E: V6 {D,E} + - F: V5 {D,F} + + - MERGE4 sends up a MERGE(V1,V2,V3,V5,V6,V7) + - CoordGmsImpl determines that the coords are A and D and the merge leader is A + - A sends a MERGE-REQ(A,B,C} to A and a MERGE-REQ(D,E,F} to D + - A fetches the digest and view for A,B,C and returns it to A + - D fetches the digest and view for D,E,F and returns it to A + - A consolidates the digests and views (into a MergeView) and tells A and D to install the new MergeView plus digests + + \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/doc/design/MERGE.new.txt libjgroups-java-2.12.2.Final/doc/design/MERGE.new.txt --- libjgroups-java-2.7.0.GA/doc/design/MERGE.new.txt 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/MERGE.new.txt 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,69 @@ + +New merging algorithm +===================== + +Author: Bela Ban +JIRAs: https://jira.jboss.org/jira/browse/JGRP-948, https://jira.jboss.org/jira/browse/JGRP-940 + +Goal +---- +The digests on a merge should not include 'hearsay' information, e.g. if we have subpartitions {A,B,C} and {D,E,F}, +then B (for example) should only return its own digest, and not the digests of A and C. + +Design +------ +On a merge, the merge leader sends a MERGE_REQ to all subpartition coordinators (in the example, this would be A and D). + +On reception of a MERGE_REQ, every coordinator multicasts a GET_DIGEST to its subpartition and waits N ms for all +replies. Example: D wait for replies from itself, E and F. It returns the aggregated information for D, E and F after N +milliseconds. + +A (the merge leader) waits for responses from itself and D. If it doesn't receive them within N ms, it cancels the merge. +After reception of responses from itself and D, it makes sure it has digests for 6 nodes A, B, C, D, E and F. It not, +it cancels the merge. + +Otherwise, A computes a MergeView and consolidates the digests, then unicasts the MERGE_VIEW to itself and D. Each +coordinator then multicasts the new view in its subpartition (same as now). + +Consolidating the digests should be simple because we only have 1 entry for each member. However, in the following +case we could have multiple overlapping entries: +A: {A} B: {A,B} + +Here's B's view includes A, so B will return digests for A and B, and A will return the digest for itself (A). In this +case, let's log a warning and make the digest for A be the maximum of all sequence numbers (seqnos). Example: + +A's digest: +A: 7 20 (20) + +B's digest: +A: 2 10 (10) +B: 5 25 (25) + +The merged digest for A would then be A: 7 20 (20). + +This should actually not happen because: +- B's digest is a result of contacting every member of its subpartition (A and B) +- If A is reachable from B, and B gets a response, the response will actually contain the correct seqnos 7 20 (20) +- However, if A's digest (for itself) contains 7 21 (21), because a message was sent in the meantime, then the + maximum would be 7 21 (21) which is correct + + +Merging of local digests (NAKACK.mergeDigest()) +----------------------------------------------- +- We have {A,B} and {C,D} +- The digest is A:15, B:7, C:10, D:9 +- Before receiving the merge digest A and B multicast more messages, A's seqno is now #20 and B's #10 +- A receives the digest: + A:20, B:10, ... + + A:15, B:7, ... + = A:20, **B:10** + ================ + +==> We currently do NOT overwrite our own digest entry, but overwrite all others. This is incorrect: we need to + not overwrite our own digest (as is done currently), but for all other members P, we need to: + - If P's seqno is higher than the one in the digest: don't overwrite + - Else: reset P's NakReceiverWindow and overwrite it with the new seqno + +==> If we didn't do this, in the example above, B's seqno would be #7 whereas we already received seqnos up to #10 from + P, so we'd receive seqnos #7-#10 from P twice ! + diff -Nru libjgroups-java-2.7.0.GA/doc/design/MERGE_View_Separation.txt libjgroups-java-2.12.2.Final/doc/design/MERGE_View_Separation.txt --- libjgroups-java-2.7.0.GA/doc/design/MERGE_View_Separation.txt 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/MERGE_View_Separation.txt 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,98 @@ + + +Separation of merges from view handling +======================================= + +Author: Bela Ban +JIRA: https://jira.jboss.org/jira/browse/JGRP-1009 + +Goal: +----- + +We don't want concurrent merges and view changes (join or leave processing). During a merge, join and leave requests +should be discarded. Likewise, during a join/leave processing, merge requests should be discarded. + +We already do discard join or leave requests during a merge (ViewHandler is suspended), but not the other way round. + +JGRP-1009 leads to spurious merges: when a join or leave request is being processed and the view is being +disseminated, if a merge is permitted to occur during this, the merge leader might detect different views +(due to them arriving at different members at different times, maybe a few millisconds apart) and initiate a merge. + +This won't happen when the merge is discarded during view processing / installation. + +Design: +------- + +There are 3 types of events we need to take into acccount: +- The coord receiving a JOIN/JOIN_WITH_STATE/LEAVE/SUSPECT event (anything which leads to a new view being installed) +- The coord receiving a MERGE event (e.g. from MERGE2 somewhere below in the stack) +- The coord receiving a MERGE-REQ request (from a coord in a different partition) + +On reception of a JOIN/JOIN_WITH_STATE/LEAVE/SUSPECT event +---------------------------------------------------------- +- If the ViewHandler is suspended --> discard the event +- Else, add the event +- When starting to process the event(s) in the queue: + - Suspend the ViewHandler + - Start the Resumer task (which resumes the ViewHandler after N seconds) + - Resume the ViewHandler when done processing + + +On reception of a MERGE event +----------------------------- +- If the ViewHandler is suspended --> discard the event +- Else: + - If there are JOIN/LEAVE/etc events in the queue: discard the event and start the processing of the queued events + - Else: + - Process the MERGE event + - Suspend the ViewHandler + - Start the Resumer task (which resumes the ViewHandler after N seconds) + - Resume the ViewHandler when done processing + + +On reception of a MERGE-REQ +--------------------------- +- If the ViewHandler is suspended --> reject the MERGE-REQ (send MERGE-RSP with merge_rejected=true) +- Else: + - Suspend the ViewHandler + - Start the Resumer task + - When the merge is done --> resume the ViewHandler + - On Resumer timeout: resume the ViewHandler + (this could happen for instance when a remote coord starts a merge, then crashes before merge completion) + + +Resuming the view handler: +-------------------------- + +The following 4 cases can resume the view handler + +#1 JOIN/LEAVE +------------- +- When the view has been installed by the coord, the view handler is resumed +- The view handler needs to be resumed also if the view installation fails, e.g. due to a failed flush + +#2 MERGE +-------- +- On competion of the merge (successful or failed), the view handler is resumed + +#3 MERGE-REQ +------------ +- Resume the view handler when getting a MergeView +- Special case: if the merge leader crashes before merge completion: + - On a MERGE-REQ, record the merge leader's address (when suspending the view handler) + - When the merge completes, null the merge leaders address again + - When we get a view excluding the merge leader, and the leader's address is non-null, resume the + view handler and null the merge leader's address + +#4 The Resumer kicks in +----------------------- +- The Resumer is started whenever the view handler is suspended +- It resumes the view handler when run +- When the view handler is resumed regularly, the Resumer is stopped + + +Issues: +------- + +- What if the client sends a JOIN_WITH_STATE, the coord processes the JOIN, but suspends the queue after it and before + processing the GET_STATE ? \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/doc/design/Multiplexer.txt libjgroups-java-2.12.2.Final/doc/design/Multiplexer.txt --- libjgroups-java-2.7.0.GA/doc/design/Multiplexer.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/Multiplexer.txt 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ =========================== Author: Bela Ban -Version: $Id: Multiplexer.txt,v 1.18 2006/07/11 12:36:37 belaban Exp $ JIRA: http://jira.jboss.com/jira/browse/JGRP-119, http://jira.jboss.com/jira/browse/JGRP-112 Overview diff -Nru libjgroups-java-2.7.0.GA/doc/design/NAKACK.txt libjgroups-java-2.12.2.Final/doc/design/NAKACK.txt --- libjgroups-java-2.7.0.GA/doc/design/NAKACK.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/NAKACK.txt 2011-10-18 11:22:35.000000000 +0000 @@ -6,7 +6,6 @@ Author: Bela Ban Date: April 3, 2007 JIRA: http://jira.jboss.com/jira/browse/JGRP-281 -Version: $Id: NAKACK.txt,v 1.9 2007/04/19 21:01:17 belaban Exp $ diff -Nru libjgroups-java-2.7.0.GA/doc/design/NullDestAddresses.txt libjgroups-java-2.12.2.Final/doc/design/NullDestAddresses.txt --- libjgroups-java-2.7.0.GA/doc/design/NullDestAddresses.txt 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/NullDestAddresses.txt 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,20 @@ + +Nulling of destination addresses for optimized marshalling +========================================================== + +Author: Bela Ban +Date: Aug 26 2005 + +When we marshall a message (org.jgroups.Message), we can transmit a null value for the destination, because +the receiver can determine the destination: +- for UDP: if received on the multicast receive socket, the destination is the multicast address (same as null) + if received on the unicast receive socket, the destination is the local_addr (ourself) +- for TCP: we use the MULTICAST byet sent with the message when unmarshalling the message: + - if true, we leave the deatination null (= multicast destination) + - if not set, we set the destination to the address passed to use from the ConnectionTable + +This requires that when marshalling a message, we send a multicast byte with each Message (or once for bundled msgs) +based on the destination address ! + +Note that we *cannot* modify the destination address in the message itself, otherwise retransmissions might fail ! + diff -Nru libjgroups-java-2.7.0.GA/doc/design/NullingSrcAddresses.txt libjgroups-java-2.12.2.Final/doc/design/NullingSrcAddresses.txt --- libjgroups-java-2.7.0.GA/doc/design/NullingSrcAddresses.txt 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/NullingSrcAddresses.txt 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,19 @@ + +Loopback adaptor issues on Windows +---------------------------------- + +JIRA: http://jira.jboss.com/jira/browse/JGRP-79 + +On Windows, when a loopback adaptor is created, we can associate multiple (virtual) IP +addresses with it, e.g. 10.0.0.1 and 10.0.0.2. + +However, when we have a member M1 bound to 10.0.0.1, and another member M2 bound to 10.0.0.2, and +bind_to_all_interfaces is set to true, then it was observed that - regardless of the bind address - +the sender's address in a DatagramPacket received was always 10.0.0.1 (the first address assigned) ! + +Therefore, members would never find each other. + +The reason this shows up now (in 2.2.8) is that as an optimization, we *don't* send the src address +in the Message anymore, so we can save a few bytes, but we null the src address, and set it to the sender's +address when we *receive* the packet. +This can be disabled by setting null_src_addresses to false (default is true) \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/doc/design/PartialStateTransfer.txt libjgroups-java-2.12.2.Final/doc/design/PartialStateTransfer.txt --- libjgroups-java-2.7.0.GA/doc/design/PartialStateTransfer.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/PartialStateTransfer.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,5 +1,4 @@ -// Version: $Id: PartialStateTransfer.txt,v 1.1 2006/01/27 14:46:12 belaban Exp $ // Author: Bela Ban Partial state transfer diff -Nru libjgroups-java-2.7.0.GA/doc/design/ProbabilisticBroadcast.txt libjgroups-java-2.12.2.Final/doc/design/ProbabilisticBroadcast.txt --- libjgroups-java-2.7.0.GA/doc/design/ProbabilisticBroadcast.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/ProbabilisticBroadcast.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,8 +1,8 @@ - Probabilistic Broadcast for JavaGroups - ====================================== + Probabilistic Broadcast for JGroups + =================================== diff -Nru libjgroups-java-2.7.0.GA/doc/design/Reincarnation.txt libjgroups-java-2.12.2.Final/doc/design/Reincarnation.txt --- libjgroups-java-2.7.0.GA/doc/design/Reincarnation.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/Reincarnation.txt 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ =========================== Author: Bela Ban -Version: $Id: Reincarnation.txt,v 1.1 2006/01/27 14:46:12 belaban Exp $ JIRA: http://jira.jboss.com/jira/browse/JGRP-130 The identity of a JGroups member is always the IP address and a port. The port is usually chosen by the OS, unless diff -Nru libjgroups-java-2.7.0.GA/doc/design/RELAY.fig libjgroups-java-2.12.2.Final/doc/design/RELAY.fig --- libjgroups-java-2.7.0.GA/doc/design/RELAY.fig 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/RELAY.fig 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,74 @@ +#FIG 3.2 Produced by xfig version 3.2.5 +Landscape +Center +Inches +Letter +100.00 +Single +-2 +1200 2 +6 3375 4800 6375 9075 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3375 4800 6375 4800 6375 9075 3375 9075 3375 4800 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3375 5400 6375 5400 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3375 6000 6375 6000 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3375 6675 6375 6675 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3375 7350 6375 7350 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3375 8025 6375 8025 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3375 8550 6375 8550 +4 0 0 50 -1 16 16 0.0000 4 195 885 4425 5175 RELAY\001 +-6 +6 1575 825 4575 4200 +6 3750 1950 4350 2550 +1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 4050 2250 270 270 4050 2250 4200 2475 +4 0 0 50 -1 16 16 0.0000 4 195 180 3975 2400 A\001 +-6 +6 2100 1500 2700 2100 +1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 2400 1800 270 270 2400 1800 2550 2025 +4 0 0 50 -1 16 16 0.0000 4 195 195 2325 1875 C\001 +-6 +6 2475 2850 3075 3450 +1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 2775 3150 270 270 2775 3150 2925 3375 +4 0 0 50 -1 16 16 0.0000 4 195 180 2700 3225 B\001 +-6 +1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 3069 2364 1479 1479 3069 2364 4344 3114 +4 0 0 50 -1 16 16 0.0000 4 240 450 2850 4125 udp\001 +-6 +6 10125 2700 10725 3300 +1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 10425 3000 270 270 10425 3000 10575 3225 +4 0 0 50 -1 16 16 0.0000 4 195 165 10350 3075 F\001 +-6 +6 10200 1200 10800 1800 +1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 10500 1500 270 270 10500 1500 10650 1725 +4 0 0 50 -1 16 16 0.0000 4 195 180 10425 1650 E\001 +-6 +6 8700 1950 9300 2550 +1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 9000 2250 270 270 9000 2250 9150 2475 +4 0 0 50 -1 16 16 0.0000 4 195 195 8925 2325 D\001 +-6 +1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 9969 2289 1479 1479 9969 2289 11244 3039 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 2175 9450 8400 9450 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 7 1 0 2 + 1 1 4.00 60.00 120.00 + 5475 9450 5475 4050 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 7 1 0 2 + 1 1 4.00 60.00 120.00 + 5475 5175 7425 5175 +2 4 0 3 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 9525 2925 3600 2925 3600 1950 9525 1950 9525 2925 +4 0 0 50 -1 18 18 0.0000 4 225 2445 1800 600 Data Center NYC\001 +4 0 0 50 -1 18 18 0.0000 4 225 2415 8775 525 Data Center SFO\001 +4 0 0 50 -1 16 16 0.0000 4 195 990 7350 9300 Network\001 +4 0 0 50 -1 16 16 0.0000 4 255 3465 6525 5025 Relaying to other data center\001 +4 0 0 50 -1 16 16 0.0000 4 240 1320 4875 3900 Application\001 +4 0 0 50 -1 16 16 0.0000 4 210 360 6300 2325 tcp\001 +4 0 0 50 -1 16 16 0.0000 4 240 450 9750 4050 udp\001 +4 0 0 50 -1 2 18 0.0000 4 195 225 3975 2850 X\001 +4 0 0 50 -1 2 18 0.0000 4 195 225 8925 2850 Y\001 Binary files /tmp/AYciA0_poF/libjgroups-java-2.7.0.GA/doc/design/RELAY.png and /tmp/XQ0f23GK1l/libjgroups-java-2.12.2.Final/doc/design/RELAY.png differ diff -Nru libjgroups-java-2.7.0.GA/doc/design/RELAY.txt libjgroups-java-2.12.2.Final/doc/design/RELAY.txt --- libjgroups-java-2.7.0.GA/doc/design/RELAY.txt 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/RELAY.txt 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,188 @@ + +RELAY - replication between data centers +======================================== + +Author: Bela Ban + +This is an enhanced version of DataCenterReplication.txt with the ability to send unicast messages and to provide views +to the application, which list members of all local clusters. + +We have data centers, each with a local cluster, in New York (NYC) and San Francisco (SFO). The idea is to relay +traffic from NYC to SFO, and vice versa. + +In case of a site failure of NYC, the state is available in SFO, and all clients can be switched over to SFO and +continue working with (almost) up-to-date data. The failing over of clients to SFO is outside the scope of this +proposal, and could be done for example by changing DNS entries, load balancers etc. + +The data centers in NYC and SFO are *completely autonomous local clusters*. There are no stability, flow control or +retransmission messages exchanged between NYC and SFO. This is critical because we don't want the SFO cluster to block +for example on waiting for credits from a node in the NYC cluster ! + +For the example, we assume that each site uses a UDP based stack, and relaying between the sites uses a +TCP based stack, see figure RELAY.png. + +There is a local cluster, based on UDP, at each site and one global cluster, based on TCP, which connects the +two sites. Each coordinator of the local cluster is also a member of the global cluster, e.g. member A in NYC +(assuming it is the coordinator) is also member X of the TCP cluster. This is called a *relay* member. A relay +member is always member of the local and global cluster, and therefore has 2 channels it joins. + +A relay member has a UDP stack which additionally contains a protocol RELAY at the top (shown in the bottom part +of the figure). RELAY has a JChannel which connects to the TCP group, but *only* when it is (or becomes) coordinator +of the local cluster. The configuration of the TCP channel is done via a property in RELAY. + +A multicast message received by RELAY traveling up the stack is wrapped and sent via the TCP channel to the +other site. When received there, the corresponding RELAY protocol unwraps the original message and changes the sender +of the message to a ProxyUUID, which wraps the original sender and the local sender. + +A ProxyUUID extends UUID and behaves like a normal UUID, but it also contains the original sender. + +A unicast message received by RELAY traveling down the stack is forwarded to the current relay if the destination is +a ProxyUUID. The relay will then wrap the message and forward it to the other site via TCP. + +When boradcasting a relayed message on the local cluster, RELAY adds a header. When it receives the multicast message it +forwarded itself, and a header is present, it does *not* relay it back to the other site but simply drops it. +Otherwise, we would have a cycle. + +When a coordinator crashes or leaves, the next-in-line becomes coordinator and activates the RELAY protocol, +connecting to the TCP channel and starting to relay messages. + +However, if we receive messages from the local cluster while the coordinator has crashed and the new one hasn't taken +over yet, we'd lose messages. Therefore, we need additional functionality in RELAY which buffers the last N messages +(or M bytes, or for T seconds) and numbers all messages sent. This is done by the second-in-line. + +When there is a coordinator failover, the new coordinator communicates briefly with the other site to determine +which was the highest message relayed by it. It then forwards buffered messages with lower numbers and removes the +remaining messages in the buffer. During this replay, message relaying is suspended. + +Therefore, a relay has to handle 3 types of messages from the global (TCP) cluster: + (1) Regular multicast messages + (2) A message asking for the highest sequence number received from another relay, and the response to this + (3) A message stating that the other side will go down gracefully (no need to replay buffered messages) + + +Example walkthrough +------------------- + +Multicasting a message: + +- C (in the NYC cluster, with coordinator A) multicasts a message +- A, B and C receive the multicast +- A is the relay. The byte buffer is extracted and a new message M is created. M's source is C, the dest is null + (= send to all). Note that the original headers are *not* sent with M. If this is needed, we need to revisit. +- A then wraps M into a message sent from X to Y +- X receives M, drops it (because it is the sender, determined by the header). +- Y receives M, and unwraps it. +- Y replaces the sender (C) with a ProxyUUID(D,C) (D is the sender and C the origial sender), adds a RelayHeader and + sends it down its local cluster +- D, E and F receive M and deliver it +- D does not relay M because M has a header + +Sending a unicast reply: + +- When F receives the multicast message M, it sends a unicast reply message R +- R.dest=ProxyUUID(D,C) and R.src=F +- RELAY.down() sees that R.dest is a ProxyUUID and therefore forwards R to the current relay (which is D) +- RELAY.up() in D sees that the destination is a ProxyUUID and relays the message, via Y to X +- D sets the destination of R to C, wraps the message and sends it to X (via the TCP cluster) +- A receives R (from X) and replaces R.src with a ProxyUUID(C,F) +- A puts R on the local channel where it is sent to C + + +Implementation +-------------- + +Becoming coordinator: +- Join TCP channel +- Register receiver + +Ceasing to be coordinator: +- Leave TCP channel + + +RELAY.up(msg): +- If RelayHeader present: // coord + - If FORWARD && coordinator: forward(msg.buf); return + - If DISSEMINATE: pass up and return + - If VIEW: // see below + - Return +- Else: + - If multicast message && coordinator: + - Copy msg to M (don't copy headers) + - Serialize M into a buffer buf + - forward(buf) + - Pass up // unicast or multicast messages + + +RELAY.down(msg): +- If msg.dest is not a ProxyUUID: pass down, return +- forwardToCoord(msg) +- Return // don't pass down ! + + +Receive message M from TCP channel: +- Switch RelayHeader: + - Case FORWARD: + - If sender = self: discard + - Else: deserialize M.buf into message M2 and putOnLocalCluster(M2) + - Case VIEW: // see below + + +forward(buf): // buf is the serialized message to be forwarded +- Create a message M with M.buf=buf, M.dst=null +- Add RelayHeader.FORWARD to M +- Put M onto the TCP channel + + +forwardToCoord(msg): +- Copy msg to M (don't copy headers) +- Set M.dst=msg.dst.original, M.src=local_addr +- Serialize M into a buffer buf +- Create message M2 (M2.buf=buf) +- Add RelayHeader.FORWARD to M2 +- Send M2 to the current relay (coordinator) + +putOnLocalCluster(M): +- Set M.src=ProxyUUID(local_addr,M.src) +- Add a RelayHeader.DISSEMINATE to M +- Put M on the local channel + + +View changes +------------ + +Local view changed: +- Set local view +- If coordinator: + - Broadcast remote and global view to local cluster + - Send remote view to remote cluster +- Every node (on reception): + - Update(RV, GV) + +Remote view (RV) changed: +- Broadcast remote and global view to local cluster +- Every node (on reception): + - Update(RV, GV) + + +Update(RV,GV): +- Update remote view from RV if needed +- Install GV: + - If GV != current global view: set current view = GV and viewAccepted(GV) + + +Bridge view changed: +- If coordinator: send local view to remote +- If remote coordinator ('creator of remote view') crashed: + - Generate empty remote view V, generate global view and send RV and GV to local cluster + + + + + +Issues: +- Do we copy the headers of a message M when M is relayed ? If not, an app won't be able to add their own headers +- Should we pass logical name information between the clusters ? Or should this be part of ProxyAddress ? + +Todo: +#3 Handling temp coordinator outage - how do we prevent message loss ? +#4 State transfer - replication across clusters, to bootstrap initial coords in a local cluster diff -Nru libjgroups-java-2.7.0.GA/doc/design/ReliableViewInstallation.txt libjgroups-java-2.12.2.Final/doc/design/ReliableViewInstallation.txt --- libjgroups-java-2.7.0.GA/doc/design/ReliableViewInstallation.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/ReliableViewInstallation.txt 2011-10-18 11:22:35.000000000 +0000 @@ -3,7 +3,6 @@ ========================== Author: Bela Ban -Version: $Id: ReliableViewInstallation.txt,v 1.1 2006/01/27 14:46:12 belaban Exp $ The default stack sees view as just messages; a view multicast is retransmitted until the sender is excluded because it left or crashed. However, this behavior can lead to problems described below. The problems occur when a view diff -Nru libjgroups-java-2.7.0.GA/doc/design/ReplCache.txt libjgroups-java-2.12.2.Final/doc/design/ReplCache.txt --- libjgroups-java-2.7.0.GA/doc/design/ReplCache.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/ReplCache.txt 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ ================ Author: Bela Ban -Id: $Id: ReplCache.txt,v 1.6 2008/12/23 16:11:02 belaban Exp $ Idea @@ -15,86 +14,96 @@ ReplCache is a hashmap which distributes its keys and values across the cluster, based on consistent hashing. +The difference to PartitionedHashMap is that ReplCache allows a user to define *per data item* whether it should be +replicated (for high availability) or not and if yes, how many times. + There are 3 methods: put(), get() and remove(). -When adding a new key/value pair, put() takes the 'replication factor' as argument, alongside the key and value and +When adding a new key/value pair, put() takes the 'replication count' as argument, along with the key, value and a timeout. -A replication factor of 0 means no replication. +A replication count of 1 means the data is stored only on 1 node in the cluster, and there is no replication. +The data will be placed on a node in the cluster that corresponds to the consistent hashcode of the data's key. -A replication factor of -1 means that the element is replicated to all cluster nodes. +A replication count of -1 means that the data item is replicated to all cluster nodes. -A replication factor of K means replicate the element K times, e.g. PUT(KEY, VAL, 2, timeout) means that an element +A replication count of K means the element is stored K times, e.g. PUT(KEY, VAL, 2, timeout) means that an element will be created on 2 nodes. When the view changes, the cluster makes sure that the above KEY, VAL is always present -in 2 nodes. Note that K has to be less than or equal to N (= number of nodes). When K > N, then ReplCache treats +on 2 nodes. Note that K has to be less than or equal to N (= number of nodes). When K > N, then ReplCache treats K as -1 (replicate to all nodes). +K == 0 is invalid and will be ignored; the data will not be stored in the cluster. -TBD: a replication factor which defines a percentage, e.g. 0.3 means replicate to 30% of all nodes. - +TBD: a replication count which defines a percentage, e.g. 0.3 means replicate to 30% of all nodes. -The advantage of defining replication factors per element is that we can define what reliability we want for +The advantage of defining replication counts per element is that we can define what reliability we want for individual data items. For example, an element that can easily be retrieved from disk or database probably does -fine with a factor of 0 (= no replication). Here, we use the cache just as a speedup to prevent DB access. -An important item that is costly to recreate, or cannot be recreated at all, should probably have a factor of -1. +fine with a count of 1 (= no replication). Here, we use the cache just as a speedup to prevent DB access. +An important item that is costly to recreate, or cannot be recreated at all, should probably have a count of -1. -The point of being able to define replication factors per data item is that we can save memory this way. If we -compare this to RAID 0+1, then - because we're replicating every single data item - we can effectively only use -half of the memory (disk space) allocated to the RAID system. With per data replication factors, we can increase the -net memory that can be used (unless of course all elements are added with a factor of -1 !). +The point of being able to define replication counts per data item is that we can save memory. If we +compare this to RAID 1, then - because we're replicating every single data item - we can effectively only use +half of the memory (disk space) allocated to the RAID system. With per data replication counts, we can increase the +net memory that can be used (unless of course all elements are added with a count of -1 !). Put() always results in a multicast across the cluster. Each node determines by itself whether it will add the KEY|VAL or not. This is done by computing a set of consistent hashes from KEY, mapping them to a set of servers and determining whether the node's address is in that set. If so, a node will add the KEY,VAL to its local cache, else not. -Get() first checks the level 1 cache (L1 cache, not mentioned so far). If the data is found, it will be returned, -else we multicast a GET request (bounded with a timeout). Every node returns its key/value from the local cache. Before -returning from get(), we add the result to our L1 cache. (The L1 cache will independently evict timed out items, or -evict items when it needs more space. Items with a timeout of -1 are never placed into the L1 cache). +Get() first checks the level 1 cache (L1 cache, not mentioned so far), and the regular cache (L2 cache). +If the data is found, it will be returned, else we multicast a GET request (bounded with a timeout). +Every node returns its key/value from the local cache. Before returning from get(), we add the result to our L1 cache. +(The L1 cache will independently evict timed-out items, or evict items when it needs more space. +Items with a timeout of -1 are never placed into the L1 cache). Remove() simply multicasts the KEY across the cluster. Every node removes KEY from its local cache, and the L1 cache if enabled. -Design points -------------- -There are a few design considerations: +Design decisions +---------------- +There are a few considerations and assumptions that influenced the design: -- Keys and values are small. We do *not* provide technology which breaks large data items into multiple chunks +- Keys and values must be small. We do *not* provide technology which breaks large data items into multiple chunks and distributes or replicates these chunks individually -- IP multicasting is the transport. If we used TCP, communication would get costly (N-1 issue) +- IP multicasting should be used in the transport. If we used TCP, communication would get costly (N-1 issue) + +- K cannot be reduced for the same key, e.g. if K == 3 and then K == 2 for the same key, we'll have some leftover + data with K == 3. If this is necessary, remove the key first before calling put() again. API --- put(KEY, VAL, K, TIMEOUT): +-------------------------- Places KEY,VAL into the hashmaps of selected cluster nodes. Existing data will be overwritten. KEY and VAL have to be serializable. -K can be -1 (replicate everywhere), 0 (create only on 1 node) or > 0 <= N (replicate to K nodes). - -TIMEOUT (ms): -1 (no caching), 0 (cache until removed) or > 0 (cache for TIMEOUT ms) - -On reception of put(): - -- The selected target nodes add the KEY,VAL to their local cache if the conistent hash matches their local_addr -- *Everyone* removes KEY from their L1 cache. (Optimization: only the non-selected nodes do this, the selected nodes - also add KEY,VAL to their L1 caches) - +K is the replication count and can be: + -1: replicate everywhere + 1: create only on 1 node + > 1 <= N: store on K nodes + +TIMEOUT (ms): + -1: no caching + 0: cache until removed + > 0: cache for TIMEOUT milliseconds + The put() method creates a message with KEY, VAL, K and TIMEOUT and multicasts it. Each node which receives the message does the following: - If K == -1: add it to the local cache and return -- If K == 0: compute the server based on the consistent hashcode for KEY and see whether local_addr == server. If +- If K == 1: compute the server based on the consistent hashcode for KEY and see whether local_addr == server. If so, add the KEY, VAL to the local cache and return. Else, drop the message. - If K > 0: compute K consistent hashes from KEY. If local_addr is part of the set of server addresses, add KEY,VAL to the local cache. Else, drop the message. VAL get(KEY): +------------- - Look up KEY in the L1 cache. If found,and not expired, return it - Multicast a GET request. @@ -102,27 +111,71 @@ - Else return null void remove(KEY): +----------------- - Multicast a REMOVE(KEY) message - On reception, every node removes KEY from its local cache -View changes: - +View change: +------------ -For a new or left node P, every node N: -- For each local KEY: - - If the K factor is -1: replicate KEY,VAL to P - - If the K factor is 0: compute consistent hash and pick server S - - If S == P, the server which hosted KEY before P joined moves KEY,VAL to P (PUT message), and - removes KEY from its local hashmap - - Else: do nothing (no rebalancing needed) - - If the factor is > 0: - - Compute K consistent hashes and determine the K servers which are supposed to be hosting KEY - - For each server S: - - Do the same as above (factor == 0) +- The handling of view changes needs to be done in a separate thread. Suggestion: do this in a timer task, and + start the task 100ms after the view change callback. This is to ensure that every cluster node installed the view. +- Old view is V, new view is V-NEW +- For a new node P + - For each key KEY + - If K == -1: copy KEY to P (only the coord does this) + - If K == 1: compute new hash (based on V-NEW), if not same as local node -> move KEY to P + - If K > 1: re-balance + +- For each left node Q + - For each key KEY: + - If K == -1: no-op + - If K == 1: compute new hash (based on V-NEW), if not same as local node -> move KEY to P + - If K > 1: re-balance + +Re-balance (K > 1): +------------------- + +- Old view is V, new view is V-NEW + +- move() installs a KEY regardless of whether it would be accepted in the current view + +- For each key KEY: + - If K == -1: + - For each newly joined node P: + - The coord sends KEY to P (move()) + + - If K == 1: + - Compute 1 hashcode (based on V-NEW) and determine the new node N + - If N != local node --> move KEY to N + + - If K > 1: + - Compute hashes for V (NODES) + - Compute hashes for V-NEW (NEW-NODES) + - If NODES == NEW-NODES --> return + - Else + - Multicast KEY (make sure everyone has new view installed, to compute correct acceptance) + - If local node is not in NEW-NODES --> remove KEY from local node + + + - Compute nodes for V (NODES) + - Compute nodes for V-NEW (NEW-NODES) + - If NODES == NEW-NODES --> return + - Else + - Multicast KEY (every node will check the hash against itself and store if needed) + - If the local node is not in NEW-NODES --> remove KEY + + +Stopping the cache: +------------------- +- For each key KEY: + - If K is -1: no-op (every node already has KEY) + - If K == 1: compute new hash (based on the view excluding the current node) and copy KEY to the new node + - If K > 1: no-op (somebody else has KEY, view change will copy to more nodes if needed) diff -Nru libjgroups-java-2.7.0.GA/doc/design/SCOPE.txt libjgroups-java-2.12.2.Final/doc/design/SCOPE.txt --- libjgroups-java-2.7.0.GA/doc/design/SCOPE.txt 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/SCOPE.txt 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,41 @@ + + +Implementation of the SCOPE protocol +==================================== + +Author: Bela Ban + +Scopes allow for concurrent delivery of messages sent by the same sender, without having to resort to OOB. + +The implementation is done by a protocol called SCOPE, which has to be somewhere above NAKACK and UNICAST. + +Scopes do apply to both multicast and unicast messages. + +A message is tagged as scoped by calling Message.setScope(short). This will add a SCOPE.ScopeHeader to the message. A +scope is always a short, so we can have ca 32'000 scopes. + +The scope can be set *per message*. It should be more or less unique, but doesn't need to be. + +All messages with the same scope are delivered in the order in which they were received by the SCOPE protocol. Because +SCOPE resides above NAKACK and UNICAST, all multicast and unicast messages are guaranteed to be received by SCOPE +(a) in order and (b) once and only once. + +Messages without a scope header are passed up the stack. + +When a message is received by SCOPE, it is examined for a scope header. If none is present, the message is passed up. + +When a scope header is present, we grab the scope and fetch the associated queue (MessageQueue). If no queue +exists yet, one will be created. + +The message is then added to the end of the queue. + +If no thread is currently processing the queue, one will be created (from a thread pool) and assigned to processing +the queue. The thread (QueueThread) will then continually remove messages from the head of the queue and pass them +up the stack. + +When no messages are available, the thread is terminated and is placed back to the thread pool. + +The thread pool is configurable (min and max threads, plus idle time). + +Unused scopes are periodically removed by the ExiryTask, which is run every expiration_interval milliseconds and +removes scopes which have been idle for more than expiration_time milliseconds. diff -Nru libjgroups-java-2.7.0.GA/doc/design/SEQUENCER.txt libjgroups-java-2.12.2.Final/doc/design/SEQUENCER.txt --- libjgroups-java-2.7.0.GA/doc/design/SEQUENCER.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/SEQUENCER.txt 2011-10-18 11:22:35.000000000 +0000 @@ -5,7 +5,6 @@ Author: Bela Ban Date: Dec 29 2005 -Version: $Id: SEQUENCER.txt,v 1.2 2008/10/06 16:05:53 belaban Exp $ Motivation @@ -22,6 +21,15 @@ its own message and multicasts it to the group. The SEQUENCER protocol (somewhere above NAKACK) unwraps the message so that the original sender is seen. +It is the coordinator who decides on the ordering of messages from different senders. When receiving FORWARD +messages from A, B and C, the coordinator establishes an ordered sequence, ordered by the seqno assigned by the +coordinator when sending the message. This sequence is delivered in order at all receivers, therefore establishing +total ordering. + +Note that there can be cases where a sender P sends message M1 and M2, but M2 is received first by the coordinator, +then M1. In this case, M2 would be *before* M1 in the global sequence established by the coordinator. If this is not +desired, then use a group RPC which gets acked by every node before a new message is sent. + Example: group is {A,B,C}. B wants to multicast a message M to the group. It does so by sending M to A. A then adds a header which records the original sender (B) and replaces M's sender address with its own (A). When a member receives M, everybody thinks the message is from A, and NAKACK will possibly retransmit from A diff -Nru libjgroups-java-2.7.0.GA/doc/design/SimpleFlowControl.txt libjgroups-java-2.12.2.Final/doc/design/SimpleFlowControl.txt --- libjgroups-java-2.7.0.GA/doc/design/SimpleFlowControl.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/SimpleFlowControl.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,5 +1,4 @@ -// Version: $Id: SimpleFlowControl.txt,v 1.2 2007/01/05 15:51:58 belaban Exp $ // Author: Bela Ban diff -Nru libjgroups-java-2.7.0.GA/doc/design/STABLE.txt libjgroups-java-2.12.2.Final/doc/design/STABLE.txt --- libjgroups-java-2.7.0.GA/doc/design/STABLE.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/STABLE.txt 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ Author: Bela Ban Date: May 29 2007 -Version: $Id: STABLE.txt,v 1.2 2007/05/31 09:56:58 belaban Exp $ Goal: to purge messages delivered by all members diff -Nru libjgroups-java-2.7.0.GA/doc/design/StreamingStateTransfer.txt libjgroups-java-2.12.2.Final/doc/design/StreamingStateTransfer.txt --- libjgroups-java-2.7.0.GA/doc/design/StreamingStateTransfer.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/StreamingStateTransfer.txt 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ Author: Vladimir Blagojevic Date: July 2006 -Version: $Id: StreamingStateTransfer.txt,v 1.4 2006/07/31 16:12:20 vlada Exp $ Overview diff -Nru libjgroups-java-2.7.0.GA/doc/design/TestNG.txt libjgroups-java-2.12.2.Final/doc/design/TestNG.txt --- libjgroups-java-2.7.0.GA/doc/design/TestNG.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/TestNG.txt 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ ============================== Author Bela Ban -Version: $Id: TestNG.txt,v 1.17 2008/04/18 09:13:12 belaban Exp $ Goals ----- diff -Nru libjgroups-java-2.7.0.GA/doc/design/TransportNextGen libjgroups-java-2.12.2.Final/doc/design/TransportNextGen --- libjgroups-java-2.7.0.GA/doc/design/TransportNextGen 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/TransportNextGen 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,29 @@ + + +Transport Next Generation +------------------------- + +Author: Bela Ban + +The next version of the transport (org.jgroups.protocols.TP) should be NIO based: TP has an NIO selector, and subclasses +such as TCP or UDP only register NIO channels with TP. For example, UDP would create 2 NIO channels, a unicast and a +multicast channel and register them with TP. TCP would do an accept() in a loop and, whenever a new peer connects, +register the client's NIO socket channel with TP's selector. + +TP would therefore be a multiplexer which handles a number of connections, be they TCP or UDP connections. Therefore, +the connection table functionality of TCP would be largely removed, because this is now handled by TP itself. + +This requires JDK 7, because NetworkChannel.open() does not yet exist in prior JDKs. To be more precise, it does exist, +but only for datagram sockets (DatagramChannel.open()), not for multicast sockets (no MulticastChannel.open()). + +Transport NetGen should be combined with the copyless stack (https://jira.jboss.org/jira/browse/JGRP-809). On the +receive side, this is done by passing the selection key to a thread from the thread pool. If the key has an attachment, +it is the ByteBuffer previously created by a (possibly different) thread to receive the message. The receiver thread +will then simply call read() (or receive()) on the input NIO channel and - when all bytes have been received (defined +by the initial length field of a message) - unmarshall the buffer and pass the resulting message up the stack. +When there is no attachment, the receiver thread creates one (according to the length field which prefixes each message) +and reads the bytes available from the NIO channel into it. If the number of bytes read is equals to the expected length, +the thread proceeds to unmarshalling and passing the message up. Otherwise, it attaches the ByteBuffer to the selection +key and returns. Later, a (potentially) different thread will complete reading the full message and then finish the job. + + diff -Nru libjgroups-java-2.7.0.GA/doc/design/UNICAST2.txt libjgroups-java-2.12.2.Final/doc/design/UNICAST2.txt --- libjgroups-java-2.7.0.GA/doc/design/UNICAST2.txt 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/UNICAST2.txt 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,181 @@ + +UNICAST2 design +=============== +(see UNICAST.txt for the old design) + +Author: Bela Ban + +Motivation +---------- + +UNICAST has issues when one end of the connnection unilaterally closes the connection and discards the state in +the connection table. + +Example: we have a conn between A and B. There's a partition such that A sees {A,B} but B sees only {B}. +B will clear its connection table for A on reception of the view, whereas A will keep it. + +Now the partition heals and A and B can communicate again. + +Assuming A's next seqno to B is #25 (and #7 for receiving messages from B), +B will store the message because it expects #1 from A (new connection). As a matter of fact, B will store *and not +deliver* all subsequent messages from A ! + +The reverse direction is also bad: B will send #1 to A, but A expects #7, so A will discard the message. The first 6 +messages from B are discarded at A ! + + +Goals +----- + +#1 Handle the above scenarios + +#2 Handle the scenario where a member communicates with a non-member (get rid of enabled_mbrs and prev_mbrs) + +#3 Handle the scenario where a member talks to a non existing (or previous) member. Get rid of + ENABLE_UNICASTS_TO and age out connections to non existing members after some time (JGRP-942) + +#4 Should be usable without group communication ('Unicast JGroups') + + +Design +------ + +As example we have a unicast connection between A and B. A is the sender and B the receiver: + + A <-------------------------------------------------> B + + B:entry.seqno=#25 A:entry.seqno=#7 + recv_win=#7 recv_win=#25 + send-conn-id=322649 send-conn-id=101200 + recv-conn-id=101200 recv-conn-id=322649 + +A has an entry in the connection table for B, and B has an entry for A. Each connection has a connection ID (conn-id). +Each entry also has a seqno which is the highest seqno sent to the peer so far, and a recv_win which has the highest +seqno received from the peer so far. For example, A's next message to B will be #25, and the next seqno expected +from B is #7. + + + +A sends a message to B: +----------------------- +- If the entry for B is null, or the seqno=0: + - Create an entry, set the seqno to 1 and set send-conn-id to the current time (needs to be unique, could also use UUIDs) + - Send the message with the next seqno and the current conn-id and first=true +- Else + - Send the message with the next seqno and the current conn-id + +B receives a message from A: +---------------------------- +- If first == true + - If entry or entry.recv_win for B == null + - Create a new entry.recv_win with msg.seqno + - Set entry.recv-conn-id to conn-id + - Else: + - If conn-id != entry.recv-conn-id: + - Create a new entry.recv_win with msg.seqno + - Set entry.recv-conn-id to conn-id + - Else + - NOP (prevents duplicate connection establishments) +- Else + - If entry.recv_win == null || conn-id != recv-conn-id: no-op + - Drop message + - Send SEND_FIRST_SEQNO to A + + +A receives GET_FIRST_SEQNO from B: +---------------------------------- +- If conn-id != send-conn-id: drop message +- A grabs the first message in its sent_win +- A adds the entry.send-conn-id to the UnicastHeader (if not yet present), sets first=true and sends the message to B + + + +Scenarios +--------- + +The scenarios are tested in UNICAST_ConnectionTests + +#1 A creates new connection to B: +- The entry for B is null, a new entry is created and added to the connection table +- Entry.send-conn-id is set and sent with the message +- Entry.seqno now is 1 + + +#2 B receives new connection: +- B creates a new entry and entry.recv_win (with msg.seqno) for A +- B sets entry.recv-conn-id to msg.conn-id +- B adds the message to entry.recv_win + + +#3 A and B close connection (e.g. based on a view change (partition)): +- Both A and B reset (cancelling pending retransmissions) and remove the entry for their peer from the connection table + + +#4 A closes the connection unilaterally (B keeps it open), then reopens it and sends a message: +- A removes the entry for B from its connection table, cancelling all pending retransmissions +- (Assuming that B's entry.recv_win for A is at #25) +- A creates a new entry for B in its connection table +- Entry.send-conn-id is set and sent with the message +- Entry.seqno now is 1 +- B receives the message with a new conn-id +- B does have an entry for A, but entry.recv-conn-id doesn't match msg.conn-id +- B creates a new entry.recv_win, sets it to msg.seqno +- B sets entry.recv-conn-id to msg.conn-id + + +#5 B closes its connection unilaterally, then A sends a message to B: +- B doesn't find an entry for A in its connection table +- B discards the message and sends a SEND-FIRST-SEQNO to A +- A receives the SEND-FIRST-SEQNO message. It grabs the message with the lowest seqno + in its entry.send_win, adds a UnicastHeader with entry.send-conn-id and sends the + message to B +- B receive the message and creates a new entry and entry.recv_win (with msg.seqno) +- B sets entry.recv-conn-id to msg.conn-id + +#6 Same as #4, but after re-establishing the connection to B, A loses the first message +(first part of #4) +- A creates a new sender window for B +- A sends #1(conn-id=322649) #2(conn-id=0) #3(conn-id=0), but loses #1 +- B receives #2 first. It thinks this is part of a regular connection, so it doesn't trash its receiver window +- B expects a seqno higher than #2 (from the prev conversation with A), and discards #2, but *acks* it nevertheless +- A removes #2 from its sender window +- B now finally receives #1, and creates a new receiver window for A at #1 +- A retransmits #3 +- B stores #3 but doesn't deliver it because it hasn't received #2 yet +- However, B will *never* receive #2 from A because that seqno has been removed from A's sender window ! + + +#7 Merge where A and B are in different partitions: +- Both A and B removes the entries for each other in their respective connection tables +- When the partition heals, both A and B will create new entries (see scenario #2) + + +#8 Merge where A and B are in overlapping partitions A: {A}, B: {A,B}: +- (This case is currently handled by shunning, not merging) +- A sends a message to B +- A removed its entry for B, but B kept its entry for A +- A new creates a new connection to B (scenario #1) and sends the message +- B receives the message, but entry.recv-conn-id doesn't match msg.conn-id, so B + removes entry.recv_win, sets entry.recv-conn-id to msg.conn-id and creates a new + entry.recv_win with msg.seqno (same as second half of scenario #4) + + +#9 Merge where A and B are in overlapping partitions A: {A,B}, B: {B}: +- A sends a message to B (msg.seqno=25) +- B doesn't have an entry for A +- B discards the message and sends a SEND-FIRST-SEQNO to A +- A receives the SEND-FIRST-SEQNO message. It grabs the message with the lowest seqno + in its entry.send_win, adds a UnicastHeader with entry.send-conn-id and sends the + message to B +- B receive the message and creates a new entry and entry.recv_win (with msg.seqno) +- B sets entry.recv-conn-id to msg.conn-id + + +Issues +------ +- How do we handle retransmissions of the first message (first=true) ? We *cannot* create a new entry.recv_win, or + else we trash already received msgs ! Use a UUID (as connection-ID) instead of first=true ? Maybe the system time + is sufficient ? After all, the ID only has to be unique between A and B ! + ==> Solved by using connection IDs (see above) + + diff -Nru libjgroups-java-2.7.0.GA/doc/design/UNICAST.txt libjgroups-java-2.12.2.Final/doc/design/UNICAST.txt --- libjgroups-java-2.7.0.GA/doc/design/UNICAST.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/UNICAST.txt 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ Author: Bela Ban Date: Aug 10 2006 -Version: $Id: UNICAST.txt,v 1.3 2006/08/11 07:55:17 belaban Exp $ Sending a unicast message to P diff -Nru libjgroups-java-2.7.0.GA/doc/design/UNIFORM.txt libjgroups-java-2.12.2.Final/doc/design/UNIFORM.txt --- libjgroups-java-2.7.0.GA/doc/design/UNIFORM.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/UNIFORM.txt 2011-10-18 11:22:35.000000000 +0000 @@ -3,7 +3,6 @@ ======================== Author: Bela Ban -Version: $Id: UNIFORM.txt,v 1.2 2006/04/25 03:00:31 belaban Exp $ Definition diff -Nru libjgroups-java-2.7.0.GA/doc/design/varia1.txt libjgroups-java-2.12.2.Final/doc/design/varia1.txt --- libjgroups-java-2.7.0.GA/doc/design/varia1.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/varia1.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -$Id: varia1.txt,v 1.1 2006/05/22 06:34:43 belaban Exp $ diff -Nru libjgroups-java-2.7.0.GA/doc/design/varia2.txt libjgroups-java-2.12.2.Final/doc/design/varia2.txt --- libjgroups-java-2.7.0.GA/doc/design/varia2.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/varia2.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -$Id: varia2.txt,v 1.1 2006/05/22 06:34:43 belaban Exp $ Design Issues ============= @@ -21,7 +20,7 @@ seqnos from all current members (including itself) seen so far. It also includes the new member, with a seqno of 0 (starting seqno). Let's assume the coordinator has a seqno of 35. Now, the new -view is mcast before the call (HandleJoin) returns to the client +view is mcast before the call (handleJoin()) returns to the client (containing the digest). The client therefore gets a wrong seqno for the coordinator. The 2 scenarios are described below: diff -Nru libjgroups-java-2.7.0.GA/doc/design/ViewHandling.txt libjgroups-java-2.12.2.Final/doc/design/ViewHandling.txt --- libjgroups-java-2.7.0.GA/doc/design/ViewHandling.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/design/ViewHandling.txt 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ ================= Author: Bela Ban -Version: $Id: ViewHandling.txt,v 1.1 2006/01/27 14:46:12 belaban Exp $ Problem: Merge, join and leave requests need to handled in the order in which they arrive. Currently, diff -Nru libjgroups-java-2.7.0.GA/doc/history.txt libjgroups-java-2.12.2.Final/doc/history.txt --- libjgroups-java-2.7.0.GA/doc/history.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/history.txt 2011-10-18 11:22:35.000000000 +0000 @@ -3,7 +3,6 @@ History List ============ -Revision: $Id: history.txt,v 1.192 2007/04/04 05:23:34 belaban Exp $ [For current version, see file Version.java (or invoke 'java org.jgroups.Version')] diff -Nru libjgroups-java-2.7.0.GA/doc/JmxSupport.txt libjgroups-java-2.12.2.Final/doc/JmxSupport.txt --- libjgroups-java-2.7.0.GA/doc/JmxSupport.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/JmxSupport.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,6 +1,5 @@ // Author: Bela Ban -// $Id: JmxSupport.txt,v 1.3 2006/10/09 13:36:18 belaban Exp $ diff -Nru libjgroups-java-2.7.0.GA/doc/manual/.cvsignore libjgroups-java-2.12.2.Final/doc/manual/.cvsignore --- libjgroups-java-2.7.0.GA/doc/manual/.cvsignore 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/manual/.cvsignore 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1 @@ +build diff -Nru libjgroups-java-2.7.0.GA/doc/manual/en/images/RELAY.fig libjgroups-java-2.12.2.Final/doc/manual/en/images/RELAY.fig --- libjgroups-java-2.7.0.GA/doc/manual/en/images/RELAY.fig 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/manual/en/images/RELAY.fig 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,74 @@ +#FIG 3.2 Produced by xfig version 3.2.5 +Landscape +Center +Inches +Letter +100.00 +Single +-2 +1200 2 +6 3375 4800 6375 9075 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 3375 4800 6375 4800 6375 9075 3375 9075 3375 4800 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3375 5400 6375 5400 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3375 6000 6375 6000 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3375 6675 6375 6675 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3375 7350 6375 7350 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3375 8025 6375 8025 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3375 8550 6375 8550 +4 0 0 50 -1 16 16 0.0000 4 195 885 4425 5175 RELAY\001 +-6 +6 1575 825 4575 4200 +6 3750 1950 4350 2550 +1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 4050 2250 270 270 4050 2250 4200 2475 +4 0 0 50 -1 16 16 0.0000 4 195 180 3975 2400 A\001 +-6 +6 2100 1500 2700 2100 +1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 2400 1800 270 270 2400 1800 2550 2025 +4 0 0 50 -1 16 16 0.0000 4 195 195 2325 1875 C\001 +-6 +6 2475 2850 3075 3450 +1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 2775 3150 270 270 2775 3150 2925 3375 +4 0 0 50 -1 16 16 0.0000 4 195 180 2700 3225 B\001 +-6 +1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 3069 2364 1479 1479 3069 2364 4344 3114 +4 0 0 50 -1 16 16 0.0000 4 240 450 2850 4125 udp\001 +-6 +6 10125 2700 10725 3300 +1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 10425 3000 270 270 10425 3000 10575 3225 +4 0 0 50 -1 16 16 0.0000 4 195 165 10350 3075 F\001 +-6 +6 10200 1200 10800 1800 +1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 10500 1500 270 270 10500 1500 10650 1725 +4 0 0 50 -1 16 16 0.0000 4 195 180 10425 1650 E\001 +-6 +6 8700 1950 9300 2550 +1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 9000 2250 270 270 9000 2250 9150 2475 +4 0 0 50 -1 16 16 0.0000 4 195 195 8925 2325 D\001 +-6 +1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 9969 2289 1479 1479 9969 2289 11244 3039 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 2175 9450 8400 9450 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 7 1 0 2 + 1 1 4.00 60.00 120.00 + 5475 9450 5475 4050 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 7 1 0 2 + 1 1 4.00 60.00 120.00 + 5475 5175 7425 5175 +2 4 0 3 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 9525 2925 3600 2925 3600 1950 9525 1950 9525 2925 +4 0 0 50 -1 18 18 0.0000 4 225 2445 1800 600 Data Center NYC\001 +4 0 0 50 -1 18 18 0.0000 4 225 2415 8775 525 Data Center SFO\001 +4 0 0 50 -1 16 16 0.0000 4 195 990 7350 9300 Network\001 +4 0 0 50 -1 16 16 0.0000 4 255 3465 6525 5025 Relaying to other data center\001 +4 0 0 50 -1 16 16 0.0000 4 240 1320 4875 3900 Application\001 +4 0 0 50 -1 16 16 0.0000 4 210 360 6300 2325 tcp\001 +4 0 0 50 -1 16 16 0.0000 4 240 450 9750 4050 udp\001 +4 0 0 50 -1 2 18 0.0000 4 195 225 3975 2850 X\001 +4 0 0 50 -1 2 18 0.0000 4 195 225 8925 2850 Y\001 Binary files /tmp/AYciA0_poF/libjgroups-java-2.7.0.GA/doc/manual/en/images/RELAY.png and /tmp/XQ0f23GK1l/libjgroups-java-2.12.2.Final/doc/manual/en/images/RELAY.png differ diff -Nru libjgroups-java-2.7.0.GA/doc/manual/en/images/StompArchitecture.fig libjgroups-java-2.12.2.Final/doc/manual/en/images/StompArchitecture.fig --- libjgroups-java-2.7.0.GA/doc/manual/en/images/StompArchitecture.fig 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/manual/en/images/StompArchitecture.fig 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,146 @@ +#FIG 3.2 Produced by xfig version 3.2.5 +Landscape +Center +Inches +Letter +100.00 +Single +-2 +1200 2 +6 1650 6900 4275 8175 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 3375 7275 3375 6900 2550 6900 2550 7275 3375 7275 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 2475 7275 2475 6900 1650 6900 1650 7275 2475 7275 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 2475 7725 2475 7350 1650 7350 1650 7725 2475 7725 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 3375 7725 3375 7350 2550 7350 2550 7725 3375 7725 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 4275 7275 4275 6900 3450 6900 3450 7275 4275 7275 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 4275 7725 4275 7350 3450 7350 3450 7725 4275 7725 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 2475 8175 2475 7800 1650 7800 1650 8175 2475 8175 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 3375 8175 3375 7800 2550 7800 2550 8175 3375 8175 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 4275 8175 4275 7800 3450 7800 3450 8175 4275 8175 +-6 +6 5025 6900 7650 8175 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 6750 7275 6750 6900 5925 6900 5925 7275 6750 7275 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 5850 7275 5850 6900 5025 6900 5025 7275 5850 7275 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 5850 7725 5850 7350 5025 7350 5025 7725 5850 7725 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 6750 7725 6750 7350 5925 7350 5925 7725 6750 7725 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 7650 7275 7650 6900 6825 6900 6825 7275 7650 7275 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 7650 7725 7650 7350 6825 7350 6825 7725 7650 7725 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 5850 8175 5850 7800 5025 7800 5025 8175 5850 8175 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 6750 8175 6750 7800 5925 7800 5925 8175 6750 8175 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 7650 8175 7650 7800 6825 7800 6825 8175 7650 8175 +-6 +6 1800 600 4425 1875 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 3525 975 3525 600 2700 600 2700 975 3525 975 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 2625 975 2625 600 1800 600 1800 975 2625 975 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 2625 1425 2625 1050 1800 1050 1800 1425 2625 1425 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 3525 1425 3525 1050 2700 1050 2700 1425 3525 1425 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 4425 975 4425 600 3600 600 3600 975 4425 975 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 4425 1425 4425 1050 3600 1050 3600 1425 4425 1425 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 2625 1875 2625 1500 1800 1500 1800 1875 2625 1875 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 3525 1875 3525 1500 2700 1500 2700 1875 3525 1875 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 4425 1875 4425 1500 3600 1500 3600 1875 4425 1875 +-6 +6 5475 600 8100 1875 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 7200 975 7200 600 6375 600 6375 975 7200 975 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 6300 975 6300 600 5475 600 5475 975 6300 975 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 6300 1425 6300 1050 5475 1050 5475 1425 6300 1425 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 7200 1425 7200 1050 6375 1050 6375 1425 7200 1425 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 8100 975 8100 600 7275 600 7275 975 8100 975 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 8100 1425 8100 1050 7275 1050 7275 1425 8100 1425 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 6300 1875 6300 1500 5475 1500 5475 1875 6300 1875 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 7200 1875 7200 1500 6375 1500 6375 1875 7200 1875 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 8100 1875 8100 1500 7275 1500 7275 1875 8100 1875 +-6 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 2400 4575 3825 4575 3825 5325 2400 5325 2400 4575 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 2400 3375 3825 3375 3825 4125 2400 4125 2400 3375 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 5025 3375 6450 3375 6450 4125 5025 4125 5025 3375 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 5025 4575 6450 4575 6450 5325 5025 5325 5025 4575 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 3.00 60.00 120.00 + 2250 6750 2250 5625 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 3.00 60.00 120.00 + 3000 6750 3000 5625 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 3.00 60.00 120.00 + 3600 6750 3600 5625 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 3.00 60.00 120.00 + 5400 6825 5400 5700 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 3.00 60.00 120.00 + 6075 6825 6075 5700 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 3.00 60.00 120.00 + 6975 6825 6450 5700 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 3.00 60.00 120.00 + 3150 2025 3150 3150 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 3.00 60.00 120.00 + 2325 2025 2775 3150 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 3.00 60.00 120.00 + 3975 2025 3600 3150 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 3.00 60.00 120.00 + 5850 2025 5850 3150 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 3.00 60.00 120.00 + 7725 2025 6450 3150 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 3.00 60.00 120.00 + 6825 2025 6075 3150 +3 3 0 1 0 7 50 -1 -1 0.000 0 0 0 11 + 1800 3075 3150 2400 4725 2550 6600 2400 7575 3600 7500 5250 + 6375 6225 4050 6225 2325 6000 1575 4575 1800 3075 + -1.000 -1.000 -1.000 -1.000 -1.000 -1.000 -1.000 -1.000 + -1.000 -1.000 -1.000 +4 0 0 50 -1 16 16 0.0000 4 195 825 2775 3825 NodeA\001 +4 0 0 50 -1 16 16 0.0000 4 195 825 5400 3825 NodeB\001 +4 0 0 50 -1 16 16 0.0000 4 195 840 5400 5025 NodeD\001 +4 0 0 50 -1 16 16 0.0000 4 195 840 2775 5025 NodeC\001 +4 0 0 50 -1 16 16 0.0000 4 195 825 8325 1350 Clients\001 +4 0 0 50 -1 16 16 0.0000 4 195 825 750 1350 Clients\001 +4 0 0 50 -1 16 16 0.0000 4 195 825 600 7650 Clients\001 +4 0 0 50 -1 16 16 0.0000 4 195 825 8250 7650 Clients\001 Binary files /tmp/AYciA0_poF/libjgroups-java-2.7.0.GA/doc/manual/en/images/StompArchitecture.png and /tmp/XQ0f23GK1l/libjgroups-java-2.12.2.Final/doc/manual/en/images/StompArchitecture.png differ diff -Nru libjgroups-java-2.7.0.GA/doc/manual/en/images/StompProtocol.fig libjgroups-java-2.12.2.Final/doc/manual/en/images/StompProtocol.fig --- libjgroups-java-2.7.0.GA/doc/manual/en/images/StompProtocol.fig 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/manual/en/images/StompProtocol.fig 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,34 @@ +#FIG 3.2 Produced by xfig version 3.2.5 +Landscape +Center +Inches +Letter +100.00 +Single +-2 +1200 2 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 4950 2025 7575 2025 7575 2850 4950 2850 4950 2025 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 4950 2925 7575 2925 7575 3750 4950 3750 4950 2925 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 4950 3825 7575 3825 7575 4650 4950 4650 4950 3825 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 4950 4725 7575 4725 7575 5550 4950 5550 4950 4725 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 4950 5625 7575 5625 7575 6450 4950 6450 4950 5625 +2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 + 4950 6525 7575 6525 7575 7350 4950 7350 4950 6525 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 7 0 0 5 + 8325 7800 8325 1650 4425 1650 4425 7800 8325 7800 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 3.00 60.00 120.00 + 2400 2400 4800 2400 +4 0 0 50 -1 16 18 0.0000 4 285 1290 5625 7050 Transport\001 +4 0 0 50 -1 16 18 0.0000 4 225 1065 5775 2550 STOMP\001 +4 0 0 50 -1 16 18 0.0000 4 225 1230 5625 4425 NAKACK\001 +4 0 0 50 -1 16 18 0.0000 4 225 840 5700 3525 FRAG\001 +4 0 0 50 -1 16 18 0.0000 4 225 750 5625 6150 PING\001 +4 0 0 50 -1 16 18 0.0000 4 285 1095 5625 5250 FD_ALL\001 +4 0 0 50 -1 16 18 0.0000 4 225 600 3375 2325 TCP\001 +4 0 0 50 -1 16 18 0.0000 4 225 1995 300 2475 STOMP clients\001 Binary files /tmp/AYciA0_poF/libjgroups-java-2.7.0.GA/doc/manual/en/images/StompProtocol.png and /tmp/XQ0f23GK1l/libjgroups-java-2.12.2.Final/doc/manual/en/images/StompProtocol.png differ diff -Nru libjgroups-java-2.7.0.GA/doc/manual/en/images/Tunneling.fig libjgroups-java-2.12.2.Final/doc/manual/en/images/Tunneling.fig --- libjgroups-java-2.7.0.GA/doc/manual/en/images/Tunneling.fig 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/manual/en/images/Tunneling.fig 2011-10-18 11:22:35.000000000 +0000 @@ -11,12 +11,6 @@ 3000 225 3000 3675 2 1 0 3 0 7 100 0 41 0.000 0 0 -1 0 0 2 6675 225 6675 3675 -2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 1 0 2 - 1 1 1.00 60.00 120.00 - 2250 2325 3975 2175 -2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 1 0 2 - 1 1 1.00 60.00 120.00 - 7575 2325 5250 2175 2 2 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 1050 1125 2250 1125 2250 3000 1050 3000 1050 1125 2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2 @@ -34,28 +28,39 @@ 2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2 7575 1650 8775 1650 2 2 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 - 3975 2025 5250 2025 5250 2700 3975 2700 3975 2025 -2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 1 1 2 + 3975 2850 5250 2850 5250 3525 3975 3525 3975 2850 +2 2 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 + 3975 1050 5250 1050 5250 1725 3975 1725 3975 1050 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 1 2 + 1 1 1.00 60.00 120.00 + 1 1 1.00 60.00 120.00 + 2250 2775 3975 3225 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 1 2 + 1 1 1.00 60.00 120.00 + 1 1 1.00 60.00 120.00 + 2250 2625 3975 1500 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 1 2 1 1 1.00 60.00 120.00 1 1 1.00 60.00 120.00 - 2250 2697 3975 2547 -2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 1 1 2 + 7575 2775 5250 3225 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 1 2 1 1 1.00 60.00 120.00 1 1 1.00 60.00 120.00 - 7573 2731 5248 2581 + 7575 2625 5250 1350 4 0 0 50 0 16 11 0.0000 4 120 495 1350 675 Host A\001 4 0 0 50 0 16 11 0.0000 4 120 495 4200 675 Host B\001 4 0 0 50 0 16 11 0.0000 4 120 510 7800 675 Host C\001 -4 0 0 50 0 16 11 0.0000 4 120 300 3150 2175 TCP\001 -4 0 0 50 0 16 11 0.0000 4 120 360 1425 1500 GMS\001 -4 0 0 50 0 16 11 0.0000 4 120 210 1500 1950 FD\001 -4 0 0 50 0 16 11 0.0000 4 120 390 1425 2400 PING\001 -4 0 0 50 0 16 11 0.0000 4 120 630 1275 2850 TUNNEL\001 -4 0 0 50 0 16 11 0.0000 4 120 630 7800 2850 TUNNEL\001 -4 0 0 50 0 16 11 0.0000 4 120 390 7950 2400 PING\001 -4 0 0 50 0 16 11 0.0000 4 120 210 8025 1950 FD\001 -4 0 0 50 0 16 11 0.0000 4 120 360 7950 1500 GMS\001 -4 0 0 50 0 16 11 0.0000 4 150 945 4125 2400 GossipRouter\001 -4 0 0 50 0 16 11 0.0000 4 120 300 3150 2775 TCP\001 -4 0 0 50 0 16 11 0.0000 4 120 300 6075 2175 TCP\001 -4 0 0 50 0 16 11 0.0000 4 120 300 6075 2775 TCP\001 +4 0 0 50 0 16 11 0.0000 4 120 375 1425 1500 GMS\001 +4 0 0 50 0 16 11 0.0000 4 120 225 1500 1950 FD\001 +4 0 0 50 0 16 11 0.0000 4 120 405 1425 2400 PING\001 +4 0 0 50 0 16 11 0.0000 4 120 660 1275 2850 TUNNEL\001 +4 0 0 50 0 16 11 0.0000 4 120 660 7800 2850 TUNNEL\001 +4 0 0 50 0 16 11 0.0000 4 120 405 7950 2400 PING\001 +4 0 0 50 0 16 11 0.0000 4 120 225 8025 1950 FD\001 +4 0 0 50 0 16 11 0.0000 4 120 375 7950 1500 GMS\001 +4 0 0 50 0 16 11 0.0000 4 150 1020 4125 3225 GossipRouter\001 +4 0 0 50 0 16 11 0.0000 4 150 1020 4125 1500 GossipRouter\001 +4 0 0 50 0 16 11 0.0000 4 120 330 3150 1800 TCP\001 +4 0 0 50 0 16 11 0.0000 4 120 330 6150 1800 TCP\001 +4 0 0 50 0 16 11 0.0000 4 120 330 6150 2850 TCP\001 +4 0 0 50 0 16 11 0.0000 4 120 330 3225 2850 TCP\001 Binary files /tmp/AYciA0_poF/libjgroups-java-2.7.0.GA/doc/manual/en/images/Tunneling.png and /tmp/XQ0f23GK1l/libjgroups-java-2.12.2.Final/doc/manual/en/images/Tunneling.png differ diff -Nru libjgroups-java-2.7.0.GA/doc/manual/en/master.xml libjgroups-java-2.12.2.Final/doc/manual/en/master.xml --- libjgroups-java-2.7.0.GA/doc/manual/en/master.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/manual/en/master.xml 2011-10-18 11:22:35.000000000 +0000 @@ -3,8 +3,8 @@ "../../../../docbook-support/support/docbook-dtd/docbookx.dtd" [ - - + + @@ -14,8 +14,7 @@ Reliable Multicasting with the JGroups Toolkit - $Date: 2008/01/23 14:36:56 $ - $Revision: 1.10 $ + Jan 2011 Bela @@ -31,51 +30,41 @@ - 1998-2008 + 1998-2006 Bela Ban - 2006-2008 + 2006-2011 Red Hat Inc - This document is copyrighted. Copies are allowed for - personal use. Redistribution only with written permission of the author(s). + This document is licensed under the + + Creative Commons Attribution-ShareAlike (CC-BY-SA) 3.0 + + Foreword - This is the Programmer's and User's Guide for - JGroups. It provides information about the following areas: + This is the JGroups manual. It provides information about: - Installation and configuration. + Installation and configuration - Using JGroups. + Using JGroups (the API) - Architecture and implementation of JGroups. Focus on the - protocol stack and protocols. + Configuration of the JGroups protocols - Most of the Installation and User's Guide has been copied - from what is freely available on the - JGroups web site - . - However, the focus of this document is to introduce programmers - who want to learn more about JGroups to the architecture and - implementation of JGroups. I will for example go into the - details of the protocol stack, how a message traverses the stack, - and how protocols can process it. I will also explain the various - design decisions I had to make when designing JGroups, which - hopefully leads to a better understanding of why things are the - way they are. + + The focus is on how to use JGroups, not on how JGroups is implemented. Here are a couple of points I want to abide by throughout @@ -88,7 +77,7 @@ I like simplicity. Keep It Simple and Stupid. This is - one of the biggest goals I have both in writing this book + one of the biggest goals I have both in writing this manual and in writing JGroups. It is easy to explain simple concepts in complex terms, but it is hard to explain a complex system in simple terms. I'll try to do the @@ -101,13 +90,13 @@ So, how did it all start? I spent 1998-1999 at the Computer Science Department at - Cornell University for a post-doc, in Ken Birman's group. Ken is + Cornell University as a post-doc, in Ken Birman's group. Ken is credited with inventing the group communication paradigm, especially the Virtual Synchrony model. At the time they were working on their third generation group communication prototype, called Ensemble. Ensemble followed Horus (written in C by Robbert VanRenesse), which followed ISIS (written by Ken Birman, also in - C). Ensemble was written in OCaml, developed at INRIA, which is a + C). Ensemble was written in OCaml, developed at INRIA, and is a functional language and related to ML. I never liked the OCaml language, which in my opinion has a hideous syntax. Therefore I never got warm with Ensemble either. @@ -115,8 +104,7 @@ However, Ensemble had a Java interface (implemented by a student in a semester project) which allowed me to program in Java and use Ensemble underneath. The Java part would require that an - Ensemble process was running somewhere on the same machine, or - within the same network, and would connect to it via a + Ensemble process was running somewhere on the same machine, and would connect to it via a bidirectional pipe. The student had developed a simple protocol for talking to the Ensemble engine, and extended the engine as well to talk back to Java. @@ -157,7 +145,7 @@ In the fall of 2002, Sacha Labourey contacted me, letting me know that JGroups was being used by JBoss for their clustering implementation. I joined JBoss in 2003 and have been working on JGroups and JBossCache. My - goal is to make JGroups the most widely used clustering software in the Java space... + goal is to make JGroups the most widely used clustering software in Java ... Bela Ban, San Jose, Aug 2002, Kreuzlingen Switzerland 2006 @@ -175,7 +163,7 @@ many fruitful discussions of all aspects of group communication in particular and distributed systems in general. - I want to dedicate this book to Jeannette and Michelle. + I want to dedicate this manual to Jeannette and Michelle. diff -Nru libjgroups-java-2.7.0.GA/doc/manual/en/modules/advanced.xml libjgroups-java-2.12.2.Final/doc/manual/en/modules/advanced.xml --- libjgroups-java-2.7.0.GA/doc/manual/en/modules/advanced.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/manual/en/modules/advanced.xml 2011-10-18 11:22:35.000000000 +0000 @@ -39,285 +39,9 @@ two different channels. -
- Using the Multiplexer to run multiple building blocks over the same - channel - - In JBoss we have multiple JGroups channels, one for each application - (e.g. JBossCache, ClusterPartition etc). - - The goal of the Multiplexer is to combine all stacks with the - same configuration into one, and have multiple - services on top of that same channel. - - To do this, we have to introduce multiplexing and demultiplexing - functionality, ie. each service will have to have a unique service ID (a - string), and when sending a message, the message has to be tagged with - that ID. When receiving a message, it will be dispatched to the right - destination service based on the ID attached to the message. We require - special handling for VIEW and SUSPECT messages: those need to be - dispatched to *all* services. State transfer also needs to be handled - specially, here we probably have to use thread locals, or change the API - (TBD). - - When deployed into JBoss, the Multiplexer will be exposed as an - MBean, and all services that depend on it will be deployed with dependency - injection on the Multiplexer. Of course, the old configuration will still - be supported. - - The config of the Multiplexer is done via a config file, which lists - a number of stacks, each keyed by a name, e.g. "udp", "tcp", "tcp-nio" - etc. See ./conf/stacks.xml for an example. An app is configured with the - name of a stack, e.g. "udp", and a reference to the Multiplexer MBean. It - will get a proxy channel through which all of its communication will take - place. The proxy channel (MuxChannel) will mux/demux messages to the real - JGroups channel. - - The advantage of the Multiplexer is that we can reduce N channels - into M where M < N. This means fewer threads, therefore fewer context - switches, less memory consumption and easier configuration and better - support.
- The Multiplexer API - - The Multiplexer is actually a JChannelFactory, which is configured - with a reference to an XML configuration file, and has a few additional - methods to get a Channel. The channel returned is actually an instance - of MuxChannel, which transparently forwards all invocations to the - underlying JChannel, and performs multiplexing and demultiplexing. - Multiple MuxChannels can share the same underlying JChannel, and each - message sent by a service over the MuxChannel will add the services's ID - to the message (as a header). That ID is then used to demultiplex the - message to the correct MuxChannel when received. - - The methods of the JChannelFactory are: - - - public Channel createMultiplexerChannel(String stack_name, String id) throws Exception; - public Channel createMultiplexerChannel(String stack_name, String id, boolean register_for_state_transfer, String substate_id) throws Exception; - - - The stack_name parameter refers to a channel configuration defined - in a separate file (see below). - - The id parameter is the service ID and has to be unique for all - services sitting on the same channel. If an ID is used more than once, - when trying to call createMultiplexerChannel(), an exception will be - thrown. - - the register_for_state_transfer and substate_id parameters are - discussed below (in ). - - The stack_name parameter is a reference to a stack, for example - defined in stacks.xml. A shortened version of stacks.xml is shown - below: - - - <protocol_stacks> - <stack name="fc-fast-minimalthreads" description="Flow control, no up or down threads"> - <config> - <UDP mcast_port="45566" - enable_bundling="true"/> - ... - <pbcast.STATE_TRANSFER down_thread="false" up_thread="false"/> - </config> - </stack> - - <stack name="sequencer" description="Totally ordered multicast using a sequencer"> - <config> - // config - </config> - </stack> - - <stack name="tcp" description="Using TCP as transport"> - <config> - <TCP start_port="7800" loopback="true" send_buf_size="100000" recv_buf_size="200000"/> - <TCPPING timeout="3000" initial_hosts="localhost[7800]" port_range="3" num_initial_members="3"/> - <FD timeout="2000" max_tries="4"/> - <VERIFY_SUSPECT timeout="1500" down_thread="false" up_thread="false"/> - <pbcast.NAKACK gc_lag="100" retransmit_timeout="600,1200,2400,4800"/> - <pbcast.STABLE stability_delay="1000" desired_avg_gossip="20000" down_thread="false" max_bytes="0" up_thread="false"/> - <VIEW_SYNC avg_send_interval="60000" down_thread="false" up_thread="false" /> - <pbcast.GMS print_local_addr="true" join_timeout="5000" shun="true"/> - </config> - </stack> - - <stack name="discovery" description="Simple UDP-only stack for discovery"> - <config> - <UDP mcast_port="7609" - mcast_addr="228.15.15.15" - ip_ttl="32"/> - </config> - </stack> - </protocol_stacks> - - - This file defines 4 configurations: fc-fast-minimalthreads, - sequencer, tcp and discovery. The first service to call - JChannelFactory.createMultiplexerChannel() with a stack_name of "tcp" - will create the JChannel with the "tcp" configuration, all subsequent - method calls for the same stack_name ("tcp") will - simply get a MuxChannel which has a reference to the same underlying - JChannel. When a service closes a MuxChannel, the underlying JChannel - will only be closed when there are no more MuxChannels referring to - it. - - For more information on Multiplexing refer to - JGroups/doc/design/Multiplexer.txt - -
- Batching state transfers - - Note that this feature is currently not - used in JBoss, because JBoss doesn't call all create() methods of all - dependent beans first, and then all start() methods. The call sequence - is indeterministic unless all dependent beans are defined in the same - XML file, which is unrealistic. We're looking into using a barrier - service to provide the guarantee that all create() methods are called - before all start() methods, possibly in JBoss 5. - - When multiple services are sharing a JChannel, and each of the - services requires state transfer at a different time, then we need - FLUSH (see ./doc/design/PartialStateTransfer.txt for a description of - the problem). FLUSH is also called the stop-the-world model, and - essentially stops everyone in a group from sending messages until the - state has been transferred, and then everyone can resume again. - The 2.3 release of JGroups will - - not - - have the FLUSH protocol integrated, so state transfer for the Multiplexer might be incorrect. 2.4 will have FLUSH, so that situation will be corrected. The main reason for putting Multiplexing into 2.3 is that people can start programming against the API, and then use it when FLUSH is available. - - - When multiple services share one JChannel, then we have to run - the FLUSH protocol for every service which requires state, so if we - have services A, B, C, D and E running on top of a JChannel J, and B,C - and E require state, then the FLUSH protocol has to be run 3 times, - which slows down startup (e.g.) of JBoss. - - To remedy this, we can batch state - transfers, so that we suspend everyone from sending messages, then - fetch the states for B, C and E at once, and then resume everyone. - Thus, the FLUSH protocol has to be run only once. - - To do this, a service has to register with the JChannelFactory - when creating the MuxChannel, and know that getState() will be a no-op - until the last registered application has called getState(). This - works as follows: - - B, C and D register for state transfer - - - - B calls MuxChannel.getState(). Nothing happens. - - - - D calls MuxChannel.getState(). Nothing happens. - - - - E calls MuxChannel.getState(). Now everyone who registered has called getState() and therefore we transfer the state for B, C and E (using partial state transfer). - - - - At this point B, C and D's setState() will be called, so that they can set the state. - - - - The code below (a snipper from MultiplexerTest) shows how - services can register for state transfers. In an MBean (JBoss) - environment, the registration could be done in the create() callback, - and the actual getState() call in start(). - - - public void testStateTransferWithRegistration() throws Exception { - final String STACK_NAME="fc-fast-minimalthreads"; - Channel ch1, ch2; - ch1=factory.createMultiplexerChannel(STACK_NAME, "c1", true, null); // register for (entire) state transfer - ch1.connect("bla"); // will create a new JChannel - - ch2=factory.createMultiplexerChannel(STACK_NAME, "c2", true, null); // register for (entire) state transfer - ch2.connect("bla"); // will share the JChannel created above (same STACK_NAME) - - boolean rc=ch1.getState(null, 5000); // this will *not* trigger the state transfer protocol - rc=ch2.getState(null, 5000); // only *this* will trigger the state transfer - } - - - The example above shows that 2 services ("c1" and "c2") share a - common JChannel because they use the same stack_name (STACK_NAME). It - also shows that only the second getState() invocation will actually - transfer the 2 states (for "c1" and "c2"). -
-
- -
- Service views - - When we have multiple service running on the same channel, then - some services might get redeployed or stopped independently from the other - services. So we might have a situation where we have services S1, S2 and - S3 running on host H1, but on host H2, only services S2 and S3 are - running. - - The cluster view is {H1, H2}, but the - service views are: - - - - S1: {H1} - - - - S2: {H1, H2} - - - - S3: {H1, H2} - - - - This can also be seen as ordered by hosts: - - - - H1: {S1, S2, S3} - - - - H2: {S2, S3} - - - - So here we host H1 running services S1, S2 and S3, whereas H2 is - only running S2 and S3. S1 might be in the process of being redeployed - on H2, or is simply not running. - - A service view is essentially a list of nodes of a cluster on - which a given service S is currently running. Service views are always - subsets of cluster views. Here's a reason we need service views: - consider the example above. Let's say service S1 on H1 wants to make a - cluster-wide method invocation on all instances of S1 running on any - host. Now, S1 is only running on H1, therefore we have to make the - invocation only on S1. However, if we took the cluster view rather than - the service view, the invocation would be across H1 and H2, and we'd be - waiting for a response from the (non-existent) service S1 on H2 forever - ! - - So, by default, calling MuxChannel.getView() will return the - service view rather than the cluster view. The cluster view can be - retrieved calling MuxChannel.getClusterView(). - - There are example unit tests in MultiplexerTest and - MultiplexerViewTest. The latter tests service views versus cluster - views. -
-
- -
- Sharing a transport between multiple channels in a JVM + The shared transport: sharing a transport between multiple channels in a JVM To save resources (threads, sockets and CPU cycles), transports of channels residing within the same @@ -645,14 +369,14 @@ The following example shows how to disable the use of IP multicasting and use a GossipRouter instead. Only the bottom two protocols are shown, the rest of the stack is the same as in the - previous example: - + previous example: - <UDP ip_mcast="false" mcast_addr="224.0.0.35" mcast_port="45566" ip_ttl="32" - mcast_send_buf_size="150000" mcast_recv_buf_size="80000"/> - <PING gossip_host="localhost" gossip_port="5555" gossip_refresh="15000" - timeout="2000" num_initial_members="3"/> - + <UDP ip_mcast="false" mcast_addr="224.0.0.35" mcast_port="45566" ip_ttl="32" + mcast_send_buf_size="150000" mcast_recv_buf_size="80000"/> + <PING gossip_host="localhost" gossip_port="5555" gossip_refresh="15000" + timeout="2000" num_initial_members="3"/> + + The property ip_mcast is set to false in UDP and the gossip @@ -684,8 +408,7 @@ WANs. The properties for a typical stack based on TCP might look like - this (edited/protocols removed for brevity): - + this (edited/protocols removed for brevity): <TCP start_port="7800" /> <TCPPING timeout="3000" @@ -702,7 +425,8 @@ <pbcast.GMS print_local_addr="true" join_timeout="3000" shun="true" view_bundling="true"/> - + + @@ -788,6 +512,10 @@ . The only difference is that TCPGOSSIP allows for multiple GossipRouters instead of only one. + + + JDBC_PING: using a shared database via JDBC or DataSource. + The next two section illustrate the use of TCP with both TCPPING @@ -797,13 +525,13 @@ Using TCP and TCPPING A protocol stack using TCP and TCPPING looks like this (other - protocols omitted): - + protocols omitted): - <TCP start_port="7800" /> + - <TCPPING initial_hosts="HostA[7800],HostB[7800]" port_range="5" - timeout="3000" num_initial_members="3" /> - + <TCP start_port="7800" /> + + <TCPPING initial_hosts="HostA[7800],HostB[7800]" port_range="5" + timeout="3000" num_initial_members="3" /> + + The concept behind TCPPING is that no external daemon such as GossipRouter is needed. Instead some selected group members assume the @@ -838,13 +566,13 @@ gossip_port and gossip_refresh set. However, in TCPGOSSIP these properties are called differently as shown below (only the bottom two - protocols are shown): - - - <TCP /> - <TCPGOSSIP initial_hosts="localhost[5555],localhost[5556]" gossip_refresh_rate="10000" - num_initial_members="3" /> - + protocols are shown): + + <TCP /> + <TCPGOSSIP initial_hosts="localhost[5555],localhost[5556]" gossip_refresh_rate="10000" + num_initial_members="3" /> + + The initial_hosts properties combines both the host and port of a GossipRouter, and it is possible to @@ -890,20 +618,24 @@ so other members (possibly also behind firewalls) can access it. The solution works as follows. A channel inside a firewall has - to use protocol TUNNEL instead of UDP as bottommost layer in the - stack, plus either PING or TCPGOSSIP, as shown below (only the bottom - two protocols shown): - - - <TUNNEL router_host="localhost" router_port="12001" /> - <TCPGOSSIP initial_hosts="localhost[12001]" gossip_refresh_rate="10000" - num_initial_members="3" /> - + to use protocol TUNNEL instead of UDP or TCP as bottommost layer. Recommended + discovery protocol is PING, starting with 2.8 release, you do not have to specify + any gossip routers in PING. + + <TUNNEL gossip_router_hosts="127.0.0.1[12001]" /> + <PING /> + + TCPGOSSIP uses the GossipRouter (outside the firewall) at port 12001 to register its address (periodically) and to retrieve the initial membership for its - group. + group. It is not recommended to use TCPGOSSIP for discovery if TUNNEL is + already used. TCPGOSSIP might be used in rare scenarios when registration and + initial member discovery has to be done through gossip + router indepedent of transport protocol being used. Starting with 2.8 release + TCPGOSSIP accepts one or multiple router hosts as a comma delimited list + of host[port] elements specified in a property initial_hosts. TUNNEL establishes a TCP connection to the GossipRouter process (also outside the firewall) that @@ -920,7 +652,10 @@ Note that TUNNEL has to be given the hostname and port of the GossipRouter process. This example assumes a GossipRouter is running on the local host at port 12001. Both - TUNNEL and TCPGOSSIP (or PING) access the same GossipRouter. + TUNNEL and TCPGOSSIP (or PING) access the same GossipRouter. + Starting with 2.8 release TUNNEL transport layer accepts one or multiple router + hosts as a comma delimited list of host[port] elements specified in a + property gossip_router_hosts. Any time a message has to be sent, TUNNEL forwards the message to GossipRouter, which distributes it to its destination: if the message's @@ -933,6 +668,28 @@ To do so, GossipRouter has to maintain a table between groups, member addresses and TCP connections. . + + + Starting with 2.8 release, gossip router is no longer a single + point of failure. In a set-up with multiple gossip routers, routers do + not communicate among themselves, and single point of failure is avoided + by having each channel simply connect to multiple available routers. In + case one or more routers go down, cluster members are still able to + exchange message through remaining available router instances, if there + are any. + + For each send invocation, a channel goes through a list of available + connections to routers and attempts to send a message on each connection + until it succeeds. If a message could not be sent on any of the + connections – an exception is raised. Default policy for connection + selection is random. However, we also provide an plug-in interface for + other policies as well. + + Gossip router configuration is static and is not updated for the + lifetime of the channel. A list of available routers has to be provided + in channel configuration file. + + To tunnel a firewall using JGroups, the following steps have to be taken: @@ -944,11 +701,11 @@ - Start the GossipRouter: - - - start org.jgroups.stack.GossipRouter -port 12001 - + Start the GossipRouter: + + start org.jgroups.stack.GossipRouter -port 12001 + + @@ -1001,24 +758,15 @@ connection to B. Also, applications behind a firewall would be able to talk to each other, joining a group. - However, there are several drawbacks: first, the central - GossipRouter process constitute a single point of failure - (if host B crashes) - Although multiple GossipRouters could be started - , second, having to maintain a TCP connection for the + However, there are several drawbacks: first, having to maintain a TCP connection for the duration of the connection might use up resources in the host system - (e.g. in the GossipRouter), leading to scalability problems, third, this + (e.g. in the GossipRouter), leading to scalability problems, second, this scheme is inappropriate when only a few channels are located behind firewalls, and the vast majority can indeed use IP multicast to communicate, and finally, it is not always possible to enable outgoing traffic on 2 ports in a firewall, e.g. when a user does not 'own' the firewall. - - - There will be a major overhaul of GossipRouter/TUNNEL in 2.6, where we'll streamline the connection - table and possibly introduce new functionality such as connecting to multiple GossipRouters, - connecting to both IP multicasting and TCP based clients etc. - +
@@ -1241,6 +989,71 @@ +
+ Scopes: concurrent message delivery for messages from the same sender + + In the previous paragraph, we showed how the concurrent stack delivers messages from different senders + concurrently. But all (non-OOB) messages from the same sender P are delivered in the order in which + P sent them. However, this is not good enough for certain types of applications. + + + Consider the case of an application which replicates HTTP sessions. If we have sessions X, Y and Z, then + updates to these sessions are delivered in the order in which there were performed, e.g. X1, X2, X3, + Y1, Z1, Z2, Z3, Y2, Y3, X4. This means that update Y1 has to wait until updates X1-3 have been delivered. + If these updates take some time, e.g. spent in lock acquisition or deserialization, then all subsequent + messages are delayed by the sum of the times taken by the messages ahead of them in the delivery order. + + + However, in most cases, updates to different web sessions should be completely unrelated, so they could + be delivered concurrently. For instance, a modification to session X should not have any effect on + session Y, therefore updates to X, Y and Z can be delivered concurrently. + + + One solution to this is out-of-band (OOB) messages (see next paragraph). However, OOB messages do not + guarantee ordering, so updates X1-3 could be delivered as X1, X3, X2. If this is not wanted, but + messages pertaining to a given web session should all be delivered concurrently between sessions, but + ordered within a given session, then we can resort to scoped messages. + + + Scoped messages apply only to regular (non-OOB) messages, and are delivered + concurrently between scopes, but ordered within a given scope. For example, if we used the sessions above + (e.g. the jsessionid) as scopes, then the delivery could be as follows ('->' means sequential, '||' means concurrent): + + X1 -> X2 -> X3 -> X4 || Y1 -> Y2 -> Y3 || Z1 -> Z2 -> Z3 + + This means that all updates to X are delivered in parallel to updates to Y and updates to Z. However, within + a given scope, updates are delivered in the order in which they were performed, so X1 is delivered before + X2, which is deliverd before X3 and so on. + + + Taking the above example, using scoped messages, update Y1 does not have to wait for + updates X1-3 to complete, but is processed immediately. + + + To set the scope of a message, use method Message.setScope(short). + + + Scopes are implemented in a separate protocol called . This protocol + has to be placed somewhere above ordering protocols like UNICAST or NAKACK (or SEQUENCER for that matter). + + + + Uniqueness of scopes + + Note that scopes should be as unique as possible. Compare this to hashing: the fewer collisions + there are, the better the concurrency will be. So, if for example, two web sessions pick the same + scope, then updates to those sessions will be delivered in the order in which they were sent, and + not concurrently. While this doesn't cause erraneous behavior, it defies the purpose of SCOPE. + + + Also note that, if multicast and unicast messages have the same scope, they will be delivered + in sequence. So if A multicasts messages to the group with scope 25, and A also unicasts messages + to B with scope 25, then A's multicasts and unicasts will be delivered in order at B ! Again, + this is correct, but since multicasts and unicasts are unrelated, might slow down things ! + + +
+
Out-of-band messages @@ -1360,6 +1173,11 @@ Misc
Shunning + + + Note that in 2.8, shunning has been removed, so the sections below only apply to versions up to 2.7. + + Let's say we have 4 members in a group: {A,B,C,D}. When a member (say D) is expelled from the group, e.g. because it didn't respond to are-you-alive messages, and later comes back, then it is shunned. Shunning causes a member to leave the group and re-join, if this is enabled on the Channel. To enable automatic @@ -1385,9 +1203,62 @@ will shun everybody else (a real shun fest :-)). This is clearly not desirable, so in this case shunning should be turned off: - <FD timeout="2000" max_tries="3" shun="false"/> ... <pbcast.GMS join_timeout="3000" shun="false"/> + <FD timeout="2000" max_tries="3" shun="false"/> + ... + <pbcast.GMS join_timeout="3000" shun="false"/>
+
+ Using a custom socket factory + + JGroups creates all of its sockets through a SocketFactory, which is located in the transport (TP) or + TP.ProtocolAdapter (in a shared transport). The factory has methods to create sockets (Socket, + ServerSocket, DatagramSocket and MulticastSocket) + + + Currently, SocketFactory does not support creation of NIO sockets / channels. + + , + closen sockets and list all open sockets. Every socket creation method has a service name, which could + be for example "jgroups.fd_sock.srv_sock". The service name is used to look up a port (e.g. in a config + file) and create the correct socket. + + + To provide one's own socket factory, the following has to be done: if we have a non-shared transport, + the code below creates a SocketFactory implementation and sets it in the transport: + + + JChannel ch; + MySocketFactory factory; // e.g. extends DefaultSocketFactory + ch=new JChannel("config.xml"); + ch.setSocketFactory(new MySocketFactory()); + ch.connect("demo"); + + + + If a shared transport is used, then we have to set 2 socket factories: 1 in the shared transport and + one in the TP.ProtocolAdapter: + + + JChannel c1=new JChannel("config.xml"), c2=new JChannel("config.xml"); + + TP transport=c1.getProtocolStack().getTransport(); + transport.setSocketFactory(new MySocketFactory("transport")); + + c1.setSocketFactory(new MySocketFactory("first-cluster")); + c2.setSocketFactory(new MySocketFactory("second-cluster")); + + c1.connect("first-cluster"); + c2.connect("second-cluster"); + + + + First, we grab one of the channels to fetch the transport and set a SocketFactory in it. Then we + set one SocketFactory per channel that resides on the shared transport. When JChannel.connect() is + called, the SocketFactory will be set in TP.ProtocolAdapter. + + +
@@ -1502,7 +1373,8 @@ private static void handleView(JChannel ch, View new_view) { if(new_view instanceof MergeView) { ViewHandler handler=new ViewHandler(ch, (MergeView)new_view); - handler.start(); // requires separate thread as we don't want to block JGroups + // requires separate thread as we don't want to block JGroups + handler.start(); } } @@ -1520,8 +1392,8 @@ View tmp_view=subgroups.firstElement(); // picks the first Address local_addr=ch.getLocalAddress(); if(!tmp_view.getMembers().contains(local_addr)) { - System.out.println("I (" + local_addr + ") am not member of the new primary partition (" + tmp_view + - "), will re-acquire the state"); + System.out.println("Not member of the new primary partition (" + + tmp_view + "), will re-acquire the state"); try { ch.getState(null, 30000); } @@ -1529,8 +1401,8 @@ } } else { - System.out.println("I (" + local_addr + ") am member of the new primary partition (" + tmp_view + - "), will do nothing"); + System.out.println("Not member of the new primary partition (" + + tmp_view + "), will do nothing"); } } } @@ -1584,7 +1456,7 @@ On view change V: - If V has >= N members: - - If not read-write: get state from coordinator and switch to read-write + - If not read-write: get state from coord and switch to read-write - Else: switch to read-only @@ -1650,4 +1522,319 @@
+ + +
+ Large clusters + + This section is a collection of best practices and tips and tricks for running large clusters on JGroups. + By large clusters, we mean several hundred nodes in a cluster. + + +
+ Reducing chattiness + + When we have a chatty protocol, scaling to a large number of nodes might be a problem: too many messages + are sent and - because they are generated in addition to the regular traffic - this can have a + negative impact on the cluster. A possible impact is that more of the regular messages are dropped, and + have to be retransmitted, which impacts performance. Or heartbeats are dropped, leading to false + suspicions. So while the negative effects of chatty protocols may not be seen in small clusters, they + will be seen in large clusters ! + + +
+ Discovery + + A discovery protocol (e.g. PING, TCPPING, MPING etc) is run at startup, to discover the initial + membership, and periodically by the merge protocol, to detect partitioned subclusters. + + + When we send a multicast discovery request to a large cluster, every node in the cluster might + possibly reply with a discovery response sent back to the sender. So, in a cluster of 300 nodes, + the discovery requester might be up to 299 discovery responses ! Even worse, because num_ping_requests + in Discovery is by default set to 2, so we're sending 2 discovery requests, we might receive up to + num_ping_requests * (N-1) discovery responses, even though we might be able to find out the + coordinator after a few responses already ! + + + To reduce the large number of responses, we can set a max_rank property: the value defines which + members are going to send a discovery response. The rank is the index of a member in a cluster: in + {A,B,C,D,E}, A's index is 1, B's index is 2 and so on. A max_rank of 3 would trigger discovery + responses from only A, B and C, but not from D or E. + + + We highly recommend setting max_rank in large clusters. + + + This functionality was implemented in + https://jira.jboss.org/browse/JGRP-1181. + +
+
+ Failure detection protocols + + Failure detection protocols determine when a member is unresponsive, and subsequently + suspect it. Usually (FD, FD_ALL), messages (heartbeats) are used to determine + the health of a member, but we can also use TCP connections (FD_SOCK) to connect to a member P, and + suspect P when the connection is closed. + + + Heartbeating requires messages to be sent around, and we need to be careful to limit the number of + messages sent by a failure detection protocol (1) to detect crashed members and (2) when a member + has been suspected. The following sections discuss how to configure FD_ALL and FD_SOCK, the most + commonly used failure detection protocols, for use in large clusters. + + +
+ FD_SOCK +
+ +
+ FD_ALL +
+
+ +
+
+ +
+ Bridging between remote clusters + + In 2.12, the RELAY protocol was added to JGroups (for the properties see RELAY). + It allows for bridging of remote clusters. For example, if we have a cluster in New York (NYC) and another + one in San Francisco (SFO), then RELAY allows us to bridge NYC and SFO, so that multicast messages sent in + NYC will be forwarded to SFO and vice versa. + + + The NYC and SFO clusters could for example use IP multicasting (UDP as transport), and the bridge could use + TCP as transport. The SFO and NYC clusters don't even need to use the same cluster name. + + + shows how the two clusters are bridged. + + +
Relaying between different clusters + +
+
+ + The cluster on the left side with nodes A (the coordinator), B and C is called "NYC" and use IP + multicasting (UDP as transport). The cluster on the right side ("SFO") has nodes D (coordinator), E and F. + + + The bridge between the local clusters NYC and SFO is essentially another cluster with the coordinators + (A and D) of the local clusters as members. The bridge typically uses TCP as transport, but any of the + supported JGroups transports could be used (including UDP, if supported across a WAN, for instance). + + + Only a coordinator relays traffic between the local and remote cluster. When A crashes or leaves, then the + next-in-line (B) takes over and starts relaying. + + + Relaying is done via the RELAY protocol added to the top of the stack. The bridge is configured with + the bridge_props property, e.g. bridge_props="/home/bela/tcp.xml". This creates a JChannel inside RELAY. + + + Note that property "site" must be set in both subclusters. In the example above, we could set site="nyc" + for the NYC subcluster and site="sfo" for the SFO ubcluster. + + + The design is described in detail in JGroups/doc/design/RELAY.txt (part of the source distribution). In + a nutshell, multicast messages received in a local cluster are wrapped and forwarded to the remote cluster + by a relay (= the coordinator of a local cluster). When a remote cluster receives such a message, it is + unwrapped and put onto the local cluster. + + + JGroups uses subclasses of UUID (PayloadUUID) to ship the site name with an address. When we see an address + with site="nyc" on the SFO side, then RELAY will forward the message to the SFO subcluster, and vice versa. + When C multicasts a message in the NYC cluster, A will forward it to D, which will re-broadcast the message on + its local cluster, with the sender being D. This means that the sender of the local broadcast will appear + as D (so all retransmit requests got to D), but the original sender C is preserved in the header. + At the RELAY protocol, the sender will be replaced with the original sender (C) having site="nyc". + When node F wants to reply to the sender of the multicast, the destination + of the message will be C, which is intercepted by the RELAY protocol and forwarded to the current + relay (D). D then picks the correct destination (C) and sends the message to the remote cluster, where + A makes sure C (the original sender) receives it. + + + An important design goal of RELAY is to be able to have completely autonomous clusters, so NYC doesn't for + example have to block waiting for credits from SFO, or a node in the SFO cluster doesn't have to ask a node + in NYC for retransmission of a missing message. + +
+ Views + + RELAY presents a global view to the application, e.g. a view received by + nodes could be {D,E,F,A,B,C}. This view is the same on all nodes, and a global view is generated by + taking the two local views, e.g. A|5 {A,B,C} and D|2 {D,E,F}, comparing the coordinators' addresses + (the UUIDs for A and D) and concatenating the views into a list. So if D's UUID is greater than + A's UUID, we first add D's members into the global view ({D,E,F}), and then A's members. + + + Therefore, we'll always see all of A's members, followed by all of D's members, or the other way round. + + + To see which nodes are local and which ones remote, we can iterate through the addresses (PayloadUUID) + and use the site (PayloadUUID.getPayload()) name to for example differentiate between "nyc" and "sfo". + +
+
+ Configuration + + To setup a relay, we need essentially 3 XML configuration files: 2 to configure the local clusters and + 1 for the bridge. + + + To configure the first local cluster, we can copy udp.xml from the JGroups distribution and add RELAY on top + of it: <RELAY bridge_props="/home/bela/tcp.xml" />. Let's say we call this config relay.xml. + + + The second local cluster can be configured by copying relay.xml to relay2.xml. Then change the + mcast_addr and/or mcast_port, so we actually have 2 different cluster in case we run instances of + both clusters in the same network. Of course, if the nodes of one cluster are run in a different + network from the nodes of the other cluster, and they cannot talk to each other, then we can simply + use the same configuration. + + + The 'site' property needs to be configured in relay.xml and relay2.xml, and it has to be different. For + example, relay.xml could use site="nyc" and relay2.xml could use site="sfo". + + + The bridge is configured by taking the stock tcp.xml and making sure both local clusters can see each + other through TCP. + +
+ +
+ +
+ Daisychaining + + Daisychaining refers to a way of disseminating messages sent to the entire cluster. + + + The idea behind it is that it is inefficient to broadcast a message in clusters where IP multicasting is + not available. For example, if we only have TCP available (as is the case in most clouds today), then we + have to send a broadcast (or group) message N-1 times. If we want to broadcast M to a cluster of 10, + we send the same message 9 times. + + + Example: if we have {A,B,C,D,E,F}, and A broadcasts M, then it sends it to B, then to C, then to D etc. + If we have a 1 GB switch, and M is 1GB, then sending a broadcast to 9 members takes 9 seconds, even if we + parallelize the sending of M. This is due to the fact that the link to the switch only sustains 1GB / sec. + (Note that I'm conveniently ignoring the fact that the switch will start dropping packets if it is + overloaded, causing TCP to retransmit, slowing things down)... + + + Let's introduce the concept of a round. A round is the time it takes to send or receive a message. + In the above example, a round takes 1 second if we send 1 GB messages. + In the existing N-1 approach, it takes X * (N-1) rounds to send X messages to a cluster of N nodes. + So to broadcast 10 messages a the cluster of 10, it takes 90 rounds. + + + Enter DAISYCHAIN. + + + + The idea is that, instead of sending a message to N-1 members, we only send it to our neighbor, which + forwards it to its neighbor, and so on. For example, in {A,B,C,D,E}, D would broadcast a message by + forwarding it to E, E forwards it to A, A to B, B to C and C to D. We use a time-to-live field, + which gets decremented on every forward, and a message gets discarded when the time-to-live is 0. + + + The advantage is that, instead of taxing the link between a member and the switch to send N-1 messages, + we distribute the traffic more evenly across the links between the nodes and the switch. + Let's take a look at an example, where A broadcasts messages m1 and m2 in + cluster {A,B,C,D}, '-->' means sending: + + +
+ Traditional N-1 approach + + + Round 1: A(m1) --> B + Round 2: A(m1) --> C + Round 3: A(m1) --> D + Round 4: A(m2) --> B + Round 5: A(m2) --> C + Round 6: A(m2) --> D + + + It takes 6 rounds to broadcast m1 and m2 to the cluster. + +
+ +
+ Daisychaining approach + + + Round 1: A(m1) --> B + Round 2: A(m2) --> B || B(m1) --> C + Round 3: B(m2) --> C || C(m1) --> D + Round 4: C(m2) --> D + + In round 1, A send m1 to B. + In round 2, A sends m2 to B, but B also forwards m1 (received in round 1) to C. + In round 3, A is done. B forwards m2 to C and C forwards m1 to D (in parallel, denoted by '||'). + In round 4, C forwards m2 to D. + +
+ +
+ Switch usage + + Let's take a look at this in terms of switch usage: in the N-1 approach, A can only send 125MB/sec, + no matter how many members there are in the cluster, so it is constrained by the link capacity to the + switch. (Note that A can also receive 125MB/sec in parallel with today's full duplex links). + + + So the link between A and the switch gets hot. + + + In the daisychaining approach, link usage is more even: if we look for example at round 2, A sending + to B and B sending to C uses 2 different links, so there are no constraints regarding capacity of a + link. The same goes for B sending to C and C sending to D. + + + In terms of rounds, the daisy chaining approach uses X + (N-2) rounds, so for a cluster size of 10 and + broadcasting 10 messages, it requires only 18 rounds, compared to 90 for the N-1 approach ! + +
+ +
+ Performance + + To measure performance of DAISYCHAIN, a performance test (test.Perf) was run, with 4 nodes connected + to a 1 GB switch; and every node sending 1 million 8K messages, for a total of 32GB received by + every node. The config used was tcp.xml. + + + The N-1 approach yielded a throughput of 73 MB/node/sec, and the daisy chaining approach 107MB/node/sec ! + + +
+ +
+ Configuration + + DAISYCHAIN can be placed directly on top of the transport, regardless of whether it is UDP or TCP, e.g. + + <TCP .../> + <DAISYCHAIN .../> + <TCPPING .../> + + +
+ +
+ +
+ Ergonomics + + Ergonomics is similar to the dynamic setting of optimal values for the JVM, e.g. garbage collection, + memory sizes etc. In JGroups, ergonomics means that we try to dynamically determine and set optimal + values for protocol properties. Examples are thread pool size, flow control credits, heartbeat + frequency and so on. + +
\ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/doc/manual/en/modules/api.xml libjgroups-java-2.12.2.Final/doc/manual/en/modules/api.xml --- libjgroups-java-2.7.0.GA/doc/manual/en/modules/api.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/manual/en/modules/api.xml 2011-10-18 11:22:35.000000000 +0000 @@ -20,7 +20,7 @@ The org.jgroups.util.Util class contains a collection of useful functionality which cannot be assigned to any - particular other package. + particular package.
objectToByteBuffer(), objectFromByteBuffer() @@ -30,28 +30,7 @@ externalizable). The byte array is then returned. This method is often used to serialize objects into the byte buffer of a message. The second method returns a reconstructed object from a buffer. Both methods throw - an exception if the object cannot be serialized or unseriali -
- -
- printMessage() - - Prints the message given as argument in readable format. Returns a - string. -
- -
- activeThreads() - - Returns a strings containing a pretty-printed list of currently - running threads. -
- -
- printMembers() - - Given a list of member addresses, pretty-prints the members and - returns a string. + an exception if the object cannot be serialized or unserialized.
@@ -62,32 +41,6 @@ therefore they are listed first.
- Transport - - Interface Transport looks as - follows: - - - public interface Transport { - public void send(Message msg) throws Exception; - public Object receive(long timeout) throws Exception; - } - - - It defines a very small subset of the functionality of a channel, - essentially only the methods for sending and receiving messages. There - are a number of classes that implement Transport - , among others Channel . Many building blocks - (see ) require nothing else than - a bare-bone facility to send and receive messages; therefore the - Transport interface was created. It increases the - genericness and portability of building blocks: being so simple, the - Transport interface can easily be ported to a - different toolkit, without requiring any modifications to building - blocks. -
- -
MessageListener Contrary to the pull-style of channels, some building blocks (e.g. @@ -96,15 +49,15 @@ the entity to be notified of message reception needs to provide a callback to be invoked whenever a message has been received. The MessageListener interface below provides a method - to do so: - - - public interface MessageListener { - public void receive(Message msg); - byte[] getState(); - void setState(byte[] state); - } - + to do so: + + public interface MessageListener { + public void receive(Message msg); + byte[] getState(); + void setState(byte[] state); + } + + Method receive() will be called when a message is received. The getState() and @@ -116,47 +69,44 @@
ExtendedMessageListener - JGroups release 2.3 introduces ExtendedMessageListener enabling - partial state transfer (refer to - ) while release 2.4 further expands ExtendedMessageListener with - streaming state transfer callbacks: + + JGroups release 2.3 introduced ExtendedMessageListener enabling + partial state transfer (refer to + ) while release 2.4 further expands ExtendedMessageListener with + streaming state transfer callbacks: - public interface ExtendedMessageListener extends MessageListener { - byte[] getState(String state_id); - void setState(String state_id, byte[] state); + public interface ExtendedMessageListener extends MessageListener { + byte[] getState(String state_id); + void setState(String state_id, byte[] state); - /*** since JGroups 2.4 *****/ - void getState(OutputStream ostream); - void getState(String state_id, OutputStream ostream); - void setState(InputStream istream); - void setState(String state_id, InputStream istream); - } - + /*** since JGroups 2.4 *****/ + void getState(OutputStream ostream); + void getState(String state_id, OutputStream ostream); + void setState(InputStream istream); + void setState(String state_id, InputStream istream); + } + + - Method receive() will be called when a - message is received. The getState() and - setState() methods are used to fetch and set - the group state (e.g. when joining). Refer to for a discussion of state transfer.
MembershipListener - The MembershipListener interface is similar - to the MessageListener interface above: every - time a new view, a suspicion message, or a block event is received, the - corresponding method of the class implementing - MembershipListener will be called. + + The MembershipListener interface is similar to the MessageListener + interface above: every time a new view, a suspicion message, or a block event is received, the corresponding + method of the class implementing MembershipListener will be called. - - public interface MembershipListener { - public void viewAccepted(View new_view); - public void suspect(Object suspected_mbr); - public void block(); - } - + + public interface MembershipListener { + public void viewAccepted(View new_view); + public void suspect(Object suspected_mbr); + public void block(); + } + + Oftentimes the only method containing any functionality will be viewAccepted() which notifies the receiver that @@ -178,33 +128,48 @@ - Therefore, block() can be used to send pending messages or complete some other work. However, - sending of messages should be done on a different thread, e.g. the current thread blocks on a mutex, - starts a different thread which notifies the mutex once the work has been done. + Therefore, block() can be used to send pending messages or complete some other work. - Note that block() should take a small amount of time to complete, otherwise the entire FLUSH protocol - is blocked. + Note that block() should be brief, or else the entire FLUSH protocol is blocked. + + Sending messages in callbacks + + Note that anything that could block should not be done in a callback. + This includes sending of messages; + if we have FLUSH on the stack, and send a message in a viewAccepted() callback, then the following + happens: the FLUSH protocol blocks all (multicast) messages before installing a view, then installs + the view, then unblocks. However, because installation of the view triggers the viewAccepted() callback, + sending of messages inside of viewAccepted() will block. This in turn blocks the viewAccepted() thread, + so the flush will never return ! + + + If we need to send a message in a callback, the sending should be done on a separate thread, or a timer + task should be submitted to the timer. + +
ExtendedMembershipListener - The ExtendedMembershipListener interface extends - MembershipListener: + + The ExtendedMembershipListener interface extends + MembershipListener: public interface ExtendedMembershipListener extends MembershipListener { public void unblock(); } + The unblock() method is called - to notify the member that the flush protocol has completed and the member can resume + to notify the member that the FLUSH protocol has completed and the member can resume sending messages. If the member did not stop sending messages on block(), FLUSH simply blocked them and will resume, so no action is required from a member. Implementation of the unblock() callback is optional. @@ -215,16 +180,17 @@
ChannelListener - - - public interface ChannelListener { - void channelConnected(Channel channel); - void channelDisconnected(Channel channel); - void channelClosed(Channel channel); - void channelShunned(); - void channelReconnected(Address addr); - } - + + + public interface ChannelListener { + void channelConnected(Channel channel); + void channelDisconnected(Channel channel); + void channelClosed(Channel channel); + void channelShunned(); // deprecated in 2.8 + void channelReconnected(Address addr); // deprecated in 2.8 + } + + A class implementing ChannelListener can use the Channel.setChannelListener() method to @@ -235,40 +201,51 @@
Receiver - + - public interface Receiver extends MessageListener, MembershipListener { - } - + public interface Receiver extends MessageListener, MembershipListener { + } + + - A Receiver can be used to receive all relevant messages and view + A Receiver can be used to receive messages and view changes in push-style; rather than having to pull these events from a channel, they will be dispatched to the receiver as soon as they have been received. This saves one thread (application thread, pulling messages from a channel, or the PullPushAdapter thread + + Note that JChannel.receive() has been deprecated and will be removed in 3.0. The + preferred way of receiving messages is now via a Receiver callback (push style). +
ExtendedReceiver - + - public interface ExtendedReceiver extends ExtendedMessageListener, MembershipListener { - } - + public interface ExtendedReceiver extends ExtendedMessageListener, MembershipListener { + } + + - This is a receiver who will be able to handle partial state - transfer + This is a receiver who will be able to handle partial state transfer
+
+ ReceiverAdapter and ExtendedReceiverAdapter + + These classes implement Receiver and ExtendedReceiver. When implementing a callback, one can simply + extend ReceiverAdapter and overwrite receive() in order to not having to implement all callbacks of + the interface. + +
+ - Merging of Extended interfaces with their super - interfaces + Merging of Extended interfaces with their super interfaces - The Extended- interfaces (ExtendedMessageListener, - ExtendedReceiver) will be merged with their parents in the 3.0 release - of JGroups. The reason is that this will create an API backwards - incompatibility, which we didn't want to introduce in the 2.x - series. + The Extended- interfaces (ExtendedMessageListener, ExtendedReceiver) will be merged with their parents in + the 3.0 release of JGroups. The reason is that this will create an API backwards + incompatibility, which we didn't want to introduce in the 2.x series.
@@ -279,18 +256,20 @@ member. The interface for such an address is Address, which requires concrete implementations to provide methods for comparison and sorting of addresses, and for determination whether the address is a multicast - address. JGroups addresses have to implement the following - interface: + address. JGroups addresses have to implement the following interface: - public interface Address extends Externalizable, Comparable, Cloneable { - boolean isMulticastAddress(); - int compareTo(Object o) throws ClassCastException; - boolean equals(Object obj); - int hashCode(); - String toString(); - } - + public interface Address extends Externalizable, Comparable, Cloneable { + boolean isMulticastAddress(); + int size(); + } + + + + + Please never use implementations of Address directly; Address should always be used + as an opaque identifier of a cluster node ! + Actual implementations of addresses are often generated by the bottommost protocol layer (e.g. UDP or TCP). This allows for all possible @@ -299,19 +278,23 @@ In JChannel, it is the IP address of the host on which the stack is running and the port on which the stack is receiving incoming messages; it is represented by the concrete class - org.jgroups.stack.IpAddress . Instances of this + org.jgroups.stack.IpAddress. Instances of this class are only used within the JChannel protocol stack; users of a channel see addresses (of any kind) only as - Addresses . Since an address uniquely identifies a channel, and + Addresses. Since an address uniquely identifies a channel, and therefore a group member, it can be used to send messages to that group member, e.g. in Messages (see next section). + + In 2.8, the default implementation of Address was changed from IpAddress to + org.jgroups.util.UUID. +
Message Data is sent between members in the form of messages ( - Message ). A message can be sent by a member to a + org.jgroups.Message ). A message can be sent by a member to a single member , or to all members of the group of which the channel is an endpoint. The structure of a message is shown in . @@ -396,14 +379,16 @@ or it can be null , which means that the message will be sent to all members of the group. A typical multicast message, sending string "Hello" to all members would look like - this: + this: - - Message msg=new Message(null, null, "Hello".getBytes()); - channel.send(msg); - + + Message msg=new Message(null, null, "Hello"); + channel.send(msg); + +
+
View @@ -421,19 +406,19 @@ coordinator easily and without having to contact other members. The code below shows how to send a (unicast) message to the first - member of a view (error checking code omitted): + member of a view (error checking code omitted): - View myview=channel.getView(); - Address first=myview.getMembers().first(); - Message msg=new Message(first, null, "Hello world"); - channel.send(msg); - + View view=channel.getView(); + Address first=view.getMembers().first(); + Message msg=new Message(first, null, "Hello world"); + channel.send(msg); + + Whenever an application is notified that a new view has been installed (e.g. by - MembershipListener.viewAccepted() or - Channel.receive() ), the view is already set in + Receiver.viewAccepted(), the view is already set in the channel. For example, calling Channel.getView() in a viewAccepted() callback would return the same @@ -446,7 +431,7 @@ The ViewId is used to uniquely number views. It consists of the address of the view creator and a sequence number. ViewIds can be compared for equality and put in a hashtable as they implement equals() - and hashCode() methods. + and hashCode() methods.Note that the latter 2 methods only take the ID into account.
@@ -465,16 +450,9 @@
-
- Membership - - This class can be used for keeping rack of members instead of a - Vector class. It adds several functions, such as duplicate elimination, - merging with other Membership instances and sorting. -
- Channel + JChannel In order to join a group and send messages, a process has to create a channel. A channel is like a socket. When a client connects to a @@ -515,82 +493,46 @@
Creating a channel - A channel can be created in two ways: an instance of a subclass of - Channel is created directly using its public - constructor (e.g. new JChannel() ), or a - channel factory is created, which -- upon request -- creates instances - of channels. We will only look at the first method of creating channel: - by direct instantiation. Note that instantiation may differ between the - various channel implementations. As example we will look at - JChannel . + + A channel can be created in two ways: an instance of a subclass of + Channel is created directly using its public + constructor (e.g. new JChannel() ), or a + channel factory is created, which -- upon request -- creates instances + of channels. We will only look at the first method of creating channel: + by direct instantiation. + - The public constructor of JChannel looks as - follows: + + The public constructor of JChannel looks as follows: + - public JChannel(Object properties) throws ChannelException {} - - - It creates an instance of JChannel . The - properties argument defines the composition of - the protocol stack (number and type of layers, parameters for each - layer, and their order). For JChannel, this has to be a String. - An example of a channel creation is: - - - String props="UDP(mcast_addr=228.1.2.3;mcast_port=45566;ip_ttl=32):" + - "PING(timeout=3000;num_initial_members=6):" + - "FD(timeout=5000):" + - "VERIFY_SUSPECT(timeout=1500):" + - "pbcast.STABLE(desired_avg_gossip=10000):" + - "pbcast.NAKACK(gc_lag=10;retransmit_timeout=3000):" + - "UNICAST(timeout=5000;min_wait_time=2000):" + - "FRAG:" + - "pbcast.GMS(initial_mbrs_timeout=4000;join_timeout=5000;" + - "shun=false;print_local_addr=false)"; - - JChannel channel; - try { - channel=new JChannel(props); - } - catch(Exception ex) { - // channel creation failed - } - + public JChannel(String props) throws ChannelException {} + - The argument is a colon-delimited string of protocols, specified - from bottom to top (left to right). The example properties argument will - be used to create a protocol stack that uses IP Multicast (UDP) as - bottom protocol, the PING protocol to locate the initial members, FD for - failure detection, VERIFY_SUSPECT for double-checking of suspected - members, STABLE for garbage collection of messages received by all - members, NAKACK for lossless delivery of multicast messages, UNICAST for - lossless delivery of unicast messages and GMS for group membership - (handling of join or leave requests). + It creates an instance of JChannel . The + props argument points to an XML file containing the configuration + of the protocol stack to be used. This can be a String, but there are also other constructors which + take for example a DOM element or a URL (more on this later). + - If the properties argument is null, the default properties will be + If the props argument is null, the default properties will be used. An exception will be thrown if the channel cannot be created. Possible causes include protocols that were specified in the property argument, but were not found, or wrong parameters to protocols. -
- Using XML to define a protocol stack - In version 2.0 of JGroups an XML-based scheme to define protocol - stacks was introduced. Instead of specifying a string containing the - protocol spec, an URL pointing to a valid protocol stack definition - can be given. For example, the Draw demo can be launched as - follows: + For example, the Draw demo can be launched as follows: - java org.javagroups.demos.Draw -props file:/home/bela/vsync.xml - + java org.javagroups.demos.Draw -props file:/home/bela/udp.xml + or - java org.javagroups.demos.Draw -props http://www.jgroups.org/udp.xml - + java org.javagroups.demos.Draw -props http://www.jgroups.org/udp.xml + In the latter case, an application downloads its protocol stack @@ -629,7 +571,7 @@ <MERGE2 max_interval="30000" min_interval="10000"/> <FD_SOCK/> - <FD timeout="10000" max_tries="5" shun="true"/> + <FD timeout="10000" max_tries="5" /> <VERIFY_SUSPECT timeout="1500" /> <BARRIER /> <pbcast.NAKACK @@ -641,7 +583,6 @@ max_bytes="400000"/> <VIEW_SYNC avg_send_interval="60000" /> <pbcast.GMS print_local_addr="true" join_timeout="3000" - shun="false" view_bundling="true"/> <FC max_credits="20000000" min_threshold="0.10"/> @@ -665,8 +606,8 @@ Each element has to be the name of a Java class that resides in the org.jgroups.stack.protocols package. Note that only the base name has to be given, not the fully - specified class name ( UDP instead of - org.jgroups.stack.protocols.UDP ). If the + specified class name (UDP instead of + org.jgroups.stack.protocols.UDP). If the protocol class is not found, JGroups assumes that the name given is a fully qualified classname and will therefore try to instantiate that class. If this does not work an exception is thrown. This allows for @@ -685,7 +626,76 @@ Note that all members in a group have to have the same protocol stack. -
+ +
+ Programmatic creation + + Usually, channels are created by passing the name of an XML configuration file to the JChannel() constructor. + On top of this declarative configuration, JGroups provides an API to create a channel programmatically. + The way to do this is to first create a JChannel, then an instance of ProtocolStack, then add all desired + protocols to the stack and finally calling init() on the stack to set it up. The rest, e.g. calling + JChannel.connect() is the same as with the declarative creation. + + + An example of how to programmatically create a channel is shown below (copied from ProgrammaticChat): + + JChannel ch=new JChannel(false); // 1 + ProtocolStack stack=new ProtocolStack(); // 2 + ch.setProtocolStack(stack); // 3 + stack.addProtocol(new UDP().setValue("bind_addr", InetAddress.getByName("192.168.1.5"))) + .addProtocol(new PING()) + .addProtocol(new MERGE2()) + .addProtocol(new FD_SOCK()) + .addProtocol(new FD_ALL().setValue("timeout", 12000).setValue("interval", 3000)) + .addProtocol(new VERIFY_SUSPECT()) + .addProtocol(new BARRIER()) + .addProtocol(new NAKACK()) + .addProtocol(new UNICAST2()) + .addProtocol(new STABLE()) + .addProtocol(new GMS()) + .addProtocol(new UFC()) + .addProtocol(new MFC()) + .addProtocol(new FRAG2()); // 4 + stack.init(); // 5 + + ch.setReceiver(new ReceiverAdapter() { + public void viewAccepted(View new_view) { + System.out.println("view: " + new_view); + } + + public void receive(Message msg) { + System.out.println(msg.getObject() + " [" + msg.getSrc() + "]"); + } + }); + + ch.connect("ChatCluster"); + + for(;;) { + String line=Util.readStringFromStdin(": "); + ch.send(null, null, line); + } + + + + First a JChannel is created. The 'false' argument tells the channel not to create a ProtocolStack. This + is needed because we will create one ourselves later (2) and set it in the channel (3). + + + Next, all protocols are added to the stack. Note that the order is from bottom (transport protocol) to + top. So UDP as transport is added first, then PING and so on, until FRAG2, which is the top protocol. + Every protocol can be configured via setters, but there is also a generic setValue(String attr_name, + Object value), which can be used to configure protocols as well, as shown in the example. + + + Once the stack is configured, we call ProtocolStack.init() to link all protocols correctly and to call + init() in every protocol instance. After this, the channel is ready to be used and all subsequent + actions (e.g. connect()) can be executed. When the init() method returns, we have essentially the + equivalent of new JChannel(config_file). + +
+ + +
@@ -695,12 +705,12 @@
Setting options - A number of options can be set in a channel. To do so, the - following method is used: - - + + A number of options can be set in a channel. To do so, the following method is used: + public void setOpt(int option, Object value); - + + Arguments are the options number and a value. The following options are currently recognized: @@ -710,9 +720,7 @@ Channel.BLOCK - The argument is a boolean object. If true, block messages - will be received. If this option is set to true, views will also - be set to true. Default is false. + The argument is a boolean object. If true, block messages will be received. @@ -734,7 +742,8 @@ When set to true, a shunned channel will leave the group and - then try to automatically re-join. Default is false + then try to automatically re-join. Default is false. Note that in 2.8, shunning has been removed, therefore + this option has been deprecated. @@ -744,20 +753,107 @@ When set to true a shunned channel, after reconnection, will attempt to fetch the state from the coordinator. This requires - AUTO_RECONNECT to be true as well. Default is false. + AUTO_RECONNECT to be true as well. Default is false. Note that in 2.8, shunning has been removed, therefore + this option has been deprecated. The equivalent method to get options is - getOpt() : + getOpt(): - public Object getOpt(int option); - + public Object getOpt(int option); + + Given an option, the current value of the option is returned. + + + Deprecating options in 3.0 + + + Most of the options (except LOCAL) have been deprecated in 2.6.x and will be removed in 3.0. + + + +
+ +
+ Giving the channel a logical name + + A channel can be given a logical name which is then used instead of the channel's address. A logical name + might show the function of a channel, e.g. "HostA-HTTP-Cluster", which is more legible than a UUID + 3c7e52ea-4087-1859-e0a9-77a0d2f69f29. + + + For example, when we have 3 channels, using logical names we might see a view "{A,B,C}", which is nicer than + "{56f3f99e-2fc0-8282-9eb0-866f542ae437, ee0be4af-0b45-8ed6-3f6e-92548bfa5cde, 9241a071-10ce-a931-f675-ff2e3240e1ad} !" + + + If no logical name is set, JGroups generates one, using the hostname and a random number, e.g. linux-3442. + If this is not desired and the UUIDs should be shown, use system property -Djgroups.print_uuids=true. + + + The logical name can be set using: + + public void setName(String logical_name); + + + + This should be done before connecting a channel. Note that the logical name stays with a channel until + the channel is destroyed, whereas a UUID is created on each connection. + + + + When JGroups starts, it prints the logical name and the associated physical address(es): + + ------------------------------------------------------------------- + GMS: address=mac-53465, cluster=DrawGroupDemo, physical address=192.168.1.3:49932 + ------------------------------------------------------------------- + ** View=[mac-53465|0] [mac-53465] + + + The logical name is mac-53465 and the physical address is 192.168.1.3:49932. The UUID is not shown here. + +
+ +
+ Generating custom addresses + + Since 2.12 address generation is pluggable. This means that an application can determine what kind of + addresses it uses. The default address type is UUID, and since some protocols use UUID, it is recommended + to provide custom classes as subclasses of UUID. + + + This can be used to for example pass additional data around with an address, for example information about + the location of the node to which the address is assigned. Note that methods equals(), hashCode() and + compare() of the UUID super class should not be changed. + + + To use custom addresses, the following things have to be done: + + Write an implementation of org.jgroups.stack.AddressGenerator + For any class CustomAddress, it will need to get registered with the ClassConfigurator + in order to marshal it correctly: + + class CustomAddress extends UUID { + static { + ClassConfigurator.add((short)8900, CustomAddress.class); + } + } + + Note that the ID should be chosen such that it doesn't collide with any IDs defined in + jg-magic-map.xml. + + Set the address generator in JChannel: setAddressGenerator(AddressGenerator). This + has to be done before the channel is connected + + + + An example of a subclass is org.jgroups.util.PayloadUUID. +
@@ -768,8 +864,8 @@ to be joined: - public void connect(String clustername) throws ChannelClosed; - + public void connect(String clustername) throws ChannelClosed; + The cluster name is a string, naming the cluster to be joined. All channels that are connected to the same name form a cluster. @@ -782,7 +878,7 @@ The method returns as soon as the group has been joined successfully. If the channel is in the closed state (see ), an exception will be thrown. If there - are no other members, i.e. no other client has connected to a group with + are no other members, i.e. no other member has connected to a group with this name, then a new group is created and the member joined. The first member of a group becomes its coordinator . A coordinator is in charge of multicasting new views whenever the @@ -807,7 +903,9 @@ - public void connect(string cluster_name, address target, string state_id, long timeout) throws channelexception; + public void connect(string cluster_name, address target, + string state_id, long timeout) + throws ChannelException; @@ -823,7 +921,9 @@ Getting the local address and the group name Method getLocalAddress() returns the - local address of the channel. In the case of + local address of the channel + Since 2.8 the method is getAddress() + . In the case of JChannel , the local address is generated by the bottom-most layer of the protocol stack when the stack is connected to. That means that -- depending on the channel implementation -- the local @@ -831,15 +931,15 @@ state. - public Address getLocalAddress(); - + public Address getLocalAddress(); // use getAddress() with 2.8.0+ + - Method getClusterlName() returns the name + Method getClusterName() returns the name of the cluster in which the channel is a member: - public String getClusterName(); - + public String getClusterName(); + Again, the result is undefined if the channel is in the unconnected or closed state. @@ -852,8 +952,8 @@ channel: - public View getView(); - + public View getView(); + This method does not retrieve a new view (message) from the channel, but only returns the current view of the @@ -874,9 +974,10 @@ send() methods: - public void send(Message msg) throws ChannelNotConnected, ChannelClosed; - public void send(Address dst, Address src, Object obj) throws ChannelNotConnected, ChannelClosed; - + public void send(Message msg) throws ChannelNotConnected, ChannelClosed; + public void send(Address dst, Address src, Object obj) + throws ChannelNotConnected, ChannelClosed; + The first send() method has only one argument, which is the message to be sent. The message's destination @@ -895,41 +996,41 @@ be thrown upon attempting to send a message. Here's an example of sending a (multicast) message to all members - of a group: + of a group: - Hashtable data; // any serializable data - try { - channel.send(null, null, data); - } - catch(Exception ex) { - // handle errors - } - + Map data; // any serializable data + try { + channel.send(null, null, data); + } + catch(Exception ex) { + // handle errors + } + + The null value as destination address means that the message will be sent to all members in the group. The sender's address will be filled - in by the bottom-most protocol. The payload is a hashtable, which will + in by the bottom-most protocol. The payload is a hashmap, which will be serialized into the message's buffer and unserialized at the receiver's end. Alternatively, any other means of generating a byte buffer and setting the message's buffer to it (e.g. using Message.setBuffer()) would also work. Here's an example of sending a (unicast) message to the first - member (coordinator) of a group: + member (coordinator) of a group: - Address receiver; - Message msg; - Hashtable data; - try { - receiver=channel.getView().getMembers().first(); - channel.send(receiver, null, data); - } - catch(Exception ex) { - // handle errors - } - + Map data; + try { + Address receiver=channel.getView().getMembers().first(); + channel.send(receiver, null, data); + } + catch(Exception ex) { + // handle errors + } + + It creates a Message with a specific address for the receiver (the first member of the group). Again, the sender's address can be left null @@ -943,8 +1044,9 @@ messages, views, suspicions and blocks: - public Object receive(long timeout) throws ChannelNotConnected, ChannelClosed, Timeout; - + public Object receive(long timeout) throws ChannelNotConnected, + ChannelClosed, Timeout; + A channel receives messages asynchronously from the network and stores them in a queue. When receive() is called, the next available @@ -1083,20 +1185,17 @@ The caller has to check the type of the object returned. This can be done using the instanceof operator, as - follows: - + follows: - Object obj; - Message msg; - View v; - obj=channel.receive(0); // wait forever - if(obj instanceof Message) - msg=(Message)obj; - else if(obj instanceof View) - v=(View)obj; - else - ; // don't handle suspicions or blocks - + Object obj=channel.receive(0); // wait forever + if(obj instanceof Message) + Message msg=(Message)obj; + else if(obj instanceof View) + View v=(View)obj; + else + ; // don't handle suspicions or blocks + + If for example views, suspicions and blocks are disabled, then the caller is guaranteed to only receive return values of type @@ -1108,19 +1207,18 @@ exception will be thrown. The example below shows how to retrieve the "Hello world" string - from a message: - + from a message: - Message msg; // received above - String s; - try { - s=(String)msg.getObject(); // error if object not Serializable - // alternative: s=new String(msg.getBuffer()); - } - catch(Exception ex) { - // handle errors, e.g. casting error above) - } - + Message msg; // received above + try { + String s=(String)msg.getObject(); // error if obj not Serializable + // alternative: s=new String(msg.getBuffer()); + } + catch(Exception ex) { + // handle errors, e.g. casting error above) + } + + The Message.getObject() method retrieves the message's byte buffer, converts it into a (serializable) object and returns the @@ -1131,22 +1229,23 @@ Using a Receiver to receive messages Instead of pulling messages from a channel in an application - thread, a Receiver can be registered with a channel; all received - messages, view changes and state transfer requests will invoke callbacks - on the registered Receiver: - - - JChannel ch=new JChannel(); - ch.setReceiver(new ExtendedReceiverAdapter() { - public void receive(Message msg) { - System.out.println("received message " + msg); - } - public void viewAccepted(View new_view) { - System.out.println("received view " + new_view); - } - }); - ch.connect("bla"); - + thread, a Receiver can be registered with a channel. This is the preferred and recommended way of receiving + messages. In 3.0, the receive() method will be removed from JChannel. + All received messages, view changes and state transfer requests will invoke callbacks + on the registered Receiver: + + JChannel ch=new JChannel(); + ch.setReceiver(new ExtendedReceiverAdapter() { + public void receive(Message msg) { + System.out.println("received message " + msg); + } + public void viewAccepted(View new_view) { + System.out.println("received view " + new_view); + } + }); + ch.connect("bla"); + + The ExtendedReceiverAdapter class implements all callbacks of ExtendedReceiver with no-ops, in the example @@ -1211,28 +1310,29 @@ transfer would not be correct. The following code fragment shows how a group member participates - in state transfers: - + in state transfers: - channel=new JChannel(); - channel.connect("TestChannel"); - boolean rc=channel.getState(null, 5000); - - ... - - Object state, copy; - Object ret=channel.receive(0); - if(ret instanceof Message) - ; - else if(ret instanceof GetStateEvent) { - copy=copyState(state); // make a copy so that other msgs don't change the state - channel.returnState(Util.objectToByteBuffer(copy)); - } - else if(ret instanceof SetStateEvent) { - SetStateEvent e=(SetStateEvent)ret; - state=e.getArg(); - } - + channel=new JChannel(); + channel.connect("TestChannel"); + boolean rc=channel.getState(null, 5000); + + ... + + Object state, copy; + Object ret=channel.receive(0); + if(ret instanceof Message) + ; + else if(ret instanceof GetStateEvent) { + // make a copy so that other msgs don't change the state + copy=copyState(state); + channel.returnState(Util.objectToByteBuffer(copy)); + } + else if(ret instanceof SetStateEvent) { + SetStateEvent e=(SetStateEvent)ret; + state=e.getArg(); + } + + A JChannel has to be created whose stack includes the @@ -1265,32 +1365,33 @@ As an alternative to handling the GetStateEvent and SetStateEvent events, and calling Channel.returnState(), a Receiver could be used. The - example above would look like this: - + example above would look like this: - class MyReceiver extends ReceiverAdapter { - final Map m=new HashMap(); - public byte[] getState() { - synchronized(m) { // so nobody else can modify the map while we serialize it - byte[] state=Util.objectToByteBuffer(m); - return state; - } - } - - public void setState(byte[] state) { - synchronized(m) { - Map new_m=(Map)Util.objectFromByteBuffer(state); - m.clear(); - m.addAll(new_m); - } - } - } - - channel=new JChannel(); // use default properties (has to include pbcast.STATE_TRANSFER protocol) - channel.setReceiver(new MyReceiver()); - channel.connect("TestChannel"); - boolean rc=channel.getState(null, 5000); - + class MyReceiver extends ReceiverAdapter { + final Map m=new HashMap(); + public byte[] getState() { + // so nobody else can modify the map while we serialize it + synchronized(m) { + byte[] state=Util.objectToByteBuffer(m); + return state; + } + } + + public void setState(byte[] state) { + synchronized(m) { + Map new_m=(Map)Util.objectFromByteBuffer(state); + m.clear(); + m.addAll(new_m); + } + } + } + // use default props (has to include STATE_TRANSFER) + channel=new JChannel(); + channel.setReceiver(new MyReceiver()); + channel.connect("TestChannel"); + boolean rc=channel.getState(null, 5000); + + In a group consisting of A,B and C, with D joining the group and calling Channel.getState(), the following sequence of callbacks happens: @@ -1326,56 +1427,58 @@ the pull model, GetStateEvent and SetStateEvent have an additional member, state_id, and in the push model, there are 2 additional getState() and setState() callbacks. The example below shows partial - state transfer for the push model: - + state transfer for the push model: - class MyReceiver extends ExtendedReceiverAdapter { - final Map m=new HashMap(); + class MyReceiver extends ExtendedReceiverAdapter { + final Map m=new HashMap(); - public byte[] getState() { - return getState(null); - } - - public byte[] getState(String substate_id) { - synchronized(m) { // so nobody else can modify the map while we serialize it - byte[] state=null; - if(substate_id == null) { - state=Util.objectToByteBuffer(m); - } - else { - Object value=m.get(substate_id); - if(value != null) { - return Util.objectToByteBuffer(value); - } - } - return state; - } - } - - public void setState(byte[] state) { - setState(null, state); - } - - public void setState(String substate_id, byte[] state) { - synchronized(m) { - if(substate_id != null) { - Object value=Util.objectFromByteBuffer(state); - m.put(substate_id, value); - } - else { - Map new_m=(Map)Util.objectFromByteBuffer(state); - m.clear(); - m.addAll(new_m); - } - } - } - } - - channel=new JChannel(); // use default properties (has to include pbcast.STATE_TRANSFER protocol) - channel.setReceiver(new MyReceiver()); - channel.connect("TestChannel"); - boolean rc=channel.getState(null, "MyID", 5000); - + public byte[] getState() { + return getState(null); + } + + public byte[] getState(String substate_id) { + // so nobody can modify the map while we serialize it + synchronized(m) { + byte[] state=null; + if(substate_id == null) { + state=Util.objectToByteBuffer(m); + } + else { + Object value=m.get(substate_id); + if(value != null) { + return Util.objectToByteBuffer(value); + } + } + return state; + } + } + + public void setState(byte[] state) { + setState(null, state); + } + + public void setState(String substate_id, byte[] state) { + synchronized(m) { + if(substate_id != null) { + Object value=Util.objectFromByteBuffer(state); + m.put(substate_id, value); + } + else { + Map new_m=(Map)Util.objectFromByteBuffer(state); + m.clear(); + m.addAll(new_m); + } + } + } + } + + // use default props (has to include pbcast.STATE_TRANSFER) + channel=new JChannel(); + channel.setReceiver(new MyReceiver()); + channel.connect("TestChannel"); + boolean rc=channel.getState(null, "MyID", 5000); + + The example shows that the Channel.getState() method specifies the ID of the substate, in this case "MyID". The @@ -1410,19 +1513,19 @@ receiving/reading state through a provided InputStream reference. In order to use streaming state transfer in a push mode, existing ExtendedMessageListener has been expanded to include additional four - methods: - + methods: -public interface ExtendedMessageListener { + public interface ExtendedMessageListener { - /*non-streaming callback methods ommitted for clarity*/ + /*non-streaming callback methods ommitted for clarity*/ - void getState(OutputStream ostream); - void getState(String state_id, OutputStream ostream); - void setState(InputStream istream); - void setState(String state_id, InputStream istream); - -} + void getState(OutputStream ostream); + void getState(String state_id, OutputStream ostream); + void setState(InputStream istream); + void setState(String state_id, InputStream istream); + } + + For a pull mode (when application uses channel.receive() to fetch events) two new event classes will be introduced: @@ -1443,29 +1546,30 @@ The following code snippet demonstrates how to pull events from a channel, processing StreamingGetStateEvent and sending hypothetical state through a provided OutputStream reference. Handling of - StreamingSetStateEvent is analogous to this example: - - ... - Object obj=channel.receive(0); - if(obj instanceof StreamingGetStateEvent) { - StreamingGetStateEvent evt=(StreamingGetStateEvent)obj; - OutputStream oos = null; - try { - oos = new ObjectOutputStream(evt.getArg()); - oos.writeObject(state); - oos.flush(); - } - catch (Exception e) {} - finally { - try { - oos.close(); - } - catch (IOException e) { - System.err.println(e); - } - } - } - ... + StreamingSetStateEvent is analogous to this example: + + ... + Object obj=channel.receive(0); + if(obj instanceof StreamingGetStateEvent) { + StreamingGetStateEvent evt=(StreamingGetStateEvent)obj; + OutputStream oos = null; + try { + oos=new ObjectOutputStream(evt.getArg()); + oos.writeObject(state); + oos.flush(); + } + catch (Exception e) {} + finally { + try { + oos.close(); + } + catch (IOException e) { + System.err.println(e); + } + } + } + + JGroups has a great flexibility with state transfer methodology by allowing application developers to implement both byte based and @@ -1481,11 +1585,11 @@ Disconnecting from a channel Disconnecting from a channel is done using the following - method: - - - public void disconnect(); - + method: + + public void disconnect(); + + It will have no effect if the channel is already in the disconnected or closed state. If connected, it will remove itself from @@ -1503,11 +1607,11 @@ To destroy a channel instance (destroy the associated protocol stack, and release all resources), method - close() is used: - - - public void close(); - + close() is used: + + public void close(); + + It moves the channel to the closed state, in which no further operations are allowed (most throw an exception when invoked on a closed diff -Nru libjgroups-java-2.7.0.GA/doc/manual/en/modules/blocks.xml libjgroups-java-2.12.2.Final/doc/manual/en/modules/blocks.xml --- libjgroups-java-2.7.0.GA/doc/manual/en/modules/blocks.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/manual/en/modules/blocks.xml 2011-10-18 11:22:35.000000000 +0000 @@ -3,9 +3,11 @@ Building blocks are layered on top of channels. Most of them do not even need a channel, all they need is a class that implements interface Transport (channels do). This - enables them to work on any type of group transport that obeys this + enables them to work on any type of group transport that implements this interface. Building blocks can be used instead of channels whenever - a higher-level interface is required. Whereas channels are simple + a higher-level interface is required. + + Whereas channels are simple socket-like constructs, building blocks may offer a far more sophisticated interface. In some cases, building blocks offer access to the underlying channel, so that -- if the building block at hand @@ -19,7 +21,9 @@
PullPushAdapter - Note that this building block has been deprecated and should not be used anymore ! + Note that this building block has been deprecated and should not be used anymore ! + Use a Receiver instead. + This class is a converter (or adapter, as used in - public class RspList { + public class RspList implements Map<Address,Rsp> { public boolean isReceived(Address sender); public int numSuspectedMembers(); public Vector getResults(); @@ -280,7 +284,6 @@ public boolean isSuspected(Address sender); public Object get(Address sender); public int size(); - public Object elementAt(int i) throws ArrayIndexOutOfBoundsException; } @@ -382,14 +385,12 @@ shown): - public RspList callRemoteMethods(Vector dests, String method_name, - int mode, long timeout); - public RspList callRemoteMethods(Vector dests, String method_name, - Object arg1, int mode, long timeout); - public Object callRemoteMethod(Address dest, String method_name, - int mode, long timeout); - public Object callRemoteMethod(Address dest, String method_name, - Object arg1, int mode, long timeout); + public RspList callRemoteMethods(Vector dests, String method_name, int mode, long timeout); + public RspList callRemoteMethods(Vector dests, String method_name, Object arg1, int mode, + long timeout); + public Object callRemoteMethod(Address dest, String method_name, int mode, long timeout); + public Object callRemoteMethod(Address dest, String method_name, Object arg1, int mode, + long timeout); The family of callRemoteMethods() @@ -485,6 +486,73 @@ by providing a higher abstraction level between the application and the primitive channels. +
RequestOptions + + RequestOptions is a collection of options that can be passed into a call, e.g. the mode (GET_ALL, GET_NONE), + timeout, flags etc. It is an alternative to passing multiple arguments to a method. + + + + All calls with individual parameters have been deprecated in 2.9 and the new calls with RequestOptions + are: + + public RspList callRemoteMethods(Collection<Address> dests, String method_name, + Object[] args,Class[] types, RequestOptions options); + public RspList callRemoteMethods(Collection<Address> dests, MethodCall method_call, + RequestOptions options); + public Object callRemoteMethod(Address dest, String method_name, Object[] args, + Class[] types, RequestOptions options); + public Object callRemoteMethod(Address dest, MethodCall call, RequestOptions options); + + + + + An example of how to use RequestOptions is: + + RpcDispatcher disp; + RequestOptions opts=new RequestOptions(Request.GET_ALL) + .setFlags(Message.NO_FC | Message.DONT_BUNDLE); + Object val=disp.callRemoteMethod(target, method_call, opts); + + +
+ +
Asynchronous calls with futures + + + When invoking a synchronous call, the calling thread is blocked until the response (or responses) has + been received. + + + + A Future allows a caller to return immediately and grab the result(s) later. In + 2.9, two new methods, which return futures, have been added to RpcDispatcher: + + public NotifyingFuture<RspList> callRemoteMethodsWithFuture(Collection<Address> dests, + MethodCall method_call, RequestOptions options); + public <T> NotifyingFuture<T> callRemoteMethodWithFuture(Address dest, MethodCall call, + RequestOptions options); + + + + + A NotifyingFuture extends java.util.concurrent.Future, with its regular methods such as isDone(), + get() and cancel(). NotifyingFuture adds setListener<FutureListener> to get notified when + the result is available. This is shown in the following code: + + NotifyingFuture<RspList> future=dispatcher.callRemoteMethodsWithFuture(...); + future.setListener(new FutureListener() { + void futureDone(Future<T> future) { + System.out.println("result is " + future.get()); + } + } + ); + + + +
+ +
@@ -616,7 +684,184 @@
- + +
+ Distributed locking + + In 2.12, a new distributed locking service was added, replacing DistributedLockManager. The new service is + implemented as a protocol and is used via org.jgroups.blocks.locking.LockService. + + + LockService talks to the locking protocol via events. The main abstraction of a distributed lock is an + implementation of java.util.concurrent.locks.Lock. All lock methods are supported, however, conditions + are not yet supported. (Based on feedback, they might be added later). + + + Below is an example of how LockService is typically used: + + // locking.xml needs to have a locking protocol + JChannel ch=new JChannel("/home/bela/locking.xml"); + LockService lock_service=new LockService(ch); + ch.connect("lock-cluster"); + Lock lock=lock_service.getLock("mylock"); + lock.lock(); + try { + // do something with the locked resource + } + finally { + lock.unlock(); + } + + + + In the example, we create a channel, then a LockService, then connect the channel. Then we grab a lock + named "mylock", which we lock and subsequently unlock. + + + Note that the owner of a lock is always a given thread in a cluster, so the owner is the JGroups address and + the thread ID. This means that different threads inside the same JVM trying to access the same named lock + will compete for it. If thread-22 grabs the lock first, then thread-5 will block until thread-23 + releases the lock. + + + JGroups includes a demo (org.jgroups.demos.LockServiceDemo), which can be used to interactively experiment + with distributed locks. LockServiceDemo -h dumps all command line options. + + + Currently (Jan 2011), there are 2 protocols which provide locking: + PEER_LOCK and CENTRAL_LOCK. The locking + protocol has to be placed at or towards the top of the stack (close to the channel). + +
+ Locking and merges + + The following scenario is susceptible to merging: we have a cluster view of {A,B,C,D} and then the cluster + splits into {A,B} and {C,D}. Assume that B and D now acquire a lock "mylock". + This is what happens (with the locking protocol being CENTRAL_LOCK): + + There are 2 coordinators: A for {A,B} and C for {C,D} + B successfully acquires "mylock" from A + D successfully acquires "mylock" from C + The partitions merge back into {A,B,C,D}. Now, only A is the coordinator, but C ceases + to be a coordinator + Problem: D still holds a lock which should actually be invalid ! + + There is no easy way (via the Lock API) to 'remove' the lock from D. We could for example simply release + D's lock on "mylock", but then there's no way telling D that the lock it holds is actually stale ! + + + Therefore the recommended solution here is for nodes to listen to MergeView changes if they expect + merging to occur, and re-acquire all of their locks after a merge, e.g.: + + Lock l1, l2, l3; + LockService lock_service; + ... + public void viewAccepted(View view) { + if(view instanceof MergeView) { + new Thread() { + public void run() { + lock_service.unlockAll(); + // stop all access to resources protected by l1, l2 or l3 + // every thread needs to re-acquire the locks it holds + } + }.start + } + } + + +
+
+
+ Distributed ExecutionService + + In 2.12, a distributed execution service was added. The new service is implemented as a protocol and is used + via org.jgroups.blocks.executor.ExecutionService. + + + ExecutionService talks to the executing protocol via events. The main abstraction is an implementation of + java.util.concurrent.locks.ExecutorService. All methods are supported. The restrictions are however that + the Callable or Runnable must be Serializable, Externalizable or Streamable. Also the result produced + from the future needs to be Serializable, Externalizable or Streamable. If the Callable or Runnable are not + then an IllegalArgumentException is immediately thrown. If a result is not then a NotSerializableException + with the name of the class will be returned to the Future as an exception cause. + + + Below is an example of how ExecutionService is typically used: + + // locking.xml needs to have a locking protocol + JChannel ch=new JChannel("/home/bela/executing.xml"); + ExecutionService exec_service =new ExecutionService(ch); + ch.connect("exec-cluster"); + Future<Value> future = exec_service.submit(new MyCallable()); + try { + Value value = future.get(); + // Do something with value + } + catch (InterruptedException e) { + e.printStackTrace(); + } + catch (ExecutionException e) { + e.getCause().printStackTrace(); + } + + + + In the example, we create a channel, then an ExecutionService, then connect the channel. Then we submit + our callable giving us a Future. Then we wait for the future to finish returning our value and do something + with it. If any exception occurs we print the stack trace of that exception. + + + JGroups includes a demo (org.jgroups.demos.ExecutionServiceDemo), which can be used to interactively + experiment with a distributed sort algorithm and performance. This is for demonstration purposes and + performance should not be assumed to be better than local. + ExecutionServiceDemo -h dumps all command line options. + + + Currently (March 2011), there is 1 protocol which provide executions: + CENTRAL_EXECUTOR. The executing protocol has to be placed at or + towards the top of the stack (close to the channel). + + + +
+ diff -Nru libjgroups-java-2.7.0.GA/doc/manual/en/modules/installation.xml libjgroups-java-2.12.2.Final/doc/manual/en/modules/installation.xml --- libjgroups-java-2.7.0.GA/doc/manual/en/modules/installation.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/manual/en/modules/installation.xml 2011-10-18 11:22:35.000000000 +0000 @@ -1,7 +1,7 @@ Installation and Configuration - The installation refers to version 2.5 of JGroups. Refer to the installation + The installation refers to version 2.8 of JGroups. Refer to the installation instructions that are shipped with JGroups for details. Note that these instructions are also available in the @@ -22,10 +22,10 @@ - JGroups 2.5 requires JDK 5 or higher. + JGroups 2.5 requires JDK 1.5 or higher. Version 2.9 requires JDK 1.6 or higher. - There is no JNI code present so it should run on all platforms. + There is no JNI code present so JGroups should run on all platforms. If you want to generate HTML-based test reports from the @@ -53,16 +53,12 @@ INSTALL.html: this file - commons-logging.jar - - - log4j.jar. This JAR is optional, for example if JDK logging is used, we don't need it. + log4j.jar. This JAR is optional, for example if JDK logging is used, we don't need it. Note that + commons-logging is not a requirement any more since version 2.8. - Place the JAR files somewhere in your CLASSPATH, and you're ready to start using - JGroups. If you want to use the JGroups JMS protocol (org.jgroups.protocols.JMS), - then you will also need to place jms.jar somewhere in your CLASSPATH. + Place the JAR files somewhere in your CLASSPATH, and you're ready to start using JGroups. @@ -93,17 +89,7 @@ Ant - JARs: used to build JGroups. If you already have - Ant installed, you won't need these files - - - - jms.jar: JMS library. Needed only if you intend to - run the org.jgroups.protocols.JMS protocol - - - junit.jar: to run the - JUnit test cases + JARs: used to build JGroups. If you already have Ant installed, you won't need these files @@ -113,9 +99,6 @@ - commons-logging.jar - - log4j.jar @@ -157,8 +140,7 @@ To generate the JARs: - $> ./build.sh - jar + $> ./build.sh jar @@ -169,13 +151,13 @@ jgroups-core.jar - - the core JGroups libraries + - the core JGroups library without unit tests and demos jgroups-all.jar - - the complete JGroups libraries including demos and unit tests + - the complete JGroups library including demos and unit tests @@ -260,11 +242,10 @@ class is found: - bela@dell /cygdrive/c/JGroups/dist - $ java -jar jgroups-all.jar + [mac] /Users/bela/JGroups$ java org.jgroups.Version - Version: 2.6.0 pre-alpha - CVS: $Id: installation.xml,v 1.5 2007/07/24 16:30:46 belaban Exp $ + Version: 2.8.0.GA + CVS: $Id: installation.xml,v 1.10 2010/04/30 14:27:39 vlada Exp $ @@ -298,6 +279,10 @@ a different transport if multicast doesn't work (it should always work on the same machine). Please consult the documentation to see how to do this. + + State transfer (see the section in the API later) can also be tested by passing the -state flag to Draw. + +
@@ -385,7 +370,7 @@ the receiver as follows: - java org.jgroups.tests.McastReceiverTest1_4 -mcast_addr 228.8.8.8 -use_all_interfaces + java org.jgroups.tests.McastReceiverTest1_4 -mcast_addr 228.8.8.8 -use_all_interfaces The multicast receiver uses the 1.4 functionality to list @@ -397,7 +382,7 @@ - java org.jgroups.tests.McastSenderTest1_4 -mcast_addr 228.8.8.8 -use_all_interfaces + java org.jgroups.tests.McastSenderTest1_4 -mcast_addr 228.8.8.8 -use_all_interfaces The sender will also determine the available network @@ -407,13 +392,14 @@ bind to when previously no packets were received. E.g. when you see the following output in the receiver: - - bash-2.03$ java org.jgroups.tests.McastReceiverTest1_4 -mcast_addr 228.8.8.8 -bind_addr 192.168.168.4 - Socket=0.0.0.0/0.0.0.0:5555, bind interface=/192.168.168.4 - dd [sender=192.168.168.4:5555] - dd [sender=192.168.168.1:5555] - dd [sender=192.168.168.2:5555] - + + bash-2.03$ java org.jgroups.tests.McastReceiverTest1_4 -mcast_addr 228.8.8.8 + -bind_addr 192.168.1.4 + Socket=0.0.0.0/0.0.0.0:5555, bind interface=/192.168.168.4 + dd [sender=192.168.168.4:5555] + dd [sender=192.168.168.1:5555] + dd [sender=192.168.168.2:5555] + you know that you can bind to any of the 192.168.168.{1,2,4} interfaces to receive your multicast packets. In this case you @@ -439,7 +425,7 @@ - java -Djava.net.preferIPv4Stack=true org.jgroups.demos.Draw -props file:/home/bela/udp.xml + java -Djava.net.preferIPv4Stack=true org.jgroups.demos.Draw -props /home/bela/udp.xml JDK 1.4.1 uses IPv6 by default, although is has a dual stack, that is, it also supports IPv4. @@ -490,5 +476,23 @@
- +
Supported classes + JGroups project has been around since 2001. Over this time, some of the JGroups classes + have been used in experimental phases and have never been matured enough to be used in today's production + releases. However, they were not removed since some people used them in their products. + + The following tables list unsupported and experimental classes. These classes are not actively maintained, and + we will not work to resolve potential issues you might find. Their final faith is not yet determined; they + might even be removed altogether in the next major release. Weight your risks if you decide to use them anyway. +
+ Experimental + ${Experimental} + +
+
+ Unsupported + ${Unsupported} + +
+
diff -Nru libjgroups-java-2.7.0.GA/doc/manual/en/modules/overview.xml libjgroups-java-2.12.2.Final/doc/manual/en/modules/overview.xml --- libjgroups-java-2.7.0.GA/doc/manual/en/modules/overview.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/manual/en/modules/overview.xml 2011-10-18 11:22:35.000000000 +0000 @@ -2,8 +2,8 @@ Group communication uses the terms group and member. Members are part - of a group. In the more common terminology, a member is a node and a groups is a - cluster. We use these words interchangeably. + of a group. In the more common terminology, a member is a node and a group is a + cluster. We use these terms interchangeably. A node is a process, residing on some host. A cluster can have one or more nodes belonging to it. There @@ -25,51 +25,36 @@ The architecture of JGroups is shown in . - -
The architecture of JGroups
- - It consists of 3 parts: (1) the Channel API used by + It consists of 3 parts: (1) the Channel used by application programmers to build reliable group communication applications, (2) the building blocks, which are layered on top of the channel and provide a higher abstraction level and (3) the protocol stack, which implements the properties specified for a given channel. - This document describes how to install and - use JGroups, ie. the Channel API and the - building blocks. The targeted audience is application programmers - who want to use JGroups to build reliable distributed programs - that need group communication. Programmers who want to - implement their own protocols to be used with - JGroups should consult the Programmer's Guide for more details - about the architecture and implementation of JGroups. - - A channel is connected to a protocol stack. Whenever the - application sends a message, the channel passes it on to the - protocol stack, which passes it to the topmost protocol. The - protocol processes the message and the passes it on to the protocol - below it. Thus the message is handed from protocol to protocol until - the bottom protocol puts it on the network. The same happens in the - reverse direction: the bottom (transport) protocol listens for - messages on the network. When a message is received it will be - handed up the protocol stack until it reaches the channel. The - channel stores the message in a queue until the application consumes - it. + + This document describes how to install and use JGroups, ie. the Channel API and the + building blocks. The targeted audience is application programmers who want to use JGroups to + build reliable distributed programs that need group communication. + + + + A channel is connected to a protocol stack. Whenever the + application sends a message, the channel passes it on to the + protocol stack, which passes it to the topmost protocol. The + protocol processes the message and the passes it on to the protocol + below it. Thus the message is handed from protocol to protocol until + the bottom (transport) protocol puts it on the network. The same happens in the + reverse direction: the transport protocol listens for + messages on the network. When a message is received it will be + handed up the protocol stack until it reaches the channel. The + channel stores the message in a queue until the application consumes it. + When an application connects to the channel, the protocol stack will be started, and when it disconnects the stack will be @@ -101,7 +86,7 @@ view. A process can select an address from this list and send a unicast message to it (also to itself), or it may send a multicast message to all members of the current - view. Whenever a process joins or leaves a group, or when a + view (also including itself). Whenever a process joins or leaves a group, or when a crashed process has been detected, a new view is sent to all remaining group members. When a member process is suspected of having crashed, a suspicion @@ -117,12 +102,20 @@ received. + + Note that the push approach to receiving messages and views is preferred. This involves setting a Receiver + in the channel and getting callbacks invoked by JGroups whenever a message or view is received. + The current pull approach (JChannel.receive() method) has been deprecated in 2.8 and will be removed in 3.0. + + + + There is currently only one implementation of Channel: JChannel. The properties of a channel are typically defined in an XML file, but JGroups also allows for configuration - through simple strings, URIs, DOM trees or even programming. + through simple strings, URIs, DOM trees or even programmatically. The Channel API and its related classes is described in @@ -177,7 +170,7 @@ The protocol stack containins a number of protocol layers in a bidirectional list. All messages sent and received over the channel have to pass - through the protocol stack. Every layer may modify, reorder, pass + through all protocols. Every layer may modify, reorder, pass or drop a message, or add a header to a message. A fragmentation layer might break up a message into several smaller messages, adding a header with an id to each fragment, and re-assemble the @@ -186,8 +179,7 @@ The composition of the protocol stack, i.e. its layers, is determined by the creator of the channel: an XML file defines the layers to be used (and the parameters for each - layer). This string might be interpreted differently by each - channel implementation; in JChannel it is used to create the + layer). The configuration is used to create the stack, depending on the protocol names given in the property. diff -Nru libjgroups-java-2.7.0.GA/doc/manual/en/modules/protocols.xml libjgroups-java-2.12.2.Final/doc/manual/en/modules/protocols.xml --- libjgroups-java-2.7.0.GA/doc/manual/en/modules/protocols.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/manual/en/modules/protocols.xml 2011-10-18 11:22:35.000000000 +0000 @@ -63,6 +63,10 @@
Initial membership discovery + The task of the discovery is to find an initial membership, which is used to determine the current + coordinator. Once a coordinator is found, the joiner sends a JOIN request to the coord. + +
PING @@ -70,6 +74,33 @@
+ +
+ FILE_PING + This uses a shared directory into which all members write their addresses. New joiners read all addresses + from this directory (which needs to be shared, e.g. via NFS or SMB) and ping each of the elements of + the resulting set of members. When a member leaves, it deletes its corresponding file. + + FILE_PING can be used instead of GossipRouter in cases where no external process is desired. + + + ${PING} + +
+ +
+ JDBC_PING + This uses a shared Database into which all members write their addresses. New joiners read all addresses + from this Database and ping each of the elements of the resulting set of members. When a member leaves, + it deletes its corresponding record. + + JDBC_PING is an alternative to S3_PING by using Amazon RDS instead of S3. + + + ${JDBC_PING} + +
+
TCPPING @@ -92,6 +123,32 @@ ${MPING}
+ +
+ BPING + + BPING uses UDP broadcasts to discover other nodes. The default broadcast address (dest) is + 255.255.255.255, and should be replaced with a subnet specific broadcast, e.g. 192.168.1.255. + + + ${BPING} + +
+ +
+ S3_PING + This uses an Amazon S3 bucket into which all members write their addresses. New joiners read all addresses + from this bucket and ping each of the elements of the resulting set of members. When a member leaves, it + deletes its corresponding file. + + S3_PING is primarily meant to be used on Amazon EC2 where multicast traffic is not allowed and + no external process (GossipRouter) is desired. When Amazon RDS is preferred over S3, or if a shared + database is used, an alternative is to use JDBC_PING. + + + ${S3_PING} + +
@@ -155,7 +212,7 @@ Example: <FD_ALL interval="3000" timeout="10000"/> - In the exampe above, we send a heartbeat every 3 seconds and suspect members if we haven't received a + In the example above, we send a heartbeat every 3 seconds and suspect members if we haven't received a heartbeat (or traffic) for more than 10 seconds. Note that since we check the timestamps every 'interval' milliseconds, we will suspect a member after roughly 4 * 3s == 12 seconds. If we set the timeout to @@ -331,7 +388,18 @@ ${UNICAST} -
+ + +
+ UNICAST2 + + UNICAST2 provides lossless, ordered, communication between 2 members. Contrary to UNICAST, it + uses negative acks (similar to NAKACK) rather than positive acks. This reduces the communication + overhead required for sending an ack for every message. + + + ${UNICAST2} +
@@ -340,20 +408,33 @@
FRAG and FRAG2 - - + ${FRAG}
- Ordering (FIFO covered by NAKACK) - + Ordering
- Total Order (SEQUENCER) + SEQUENCER - + + SEQUENCER provider total order for multicast (=group) messages by forwarding messages to the current + coordinator, which then sends the messages to the cluster on behalf of the original sender. Because it + is always the same sender (whose messages are delivered in FIFO order), a global (or total) order + is established. + + + Sending members add every forwarded message M to a buffer and remove M when they receive it. Should + the current coordinator crash, all buffered messages are forwarded to the new coordinator. + + + Note that retransmissions go to the original sender, not + to the coordinator. + + + ${SEQUENCER}
@@ -534,6 +615,15 @@ state transfer transfers the state in chunks of N bytes where N is user configurable.
+ + Prior to 2.6.9 and 2.8 releases streaming state transfer relied + exclusively on its own tcp sockets to transfer state between members. + The downside of tcp socket approach is that it is not firewall friendly. If + use_default_transport property of pbcast.STREAMING_STATE_TRANSFER is + set to true streaming state transfer will use normal messages to transfer + state. This approach besides being completely transparent to application is also + firewall friendly. However, as expected, tcp sockets have better performance. +
@@ -771,11 +861,35 @@ until it has processed all messages worth max_credits bytes, the senders will block. This in turn allows STABLE to progress and eventually garbage collect most messages from all senders. Therefore, SFC and STABLE complement each other, with SFC blocking senders so that STABLE can catch up. + + + SFC is currently experimental, we recommend to use MFC and UFC (see below) instead. ${SFC}
+ +
+ MFC and UFC + + In 2.10, FC was separated into MFC (Multicast Flow Control) and Unicast Flow Control (UFC). The reason + was that multicast flow control should not be impeded by unicast flow control, and vice versa. Also, + performance for the separate implementations could be increased, plus they can be individually omitted. + For example, if no unicast flow control is needed, UFC can be left out of the stack configuration. + + +
+ MFC + ${MFC} +
+ +
+ UFC + ${UFC} +
+ +
@@ -817,7 +931,7 @@
COMPRESS - + ${COMPRESS}
@@ -881,6 +995,231 @@ ${FLUSH} + +
+ SCOPE + + As discussed in Scopes, the SCOPE protocol is used to deliver updates + to different scopes concurrently. It has to be placed somewhere above UNICAST and NAKACK. + + + + SCOPE has a separate thread pool. The reason why the default thread pool from the transport wasn't used + is that the default thread pool has a different purpose. For example, it can use a queue to which all + incoming messages are added, which would defy the purpose of concurrent delivery in SCOPE. As a matter + of fact, using a queue would most likely delay messages get sent up into SCOPE ! + + + Also, the default pool's rejection policy might not be "run", so the SCOPE implementation would have + to catch rejection exceptions and engage in a retry protocol, which is complex and wastes resources. + + + + The configuration of the thread pool is shown below. If you expect concurrent + messages to N different scopes, then the max pool size would ideally be set + to N. However, in most cases, this is not necessary as (a) the messages might not be to different + scopes or (b) not all N scopes might get messages at the same time. So even if the max pool size is a + bit smaller, the cost of this is slight delays, in the sense that a message for scope Y might wait until + the thread processing message for scope X is available. + + + + To remove unused scopes, an expiry policy is provided: expiration_time is the number of milliseconds + after which an idle scope is removed. An idle scope is a scope which hasn't seen any messages for + expiration_time milliseconds. The expiration_interval value defines the number of milliseconds at + which the expiry task runs. Setting both values to 0 disables expiration; it would then have to be + done manually (see for details). + + + ${SCOPE} +
+ +
+ RELAY + + RELAY bridges traffic between seperate clusters, see for details. + + ${RELAY} +
+ +
+ STOMP + + STOMP is a JGroups protocol which implements the STOMP + protocol. Currently (as of Nov 2010), transactions and acks are not implemented. + + + The location of a STOMP protocol in a stack is shown in . + + +
+ STOMP in a protocol stack + +
+
+ + + The STOMP protocol should be near the top of the stack. + + + A STOMP instance listens on a TCP socket for client connections. The port and bind address of the + server socket can be defined via properties. + + + A client can send SUBSCRIBE commands for various destinations. When a SEND for a given destination is + received, STOMP adds a header to the message and broadcasts it to all cluster nodes. Every node then in + turn forwards the message to all of its connected clients which have subscribed to the same destination. + When a destination is not given, STOMP simply forwards the message to all connected + clients. + + + Traffic can be generated by clients and by servers. In the latter case, we could for example have code + executing in the address space of a JGroups (server) node. In the former case, clients use the SEND + command to send messages to a JGroups server and receive messages via the MESSAGE command. If there is + code on the server which generates messages, it is important that both client and server code agree + on a marshalling format, e.g. JSON, so that they understand each other's messages. + + + Clients can be written in any language, as long as they understand the STOMP protocol. Note that the + JGroups STOMP protocol implementation sends additional information (e.g. INFO) to clients; non-JGroups + STOMP clients should simply ignore them. + + + JGroups comes with a STOMP client (org.jgroups.client.StompConnection) and a demo (StompDraw). Both + need to be started with the address and port of a JGroups cluster node. Once they have been started, + the JGroups STOMP protocol will notify clients of cluster changes, which is needed so client can + failover to another JGroups server node when a node is shut down. E.g. when a client connects to C, after + connection, it'll get a list of endpoints (e.g. A,B,C,D). When C is terminated, or crashes, the client + automatically reconnects to any of the remaining nodes, e.g. A, B, or D. When this happens, a client + is also re-subscribed to the destinations it registered for. + + + The JGroups STOMP protocol can be used when we have clients, which are either not in the same network + segment as the JGroups server nodes, or which don't want to become full-blown JGroups server nodes. + shows a typical setup. + + + +
+ STOMP architecture + +
+
+ + + There are 4 nodes in a cluster. Say the cluster is in a LAN, and communication is via IP multicasting + (UDP as transport). We now have clients which do not want to be part of the cluster themselves, e.g. + because they're in a different geographic location (and we don't want to switch the main cluster to TCP), + or because clients are frequently started and stopped, and therefore the cost of startup and joining + wouldn't be amortized over the lifetime of a client. Another reason could be that clients are written + in a different language, or perhaps, we don't want a large cluster, which could be the case if we + for example have 10 JGroups server nodes and 1000 clients connected to them. + + + In the example, we see 9 clients connected to every JGroups cluster node. If a client connected to + node A sends a message to destination /topics/chat, then the message is multicast from node A to all other + nodes (B, C and D). Every node then forwards the message to those clients which have previously subscribed + to /topics/chat. + + + When node A crashes (or leaves) the JGroups STOMP clients (org.jgroups.client.StompConnection) simply pick + another server node and connect to it. + + + + + + The properties for STOMP are shown below: + + ${STOMP} +
+ + +
+ DAISYCHAIN + + The DAISYCHAIN protocol is discussed in . + + + ${DAISYCHAIN} + +
+ +
+ RATE_LIMITER + + RATE_LIMITER can be used to set a limit on the data sent per time unit. When sending data, only + max_bytes can be sent per time_period milliseconds. E.g. if max_bytes="50M" and time_period="1000", then + a sender can only send 50MBytes / sec max. + + + ${RATE_LIMITER} +
+ +
+ Locking protocols + + There are currently 2 locking protocols: org.jgroups.protocols.CENTRAL_LOCK and + org.jgroups.protocols.PEER_LOCK. + +
+ CENTRAL_LOCK + + CENTRAL_LOCK has the current coordinator of a cluster grants locks, so every node has to communicate + with the coordinator to acquire or release a lock. Lock requests by different nodes for the same lock + are processed in the order in which they are received. + + + A coordinator maintains a lock table. To prevent losing the knowledge of who holds which locks, the + coordinator can push lock information to a number of backups defined by num_backups. If num_backups + is 0, no replication of lock information happens. If num_backups is greater than 0, then the coordinator + pushes information about acquired and released locks to all backup nodes. Topology changes might + create new backup nodes, and lock information is pushed to those on becoming a new backup node. + + + The advantage of CENTRAL_LOCK is that all lock requests are granted in the same order across + the cluster, which is not the case with PEER_LOCK. + + + ${CENTRAL_LOCK} +
+ +
+ PEER_LOCK + + PEER_LOCK acquires a lock by contacting all cluster nodes, and lock acquisition is only successful + if all non-faulty cluster nodes (peers) grant it. + + + Unless a total order configuration is used (e.g. org.jgroups.protocols.SEQUENCER based), lock + requests for the same resource from different senders may be received in different order, so + deadlocks can occur. Example: + + Nodes A and B + A and B call lock(X) at the same time + A receives L(X,A) followed by L(X,B): locks X(A), queues L(X,B) + B receives L(X,B) followed by L(X,A): locks X(B), queues L(X,A) + + + + To acquire a lock, we need lock grants from both A and B, but this will never happen here. + To fix this, either add SEQUENCER to the configuration, so that all lock requests are received in + the same global order at both A and B, or use + java.util.concurrent.locks.Lock.tryLock(long,javaTimeUnit) with retries if a lock cannot be acquired. + + + ${PEER_LOCK} +
+
+ +
+ CENTRAL_EXECUTOR + + CENTRAL_EXECUTOR is an implementation of Executing which is needed by the ExecutionService. + + ${CENTRAL_EXECUTOR} +
+ diff -Nru libjgroups-java-2.7.0.GA/doc/manual/en/modules/writing.xml libjgroups-java-2.12.2.Final/doc/manual/en/modules/writing.xml --- libjgroups-java-2.7.0.GA/doc/manual/en/modules/writing.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/manual/en/modules/writing.xml 2011-10-18 11:22:35.000000000 +0000 @@ -66,7 +66,7 @@ int cnt=1; for(int i=0; i < 5; i++) { Message msg=new Message(); - msg.putHeader("x", new MyHeader(cnt++)); + msg.putHeader((short)1900, new MyHeader(cnt++)); ch.send(msg); } ch.close(); @@ -83,12 +83,6 @@ this.counter=counter; } - private static final long serialVersionUID=7726837062616954053L; - - public void writeExternal(ObjectOutput out) throws IOException {} - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {} - public String toString() { return "counter=" + counter; } diff -Nru libjgroups-java-2.7.0.GA/doc/MarshalingFormat.txt libjgroups-java-2.12.2.Final/doc/MarshalingFormat.txt --- libjgroups-java-2.7.0.GA/doc/MarshalingFormat.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/MarshalingFormat.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,61 +0,0 @@ - -// Author: Bela Ban -// $Id: MarshalingFormat.txt,v 1.10 2008/10/21 07:40:26 belaban Exp $ - - -Binary format for marshalled messages -===================================== - -An org.jgroups.Message is marshalled to a byte stream as follows. - -- version ID: short (2 bytes). Version.encode() encodes the major, minor and patch version into the short. -- flags (byte): - - single message or list of messages (LIST) - If single message --> SingleMessage, else MessageList - - multicast (MULTICAST) or unicast message (for optimizations) - -SingleMessage: -- leading: byte. Has bits set for null src and dest addresses, buf and headers -- flags: byte -- src address: Address -- [length of buf]: int -- [buf]: byte[] array -- [Headers]: list of headers --> Headers - - -MessageList: -- length: int. Number of messages in the list -- src address: Address -- 1-m Messages: --> SingleMessage, but with no src and dest addresses - - - -Headers: -- length: int. Number of headers -- For each Header: - - Key: UTF-8 string - - Header - - -Header: -- magic_number (short) -- if magic number == -1 (not present): - - no-op -- else - - UTF-8 string (class name) -- size in bytes (short) -- contents (header-specific) - - -UTF-8 strings: -- All strings start with a short that is the length of the string (DataOutputStream.writeUTF(String)) - - -Notes: - -- In most cases, we don't need to send the dest address, because the sender knows whether the message - was received on the unicast or multicast socket, and can thus set the dest address in an incoming - message to its own local address, or multicast address - -- This is currently as used by UDP. Once we move to Transport (e.g. including TCP), this needs to be - revisited. Currently (2.2.8), TCP uses externalization, *not* Streamable. \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/doc/NullDestAddresses.txt libjgroups-java-2.12.2.Final/doc/NullDestAddresses.txt --- libjgroups-java-2.7.0.GA/doc/NullDestAddresses.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/NullDestAddresses.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,21 +0,0 @@ - -Nulling of destination addresses for optimized marshalling -========================================================== - -Version: $Id: NullDestAddresses.txt,v 1.2 2005/08/26 11:06:37 belaban Exp $ -Author: Bela Ban -Date: Aug 26 2005 - -When we marshall a message (org.jgroups.Message), we can transmit a null value for the destination, because -the receiver can determine the destination: -- for UDP: if received on the multicast receive socket, the destination is the multicast address (same as null) - if received on the unicast receive socket, the destination is the local_addr (ourself) -- for TCP: we use the MULTICAST byet sent with the message when unmarshalling the message: - - if true, we leave the deatination null (= multicast destination) - - if not set, we set the destination to the address passed to use from the ConnectionTable - -This requires that when marshalling a message, we send a multicast byte with each Message (or once for bundled msgs) -based on the destination address ! - -Note that we *cannot* modify the destination address in the message itself, otherwise retransmissions might fail ! - diff -Nru libjgroups-java-2.7.0.GA/doc/NullingSrcAddresses.txt libjgroups-java-2.12.2.Final/doc/NullingSrcAddresses.txt --- libjgroups-java-2.7.0.GA/doc/NullingSrcAddresses.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/NullingSrcAddresses.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ - -Loopback adaptor issues on Windows ----------------------------------- - -JIRA: http://jira.jboss.com/jira/browse/JGRP-79 -Version: $Id: NullingSrcAddresses.txt,v 1.1 2005/05/19 07:57:46 belaban Exp $ - -On Windows, when a loopback adaptor is created, we can associate multiple (virtual) IP -addresses with it, e.g. 10.0.0.1 and 10.0.0.2. - -However, when we have a member M1 bound to 10.0.0.1, and another member M2 bound to 10.0.0.2, and -bind_to_all_interfaces is set to true, then it was observed that - regardless of the bind address - -the sender's address in a DatagramPacket received was always 10.0.0.1 (the first address assigned) ! - -Therefore, members would never find each other. - -The reason this shows up now (in 2.2.8) is that as an optimization, we *don't* send the src address -in the Message anymore, so we can save a few bytes, but we null the src address, and set it to the sender's -address when we *receive* the packet. -This can be disabled by setting null_src_addresses to false (default is true) \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/doc/perfgraph.plt libjgroups-java-2.12.2.Final/doc/perfgraph.plt --- libjgroups-java-2.7.0.GA/doc/perfgraph.plt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/perfgraph.plt 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -#This is a gnuplot file used for plotting performance test result graphs - -set term png -set out "picname.png" -set data style boxes -set style fill solid border -1 -set logscale xy -set yrange [1:100000] -set xrange [1:1000000] -set style fill solid 1.0 -set boxwidth 0.10 -set title "Throughput in msg/sec(cluster of 8 machines,udp.xml,JGroups 2.3)" -set xlabel "Message size" -set ylabel "Number of messages per sec" -set xtics ("100B" 100,"1K" 1000, "10K" 10000, "100K" 100000) -plot "perfdata.dat" using ($1):($2) lt rgb "#6495ED" t "1 sender",\ -"perfdata.dat" using ($1*1.27):($3) lt rgb "#BA55D3" t "4 senders",\ -"perfdata.dat" using ($1*1.63):($4) lt rgb "#FFF8DC" t "8 senders" - - -#where perfdata.dat looks something like this - -#100 29754 30873 27161 -#1000 15886 11354 15918 -#10000 2013 314 624 -#100000 183 77 54 diff -Nru libjgroups-java-2.7.0.GA/doc/PerformanceNotes.txt libjgroups-java-2.12.2.Final/doc/PerformanceNotes.txt --- libjgroups-java-2.7.0.GA/doc/PerformanceNotes.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/PerformanceNotes.txt 2011-10-18 11:22:35.000000000 +0000 @@ -2,7 +2,6 @@ Check list for performance tuning (mainly gige) =============================================== -Version: $Id: PerformanceNotes.txt,v 1.1 2006/08/15 08:23:33 belaban Exp $ Author: Bela Ban, Eric For the transmit queue: diff -Nru libjgroups-java-2.7.0.GA/doc/persistence.html libjgroups-java-2.12.2.Final/doc/persistence.html --- libjgroups-java-2.7.0.GA/doc/persistence.html 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/persistence.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,217 +0,0 @@ - - - - Persistence for Hashtables - - - - - - - - -
- -

Persistence
-

-

Using Persistence

-
    -
  • The package is meant for users who need to maintain the state of their -Serializable name-value pair sets for better fault tolerance. Using this package -will improve usability but will affect performance depending on the usage -of the persist API.
  • -
  • The current package will support only 2 known implementations, the -DB storage way (which is vendor independant) and an ad-hoc (or some open -source) implementation.
  • -
  • The Persistence package assumes  that the user will provide appropriate -properties to initialize and use the storage mechanism.
  • -
-

Code Example

-Code examples make it easy to comprehend the package. -


-

-import org.jgroups.persistence.*; -
-
-// getting default factor
-
PersistenceFactory - factory = PersistenceFactory();
-
//get reference to -PersistenceManager (handle Exception as required by application)
-
// if user defines own properties
-
 try
-
{
-
    String -filepath = "/home/user/userdefined.properties"
-
    PersistenceManager -manager = factory.createPersistenceManager(filepath);
-
   
-    //if user used existing properties by filling the values -(default properties)
-
    // PersistenceManager -manager = factory.createPersistenceManager();
-
}catch (Exception e)
-
{
-
    //handle -Exception
-
}
-

-
-

-
-
//once the persistencemanager -reference is obtained, use available
-
// to save a NV
-
HashMap map = new HashMap();
-
map.put("bela", "bela");
-
map.put("mandar", "mandar");
-
try
-
{
-
    manager.saveAll(map);                      -// will store all values from map into storage
-
}catch (CannotPersistException -cpe)
-
{
-
    //handle -exception
-
}
-

-
-

-
-
//to update a value;
-
try
-
{
-
    map.put("bela", -"geek");
-
    manager.save("bela", -"geek");             - // will update { {bela,bela}, {mandar,mandar})
-
                                               -// to          { {bela,geek}, -{mandar,mandar})
-
}catch (CannotPersistException -cpe)
-
{
-
    //handle -exception
-
}
-

-
-

-
-
// to remove an entry
-
try
-
{
-
    map.remove("mandar");
-
    String -value = (String) manager.remove("mandar");// will update {{bela,geek}, -{mandar, mandar}}
-
    //use -value if required                           -// to          {bela,geek}
-
}catch (CannotRemoveException -cre)
-
{
-
    // handle -exception
-
}
-

-
-
//**************************************************************************
-

-
-
// say, at start of application -, need to get back stored state
-
//use above example to get -back reference to "manager"
-
try
-
{
-
    HashMap -map = (HashMap) manager.retrieveAll();    // map will -get {{bela,geek}}
-
}catch (CannotRetrieveException -cree)
-
{
-
    // handle -exception
-
}
-

-
-

Entering Properties (persist.properties)

-Currently, the properties reflect only the DB related, as the FILE based implementation -is not in place.
-  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameDescriptionTypical Value
persistUsed to decide between DB or FILE.DB/FILE (No default assumed)
jdbc.tableLets the user dictate the schema used to storage purposes on the -database.create table replhashmap(key varchar(100), keybin blob, valbin -blob) 
jdbc.DriverClassname for driver that needs to be loaded for DB storage.oracle.jdbc.driver.OracleDriver
jdbc.ConnConnection string required for user to connect to Db instancejdbc:oracle:oci8:@instance
jdbc.UserUser name required to connect to provided DB instanceuser
jdbc.PassPassword wrt to User providedpass
-

-
-

-
-

-
-

Using org.jgroups.blocks.DistributedHashtable with PersistenceManager

-
// Example of how to setup and use DistributedHashtable
-
-
    -
  • Create persist.properties (classpath or home dir)
    -
  • -
  • Start one instance with -persist
  • -
  • Start another instance with -persist
  • -
  • Add a couple of values
  • -
  • Delete both instances
  • -
  • Start first instance again: previous values should be available
  • -
-
-

-
-

-
-

-
-
- - diff -Nru libjgroups-java-2.7.0.GA/doc/PrimaryPartition.txt libjgroups-java-2.12.2.Final/doc/PrimaryPartition.txt --- libjgroups-java-2.7.0.GA/doc/PrimaryPartition.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/PrimaryPartition.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,43 +0,0 @@ - -Design of the PRIMARY_PARTITION protocol -======================================== - -Author: Bela Ban -Version: $Id: PrimaryPartition.txt,v 1.1 2005/07/21 20:33:01 belaban Exp $ - - -Dawid Kurzyniec wrote: - -> Bela Ban wrote: -> ->> I think adding a protocol on top of (or below ?) GMS will work. However, there is the question of how you actually determine the primary and secondary partitions ? ->> For example, if we have a switch crash, and all 5 members in the group become singleton groups, then the switch is turned back on, which one ->> is the primary partition ? There is no majority. Of course, you simply need to take a deterministic decision, e.g. in this case do a lexical sort and take ->> the first (A). Is this what you are thinking of doing ? So B, C, D and E would get an EXIT event, would have to leave and possibly re-join ? This ->> would be simple to implement. -> -> -> Yes, this is pretty much what we have in mind. We intend in a conflicting case to find the greatest address of all members from candidate groups, then pick as a surviving group the one where this greatest guy belongs to (or should we take the smallest one? It would be nice to bias towards the group containing the current coordinator; does the coordinator has the smallest or the largest address in its group?) - - -It is a lexical sort, e.g. Address extends Comparable, so we sort and take the first member of the resulting merged group as the coordinator - -> In fact we figured we don't even need a protocol - handling this in MembershipListener should do the job, I guess?... (We are lazy and we want to deal with JGroups at the highest level possible). - - -I think it should be a protocols, PRIMARY_PARTITION, and can be implemented as follows: - - * Place it somewhere below GMS, but above MERGE2, It probably needs lossless delivery, so it should be ablove UNICAST and NAKACK as well - * Handle the MERGE event on the up() method: - o Get the subgroups, e.g. {A,B}, {C}, {D,E} and {F} - o Consult a *merge policy*, which determines (given the list of subgroups), the primary partition - o If we are the coordinator of the primary partition: - + Send an exit message to all other coordinators (hmm, you probably can't do that as you are not a member of the subgroups, so probably we have to handle VIEW(MergeView) rather than the MERGE event - + The other coordinators forward the EXIT event to everyone else in their group, so all members leave (and possibly rejoin) the group - o Else - + Send the EXIT event to everyone in my group - + Everyone shuts down and possibly rejoins later - -The MergePolicy implementation needs to be configurable, so devs can specify their own implementation. We would supply a default impl if not specified. -Looks relatively straightforward. The only thing I don't really like is that we have to merge *first* before we send the EXIT message to members of the *previous* subgroups. -However, this is probably necessary as we cannot send messages to members *not* in our group \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/doc/ProtocolReentrancy libjgroups-java-2.12.2.Final/doc/ProtocolReentrancy --- libjgroups-java-2.7.0.GA/doc/ProtocolReentrancy 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/ProtocolReentrancy 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ - -Author: Bela Ban -Version: $id$ - -A typical protocol in JGroups can be assured that only up() and down() methods can be called concurrently, -however up() and up() methods, and down() and down() methods cannot be called concurrently. This is due -to JGroups using an up and down queue, and 1 thread per queue continually dequeues messages and invokes up() or down(). -However, with the introduction of down_thread=false and up_thread=false, we don't use queues anymore. If, in this case, -we have multiple threads calling Channel.send() concurrently, then the above guarantee cannot be provided anymore. -As a result, down() in any given protocol *can* now be called concurrently, by different threads, and so each protocol -has to be made thread safe (reentrant). - -The focus here has to be on the down() side; multiple threads can call send() simultaneously. -However, the up() methods are probably not much of a problem because they are called by a single -thread (the receiver thread). We *cannot* use a thread pool for receiving messages, as this would -violate the ordering previously established. \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/doc/README libjgroups-java-2.12.2.Final/doc/README --- libjgroups-java-2.7.0.GA/doc/README 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/README 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,19 @@ + +JGroups ships as a single JAR file: jgroups-x.y.z.jar. Simply add it to your classpath and start coding ! + +The JGroups version can be printed with java -jar jgroups-x.y.z.jar. + +There's a simple draw demo, start 2 or more instances: java -cp jgroups-x.y.z.jar org.jgroups.demos.Draw, and see if +the members find each other. + +The sources for the core classes are in jgroups-sources.jar. + +If you want the complete sources, including unit tests, go to http://www.github.com/belaban/jgroups. + +JGroups is hosted at www.jgroups.org. + +For questions use either the dev or users mailing list. + +Enjoy, + +Bela Ban diff -Nru libjgroups-java-2.7.0.GA/doc/RELEASE_INSTRUCTIONS libjgroups-java-2.12.2.Final/doc/RELEASE_INSTRUCTIONS --- libjgroups-java-2.7.0.GA/doc/RELEASE_INSTRUCTIONS 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/RELEASE_INSTRUCTIONS 2011-10-18 11:22:35.000000000 +0000 @@ -1,11 +1,10 @@ -// $Id: RELEASE_INSTRUCTIONS,v 1.21 2008/09/17 15:16:51 belaban Exp $ Things to do before packaging a release --------------------------------------- Pre-release: - Do a cvs update -dP and commit all pending changes -- Run all unit tests (all-tests-cc target) +- Run all unit tests: ./build clean all-tests reports - Run the manual tests in doc/tests/ManualTests.txt - Run the performance tests to see whether performance is about the same (see wiki http://wiki.jboss.org/wiki/Wiki.jsp?page=PerfTests) @@ -13,12 +12,12 @@ Release: - Update the ReleaseNotes-xxx.txt for the given release -- Set version in: Version.java, manifest.mf, build.xml and jgroups-pom.xml +- Set version in: Version.java, manifest.mf, build.xml and pom.xml * Note that the version needs to conform to the version standards, e.g. 2.6.2.GA or 2.6.4.CR2, 2.5.4.Beta1 - Create a CVS tag: JGroups_x_y_z - Add tag information to wiki: http://wiki.jboss.org/wiki/Wiki.jsp?page=Branches -- Create a distribution: ./build.sh dist - - the distribution files are dist/JGroups-x.y.z-rel.bin.zip, dist/JGroups-x.y.z-rel.src.zip +- Create a distribution: ./build.sh jar + - the distribution files are dist/jgroups-x.y.z.jar, dist/jgroups-sources.jar - Upload distribution files to web site: - Go to http://sourceforge.net/projects/javagroups/ - Log in and pick http://sourceforge.net/project/admin/editpackages.php?group_id=6081 @@ -30,25 +29,13 @@ - see JGroups/bin/upload_manual.sh - Create javadoc: ./build.sh javadoc and upload to web site - use process similar to pdf and html (script is bin/upload_javadocs.sh) -- Add new release binary jar *and* source jar to maven2 repository (http://repository.jboss.org/maven2) - a. Check out the jgroups directory of the maven repository into a working directory $JG_REPO - > svn co https://svn.jboss.org/repos/repository.jboss.org/maven2/jgroups/jgroups $JG_REPO - b. Update the working copy with the new release binary jar file - > mvn deploy:deploy-file -Dfile=$JG_RELEASE/dist/jgroups-all.jar -Durl=file://$JG_REPO -DpomFile=$JG_RELEASE/jgroups-pom.xml - This command will do the following: - - use the pom you passed to determine groupId/artifactId/version - - create a directory in the svn working copy you checked out with the groupId/artifactID/version number - - copy the jar into the directory and name it by version number - - copy the pom passed into the directory - - create checksums for the jar and the pom - - update the maven meta-data files in the parent directory - c. Update the working copy with the new release sources jar file - > mvn deploy:deploy-file -Dfile=$JG_RELEASE/dist/jgroups-sources.jar -Durl=file://$JG_REPO \ - -DpomFile=$JG_RELEASE/jgroups-pom.xml -Dclassifier=sources - Performs the same actions as in step b., except that the classifier parameter causes the file to retain - the sources suffix in the repository. - d. Add the new directories and files to version control and commit the changes to the maven2 repo. - (see wiki http://wiki.jboss.org/wiki/Wiki.jsp?page=MavenReleaseRepository) +- Add new release binary jar *and* source jar to Nexus maven repository (http://repository.jboss.org/nexus) + a. Run ./bin/release_to_nexus.sh (you need access permissions to Nexus) + b. Go to Nexus: https://repository.jboss.org/nexus + c. Log in (jboss.org login) + d. Click on Staging repositories in the left hand pane + e. Right click on the uploaded artifacts (JAR and src JAR) and either drop or promote them. In case of promotion, + the artifacts will be promoted to the jboss-releases repository and removed from the staging repo - Announcement to the jg-dev and jg-users mailing lists -include release notes and changelog diff -Nru libjgroups-java-2.7.0.GA/doc/ReleaseNotes-2.12.txt libjgroups-java-2.12.2.Final/doc/ReleaseNotes-2.12.txt --- libjgroups-java-2.7.0.GA/doc/ReleaseNotes-2.12.txt 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/ReleaseNotes-2.12.txt 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,95 @@ + + +Release Notes JGroups 2.12.1 +============================ + + +Author: Bela Ban + +JGroups 2.12.1 is API-backwards compatible with previous versions (down to 2.2.7). + +Below is a summary (with links to the detailed description) of the major new features, optimizations and bug fixes. + + + + +New features +============ + +Sample config for large clusters +-------------------------------- +[https://issues.jboss.org/browse/JGRP-1307] + + +Config udp-largecluster.xml was added, serves as blueprint for large clusters. This is work-in-progress. + + + + +Optimizations +============= + + +ExecutionService +---------------- +[https://issues.jboss.org/browse/JGRP-1308] +[https://issues.jboss.org/browse/JGRP-1310] + +Removed serialization overhead when assigning task to self + + + + + +Bug fixes +========= + + +ENCRYPT: use alg provider for cipher creation +---------------------------------------------------- +[https://issues.jboss.org/browse/JGRP-1311] + +During distribution of the encryption key from the master to each cluster member, +use the configured asym_provider and sym_provider when getting Ciphers. + + + +TCP SocketFactory ignored +------------------------- +[https://issues.jboss.org/browse/JGRP-1312] + +Despite setting a custom SocketFactory, it was ignored. + + +Configuration sanity check: order of protocols was not enforced +--------------------------------------------------------------- +[https://issues.jboss.org/browse/JGRP-1305] + +E.g. STABLE being below NAKACK would not throw an exception + + +Custom thread factories, thread pools and timer overridden in init() +-------------------------------------------------------------------- +[https://issues.jboss.org/browse/JGRP-1306] + +When setting a custom thread factory, pool or timer, they would get overwritten in TP.init() + + + +Manual +====== + +The manual is online at http://www.jgroups.org/manual/html/index.html + + + +The complete list of features and bug fixes can be found at http://jira.jboss.com/jira/browse/JGRP. + + +Bela Ban, Kreuzlingen, Switzerland +Vladimir Blagojevic, Toronto, Canada +Richard Achmatowicz, Toronto, Canada +Sanne Grinovero, Newcastle, Great Britain + +April 2011 + diff -Nru libjgroups-java-2.7.0.GA/doc/ReleaseNotes-2.2.8.txt libjgroups-java-2.12.2.Final/doc/ReleaseNotes-2.2.8.txt --- libjgroups-java-2.7.0.GA/doc/ReleaseNotes-2.2.8.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/ReleaseNotes-2.2.8.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ - -Release Notes JGroups 2.2.8 -=========================== - -Version: $Id: ReleaseNotes-2.2.8.txt,v 1.2 2007/08/20 11:15:39 belaban Exp $ -Author: Bela Ban - - -Fast Message marshalling ------------------------- -- Replaced Externalizable for Message with Streamable, resulting in much faster marshalling and - reduced size of marshalled messages, allowing for more messages to be sent / second -- org.jgroups.tests.MessageSerializationTest2 can be used to compare Externalizable with - Streamable: -- For 50000 messages, size reduction is almost 50%, marshalling 150% faster and unmarshalling 650% faster - flags="-Xmx500M -Xms500M -XX:NewRatio=1 -XX:+AggressiveHeap -verbose:gc - -XX:+DisableExplicitGC -XX:ThreadStackSize=32 -XX:CompileThreshold=100" - java $flags org.jgroups.tests.MessageSerializationTest2 -num 100000 -add_headers false - serialized size=8588935, streamable size=5788899, streamable is 48 percent smaller - serialized write=831, streamable write=331, streamable write is 151 percent faster - serialized read=1352, streamable read=180, streamable read is 651 percent faster - -Performance numbers -------------------- -- Real tests will be produced in 2.2.9, using org.jgroups.tests.perf.Test -- With JGroups/conf/fc-fast-minimalthreads.xml, I got ca 5000 1K messages on my laptop: - (2 members, 1 sender, 1 receiver, 1 CPU laptop, 20000 1K msgs, 100Mbps switch) - - April 20 2005: **5006** msgs/sec (on 192.168.5.1): - -Xmx500M -Xms500M -XX:NewRatio=1 -XX:+AggressiveHeap -verbose:gc -XX:+DisableExplicitGC - -XX:ThreadStackSize=32 -XX:CompileThreshold=100: - combined: num_msgs_expected=20000, num_msgs_received=20000 (loss rate=0%), received=20MB, - time=3995ms, msgs/sec=5006.26, throughput=5.01MB/sec - -UDP ----------- -- bind_to_all_interfaces="true" now allows to listen for multicast messages on *all* available interfaces. - This requires 1.4, under 1.3 the default interface will be selected - -MPING ------ -- MPING allows for a combination where node discovery in a cluster uses multicast, but the real transport uses - TCP. This is an addition to TCP:TCPPING and TCP:TCPGOSSIP. Example (short version of JGroups/conf/mping.xml): - - - - - - - - - -Concurrent startup ------------------- -- When multiple members are started simultaneously, and no other member is running yet, - they form singleton groups, and merge after some time. - The new version avoids this merge, so merging occurs only after network partitions now, never - on concurrent startup of initial members - - - - - - diff -Nru libjgroups-java-2.7.0.GA/doc/ReleaseNotes-2.2.9.txt libjgroups-java-2.12.2.Final/doc/ReleaseNotes-2.2.9.txt --- libjgroups-java-2.7.0.GA/doc/ReleaseNotes-2.2.9.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/ReleaseNotes-2.2.9.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,143 +0,0 @@ - -Release Notes JGroups 2.2.9 -=========================== - -Version: $Id: ReleaseNotes-2.2.9.txt,v 1.8 2005/12/09 14:53:36 belaban Exp $ -Author: Bela Ban -Date: Dec 9 2005 - - -JMX support ------------ -The channel and most protocols can now be accessed via JMX. This can -be used in any environment that provides an MBeanServer, e.g. JBoss or -JDK 5. With JDK 5's jconsole, for example, retransmission counters can -be viewed in realtime, or operations can be invoked that dump the -retransmission windows for NAKACK etc. -More information is available at -http://wiki.jboss.org/wiki/Wiki.jsp?page=JMX. - - -Push model available in JChannel --------------------------------- -Instead of having to pull (receive()) a message out of a channel, a Receiver listener can -be registered with a channel. When a message, view change, state request etc -is available, the listener is called immediately. This avoids a context switch, given -that all messages are usually placed in a queue inside the channel and then pulled out -by the application thread. Here's an example of how this can be used: - -JChannel ch=new JChannel(); -ch.setReceiver(new ReceiverAdapter() { - public void receive(Message msg) { - System.out.println("-- received " + msg); - } - - public void viewAccepted(View new_view) { - System.out.println("-- new view: " + new_view); - } -}); -ch.connect("demo"); -ch.send(new Message()); - -Note that ReceiverAdapter is a class which implements the Receiver -interface, so that only the methods one is interested in have to be -overridden. - - -Fine-grained interface binding ------------------------------- -Attributes receive_on_all_interfaces and receive_interfaces enable -receiving multicast packets on all or a number of interfaces, e.g. -receive_interfaces="hme0,hme1,192.168.5.3" - - -Retransmission from random member ---------------------------------- -[NAKACK] This is helpful if we have a large group, and want to avoid -having to ask the original sender of a message for retransmission. By -asking a random member, we take some potential load off of the -original sender. - - -Added payload to MethodCall ---------------------------- -Needed to pass additional information with a method call, required -in JBossCache. - - -Common transport protocol TP ----------------------------- -UDP and TCP now derive from this, therefore common functionality has -to be implemented and tested only once. TCP now has many more -properties supported by TP. - - -Bounded buffer in message bundling ----------------------------------- -Message bundling has by default always used unbounded buffers. For -very fast senders, this could result in more and more messages -being stored in the queue before the bundling thread could dequeue and -send them, resulting in out-of-memory issues. -The bundling buffer is now bounded, by default a size of 2000 elements -is configured. This can be set via -outgoing_queue_max_size="". - - -Performance improvements ------------------------- -50% speed improvement for -RpcDispatcher/MessageDispatcher/RequestCorrelator/MethodCall. -Most headers now support size() and Streamable, making marshalling and -unmarshalling faster. - - -Discovery of all clusters in a network --------------------------------------- -With JGroups/bin/probe.sh or probe.bat, it is now possible to discover *all* clusters running in -a network. This is useful for -- management tools that needs to discover the clusters running, and then drill down into each individual cluster -- for diagnostics and trouble shooting -Details at http://wiki.jboss.org/wiki/Wiki.jsp?page=Probe - - -View reconciliation (VIEW_SYNC protocol) ----------------------------------------- -When a coordinator sends out a new view V2 and then leaves (or crashes), it is possible that not -all members receive that view. So we could end up with some members still having V1, and others having -V2. The members having V2 will discard all messages from members with V1. -Note that this is a very rare case, but when it happens, the cluster is screwed up. -VIEW_SYNC solves this by having each member periodically broadcast its view. When a member receives a view -that is greater than its own view, it installs it. Thus, all members will eventually end up with the same -view should the above problem occur. Note that the view sending is done by default every 60 seconds, but it can -also be triggered through JMX by calling the sendView() method directly. -See JGroups/doc/ReliableViewInstallation for details. - - -Address canonicalization ------------------------- -To avoid many instances of Address, 2.2.9 now uses canonicalization, which points the same -addresses to the same memory location. Therefore most addresses can be garbage collected as soon as they -have been created, resulting in reduced memory overhead. Can be turned off with -Ddisable_canonicalization=true. - - -TCP_NIO support ---------------- -A new implementation of NIO support has been added. See configuration sample tcp_nio.xml. -You can configure the number of "reader_threads", "writer_threads" and "processor_threads". -This should allow for large scale TCP based deployments, compared to TCP. - -Bug fixes ---------- -Critical: in rare cases, the digests could be computed incorrectly, -leading to higher message buffering than necessary - -Critical: message bundling (in TP) changed the destination address, so -when unicast messages had to be retransmitted, because dest=null, the -receiver would drop them. This would cause UNICAST to stop delivering -messages, which would accumulate forever ! This happened only in very -rare cases when a high sustained throughput was encountered (e.g. 20 -million messages sent at the highest possible speed). -Workaround: set enable_bundling="false" in UDP. - -Minor: Apply recv_buf_size/send_buf_size to all TCP socket connections. -Many smaller bug fixes. diff -Nru libjgroups-java-2.7.0.GA/doc/ReleaseNotes-2.3.txt libjgroups-java-2.12.2.Final/doc/ReleaseNotes-2.3.txt --- libjgroups-java-2.7.0.GA/doc/ReleaseNotes-2.3.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/ReleaseNotes-2.3.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,89 +0,0 @@ - -Release Notes JGroups 2.3 -========================= - -Version: $Id: ReleaseNotes-2.3.txt,v 1.4 2006/04/26 07:56:01 belaban Exp $ -Author: Bela Ban - - -Multiplexer ------------ -In JBoss we have multiple JGroups channels, one for each application (e.g. JBossCache, ClusterPartition etc). - -The goal of the Multiplexer is to combine all stacks with the *same* configuration into one, and have multiple -apps on top of that same channel. - -To do this we have to introduce multiplexing and demultiplexing functionality, ie. each app will have to have -a unique application ID (a string), and when sending a message, the message has to be tagged with that ID. When -receiving a message, it will be dispatched to the right app based on the ID attached to the message. -We require special handling for VIEW and SUSPECT messages: those need to be dispatched to *all* apps. -State transfer also needs to be handled specially, here we probably have to use thread locals, or change the API (TBD). - -When deployed into JBoss, the Multiplexer will be exposed as an MBean, and all apps that depend on it will be deployed -with dependency injection on the Multiplexer. Of course, the old configuration will still be supported. - -The config of the Multiplexer is done via a config file, which lists a number of stacks, each keyed by a name, e.g. -"udp", "tcp", "tcp-nio" etc. See ./conf/stacks.xml for an example. An app is configured with the name of a stack, e.g. -"udp", and a reference to the Multiplexer MBean. It will get a proxy channel through which all of its communication -will take place. The proxy channel (MuxChannel) will mux/demux messages to the real JGroups channel. - -The advantage of the Multiplexer is that we can reduce N channels into M where M < N. This means fewer threads, therefore -fewer context switches, less memory consumption and easier configuration and better support. - - -Partial state transfer ----------------------- -The Channel.getState() method can now define the ID of a substate to be fetched. This allows applications to -get only a part of its state, not the entire state. -See http://www.jgroups.org/javagroupsnew/docs/manual/html/user-channel.html#PartialStateTransfer for details. - - -AUTH ----- -AUTH is a protocol directly under GMS, which allows to authenticate members who want to join a group. Based on a -pluggable authentication mechanism, new members are either admitted or rejected. In the latter case, Channel.connect() -will fail with a security exception. -For details see JGroups/doc/design/AUTH.txt. - -Sequencer based total order protocol ------------------------------------- -SEQUENCER is an improved implementation of total order, and faster than TOTAL. When sending a message to -the group, the sender sends the message via unicast to the coordinator, who then broadcasts the message -(on behalf of the sender) to all members, with a unique sequence number. The coordinator uses FIFO, -but since there is only 1 sender, this results in total order. -SEQUENCER can be used for example to maintain identical replicas of a (JMS) queue: senders and receivers can -send and receive messages to/from any queue replica simultaneously, without affecting the consistency -of all replicas across the cluster. -A quick 2 node performance test (perf.Test) with 2 million 1K messages showed -ca 6500 messages/sec with sequencer.xml, compared to ca. 10500 messages/sec with fc-fast-minimalthreads.xml. -For details see JGroups/doc/design/SEQUENCER.txt. - -ENCRYPT enhancements --------------------- -The encrypt_entire_message flag (if set to true) will now encrypt the entire message (including the headers), -as opposed to only encrypting the message buffer. Note that this operation requires serialization of the message, -so setting this option to true is expensive. Why use it ? When one wants to prevent eavesdroppers from snooping -out information located in the (non-encrypted) headers, such as sequence numbers. - - - -Incompatibilities to previous version -------------------------------------- -Changed method signature of the RpcDispatcher.callRemoteMethod() methods to throw a Throwable. -Previously they returned the exception as an object, now the exception will be thrown. -Callers of these methods have to change their code, so this is an incompatible change. However, -these calls are not used in JBossCache and JBoss Clustering. -(http://jira.jboss.com/jira/browse/JGRP-154) - - - -Enhancements and bug fixes --------------------------- -- FD_SOCK now uses the bind address of the transport unless a bind_addr is specifically - specified, or the -Dbind.address system property is used. -- FRAG had a bug that corrupted messages when messages were sent concurrently in multiple threads - - -Documentation -------------- -- The user's guide has been updated (http://www.jgroups.org/javagroupsnew/docs/manual/html/index.html) \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/doc/ReleaseNotes-2.4.txt libjgroups-java-2.12.2.Final/doc/ReleaseNotes-2.4.txt --- libjgroups-java-2.7.0.GA/doc/ReleaseNotes-2.4.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/ReleaseNotes-2.4.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,117 +0,0 @@ - -Release Notes JGroups 2.4 -========================= - -Version: $Id: ReleaseNotes-2.4.txt,v 1.10 2006/10/27 12:58:17 belaban Exp $ -Author: Bela Ban - -JGroups 2.4 is a backward compatible release, that is it can replace previous versions (2.2.7 - 2.3) by simply replacing -the JGroups JAR file. The new features are described below. - -The manual is online at http://www.jgroups.org/javagroupsnew/docs/manual/html/index.html - - -FLUSH protocol --------------- -This essentially adds another virtual synchrony implementation for JGroups (the old one being vsync.xml), however it -is based on the default stack which has been used for quite a while now, and which is well tested. -FLUSH is required when we have multiple services sitting on the same Multiplexer, and more than 1 of them requires -state transfer (for an explanation see JGroups/doc/design/PartialStateTransfer.txt). - - -Partial state transfer ----------------------- -Sometimes an application doesn't want to transfer the entire state, but just a subset. Example: JBossCache in JBoss, -where we may want to transfer just a subtree, not the entire tree. - - -Streaming state transfer ------------------------- -When we need to transfer large state, the existing state transfer requires an application programmer to provide the -state as a byte[] buffer. If the state is large, or needs to be retrieved by traversing a tree structure (e.g. -a DOM tree), then providing a byte[] buffer may not make much sense. -Streaming state transfer provides an InputStream to read state and an OutputStream to write state, and transfer the -written bytes in *chunks*, so that the memory requirements are smaller. Example: if we have a huge DOM tree, whose -aggregate size is 2GB (and which has partly been passivated to disk), then the state provider (ie. the coordinator) can -simply iterate over the DOM tree (activating the parts which have been passivated out to disk), and writing to the -OutputStream as he traverses the tree. The receiver will simply read from the InputStream and reconstruct the tree -on his side, possibly again passivating parts to disk. Rather than having to provide a 2GB byte[] buffer (besides the -state, so the needed memory is ca 4GB temporarily), streaming state transfer transfers the state in chunks of N bytes -(user configurable). -Check out the documentation for details and sample code. - - -View bundling -------------- -When we have many concurrent JOINS, LEAVES or SUSPECTs, then we emit a view for each and every one of them. Say we have -20 JOINs, then we will have 20 views. With view bundling, we can reduce this by simply handling multiple -JOIN/LEAVE/SUSPECT requests together as one. This might generate 5 views rather than 20 (config-dependent). - - -Failure detection: FD_PING --------------------------- -This allows to run a script to probe for liveness of a host. Default is /sbin/ping (ping.exe on Windows). Can be used -in addition with FD_SOCK/FD. - - -Failure detection: FD_ICMP --------------------------- -Uses InetAddress.isReachable() to determine whether a given host is reachable. Note that this may or may not use -ICMP pings, depending on the JDK implementation. Can be used in conjunction with FD_SOCK/FD/FD_PING. - - -Performance measurements ------------------------- -Performed on 4, 6 and 8 node clusters. Results are available at http://www.jgroups.org/javagroupsnew/perf/Report.html. - - -Use of variables in configuration files ---------------------------------------- -Variables of type ${var:default} or ${var} can now be used in configuration files (both XML and plain text). -They will get replaced by the result of System.getProperty(var, default). If no system property is set, the -variables will not get substituted. Note that variables and default values cannot contain characters '{', '}' or ':'. - - -User-defined Marshaller is now used for return values too ---------------------------------------------------------- -Before, a user defined Marshaller in RpcDispatcher was only used for marshalling of a request at the caller and -unmarshalling of the request at the receiver, but not for marshalling the response at the receiver and -unmarshalling the response at the caller. In the latter 2 cases, Util.objectTo/FromByteBuffer() was used. -Now, in all 4 cases, the Marshaller will be used (if set). - - -Optimization in Util.objectTo/FromByteBuffer() ----------------------------------------------- -We don't create an ObjectInput/OutputStream for null values and simple primitive types. - - -Incompatibilities to previous version -------------------------------------- -- ExtendedMessageListener: we added 4 methods, so if you used ExtendedReceiverAdapter you're fine. Otherwise you will - have to recompile. Note that this does *not* affect just linking against JGroups, so if you simply replace your - jgroups.jar, you're fine - - -Enhancements and bug fixes --------------------------- -- COMPRESS now uses multiple deflaters/inflaters, this results in more concurrent compression, more performance -- Multiplexer: few bug fixes (detected by integrating into JBoss), there are now 2 new unit tests that cover the bugs fixed -- Bug fix for VIEW_SYNC after network partition and subsequent merge (http://jira.jboss.com/jira/browse/JGRP-217) -- DistributedLockManager now releases locks leftover by a crashed process -- Multiplexer: problem with state transfer (http://jira.jboss.com/jira/browse/JGRP-280) -- Issue when members would suspect themselves after a network partition and subsequent merge - (FD, FD_SOCK and VERIFY_SUSPECT) (http://jira.jboss.com/jira/browse/JGRP-282, http://jira.jboss.com/jira/browse/JGRP-283) -- Optimization of marshalling in Util.object{To,From}ByteBuffer() (http://jira.jboss.com/jira/browse/JGRP-284) -- Incorrect suspect warning with FD_SOCK (http://jira.jboss.com/jira/browse/JGRP-285) -- Multiplexer: view not sent when member crashes (http://jira.jboss.com/jira/browse/JGRP-286) -- Multiplexer: underlying JChannel not closed in MuxChannel in certain scenarios - (http://jira.jboss.com/jira/browse/JGRP-288) -- Pull-the-plug scenarios with TCP, lead to incorrect behavior before, is now fixed: - - http://jira.jboss.com/jira/browse/JGRP-217 - - http://jira.jboss.com/jira/browse/JGRP-302 - - http://jira.jboss.com/jira/browse/JGRP-304 - - -Documentation -------------- -- The user's guide has been updated (http://www.jgroups.org/javagroupsnew/docs/manual/html/index.html) \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/doc/ReleaseNotes-2.5.1.txt libjgroups-java-2.12.2.Final/doc/ReleaseNotes-2.5.1.txt --- libjgroups-java-2.7.0.GA/doc/ReleaseNotes-2.5.1.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/ReleaseNotes-2.5.1.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,34 +0,0 @@ - -Release Notes JGroups 2.5.1 -=========================== - -Version: $Id: ReleaseNotes-2.5.1.txt,v 1.1 2007/09/20 08:13:53 belaban Exp $ -Author: Bela Ban - -JGroups 2.5.1 is a patch release for 2.5 GA. It contains no new functionality, but only bug fixes. - - - -Manual ------- -The manual is online at http://www.jgroups.org/javagroupsnew/docs/manual/html/index.html - - -Bug fixes ---------- - -AUTH: bug in 2.5 which caused AUTH to fail on second and subsequent JOIN attempts *if* the first -attempt was rejected by AUTH. -[http://jira.jboss.com/jira/browse/JGRP-577] - -VIEW_SYNC: there was a regression in 2.5, which causes VIEW_SYNC messages to get dropped. -Note that this bug didn't occur in 2.4.x. -[http://jira.jboss.com/jira/browse/JGRP-586] - -X.509 token not marshalled correctly. This affects ENCRYPT. -[http://jira.jboss.com/jira/browse/JGRP-576] - -The complete list of features and bug fixes can be found at http://jira.jboss.com/jira/browse/JGRP. - - -Bela Ban, Kreuzlingen, Switzerland, Sept 2007 diff -Nru libjgroups-java-2.7.0.GA/doc/ReleaseNotes-2.5.txt libjgroups-java-2.12.2.Final/doc/ReleaseNotes-2.5.txt --- libjgroups-java-2.7.0.GA/doc/ReleaseNotes-2.5.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/ReleaseNotes-2.5.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,193 +0,0 @@ - -Release Notes JGroups 2.5 -========================= - -Version: $Id: ReleaseNotes-2.5.txt,v 1.11 2007/07/03 12:28:05 belaban Exp $ -Author: Bela Ban - -JGroups 2.5 is still API-backwards compatible with previous versions (down to 2.2.5). However, there are some changes: - - JDK 5 is required - - some older protocols were tossed out, e.g. if you used protocols listed in vsync.xml, they won't be found anymore - -The biggest new functionality is the concurrent stack, which allows for concurrent processing of -unrelated messages. - -Below is a summary (with links to the detailed description) of the major new features. - - - -Concurrent stack ----------------- -[http://jira.jboss.com/jira/browse/JGRP-181] - -The concurrent stack is a major performance improvement for clusters where multiple nodes are sending messages at the -same time. Up to and including 2.4.x, all messages from all senders were placed into a single queue and delivered in -order of reception (FIFO) to the application. - -This means that, for a given message M, all messages ahead of M had to get processed before M could get processed, even -if some of those messages were from different senders. - -Now, messages from different senders are processed concurrently. This is done through 2 thread pools, one for default -messages and another one for out-of-band messages. Both pools can be configured through XML, e.g. core and max number -of threads, rejection policy ("run", "discard", "discardoldest" etc), whether to use a queue and if so, queue length etc. - -The concurrent stack will improve performance dramatically when -- there are multiple senders and/or -- the processing of a message takes some time. - -In a cluster of N with N senders, X messages and T think time/message, we have seen total processing time of -all messages drop from X * N * T to (X * T) + ! - - - -Out-of-band (unordered) messages --------------------------------- -[http://jira.jboss.com/jira/browse/JGRP-205] - -In some cases, messages do not need to get delivered in the order in which they were sent. For example, if a sender -A sends messages M1 --> M2 --> M3 --> M4 --> M5 (--> means followed by), and all messages except M3 (heartbeat) and -M5 (discovery request) are regular messages, then all 5 messages will be delivered sequentially. -This means that M3 has to wait for M1 and M2 to get processed, and M5 has to wait for all 4 messages ahead of it, until -it gets processed. -An out-of-band (OOB) message is one that is tagged: -Message msg; -msg.setFlag(Message.OOB) - -An OOB message is reliably transmitted, that is if the network drops it, JGroups will retransmit it. However, the ordering -defined by the stack is ignored for an OOB messages, e.g. in the above case, M3 and M5 can be delivered out of sequence -with regard to the other messages. - -This is perfect for messages like heartbeats or discovery requests or responses, which do not need to be delivered in -FIFO order with respect to other messages from the same sender. If a message is tagged as OOB, it will be handled by the -OOB thread pool rather than the regular thread pool. - - - -Concurrent Multiplexer ----------------------- -[http://jira.jboss.com/jira/browse/JGRP-415] - -A similar problem that the concurrent stack solved, was encountered in the Multiplexer: requests to different services, -but sent by the same sender, were processed sequentially (even with the concurrent stack, where messages from the same sender -are processed in FIFO order). - -This was solved by adding a thread pool to the Multiplexer, which handles requests to different services concurrently, -even if they were sent by the same sender. - - - -Simplified and fast flow control (SFC) --------------------------------------- -[http://jira.jboss.com/jira/browse/JGRP-402] - -SFC is a simple flow control protocol for group (= multipoint) messages. It is simpler than FC, but as the performance -report (http://www.jgroups.org/javagroupsnew/perfnew/Report.html) shows, it has about the same performance as FC, so -we may combine the 2 in the future. Note, however, that SFC does not apply flow control to unicast messages, whereas -FC does. - -Every sender has max_credits bytes for sending multicast messages to the group. - -Every multicast message (we don't consider unicast messages) decrements max_credits by its size. -When max_credits falls below 0, the sender asks all receivers for new credits and blocks -until *all* credits have been received from all members. - -When the receiver receives a credit request, it checks whether it has received max_credits bytes from the requester since -the last credit request. If yes, it sends new credits to the requester and resets the max_credits for the requester. -Else, it takes a note of the credit request from P and - when max_credits bytes have finally been received from P - it -sends the credits to P and resets max_credits for P. - -The maximum amount of memory for received messages is therefore * max_credits. - -The relationship with STABLE is as follows: when a member Q is slow, it will prevent STABLE from collecting messages above -the ones seen by Q (everybody else has seen more messages). However, because Q will *not* send credits back to the senders -until it has processed all messages worth max_credits bytes, the senders will block. This in turn allows STABLE to -progress and eventually garbage collect most messages from all senders. Therefore, SFC and STABLE complement each other, -with SFC blocking senders so that STABLE can catch up. - - - -Full support for virtual synchrony ----------------------------------- -[http://jira.jboss.com/jira/browse/JGRP-341] - -The FLUSH protocol in 2.4.x supported virtual synchrony ([1]), but the flush phase didn't include a message -reconciliation part. This has now been added in 2.5. - -FLUSH is very important for the Multiplexer, where a flush phase is run whenever a member joins, leaves or crashes, or -when a new member acquires the state from an existing member (state transfer). - - - -Simple failure detection protocol (FD_ALL) ------------------------------------------- -[http://jira.jboss.com/jira/browse/JGRP-395] - -Failure detection based on simple heartbeat protocol. Every member periodically multicasts a heartbeat. -Every member also maintains a table of all members (minus itself). -When data or a heartbeat from P are received, we reset the timestamp for P to the current time. -Periodically, we check for expired members, and suspect those. - -Example: - -In the exampe above, we send a heartbeat every 3 seconds and suspect members if we haven't received a heartbeat -(or traffic) for more than 10 seconds. Note that since we check the timestamps every 'interval' milliseconds, -we will suspect a member after roughly 4 * 3s == 12 seconds. If we set the timeout to 8500, then we would suspect -a member after 3 * 3 secs == 9 seconds. - -FD_ALL is interchangeable with FD. - - - -Better naming of threads ------------------------- -Almost all threads have the cluster name and local address appended to their names. This is good if we have multiple -clusters in the same JVM (e.g. in JBossAS), allowing for more meaningful stack traces (knowing which thread -belongs to which cluster). - - -No need for escape characters in stack configuration ----------------------------------------------------- -Now backslashes are not needed for protocol attribute values, e.g. to define an IPv6 mcast_addr, the following works: - - - -The colons in mcast_addr do not need to be escaped with a backslash any longer. Note that '(', ')' and '=' are -still reserved characters and cannot be used as part of an attribute name or value. - - - -Switch to java.util.concurrent classes (JDK 5) ----------------------------------------------- -[http://jira.jboss.com/jira/browse/JGRP-391] - -We switched from concurrent.jar to java.util.concurrent, resulting in slightly better performance and we could also -drop a JAR (concurrent.jar). - - - -Manual ------- -The manual is online at http://www.jgroups.org/javagroupsnew/docs/manual/html/index.html - - -Performance ------------ - -We measured the performance of the 2.5 stack in a cluster of 4, 6 and 8 nodes. The results are discussed in -http://www.jgroups.org/javagroupsnew/perfnew/Report.html. - -We'll present more comprehensive performance numbers (sepecially for the concurrent stack) in 2.6. - - -Bug fixes ---------- -The list of features and bug fixes can be found at http://jira.jboss.com/jira/browse/JGRP. - - -Bela Ban, Kreuzlingen, Switzerland, June 2007 - - -[1] Reliable communication in presence of failures. - Kenneth P. Birman, Thomas A.Joseph. - ACM Transactions on Computer Systems, Vol. 5, No. 1, Feb. 1987 - diff -Nru libjgroups-java-2.7.0.GA/doc/ReleaseNotes-2.6.txt libjgroups-java-2.12.2.Final/doc/ReleaseNotes-2.6.txt --- libjgroups-java-2.7.0.GA/doc/ReleaseNotes-2.6.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/ReleaseNotes-2.6.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,124 +0,0 @@ - -Release Notes JGroups 2.6 -========================= - -Version: $Id: ReleaseNotes-2.6.txt,v 1.3 2007/11/06 12:32:33 belaban Exp $ -Author: Bela Ban - -JGroups 2.6 is still API-backwards compatible with previous versions (down to 2.2.5). - -Below is a summary (with links to the detailed description) of the major new features. - - -Join and state transfer ------------------------ -[http://jira.jboss.com/jira/browse/JGRP-236] - -We added another connect() method in JChannel, which combines joining a cluster and fetching the state from the -coordinator into one method. This is especially useful when we have FLUSH in the stack; thus we only have to use 1 rather -than 2 (1 for JOIN, 1 for state transfer) flush phases. - - -Improved ReplicatedHashMap --------------------------- -[http://jira.jboss.com/jira/browse/JGRP-581] - -ReplicatedHashMap was converted to use generics, and java.util.concurrent.ConcurrentHashMap. It therefore supports 4 new -methods putIfAbsent(), remove() and the two replace() methods. -Developers can choose whether to use asynchronous or synchronous replication, and also pick the timeout for synchronous -calls. -This class supercedes ReplicatedHashtable and DistributedHashtable, which will be removed in version 3.0. - - -Reincarnation issue -------------------- -[http://jira.jboss.com/jira/browse/JGRP-130] - -Using the GMS.reject_join_from_existing_member (default=false) property, we can reject a JOIN request from a reincarnated -member X who crashed, but has not yet been removed (e.g. due to a high timeout in FD). The member would have to retry, -and would only succeed when (the old) X has been excluded from the cluster. -For shunned members who use AUTO_RECONNECT, we loop until this is true [http://jira.jboss.com/jira/browse/JGRP-584]. - - -New transport property 'bind_interface' ---------------------------------------- -[http://jira.jboss.com/jira/browse/JGRP-579] - -This can be used when multipler network interfaces have the *same* IP address, to define the interface to get used, e.g -bind_addr="192.168.2.5" bind_interface="eth1". Useful e.g. with IP Bonding on Linux. - - -FLUSH simplification --------------------- -[http://jira.jboss.com/jira/browse/JGRP-598] - -Removed 2 phases from FLUSH protocol, which simplified code and made FLUSH faster. - - -Unicast bundling can be disabled at the transport level -------------------------------------------------------- -[http://jira.jboss.com/jira/browse/JGRP-429] - -When dealing with latency sensitive applications, we may want to disable message bundling for *responses* (but not for -requests, as requests might carry large payloads). This can be done via the enable_unicast_bundling (default=true) -property. - - -RpcDispatcher can now filter responses as they arrive ------------------------------------------------------- -[http://jira.jboss.com/jira/browse/JGRP-518] - -There's a new callRemoteMethods() method taking an RspFilter, which is called whenever a response has been received, -allowing a request to return based on a condition (e.g. the first non null return value) before all responses -have been received. - - -Ability to add data to a view ------------------------------ -[http://jira.jboss.com/jira/browse/JGRP-597] - -Arbitrary data can be added to a View, which now has a hashmap. Keys and values need to be serializable. - - -Improved thread naming ----------------------- -[http://jira.jboss.com/jira/browse/JGRP-570] - -All threads now hae meaningful names, including their local address and cluster name. This makes it easier to -read stack traces, when we have multiple channels (e.g. in JBoss). -In addition, all threads are now created by the same thread pool. - - -Manual ------- -The manual is online at http://www.jgroups.org/javagroupsnew/docs/manual/html/index.html - - -Performance ------------ -Links to performance tuning: http://wiki.jboss.org/wiki/Wiki.jsp?page=PerfTuning - - - -Bug fixes ---------- -AUTH: bug in 2.5 which caused AUTH to fail on second and subsequent JOIN attempts *if* the first -attempt was rejected by AUTH. -[http://jira.jboss.com/jira/browse/JGRP-577] - -VIEW_SYNC: there was a regression in 2.5, which causes VIEW_SYNC messages to get dropped. -Note that this bug didn't occur in 2.4.x. -[http://jira.jboss.com/jira/browse/JGRP-586] - -X.509 token not marshalled correctly. This affects ENCRYPT. -[http://jira.jboss.com/jira/browse/JGRP-576] - -The complete list of features and bug fixes can be found at http://jira.jboss.com/jira/browse/JGRP. - - -Bela Ban, Kreuzlingen, Switzerland -Vladimir Blagojevic, Toronto, Canada - -Nov 2007 - - diff -Nru libjgroups-java-2.7.0.GA/doc/ReleaseNotes-2.7.txt libjgroups-java-2.12.2.Final/doc/ReleaseNotes-2.7.txt --- libjgroups-java-2.7.0.GA/doc/ReleaseNotes-2.7.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/ReleaseNotes-2.7.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,213 +0,0 @@ - -Release Notes JGroups 2.7 -========================= - -Version: $Id: ReleaseNotes-2.7.txt,v 1.6 2008/11/13 08:18:41 belaban Exp $ -Author: Bela Ban - -JGroups 2.7 is still API-backwards compatible with previous versions (down to 2.2.7). - -This is a big release, with close to 200 JIRA issues fixed and major new functionality. - -Below is a summary (with links to the detailed description) of the major new features. - - -Features -======== - -Shared transport ----------------- -[https://jira.jboss.org/jira/browse/JGRP-631] - -Multiple channels can now share the same transport (and still have different stack configurations on top). This -replaces the Multiplexer, which is not supported any longer as of 2.7. - -See http://www.jgroups.org/javagroupsnew/docs/manual/html/user-advanced.html#d0e2204 for details. - - - -Converted unit tests from JUnit to TestNG ------------------------------------------ -[https://jira.jboss.org/jira/browse/JGRP-410] - -This cut down the time to run all tests from 2.5 hours to 15 minutes ! - - -Use of annotations to provide JMX management information --------------------------------------------------------- -[https://jira.jboss.org/jira/browse/JGRP-723] -[https://jira.jboss.org/jira/browse/JGRP-408] - -By annotating a protocol as @ManagedResource, an attribute as @ManagedAttribute or an operation as -@ManagedOperation, we can simply expose JMX management information. - -This change allowed us to remove the parallel JMX class hierarchy (org.jgroups.jmx package), and dramatically -reduced the effort needed to expose protocols via JMX. - -Credits for the initial implementation go to Chris Mills. - - - -Use of annotations to set properties ------------------------------------- -[https://jira.jboss.org/jira/browse/JGRP-765] - -Instead of implementing setProperties() in each protocol, parsing the input string and converting it to a variable, we -now use the @Property annoation to mark an attribute or getter/setter method. This way, input strings are -automatically mapped to the corresponding fields in a protocol. - -This allowed us to remove a lot of boilerplate code. - -In addition, we now generate the protocol list -documentation (http://www.jgroups.org/javagroupsnew/docs/manual/html/protlist.html) from the @Property annotations. -The benefit is that we need to maintain the documentation only in one place (the code) instead of two, and we now -have a complete documentation of all protocol properties. - - -Ability to replace thread pools with custom thread pools --------------------------------------------------------- -[https://jira.jboss.org/jira/browse/JGRP-632] - -This allows (for example) system integrators to use the thread pools they already have in their applications. It -also gives greater control over thread pool management, e.g. a provider can make all threads in a pool daemon threads. - - -Allow flushing of a cluster subset ----------------------------------- -[https://jira.jboss.org/jira/browse/JGRP-661] - -Rather than flushing the entire cluster, we can now provide a list of target members for the FLUSH. This is needed -for example for buddy replication in JBossCache. - - -Performance improvements ------------------------- -[https://jira.jboss.org/jira/browse/JGRP-846] -[https://jira.jboss.org/jira/browse/JGRP-847] -[https://jira.jboss.org/jira/browse/JGRP-805] -[https://jira.jboss.org/jira/browse/JGRP-806] -[https://jira.jboss.org/jira/browse/JGRP-829] -[https://jira.jboss.org/jira/browse/JGRP-813] - - -http://www.jgroups.org/javagroupsnew/docs/performance.html shows that we can get 150MB/sec/node on a 4 node -cluster connected to a 1GB switch with udp.xml (IP multicasting) on 2.7. This means we get an aggregate cluster -throughput of 600MB/sec ! - -2.7 is ca 30-40% faster than 2.6 ! - - -FC: max block times depending on message size ---------------------------------------------- -[https://jira.jboss.org/jira/browse/JGRP-804] - -We can now set the max time to block for a given message, e.g. block 10ms max for messages up to 10K, 100ms for messages -up to 1MB, 500ms for messages up to 10MB and 2000ms for messages larger than that. - -This means that - regardless of missing credits - messages will be sent after the deadline (max block time) has -elapsed. This adds more predictability as to when messages are sent, but it also can lead to OOMEs if those values -are too low, defying the purpose of flow control. - - -UNICAST/NAKACK: eager lock release ----------------------------------- -[https://jira.jboss.org/jira/browse/JGRP-656] - -Better performance in cases where the receiver of a message uses the calling thread to send a message down the stack. -In this case, the lock will be release as soon as send() is called, releasing the lock and allowing threads with -messages from the same sender to proceed. - - -GossipRouter / GossipClient: make sockets non-blocking ------------------------------------------------------- -[https://jira.jboss.org/jira/browse/JGRP-702] -[https://jira.jboss.org/jira/browse/JGRP-852] - -Can now be configured to use non blocking socket close, connect and read/write - - -Paralellize discovery phase ---------------------------- -[https://jira.jboss.org/jira/browse/JGRP-375] - - -Pluggable Probe ---------------- -[https://jira.jboss.org/jira/browse/JGRP-832] - -This allows for users to write their own plugins which respond to a ping (a probe) and return (for example) -application specific information. - -Details are at http://www.jboss.org/community/docs/DOC-11689 (towards the bottom of the page). - - - - - -Bug fixes -========= - -FD: nodes would not get suspected if traffic from different nodes was received ------------------------------------------------------------------------------- -[https://jira.jboss.org/jira/browse/JGRP-746] - -Traffic from *any* node was counted as a heartbeat. This is incorrect as only traffic from the pinged member -should count as heartbeat. - -FRAG/FRAG2: fragment list is not cleared for crashed member (can lead to memory leak) -------------------------------------------------------------------------------------- -[https://jira.jboss.org/jira/browse/JGRP-800] - -NAKACK: regular message not delivered (in some cases) until new message arrives -------------------------------------------------------------------------------- -[https://jira.jboss.org/jira/browse/JGRP-781] - -STATE_TRANSFER: state transfer broken for large states ------------------------------------------------------- -[https://jira.jboss.org/jira/browse/JGRP-774] - -Concurrent connect of multiple channels with shared transport fails -------------------------------------------------------------------- -[https://jira.jboss.org/jira/browse/JGRP-849] - -Eliminate Linux cross-talk in MPING ------------------------------------ -[https://jira.jboss.org/jira/browse/JGRP-836] - -FLUSH fixes ------------ -[https://jira.jboss.org/jira/browse/JGRP-756] -[https://jira.jboss.org/jira/browse/JGRP-759] -[https://jira.jboss.org/jira/browse/JGRP-700] -[https://jira.jboss.org/jira/browse/JGRP-622] - -FD_SOCK: fixes --------------- -[https://jira.jboss.org/jira/browse/JGRP-841] -[https://jira.jboss.org/jira/browse/JGRP-845] -[https://jira.jboss.org/jira/browse/JGRP-794] -[https://jira.jboss.org/jira/browse/JGRP-745] - -NAKACK: merging of digests is incorrect ---------------------------------------- -[https://jira.jboss.org/jira/browse/JGRP-699] - - - - - -Manual ------- -The manual is online at http://www.jgroups.org/javagroupsnew/docs/manual/html/index.html - - - -The complete list of features and bug fixes can be found at http://jira.jboss.com/jira/browse/JGRP. - - -Bela Ban, Kreuzlingen, Switzerland -Vladimir Blagojevic, Toronto, Canada - -Nov 2008 - - diff -Nru libjgroups-java-2.7.0.GA/doc/RULES libjgroups-java-2.12.2.Final/doc/RULES --- libjgroups-java-2.7.0.GA/doc/RULES 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/RULES 2011-10-18 11:22:35.000000000 +0000 @@ -1,5 +1,4 @@ -# $Id: RULES,v 1.7 2008/03/13 08:11:32 belaban Exp $ Rules developers should adhere to --------------------------------- diff -Nru libjgroups-java-2.7.0.GA/doc/tests/ManualTests.txt libjgroups-java-2.12.2.Final/doc/tests/ManualTests.txt --- libjgroups-java-2.7.0.GA/doc/tests/ManualTests.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/tests/ManualTests.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -$Id: ManualTests.txt,v 1.13 2009/01/05 12:14:00 belaban Exp $ Group Membership Service (GMS) Protocol @@ -12,7 +11,6 @@ - All the tests use the JGroups/Demos/Draw program - All tests should be run with both FD and FD_SOCK - All test should be run with FLUSH (flush-udp.xml) and without FLUSH (udp.xml) -- When FLUSH is used tests should be run with both Channel.BLOCK turned off and on When *FD only* is used make sure that timeout and max_tries are relatively low. For example, @@ -139,9 +137,9 @@ ------- - Start A, B -- Shun A (CTRL-Z and then fg) -- When A has rejoined the group, kill A -- The view needs to be {B} now +- Suspend A (CTRL-Z) +- Wait until B shows membershiop of 1, then resume A (fg) +- A and B needs to merge back into a group of 2 diff -Nru libjgroups-java-2.7.0.GA/doc/todo.lst libjgroups-java-2.12.2.Final/doc/todo.lst --- libjgroups-java-2.7.0.GA/doc/todo.lst 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/todo.lst 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -$Id: todo.lst,v 1.22 2005/06/06 07:47:57 belaban Exp $ diff -Nru libjgroups-java-2.7.0.GA/doc/tutorial/.cvsignore libjgroups-java-2.12.2.Final/doc/tutorial/.cvsignore --- libjgroups-java-2.7.0.GA/doc/tutorial/.cvsignore 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/tutorial/.cvsignore 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1 @@ +build diff -Nru libjgroups-java-2.7.0.GA/doc/tutorial/en/master.xml libjgroups-java-2.12.2.Final/doc/tutorial/en/master.xml --- libjgroups-java-2.7.0.GA/doc/tutorial/en/master.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/tutorial/en/master.xml 2011-10-18 11:22:35.000000000 +0000 @@ -9,8 +9,7 @@ JGroups tutorial - $Date: 2007/07/18 14:26:26 $ - $Id: master.xml,v 1.4 2007/07/18 14:26:26 belaban Exp $ + 2009 Bela @@ -30,12 +29,15 @@ Bela Ban - 2006-2007 + 2006-2011 Red Hat Inc - This document is copyrighted. Copies are allowed for - personal use. Redistribution only with written permission of the author(s). + This document is licensed under the + + Creative Commons Attribution-ShareAlike (CC-BY-SA) 3.0 + + diff -Nru libjgroups-java-2.7.0.GA/doc/tutorial/en/modules/installation.xml libjgroups-java-2.12.2.Final/doc/tutorial/en/modules/installation.xml --- libjgroups-java-2.7.0.GA/doc/tutorial/en/modules/installation.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/doc/tutorial/en/modules/installation.xml 2011-10-18 11:22:35.000000000 +0000 @@ -28,10 +28,6 @@ INSTALL.html: detailed configuration instructions plus trouble shooting - commons-logging.jar: required JAR that provides general logging. This - might get dropped in 3.0 - - jgroups-all.jar (required): JGroups functionality, including demo and junit apps. If a smaller JAR is required, this can be done by downloading the source distribution and invoking the "jar" target, which creates a jgroups-core.jar file (ca 1MB). @@ -76,7 +72,7 @@
Configuration - Add jgroups-all.jar and commons-logging.jar to your CLASSPATH. If you use the log4j logging system, you also + Add jgroups-all.jar to your CLASSPATH. If you use the log4j logging system, you also have to add log4j.jar (this is not necessary if you use the JDK logging system). @@ -107,7 +103,7 @@ $ java -jar jgroups-all.jar Version: 2.5.0 - CVS: $Id: installation.xml,v 1.3 2007/07/16 11:04:12 belaban Exp $ + CVS: $Id: installation.xml,v 1.4 2009/05/13 13:22:09 belaban Exp $ History: (see doc/history.txt for details) diff -Nru libjgroups-java-2.7.0.GA/EULA libjgroups-java-2.12.2.Final/EULA --- libjgroups-java-2.7.0.GA/EULA 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/EULA 1970-01-01 00:00:00.000000000 +0000 @@ -1,109 +0,0 @@ -// $Id: EULA,v 1.1 2006/11/02 08:04:26 belaban Exp $ - -LICENSE AGREEMENT -JBOSS(r) - -This License Agreement governs the use of the Software Packages and any updates to the Software -Packages, regardless of the delivery mechanism. Each Software Package is a collective work -under U.S. Copyright Law. Subject to the following terms, Red Hat, Inc. ("Red Hat") grants to -the user ("Client") a license to the applicable collective work(s) pursuant to the -GNU Lesser General Public License v. 2.1 except for the following Software Packages: -(a) JBoss Portal Forums and JBoss Transactions JTS, each of which is licensed pursuant to the -GNU General Public License v.2; - -(b) JBoss Rules, which is licensed pursuant to the Apache License v.2.0; - -(c) an optional download for JBoss Cache for the Berkeley DB for Java database, which is licensed under the -(open source) Sleepycat License (if Client does not wish to use the open source version of this database, -it may purchase a license from Sleepycat Software); - -and (d) the BPEL extension for JBoss jBPM, which is licensed under the Common Public License v.1, -and, pursuant to the OASIS BPEL4WS standard, requires parties wishing to redistribute to enter various -royalty-free patent licenses. - -Each of the foregoing licenses is available at http://www.opensource.org/licenses/index.php. - -1. The Software. "Software Packages" refer to the various software modules that are created and made available -for distribution by the JBoss.org open source community at http://www.jboss.org. Each of the Software Packages -may be comprised of hundreds of software components. The end user license agreement for each component is located in -the component's source code. With the exception of certain image files identified in Section 2 below, -the license terms for the components permit Client to copy, modify, and redistribute the component, -in both source code and binary code forms. This agreement does not limit Client's rights under, -or grant Client rights that supersede, the license terms of any particular component. - -2. Intellectual Property Rights. The Software Packages are owned by Red Hat and others and are protected under copyright -and other laws. Title to the Software Packages and any component, or to any copy, modification, or merged portion shall -remain with the aforementioned, subject to the applicable license. The "JBoss" trademark, "Red Hat" trademark, the -individual Software Package trademarks, and the "Shadowman" logo are registered trademarks of Red Hat and its affiliates -in the U.S. and other countries. This agreement permits Client to distribute unmodified copies of the Software Packages -using the Red Hat trademarks that Red Hat has inserted in the Software Packages on the condition that Client follows Red Hat's -trademark guidelines for those trademarks located at http://www.redhat.com/about/corporate/trademark/. Client must abide by -these trademark guidelines when distributing the Software Packages, regardless of whether the Software Packages have been modified. -If Client modifies the Software Packages, then Client must replace all Red Hat trademarks and logos identified at -http://www.jboss.com/company/logos, unless a separate agreement with Red Hat is executed or other permission granted. -Merely deleting the files containing the Red Hat trademarks may corrupt the Software Packages. - -3. Limited Warranty. Except as specifically stated in this Paragraph 3 or a license for a particular -component, to the maximum extent permitted under applicable law, the Software Packages and the -components are provided and licensed "as is" without warranty of any kind, expressed or implied, -including the implied warranties of merchantability, non-infringement or fitness for a particular purpose. -Red Hat warrants that the media on which Software Packages may be furnished will be free from defects in -materials and manufacture under normal use for a period of 30 days from the date of delivery to Client. -Red Hat does not warrant that the functions contained in the Software Packages will meet Client's requirements -or that the operation of the Software Packages will be entirely error free or appear precisely as described -in the accompanying documentation. This warranty extends only to the party that purchases the Services -pertaining to the Software Packages from Red Hat or a Red Hat authorized distributor. - -4. Limitation of Remedies and Liability. To the maximum extent permitted by applicable law, the remedies -described below are accepted by Client as its only remedies. Red Hat's entire liability, and Client's -exclusive remedies, shall be: If the Software media is defective, Client may return it within 30 days of -delivery along with a copy of Client's payment receipt and Red Hat, at its option, will replace it or -refund the money paid by Client for the Software. To the maximum extent permitted by applicable law, -Red Hat or any Red Hat authorized dealer will not be liable to Client for any incidental or consequential -damages, including lost profits or lost savings arising out of the use or inability to use the Software, -even if Red Hat or such dealer has been advised of the possibility of such damages. In no event shall -Red Hat's liability under this agreement exceed the amount that Client paid to Red Hat under this -Agreement during the twelve months preceding the action. - -5. Export Control. As required by U.S. law, Client represents and warrants that it: -(a) understands that the Software Packages are subject to export controls under the -U.S. Commerce Department's Export Administration Regulations ("EAR"); - -(b) is not located in a prohibited destination country under the EAR or U.S. sanctions regulations -(currently Cuba, Iran, Iraq, Libya, North Korea, Sudan and Syria); - -(c) will not export, re-export, or transfer the Software Packages to any prohibited destination, entity, -or individual without the necessary export license(s) or authorizations(s) from the U.S. Government; - -(d) will not use or transfer the Software Packages for use in any sensitive nuclear, chemical or -biological weapons, or missile technology end-uses unless authorized by the U.S. Government by -regulation or specific license; - -(e) understands and agrees that if it is in the United States and exports or transfers the Software -Packages to eligible end users, it will, as required by EAR Section 740.17(e), submit semi-annual -reports to the Commerce Department's Bureau of Industry & Security (BIS), which include the name and -address (including country) of each transferee; - -and (f) understands that countries other than the United States may restrict the import, use, or -export of encryption products and that it shall be solely responsible for compliance with any such -import, use, or export restrictions. - -6. Third Party Programs. Red Hat may distribute third party software programs with the Software Packages -that are not part of the Software Packages and which Client must install separately. These third party -programs are subject to their own license terms. The license terms either accompany the programs or -can be viewed at http://www.redhat.com/licenses/. If Client does not agree to abide by the applicable -license terms for such programs, then Client may not install them. If Client wishes to install the programs -on more than one system or transfer the programs to another party, then Client must contact the licensor -of the programs. - -7. General. If any provision of this agreement is held to be unenforceable, that shall not affect the -enforceability of the remaining provisions. This License Agreement shall be governed by the laws of the -State of North Carolina and of the United States, without regard to any conflict of laws provisions, -except that the United Nations Convention on the International Sale of Goods shall not apply. - -Copyright 2006 Red Hat, Inc. All rights reserved. -"JBoss" and the JBoss logo are registered trademarks of Red Hat, Inc. -All other trademarks are the property of their respective owners. - - Page 1 of 1 18 October 2006 - diff -Nru libjgroups-java-2.7.0.GA/.gitignore libjgroups-java-2.12.2.Final/.gitignore --- libjgroups-java-2.7.0.GA/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/.gitignore 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,15 @@ +*~ +*.iws +*.ipr +*.iml +.project +.classpath +.idea/ +.idea +classes/ +build.properties +dist/ +atlassian* +keystore/ +tmp/ +bla*.java diff -Nru libjgroups-java-2.7.0.GA/INSTALL.html libjgroups-java-2.12.2.Final/INSTALL.html --- libjgroups-java-2.7.0.GA/INSTALL.html 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/INSTALL.html 2011-10-18 11:22:35.000000000 +0000 @@ -5,7 +5,7 @@ content="text/html; charset=iso-8859-1"> - JGroups 2.2.X Installation + JGroups 2.8.x Installation
  1. jgroups-all.jar: the JGroups library including the demos
  2. -
  3. commons-logging.jar
  4. -
  5. concurrent.jar
  6. -
  7. log4j.jar
  8. -
  9. jmxri.jar: the JMX reference implementation, this is not needed -if running in JDK 5 or higher
  10. jgroups.bat/jgroups.sh: convenience script to start demo programs (set the CLASSPATH etc) - see below
  11. Some sample configuration files, udp.xml, mping.xml etc
    @@ -95,8 +90,6 @@ test cases
  12. xalan.jar : to format the output of the JUnit tests using an XSLT converter to HTML
  13. -
  14. concurrent.jar
  15. -
  16. commons-logging.jar
  17. log4j.jar
  18. etc
  19. @@ -166,7 +159,7 @@
    You should see the following output (more or less) if the class is found: -

    Version: 2.2.8 RC1
    CVS: $Id: INSTALL.html,v 1.9 2006/12/27 10:17:58 belaban Exp $
    History: (see doc/history.txt for details)


    +

    Version: 2.2.8 RC1
    CVS: $Id: INSTALL.html,v 1.11 2009/12/02 13:00:10 belaban Exp $
    History: (see doc/history.txt for details)


    Running the performance tests


    By default, we're running 2 senders with 10000 1K messages each, to do diff -Nru libjgroups-java-2.7.0.GA/JGroups-2.7-branch.iml libjgroups-java-2.12.2.Final/JGroups-2.7-branch.iml --- libjgroups-java-2.7.0.GA/JGroups-2.7-branch.iml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/JGroups-2.7-branch.iml 1970-01-01 00:00:00.000000000 +0000 @@ -1,107 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff -Nru libjgroups-java-2.7.0.GA/JGroups-2.7-branch.ipr libjgroups-java-2.12.2.Final/JGroups-2.7-branch.ipr --- libjgroups-java-2.7.0.GA/JGroups-2.7-branch.ipr 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/JGroups-2.7-branch.ipr 1970-01-01 00:00:00.000000000 +0000 @@ -1,523 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff -Nru libjgroups-java-2.7.0.GA/JGroups-2.7-branch.iws libjgroups-java-2.12.2.Final/JGroups-2.7-branch.iws --- libjgroups-java-2.7.0.GA/JGroups-2.7-branch.iws 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/JGroups-2.7-branch.iws 1970-01-01 00:00:00.000000000 +0000 @@ -1,318 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - localhost - 5050 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff -Nru libjgroups-java-2.7.0.GA/jgroups-pom.xml libjgroups-java-2.12.2.Final/jgroups-pom.xml --- libjgroups-java-2.7.0.GA/jgroups-pom.xml 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/jgroups-pom.xml 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ - - - - 4.0.0 - jgroups - jgroups - JGroups - 2.7.0.GA - http://www.jgroups.org - - src - tests - - - conf - - **/*.xml - - - - - - maven-compiler-plugin - - 1.5 - 1.5 - - functional/**/*.java - - - - - maven-surefire-plugin - - true - - - - - - - jboss - JBoss Inc. Repository - http://repository.jboss.com/maven2/ - - true - - - - - - ant - ant - 1.6.5 - true - - - ant - ant-junit - 1.6.5 - true - - - junit - junit - 3.8.1 - test - - - \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/pom.xml libjgroups-java-2.12.2.Final/pom.xml --- libjgroups-java-2.7.0.GA/pom.xml 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/pom.xml 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,228 @@ + + 4.0.0 + org.jgroups + jgroups + bundle + JGroups + 2.12.2.Final + http://www.jgroups.org + + Reliable cluster communication toolkit + + + + JBoss, a division of Red Hat + http://www.jboss.org + + + + + Bela Ban + belaban@yahoo.com + + + + + + GNU Lesser General Public License 2.1 + http://www.opensource.org/licenses/lgpl-2.1.php + + + + + cvs -d:pserver:anonymous@javagroups.cvs.sourceforge.net:/cvsroot/javagroups + cvs -d:ext:USER@javagroups.cvs.sf.net/cvsroot/javagroups + cvs -d:pserver:anonymous@javagroups.cvs.sourceforge.net:/cvsroot/javagroups + + + + jira + https://jira.jboss.com/jira/browse/JGRP + + + + + jboss-releases-repository + JBoss Releases Repository + https://repository.jboss.org/nexus/service/local/staging/deploy/maven2/ + + + + + + jboss-public-repository-group + JBoss Public Maven Repository Group + https://repository.jboss.org/nexus/content/groups/public/ + default + + true + never + + + true + never + + + + + + + bsh + bsh + 1.2b7 + true + compile + + + log4j + log4j + 1.2.14 + true + compile + + + + + + src + + + conf + + *.xml + + + *-service.xml + + + + ${project.build.outputDirectory}/schema + + + + + maven-compiler-plugin + + 1.6 + 1.6 + + org/jgroups/util/JUnitXMLReporter.java + + + + + + maven-antrun-plugin + + + compile + + run + + + + + + + + + + + + + + + + + + + + + xalan + xalan + 2.7.1 + + + xalan + serializer + 2.7.1 + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.1.1 + true + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + conf/manifest.mf + + + + + org.apache.felix + maven-bundle-plugin + true + + + + schema;version=${project.version}, + org.jgroups;version=${project.version}, + org.jgroups.annotations;version=${project.version}, + org.jgroups.auth;version=${project.version}, + org.jgroups.blocks;version=${project.version}, + org.jgroups.blocks.mux;version=${project.version}, + org.jgroups.blocks.locking;version=${project.version}, + org.jgroups.blocks.executor;version=${project.version}, + org.jgroups.client;version=${project.version}, + org.jgroups.conf;version=${project.version}, + org.jgroups.debug;version=${project.version}, + org.jgroups.demos;version=${project.version}, + org.jgroups.demos.wb;version=${project.version}, + org.jgroups.jmx;version=${project.version}, + org.jgroups.logging;version=${project.version}, + org.jgroups.mux;version=${project.version}, + org.jgroups.persistence;version=${project.version}, + org.jgroups.protocols;version=${project.version}, + org.jgroups.protocols.pbcast;version=${project.version}, + org.jgroups.stack;version=${project.version}, + org.jgroups.util;version=${project.version}, + org.jgroups.tests;version=${project.version} + + + !bsh.*,* + + J2SE-1.6 + + + + + + diff -Nru libjgroups-java-2.7.0.GA/README libjgroups-java-2.12.2.Final/README --- libjgroups-java-2.7.0.GA/README 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/README 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: README,v 1.7 2006/08/09 13:08:02 belaban Exp $ @@ -15,34 +14,33 @@ belaban@yahoo.com -JGroups is a Java package for reliable group communication. It +JGroups is a Java library for reliable group communication. It consists of 3 parts: (1) a socket-like API for application development, (2) a protocol stack, which implements reliable communication, and (3) a set of building blocks, which give the -application/protocol programmer high-level abstractions -(e.g. DistributedHashtable, derived from java.util.Hashtable, which is -similar to Linda/JavaSpaces). +developer high-level abstractions (e.g. ReplicatedHashMap, an +implementation of java.util.Map. -The API (a channel) looks like a socket: there a methods for joining -and leaving groups, sending and receiving messages to/from members, +The API (a channel) looks like a socket: there are methods for joining +and leaving groups, sending and receiving messages, getting the shared group state, and registering for notifications when a member joins, or an existing member leaves or crashes. -The protocol stack is a linked list of protocols, through which each -message has to be passed. Each protocol implements an up() and down() -method, and may modify, reorder, encrypt, fragment/unfragment, drop a -message, or pass it up/down unchanged. The protocol stack is created +The protocol stack is a list of protocols, through which each +message passes. Each protocol implements an up() and down() +method, and may modify, reorder, encrypt, fragment/unfragment, drop, +or pass a message up/down. The protocol stack is created according to a specification given when a channel is created. New protocols can be plugged into the stack easily. -Building blocks hide the channel and provide a higher -abstraction. Example: DistributedHashtable is a subclass of -java.util.Hashtable and overrides all methods that change the -hashtable (clear, put, remove). Those methods are invoked on all -hashtables in the same group simultaneously, so that all hashtables -have the same state. A new hashtable uses a state transfer protocol to -initially obtain the shared group state from an existing member. This -enables replication of data structures across process boundaries. +Building blocks hide the channel and provide a higher abstraction. +Example: ReplicatedHashMap implements java.util.Mapand implements +all methods that change the map (clear(), put(), remove()). +Those methods are invoked on all hashmap instances in the same group +simultaneously, so that all hashmaps have the same state. +A new hashmap uses a state transfer protocol to initially obtain +the shared group state from an existing member. This allows for +replication of data structures across processes. @@ -59,8 +57,8 @@ - Notification service / push technology: receivers subscribe to a channel, senders send data to the channels, channels distribute - data to all receivers subscribed to the channel (see iBus, CastaNet - etc.). Used for example for video distribution, videoconferencing + data to all receivers subscribed to the channel. + Used for example for video distribution, videoconferencing JGroups deliberately models a rather low-level message-oriented @@ -72,13 +70,6 @@ extend/replace classes at will, as the granularity of the system is finer. -JGroups can also be used for the construction of higher level -toolkits/frameworks. Such frameworks should provide a certain -transparency, without, however, preventing extensions to be made. The -principle of creating partly 'opened-up' black boxes is called Open -Implementation (OI, http://www.parc.xerox.com/spl/projects/oi/) and -will be applied both to JGroups and to a further higher level -toolkit. diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/Address.java libjgroups-java-2.12.2.Final/src/org/jgroups/Address.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/Address.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/Address.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: Address.java,v 1.5 2008/01/10 08:06:59 belaban Exp $ package org.jgroups; @@ -14,6 +13,11 @@ * @author Bela Ban */ public interface Address extends Externalizable, Streamable, Comparable
    , Cloneable { // todo: remove Externalizable + // flags used for marshalling + public static final byte NULL = 1 << 0; + public static final byte UUID_ADDR = 1 << 1; + public static final byte IP_ADDR = 1 << 2; + /** * Checks whether this is an address that represents multiple destinations; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/annotations/DeprecatedProperty.java libjgroups-java-2.12.2.Final/src/org/jgroups/annotations/DeprecatedProperty.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/annotations/DeprecatedProperty.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/annotations/DeprecatedProperty.java 2011-10-18 11:22:35.000000000 +0000 @@ -8,7 +8,6 @@ * * * @author Vladimir Blagojevic - * @version $Id: DeprecatedProperty.java,v 1.1 2008/05/29 13:53:04 vlada Exp $ */ @Retention(RetentionPolicy.RUNTIME) diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/annotations/Experimental.java libjgroups-java-2.12.2.Final/src/org/jgroups/annotations/Experimental.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/annotations/Experimental.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/annotations/Experimental.java 2011-10-18 11:22:35.000000000 +0000 @@ -8,9 +8,8 @@ /** * Elements annotated with this annotation are experimental and may get removed from the distribution at any time * @author Bela Ban - * @version $Id: Experimental.java,v 1.3 2008/10/21 08:59:03 vlada Exp $ */ -@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PACKAGE}) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PACKAGE}) @Retention(RetentionPolicy.RUNTIME) public @interface Experimental { String comment() default ""; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/annotations/LocalAddress.java libjgroups-java-2.12.2.Final/src/org/jgroups/annotations/LocalAddress.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/annotations/LocalAddress.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/annotations/LocalAddress.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,18 @@ +package org.jgroups.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This is an assertion, checked at startup time. It ensures that the field this annotation is on is (1) an InetAddress + * and (2) a valid address on a local network interface + * @author Bela Ban + * @since 2.11.2 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface LocalAddress { +} + diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/annotations/ManagedAttribute.java libjgroups-java-2.12.2.Final/src/org/jgroups/annotations/ManagedAttribute.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/annotations/ManagedAttribute.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/annotations/ManagedAttribute.java 2011-10-18 11:22:35.000000000 +0000 @@ -10,7 +10,6 @@ * a superclass. * * @author Chris Mills - * @version $Id: ManagedAttribute.java,v 1.6 2008/03/13 02:00:23 vlada Exp $ */ @Retention(RetentionPolicy.RUNTIME) diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/annotations/ManagedOperation.java libjgroups-java-2.12.2.Final/src/org/jgroups/annotations/ManagedOperation.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/annotations/ManagedOperation.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/annotations/ManagedOperation.java 2011-10-18 11:22:35.000000000 +0000 @@ -10,7 +10,6 @@ * annotation from a superclass. * * @author Chris Mills - * @version $Id: ManagedOperation.java,v 1.4 2008/03/12 00:26:42 vlada Exp $ */ diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/annotations/MBean.java libjgroups-java-2.12.2.Final/src/org/jgroups/annotations/MBean.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/annotations/MBean.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/annotations/MBean.java 2011-10-18 11:22:35.000000000 +0000 @@ -12,7 +12,6 @@ * * * @author Chris Mills - * @version $Id: MBean.java,v 1.6 2008/04/28 13:43:10 vlada Exp $ */ @Retention(RetentionPolicy.RUNTIME) diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/annotations/Property.java libjgroups-java-2.12.2.Final/src/org/jgroups/annotations/Property.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/annotations/Property.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/annotations/Property.java 2011-10-18 11:22:35.000000000 +0000 @@ -29,7 +29,6 @@ * * * @author Vladimir Blagojevic - * @version $Id: Property.java,v 1.5 2008/05/23 11:11:02 belaban Exp $ */ @Retention(RetentionPolicy.RUNTIME) @@ -43,4 +42,26 @@ String deprecatedMessage() default ""; Class converter() default PropertyConverters.Default.class; + + String dependsUpon() default ""; + + String[] systemProperty() default {}; + + /** + * Global.NON_LOOPBACK_ADDRESS means pick any valid non-loopback IPv4 address + */ + String defaultValueIPv4() default "" ; + + /** + * Global.NON_LOOPBACK_ADDRESS means pick any valid non-loopback IPv6 address + */ + String defaultValueIPv6() default "" ; + + /** Expose this property also as a managed attribute */ + boolean exposeAsManagedAttribute() default true; + + /* Should this managed attribute be writeable ? If set to true, automatically sets exposeAsManagedAttribute to true */ + boolean writable() default true; } + + diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/annotations/Unsupported.java libjgroups-java-2.12.2.Final/src/org/jgroups/annotations/Unsupported.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/annotations/Unsupported.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/annotations/Unsupported.java 2011-10-18 11:22:35.000000000 +0000 @@ -8,7 +8,6 @@ /** * Elements annotated with this annotation are unsupported and may get removed from the distribution at any time * @author Bela Ban - * @version $Id: Unsupported.java,v 1.3 2008/10/21 12:10:32 vlada Exp $ */ @Target({ElementType.TYPE, ElementType.METHOD, ElementType.PACKAGE}) @Retention(RetentionPolicy.RUNTIME) diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/auth/AuthToken.java libjgroups-java-2.12.2.Final/src/org/jgroups/auth/AuthToken.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/auth/AuthToken.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/auth/AuthToken.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,29 +1,45 @@ package org.jgroups.auth; -import org.jgroups.util.Streamable; +import java.io.Serializable; + import org.jgroups.Message; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.protocols.AUTH; +import org.jgroups.util.Streamable; -import java.io.Serializable; -import java.util.Properties; /** * Abstract AuthToken class used by implementations of AUTH, e.g. SimpleToken, X509Token + * * @author Chris Mills */ -public abstract class AuthToken implements Serializable, Streamable{ +public abstract class AuthToken implements Serializable, Streamable { protected final Log log = LogFactory.getLog(this.getClass()); - + + /** A reference to AUTH */ + protected transient AUTH auth = null; + + public void setAuth(AUTH auth) { + this.auth = auth; + } + + public void init() {} + /** - * Used to return the full package and class name of the implementation. This is used by the AUTH protocol to create an instance of the implementation. + * Used to return the full package and class name of the implementation. This is used by the + * AUTH protocol to create an instance of the implementation. + * * @return a java.lang.String object of the package and class name */ public abstract String getName(); /** * This method should be implemented to perform the actual authentication of joining members. - * @param token the token sent by the joiner - * @param msg the Message object containing the actual JOIN_REQ + * + * @param token + * the token sent by the joiner + * @param msg + * the Message object containing the actual JOIN_REQ * @return true if authenticaion passed or false if it failed. */ public abstract boolean authenticate(AuthToken token, Message msg); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/auth/FixedMembershipToken.java libjgroups-java-2.12.2.Final/src/org/jgroups/auth/FixedMembershipToken.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/auth/FixedMembershipToken.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/auth/FixedMembershipToken.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,30 +1,26 @@ /* -* JBoss, Home of Professional Open Source -* Copyright 2005, JBoss Inc., and individual contributors as indicated -* by the @authors tag. See the copyright.txt in the distribution for a -* full listing of individual contributors. -* -* This 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 software 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 software; if not, write to the Free -* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -* 02110-1301 USA, or see the FSF site: http://www.fsf.org. -*/ + * JBoss, Home of Professional Open Source + * Copyright 2005, JBoss Inc., and individual contributors as indicated + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This 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 software 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 software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ package org.jgroups.auth; -import org.jgroups.Message; -import org.jgroups.annotations.Property; -import org.jgroups.util.Util; - import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; @@ -32,26 +28,35 @@ import java.util.List; import java.util.StringTokenizer; +import org.jgroups.Event; +import org.jgroups.Message; +import org.jgroups.PhysicalAddress; +import org.jgroups.annotations.Property; +import org.jgroups.util.Util; + /** *

    - * The FixedMemberShipToken object predefines a list of IP addresses and Ports that can join the group. + * The FixedMemberShipToken object predefines a list of IP addresses and ports that can join the + * group. *

    *

    * Configuration parameters for this example are shown below: *

    *
      - *
    • fixed_members_value (required) = List of IP addresses & ports (optionally) - ports must be seperated by a '/' e.g. 127.0.0.1/1010*127.0.0.1/4567
    • + *
    • fixed_members_value (required) = List of IP addresses & ports (optionally) - ports must be + * seperated by a '/' e.g. 127.0.0.1/1010*127.0.0.1/4567
    • *
    • fixed_members_seperator (required) = The seperator used between IP addresses - e.g. *
    • *
    + * * @author Chris Mills (millsy@jboss.com) */ public class FixedMembershipToken extends AuthToken { - - private List memberList=null; - private String token="emptyToken"; + private List memberList = null; + private String token = "emptyToken"; @Property - private String fixed_members_seperator=","; + private String fixed_members_seperator = ","; + private static final long serialVersionUID = 4717069536900221681L; public FixedMembershipToken() { } @@ -60,56 +65,68 @@ return "org.jgroups.auth.FixedMembershipToken"; } + @Property + public void setFixedMembersSeparator(String value) { + fixed_members_seperator = value; + } + public boolean authenticate(AuthToken token, Message msg) { - if((token != null) && (token instanceof FixedMembershipToken) && (this.memberList != null)) { - //Found a valid Token to authenticate against - FixedMembershipToken serverToken=(FixedMembershipToken)token; + if ((token != null) && (token instanceof FixedMembershipToken) && (this.memberList != null)) { + PhysicalAddress src = (PhysicalAddress) auth.down(new Event(Event.GET_PHYSICAL_ADDRESS, + msg.getSrc())); + if (src == null) { + if (log.isErrorEnabled()) + log.error("didn't find physical address for " + msg.getSrc()); + return false; + } - String sourceAddressWithPort=msg.getSrc().toString(); - String sourceAddressWithoutPort=sourceAddressWithPort.substring(0, sourceAddressWithPort.indexOf(":")); + String sourceAddressWithPort = src.toString(); + String sourceAddressWithoutPort = sourceAddressWithPort.substring(0, + sourceAddressWithPort.indexOf(":")); - if(log.isDebugEnabled()) { + if (log.isDebugEnabled()) { log.debug("AUTHToken received from " + sourceAddressWithPort); } - if((this.memberList.contains(sourceAddressWithPort)) || (this.memberList.contains(sourceAddressWithoutPort))) { - //validated - if(log.isDebugEnabled()) { - log.debug("FixedMembershipToken match"); + for (String member : memberList) { + if (hasPort(member)) { + if (member.equals(sourceAddressWithPort)) + return true; + } else { + if (member.equals(sourceAddressWithoutPort)) + return true; } - return true; - } - else { -// if(log.isWarnEnabled()){ -// log.warn("Authentication failed on FixedMembershipToken"); -// } - return false; } + return false; } - if(log.isWarnEnabled()) { + if (log.isWarnEnabled()) { log.warn("Invalid AuthToken instance - wrong type or null"); } return false; } - @Property(name="fixed_members_value") + private static boolean hasPort(String member) { + return member.contains(":"); + } + + @Property(name = "fixed_members_value") public void setMemberList(String list) { - memberList=new ArrayList(); - StringTokenizer memberListTokenizer=new StringTokenizer(list, fixed_members_seperator); - while(memberListTokenizer.hasMoreTokens()) { + memberList = new ArrayList(); + StringTokenizer memberListTokenizer = new StringTokenizer(list, fixed_members_seperator); + while (memberListTokenizer.hasMoreTokens()) { memberList.add(memberListTokenizer.nextToken().replace('/', ':')); } } - /** * Required to serialize the object to pass across the wire + * * @param out * @throws java.io.IOException */ public void writeTo(DataOutputStream out) throws IOException { - if(log.isDebugEnabled()) { + if (log.isDebugEnabled()) { log.debug("SimpleToken writeTo()"); } Util.writeString(this.token, out); @@ -117,15 +134,17 @@ /** * Required to deserialize the object when read in from the wire + * * @param in * @throws IOException * @throws IllegalAccessException * @throws InstantiationException */ - public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { - if(log.isDebugEnabled()) { + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, + InstantiationException { + if (log.isDebugEnabled()) { log.debug("SimpleToken readFrom()"); } - this.token=Util.readString(in); + this.token = Util.readString(in); } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/auth/MD5Token.java libjgroups-java-2.12.2.Final/src/org/jgroups/auth/MD5Token.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/auth/MD5Token.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/auth/MD5Token.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,56 +1,58 @@ package org.jgroups.auth; -import org.jgroups.Message; -import org.jgroups.annotations.Property; -import org.jgroups.util.Util; - import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.util.Properties; + +import org.jgroups.Message; +import org.jgroups.annotations.Property; +import org.jgroups.util.Util; /** *

    - * This is an example of using a preshared token that is encrypted using an MD5/SHA hash for authentication purposes. All members of the group have to have the same string value in the JGroups config. + * This is an example of using a preshared token that is encrypted using an MD5/SHA hash for + * authentication purposes. All members of the group have to have the same string value in the + * JGroups config. *

    *

    * Configuration parameters for this example are shown below: *

    *
      - *
    • token_hash (required) = MD5(default)/SHA
    • - *
    • auth_value (required) = the string to encrypt
    • + *
    • token_hash (required) = MD5(default)/SHA
    • + *
    • auth_value (required) = the string to encrypt
    • *
    + * * @see org.jgroups.auth.AuthToken * @author Chris Mills */ public class MD5Token extends AuthToken { @Property - private String auth_value= null; + private String auth_value = null; - @Property(name="token_hash") + @Property(name = "token_hash") private String hash_type = "MD5"; + private static final long serialVersionUID = -5787154335375249191L; - public MD5Token(){ - //need an empty constructor + public MD5Token() { + // need an empty constructor } - public MD5Token(String authvalue){ - this.auth_value= hash(authvalue); + public MD5Token(String authvalue) { + this.auth_value = hash(authvalue); } - public MD5Token(String authvalue, String hash_type){ - this.auth_value= hash(authvalue); + public MD5Token(String authvalue, String hash_type) { + this.auth_value = hash(authvalue); this.hash_type = hash_type; } - public String getHashType() { return hash_type; } public void setHashType(String hash_type) { - this.hash_type=hash_type; + this.hash_type = hash_type; } public String getAuthValue() { @@ -58,33 +60,33 @@ } public void setAuthValue(String auth_value) { - this.auth_value=auth_value; + this.auth_value = auth_value; } - public String getName(){ + public String getName() { return "org.jgroups.auth.MD5Token"; } - - /** * Called during setup to hash the auth_value string in to an MD5/SHA hash - * @param token the string to hash + * + * @param token + * the string to hash * @return the hashed version of the string */ - private String hash(String token){ - //perform the hashing of the token key + private String hash(String token) { + // perform the hashing of the token key String hashedToken = null; - if(hash_type.equalsIgnoreCase("SHA")){ + if (hash_type.equalsIgnoreCase("SHA")) { hashedToken = Util.sha(token); - }else{ + } else { hashedToken = Util.md5(token); } - if(hashedToken == null){ - //failed to encrypt - if(log.isWarnEnabled()){ + if (hashedToken == null) { + // failed to encrypt + if (log.isWarnEnabled()) { log.warn("Failed to hash token - sending in clear text"); } return token; @@ -92,43 +94,45 @@ return hashedToken; } - public boolean authenticate(AuthToken token, Message msg){ + public boolean authenticate(AuthToken token, Message msg) { - if((token != null) && (token instanceof MD5Token)){ - //Found a valid Token to authenticate against + if ((token != null) && (token instanceof MD5Token)) { + // Found a valid Token to authenticate against MD5Token serverToken = (MD5Token) token; - if((this.auth_value != null) && (serverToken.auth_value != null) && (this.auth_value.equalsIgnoreCase(serverToken.auth_value))){ - //validated - if(log.isDebugEnabled()){ + if ((this.auth_value != null) && (serverToken.auth_value != null) + && (this.auth_value.equalsIgnoreCase(serverToken.auth_value))) { + // validated + if (log.isDebugEnabled()) { log.debug("MD5Token match"); } return true; - }else{ -// if(log.isWarnEnabled()){ -// log.warn("Authentication failed on MD5Token"); -// } + } else { + // if(log.isWarnEnabled()){ + // log.warn("Authentication failed on MD5Token"); + // } return false; } } - if(log.isWarnEnabled()){ + if (log.isWarnEnabled()) { log.warn("Invalid AuthToken instance - wrong type or null"); } return false; } public void writeTo(DataOutputStream out) throws IOException { - if(log.isDebugEnabled()){ + if (log.isDebugEnabled()) { log.debug("MD5Token writeTo()"); } Util.writeString(this.auth_value, out); } - public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { - if(log.isDebugEnabled()){ + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, + InstantiationException { + if (log.isDebugEnabled()) { log.debug("MD5Token readFrom()"); } - this.auth_value= Util.readString(in); + this.auth_value = Util.readString(in); } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/auth/RegexMembership.java libjgroups-java-2.12.2.Final/src/org/jgroups/auth/RegexMembership.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/auth/RegexMembership.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/auth/RegexMembership.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,126 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2005, JBoss Inc., and individual contributors as indicated + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This 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 software 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 software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jgroups.auth; + +import org.jgroups.Address; +import org.jgroups.Event; +import org.jgroups.Message; +import org.jgroups.PhysicalAddress; +import org.jgroups.annotations.Property; +import org.jgroups.util.UUID; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Matches the IP address or logical name of a joiner against a regular expression and accepts or rejects based on + * pattern matching + * @author Bela Ban + */ +public class RegexMembership extends AuthToken { + + @Property(description="The regular expression against which the IP address or logical host of a joiner will be matched") + protected String match_string=null; + + @Property(description="Matches the IP address of the joiner against the match string") + protected boolean match_ip_address=true; + + @Property(description="Matches the logical name of the joiner against the match string") + protected boolean match_logical_name=false; + + + // ------------------------------------------- Fields ------------------------------------------------------ // + + protected Pattern pattern; + + private static final long serialVersionUID=4717069536900221681L; + + + + + public RegexMembership() { + } + + + public String getName() { + return "org.jgroups.auth.RegexMembership"; + } + + + public void init() { + super.init(); + if(!match_ip_address && !match_logical_name) + throw new IllegalArgumentException("either match_ip_address or match_logical_address has to be true"); + if(match_string == null) + throw new IllegalArgumentException("match_string cannot be null"); + pattern=Pattern.compile(match_string); + } + + + public boolean authenticate(AuthToken token, Message msg) { + Address sender=msg.getSrc(); + + + if(match_ip_address) { + PhysicalAddress src=sender != null? (PhysicalAddress)auth.down(new Event(Event.GET_PHYSICAL_ADDRESS, sender)) : null; + String ip_addr=src != null? src.toString() : null; + if(ip_addr != null) { + Matcher matcher=pattern.matcher(ip_addr); + boolean result=matcher.matches(); + if(log.isTraceEnabled()) + log.trace("matching ip_address: pattern= " + pattern + ", input= " + ip_addr + ", result= " + result); + if(result) + return true; + } + } + if(match_logical_name) { + String logical_name=sender != null? UUID.get(sender) : null; + if(logical_name != null) { + Matcher matcher=pattern.matcher(logical_name); + boolean result=matcher.matches(); + if(log.isTraceEnabled()) + log.trace("matching logical_name: pattern= " + pattern + ", input= " + logical_name + ", result= " + result); + if(result) + return true; + } + } + return false; + } + + + + public void writeTo(DataOutputStream out) throws IOException { + } + + /** + * Required to deserialize the object when read in from the wire + * @param in + * @throws java.io.IOException + * @throws IllegalAccessException + * @throws InstantiationException + */ + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/auth/SimpleToken.java libjgroups-java-2.12.2.Final/src/org/jgroups/auth/SimpleToken.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/auth/SimpleToken.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/auth/SimpleToken.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,38 +1,41 @@ package org.jgroups.auth; -import org.jgroups.Message; -import org.jgroups.annotations.Property; -import org.jgroups.util.Util; - import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.util.Properties; + +import org.jgroups.Message; +import org.jgroups.annotations.Property; +import org.jgroups.util.Util; /** *

    - * This is an example of using a preshared token for authentication purposes. All members of the group have to have the same string value in the JGroups config. + * This is an example of using a preshared token for authentication purposes. All members of the + * group have to have the same string value in the JGroups config. + *

    + *

    + * JGroups config parameters: *

    - *

    JGroups config parameters:

    *
      *
    • auth_value (required) = the string to encrypt
    • *
    + * * @author Chris Mills * @see org.jgroups.auth.AuthToken */ public class SimpleToken extends AuthToken { @Property - private String auth_value=null; + private String auth_value = null; + private static final long serialVersionUID = 5020668015439045326L; public SimpleToken() { // need an empty constructor } public SimpleToken(String authvalue) { - this.auth_value=authvalue; + this.auth_value = authvalue; } - public String getName() { return "org.jgroups.auth.SimpleToken"; } @@ -42,30 +45,30 @@ } public void setAuthValue(String auth_value) { - this.auth_value=auth_value; + this.auth_value = auth_value; } public boolean authenticate(AuthToken token, Message msg) { - if((token != null) && (token instanceof SimpleToken)) { - //Found a valid Token to authenticate against - SimpleToken serverToken=(SimpleToken)token; - - if((this.auth_value != null) && (serverToken.auth_value != null) && (this.auth_value.equalsIgnoreCase(serverToken.auth_value))) { - //validated - if(log.isDebugEnabled()) { + if ((token != null) && (token instanceof SimpleToken)) { + // Found a valid Token to authenticate against + SimpleToken serverToken = (SimpleToken) token; + + if ((this.auth_value != null) && (serverToken.auth_value != null) + && (this.auth_value.equalsIgnoreCase(serverToken.auth_value))) { + // validated + if (log.isDebugEnabled()) { log.debug("SimpleToken match"); } return true; - } - else { - //if(log.isWarnEnabled()){ - // log.warn("Authentication failed on SimpleToken"); - //} + } else { + // if(log.isWarnEnabled()){ + // log.warn("Authentication failed on SimpleToken"); + // } return false; } } - if(log.isWarnEnabled()) { + if (log.isWarnEnabled()) { log.warn("Invalid AuthToken instance - wrong type or null"); } return false; @@ -73,11 +76,12 @@ /** * Required to serialize the object to pass across the wire + * * @param out * @throws IOException */ public void writeTo(DataOutputStream out) throws IOException { - if(log.isDebugEnabled()) { + if (log.isDebugEnabled()) { log.debug("SimpleToken writeTo()"); } Util.writeString(this.auth_value, out); @@ -85,15 +89,17 @@ /** * Required to deserialize the object when read in from the wire + * * @param in * @throws IOException * @throws IllegalAccessException * @throws InstantiationException */ - public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { - if(log.isDebugEnabled()) { + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, + InstantiationException { + if (log.isDebugEnabled()) { log.debug("SimpleToken readFrom()"); } - this.auth_value=Util.readString(in); + this.auth_value = Util.readString(in); } -} +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/auth/X509Token.java libjgroups-java-2.12.2.Final/src/org/jgroups/auth/X509Token.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/auth/X509Token.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/auth/X509Token.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,185 +1,202 @@ package org.jgroups.auth; -import org.jgroups.Message; -import org.jgroups.annotations.Property; -import org.jgroups.util.Util; - -import javax.crypto.Cipher; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.BadPaddingException; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.io.FileNotFoundException; -import java.security.*; -import java.security.cert.X509Certificate; +import java.security.InvalidKeyException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.UnrecoverableEntryException; import java.security.cert.CertificateException; -import java.util.Properties; +import java.security.cert.X509Certificate; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; + +import org.jgroups.Message; +import org.jgroups.annotations.Property; +import org.jgroups.util.Util; /** *

    - * This is an example of using a preshared token that is encrypted using an X509 certificate for authentication purposes. All members of the group have to have the same string value in the JGroups config. + * This is an example of using a preshared token that is encrypted using an X509 certificate for + * authentication purposes. All members of the group have to have the same string value in the + * JGroups config. *

    *

    - * This example uses certificates contained within a specified keystore. Configuration parameters for this example are shown below: + * This example uses certificates contained within a specified keystore. Configuration parameters + * for this example are shown below: *

    *
      - *
    • keystore_type = JKS(default)/PKCS12 - see http://java.sun.com/j2se/1.4.2/docs/guide/security/CryptoSpec.html#AppA
    • + *
    • keystore_type = JKS(default)/PKCS12 - see + * http://java.sun.com/j2se/1.4.2/docs/guide/security/CryptoSpec.html#AppA
    • *
    • keystore_path (required) = the location of the keystore
    • - *
    • keystore_password (required) = the password of the keystore
    • + *
    • keystore_password (required) = the password of the keystore
    • *
    • cert_alias (required) = the alias of the certification within the keystore
    • *
    • cert_password = the password of the certification within the keystore
    • *
    • auth_value (required) = the string to encrypt
    • - *
    • cipher_type = RSA(default)/AES/Blowfish/DES/DESede/PBEWithMD5AndDES/PBEWithHmacSHA1AndDESede/RC2/RC4/RC5 - see http://java.sun.com/j2se/1.4.2/docs/guide/security/jce/JCERefGuide.html#AppA
    • + *
    • cipher_type = + * RSA(default)/AES/Blowfish/DES/DESede/PBEWithMD5AndDES/PBEWithHmacSHA1AndDESede/RC2/RC4/RC5 - see + * http://java.sun.com/j2se/1.4.2/docs/guide/security/jce/JCERefGuide.html#AppA
    • *
    + * * @author Chris Mills * @see org.jgroups.auth.AuthToken */ public class X509Token extends AuthToken { - public static final String KEYSTORE_TYPE="keystore_type"; - public static final String KEYSTORE_PATH="keystore_path"; - public static final String KEYSTORE_PASSWORD="keystore_password"; - public static final String CERT_ALIAS="cert_alias"; - public static final String CERT_PASSWORD="cert_password"; - public static final String TOKEN_ATTR="auth_value"; - public static final String CIPHER_TYPE="cipher_type"; + public static final String KEYSTORE_TYPE = "keystore_type"; + public static final String KEYSTORE_PATH = "keystore_path"; + public static final String KEYSTORE_PASSWORD = "keystore_password"; + public static final String CERT_ALIAS = "cert_alias"; + public static final String CERT_PASSWORD = "cert_password"; + public static final String TOKEN_ATTR = "auth_value"; + public static final String CIPHER_TYPE = "cipher_type"; - private boolean valueSet=false; + private boolean valueSet = false; @Property - private String keystore_type="JKS"; + private String keystore_type = "JKS"; @Property - private String cert_alias=null; + private String cert_alias = null; @Property - private String keystore_path=null; + private String keystore_path = null; @Property - private String auth_value=null; + private String auth_value = null; @Property - private String cipher_type="RSA"; + private String cipher_type = "RSA"; - private byte[] encryptedToken=null; + private byte[] encryptedToken = null; - private char[] cert_password=null; - private char[] keystore_password=null; + private char[] cert_password = null; + private char[] keystore_password = null; - private Cipher cipher=null; - private PrivateKey certPrivateKey=null; - private X509Certificate certificate=null; + private Cipher cipher = null; + private PrivateKey certPrivateKey = null; + private X509Certificate certificate = null; + private static final long serialVersionUID = -514501306160844271L; public X509Token() { - //need an empty constructor + // need an empty constructor } - - @Property(name="cert_password") + @Property(name = "cert_password") public void setCertPassword(String pwd) { - this.cert_password=pwd.toCharArray(); + this.cert_password = pwd.toCharArray(); } - @Property(name="keystore_password") + @Property(name = "keystore_password") public void setKeyStorePassword(String pwd) { - this.keystore_password=pwd.toCharArray(); - if(cert_password == null) - cert_password=keystore_password; + this.keystore_password = pwd.toCharArray(); + if (cert_password == null) + cert_password = keystore_password; } - - public String getName() { return "org.jgroups.auth.X509Token"; } public boolean authenticate(AuthToken token, Message msg) { - if(!this.valueSet) { - if(log.isFatalEnabled()) { + if (!this.valueSet) { + if (log.isFatalEnabled()) { log.fatal("X509Token not setup correctly - check token attrs"); } return false; } - if((token != null) && (token instanceof X509Token)) { - //got a valid X509 token object - X509Token serverToken=(X509Token)token; - if(!serverToken.valueSet) { - if(log.isFatalEnabled()) { + if ((token != null) && (token instanceof X509Token)) { + // got a valid X509 token object + X509Token serverToken = (X509Token) token; + if (!serverToken.valueSet) { + if (log.isFatalEnabled()) { log.fatal("X509Token - recieved token not valid"); } return false; } try { - if(log.isDebugEnabled()) { + if (log.isDebugEnabled()) { log.debug("setting cipher to decrypt mode"); } this.cipher.init(Cipher.DECRYPT_MODE, this.certPrivateKey); - String serverBytes=new String(this.cipher.doFinal(serverToken.encryptedToken)); - if((serverBytes.equalsIgnoreCase(this.auth_value))) { - if(log.isDebugEnabled()) { + String serverBytes = new String(this.cipher.doFinal(serverToken.encryptedToken)); + if ((serverBytes.equalsIgnoreCase(this.auth_value))) { + if (log.isDebugEnabled()) { log.debug("X509 authentication passed"); } return true; } - } - catch(Exception e) { - if(log.isFatalEnabled()) { - log.fatal(e); + } catch (Exception e) { + if (log.isFatalEnabled()) { + log.fatal(e.toString()); } } } -// if(log.isWarnEnabled()){ -// log.warn("X509 authentication failed"); -// } + // if(log.isWarnEnabled()){ + // log.warn("X509 authentication failed"); + // } return false; } public void writeTo(DataOutputStream out) throws IOException { - if(log.isDebugEnabled()) { + if (log.isDebugEnabled()) { log.debug("X509Token writeTo()"); } Util.writeByteBuffer(this.encryptedToken, out); } - public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { - if(log.isDebugEnabled()) { + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, + InstantiationException { + if (log.isDebugEnabled()) { log.debug("X509Token readFrom()"); } - this.encryptedToken=Util.readByteBuffer(in); - this.valueSet=true; + this.encryptedToken = Util.readByteBuffer(in); + this.valueSet = true; } /** - * Used during setup to get the certification from the keystore and encrypt the auth_value with the private key - * @return true if the certificate was found and the string encypted correctly otherwise returns false + * Used during setup to get the certification from the keystore and encrypt the auth_value with + * the private key + * + * @return true if the certificate was found and the string encypted correctly otherwise returns + * false */ - public void setCertificate() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnrecoverableEntryException { - KeyStore store=KeyStore.getInstance(this.keystore_type); - java.io.FileInputStream fis=new java.io.FileInputStream(this.keystore_path); + public void setCertificate() throws KeyStoreException, IOException, NoSuchAlgorithmException, + CertificateException, NoSuchPaddingException, InvalidKeyException, + IllegalBlockSizeException, BadPaddingException, UnrecoverableEntryException { + KeyStore store = KeyStore.getInstance(this.keystore_type); + java.io.FileInputStream fis = new java.io.FileInputStream(this.keystore_path); store.load(fis, this.keystore_password); - this.cipher=Cipher.getInstance(this.cipher_type); - this.certificate=(X509Certificate)store.getCertificate(this.cert_alias); + this.cipher = Cipher.getInstance(this.cipher_type); + this.certificate = (X509Certificate) store.getCertificate(this.cert_alias); - if(log.isDebugEnabled()) { + if (log.isDebugEnabled()) { log.debug("certificate = " + this.certificate.toString()); } this.cipher.init(Cipher.ENCRYPT_MODE, this.certificate); - this.encryptedToken=this.cipher.doFinal(this.auth_value.getBytes()); + this.encryptedToken = this.cipher.doFinal(this.auth_value.getBytes()); - if(log.isDebugEnabled()) { + if (log.isDebugEnabled()) { log.debug("encryptedToken = " + this.encryptedToken); } - KeyStore.PrivateKeyEntry privateKey=(KeyStore.PrivateKeyEntry)store.getEntry(this.cert_alias, new KeyStore.PasswordProtection(this.cert_password)); - this.certPrivateKey=privateKey.getPrivateKey(); + KeyStore.PrivateKeyEntry privateKey = (KeyStore.PrivateKeyEntry) store.getEntry( + this.cert_alias, new KeyStore.PasswordProtection(this.cert_password)); + this.certPrivateKey = privateKey.getPrivateKey(); + + this.valueSet=true; - if(log.isDebugEnabled()) { + if (log.isDebugEnabled()) { log.debug("certPrivateKey = " + this.certPrivateKey.toString()); } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/BlockEvent.java libjgroups-java-2.12.2.Final/src/org/jgroups/BlockEvent.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/BlockEvent.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/BlockEvent.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: BlockEvent.java,v 1.2 2005/07/17 11:38:05 chrislott Exp $ package org.jgroups; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/AbstractConnectionMap.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/AbstractConnectionMap.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/AbstractConnectionMap.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/AbstractConnectionMap.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,22 +1,17 @@ package org.jgroups.blocks; -import java.io.IOException; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Vector; -import java.util.Map.Entry; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.Address; import org.jgroups.Global; import org.jgroups.util.ThreadFactory; import org.jgroups.util.Util; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + public abstract class AbstractConnectionMap implements ConnectionMap { protected final Vector> conn_listeners=new Vector>(); @@ -70,7 +65,8 @@ public void addConnection(Address address, V conn) { lock.lock(); try { - conns.put(address, conn); + V previous = conns.put(address, conn); + Util.close(previous); } finally { lock.unlock(); @@ -93,6 +89,22 @@ } } + public String printConnections() { + StringBuilder sb=new StringBuilder(); + + lock.lock(); + try { + for(Map.Entry entry: conns.entrySet()) { + sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); + } + } + finally { + lock.unlock(); + } + + return sb.toString(); + } + public ThreadFactory getThreadFactory() { return factory; } @@ -115,8 +127,7 @@ } /** - * Removes all connections from ConnectionTable which are not in - * current_mbrs + * Removes all connections which are not in current_mbrs * * @param current_mbrs */ @@ -157,12 +168,12 @@ for(Iterator> i = conns.entrySet().iterator();i.hasNext();) { Entry e = i.next(); Util.close(e.getValue()); - } + } + clear(); } finally { lock.unlock(); - } - clear(); + } conn_listeners.clear(); } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/BasicConnectionTable.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/BasicConnectionTable.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/BasicConnectionTable.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/BasicConnectionTable.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,15 +1,12 @@ package org.jgroups.blocks; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.Address; import org.jgroups.Global; import org.jgroups.Version; import org.jgroups.stack.IpAddress; -import org.jgroups.util.DefaultThreadFactory; -import org.jgroups.util.PortsManager; -import org.jgroups.util.ThreadFactory; -import org.jgroups.util.Util; +import org.jgroups.util.*; import java.io.*; import java.net.InetAddress; @@ -53,8 +50,7 @@ volatile ServerSocket srv_sock=null; boolean tcp_nodelay=false; int linger=-1; - - protected PortsManager pm=null; + protected SocketFactory socket_factory=new DefaultSocketFactory(); /** * The address which will be broadcast to the group (the externally visible address which this host should @@ -69,6 +65,8 @@ final static long MAX_JOIN_TIMEOUT=Global.THREAD_SHUTDOWN_WAIT_TIME; + + protected BasicConnectionTable() { factory = new DefaultThreadFactory(new ThreadGroup(Util.getGlobalThreadGroup(),"ConnectionTable"),"Connection Table", false); } @@ -156,6 +154,14 @@ return factory; } + public SocketFactory getSocketFactory() { + return socket_factory; + } + + public void setSocketFactory(SocketFactory socket_factory) { + this.socket_factory=socket_factory; + } + public boolean getUseSendQueues() {return use_send_queues;} public void setUseSendQueues(boolean flag) {this.use_send_queues=flag;} @@ -182,13 +188,9 @@ // 2. close the server socket (this also stops the acceptor thread) if(srv_sock != null) { try { - if(pm != null) { - int tmp_port=srv_sock.getLocalPort(); - pm.updatePort(tmp_port); - } ServerSocket tmp=srv_sock; srv_sock=null; - tmp.close(); + socket_factory.close(tmp); if(acceptor != null) Util.interruptAndWaitToDie(acceptor); } @@ -257,7 +259,7 @@ entry=it.next(); key=entry.getKey(); val=entry.getValue(); - ret.append("key: " + key + ": " + val + '\n'); + ret.append(key + ": " + val + '\n'); } ret.append('\n'); return ret.toString(); @@ -572,7 +574,7 @@ initCookie(input_cookie); // read the cookie first - in.read(input_cookie, 0, input_cookie.length); + in.readFully(input_cookie, 0, input_cookie.length); if(!matchCookie(input_cookie)) throw new SocketException("ConnectionTable.Connection.readPeerAddress(): cookie sent by " + client_peer_addr + " does not match own cookie; terminating connection"); @@ -583,7 +585,7 @@ if(log.isWarnEnabled()) log.warn(new StringBuilder("packet from ").append(client_addr).append(':').append(client_port). append(" has different version (").append(Version.print(version)).append(") from ours ("). - append(Version.printVersion()).append("). This may cause problems")); + append(Version.printVersion()).append("). This may cause problems").toString()); } client_peer_addr=new IpAddress(); client_peer_addr.readFrom(in); @@ -646,18 +648,14 @@ public void run() { - byte[] buf=new byte[256]; // start with 256, increase as we go - int len=0; - while(receiverThread != null && receiverThread.equals(Thread.currentThread()) && is_running) { try { if(in == null) { if(log.isErrorEnabled()) log.error("input stream is null !"); break; } - len=in.readInt(); - if(len > buf.length) - buf=new byte[len]; + int len=in.readInt(); + byte[] buf=new byte[len]; in.readFully(buf, 0, len); updateLastAccessed(); receive(peer_addr, buf, 0, len); // calls receiver.receive(msg) @@ -668,7 +666,7 @@ } catch(IOException io_ex) { //this is very common occurrence, hence log under trace level - if(log.isTraceEnabled()) log.trace("Excption while read blocked for data from peer ", io_ex); + if(log.isTraceEnabled()) log.trace("Exception while read blocked for data from peer ", io_ex); notifyConnectionClosed(peer_addr); break; } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/Cache.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/Cache.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/Cache.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/Cache.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,11 +1,12 @@ package org.jgroups.blocks; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.annotations.Experimental; import org.jgroups.annotations.Unsupported; import org.jgroups.annotations.ManagedAttribute; import org.jgroups.annotations.ManagedOperation; +import org.jgroups.util.Util; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; @@ -16,17 +17,18 @@ * Simple cache which maintains keys and value. A reaper can be enabled which periodically evicts expired entries. * Also, when the cache is configured to be bounded, entries in excess of the max size will be evicted on put(). * @author Bela Ban - * @version $Id: Cache.java,v 1.10 2008/09/03 10:38:25 belaban Exp $ */ @Experimental @Unsupported public class Cache { private static final Log log=LogFactory.getLog(Cache.class); - private final ConcurrentMap> map=new ConcurrentHashMap>(); + private final ConcurrentMap> map=Util.createConcurrentMap(); private ScheduledThreadPoolExecutor timer=new ScheduledThreadPoolExecutor(1); private Future task=null; private final AtomicBoolean is_reaping=new AtomicBoolean(false); + private Set change_listeners=new HashSet(); + /** The maximum number of keys, When this value is exceeded we evict older entries, until we drop below this * mark again. This effectively maintains a bounded cache. A value of 0 means don't bound the cache. */ @@ -41,6 +43,14 @@ this.max_num_entries=max_num_entries; } + public void addChangeListener(ChangeListener l) { + change_listeners.add(l); + } + + public void removeChangeListener(ChangeListener l) { + change_listeners.remove(l); + } + @ManagedAttribute public int getSize() { return map.size(); @@ -93,7 +103,7 @@ public V put(K key, V val, long caching_time) { if(log.isTraceEnabled()) log.trace("put(" + key + ", " + val + ", " + caching_time + ")"); - Value value=new Value(val, caching_time <= 0? caching_time : System.currentTimeMillis() + caching_time); + Value value=new Value(val, caching_time); Value retval=map.put(key, value); if(max_num_entries > 0 && map.size() > max_num_entries) { @@ -149,14 +159,23 @@ Value val=map.get(key); if(val == null) return null; - if(val.expiration_time == -1 || - (val.expiration_time > 0 && val.expiration_time < System.currentTimeMillis())) { + if(val.timeout == -1 || + (val.timeout > 0 && val.timeout < System.currentTimeMillis())) { map.remove(key); return null; } return val.value; } + /** + * This method should not be used to add or remove elements ! It was just added because ReplCacheDemo + * requires it for its data model + * @return + */ + public ConcurrentMap> getInternalMap() { + return map; + } + public Value getEntry(K key) { if(log.isTraceEnabled()) log.trace("getEntry(" + key + ")"); @@ -181,7 +200,7 @@ Value val=entry.getValue(); sb.append(entry.getKey()).append(": ").append(entry.getValue().getValue()); sb.append(" (expiration_time: "); - long expiration_time=val.getExpirationTime(); + long expiration_time=val.getTimeout(); if(expiration_time <= 0) sb.append(expiration_time); else { @@ -210,17 +229,33 @@ } private void evict() { + boolean evicted=false; for(Iterator>> it=map.entrySet().iterator(); it.hasNext();) { Map.Entry> entry=it.next(); Value val=entry.getValue(); if(val != null) { - if(val.expiration_time == -1 || (val.expiration_time > 0 && System.currentTimeMillis() > val.expiration_time)) { + if(val.timeout == -1 || (val.timeout > 0 && System.currentTimeMillis() > val.insertion_time + val.timeout)) { if(log.isTraceEnabled()) log.trace("evicting " + entry.getKey() + ": " + entry.getValue().value); it.remove(); + evicted=true; } } } + if(evicted) + notifyChangeListeners(); + } + + private void notifyChangeListeners() { + for(ChangeListener l: change_listeners) { + try { + l.changed(); + } + catch(Throwable t) { + if(log.isErrorEnabled()) + log.error("failed notifying change listener", t); + } + } } @@ -231,13 +266,13 @@ private long insertion_time=System.currentTimeMillis(); /** When the value can be reaped (in ms) */ - private transient long expiration_time; + private transient long timeout; private static final long serialVersionUID=-3445944261826378608L; - public Value(V value, long expiration_time) { + public Value(V value, long timeout) { this.value=value; - this.expiration_time=expiration_time; + this.timeout=timeout; } public Value() { @@ -245,16 +280,16 @@ public V getValue() {return value;} public long getInsertionTime() {return insertion_time;} - public long getExpirationTime() {return expiration_time;} + public long getTimeout() {return timeout;} public void writeExternal(ObjectOutput out) throws IOException { - out.writeLong(expiration_time); + out.writeLong(timeout); out.writeObject(value); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { insertion_time=System.currentTimeMillis(); - expiration_time=in.readLong(); + timeout=in.readLong(); value=(V)in.readObject(); } } @@ -267,4 +302,8 @@ } } + public interface ChangeListener { + void changed(); + } + } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/ConnectionTable.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/ConnectionTable.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/ConnectionTable.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/ConnectionTable.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,373 +0,0 @@ -// $Id: ConnectionTable.java,v 1.67 2008/10/22 08:51:13 belaban Exp $ - -package org.jgroups.blocks; - -import org.jgroups.Address; -import org.jgroups.util.PortsManager; -import org.jgroups.stack.IpAddress; - -import java.io.IOException; -import java.net.*; - - -/** - * Manages incoming and outgoing TCP connections. For each outgoing message to destination P, if there - * is not yet a connection for P, one will be created. Subsequent outgoing messages will use this - * connection. For incoming messages, one server socket is created at startup. For each new incoming - * client connecting, a new thread from a thread pool is allocated and listens for incoming messages - * until the socket is closed by the peer.
    Sockets/threads with no activity will be killed - * after some time. - *

    - * Incoming messages from any of the sockets can be received by setting the message listener. - * @author Bela Ban - */ -public class ConnectionTable extends BasicConnectionTable implements Runnable { - - /** - * Regular ConnectionTable without expiration of idle connections - * @param srv_port The port on which the server will listen. If this port is reserved, the next - * free port will be taken (incrementing srv_port). - */ - public ConnectionTable(int srv_port) throws Exception { - this.srv_port=srv_port; - init(); - } - - - public ConnectionTable(InetAddress bind_addr, int srv_port) throws Exception { - this.srv_port=srv_port; - this.bind_addr=bind_addr; - init(); - } - - - /** - * ConnectionTable including a connection reaper. Connections that have been idle for more than conn_expire_time - * milliseconds will be closed and removed from the connection table. On next access they will be re-created. - * @param srv_port The port on which the server will listen - * @param reaper_interval Number of milliseconds to wait for reaper between attepts to reap idle connections - * @param conn_expire_time Number of milliseconds a connection can be idle (no traffic sent or received until - * it will be reaped - */ - public ConnectionTable(int srv_port, long reaper_interval, long conn_expire_time) throws Exception { - this.srv_port=srv_port; - this.reaper_interval=reaper_interval; - this.conn_expire_time=conn_expire_time; - use_reaper=true; - init(); - } - - - public ConnectionTable(Receiver r, InetAddress bind_addr, InetAddress external_addr, - int srv_port, int max_port) throws Exception { - setReceiver(r); - this.bind_addr=bind_addr; - this.external_addr=external_addr; - this.srv_port=srv_port; - this.max_port=max_port; - init(); - } - - - /** - * Create a ConnectionTable - * @param r A reference to a receiver of all messages received by this class. Method receive() - * will be called. - * @param bind_addr The host name or IP address of the interface to which the server socket will bind. - * This is interesting only in multi-homed systems. If bind_addr is null, the - * server socket will bind to the first available interface (e.g. /dev/hme0 on - * Solaris or /dev/eth0 on Linux systems). - * @param external_addr The address which will be broadcast to the group (the externally visible address - * which this host should be contacted on). If external_addr is null, it will default to - * the same address that the server socket is bound to. - * @param srv_port The port to which the server socket will bind to. If this port is reserved, the next - * free port will be taken (incrementing srv_port). - * @param max_port The largest port number that the server socket will be bound to. If max_port < srv_port - * then there is no limit. - */ - public ConnectionTable(Receiver r, InetAddress bind_addr, InetAddress external_addr, - int srv_port, int max_port, PortsManager pm) throws Exception { - setReceiver(r); - this.bind_addr=bind_addr; - this.external_addr=external_addr; - this.srv_port=srv_port; - this.max_port=max_port; - this.pm=pm; - init(); - } - - - public ConnectionTable(Receiver r, InetAddress bind_addr, InetAddress external_addr, int srv_port, int max_port, - long reaper_interval, long conn_expire_time) throws Exception { - setReceiver(r); - this.bind_addr=bind_addr; - this.external_addr=external_addr; - this.srv_port=srv_port; - this.max_port=max_port; - this.reaper_interval=reaper_interval; - this.conn_expire_time=conn_expire_time; - use_reaper=true; - init(); - } - - /** - * ConnectionTable including a connection reaper. Connections that have been idle for more than conn_expire_time - * milliseconds will be closed and removed from the connection table. On next access they will be re-created. - * - * @param r The Receiver - * @param bind_addr The host name or IP address of the interface to which the server socket will bind. - * This is interesting only in multi-homed systems. If bind_addr is null, the - * server socket will bind to the first available interface (e.g. /dev/hme0 on - * Solaris or /dev/eth0 on Linux systems). - * @param external_addr The address which will be broadcast to the group (the externally visible address - * which this host should be contacted on). If external_addr is null, it will default to - * the same address that the server socket is bound to. - * @param srv_port The port to which the server socket will bind to. If this port is reserved, the next - * free port will be taken (incrementing srv_port). - * @param max_port The largest port number that the server socket will be bound to. If max_port < srv_port - * then there is no limit. - * @param reaper_interval Number of milliseconds to wait for reaper between attepts to reap idle connections - * @param conn_expire_time Number of milliseconds a connection can be idle (no traffic sent or received until - * it will be reaped - */ - public ConnectionTable(Receiver r, InetAddress bind_addr, InetAddress external_addr, int srv_port, int max_port, - long reaper_interval, long conn_expire_time, PortsManager pm) throws Exception { - setReceiver(r); - this.bind_addr=bind_addr; - this.external_addr=external_addr; - this.srv_port=srv_port; - this.max_port=max_port; - this.pm=pm; - this.reaper_interval=reaper_interval; - this.conn_expire_time=conn_expire_time; - use_reaper=true; - init(); - } - - - - /** Try to obtain correct Connection (or create one if not yet existent) */ - Connection getConnection(Address dest) throws Exception { - Connection conn=null; - Socket sock; - - synchronized(conns) { - conn=conns.get(dest); - if(conn == null) { - // changed by bela Jan 18 2004: use the bind address for the client sockets as well - SocketAddress tmpBindAddr=new InetSocketAddress(bind_addr, 0); - InetAddress tmpDest=((IpAddress)dest).getIpAddress(); - SocketAddress destAddr=new InetSocketAddress(tmpDest, ((IpAddress)dest).getPort()); - sock=new Socket(); - sock.bind(tmpBindAddr); - sock.setKeepAlive(true); - sock.setTcpNoDelay(tcp_nodelay); - if(linger > 0) - sock.setSoLinger(true, linger); - else - sock.setSoLinger(false, -1); - sock.connect(destAddr, sock_conn_timeout); - - try { - sock.setSendBufferSize(send_buf_size); - } - catch(IllegalArgumentException ex) { - if(log.isErrorEnabled()) log.error("exception setting send buffer size to " + - send_buf_size + " bytes", ex); - } - try { - sock.setReceiveBufferSize(recv_buf_size); - } - catch(IllegalArgumentException ex) { - if(log.isErrorEnabled()) log.error("exception setting receive buffer size to " + - send_buf_size + " bytes", ex); - } - conn=new Connection(sock, dest); - conn.sendLocalAddress(local_addr); - notifyConnectionOpened(dest); - addConnection(dest, conn); - conn.init(); - if(log.isTraceEnabled()) log.trace("created socket to " + dest); - } - return conn; - } - } - - - public final void start() throws Exception { - acceptor=getThreadFactory().newThread(thread_group,this, "ConnectionTable.AcceptorThread"); - acceptor.start(); - - // start the connection reaper - will periodically remove unused connections - if(use_reaper && reaper == null) { - reaper=new Reaper(); - reaper.start(); - } - super.start(); - } - - protected void init() throws Exception { - - srv_sock=createServerSocket(srv_port, max_port); - - if (external_addr!=null) - local_addr=new IpAddress(external_addr, srv_sock.getLocalPort()); - else if (bind_addr != null) - local_addr=new IpAddress(bind_addr, srv_sock.getLocalPort()); - else - local_addr=new IpAddress(srv_sock.getLocalPort()); - - if(log.isDebugEnabled()) log.debug("server socket listening on " + local_addr); - } - - - - - /** - * Acceptor thread. Continuously accept new connections. Create a new thread for each new - * connection and put it in conns. When the thread should stop, it is - * interrupted by the thread creator. - */ - public void run() { - Socket client_sock=null; - Connection conn=null; - Address peer_addr; - - while(srv_sock != null) { - try { - conn=null; - client_sock=srv_sock.accept(); - if(!running) { - if(log.isWarnEnabled()) - log.warn("cannot accept connection from " + client_sock.getRemoteSocketAddress() + " as I'm closed"); - break; - } - if(log.isTraceEnabled()) - log.trace("[" +local_addr + "] accepted connection from " + client_sock.getInetAddress() + ":" + client_sock.getPort()); - try { - client_sock.setSendBufferSize(send_buf_size); - } - catch(IllegalArgumentException ex) { - if(log.isErrorEnabled()) log.error("exception setting send buffer size to " + send_buf_size + " bytes", ex); - } - try { - client_sock.setReceiveBufferSize(recv_buf_size); - } - catch(IllegalArgumentException ex) { - if(log.isErrorEnabled()) log.error("exception setting receive buffer size to " + send_buf_size + " bytes", ex); - } - - client_sock.setKeepAlive(true); - client_sock.setTcpNoDelay(tcp_nodelay); - if(linger > 0) - client_sock.setSoLinger(true, linger); - else - client_sock.setSoLinger(false, -1); - - // create new thread and add to conn table - conn=new Connection(client_sock, null); // will call receive(msg) - // get peer's address - peer_addr=conn.readPeerAddress(client_sock); - - // client_addr=new IpAddress(client_sock.getInetAddress(), client_port); - conn.setPeerAddress(peer_addr); - - synchronized(conns) { - Connection tmp=conns.get(peer_addr); - //Vladimir Nov, 5th, 2007 - //we might have a connection to peer but is that - //connection still open? - boolean connectionOpen = tmp != null && !tmp.isSocketClosed(); - if(connectionOpen) { - if(peer_addr.compareTo(local_addr) > 0) { - if(log.isTraceEnabled()) - log.trace("peer's address (" + peer_addr + ") is greater than our local address (" + - local_addr + "), replacing our existing connection"); - // peer's address is greater, add peer's connection to ConnectionTable, destroy existing connection - removeConnection(peer_addr); - addConnection(peer_addr, conn); - notifyConnectionOpened(peer_addr); - } - else { - if(log.isTraceEnabled()) - log.trace("peer's address (" + peer_addr + ") is smaller than our local address (" + - local_addr + "), rejecting peer connection request"); - conn.destroy(); - continue; - } - } - else { - addConnection(peer_addr, conn); - notifyConnectionOpened(peer_addr); - } - } - - conn.init(); // starts handler thread on this socket - } - catch(SocketTimeoutException timeout_ex) { - if(log.isWarnEnabled()) log.warn("timed out waiting for peer address, closing connection " + conn + ": " + timeout_ex); - if(conn != null) - conn.destroy(); - if(srv_sock == null) - break; // socket was closed, therefore stop - } - catch(SocketException sock_ex) { - if(log.isWarnEnabled() && srv_sock != null) log.warn("Could not read accept connection from peer " + sock_ex); - if(conn != null) - conn.destroy(); - if(srv_sock == null) - break; // socket was closed, therefore stop - } - catch(Throwable ex) { - if(log.isWarnEnabled()) log.warn("Could not read accept connection from peer " + ex); - if(srv_sock == null) - break; // socket was closed, therefore stop - } - } - if(client_sock != null) - try {client_sock.close();} catch(IOException e) {} - if(log.isTraceEnabled()) - log.trace(Thread.currentThread().getName() + " terminated"); - } - - - /** Finds first available port starting at start_port and returns server socket. - * Will not bind to port >end_port. Sets srv_port */ - protected ServerSocket createServerSocket(int start_port, int end_port) throws Exception { - ServerSocket ret=null; - - while(true) { - try { - if(start_port > 0 && pm != null) - start_port=pm.getNextAvailablePort(start_port); - if(bind_addr == null) - ret=new ServerSocket(start_port); - else { - // changed (bela Sept 7 2007): we accept connections on all NICs - ret=new ServerSocket(start_port, backlog, null); - } - } - catch(BindException bind_ex) { - if (start_port==end_port) throw new BindException("No available port to bind to"); - if(bind_addr != null && !bind_addr.isLoopbackAddress()) { - NetworkInterface nic=NetworkInterface.getByInetAddress(bind_addr); - if(nic == null) - throw new BindException("bind_addr " + bind_addr + " is not a valid interface"); - } - start_port++; - continue; - } - catch(IOException io_ex) { - if(log.isErrorEnabled()) log.error("exception is " + io_ex); - } - srv_port=start_port; - break; - } - return ret; - } - - - - -} - diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/ConnectionTableNIO.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/ConnectionTableNIO.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/ConnectionTableNIO.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/ConnectionTableNIO.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,12 +1,10 @@ -// $Id: ConnectionTableNIO.java,v 1.41 2008/09/25 14:20:51 belaban Exp $ package org.jgroups.blocks; -import org.apache.commons.logging.Log; +import org.jgroups.logging.Log; import org.jgroups.Address; import org.jgroups.Global; import org.jgroups.stack.IpAddress; -import org.jgroups.util.PortsManager; import org.jgroups.util.ShutdownRejectedExecutionHandler; import java.io.IOException; @@ -120,21 +118,6 @@ start(); } - public ConnectionTableNIO(Receiver r, InetAddress bind_addr, InetAddress external_addr, - int srv_port, int max_port, PortsManager pm, - boolean doStart) - throws Exception - { - setReceiver(r); - this.external_addr=external_addr; - this.bind_addr=bind_addr; - this.srv_port=srv_port; - this.max_port=max_port; - this.pm=pm; - use_reaper=true; - if(doStart) - start(); - } /** @@ -180,25 +163,6 @@ start(); } - public ConnectionTableNIO(Receiver r, InetAddress bind_addr, InetAddress external_addr, - int srv_port, int max_port, PortsManager pm, - long reaper_interval, long conn_expire_time, boolean doStart - ) throws Exception - { - setReceiver(r); - this.bind_addr=bind_addr; - this.external_addr=external_addr; - this.srv_port=srv_port; - this.max_port=max_port; - this.pm=pm; - this.reaper_interval=reaper_interval; - this.conn_expire_time=conn_expire_time; - use_reaper=true; - if(doStart) - start(); - } - - public int getReaderThreads() { return m_reader_threads; } @@ -246,7 +210,7 @@ /** * Try to obtain correct Connection (or create one if not yet existent) */ - ConnectionTable.Connection getConnection(Address dest) throws Exception + BasicConnectionTable.Connection getConnection(Address dest) throws Exception { Connection conn; SocketChannel sock_ch; @@ -623,9 +587,6 @@ m_serverSocketChannel = ServerSocketChannel.open(); m_serverSocketChannel.configureBlocking(false); - if(start_port > 0 && pm != null) - start_port=pm.getNextAvailablePort(start_port); - while (true) { try @@ -697,7 +658,7 @@ return Selector.open(); } catch (IOException e) { - if (log.isErrorEnabled()) log.error(e); + if (log.isErrorEnabled()) log.error(e.toString()); throw new IllegalStateException(e.getMessage()); } @@ -991,7 +952,7 @@ } } - class Connection extends ConnectionTable.Connection { + class Connection extends BasicConnectionTable.Connection { private SocketChannel sock_ch = null; private WriteHandler m_writeHandler; private SelectorWriteHandler m_selectorWriteHandler; @@ -1043,7 +1004,7 @@ return sock_ch; } - void closeSocket() + synchronized void closeSocket() { if (sock_ch != null) @@ -1102,7 +1063,7 @@ } catch (IOException e) { - if (log.isErrorEnabled()) log.error(e); + if (log.isErrorEnabled()) log.error(e.toString()); throw new IllegalStateException(e.getMessage()); } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/DistributedHashtable.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/DistributedHashtable.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/DistributedHashtable.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/DistributedHashtable.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,703 +0,0 @@ -// $Id: DistributedHashtable.java,v 1.35 2008/04/08 14:41:22 belaban Exp $ - -package org.jgroups.blocks; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jgroups.*; -import org.jgroups.annotations.Unsupported; -import org.jgroups.persistence.CannotPersistException; -import org.jgroups.persistence.CannotRemoveException; -import org.jgroups.persistence.PersistenceFactory; -import org.jgroups.persistence.PersistenceManager; -import org.jgroups.util.Promise; -import org.jgroups.util.Util; - -import java.io.*; -import java.util.*; - - -/** - * Provides the abstraction of a java.util.Hashtable that is replicated at several - * locations. Any change to the hashtable (clear, put, remove etc) will transparently be - * propagated to all replicas in the group. All read-only methods will always access the - * local replica.

    - * Both keys and values added to the hashtable must be serializable, the reason - * being that they will be sent across the network to all replicas of the group. Having said - * this, it is now for example possible to add RMI remote objects to the hashtable as they - * are derived from java.rmi.server.RemoteObject which in turn is serializable. - * This allows to lookup shared distributed objects by their name and invoke methods on them, - * regardless of one's onw location. A DistributedHashtable thus allows to - * implement a distributed naming service in just a couple of lines.

    - * An instance of this class will contact an existing member of the group to fetch its - * initial state (using the state exchange funclet StateExchangeFunclet. - * @author Bela Ban - * @author Alfonso Olias-Sanz - * @version $Id: DistributedHashtable.java,v 1.35 2008/04/08 14:41:22 belaban Exp $ - * @deprecated Use {@link org.jgroups.blocks.ReplicatedHashMap} instead - */ -@Unsupported -public class DistributedHashtable extends Hashtable implements ExtendedMessageListener, ExtendedMembershipListener { - private static final long serialVersionUID=7910133360803785134L; - - - public interface Notification { - void entrySet(Object key, Object value); - void entryRemoved(Object key); - void viewChange(Vector new_mbrs, Vector old_mbrs); - void contentsSet(Map new_entries); - void contentsCleared(); - } - - - private transient Channel channel; - protected transient RpcDispatcher disp=null; - private String groupname=null; - private final transient Vector notifs=new Vector(); // to be notified when mbrship changes - private final Vector members=new Vector(); // keeps track of all DHTs - private transient Class[] put_signature=null; - private transient Class[] putAll_signature=null; - private transient Class[] clear_signature=null; - private transient Class[] remove_signature=null; - private transient boolean persistent=false; // whether to use PersistenceManager to save state - private transient PersistenceManager persistence_mgr=null; - - /** Determines when the updates have to be sent across the network, avoids sending unnecessary - * messages when there are no member in the group */ - private transient boolean send_message = false; - - protected final transient Promise state_promise=new Promise(); - - protected final Log log=LogFactory.getLog(this.getClass()); - - - - - /** - * Creates a DistributedHashtable - * @param groupname The name of the group to join - * @param factory The ChannelFactory which will be used to create a channel - * @param properties The property string to be used to define the channel. This will override the properties of - * the factory. If null, then the factory properties will be used - * @param state_timeout The time to wait until state is retrieved in milliseconds. A value of 0 means wait forever. - */ - public DistributedHashtable(String groupname, ChannelFactory factory, - String properties, long state_timeout) - throws ChannelException { - this.groupname=groupname; - initSignatures(); - if(factory != null) { - channel=properties != null? factory.createChannel((Object)properties) : factory.createChannel(); - } - else { - channel=new JChannel(properties); - } - disp=new RpcDispatcher(channel, this, this, this); - channel.connect(groupname); - start(state_timeout); - } - - /** - * Creates a DisttributedHashtable. Optionally the contents can be saved to - * persistemt storage using the {@link PersistenceManager}. - * @param groupname Name of the group to join - * @param factory Instance of a ChannelFactory to create the channel - * @param properties Protocol stack properties. This will override the properties of the factory. If - * null, then the factory properties will be used - * @param persistent Whether the contents should be persisted - * @param state_timeout Max number of milliseconds to wait until state is - * retrieved - */ - public DistributedHashtable(String groupname, ChannelFactory factory, String properties, - boolean persistent, long state_timeout) - throws ChannelException { - this.groupname=groupname; - this.persistent=persistent; - initSignatures(); - if(factory != null) { - channel=properties != null? factory.createChannel((Object)properties) : factory.createChannel(); - } - else { - channel=new JChannel(properties); - } - disp=new RpcDispatcher(channel, this, this, this); - channel.connect(groupname); - start(state_timeout); - } - - - public DistributedHashtable(Channel channel, long state_timeout) { - this(channel, false, state_timeout); - } - - - public DistributedHashtable(Channel channel, boolean persistent, long state_timeout) { - this.groupname = channel.getClusterName(); - this.channel = channel; - this.persistent=persistent; - init(state_timeout); - } - - /** - * Uses a user-provided PullPushAdapter to create the dispatcher rather than a Channel. If id is non-null, it will be - * used to register under that id. This is typically used when another building block is already using - * PullPushAdapter, and we want to add this building block in addition. The id is the used to discriminate - * between messages for the various blocks on top of PullPushAdapter. If null, we will assume we are the - * first block created on PullPushAdapter. - * @param adapter The PullPushAdapter which to use as underlying transport - * @param id A serializable object (e.g. an Integer) used to discriminate (multiplex/demultiplex) between - * requests/responses for different building blocks on top of PullPushAdapter. - * @param state_timeout Max number of milliseconds to wait until state is - * retrieved - */ - public DistributedHashtable(PullPushAdapter adapter, Serializable id, long state_timeout) - throws ChannelNotConnectedException, ChannelClosedException { - initSignatures(); - this.channel = (Channel)adapter.getTransport(); - this.groupname = this.channel.getClusterName(); - disp=new RpcDispatcher(adapter, id, this, this, this); - start(state_timeout); - } - - public DistributedHashtable(PullPushAdapter adapter, Serializable id) { - initSignatures(); - this.channel = (Channel)adapter.getTransport(); - this.groupname = this.channel.getClusterName(); - disp=new RpcDispatcher(adapter, id, this, this, this); - } - - protected final void init(long state_timeout) { - initSignatures(); - disp = new RpcDispatcher(channel, this, this, this); - - // Changed by bela (jan 20 2003): start() has to be called by user (only when providing - // own channel). First, Channel.connect() has to be called, then start(). - // start(state_timeout); - } - - - /** - * Fetches the state - * @param state_timeout - * @throws ChannelClosedException - * @throws ChannelNotConnectedException - */ - public final void start(long state_timeout) throws ChannelClosedException, ChannelNotConnectedException { - boolean rc; - if(persistent) { - if(log.isInfoEnabled()) log.info("fetching state from database"); - try { - persistence_mgr=PersistenceFactory.getInstance().createManager(); - } - catch(Throwable ex) { - if(log.isErrorEnabled()) log.error("failed creating PersistenceManager, " + - "turning persistency off. Exception: " + Util.printStackTrace(ex)); - persistent=false; - } - } - - state_promise.reset(); - rc=channel.getState(null, state_timeout); - if(rc) { - if(log.isInfoEnabled()) log.info("state was retrieved successfully, waiting for setState()"); - Boolean result=state_promise.getResult(state_timeout); - if(result == null) { - if(log.isErrorEnabled()) log.error("setState() never got called"); - } - else { - if(log.isInfoEnabled()) log.info("setState() was called"); - } - } - else { - if(log.isInfoEnabled()) log.info("state could not be retrieved (first member)"); - if(persistent) { - if(log.isInfoEnabled()) log.info("fetching state from database"); - try { - Map m=persistence_mgr.retrieveAll(); - if(m != null) { - Map.Entry entry; - Object key, val; - for(Iterator it=m.entrySet().iterator(); it.hasNext();) { - entry=(Map.Entry)it.next(); - key=entry.getKey(); - val=entry.getValue(); - if(log.isInfoEnabled()) log.info("inserting " + key + " --> " + val); - put(key, val); // will replicate key and value - } - } - } - catch(Throwable ex) { - if(log.isErrorEnabled()) log.error("failed creating PersistenceManager, " + - "turning persistency off. Exception: " + Util.printStackTrace(ex)); - persistent=false; - } - } - } - } - - - public Address getLocalAddress() {return channel != null ? channel.getLocalAddress() : null;} - public String getGroupName() {return groupname;} - public Channel getChannel() {return channel;} - public boolean getPersistent() {return persistent;} - public void setPersistent(boolean p) {persistent=p;} - - - public void setDeadlockDetection(boolean flag) { - if(disp != null) - disp.setDeadlockDetection(flag); - } - - public void addNotifier(Notification n) { - if(!notifs.contains(n)) - notifs.addElement(n); - } - - public void removeNotifier(Notification n) { - if(notifs.contains(n)) - notifs.removeElement(n); - } - - public void stop() { - if(disp != null) { - disp.stop(); - disp=null; - } - if(channel != null) { - channel.close(); - channel=null; - } - } - - - /** - * Maps the specified key to the specified value in the hashtable. Neither of both parameters can be null - * @param key - the hashtable key - * @param value - the value - * @return the previous value of the specified key in this hashtable, or null if it did not have one - */ - public Object put(Object key, Object value) { - Object prev_val=get(key); - - //Changes done by - //if true, propagate action to the group - if(send_message == true) { - try { - disp.callRemoteMethods( - null, "_put", new Object[]{key,value}, - put_signature, - GroupRequest.GET_ALL, - 0); - } - catch(Exception e) { - //return null; - } - } - else { - _put(key, value); - //don't have to do prev_val = super.put(..) as is done at the beginning - } - return prev_val; - } - - /** - * Copies all of the mappings from the specified Map to this Hashtable These mappings will replace any mappings that this Hashtable had for any of the keys currently in the specified Map. - * @param m - Mappings to be stored in this map - */ - public void putAll(Map m) { - //Changes done by - //if true, propagate action to the group - if(send_message == true) { - try { - disp.callRemoteMethods( - null, "_putAll", new Object[]{m}, - putAll_signature, - GroupRequest.GET_ALL, - 0); - } - catch(Throwable t) { - } - } - else { - _putAll(m); - } - } - - /** - * Clears this hashtable so that it contains no keys - */ - public void clear() { - //Changes done by - //if true, propagate action to the group - if(send_message == true) { - try { - disp.callRemoteMethods( - null, "_clear", null, - clear_signature, - GroupRequest.GET_ALL, - 0); - } - catch(Exception e) { - if(log.isErrorEnabled()) log.error("exception=" + e); - } - } - else { - _clear(); - } - } - - /** - * Removes the key (and its corresponding value) from the Hashtable. - * @param key - the key to be removed. - * @return the value to which the key had been mapped in this hashtable, or null if the key did not have a mapping. - */ - public Object remove(Object key) { - Object retval = get(key); - - //Changes done by - //if true, propagate action to the group - if(send_message == true) { - try { - disp.callRemoteMethods( - null, "_remove", new Object[]{key}, - remove_signature, - GroupRequest.GET_ALL, - 0); - //return retval; - } - catch(Exception e) { - //return null; - } - } - else { - _remove(key); - //don't have to do retval = super.remove(..) as is done at the beginning - } - return retval; - } - - - - /*------------------------ Callbacks -----------------------*/ - - public Object _put(Object key, Object value) { - Object retval=super.put(key, value); - if(persistent) { - try { - persistence_mgr.save((Serializable)key, (Serializable)value); - } - catch(CannotPersistException cannot_persist_ex) { - if(log.isErrorEnabled()) log.error("failed persisting " + key + " + " + - value + ", exception=" + cannot_persist_ex); - } - catch(Throwable t) { - if(log.isErrorEnabled()) log.error("failed persisting " + key + " + " + - value + ", exception=" + Util.printStackTrace(t)); - } - } - for(int i=0; i < notifs.size(); i++) - ((Notification)notifs.elementAt(i)).entrySet(key, value); - return retval; - } - - - /** - * @see java.util.Map#putAll(java.util.Map) - */ - public void _putAll(Map m) { - if (m == null) - return; - - // Calling the method below seems okay, but would result in ... deadlock ! - // The reason is that Map.putAll() calls put(), which we override, which results in - // lock contention for the map. - - // ---> super.putAll(m); <--- CULPRIT !!!@#$%$ - - // That said let's do it the stupid way: - Map.Entry entry; - for(Iterator it=m.entrySet().iterator(); it.hasNext();) { - entry=(Map.Entry)it.next(); - super.put(entry.getKey(), entry.getValue()); - } - - if (persistent) { - try { - persistence_mgr.saveAll(m); - } - catch (CannotPersistException persist_ex) { - if(log.isErrorEnabled()) log.error("failed persisting contents: " + persist_ex); - } - catch (Throwable t) { - if(log.isErrorEnabled()) log.error("failed persisting contents: " + t); - } - } - for(int i=0; i < notifs.size(); i++) - ((Notification)notifs.elementAt(i)).contentsSet(m); - } - - - public void _clear() { - super.clear(); - if(persistent) { - try { - persistence_mgr.clear(); - } - catch(CannotRemoveException cannot_remove_ex) { - if(log.isErrorEnabled()) log.error("failed clearing contents, exception=" + cannot_remove_ex); - } - catch(Throwable t) { - if(log.isErrorEnabled()) log.error("failed clearing contents, exception=" + t); - } - } - for(int i=0; i < notifs.size(); i++) - ((Notification)notifs.elementAt(i)).contentsCleared(); - } - - - public Object _remove(Object key) { - Object retval=super.remove(key); - if(persistent) { - try { - persistence_mgr.remove((Serializable)key); - } - catch(CannotRemoveException cannot_remove_ex) { - if(log.isErrorEnabled()) log.error("failed clearing contents, exception=" + cannot_remove_ex); - } - catch(Throwable t) { - if(log.isErrorEnabled()) log.error("failed clearing contents, exception=" + t); - } - } - for(int i=0; i < notifs.size(); i++) - ((Notification)notifs.elementAt(i)).entryRemoved(key); - - return retval; - } - - /*----------------------------------------------------------*/ - - - - /*-------------------- State Exchange ----------------------*/ - - public void receive(Message msg) { } - - public byte[] getState() { - Object key, val; - Hashtable copy=new Hashtable(); - - for(Enumeration e=keys(); e.hasMoreElements();) { - key=e.nextElement(); - val=get(key); - copy.put(key, val); - } - try { - return Util.objectToByteBuffer(copy); - } - catch(Throwable ex) { - if(log.isErrorEnabled()) log.error("exception marshalling state: " + ex); - return null; - } - } - - - public void setState(byte[] new_state) { - Hashtable new_copy; - - try { - new_copy=(Hashtable)Util.objectFromByteBuffer(new_state); - if(new_copy == null) - return; - } - catch(Throwable ex) { - if(log.isErrorEnabled()) log.error("exception unmarshalling state: " + ex); - return; - } - _putAll(new_copy); - state_promise.setResult(Boolean.TRUE); - } - - - - /*------------------- Membership Changes ----------------------*/ - - public void viewAccepted(View new_view) { - Vector new_mbrs=new_view.getMembers(); - - if(new_mbrs != null) { - sendViewChangeNotifications(new_mbrs, members); // notifies observers (joined, left) - members.removeAllElements(); - for(int i=0; i < new_mbrs.size(); i++) - members.addElement(new_mbrs.elementAt(i)); - } - //if size is bigger than one, there are more peers in the group - //otherwise there is only one server. - send_message=members.size() > 1; - } - - - /** Called when a member is suspected */ - public void suspect(Address suspected_mbr) { - ; - } - - - /** Block sending and receiving of messages until ViewAccepted is called */ - public void block() {} - - - - void sendViewChangeNotifications(Vector new_mbrs, Vector old_mbrs) { - Vector joined, left; - Object mbr; - Notification n; - - if(notifs.size() == 0 || old_mbrs == null || new_mbrs == null || - old_mbrs.size() == 0 || new_mbrs.size() == 0) - return; - - - // 1. Compute set of members that joined: all that are in new_mbrs, but not in old_mbrs - joined=new Vector(); - for(int i=0; i < new_mbrs.size(); i++) { - mbr=new_mbrs.elementAt(i); - if(!old_mbrs.contains(mbr)) - joined.addElement(mbr); - } - - - // 2. Compute set of members that left: all that were in old_mbrs, but not in new_mbrs - left=new Vector(); - for(int i=0; i < old_mbrs.size(); i++) { - mbr=old_mbrs.elementAt(i); - if(!new_mbrs.contains(mbr)) { - left.addElement(mbr); - } - } - - for(int i=0; i < notifs.size(); i++) { - n=(Notification)notifs.elementAt(i); - n.viewChange(joined, left); - } - } - - - final void initSignatures() { - try { - if(put_signature == null) { - put_signature=new Class[] {Object.class,Object.class}; - } - - if(putAll_signature == null) { - putAll_signature=new Class[] {Map.class}; - } - - if(clear_signature == null) - clear_signature=new Class[0]; - - if(remove_signature == null) { - remove_signature=new Class[] {Object.class}; - } - } - catch(Throwable ex) { - if(log.isErrorEnabled()) log.error("exception=" + ex); - } - } - - public static void main(String[] args) { - try { - // The setup here is kind of weird: - // 1. Create a channel - // 2. Create a DistributedHashtable (on the channel) - // 3. Connect the channel (so the HT gets a VIEW_CHANGE) - // 4. Start the HT - // - // A simpler setup is - // DistributedHashtable ht = new DistributedHashtable("demo", null, - // "file://c:/JGroups-2.0/conf/state_transfer.xml", 5000); - - JChannel c = new JChannel("file:/c:/JGroups-2.0/conf/state_transfer.xml"); - DistributedHashtable ht = new DistributedHashtable(c, false, 5000); - c.connect("demo"); - ht.start(5000); - - - - ht.put("name", "Michelle Ban"); - Object old_key = ht.remove("name"); - System.out.println("old key was " + old_key); - ht.put("newkey", "newvalue"); - - Map m = new HashMap(); - m.put("k1", "v1"); - m.put("k2", "v2"); - - ht.putAll(m); - - System.out.println("hashmap is " + ht); - } - catch (Throwable t) { - t.printStackTrace(); - } - } - - public byte[] getState(String state_id) { - // not implemented - return null; - } - - public void getState(OutputStream ostream) { - Object key, val; - Hashtable copy=new Hashtable(); - ObjectOutputStream oos = null; - - for(Enumeration e=keys(); e.hasMoreElements();) { - key=e.nextElement(); - val=get(key); - copy.put(key, val); - } - try { - oos = new ObjectOutputStream(ostream); - oos.writeObject(copy); - } - catch(Throwable ex) { - if(log.isErrorEnabled()) log.error("exception marshalling state: " + ex); - } - finally{ - Util.close(oos); - } - } - - public void getState(String state_id, OutputStream ostream) { - } - - public void setState(String state_id, byte[] state) { - } - - public void setState(InputStream istream) { - Hashtable new_copy = null; - ObjectInputStream ois = null; - try{ - ois = new ObjectInputStream(istream); - new_copy = (Hashtable) ois.readObject(); - ois.close(); - }catch(Throwable e){ - e.printStackTrace(); - if(log.isErrorEnabled()) log.error("exception marshalling state: " + e); - }finally{ - Util.close(ois); - } - if(new_copy != null) - _putAll(new_copy); - - state_promise.setResult(Boolean.TRUE); - } - - public void setState(String state_id, InputStream istream) { - } - - public void unblock() { - } - -} - diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/DistributedLockManager.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/DistributedLockManager.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/DistributedLockManager.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/DistributedLockManager.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,7 +1,7 @@ package org.jgroups.blocks; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.ChannelException; import org.jgroups.MembershipListener; import org.jgroups.View; @@ -21,9 +21,10 @@ * * @author Roman Rokytskyy (rrokytskyy@acm.org) * @author Robert Schaffar-Taurok (robert@fusion.at) - * @version $Id: DistributedLockManager.java,v 1.12 2008/05/22 09:50:22 belaban Exp $ + * @deprecated Succeessor is {@link org.jgroups.blocks.locking.LockService}. */ @Unsupported +@Deprecated public class DistributedLockManager implements TwoPhaseVotingListener, LockManager, VoteResponseProcessor, MembershipListener { /** * Definitions for the implementation of the VoteResponseProcessor @@ -224,7 +225,7 @@ throws LockNotGrantedException, ChannelException { if (!(lockId instanceof Serializable) || !(owner instanceof Serializable)) - throw new ClassCastException("DistributedLockManager works only with serializable objects."); + throw new IllegalArgumentException("DistributedLockManager works only with serializable objects."); boolean acquired = votingAdapter.vote( new AcquireLockDecree(lockId, owner, id), timeout); @@ -702,6 +703,10 @@ */ public Object getKey() { return lockId; } + public Object getRequester() { + return requester; + } + /** * This is a place-holder for future lock expiration code. */ @@ -729,9 +734,12 @@ } public boolean equals(Object other) { - return other instanceof LockDecree && ((LockDecree)other).lockId.equals(this.lockId); } + + public String toString() { + return "lockId=" + lockId + ", requester=" + requester + ", managerId=" + managerId + ", committed=" + commited; + } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/DistributedQueue.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/DistributedQueue.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/DistributedQueue.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/DistributedQueue.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,8 +1,7 @@ -// $Id: DistributedQueue.java,v 1.21 2008/04/08 14:41:22 belaban Exp $ package org.jgroups.blocks; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.*; import org.jgroups.annotations.Unsupported; import org.jgroups.util.RspList; @@ -142,7 +141,7 @@ public Address getLocalAddress() { - return (channel != null) ? channel.getLocalAddress() : null; + return (channel != null) ? channel.getAddress() : null; } public Channel getChannel() diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/DistributedTree.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/DistributedTree.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/DistributedTree.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/DistributedTree.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,10 +1,9 @@ -// $Id: DistributedTree.java,v 1.19 2008/06/10 10:25:49 belaban Exp $ package org.jgroups.blocks; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.*; import org.jgroups.annotations.Unsupported; import org.jgroups.util.Util; @@ -43,8 +42,7 @@ "UNICAST(timeout=5000):" + "FRAG(down_thread=false;up_thread=false):" + "pbcast.GMS(join_timeout=5000;" + - "shun=false;print_local_addr=true):" + - // trace=true is not supported anymore + "print_local_addr=true):" + "pbcast.STATE_TRANSFER()"; static final long state_timeout=5000; // wait 5 secs max to obtain state @@ -108,7 +106,7 @@ } public Object getLocalAddress() { - return channel != null? channel.getLocalAddress() : null; + return channel != null? channel.getAddress() : null; } public void setDeadlockDetection(boolean flag) { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/executor/ExecutionCompletionService.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/executor/ExecutionCompletionService.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/executor/ExecutionCompletionService.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/executor/ExecutionCompletionService.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,163 @@ +package org.jgroups.blocks.executor; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletionService; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.jgroups.util.FutureListener; +import org.jgroups.util.NotifyingFuture; + +/** + * A {@link CompletionService} that uses a supplied {@link ExecutionService} + * to execute tasks. This class arranges that submitted tasks are, + * upon completion, placed on a queue accessible using take. + * The class is lightweight enough to be suitable for transient use + * when processing groups of tasks. + *

    + * This class must be used instead of a {@link ExecutorCompletionService} + * provided from java.util.concurrent package. The + * {@link ExecutorCompletionService} may not be used since it requires the use + * of a non serializable RunnableFuture object. Since a ExecutionService + * may only be used with serializable request objects, this class must be used + * instead. + */ +public class ExecutionCompletionService implements CompletionService { + protected final ExecutionService executor; + protected final BlockingQueue> completionQueue; + protected final QueueingListener listener; + + protected class QueueingListener implements FutureListener { + @Override + public void futureDone(Future future) { + // This is a safe cast since this listener should only used + // in this class + completionQueue.add((NotifyingFuture)future); + } + + } + + /** + * Creates an ExecutorCompletionService using the supplied + * executor for base task execution and a + * {@link LinkedBlockingQueue} as a completion queue. + * + * @param executor the executor to use + * @throws NullPointerException if executor is null + */ + public ExecutionCompletionService(ExecutionService executor) { + this(executor, null, null); + } + + /** + * Creates an ExecutorCompletionService using the supplied + * executor for base task execution and the supplied queue as its + * completion queue. + * + * @param executor the executor to use + * @param completionQueue the queue to use as the completion queue + * normally one dedicated for use by this service + * @throws NullPointerException if executor is null + */ + public ExecutionCompletionService(ExecutionService executor, + BlockingQueue> completionQueue) { + this(executor, completionQueue, null); + } + + /** + * This constructor is here if someone wants to override this class and + * provide their own QueueingListener to possibly listen in on futures + * being finished + * @param executor the executor to use + * @param completionQueue the queue to use as the completion queue + * normally one dedicated for use by this service + * @param listener the listener to notify. To work properly this listner + * should at minimum call the super.futureDone or else this + * completion service may not work correctly. + * @throws NullPointerException if executor is null + */ + protected ExecutionCompletionService(ExecutionService executor, + BlockingQueue> completionQueue, + QueueingListener listener) { + if (executor == null) + throw new NullPointerException(); + this.executor = executor; + + if (completionQueue == null) { + this.completionQueue = new LinkedBlockingQueue>(); + } + else { + this.completionQueue = completionQueue; + } + + if (listener == null) { + this.listener = new QueueingListener(); + } + else { + this.listener = listener; + } + } + + /** + * {@inheritDoc CompletionService} + *

    + * This future object may not be used as a NotifyingFuture. That is because + * internally this class sets the listener to provide ability to add to the queue. + */ + public Future submit(Callable task) { + if (task == null) throw new NullPointerException(); + NotifyingFuture f = executor.submit(task); + f.setListener(listener); + return f; + } + + /** + * {@inheritDoc CompletionService} + *

    + * This future object may not be used as a NotifyingFuture. That is because + * internally this class sets the listener to provide ability to add to the queue. + */ + public Future submit(Runnable task, V result) { + if (task == null) throw new NullPointerException(); + NotifyingFuture f = executor.submit(task, result); + f.setListener(listener); + return f; + } + + /** + * {@inheritDoc CompletionService} + *

    + * This future may safely be used as a NotifyingFuture if desired. This + * is because if it tries to set a listener it will be called immediately + * since the task has already been completed. + */ + public NotifyingFuture take() throws InterruptedException { + return completionQueue.take(); + } + + /** + * {@inheritDoc CompletionService} + *

    + * This future may safely be used as a NotifyingFuture if desired. This + * is because if it tries to set a listener it will be called immediately + * since the task has already been completed. + */ + public NotifyingFuture poll() { + return completionQueue.poll(); + } + + /** + * {@inheritDoc CompletionService} + *

    + * This future may safely be used as a NotifyingFuture if desired. This + * is because if it tries to set a listener it will be called immediately + * since the task has already been completed. + */ + public NotifyingFuture poll(long timeout, TimeUnit unit) throws InterruptedException { + return completionQueue.poll(timeout, unit); + } + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/executor/ExecutionRunner.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/executor/ExecutionRunner.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/executor/ExecutionRunner.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/executor/ExecutionRunner.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,92 @@ +package org.jgroups.blocks.executor; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.jgroups.JChannel; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.protocols.Executing; + +/** + * This class is to be used to pick up execution requests and actually run + * them. A single instance can be used across any number of threads. + * + * @author wburns + */ +public class ExecutionRunner implements Runnable { + protected JChannel ch; + protected Executing _execProt; + + public ExecutionRunner(JChannel channel) { + setChannel(channel); + } + + public void setChannel(JChannel ch) { + this.ch=ch; + _execProt=(Executing)ch.getProtocolStack().findProtocol(Executing.class); + if(_execProt == null) + throw new IllegalStateException("Channel configuration must include a executing protocol " + + "(subclass of " + Executing.class.getName() + ")"); + } + + // @see java.lang.Runnable#run() + @Override + public void run() { + final AtomicBoolean shutdown = new AtomicBoolean(); + // This thread is only spawned so that we can differentiate between + // an interrupt of a task and an interrupt causing a shutdown of + // runner itself. + Thread executionThread = new Thread() { + + // @see java.lang.Thread#run() + @Override + public void run() { + Runnable runnable = null; + // This task exits by being interrupted when the task isn't running + while (!shutdown.get()) { + runnable = (Runnable)ch.downcall(new ExecutorEvent( + ExecutorEvent.CONSUMER_READY, null)); + if (Thread.interrupted()) { + if (runnable != null) { + // We assume that if an interrupt occurs here that + // it is trying to close down the task. Since the + // window is so small. Therefore if we get a + // task we need to reject it so it can be passed + // off to a different consumer + ch.down(new ExecutorEvent(ExecutorEvent.TASK_COMPLETE, + new Object[]{runnable, new InterruptedException()})); + } + continue; + } + Throwable throwable = null; + try { + runnable.run(); + } + // This can only happen if user is directly doing an execute(Runnable) + catch (Throwable t) { + _logger.error("Unexpected Runtime Error encountered in Runnable request", t); + throwable = t; + } + ch.down(new ExecutorEvent(ExecutorEvent.TASK_COMPLETE, + throwable != null ? new Object[]{runnable, throwable} : runnable)); + } + } + }; + + executionThread.setName(Thread.currentThread().getName() + "- Task Runner"); + executionThread.start(); + + try { + executionThread.join(); + } + catch (InterruptedException e) { + shutdown.set(true); + executionThread.interrupt(); + if (_logger.isTraceEnabled()) { + _logger.trace("Shutting down Execution Runner"); + } + } + } + + protected static final Log _logger = LogFactory.getLog(ExecutionRunner.class); +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/executor/ExecutionService.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/executor/ExecutionService.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/executor/ExecutionService.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/executor/ExecutionService.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,805 @@ +package org.jgroups.blocks.executor; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.NotSerializableException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.AbstractExecutorService; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletionService; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.RunnableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.AbstractQueuedSynchronizer; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.jgroups.JChannel; +import org.jgroups.protocols.Executing; +import org.jgroups.util.FutureListener; +import org.jgroups.util.NotifyingFuture; +import org.jgroups.util.Streamable; +import org.jgroups.util.Util; + +/** + * This is a jgroups implementation of an ExecutorService, where the consumers + * are running on any number of nodes. The nodes should run + * {@link ExecutionRunner} to start picking up requests. + *

    + * Every future object returned will be a {@link NotifyingFuture} which + * allows for not having to query the future and have a callback instead. This + * can then be used as a workflow to submit other tasks sequentially or also to + * query the future for the value at that time. + *

    + * Every callable or runnable submitted must be either {@link Serializable} or + * {@link Streamable}. Also the value returned from + * a callable must {@link Serializable} or + * {@link Streamable}. Unfortunately if the value returned is not serializable + * then a {@link NotSerializableException} will be thrown as the cause. + * @author wburns + * @since 2.12.0 + */ +public class ExecutionService extends AbstractExecutorService { + protected JChannel ch; + protected Executing _execProt; + + protected Lock _unfinishedLock = new ReentrantLock(); + protected Condition _unfinishedCondition = _unfinishedLock.newCondition(); + + protected Set> _unfinishedFutures = new HashSet>(); + + protected AtomicBoolean _shutdown = new AtomicBoolean(false); + + public ExecutionService() { + + } + + public ExecutionService(JChannel ch) { + setChannel(ch); + } + + public void setChannel(JChannel ch) { + this.ch=ch; + _execProt=(Executing)ch.getProtocolStack().findProtocol(Executing.class); + if(_execProt == null) + throw new IllegalStateException("Channel configuration must include a executing protocol " + + "(subclass of " + Executing.class.getName() + ")"); + } + + // @see java.util.concurrent.AbstractExecutorService#submit(java.lang.Runnable, java.lang.Object) + @Override + public NotifyingFuture submit(Runnable task, T result) { + // This cast is okay cause we control creation of the task + return (NotifyingFuture)super.submit(task, result); + } + + // @see java.util.concurrent.AbstractExecutorService#submit(java.util.concurrent.Callable) + @Override + public NotifyingFuture submit(Callable task) { + // This cast is okay cause we control creation of the task + return (NotifyingFuture)super.submit(task); + } + + /** + * This is basically a copy of the FutureTask in java.util.concurrent but + * added serializable to it. Also added in the NotifyingFuture + * so that the channel can update the future when the value is calculated. + * + * @param + * @author wburns + */ + public static class DistributedFuture implements RunnableFuture, + ExecutorNotification, NotifyingFuture { + // @see java.lang.Object#toString() + @Override + public String toString() { + return "DistributedFuture [callable=" + sync.callable + "]"; + } + + /** Synchronization control for FutureTask */ + protected final Sync sync; + + /** The following values are only used on the client side */ + private final JChannel channel; + private final Set> _unfinishedFutures; + private final Lock _unfinishedLock; + private final Condition _unfinishedCondition; + private volatile FutureListener _listener; + + /** + * Creates a FutureTask that will upon running, execute the + * given Callable. + * + * @param channel The channel that messages are sent down + * @param unfinishedLock The lock which protects the futuresToFinish + * set object. + * @param condition The condition to signal when this future finishes + * @param futuresToFinish The set to remove this future from when + * it is finished. + * @param callable The callable to actually run on the server side + */ + public DistributedFuture(JChannel channel, Lock unfinishedLock, + Condition condition, + Set> futuresToFinish, + Callable callable) { + if (callable == null) + throw new NullPointerException(); + sync = new Sync(this, callable); + this.channel = channel; + // We keep the real copy to update the outside + _unfinishedFutures = futuresToFinish; + _unfinishedLock = unfinishedLock; + _unfinishedCondition = condition; + } + + /** + * Creates a FutureTask that will upon running, execute the + * given Runnable, and arrange that get will return the + * given result on successful completion. + * + * @param channel The channel that messages are sent down + * @param unfinishedLock The lock which protects the futuresToFinish + * set object. + * @param condition The condition to signal when this future finishes + * @param futuresToFinish The set to remove this future from when + * it is finished. + * @param runnable the runnable task + * @param result the result to return on successful completion. If + * you don't need a particular result, consider using + * constructions of the form: + * Future<?> f = new FutureTask<Object>(runnable, null) + * @throws NullPointerException if runnable is null + */ + public DistributedFuture(JChannel channel, Lock unfinishedLock, + Condition condition, Set> futuresToFinish, + Runnable runnable, V result) { + sync = new Sync(this, new RunnableAdapter(runnable, result)); + this.channel = channel; + // We keep the real copy to update the outside + _unfinishedFutures = futuresToFinish; + _unfinishedLock = unfinishedLock; + _unfinishedCondition = condition; + } + + public Callable getCallable() { + return sync.callable; + } + + public boolean isCancelled() { + return sync.innerIsCancelled(); + } + + public boolean isDone() { + return sync.innerIsDone(); + } + + public boolean cancel(boolean mayInterruptIfRunning) { + if (sync.innerIsDone()) { + return false; + } + // This will only happen on calling side since it is transient + if (channel != null) { + return (Boolean)channel.downcall(new ExecutorEvent( + ExecutorEvent.TASK_CANCEL, new Object[] {this, mayInterruptIfRunning})); + } + return sync.innerCancel(mayInterruptIfRunning); + } + + /** + * @throws CancellationException {@inheritDoc} + */ + public V get() throws InterruptedException, ExecutionException { + return sync.innerGet(); + } + + /** + * @throws CancellationException {@inheritDoc} + */ + public V get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return sync.innerGet(unit.toNanos(timeout)); + } + + /** + * Protected method invoked when this task transitions to state + * isDone (whether normally or via cancellation). The + * default implementation does nothing. Subclasses may override + * this method to invoke completion callbacks or perform + * bookkeeping. Note that you can query status inside the + * implementation of this method to determine whether this task + * has been cancelled. + */ + protected void done() { + _unfinishedLock.lock(); + try { + _unfinishedFutures.remove(this); + _unfinishedCondition.signalAll(); + } + finally { + _unfinishedLock.unlock(); + } + // We assign the listener to a local variable so we don't have to + // worry about it becoming null inside the if + FutureListener listener = _listener; + // We don't want this to run on server + if (listener != null) { + listener.futureDone(this); + } + } + + @Override + public NotifyingFuture setListener(FutureListener listener) { + _listener = listener; + if (sync.innerIsDone()) { + _listener.futureDone(this); + } + return this; + } + + /** + * Sets the result of this Future to the given value unless + * this future has already been set or has been cancelled. + * This method is invoked internally by the run method + * upon successful completion of the computation. + * @param v the value + */ + protected void set(V v) { + sync.innerSet(v); + } + + /** + * Causes this future to report an ExecutionException + * with the given throwable as its cause, unless this Future has + * already been set or has been cancelled. + * This method is invoked internally by the run method + * upon failure of the computation. + * @param t the cause of failure + */ + protected void setException(Throwable t) { + sync.innerSetException(t); + } + + // The following (duplicated) doc comment can be removed once + // + // 6270645: Javadoc comments should be inherited from most derived + // superinterface or superclass + // is fixed. + /** + * Sets this Future to the result of its computation + * unless it has been cancelled. + */ + public void run() { + sync.innerRun(); + } + + /** + * Synchronization control for FutureTask. Note that this must be + * a non-static inner class in order to invoke the protected + * done method. For clarity, all inner class support + * methods are same as outer, prefixed with "inner". + * + * Uses AQS sync state to represent run status + */ + protected static final class Sync extends AbstractQueuedSynchronizer { + private static final long serialVersionUID = -7828117401763700385L; + + /** State value representing that task is running */ + protected static final int RUNNING = 1; + /** State value representing that task ran */ + protected static final int RAN = 2; + /** State value representing that task was cancelled */ + protected static final int CANCELLED = 4; + + /** The containing future */ + protected final DistributedFuture future; + /** The underlying callable */ + protected final Callable callable; + /** The result to return from get() */ + protected V result; + /** The exception to throw from get() */ + protected Throwable exception; + + /** + * The thread running task. When nulled after set/cancel, this + * indicates that the results are accessible. Must be + * volatile, to ensure visibility upon completion. + */ + protected transient volatile Thread runner; + + public Sync(DistributedFuture future, Callable callable) { + this.future = future; + this.callable = callable; + } + + private static boolean ranOrCancelled(int state) { + return (state & (RAN | CANCELLED)) != 0; + } + + /** + * Implements AQS base acquire to succeed if ran or cancelled + */ + protected int tryAcquireShared(int ignore) { + return innerIsDone()? 1 : -1; + } + + /** + * Implements AQS base release to always signal after setting + * final done status by nulling runner thread. + */ + protected boolean tryReleaseShared(int ignore) { + runner = null; + return true; + } + + boolean innerIsCancelled() { + return getState() == CANCELLED; + } + + boolean innerIsDone() { + return ranOrCancelled(getState()) && runner == null; + } + + V innerGet() throws InterruptedException, ExecutionException { + acquireSharedInterruptibly(0); + if (getState() == CANCELLED) + throw new CancellationException(); + if (exception != null) + throw new ExecutionException(exception); + return result; + } + + V innerGet(long nanosTimeout) throws InterruptedException, ExecutionException, TimeoutException { + if (!tryAcquireSharedNanos(0, nanosTimeout)) + throw new TimeoutException(); + if (getState() == CANCELLED) + throw new CancellationException(); + if (exception != null) + throw new ExecutionException(exception); + return result; + } + + void innerSet(V v) { + for (;;) { + int s = getState(); + if (s == RAN) + return; + if (s == CANCELLED) { + // aggressively release to set runner to null, + // in case we are racing with a cancel request + // that will try to interrupt runner + releaseShared(0); + return; + } + if (compareAndSetState(s, RAN)) { + result = v; + releaseShared(0); + future.done(); + return; + } + } + } + + void innerSetException(Throwable t) { + for (;;) { + int s = getState(); + if (s == RAN) + return; + if (s == CANCELLED) { + // aggressively release to set runner to null, + // in case we are racing with a cancel request + // that will try to interrupt runner + releaseShared(0); + return; + } + if (compareAndSetState(s, RAN)) { + exception = t; + result = null; + releaseShared(0); + future.done(); + return; + } + } + } + + boolean innerCancel(boolean mayInterruptIfRunning) { + for (;;) { + int s = getState(); + if (ranOrCancelled(s)) + return false; + if (compareAndSetState(s, CANCELLED)) + break; + } + if (mayInterruptIfRunning) { + Thread r = runner; + if (r != null) + r.interrupt(); + } + releaseShared(0); + future.done(); + return true; + } + + void innerRun() { + if (!compareAndSetState(0, RUNNING)) + return; + try { + runner = Thread.currentThread(); + if (getState() == RUNNING) // recheck after setting thread + innerSet(callable.call()); + else + releaseShared(0); // cancel + } catch (Throwable ex) { + innerSetException(ex); + } + } + + boolean innerRunAndReset() { + if (!compareAndSetState(0, RUNNING)) + return false; + try { + runner = Thread.currentThread(); + if (getState() == RUNNING) + callable.call(); // don't set result + runner = null; + return compareAndSetState(RUNNING, 0); + } catch (Throwable ex) { + innerSetException(ex); + return false; + } + } + } + + // @see org.jgroups.blocks.executor.ExecutorNotification#resultReturned(java.lang.Object) + @SuppressWarnings("unchecked") + @Override + public void resultReturned(Object obj) { + set((V)obj); + } + + // @see org.jgroups.blocks.executor.ExecutorNotification#throwableEncountered(java.lang.Throwable) + @Override + public void throwableEncountered(Throwable t) { + setException(t); + } + + @Override + public void interrupted(Runnable runnable) { + _unfinishedLock.lock(); + try { + _unfinishedFutures.remove(this); + _unfinishedCondition.signalAll(); + } + finally { + _unfinishedLock.unlock(); + } + + // We assign the listener to a local variable so we don't have to + // worry about it becoming null inside the if + FutureListener listener = _listener; + // We don't want this to run on server + if (listener != null) { + listener.futureDone(this); + } + } + } + + // @see java.util.concurrent.ExecutorService#shutdown() + @Override + public void shutdown() { + _realShutdown(false); + } + + @SuppressWarnings("unchecked") + private List _realShutdown(boolean interrupt) { + _shutdown.set(true); + _unfinishedLock.lock(); + Set> futures; + try { + futures = new HashSet>(_unfinishedFutures); + } + finally { + _unfinishedLock.unlock(); + } + return (List)ch.downcall(new ExecutorEvent( + ExecutorEvent.ALL_TASK_CANCEL, new Object[]{futures, interrupt})); + } + + // @see java.util.concurrent.ExecutorService#shutdownNow() + @Override + public List shutdownNow() { + return _realShutdown(true); + } + + // @see java.util.concurrent.ExecutorService#isShutdown() + @Override + public boolean isShutdown() { + return _shutdown.get(); + } + + // @see java.util.concurrent.ExecutorService#isTerminated() + @Override + public boolean isTerminated() { + if (_shutdown.get()) { + _unfinishedLock.lock(); + try { + return _unfinishedFutures.isEmpty(); + } + finally { + _unfinishedLock.unlock(); + } + } + return false; + } + + // @see java.util.concurrent.ExecutorService#awaitTermination(long, java.util.concurrent.TimeUnit) + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) + throws InterruptedException { + long nanoTimeWait = unit.toNanos(timeout); + _unfinishedLock.lock(); + try { + while (!_unfinishedFutures.isEmpty()) { + if ((nanoTimeWait = _unfinishedCondition.awaitNanos( + nanoTimeWait)) <= 0) { + return false; + } + } + } + finally { + _unfinishedLock.unlock(); + } + + return true; + } + + // @see java.util.concurrent.AbstractExecutorService#invokeAny(java.util.Collection) + @Override + public T invokeAny(Collection> tasks) + throws InterruptedException, ExecutionException { + try { + return doInvokeAny(tasks, false, 0); + } catch (TimeoutException cannotHappen) { + assert false; + return null; + } + } + + // @see java.util.concurrent.AbstractExecutorService#invokeAny(java.util.Collection, long, java.util.concurrent.TimeUnit) + @Override + public T invokeAny(Collection> tasks, + long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return doInvokeAny(tasks, true, unit.toNanos(timeout)); + } + + /** + * the main mechanics of invokeAny. + * This was essentially copied from {@link AbstractExecutorService} + * doInvokeAny except that we replaced the {@link ExecutorCompletionService} + * with an {@link ExecutionCompletionService}. + */ + private T doInvokeAny(Collection> tasks, + boolean timed, long nanos) + throws InterruptedException, ExecutionException, TimeoutException { + if (tasks == null) + throw new NullPointerException(); + int ntasks = tasks.size(); + if (ntasks == 0) + throw new IllegalArgumentException(); + List> futures= new ArrayList>(ntasks); + CompletionService ecs = + new ExecutionCompletionService(this); + + // For efficiency, especially in executors with limited + // parallelism, check to see if previously submitted tasks are + // done before submitting more of them. This interleaving + // plus the exception mechanics account for messiness of main + // loop. + + try { + // Record exceptions so that if we fail to obtain any + // result, we can throw the last exception we got. + ExecutionException ee = null; + long lastTime = (timed)? System.nanoTime() : 0; + Iterator> it = tasks.iterator(); + + // Start one task for sure; the rest incrementally + futures.add(ecs.submit(it.next())); + --ntasks; + int active = 1; + + for (;;) { + Future f = ecs.poll(); + if (f == null) { + if (ntasks > 0) { + --ntasks; + futures.add(ecs.submit(it.next())); + ++active; + } + else if (active == 0) + break; + else if (timed) { + f = ecs.poll(nanos, TimeUnit.NANOSECONDS); + if (f == null) + throw new TimeoutException(); + long now = System.nanoTime(); + nanos -= now - lastTime; + lastTime = now; + } + else + f = ecs.take(); + } + if (f != null) { + --active; + try { + return f.get(); + } catch (InterruptedException ie) { + throw ie; + } catch (ExecutionException eex) { + ee = eex; + } catch (RuntimeException rex) { + ee = new ExecutionException(rex); + } + } + } + + if (ee == null) + ee = new ExecutionException() { + private static final long serialVersionUID = 200818694545553992L; + }; + throw ee; + + } finally { + for (Future f : futures) + f.cancel(true); + } + } + + // @see java.util.concurrent.Executor#execute(java.lang.Runnable) + @Override + public void execute(Runnable command) { + if (!_shutdown.get()) { + Object serializeCheck; + // If it is wrapped by our future, then we have to make sure to + // check the actual callable/runnable given to us for serialization + if (command instanceof DistributedFuture) { + serializeCheck = ((DistributedFuture)command).getCallable(); + if (serializeCheck instanceof RunnableAdapter) { + serializeCheck = ((RunnableAdapter)serializeCheck).task; + } + } + else { + serializeCheck = command; + } + + if (serializeCheck instanceof Serializable || + serializeCheck instanceof Streamable) { + ch.down(new ExecutorEvent(ExecutorEvent.TASK_SUBMIT, command)); + } + else { + throw new IllegalArgumentException( + "Command was not Serializable or Streamable - " + + serializeCheck); + } + } + else { + throw new RejectedExecutionException(); + } + } + + /** + * This is copied from {@see java.util.concurrent.Executors} class which + * contains RunnableAdapter. However that adapter isn't serializable, and + * is final and package level so we can' reference. + */ + protected static final class RunnableAdapter implements Callable, Streamable { + protected Runnable task; + protected T result; + + protected RunnableAdapter() { + + } + protected RunnableAdapter(Runnable task, T result) { + this.task = task; + this.result = result; + } + public T call() { + task.run(); + return result; + } + @Override + public void writeTo(DataOutputStream out) throws IOException { + try { + Util.writeObject(task, out); + } + catch (IOException e) { + throw e; + } + catch (Exception e) { + throw new IOException("Exception encountered while writing execution runnable", e); + } + + try { + Util.writeObject(result, out); + } + catch (IOException e) { + throw e; + } + catch (Exception e) { + throw new IOException("Exception encountered while writing execution result", e); + } + } + @SuppressWarnings("unchecked") + @Override + public void readFrom(DataInputStream in) throws IOException, + IllegalAccessException, InstantiationException { + // We can't use Util.readObject since it's size is limited to 2^15-1 + // The runner could be larger than that possibly + try { + task = (Runnable)Util.readObject(in); + } + catch (IOException e) { + throw e; + } + catch (Exception e) { + throw new IOException("Exception encountered while reading execution runnable", e); + } + + try { + result = (T)Util.readObject(in); + } + catch (IOException e) { + throw e; + } + catch (Exception e) { + throw new IOException("Exception encountered while reading execution result", e); + } + } + } + + // @see java.util.concurrent.AbstractExecutorService#newTaskFor(java.lang.Runnable, java.lang.Object) + @Override + protected RunnableFuture newTaskFor(Runnable runnable, T value) { + DistributedFuture future = new DistributedFuture(ch, _unfinishedLock, + _unfinishedCondition, _unfinishedFutures, runnable, value); + _execProt.addExecutorListener(future, future); + _unfinishedLock.lock(); + try { + _unfinishedFutures.add(future); + } + finally { + _unfinishedLock.unlock(); + } + return future; + } + + // @see java.util.concurrent.AbstractExecutorService#newTaskFor(java.util.concurrent.Callable) + @Override + protected RunnableFuture newTaskFor(Callable callable) { + DistributedFuture future = new DistributedFuture(ch, _unfinishedLock, + _unfinishedCondition, _unfinishedFutures, callable); + _execProt.addExecutorListener(future, future); + _unfinishedLock.lock(); + try { + _unfinishedFutures.add(future); + } + finally { + _unfinishedLock.unlock(); + } + return future; + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/executor/Executions.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/executor/Executions.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/executor/Executions.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/executor/Executions.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,177 @@ +package org.jgroups.blocks.executor; + +import org.jgroups.util.Streamable; +import org.jgroups.util.Util; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.concurrent.Callable; + +public class Executions { + /** + * This method should be used to convert a callable that would not normally + * be serializable, externalizable or streamable but has serializable, + * externalizable or streamable arguments to a constructor to construct it. + *

    + * When the call method is called on the callable it will call the provided + * constructor passing in the given arguments. It will then invoke the call + * method on resulting callable that was created. + *

    + * The amount of arguments cannot exceed {@link Byte#MAX_VALUE}. Also the + * constructor cannot exceed {@link Byte#MAX_VALUE} position in the + * constructor array returned from {@link Class#getConstructors()} + *

    + * The amount of arguments must match the amount of arguments required + * by the constructor. Also the arguments must be compatibile with the + * types required of the constructor. + *

    + * Unfortunately it isn't easy to pass a Constructor> + * so we can't pass back a callable that is properly typed. Also this + * forces the caller to cast their callable or returned value to the correct + * type manually. + * + * @param constructorToUse The constructor to use when creating the callable + * @param args The arguments to pass to the constructor + * @return The callable that will upon being called will instantiate the + * given callable using the constructor with the provided arguments + * and calls the call method + * @throws IllegalArgumentException This is thrown if the arguments are + * not serializable, externalizable or streamable. It can be thrown + * if the constructo is not accessible. It can also be thrown + * if too many arguments or the constructor is to high up in the + * constructo array returned by the class. + */ + public static Callable serializableCallable(@SuppressWarnings("rawtypes") + Constructor constructorToUse, Object... args) + throws IllegalArgumentException { + if (args.length > (int)Byte.MAX_VALUE) { + throw new IllegalArgumentException( + "Max number of arguments exceeded: " + Byte.MAX_VALUE); + } + Class[] params = constructorToUse.getParameterTypes(); + + if (params.length != args.length) { + throw new IllegalArgumentException("Number of arguments [" + + args.length + "] doesn't match number of arguments for " + + "constructor [" + params.length + "]"); + } + + for (int i = 0; i < args.length; ++i) { + Object arg = args[i]; + if (arg instanceof Serializable || + arg instanceof Streamable) { + Class classArg = params[i]; + + if (!classArg.isInstance(arg)) { + throw new IllegalArgumentException("Argument [" + arg + + "] is not an instance of [" + classArg + "]"); + } + } + else { + throw new IllegalArgumentException( + "Argument is not serializable, externalizable or streamable: " + arg); + } + } + @SuppressWarnings("unchecked") + Class> classToUse = + (Class>)constructorToUse.getDeclaringClass(); + Constructor[] constructors = classToUse.getConstructors(); + byte constructorPosition = -1; + for (int i = 0; i < constructors.length; ++i) { + Constructor constructor = constructors[i]; + if (constructor.equals(constructorToUse)) { + if (i > (int)Byte.MAX_VALUE) { + throw new IllegalArgumentException( + "Constructor position in array cannot be higher than " + + Byte.MAX_VALUE); + } + constructorPosition = (byte)i; + } + } + if (constructorPosition == -1) { + throw new IllegalArgumentException( + "Constructor was not found in public constructor array on class"); + } + return new StreamableCallable(classToUse, constructorPosition, args); + } + + protected static class StreamableCallable implements Callable, Streamable { + + protected Class> _classCallable; + protected short _constructorNumber; + protected Object[] _args; + + public StreamableCallable() { + + } + + public StreamableCallable(Class> classCallable, + byte constructorNumber, Object... args) { + _classCallable = classCallable; + _constructorNumber = constructorNumber; + _args = args; + } + + @Override + public Object call() throws Exception { + @SuppressWarnings("unchecked") + // Unfortunately getConstructors doesn't return typed constructors + // correctly so we have to cast + Constructor> constructor = + (Constructor>) _classCallable + .getConstructors()[_constructorNumber]; + Callable callable = constructor.newInstance(_args); + return callable.call(); + } + + @Override + public void writeTo(DataOutputStream out) throws IOException { + Util.writeClass(_classCallable, out); + out.writeByte(_constructorNumber); + out.writeByte(_args.length); + for (Object arg : _args) { + try { + Util.writeObject(arg, out); + } + catch (Exception e) { + throw new IOException("failed to write arg " + arg); + } + } + } + + @SuppressWarnings("unchecked") + @Override + public void readFrom(DataInputStream in) throws IOException, + IllegalAccessException, InstantiationException { + try { + _classCallable = (Class>)Util.readClass(in); + } + catch (ClassNotFoundException e) { + throw new IOException("failed to read class from classname", e); + } + _constructorNumber = in.readByte(); + short numberOfArgs = in.readByte(); + _args = new Object[numberOfArgs]; + for (int i = 0; i < numberOfArgs; ++i) { + try { + _args[i] = Util.readObject(in); + } + catch (Exception e) { + throw new IOException("failed to read arg", e); + } + } + } + + // @see java.lang.Object#toString() + @Override + public String toString() { + return "StreamableCallable [class=" + _classCallable + + ", constructor=" + _constructorNumber + ", arguments=" + + Arrays.toString(_args) + "]"; + } + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/executor/ExecutorEvent.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/executor/ExecutorEvent.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/executor/ExecutorEvent.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/executor/ExecutorEvent.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,25 @@ +package org.jgroups.blocks.executor; + +import org.jgroups.Event; + +/** + * Defines an event class for the execution of an entity. + * + * @author wburns + */ +public class ExecutorEvent extends Event { + + public static final int TASK_SUBMIT = 1024; // arg = Runnable (Serializable) + public static final int CONSUMER_READY = 1025; // arg = null + public static final int TASK_COMPLETE = 1026; // arg = [Runnable, Throwable] or Runnable + public static final int TASK_CANCEL = 1027; // arg = [Runnable, boolean] + public static final int ALL_TASK_CANCEL = 1028; // arg = [Set, boolean] + + /** + * @param type + * @param arg + */ + public ExecutorEvent(int type, Object arg) { + super(type, arg); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/executor/ExecutorNotification.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/executor/ExecutorNotification.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/executor/ExecutorNotification.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/executor/ExecutorNotification.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,12 @@ +package org.jgroups.blocks.executor; + +/** + * @author wburns + */ +public interface ExecutorNotification { + public void resultReturned(Object obj); + + public void throwableEncountered(Throwable t); + + public void interrupted(Runnable runnable); +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/GridFile.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/GridFile.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/GridFile.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/GridFile.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,390 @@ +package org.jgroups.blocks; + +import org.jgroups.util.Streamable; +import org.jgroups.util.Util; +import org.jgroups.annotations.Experimental; + +import java.io.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.Set; + +/** + * Subclass of File to iterate through directories and files in a grid + * @author Bela Ban + */ +@Experimental +public class GridFile extends File { + private static final long serialVersionUID=-6729548421029004260L; + private final ReplCache cache; + private final GridFilesystem fs; + private final String name; + private int chunk_size; + + GridFile(String pathname, ReplCache cache, int chunk_size, GridFilesystem fs) { + super(pathname); + this.fs=fs; + this.name=trim(pathname); + this.cache=cache; + this.chunk_size=chunk_size; + initMetadata(); + } + + GridFile(String parent, String child, ReplCache cache, int chunk_size, GridFilesystem fs) { + super(parent, child); + this.fs=fs; + this.name=trim(parent + File.separator + child); + this.cache=cache; + this.chunk_size=chunk_size; + initMetadata(); + } + + GridFile(File parent, String child, ReplCache cache, int chunk_size, GridFilesystem fs) { + super(parent, child); + this.fs=fs; + this.name=trim(parent.getAbsolutePath() + File.separator + child); + this.cache=cache; + this.chunk_size=chunk_size; + initMetadata(); + } + + public String getName() { + return name; + } + + public String getPath() { + String my_path=super.getPath(); + if(my_path != null && my_path.endsWith(File.separator)) { + int index=my_path.lastIndexOf(File.separator); + if(index != -1) + my_path=my_path.substring(0, index); + } + return my_path; + } + + public long length() { + Metadata metadata=cache.get(getPath()); + if(metadata != null) + return metadata.length; + return 0; + } + + void setLength(int new_length) { + Metadata metadata=cache.get(getPath()); + if(metadata != null) { + metadata.length=new_length; + metadata.setModificationTime(System.currentTimeMillis()); + cache.put(getPath(), metadata, (short)-1, 0, false); + } + else + System.err.println("metadata for " + getPath() + " not found !"); + } + + public int getChunkSize() { + return chunk_size; + } + + public boolean createNewFile() throws IOException { + if(exists()) + return true; + if(!checkParentDirs(getPath(), false)) + return false; + cache.put(getPath(), new Metadata(0, System.currentTimeMillis(), chunk_size, Metadata.FILE), (short)-1, 0, true); + return true; + } + + public boolean delete() { + return delete(false); // asynchronous delete by default + } + + public boolean delete(boolean synchronous) { + if(!exists()) + return false; + if(isFile()) { + fs.remove(getPath(), synchronous); // removes all the chunks belonging to the file + cache.remove(getPath(), synchronous); // removes the metadata information + return true; + } + if(isDirectory()) { + File[] files=listFiles(); + if(files != null && files.length > 0) + return false; + fs.remove(getPath(), synchronous); // removes all the chunks belonging to the file + cache.remove(getPath(), synchronous); // removes the metadata information + } + return true; + } + + public boolean mkdir() { + try { + boolean parents_exist=checkParentDirs(getPath(), false); + if(!parents_exist) + return false; + cache.put(getPath(), new Metadata(0, System.currentTimeMillis(), chunk_size, Metadata.DIR), (short)-1, 0, true); + return true; + } + catch(IOException e) { + e.printStackTrace(); + return false; + } + } + + public boolean mkdirs() { + try { + boolean parents_exist=checkParentDirs(getPath(), true); + if(!parents_exist) + return false; + cache.put(getPath(), new Metadata(0, System.currentTimeMillis(), chunk_size, Metadata.DIR), (short)-1, 0, true); + return true; + } + catch(IOException e) { + return false; + } + } + + public boolean exists() { + return cache.get(getPath()) != null; + } + + public String[] list() { + return list(null); + } + + public String[] list(FilenameFilter filter) { + return _list(filter); + } + + public File[] listFiles() { + return listFiles((FilenameFilter)null); + } + + public File[] listFiles(FilenameFilter filter) { + return _listFiles(filter); + } + + public File[] listFiles(FileFilter filter) { + return _listFiles(filter); + } + + + public boolean isDirectory() { + Metadata val=cache.get(getPath()); + return val.isDirectory(); + } + + public boolean isFile() { + Metadata val=cache.get(getPath()); + return val.isFile(); + } + + protected void initMetadata() { + Metadata metadata=cache.get(getPath()); + if(metadata != null) + this.chunk_size=metadata.getChunkSize(); + } + + + protected File[] _listFiles(Object filter) { + String[] files=_list(filter); + File[] retval=new File[files.length]; + for(int i=0; i < files.length; i++) + retval[i]=new GridFile(files[i], cache, chunk_size, fs); + return retval; + } + + + protected String[] _list(Object filter) { + Cache> internal_cache=cache.getL2Cache(); + Set keys=internal_cache.getInternalMap().keySet(); + if(keys == null) + return null; + Collection list=new ArrayList(keys.size()); + for(String str: keys) { + if(isChildOf(getPath(), str)) { + if(filter instanceof FilenameFilter && !((FilenameFilter)filter).accept(new File(name), filename(str))) + continue; + else if(filter instanceof FileFilter && !((FileFilter)filter).accept(new File(str))) + continue; + list.add(str); + } + } + String[] retval=new String[list.size()]; + int index=0; + for(String tmp: list) + retval[index++]=tmp; + return retval; + } + + /** + * Verifies whether child is a child (dir or file) of parent + * @param parent + * @param child + * @return True if child is a child, false otherwise + */ + protected static boolean isChildOf(String parent, String child) { + if(parent == null || child == null) + return false; + if(!child.startsWith(parent)) + return false; + if(child.length() <= parent.length()) + return false; + int from=parent.equals(File.separator)? parent.length() : parent.length() +1; + // if(from-1 > child.length()) + // return false; + String[] comps=Util.components(child.substring(from), File.separator); + return comps != null && comps.length <= 1; + } + + protected static String filename(String full_path) { + String[] comps=Util.components(full_path, File.separator); + return comps != null? comps[comps.length -1] : null; + } + + + + /** + * Checks whether the parent directories are present (and are directories). If create_if_absent is true, + * creates missing dirs + * @param path + * @param create_if_absent + * @return + */ + protected boolean checkParentDirs(String path, boolean create_if_absent) throws IOException { + String[] components=Util.components(path, File.separator); + if(components == null) + return false; + if(components.length == 1) // no parent directories to create, e.g. "data.txt" + return true; + + StringBuilder sb=new StringBuilder(); + boolean first=true; + + for(int i=0; i < components.length-1; i++) { + String tmp=components[i]; + if(!tmp.equals(File.separator)) { + if(first) + first=false; + else + sb.append(File.separator); + } + sb.append(tmp); + String comp=sb.toString(); + if(exists(comp)) { + if(isFile(comp)) + throw new IOException("cannot create " + path + " as component " + comp + " is a file"); + } + else { + if(create_if_absent) + cache.put(comp, new Metadata(0, System.currentTimeMillis(), chunk_size, Metadata.DIR), (short)-1, 0); + else + return false; + } + } + return true; + } + + + + protected static String trim(String str) { + if(str == null) return null; + str=str.trim(); + if(str.equals(File.separator)) + return str; + String[] comps=Util.components(str, File.separator); + return comps != null && comps.length > 0? comps[comps.length-1] : null; + } + + private boolean exists(String key) { + return cache.get(key) != null; + } + + private boolean isFile(String key) { + Metadata val=cache.get(key); + return val.isFile(); + } + + + + + public static class Metadata implements Streamable { + public static final byte FILE = 1 << 0; + public static final byte DIR = 1 << 1; + + private int length =0; + private long modification_time=0; + private int chunk_size=0; + private byte flags=0; + + + public Metadata() { + } + + public Metadata(int length, long modification_time, int chunk_size, byte flags) { + this.length=length; + this.modification_time=modification_time; + this.chunk_size=chunk_size; + this.flags=flags; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length=length; + } + + public long getModificationTime() { + return modification_time; + } + + public void setModificationTime(long modification_time) { + this.modification_time=modification_time; + } + + public int getChunkSize() { + return chunk_size; + } + + public boolean isFile() { + return Util.isFlagSet(flags, FILE); + } + + public boolean isDirectory() { + return Util.isFlagSet(flags, DIR); + } + + public String toString() { + boolean is_file=Util.isFlagSet(flags, FILE); + StringBuilder sb=new StringBuilder(); + sb.append(getType()); + if(is_file) + sb.append(", len=" + Util.printBytes(length) + ", chunk_size=" + chunk_size); + sb.append(", mod_time=" + new Date(modification_time)); + return sb.toString(); + } + + public void writeTo(DataOutputStream out) throws IOException { + out.writeInt(length); + out.writeLong(modification_time); + out.writeInt(chunk_size); + out.writeByte(flags); + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + length=in.readInt(); + modification_time=in.readLong(); + chunk_size=in.readInt(); + flags=in.readByte(); + } + + private String getType() { + if(Util.isFlagSet(flags, FILE)) + return "file"; + if(Util.isFlagSet(flags, DIR)) + return "dir"; + return "n/a"; + } + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/GridFilesystem.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/GridFilesystem.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/GridFilesystem.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/GridFilesystem.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,108 @@ +package org.jgroups.blocks; + +import org.jgroups.annotations.Experimental; + +import java.io.*; + +/** + * Entry point for GridFile and GridInputStream / GridOutputStream + * @author Bela Ban + */ +@Experimental +public class GridFilesystem { + protected final ReplCache data; + protected final ReplCache metadata; + protected final int default_chunk_size; + protected final short default_repl_count; + + + + /** + * Creates an instance. The data and metadata caches should already have been setup and started + * @param data + * @param metadata + * @param default_chunk_size + */ + public GridFilesystem(ReplCache data, ReplCache metadata, + short default_repl_count, int default_chunk_size) { + this.data=data; + this.metadata=metadata; + this.default_chunk_size=default_chunk_size; + this.default_repl_count=default_repl_count; + } + + public GridFilesystem(ReplCache data, ReplCache metadata) { + this(data, metadata, (short)1, 8000); + } + + public File getFile(String pathname) { + return getFile(pathname, default_chunk_size); + } + + public File getFile(String pathname, int chunk_size) { + return new GridFile(pathname, metadata, chunk_size, this); + } + + public File getFile(String parent, String child) { + return getFile(parent, child, default_chunk_size); + } + + public File getFile(String parent, String child, int chunk_size) { + return new GridFile(parent, child, metadata, chunk_size, this); + } + + public File getFile(File parent, String child) { + return getFile(parent, child, default_chunk_size); + } + + public File getFile(File parent, String child, int chunk_size) { + return new GridFile(parent, child, metadata, chunk_size, this); + } + + public OutputStream getOutput(String pathname) throws IOException { + return getOutput(pathname, false, default_repl_count, default_chunk_size); + } + + public OutputStream getOutput(String pathname, boolean append) throws IOException { + return getOutput(pathname, append, default_repl_count, default_chunk_size); + } + + public OutputStream getOutput(String pathname, boolean append, short repl_count, int chunk_size) throws IOException { + GridFile file=(GridFile)getFile(pathname, chunk_size); + if(!file.createNewFile()) + throw new IOException("creation of " + pathname + " failed"); + + return new GridOutputStream(file, append, data, repl_count, chunk_size); + } + + public OutputStream getOutput(GridFile file) throws IOException { + if(!file.createNewFile()) + throw new IOException("creation of " + file + " failed"); + return new GridOutputStream(file, false, data, default_repl_count, default_chunk_size); + } + + + + public InputStream getInput(String pathname) throws FileNotFoundException { + GridFile file=(GridFile)getFile(pathname); + if(!file.exists()) + throw new FileNotFoundException(pathname); + return new GridInputStream(file, data, default_chunk_size); + } + + public InputStream getInput(File pathname) throws FileNotFoundException { + return pathname != null? getInput(pathname.getPath()) : null; + } + + + public void remove(String path, boolean synchronous) { + if(path == null) + return; + GridFile.Metadata md=metadata.get(path); + if(md == null) + return; + int num_chunks=md.getLength() / md.getChunkSize() + 1; + for(int i=0; i < num_chunks; i++) + data.remove(path + ".#" + i, synchronous); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/GridInputStream.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/GridInputStream.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/GridInputStream.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/GridInputStream.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,116 @@ +package org.jgroups.blocks; + +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.annotations.Experimental; + +import java.io.*; + +/** + * @author Bela Ban + */ +@Experimental +public class GridInputStream extends InputStream { + final ReplCache cache; + final int chunk_size; + final String name; + protected final GridFile file; // file representing this input stream + int index=0; // index into the file for writing + int local_index=0; + byte[] current_buffer=null; + boolean end_reached=false; + final static Log log=LogFactory.getLog(GridInputStream.class); + + + + GridInputStream(GridFile file, ReplCache cache, int chunk_size) throws FileNotFoundException { + this.file=file; + this.name=file.getPath(); + this.cache=cache; + this.chunk_size=chunk_size; + } + + + + public int read() throws IOException { + int bytes_remaining_to_read=getBytesRemainingInChunk(); + if(bytes_remaining_to_read == 0) { + if(end_reached) + return -1; + current_buffer=fetchNextChunk(); + local_index=0; + if(current_buffer == null) + return -1; + else if(current_buffer.length < chunk_size) + end_reached=true; + bytes_remaining_to_read=getBytesRemainingInChunk(); + } + int retval=current_buffer[local_index++]; + index++; + return retval; + } + + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + public int read(byte[] b, int off, int len) throws IOException { + int bytes_read=0; + while(len > 0) { + int bytes_remaining_to_read=getBytesRemainingInChunk(); + if(bytes_remaining_to_read == 0) { + if(end_reached) + return bytes_read > 0? bytes_read : -1; + current_buffer=fetchNextChunk(); + local_index=0; + if(current_buffer == null) + return bytes_read > 0? bytes_read : -1; + else if(current_buffer.length < chunk_size) + end_reached=true; + bytes_remaining_to_read=getBytesRemainingInChunk(); + } + int bytes_to_read=Math.min(len, bytes_remaining_to_read); + // bytes_to_read=Math.min(bytes_to_read, current_buffer.length - local_index); + System.arraycopy(current_buffer, local_index, b, off, bytes_to_read); + local_index+=bytes_to_read; + off+=bytes_to_read; + len-=bytes_to_read; + bytes_read+=bytes_to_read; + index+=bytes_to_read; + } + + return bytes_read; + } + + public long skip(long n) throws IOException { + throw new UnsupportedOperationException(); + } + + public int available() throws IOException { + throw new UnsupportedOperationException(); + } + + public void close() throws IOException { + local_index=index=0; + end_reached=false; + } + + private int getBytesRemainingInChunk() { + // return chunk_size - local_index; + return current_buffer == null? 0 : current_buffer.length - local_index; + } + + private byte[] fetchNextChunk() { + int chunk_number=getChunkNumber(); + String key=name + ".#" + chunk_number; + byte[] val= cache.get(key); + if(log.isTraceEnabled()) + log.trace("fetching index=" + index + ", key=" + key +": " + (val != null? val.length + " bytes" : "null")); + return val; + } + + private int getChunkNumber() { + return index / chunk_size; + } + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/GridOutputStream.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/GridOutputStream.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/GridOutputStream.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/GridOutputStream.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,109 @@ +package org.jgroups.blocks; + +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.annotations.Experimental; + +import java.io.*; + +/** + * @author Bela Ban + */ +@Experimental +public class GridOutputStream extends OutputStream { + final ReplCache cache; + final short repl_count; + final int chunk_size; + final String name; + protected final GridFile file; // file representing this output stream + int index=0; // index into the file for writing + int local_index=0; + final byte[] current_buffer; + static final Log log=LogFactory.getLog(GridOutputStream.class); + + + + GridOutputStream(GridFile file, boolean append, ReplCache cache, + short repl_count, int chunk_size) throws FileNotFoundException { + this.file=file; + this.name=file.getPath(); + this.cache=cache; + this.repl_count=repl_count; + this.chunk_size=chunk_size; + current_buffer=new byte[chunk_size]; + } + + + + + public void write(int b) throws IOException { + int remaining=getBytesRemainingInChunk(); + if(remaining == 0) { + flush(); + local_index=0; + remaining=chunk_size; + } + current_buffer[local_index]=(byte)b; + local_index++; + index++; + } + + + public void write(byte[] b) throws IOException { + if(b != null) + write(b, 0, b.length); + } + + + + public void write(byte[] b, int off, int len) throws IOException { + while(len > 0) { + int remaining=getBytesRemainingInChunk(); + if(remaining == 0) { + flush(); + local_index=0; + remaining=chunk_size; + } + int bytes_to_write=Math.min(remaining, len); + System.arraycopy(b, off, current_buffer, local_index, bytes_to_write); + off+=bytes_to_write; + len-=bytes_to_write; + local_index+=bytes_to_write; + index+=bytes_to_write; + } + } + + + public void close() throws IOException { + flush(); + reset(); + } + + public void flush() throws IOException { + int chunk_number=getChunkNumber(); + String key=name + ".#" + chunk_number; + byte[] val=new byte[local_index]; + System.arraycopy(current_buffer, 0, val, 0, local_index); + cache.put(key, val, repl_count, 0); + if(log.isTraceEnabled()) + log.trace("put(): index=" + index + ", key=" + key + ": " + val.length + " bytes"); + file.setLength(index); + } + + private int getBytesRemainingInChunk() { + return chunk_size - local_index; + } + + + + private int getChunkNumber() { + return (index-1) / chunk_size; + } + + private void reset() { + index=local_index=0; + } + + + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/GroupRequest.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/GroupRequest.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/GroupRequest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/GroupRequest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,22 +1,18 @@ package org.jgroups.blocks; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.Address; import org.jgroups.Message; import org.jgroups.Transport; import org.jgroups.View; import org.jgroups.annotations.GuardedBy; -import org.jgroups.util.Command; import org.jgroups.util.Rsp; import org.jgroups.util.RspList; import java.util.*; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.TimeoutException; /** @@ -51,227 +47,62 @@ * confirmation. * * @author Bela Ban - * @version $Id: GroupRequest.java,v 1.37 2008/11/14 17:08:32 belaban Exp $ */ -public class GroupRequest implements RspCollector, Command { - /** return only first response */ - public static final int GET_FIRST=1; +public class GroupRequest extends Request { - /** return all responses */ - public static final int GET_ALL=2; - - /** return majority (of all non-faulty members) */ - public static final int GET_MAJORITY=3; - - /** return majority (of all members, may block) */ - public static final int GET_ABS_MAJORITY=4; - - /** return n responses (may block) */ - public static final int GET_N=5; - - /** return no response (async call) */ - public static final int GET_NONE=6; - - /** keep suspects vector bounded */ - private static final int MAX_SUSPECTS=40; - - private static final Log log=LogFactory.getLog(GroupRequest.class); - - /** to generate unique request IDs (see getRequestId()) */ - private static long last_req_id=1; - - private Address caller; - - private final Lock lock=new ReentrantLock(); - - /** Is set as soon as the request has received all required responses */ - private final Condition completed=lock.newCondition(); - - /** Map. Maps requests and responses */ + /** Correlates requests and responses */ @GuardedBy("lock") - private final Map requests=new HashMap(); + private final Map requests; - - /** bounded queue of suspected members */ - @GuardedBy("lock") - private final List
    suspects=new ArrayList
    (); - - /** list of members, changed by viewChange() */ @GuardedBy("lock") - private final Collection
    members=new TreeSet
    (); - - protected final Message request_msg; - protected final RequestCorrelator corr; // either use RequestCorrelator or ... - protected final Transport transport; // Transport (one of them has to be non-null) - - protected RspFilter rsp_filter=null; - - protected final int rsp_mode; - protected volatile boolean done=false; - protected final long timeout; - protected final int expected_mbrs; - - private final long req_id; // request ID for this request - + int num_received, num_not_received, num_suspected; - /** - * @param m - * The message to be sent - * @param corr - * The request correlator to be used. A request correlator - * sends requests tagged with a unique ID and notifies the - * sender when matching responses are received. The reason - * GroupRequest uses it instead of a - * Transport is that multiple - * requests/responses might be sent/received concurrently. - * @param members - * The initial membership. This value reflects the membership - * to which the request is sent (and from which potential - * responses are expected). Is reset by reset(). - * @param rsp_mode - * How many responses are expected. Can be - *
      - *
    1. GET_ALL: wait for all responses from - * non-suspected members. A suspicion service might warn us - * when a member from which a response is outstanding has - * crashed, so it can be excluded from the responses. If no - * suspicion service is available, a timeout can be used (a - * value of 0 means wait forever). If a timeout of - * 0 is used, no suspicion service is available and a member from which we - * expect a response has crashed, this methods blocks forever !. - *
    2. GET_FIRST: wait for the first - * available response. - *
    3. GET_MAJORITY: wait for the majority - * of all responses. The majority is re-computed when a - * member is suspected. - *
    4. GET_ABS_MAJORITY: wait for the - * majority of all members. This includes failed - * members, so it may block if no timeout is specified. - *
    5. GET_N: wait for N members. Return if - * n is >= membership+suspects. - *
    6. GET_NONE: don't wait for any - * response. Essentially send an asynchronous message to the - * group members. - *
    + + /** + * @param msg The message to be sent + * @param corr The request correlator to be used. A request correlator sends requests tagged with a unique ID and + * notifies the sender when matching responses are received. The reason GroupRequest uses + * it instead of a Transport is that multiple requests/responses might be sent/received concurrently + * @param targets The targets, which are supposed to receive the message. Any receiver not in this set will + * discard the message. Targets are always a subset of the current membership + * @param options The request options to be used for this call */ - public GroupRequest(Message m, RequestCorrelator corr, Vector
    members, int rsp_mode) { - this(m,corr,members,rsp_mode,0,0); + public GroupRequest(Message msg, RequestCorrelator corr, Collection
    targets, RequestOptions options) { + super(msg, corr, null, options); + int size=targets.size(); + requests=new HashMap(size); + setTargets(targets); } - - /** - @param timeout Time to wait for responses (ms). A value of <= 0 means wait indefinitely - (e.g. if a suspicion service is available; timeouts are not needed). - */ - public GroupRequest(Message m, RequestCorrelator corr, Vector
    mbrs, int rsp_mode, - long timeout, int expected_mbrs) { - this.request_msg=m; - this.transport = null; - this.corr=corr; - this.rsp_mode=rsp_mode; - this.req_id=getRequestId(); - this.timeout=timeout; - this.expected_mbrs=expected_mbrs; - if(mbrs != null) { - for(Address mbr: mbrs) { - requests.put(mbr, new Rsp(mbr)); - } - this.members.clear(); - this.members.addAll(mbrs); - } + public GroupRequest(Message m, RequestCorrelator corr, Address target, RequestOptions options) { + super(m, corr, null, options); + requests=new HashMap(1); + setTarget(target); } - public GroupRequest(Message m, Transport transport, Vector
    members, int rsp_mode) { - this(m,transport,members,rsp_mode,0,0); + public GroupRequest(Message m, Transport transport, Collection
    mbrs, RequestOptions options) { + super(m, null, transport, options); + int size=mbrs.size(); + requests=new HashMap(size); + setTargets(mbrs); } - /** - * @param timeout Time to wait for responses (ms). A value of <= 0 means wait indefinitely - * (e.g. if a suspicion service is available; timeouts are not needed). - */ - public GroupRequest(Message m, - Transport transport, - Vector
    mbrs, - int rsp_mode, - long timeout, - int expected_mbrs) { - this.corr=null; - this.request_msg=m; - this.transport=transport; - this.rsp_mode=rsp_mode; - this.req_id=getRequestId(); - this.timeout=timeout; - this.expected_mbrs=expected_mbrs; - if(mbrs != null) { - for(Address mbr: mbrs) { - requests.put(mbr, new Rsp(mbr)); - } - this.members.clear(); - this.members.addAll(mbrs); - } - } - public Address getCaller() { - return caller; - } - - public void setCaller(Address caller) { - this.caller=caller; + public boolean getAnycasting() { + return options.getAnycasting(); } - public void setResponseFilter(RspFilter filter) { - rsp_filter=filter; + public void setAnycasting(boolean anycasting) { + options.setAnycasting(anycasting); } - public boolean execute() throws Exception { - return execute(false); - } - /** - * Sends the message. Returns when n responses have been received, or a - * timeout has occurred. n can be the first response, all - * responses, or a majority of the responses. - */ - public boolean execute(boolean use_anycasting) throws Exception { - if(corr == null && transport == null) { - if(log.isErrorEnabled()) log.error("both corr and transport are null, cannot send group request"); - return false; - } - - Vector
    targets=null; - lock.lock(); - try { - targets=new Vector
    (members); - for(Address suspect: suspects) { // mark all suspects in 'received' array - Rsp rsp=requests.get(suspect); - if(rsp != null) { - rsp.setSuspected(true); - break; // we can break here because we ensure there are no duplicate members - } - } - } - finally { - lock.unlock(); - } - - sendRequest(targets, req_id, use_anycasting); - - lock.lock(); - try { - done=false; - boolean retval=collectResponses(timeout); - if(retval == false && log.isTraceEnabled()) - log.trace("call did not execute correctly, request is " + this.toString()); - return retval; - } - finally { - done=true; - lock.unlock(); - } + public void sendRequest() throws Exception { + sendRequest(requests.keySet(), req_id); } /* ---------------------- Interface RspCollector -------------------------- */ @@ -281,38 +112,35 @@ * execute() returns. */ public void receiveResponse(Object response_value, Address sender) { + if(done) + return; + Rsp rsp=requests.get(sender); + if(rsp == null) + return; + + RspFilter rsp_filter=options.getRspFilter(); + boolean responseReceived=false; + lock.lock(); try { - if(done) { - if(log.isTraceEnabled()) - log.trace("command is done; cannot add response !"); - } - else if(suspects.contains(sender)) { - if(log.isWarnEnabled()) - log.warn("received response from suspected member " + sender + "; discarding"); - } - else { - Rsp rsp=requests.get(sender); - if(rsp != null) { - if(!rsp.wasReceived()) { - boolean responseReceived =(rsp_filter == null) || rsp_filter.isAcceptable(response_value, sender); - rsp.setValue(response_value); - rsp.setReceived(responseReceived); - if(log.isTraceEnabled()) - log.trace(new StringBuilder("received response for request ").append(req_id) - .append(", sender=") - .append(sender) - .append(", val=") - .append(response_value)); - } - done=rsp_filter != null && !rsp_filter.needMoreResponses(); - } + if(!rsp.wasReceived()) { + if((responseReceived=(rsp_filter == null) || rsp_filter.isAcceptable(response_value, sender))) + rsp.setValue(response_value); + rsp.setReceived(responseReceived); } + if(responseReceived) + num_received++; + done=rsp_filter == null? responsesComplete() : !rsp_filter.needMoreResponses(); + if(responseReceived || done) + completed.signalAll(); // wakes up execute() + if(done && corr != null) + corr.done(req_id); } finally { - completed.signalAll(); // wakes up execute() lock.unlock(); } + if(responseReceived || done) + checkCompletion(this); } @@ -326,19 +154,25 @@ if(suspected_member == null) return; - lock.lock(); - try { - addSuspect(suspected_member); - Rsp rsp=requests.get(suspected_member); - if(rsp != null) { - rsp.setSuspected(true); + boolean changed=false; + Rsp rsp=requests.get(suspected_member); + if(rsp != null) { + if(rsp.setSuspected(true)) { rsp.setValue(null); - completed.signalAll(); + changed=true; + lock.lock(); + try { + num_suspected++; + completed.signalAll(); + } + finally { + lock.unlock(); + } } } - finally { - lock.unlock(); - } + + if(changed) + checkCompletion(this); } @@ -360,42 +194,35 @@ * */ public void viewChange(View new_view) { - Address mbr; Vector
    mbrs=new_view != null? new_view.getMembers() : null; + if(mbrs == null) + return; - lock.lock(); - try { - if(requests == null || requests.isEmpty() || mbrs == null) + boolean changed=false; + if(requests == null || requests.isEmpty()) return; - this.members.clear(); - this.members.addAll(mbrs); - - Rsp rsp; - Set
    tmp=null; + lock.lock(); + try { for(Map.Entry entry: requests.entrySet()) { - mbr=entry.getKey(); + Address mbr=entry.getKey(); if(!mbrs.contains(mbr)) { - if(tmp == null) - tmp=new HashSet
    (); - tmp.add(mbr); - addSuspect(mbr); - rsp=entry.getValue(); + Rsp rsp=entry.getValue(); rsp.setValue(null); - rsp.setSuspected(true); + if(rsp.setSuspected(true)) { + num_suspected++; + changed=true; + } } } - - if(tmp != null) { - for(Address suspect: tmp) { - addSuspect(suspect); - } + if(changed) completed.signalAll(); - } } finally { lock.unlock(); } + if(changed) + checkCompletion(this); } @@ -405,135 +232,78 @@ /** Returns the results as a RspList */ public RspList getResults() { + Collection rsps=requests.values(); + return new RspList(rsps); + } + + + + public RspList get() throws InterruptedException, ExecutionException { lock.lock(); try { - Collection rsps=requests.values(); - return new RspList(rsps); + waitForResults(0); } finally { lock.unlock(); } + return getResults(); } - - public String toString() { - StringBuilder ret=new StringBuilder(128); - ret.append("[req_id=").append(req_id).append('\n'); - if(caller != null) - ret.append("caller=").append(caller).append("\n"); - + public RspList get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + boolean ok; lock.lock(); try { - if(!requests.isEmpty()) { - ret.append("entries:\n"); - for(Map.Entry entry: requests.entrySet()) { - Address mbr=entry.getKey(); - Rsp rsp=entry.getValue(); - ret.append(mbr).append(": ").append(rsp).append("\n"); - } - } + ok=waitForResults(unit.toMillis(timeout)); } finally { lock.unlock(); } - return ret.toString(); + if(!ok) + throw new TimeoutException(); + return getResults(); } + public String toString() { + StringBuilder ret=new StringBuilder(128); + ret.append(super.toString()); - public int getNumSuspects() { - return suspects.size(); - } - - /** Returns the list of suspected members. An attempt to modify the return value will throw an excxeption */ - public Vector
    getSuspects() { - return new Vector
    (suspects); - } - - - public boolean isDone() { - return done; + if(!requests.isEmpty()) { + ret.append(", entries:\n"); + for(Map.Entry entry: requests.entrySet()) { + Address mbr=entry.getKey(); + Rsp rsp=entry.getValue(); + ret.append(mbr).append(": ").append(rsp).append("\n"); + } + } + return ret.toString(); } - /* --------------------------------- Private Methods -------------------------------------*/ - private static int determineMajority(int i) { - return i < 2? i : (i / 2) + 1; + private void setTarget(Address mbr) { + requests.put(mbr, new Rsp(mbr)); + num_not_received=1; } - /** Generates a new unique request ID */ - private static synchronized long getRequestId() { - long result=System.currentTimeMillis(); - if(result <= last_req_id) { - result=last_req_id + 1; - } - last_req_id=result; - return result; + private void setTargets(Collection
    mbrs) { + for(Address mbr: mbrs) + requests.put(mbr, new Rsp(mbr)); + num_not_received=requests.size(); } - /** This method runs with lock locked (called by execute()). */ - @GuardedBy("lock") - private boolean collectResponses(long timeout) throws Exception { - if(timeout <= 0) { - while(true) { /* Wait for responses: */ - adjustMembership(); // may not be necessary, just to make sure... - if(responsesComplete()) { - if(corr != null) { - corr.done(req_id); - } - if(log.isTraceEnabled() && rsp_mode != GET_NONE) { - log.trace("received all responses: " + toString()); - } - return true; - } - try { - completed.await(); - } - catch(Exception e) { - } - } - } - else { - long start_time=System.currentTimeMillis(); - long timeout_time=start_time + timeout; - while(timeout > 0) { /* Wait for responses: */ - if(responsesComplete()) { - if(corr != null) - corr.done(req_id); - if(log.isTraceEnabled() && rsp_mode != GET_NONE) { - log.trace("received all responses: " + toString()); - } - return true; - } - timeout=timeout_time - System.currentTimeMillis(); - if(timeout > 0) { - try { - completed.await(timeout, TimeUnit.MILLISECONDS); - } - catch(Exception e) { - } - } - } - if(corr != null) { - corr.done(req_id); - } - if(log.isTraceEnabled()) - log.trace("timed out waiting for responses"); - - return false; - } + private static int determineMajority(int i) { + return i < 2? i : (i / 2) + 1; } - private void sendRequest(Vector
    targetMembers, long requestId,boolean use_anycasting) throws Exception { + private void sendRequest(final Collection
    targetMembers, long requestId) throws Exception { try { - if(log.isTraceEnabled()) log.trace(new StringBuilder("sending request (id=").append(req_id).append(')')); - if(corr != null) { - corr.sendRequest(requestId, targetMembers, request_msg, rsp_mode == GET_NONE? null : this, use_anycasting); + if(corr != null) { + corr.sendRequest(requestId, targetMembers, request_msg, options.getMode() == GET_NONE? null : this, options); } else { - if(use_anycasting) { + if(options.getAnycasting()) { for(Address mbr: targetMembers) { Message copy=request_msg.copy(true); copy.setDest(mbr); @@ -554,28 +324,13 @@ @GuardedBy("lock") - private boolean responsesComplete() { - int num_received=0, num_not_received=0, num_suspected=0; - final int num_total=requests.size(); - + protected boolean responsesComplete() { if(done) return true; - for(Rsp rsp: requests.values()) { - if(rsp.wasReceived()) { - num_received++; - } - else { - if(rsp.wasSuspected()) { - num_suspected++; - } - else { - num_not_received++; - } - } - } + final int num_total=requests.size(); - switch(rsp_mode) { + switch(options.getMode()) { case GET_FIRST: if(num_received > 0) return true; @@ -596,14 +351,11 @@ return true; break; case GET_N: - if(expected_mbrs >= num_total) { - return responsesComplete(); - } - return num_received >= expected_mbrs || num_received + num_not_received < expected_mbrs && num_received + num_suspected >= expected_mbrs; + return true; case GET_NONE: return true; default : - if(log.isErrorEnabled()) log.error("rsp_mode " + rsp_mode + " unknown !"); + if(log.isErrorEnabled()) log.error("rsp_mode " + options.getMode() + " unknown !"); break; } return false; @@ -612,60 +364,4 @@ - - /** - * Adjusts the 'received' array in the following way: - *
      - *
    • if a member P in 'membership' is not in 'members', P's entry in the 'received' array - * will be marked as SUSPECTED - *
    • if P is 'suspected_mbr', then P's entry in the 'received' array will be marked - * as SUSPECTED - *
    - * This call requires exclusive access to rsp_mutex (called by getResponses() which has - * a the rsp_mutex locked, so this should not be a problem). This method needs to have lock held. - */ - @GuardedBy("lock") - private void adjustMembership() { - if(requests.isEmpty()) - return; - - Address mbr; - Rsp rsp; - for(Map.Entry entry: requests.entrySet()) { - mbr=entry.getKey(); - if((!this.members.contains(mbr)) || suspects.contains(mbr)) { - addSuspect(mbr); - rsp=entry.getValue(); - rsp.setValue(null); - rsp.setSuspected(true); - } - } - } - - /** - * Adds a member to the 'suspects' list. Removes oldest elements from 'suspects' list - * to keep the list bounded ('max_suspects' number of elements), Requires lock to be held - */ - @GuardedBy("lock") - private void addSuspect(Address suspected_mbr) { - if(!suspects.contains(suspected_mbr)) { - suspects.add(suspected_mbr); - while(suspects.size() >= MAX_SUSPECTS && !suspects.isEmpty()) - suspects.remove(0); // keeps queue bounded - } - } - - private static String modeToString(int m) { - switch(m) { - case GET_FIRST: return "GET_FIRST"; - case GET_ALL: return "GET_ALL"; - case GET_MAJORITY: return "GET_MAJORITY"; - case GET_ABS_MAJORITY: return "GET_ABS_MAJORITY"; - case GET_N: return "GET_N"; - case GET_NONE: return "GET_NONE"; - default: return " (" + m + ")"; - } - } - - } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/LazyRemovalCache.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/LazyRemovalCache.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/LazyRemovalCache.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/LazyRemovalCache.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,253 @@ +package org.jgroups.blocks; + +import org.jgroups.util.Util; + +import java.util.*; +import java.util.concurrent.ConcurrentMap; + +/** + * Cache which doesn't remove elements on remove(), removeAll() or retainAll(), but only removes elements when a + * configurable size limit has been exceeded. In that case, all elements marked as removable and older than a + * configurable time are evicted. Elements are marked as removable by remove(), removeAll() and retainAll(). When + * an elements is marked as removable, but later reinserted, the mark is removed. + * @author Bela Ban + */ +public class LazyRemovalCache { + private final ConcurrentMap> map=Util.createConcurrentMap(); + + /** Max number of elements, if exceeded, we remove all elements marked as removable and older than max_age ms */ + private final int max_elements; + + private final long max_age; + + + public interface Printable { + String print(K key,V val); + } + + + public LazyRemovalCache() { + this(200, 5000L); + } + + public LazyRemovalCache(int max_elements, long max_age) { + this.max_elements=max_elements; + this.max_age=max_age; + } + + public void add(K key, V val) { + if(key != null && val != null) + map.put(key, new Entry(val)); // overwrite existing element (new timestamp, and possible removable mark erased) + checkMaxSizeExceeded(); + } + + public boolean containsKey(K key) { + return map.containsKey(key); + } + + /** Returns true if all of the keys in keys are present. Returns false if one or more of the keys are absent */ + public boolean containsKeys(Collection keys) { + for(K key: keys) + if(!map.containsKey(key)) + return false; + return true; + } + + + public V get(K key) { + if(key == null) + return null; + Entry entry=map.get(key); + return entry != null? entry.val : null; + } + + public K getByValue(V val) { + if(val == null) return null; + for(Map.Entry> entry: map.entrySet()) { + Entry v=entry.getValue(); + if(v.val != null && v.val.equals(val)) + return entry.getKey(); + } + return null; + } + + public void remove(K key) { + remove(key, false); + } + + public void remove(K key, boolean force) { + if(key == null) + return; + if(force) + map.remove(key); + else { + Entry entry=map.get(key); + if(entry != null) + entry.removable=true; + } + checkMaxSizeExceeded(); + } + + public void removeAll(Collection keys) { + removeAll(keys, false); + } + + public void removeAll(Collection keys, boolean force) { + if(keys == null || keys.isEmpty()) + return; + if(force) + map.keySet().removeAll(keys); + else { + for(K key: keys) { + Entry entry=map.get(key); + if(entry != null) + entry.removable=true; + } + } + checkMaxSizeExceeded(); + } + + public void clear(boolean force) { + if(force) + map.clear(); + else { + for(Map.Entry> entry: map.entrySet()) { + Entry val=entry.getValue(); + if(val != null) { + Entry tmp=entry.getValue(); + if(tmp != null) + tmp.removable=true; + } + } + } + } + + public void retainAll(Collection keys) { + retainAll(keys, false); + } + + public void retainAll(Collection keys, boolean force) { + if(keys == null || keys.isEmpty()) + return; + if(force) + map.keySet().retainAll(keys); + else { + for(Map.Entry> entry: map.entrySet()) { + if(!keys.contains(entry.getKey())) { + Entry val=entry.getValue(); + if(val != null) + val.removable=true; + } + } + } + + // now make sure that all elements in keys have removable=false + for(K key: keys) { + Entry val=map.get(key); + if(val != null && val.removable) + val.removable=false; + } + + checkMaxSizeExceeded(); + } + + public Set values() { + Set retval=new HashSet(); + for(Entry entry: map.values()) { + retval.add(entry.val); + } + return retval; + } + + /** + * Adds all value which have not been marked as removable to the returned set + * @return + */ + public Set nonRemovedValues() { + Set retval=new HashSet(); + for(Entry entry: map.values()) { + if(!entry.removable) + retval.add(entry.val); + } + return retval; + } + + public Map contents() { + Map retval=new HashMap(); + for(Map.Entry> entry: map.entrySet()) + retval.put(entry.getKey(), entry.getValue().val); + return retval; + } + + public int size() { + return map.size(); + } + + public String printCache() { + StringBuilder sb=new StringBuilder(); + for(Map.Entry> entry: map.entrySet()) { + sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); + } + return sb.toString(); + } + + public String printCache(Printable print_function) { + StringBuilder sb=new StringBuilder(); + for(Map.Entry> entry: map.entrySet()) { + K key=entry.getKey(); + V val=entry.getValue().val; + sb.append(print_function.print(key, val)); + } + return sb.toString(); + } + + public String toString() { + return printCache(); + } + + + private void checkMaxSizeExceeded() { + if(map.size() > max_elements) { + removeMarkedElements(); + } + } + + /** + * Removes elements marked as removable + * @param force If set to true, all elements marked as 'removable' will get removed, regardless of expiration + */ + public void removeMarkedElements(boolean force) { + long curr_time=System.currentTimeMillis(); + for(Iterator>> it=map.entrySet().iterator(); it.hasNext();) { + Map.Entry> entry=it.next(); + Entry tmp=entry.getValue(); + if(tmp == null) + continue; + if(tmp.removable && (curr_time - tmp.timestamp) >= max_age || force) { + it.remove(); + } + } + } + + /** + * Removes elements marked as removable + */ + public void removeMarkedElements() { + removeMarkedElements(false); + } + + + private static class Entry { + private final V val; + private final long timestamp=System.currentTimeMillis(); + private boolean removable=false; + + public Entry(V val) { + this.val=val; + } + + public String toString() { + return val + " (" + (System.currentTimeMillis() - timestamp) + "ms old" + (removable? ", removable" : "") + ")"; + } + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/LazyRemovalSet.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/LazyRemovalSet.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/LazyRemovalSet.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/LazyRemovalSet.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,260 @@ +package org.jgroups.blocks; + +import java.util.*; + +/** + * Hash set which doesn't remove elements on remove(), removeAll() or retainAll(), but only removes elements when a + * configurable size limit has been exceeded. In that case, all elements marked as removable and older than a + * configurable time are evicted. Elements are marked as removable by remove(), removeAll() and retainAll(). When + * an elements is marked as removable, but later reinserted, the mark is removed. + * @author Bela Ban + */ +public class LazyRemovalSet { + private final Set> set=new HashSet>(); + + /** Max number of elements, if exceeded, we remove all elements marked as removable and older than max_age ms */ + private final int max_elements; + + private final long max_age; + + + public interface Printable { + String print(V val); + } + + + public LazyRemovalSet() { + this(200, 5000L); + } + + public LazyRemovalSet(int max_elements, long max_age) { + this.max_elements=max_elements; + this.max_age=max_age; + } + + public void add(V val) { + if(val != null) { + final Entry entry=new Entry(val); + if(set.contains(entry)) + set.remove(entry); + set.add(entry); // overwrite existing element (new timestamp, and possible removable mark erased) + } + checkMaxSizeExceeded(); + } + + public void add(V ... vals) { + add(Arrays.asList(vals)); + } + + public void add(Collection vals) { + if(vals == null || vals.isEmpty()) + return; + for(V val: vals) { + Entry entry=find(val); + if(entry != null) + set.remove(entry); + set.add(new Entry(val)); // overwrite existing element (new timestamp, and possible removable mark erased) + } + checkMaxSizeExceeded(); + } + + public boolean contains(V val) { + if(val == null) + return false; + Entry entry=new Entry(val); + return set.contains(entry); + } + + + + public void remove(V val) { + remove(val, false); + } + + public void remove(V val, boolean force) { + if(val == null) + return; + Entry entry=new Entry(val); + if(force) + set.remove(entry); + else { + entry=find(val); + if(entry != null) + entry.removable=true; + } + checkMaxSizeExceeded(); + } + + public void removeAll(Collection values) { + removeAll(values, false); + } + + public void removeAll(Collection values, boolean force) { + if(values == null || values.isEmpty()) + return; + if(force) + set.removeAll(values); + else { + for(Entry entry: set) { + if(values.contains(entry.val)) + entry.removable=true; + } + } + checkMaxSizeExceeded(); + } + + public void clear(boolean force) { + if(force) + set.clear(); + else { + for(Entry entry: set) { + if(entry.val != null) + entry.removable=true; + } + } + } + + public void retainAll(Collection values) { + retainAll(values, false); + } + + public void retainAll(Collection values, boolean force) { + if(values == null || values.isEmpty()) + return; + + if(force) + set.retainAll(values); + else { + for(Entry entry: set) { + if(!values.contains(entry.val)) + entry.removable=true; + } + } + + // now make sure that all elements in keys have removable=false + for(V val: values) { + Entry entry=find(val); + if(entry != null) + entry.removable=false; + } + + checkMaxSizeExceeded(); + } + + public Set values() { + Set retval=new HashSet(); + for(Entry entry: set) + retval.add(entry.val); + return retval; + } + + /** + * Adds all value which have not been marked as removable to the returned set + * @return + */ + public Set nonRemovedValues() { + Set retval=new HashSet(); + for(Entry entry: set) { + if(!entry.removable) + retval.add(entry.val); + } + return retval; + } + + + public int size() { + return set.size(); + } + + public String printCache() { + StringBuilder sb=new StringBuilder(); + for(Entry entry: set) { + sb.append(entry).append("\n"); + } + return sb.toString(); + } + + public String printCache(Printable print_function) { + StringBuilder sb=new StringBuilder(); + boolean first=true; + for(Entry entry: set) { + V val=entry.val; + if(first) + first=false; + else + sb.append(", "); + sb.append(print_function.print(val)); + } + return sb.toString(); + } + + public String toString() { + return printCache(); + } + + + private void checkMaxSizeExceeded() { + if(set.size() > max_elements) { + removeMarkedElements(); + } + } + + protected Entry find(V val) { + if(val == null) + return null; + for(Entry entry: set) { + if(val.equals(entry.val)) + return entry; + } + return null; + } + + /** + * Removes elements marked as removable + * @param force If set to true, all elements marked as 'removable' will get removed, regardless of expiration + */ + public void removeMarkedElements(boolean force) { + long curr_time=System.currentTimeMillis(); + for(Iterator> it=set.iterator(); it.hasNext();) { + Entry entry=it.next(); + if(entry == null) + continue; + if(entry.removable && (curr_time - entry.timestamp) >= max_age || force) { + it.remove(); + } + } + } + + /** + * Removes elements marked as removable + */ + public void removeMarkedElements() { + removeMarkedElements(false); + } + + + protected static class Entry { + protected final V val; + protected final long timestamp=System.currentTimeMillis(); + protected boolean removable=false; + + public Entry(V val) { + this.val=val; + } + + public boolean equals(Object obj) { + if(obj instanceof Entry) { + return val.equals(((Entry)obj).val); + } + return val.equals(obj); + } + + public int hashCode() { + return val.hashCode(); + } + + public String toString() { + return val + " (" + (System.currentTimeMillis() - timestamp) + "ms old" + (removable? ", removable" : "") + ")"; + } + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/locking/AwaitInfo.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/locking/AwaitInfo.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/locking/AwaitInfo.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/locking/AwaitInfo.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,30 @@ +package org.jgroups.blocks.locking; + + +public class AwaitInfo { + protected final String name; + protected final boolean all; + + public AwaitInfo(String name, boolean all) { + this.name=name; + this.all=all; + } + + /** + * @return Returns the name. + */ + public String getName() { + return name; + } + + /** + * @return Returns whether is all. + */ + public boolean isAll() { + return all; + } + + public String toString() { + return name + ", awaitAll=" + all; + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/locking/LockInfo.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/locking/LockInfo.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/locking/LockInfo.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/locking/LockInfo.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,55 @@ +package org.jgroups.blocks.locking; + +import java.util.concurrent.TimeUnit; + +/** + * @author Bela Ban + */ +public class LockInfo { + protected final String name; + protected final boolean is_trylock; + protected final boolean lock_interruptibly; + protected final boolean use_timeout; + protected final long timeout; + protected final TimeUnit time_unit; + + public LockInfo(String name, boolean is_trylock, boolean lock_interruptibly, boolean use_timeout, + long timeout, TimeUnit time_unit) { + this.name=name; + this.is_trylock=is_trylock; + this.lock_interruptibly=lock_interruptibly; + this.use_timeout=use_timeout; + this.timeout=timeout; + this.time_unit=time_unit; + } + + + public boolean isTrylock() { + return is_trylock; + } + + public boolean isLockInterruptibly() { + return lock_interruptibly; + } + + public boolean isUseTimeout() { + return use_timeout; + } + + public String getName() { + return name; + } + + public long getTimeout() { + return timeout; + } + + public TimeUnit getTimeUnit() { + return time_unit; + } + + public String toString() { + return name + ", trylock=" + is_trylock + ", timeout=" + timeout; + } +} + diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/locking/LockNotification.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/locking/LockNotification.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/locking/LockNotification.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/locking/LockNotification.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,13 @@ +package org.jgroups.blocks.locking; + +/** + * @author Bela Ban + */ +public interface LockNotification { + void lockCreated(String name); + void lockDeleted(String name); + void locked(String lock_name, Owner owner); + void unlocked(String lock_name, Owner owner); + void awaiting(String lock_name, Owner owner); + void awaited(String lock_name, Owner owner); +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/locking/LockService.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/locking/LockService.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/locking/LockService.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/locking/LockService.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,200 @@ +package org.jgroups.blocks.locking; + +import java.util.Date; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; + +import org.jgroups.Event; +import org.jgroups.JChannel; +import org.jgroups.annotations.Experimental; +import org.jgroups.protocols.Locking; + +/** + * LockService is the main class for to use for distributed locking functionality. LockService needs access to a + * {@link JChannel} and interacts with a locking protocol (e.g. {@link org.jgroups.protocols.CENTRAL_LOCK}) via events.

    + * When no locking protocol is seen on the channel's stack, LockService will throw an exception at startup. An example + * of using LockService is: + *

    +   JChannel ch=new JChannel("/home/bela/locking.xml); // locking.xml needs to have a locking protocol towards the top
    +   LockService lock_service=new LockService(ch);
    +   ch.connect("lock-cluster");
    +   Lock lock=lock_service.getLock("mylock");
    +   lock.lock();
    +   try {
    +      // do something with the lock acquired
    +   }
    +   finally {
    +      lock.unlock();
    +   }
    + * 
    + * Note that, contrary to the semantics of {@link java.util.concurrent.locks.Lock}, unlock() can be called multiple + * times; after a lock has been released, future calls to unlock() have no effect. + * @author Bela Ban + * @since 2.12 + */ +@Experimental +public class LockService { + protected JChannel ch; + protected Locking lock_prot; + + + public LockService() { + + } + + public LockService(JChannel ch) { + setChannel(ch); + } + + public void setChannel(JChannel ch) { + this.ch=ch; + lock_prot=(Locking)ch.getProtocolStack().findProtocol(Locking.class); + if(lock_prot == null) + throw new IllegalStateException("Channel configuration must include a locking protocol " + + "(subclass of " + Locking.class.getName() + ")"); + } + + public Lock getLock(String lock_name) { + return new LockImpl(lock_name); + } + + public void unlockAll() { + ch.down(new Event(Event.UNLOCK_ALL)); + } + + public void addLockListener(LockNotification listener) { + lock_prot.addLockListener(listener); + } + + public void removeLockListener(LockNotification listener) { + lock_prot.removeLockListener(listener); + } + + public String printLocks() { + return lock_prot.printLocks(); + } + + + + protected class LockImpl implements Lock { + protected final String name; + protected final AtomicReference holder = new AtomicReference(); + + public LockImpl(String name) { + this.name=name; + } + + public void lock() { + ch.down(new Event(Event.LOCK, new LockInfo(name, false, false, false, 0, TimeUnit.MILLISECONDS))); + holder.set(Thread.currentThread()); + } + + public void lockInterruptibly() throws InterruptedException { + ch.down(new Event(Event.LOCK, new LockInfo(name, false, true, false, 0, TimeUnit.MILLISECONDS))); + Thread currentThread = Thread.currentThread(); + if(currentThread.isInterrupted()) + throw new InterruptedException(); + else + holder.set(Thread.currentThread()); + } + + public boolean tryLock() { + Boolean retval=(Boolean)ch.downcall(new Event(Event.LOCK, new LockInfo(name, true, false, false, 0, TimeUnit.MILLISECONDS))); + if (retval == Boolean.TRUE) { + holder.set(Thread.currentThread()); + } + return retval.booleanValue(); + } + + public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + Boolean retval=(Boolean)ch.downcall(new Event(Event.LOCK, new LockInfo(name, true, true, true, time, unit))); + if(Thread.currentThread().isInterrupted()) + throw new InterruptedException(); + if (retval == Boolean.TRUE) { + holder.set(Thread.currentThread()); + } + return retval.booleanValue(); + } + + public void unlock() { + ch.down(new Event(Event.UNLOCK, new LockInfo(name, false, false, false, 0, TimeUnit.MILLISECONDS))); + holder.set(null); + } + + /** + * This condition object is only allowed to work 1 for each lock. + * If more than 1 condition is created for this lock, they both will + * be awaiting/signalling on the same lock + */ + public Condition newCondition() { + return new ConditionImpl(name, holder); + } + } + + private class ConditionImpl implements Condition { + protected final String name; + protected final AtomicReference holder; + + public ConditionImpl(String name, AtomicReference holder) { + this.name=name; + this.holder=holder; + } + + @Override + public void await() throws InterruptedException { + ch.down(new Event(Event.LOCK_AWAIT, new LockInfo(name, false, + true, false, 0, TimeUnit.MILLISECONDS))); + if(Thread.currentThread().isInterrupted()) + throw new InterruptedException(); + } + + @Override + public void awaitUninterruptibly() { + ch.down(new Event(Event.LOCK_AWAIT, new LockInfo(name, false, + false, false, 0, TimeUnit.MILLISECONDS))); + } + + @Override + public long awaitNanos(long nanosTimeout) throws InterruptedException { + Long waitLeft = (Long)ch.downcall(new Event(Event.LOCK_AWAIT, + new LockInfo(name, false, true, true, nanosTimeout, + TimeUnit.NANOSECONDS))); + if(Thread.currentThread().isInterrupted()) + throw new InterruptedException(); + return waitLeft.longValue(); + } + + @Override + public boolean await(long time, TimeUnit unit) + throws InterruptedException { + return awaitNanos(unit.toNanos(time)) > 0; + } + + @Override + public boolean awaitUntil(Date deadline) throws InterruptedException { + long waitUntilTime = deadline.getTime(); + long currentTime = System.currentTimeMillis(); + + long waitTime = waitUntilTime - currentTime; + return waitTime > 0 && await(waitTime, TimeUnit.MILLISECONDS); + } + + @Override + public void signal() { + if (holder.get() != Thread.currentThread()) { + throw new IllegalMonitorStateException(); + } + ch.down(new Event(Event.LOCK_SIGNAL, new AwaitInfo(name, false))); + } + + @Override + public void signalAll() { + if (holder.get() != Thread.currentThread()) { + throw new IllegalMonitorStateException(); + } + ch.down(new Event(Event.LOCK_SIGNAL, new AwaitInfo(name, true))); + } + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/locking/Owner.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/locking/Owner.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/locking/Owner.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/locking/Owner.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,60 @@ +package org.jgroups.blocks.locking; + + +import org.jgroups.Address; +import org.jgroups.util.Streamable; +import org.jgroups.util.Util; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + + + +/** + * Represents the owner of a lock. Wraps address and thread ID + * @author Bela Ban + */ +public class Owner implements Streamable { + protected Address address; + protected long thread_id; + + public Owner() { + } + + public Owner(Address address, long thread_id) { + this.address=address; + this.thread_id=thread_id; + } + + public Address getAddress() { + return address; + } + + public long getThreadId() { + return thread_id; + } + + public void writeTo(DataOutputStream out) throws IOException { + Util.writeAddress(address, out); + out.writeLong(thread_id); + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + address=Util.readAddress(in); + thread_id=in.readLong(); + } + + public boolean equals(Object obj) { + Owner other=(Owner)obj; + return address.equals(other.address) && thread_id == other.thread_id; + } + + public int hashCode() { + return (int)(address.hashCode() + thread_id); + } + + public String toString() { + return address + "::" + thread_id; + } +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/LockingException.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/LockingException.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/LockingException.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/LockingException.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: LockingException.java,v 1.3 2008/01/22 10:44:31 belaban Exp $ package org.jgroups.blocks; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/LockManager.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/LockManager.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/LockManager.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/LockManager.java 2011-10-18 11:22:35.000000000 +0000 @@ -8,8 +8,8 @@ * * @author Roman Rokytskyy (rrokytskyy@acm.org) * @author Robert Schaffar-Taurok (robert@fusion.at) - * @version $Id: LockManager.java,v 1.2 2005/06/08 15:56:54 publicnmi Exp $ */ +@Deprecated public interface LockManager { /** diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/LockMultiLockedException.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/LockMultiLockedException.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/LockMultiLockedException.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/LockMultiLockedException.java 2011-10-18 11:22:35.000000000 +0000 @@ -6,7 +6,6 @@ * by multiple DistributedLockManagers. This can happen after a merge for example. * * @author Robert Schaffar-Taurok (robert@fusion.at) - * @version $Id: LockMultiLockedException.java,v 1.3 2006/11/13 17:42:10 bstansberry Exp $ */ public class LockMultiLockedException extends Exception { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/MemcachedConnector.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/MemcachedConnector.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/MemcachedConnector.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/MemcachedConnector.java 2011-10-18 11:22:35.000000000 +0000 @@ -11,10 +11,7 @@ import java.net.ServerSocket; import java.net.Socket; import java.nio.channels.ClosedSelectorException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.*; /** Class which listens on a server socket for memcached clients, reads the requests, forwards them to an instance of @@ -22,7 +19,6 @@ * memcached protocol (http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt) has been implemented * completely.
    * @author Bela Ban - * @version $Id: MemcachedConnector.java,v 1.14 2008/09/01 11:54:02 belaban Exp $ */ public class MemcachedConnector implements Runnable { @ManagedAttribute(writable=false) @@ -340,8 +336,7 @@ case GET: case GETS: req.keys=new ArrayList(5); - for(int i=1; i < args.length; i++) - req.keys.add(args[i]); + req.keys.addAll(Arrays.asList(args).subList(1, args.length)); break; case DELETE: diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/MessageDispatcher.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/MessageDispatcher.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/MessageDispatcher.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/MessageDispatcher.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,23 +1,19 @@ package org.jgroups.blocks; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.*; +import org.jgroups.blocks.mux.Muxer; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.protocols.TP; import org.jgroups.stack.Protocol; import org.jgroups.stack.StateTransferInfo; -import org.jgroups.util.Rsp; -import org.jgroups.util.RspList; -import org.jgroups.util.Util; +import org.jgroups.util.*; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.TreeSet; -import java.util.Vector; +import java.util.*; /** @@ -38,7 +34,6 @@ * the application instead of protocol level. * * @author Bela Ban - * @version $Id: MessageDispatcher.java,v 1.81 2008/12/05 14:59:40 belaban Exp $ */ public class MessageDispatcher implements RequestHandler { protected Channel channel=null; @@ -48,21 +43,13 @@ protected RequestHandler req_handler=null; protected ProtocolAdapter prot_adapter=null; protected TransportAdapter transport_adapter=null; - protected final Collection members=new TreeSet(); + protected final Collection
    members=new TreeSet
    (); protected Address local_addr=null; protected PullPushAdapter adapter=null; protected PullPushHandler handler=null; protected Serializable id=null; protected final Log log=LogFactory.getLog(getClass()); - - - /** - * Process items on the queue concurrently (RequestCorrelator). The default is to wait until the processing of an - * item has completed before fetching the next item from the queue. Note that setting this to true may destroy the - * properties of a protocol stack, e.g total or causal order may not be guaranteed. Set this to true only if you - * know what you're doing ! - */ - protected boolean concurrent_processing=false; + protected boolean hardware_multicast_supported=false; public MessageDispatcher() { @@ -72,43 +59,43 @@ this.channel=channel; prot_adapter=new ProtocolAdapter(); if(channel != null) { - local_addr=channel.getLocalAddress(); + local_addr=channel.getAddress(); } setMessageListener(l); setMembershipListener(l2); if(channel != null) { - channel.setUpHandler(prot_adapter); + installUpHandler(prot_adapter, true); } start(); } - + @Deprecated public MessageDispatcher(Channel channel, MessageListener l, MembershipListener l2, boolean deadlock_detection) { this.channel=channel; prot_adapter=new ProtocolAdapter(); if(channel != null) { - local_addr=channel.getLocalAddress(); + local_addr=channel.getAddress(); } setMessageListener(l); setMembershipListener(l2); if(channel != null) { - channel.setUpHandler(prot_adapter); + installUpHandler(prot_adapter, true); } start(); } + @Deprecated public MessageDispatcher(Channel channel, MessageListener l, MembershipListener l2, boolean deadlock_detection, boolean concurrent_processing) { this.channel=channel; - this.concurrent_processing=concurrent_processing; prot_adapter=new ProtocolAdapter(); if(channel != null) { - local_addr=channel.getLocalAddress(); + local_addr=channel.getAddress(); } setMessageListener(l); setMembershipListener(l2); if(channel != null) { - channel.setUpHandler(prot_adapter); + installUpHandler(prot_adapter, true); } start(); } @@ -120,12 +107,14 @@ } + @Deprecated public MessageDispatcher(Channel channel, MessageListener l, MembershipListener l2, RequestHandler req_handler, boolean deadlock_detection) { this(channel, l, l2, deadlock_detection, false); setRequestHandler(req_handler); } + @Deprecated public MessageDispatcher(Channel channel, MessageListener l, MembershipListener l2, RequestHandler req_handler, boolean deadlock_detection, boolean concurrent_processing) { this(channel, l, l2, deadlock_detection, concurrent_processing); @@ -143,8 +132,8 @@ * @param id A serializable object (e.g. an Integer) used to discriminate (multiplex/demultiplex) between * requests/responses for different building blocks on top of PullPushAdapter. */ - public MessageDispatcher(PullPushAdapter adapter, Serializable id, - MessageListener l, MembershipListener l2) { + @Deprecated + public MessageDispatcher(PullPushAdapter adapter, Serializable id, MessageListener l, MembershipListener l2) { this.adapter=adapter; this.id=id; setMembers(((Channel) adapter.getTransport()).getView().getMembers()); @@ -162,7 +151,7 @@ Transport tp; if((tp=adapter.getTransport()) instanceof Channel) { - local_addr=((Channel) tp).getLocalAddress(); + local_addr=((Channel) tp).getAddress(); } start(); } @@ -179,6 +168,7 @@ * requests/responses for different building blocks on top of PullPushAdapter. * @param req_handler The object implementing RequestHandler. It will be called when a request is received */ + @Deprecated public MessageDispatcher(PullPushAdapter adapter, Serializable id, MessageListener l, MembershipListener l2, RequestHandler req_handler) { @@ -200,17 +190,16 @@ Transport tp; if((tp=adapter.getTransport()) instanceof Channel) { - local_addr=((Channel) tp).getLocalAddress(); // fixed bug #800774 + local_addr=((Channel) tp).getAddress(); // fixed bug #800774 } start(); } - + @Deprecated public MessageDispatcher(PullPushAdapter adapter, Serializable id, MessageListener l, MembershipListener l2, RequestHandler req_handler, boolean concurrent_processing) { - this.concurrent_processing=concurrent_processing; this.adapter=adapter; this.id=id; setMembers(((Channel) adapter.getTransport()).getView().getMembers()); @@ -229,12 +218,18 @@ Transport tp; if((tp=adapter.getTransport()) instanceof Channel) { - local_addr=((Channel) tp).getLocalAddress(); // fixed bug #800774 + local_addr=((Channel) tp).getAddress(); // fixed bug #800774 } start(); } + + public UpHandler getProtocolAdapter() { + return prot_adapter; + } + + /** Returns a copy of members */ protected Collection getMembers() { synchronized(members) { @@ -264,22 +259,21 @@ } - public boolean getConcurrentProcessing() {return concurrent_processing;} - + @Deprecated + public boolean getConcurrentProcessing() {return false;} + + @Deprecated public void setConcurrentProcessing(boolean flag) { - this.concurrent_processing=flag; } - public final void start() { + public void start() { if(corr == null) { if(transport_adapter != null) { - corr=new RequestCorrelator("MsgDisp", transport_adapter, - this, local_addr, concurrent_processing); + corr=createRequestCorrelator(transport_adapter, this, local_addr); } else { - corr=new RequestCorrelator("MsgDisp", prot_adapter, - this, local_addr, concurrent_processing); + corr=createRequestCorrelator(prot_adapter, this, local_addr); } } correlatorStarted(); @@ -291,8 +285,14 @@ if(channel instanceof JChannel) { TP transport=channel.getProtocolStack().getTransport(); corr.registerProbeHandler(transport); + } + TP transport=channel.getProtocolStack().getTransport(); + hardware_multicast_supported=transport.supportsMulticasting(); } } + + protected RequestCorrelator createRequestCorrelator(Object transport, RequestHandler handler, Address local_addr) { + return new RequestCorrelator(transport, handler, local_addr); } protected void correlatorStarted() { @@ -349,89 +349,143 @@ if(ch == null) return; this.channel=ch; - local_addr=channel.getLocalAddress(); + local_addr=channel.getAddress(); if(prot_adapter == null) prot_adapter=new ProtocolAdapter(); - channel.setUpHandler(prot_adapter); + // Don't force installing the UpHandler so subclasses can use this + // method and still integrate with a MuxUpHandler + installUpHandler(prot_adapter, false); + } + + /** + * Sets the given UpHandler as the UpHandler for the channel, or, if the + * channel already has a Muxer installed as it's UpHandler, sets the given + * handler as the Muxer's {@link Muxer#setDefaultHandler(Object) default handler}. + * If the relevant handler is already installed, the canReplace + * controls whether this method replaces it (after logging a WARN) or simply + * leaves handler uninstalled. + *

    + * Passing false as the canReplace value allows + * callers to use this method to install defaults without concern about + * inadvertently overriding + * + * @param handler the UpHandler to install + * @param canReplace true if an existing Channel upHandler or + * Muxer default upHandler can be replaced; false + * if this method shouldn't install + */ + protected void installUpHandler(UpHandler handler, boolean canReplace) + { + UpHandler existing = channel.getUpHandler(); + if (existing == null) { + channel.setUpHandler(handler); + } + else if (existing instanceof Muxer) { + @SuppressWarnings("unchecked") + Muxer mux = (Muxer) existing; + if (mux.getDefaultHandler() == null) { + mux.setDefaultHandler(handler); + } + else if (canReplace) { + log.warn("Channel Muxer already has a default up handler installed (" + + mux.getDefaultHandler() + ") but now it is being overridden"); + mux.setDefaultHandler(handler); + } + } + else if (canReplace) { + log.warn("Channel already has an up handler installed (" + existing + ") but now it is being overridden"); + channel.setUpHandler(handler); + } } - + @Deprecated public void send(Message msg) throws ChannelNotConnectedException, ChannelClosedException { if(channel != null) { channel.send(msg); + return; } - else - if(adapter != null) { - try { - if(id != null) { - adapter.send(id, msg); - } - else { - adapter.send(msg); - } - } - catch(Throwable ex) { - if(log.isErrorEnabled()) { - log.error("exception=" + Util.print(ex)); - } - } + if(adapter != null) { + try { + if(id != null) + adapter.send(id, msg); + else + adapter.send(msg); } - else { - if(log.isErrorEnabled()) { - log.error("channel == null"); - } + catch(Throwable ex) { + log.error("exception=" + Util.print(ex)); } + } + else { + log.error("channel == null"); + } } - + @Deprecated public RspList castMessage(final Vector dests, Message msg, int mode, long timeout) { - return castMessage(dests, msg, mode, timeout, false); + return castMessage(dests, msg, new RequestOptions(mode, timeout, false, null)); } - /** - * Cast a message to all members, and wait for mode responses. The responses are returned in a response - * list, where each response is associated with its sender.

    Uses GroupRequest. - * - * @param dests The members to which the message is to be sent. If it is null, then the message is sent to all - * members - * @param msg The message to be sent to n members - * @param mode Defined in GroupRequest. The number of responses to wait for:

    1. GET_FIRST: - * return the first response received.
    2. GET_ALL: wait for all responses (minus the ones from - * suspected members)
    3. GET_MAJORITY: wait for a majority of all responses (relative to the grp - * size)
    4. GET_ABS_MAJORITY: wait for majority (absolute, computed once)
    5. GET_N: wait for n - * responses (may block if n > group size)
    6. GET_NONE: wait for no responses, return immediately - * (non-blocking)
    - * @param timeout If 0: wait forever. Otherwise, wait for mode responses or timeout time. - * @return RspList A list of responses. Each response is an Object and associated to its sender. - */ + @Deprecated public RspList castMessage(final Vector dests, Message msg, int mode, long timeout, boolean use_anycasting) { - return castMessage(dests, msg, mode, timeout, use_anycasting, null); + return castMessage(dests, msg, new RequestOptions(mode, timeout, use_anycasting, null)); } - + // used by Infinispan + @Deprecated + /** + * @deprecated Use {@link #castMessage(java.util.Collection, org.jgroups.Message, RequestOptions)} instead + */ public RspList castMessage(final Vector dests, Message msg, int mode, long timeout, boolean use_anycasting, RspFilter filter) { - GroupRequest _req=null; - Vector real_dests; - Channel tmp; + RequestOptions opts=new RequestOptions(mode, timeout, use_anycasting, filter); + return castMessage(dests, msg, opts); + } + + /** + * Sends a message to the members listed in dests. If dests is null, the message is sent to all current group + * members. + * @param dests A list of group members. The message is sent to all members of the current group if null + * @param msg The message to be sent + * @param options A set of options that govern the call. See {@link org.jgroups.blocks.RequestOptions} for details + * @return + * @since 2.9 + */ + public RspList castMessage(final Collection
    dests, Message msg, RequestOptions options) { + GroupRequest req=cast(dests, msg, options, true); + return req != null? req.getResults() : RspList.EMPTY_RSP_LIST; + } + + @Deprecated + public NotifyingFuture castMessageWithFuture(final Vector dests, Message msg, int mode, long timeout, boolean use_anycasting, + RspFilter filter) { + return castMessageWithFuture(dests, msg, new RequestOptions(mode, timeout, use_anycasting, filter)); + } + + public NotifyingFuture castMessageWithFuture(final Collection
    dests, Message msg, RequestOptions options) { + GroupRequest req=cast(dests, msg, options, false); + return req != null? req : new NullFuture(RspList.EMPTY_RSP_LIST); + } + + protected GroupRequest cast(final Collection
    dests, Message msg, RequestOptions options, boolean block_for_results) { + List
    real_dests; // we need to clone because we don't want to modify the original // (we remove ourselves if LOCAL is false, see below) ! // real_dests=dests != null ? (Vector) dests.clone() : (members != null ? new Vector(members) : null); if(dests != null) { - real_dests=(Vector)dests.clone(); + real_dests=new ArrayList
    (dests); real_dests.retainAll(this.members); } else { synchronized(members) { - real_dests=new Vector(members); + real_dests=new ArrayList(members); } } // if local delivery is off, then we should not wait for the message from the local member. // therefore remove it from the membership - tmp=channel; + Channel tmp=channel; if(tmp == null) { if(adapter != null && adapter.getTransport() instanceof Channel) { tmp=(Channel) adapter.getTransport(); @@ -440,13 +494,18 @@ if(tmp != null && tmp.getOpt(Channel.LOCAL).equals(Boolean.FALSE)) { if(local_addr == null) { - local_addr=tmp.getLocalAddress(); + local_addr=tmp.getAddress(); } if(local_addr != null) { - real_dests.removeElement(local_addr); + real_dests.remove(local_addr); } } + if(options != null && options.hasExclusionList()) { + Collection
    exclusion_list=options.getExclusionList(); + real_dests.removeAll(exclusion_list); + } + // don't even send the message if the destination list is empty if(log.isTraceEnabled()) log.trace("real_dests=" + real_dests); @@ -454,98 +513,27 @@ if(real_dests.isEmpty()) { if(log.isTraceEnabled()) log.trace("destination list is empty, won't send message"); - return new RspList(); // return empty response list - } - - _req=new GroupRequest(msg, corr, real_dests, mode, timeout, 0); - _req.setCaller(this.local_addr); - _req.setResponseFilter(filter); - try { - _req.execute(use_anycasting); - } - catch(Exception ex) { - throw new RuntimeException("failed executing request " + _req, ex); - } - - return _req.getResults(); - } - - - /** - * Multicast a message request to all members in dests and receive responses via the RspCollector - * interface. When done receiving the required number of responses, the caller has to call done(req_id) on the - * underlyinh RequestCorrelator, so that the resources allocated to that request can be freed. - * - * @param dests The list of members from which to receive responses. Null means all members - * @param req_id The ID of the request. Used by the underlying RequestCorrelator to correlate responses with - * requests - * @param msg The request to be sent - * @param coll The sender needs to provide this interface to collect responses. Call will return immediately if - * this is null - */ - public void castMessage(final Vector dests, long req_id, Message msg, RspCollector coll) { - Vector real_dests; - Channel tmp; - - if(msg == null) { - if(log.isErrorEnabled()) - log.error("request is null"); - return; - } - - if(coll == null) { - if(log.isErrorEnabled()) - log.error("response collector is null (must be non-null)"); - return; - } - - // we need to clone because we don't want to modify the original - // (we remove ourselves if LOCAL is false, see below) ! - //real_dests=dests != null ? (Vector) dests.clone() : (Vector) members.clone(); - if(dests != null) { - real_dests=(Vector)dests.clone(); - real_dests.retainAll(this.members); - } - else { - synchronized(members) { - real_dests=new Vector(members); - } - } - - // if local delivery is off, then we should not wait for the message from the local member. - // therefore remove it from the membership - tmp=channel; - if(tmp == null) { - if(adapter != null && adapter.getTransport() instanceof Channel) { - tmp=(Channel) adapter.getTransport(); - } + return null; } - if(tmp != null && tmp.getOpt(Channel.LOCAL).equals(Boolean.FALSE)) { - if(local_addr == null) { - local_addr=tmp.getLocalAddress(); - } - if(local_addr != null) { - real_dests.removeElement(local_addr); - } - } - - // don't even send the message if the destination list is empty - if(real_dests.isEmpty()) { - if(log.isDebugEnabled()) - log.debug("destination list is empty, won't send message"); - return; + GroupRequest req=new GroupRequest(msg, corr, real_dests, options); + if(options != null) { + req.setResponseFilter(options.getRspFilter()); + req.setAnycasting(options.getAnycasting()); } + req.setBlockForResults(block_for_results); try { - corr.sendRequest(req_id, real_dests, msg, coll); + req.execute(); + return req; } - catch(Exception e) { - throw new RuntimeException("failure sending request " + req_id + " to " + real_dests, e); + catch(Exception ex) { + throw new RuntimeException("failed executing request " + req, ex); } } + public void done(long req_id) { corr.done(req_id); } @@ -554,54 +542,65 @@ /** * Sends a message to a single member (destination = msg.dest) and returns the response. The message's destination * must be non-zero ! + * @deprecated Use {@link #sendMessage(org.jgroups.Message, RequestOptions)} instead */ + @Deprecated public Object sendMessage(Message msg, int mode, long timeout) throws TimeoutException, SuspectedException { - Vector mbrs=new Vector(); - RspList rsp_list=null; - Object dest=msg.getDest(); - Rsp rsp; - GroupRequest _req=null; + return sendMessage(msg, new RequestOptions(mode, timeout, false, null)); + } + + public Object sendMessage(Message msg, RequestOptions opts) throws TimeoutException, SuspectedException { + Address dest=msg.getDest(); if(dest == null) { if(log.isErrorEnabled()) log.error("the message's destination is null, cannot send message"); return null; } - mbrs.addElement(dest); // dummy membership (of destination address) - - _req=new GroupRequest(msg, corr, mbrs, mode, timeout, 0); - _req.setCaller(local_addr); + UnicastRequest req=new UnicastRequest(msg, corr, dest, opts); try { - _req.execute(); + req.execute(); } catch(Exception t) { - throw new RuntimeException("failed executing request " + _req, t); + throw new RuntimeException("failed executing request " + req, t); } - if(mode == GroupRequest.GET_NONE) { + if(opts.getMode() == Request.GET_NONE) return null; - } - rsp_list=_req.getResults(); + Rsp rsp=req.getResult(); + if(rsp.wasSuspected()) + throw new SuspectedException(dest); + if(!rsp.wasReceived()) + throw new TimeoutException("timeout sending message to " + dest); + return rsp.getValue(); + } - if(rsp_list.isEmpty()) { - if(log.isWarnEnabled()) - log.warn(" response list is empty"); + @Deprecated + public NotifyingFuture sendMessageWithFuture(Message msg, int mode, long timeout) throws TimeoutException, SuspectedException { + return sendMessageWithFuture(msg, new RequestOptions(mode, timeout, false, null)); + } + + public NotifyingFuture sendMessageWithFuture(Message msg, RequestOptions options) throws TimeoutException, SuspectedException { + Address dest=msg.getDest(); + if(dest == null) { + if(log.isErrorEnabled()) + log.error("the message's destination is null, cannot send message"); return null; } - if(rsp_list.size() > 1) { - if(log.isWarnEnabled()) - log.warn("response list contains more that 1 response; returning first response !"); - } - rsp=(Rsp)rsp_list.elementAt(0); - if(rsp.wasSuspected()) { - throw new SuspectedException(dest); + + UnicastRequest req=new UnicastRequest(msg, corr, dest, options); + req.setBlockForResults(false); + try { + req.execute(); + if(options.getMode() == Request.GET_NONE) + return new NullFuture(null); + return req; } - if(!rsp.wasReceived()) { - throw new TimeoutException("timeout sending message to " + dest); + catch(Exception t) { + throw new RuntimeException("failed executing request " + req, t); } - return rsp.getValue(); } @@ -693,12 +692,12 @@ return new StateTransferInfo(null, os, sti.state_id); } else if(msg_listener instanceof MessageListener){ - if(log.isWarnEnabled()){ - log.warn("Channel has STREAMING_STATE_TRANSFER, however," - + " application does not implement ExtendedMessageListener. State is not transfered"); - Util.close(os); - } - } + if(log.isWarnEnabled()){ + log.warn("Channel has STREAMING_STATE_TRANSFER, however," + + " application does not implement ExtendedMessageListener. State is not transfered"); + Util.close(os); + } + } break; case Event.STATE_TRANSFER_INPUTSTREAM: @@ -713,12 +712,12 @@ } } else if(msg_listener instanceof MessageListener){ - if(log.isWarnEnabled()){ - log.warn("Channel has STREAMING_STATE_TRANSFER, however," - + " application does not implement ExtendedMessageListener. State is not transfered"); - Util.close(is); - } - } + if(log.isWarnEnabled()){ + log.warn("Channel has STREAMING_STATE_TRANSFER, however," + + " application does not implement ExtendedMessageListener. State is not transfered"); + Util.close(is); + } + } break; case Event.VIEW_CHANGE: @@ -749,10 +748,10 @@ channel.blockOk(); break; case Event.UNBLOCK: - if(membership_listener instanceof ExtendedMembershipListener) { - ((ExtendedMembershipListener)membership_listener).unblock(); - } - break; + if(membership_listener instanceof ExtendedMembershipListener) { + ((ExtendedMembershipListener)membership_listener).unblock(); + } + break; } return null; @@ -798,7 +797,7 @@ } - + @Deprecated class TransportAdapter implements Transport { public void send(Message msg) throws Exception { @@ -833,7 +832,7 @@ } } - + @Deprecated class PullPushHandler implements ExtendedMessageListener, MembershipListener { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/MethodCall.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/MethodCall.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/MethodCall.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/MethodCall.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,8 +1,8 @@ package org.jgroups.blocks; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import java.io.Externalizable; import java.io.IOException; @@ -18,7 +18,7 @@ * It includes the name of the method (case sensitive) and a list of arguments. * A method call is serializable and can be passed over the wire. * @author Bela Ban - * @version $Revision: 1.27 $ + * @version $Revision: 1.39 $ */ public class MethodCall implements Externalizable { @@ -28,7 +28,7 @@ protected String method_name; /** The ID of a method, maps to a java.lang.reflect.Method */ - protected short method_id=-1; + protected short method_id; /** The arguments of the method. */ protected Object[] args; @@ -37,18 +37,18 @@ protected Class[] types; /** The signature, e.g., new String[]{String.class.getName(), int.class.getName()}. */ - protected String[] signature; + @Deprecated protected String[] signature; /** The Method of the call. */ protected Method method; /** To carry arbitrary data with a method call, data needs to be serializable if sent across the wire */ - protected Map payload; + @Deprecated protected Map payload; protected static final Log log=LogFactory.getLog(MethodCall.class); /** Which mode to use. */ - protected short mode=OLD; + protected short mode; /** Infer the method from the arguments. */ protected static final short OLD=1; @@ -66,6 +66,7 @@ protected static final short ID=5; + /** * Creates an empty method call, this is always invalid, until * setName() has been called. @@ -75,10 +76,10 @@ public MethodCall(Method method) { - this(method, null); + this(method, (Object[])null); } - public MethodCall(Method method, Object[] arguments) { + public MethodCall(Method method, Object... arguments) { init(method); if(arguments != null) args=arguments; } @@ -87,15 +88,15 @@ * * @param method_name * @param args - * @deprecated Use one of the constructors that take class types as arguments */ - public MethodCall(String method_name, Object[] args) { + @Deprecated + public MethodCall(String method_name, Object... args) { this.method_name=method_name; this.mode=OLD; this.args=args; } - public MethodCall(short method_id, Object[] args) { + public MethodCall(short method_id, Object... args) { this.method_id=method_id; this.mode=ID; this.args=args; @@ -109,6 +110,7 @@ this.mode=TYPES; } + @Deprecated public MethodCall(String method_name, Object[] args, String[] signature) { this.method_name=method_name; this.args=args; @@ -128,7 +130,6 @@ } - /** * returns the name of the method to be invoked using this method call object * @return a case sensitive name, can be null for an invalid method call @@ -177,12 +178,15 @@ } + + @Deprecated public synchronized Object put(Object key, Object value) { if(payload == null) payload=new HashMap(); return payload.put(key, value); } + @Deprecated public synchronized Object get(Object key) { return payload != null? payload.get(key) : null; } @@ -211,6 +215,22 @@ } + public static Method findMethod(Class target_class, String method_name, Object[] args) throws Exception { + int len=args != null? args.length : 0; + + Method[] methods=getAllMethods(target_class); + for(int i=0; i < methods.length; i++) { + Method m=methods[i]; + if(m.getName().equals(method_name)) { + if(m.getParameterTypes().length == len) + return m; + } + } + + return null; + } + + /** * The method walks up the class hierarchy and returns all methods of this class * and those inherited from superclasses and superinterfaces. @@ -292,7 +312,7 @@ public Object invoke(Object target) throws Throwable { Class cl; Method meth=null; - Object retval=null; + Object retval; if(method_name == null || target == null) { @@ -516,6 +536,25 @@ } + public static Object convert(String arg, Class type) { + if(type == String.class) + return arg; + if(type == boolean.class || type == Boolean.class) + return Boolean.valueOf(arg); + if(type == byte.class || type == Byte.class) + return Byte.valueOf(arg); + if(type == short.class || type == Short.class) + return Short.valueOf(arg); + if(type == int.class || type == Integer.class) + return Integer.valueOf(arg); + if(type == long.class || type == Long.class) + return Long.valueOf(arg); + if(type == float.class || type == Float.class) + return Float.valueOf(arg); + if(type == double.class || type == Double.class) + return Double.valueOf(arg); + return arg; + } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/MethodLookup.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/MethodLookup.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/MethodLookup.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/MethodLookup.java 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ /** * @author Bela Ban - * @version $Id: MethodLookup.java,v 1.3 2005/07/22 08:59:20 belaban Exp $ */ public interface MethodLookup { Method findMethod(short id); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/MultiRequest.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/MultiRequest.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/MultiRequest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/MultiRequest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,383 @@ +package org.jgroups.blocks; + + +import org.jgroups.Address; +import org.jgroups.Message; +import org.jgroups.Transport; +import org.jgroups.View; +import org.jgroups.annotations.GuardedBy; +import org.jgroups.util.Rsp; +import org.jgroups.util.RspList; + +import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + + +/** + * Sends a request to multiple destinations. Alternative implementation when we have few targets: between UnicastRequest + * with 1 target and GroupRequest with many destination members. Performance is about the same as for GroupRequest, but + * this class should use less memory as it doesn't create hashmaps. Don't use with many targets as we have to do + * a linear search through an array of targets to match a response to a request.

    + * MultiRequest is currently not used + * + * @author Bela Ban + * @since 2.9 + */ +public class MultiRequest extends Request { + @GuardedBy("lock") + private final Rsp[] responses; + + protected final int expected_mbrs; + + @GuardedBy("lock") + int num_received, num_not_received, num_suspected; + + + + /** + * @param m + * The message to be sent + * @param corr + * The request correlator to be used. A request correlator + * sends requests tagged with a unique ID and notifies the + * sender when matching responses are received. The reason + * GroupRequest uses it instead of a + * Transport is that multiple + * requests/responses might be sent/received concurrently. + * @param mbrs + * The initial membership. This value reflects the membership + * to which the request is sent (and from which potential + * responses are expected). Is reset by reset(). + * @param options The options to be passed to the request + */ + public MultiRequest(Message m, RequestCorrelator corr, Collection

    mbrs, RequestOptions options, int expected_mbrs) { + super(m, corr, null, options); + this.expected_mbrs=expected_mbrs; + responses=new Rsp[mbrs.size()]; + setTargets(mbrs); + } + + public MultiRequest(Message m, RequestCorrelator corr, Address target, RequestOptions options, int expected_mbrs) { + super(m, corr, null, options); + this.expected_mbrs=expected_mbrs; + responses=new Rsp[1]; + setTarget(target); + } + + + + /** + * @param timeout Time to wait for responses (ms). A value of <= 0 means wait indefinitely + * (e.g. if a suspicion service is available; timeouts are not needed). + */ + public MultiRequest(Message m, Transport transport, Collection
    mbrs, RequestOptions options, int expected_mbrs) { + super(m, null, transport, options); + this.expected_mbrs=expected_mbrs; + responses=new Rsp[1]; + setTargets(mbrs); + } + + void setTarget(Address mbr) { + responses[0]=new Rsp(mbr); + num_not_received++; + } + + void setTargets(Collection
    mbrs) { + int index=0; + for(Address mbr: mbrs) { + responses[index++]=new Rsp(mbr); + num_not_received++; + } + } + + public boolean getAnycasting() { + return options.getAnycasting(); + } + + public void setAnycasting(boolean anycasting) { + options.setAnycasting(anycasting); + } + + + + public void sendRequest() throws Exception { + List
    targets=null; + targets=new ArrayList
    (responses.length); + for(Rsp rsp: responses) + targets.add(rsp.getSender()); + + sendRequest(targets, req_id, options); + } + + Rsp findResponse(Address target) { + for(Rsp rsp: responses) { + if(rsp != null && target.equals(rsp.getSender())) + return rsp; + } + return null; + } + + /* ---------------------- Interface RspCollector -------------------------- */ + /** + * Callback (called by RequestCorrelator or Transport). + * Adds a response to the response table. When all responses have been received, + * execute() returns. + */ + public void receiveResponse(Object response_value, Address sender) { + if(done) + return; + Rsp rsp=findResponse(sender); + if(rsp == null) + return; + + RspFilter rsp_filter=options.getRspFilter(); + boolean responseReceived=false; + if(!rsp.wasReceived()) { + if((responseReceived=(rsp_filter == null) || rsp_filter.isAcceptable(response_value, sender))) + rsp.setValue(response_value); + rsp.setReceived(responseReceived); + } + + lock.lock(); + try { + if(responseReceived) + num_received++; + done=rsp_filter == null? responsesComplete() : !rsp_filter.needMoreResponses(); + if(responseReceived || done) + completed.signalAll(); // wakes up execute() + if(done && corr != null) + corr.done(req_id); + } + finally { + lock.unlock(); + } + if(responseReceived || done) + checkCompletion(this); + } + + + /** + * Callback (called by RequestCorrelator or Transport). + * Report to GroupRequest that a member is reported as faulty (suspected). + * This method would probably be called when getting a suspect message from a failure detector + * (where available). It is used to exclude faulty members from the response list. + */ + public void suspect(Address suspected_member) { + if(suspected_member == null) + return; + + boolean changed=false; + Rsp rsp=findResponse(suspected_member); + if(rsp != null) { + if(rsp.setSuspected(true)) { + rsp.setValue(null); + changed=true; + lock.lock(); + try { + num_suspected++; + completed.signalAll(); + } + finally { + lock.unlock(); + } + } + } + + if(changed) + checkCompletion(this); + } + + + /** + * Any member of 'membership' that is not in the new view is flagged as + * SUSPECTED. Any member in the new view that is not in the + * membership (ie, the set of responses expected for the current RPC) will + * not be added to it. If we did this we might run into the + * following problem: + *
      + *
    • Membership is {A,B} + *
    • A sends a synchronous group RPC (which sleeps for 60 secs in the + * invocation handler) + *
    • C joins while A waits for responses from A and B + *
    • If this would generate a new view {A,B,C} and if this expanded the + * response set to {A,B,C}, A would wait forever on C's response because C + * never received the request in the first place, therefore won't send a + * response. + *
    + */ + public void viewChange(View new_view) { + Vector
    mbrs=new_view != null? new_view.getMembers() : null; + if(mbrs == null) + return; + + boolean changed=false; + if(responses == null || responses.length == 0) + return; + + lock.lock(); + try { + for(Rsp rsp: responses) { + Address mbr=rsp.getSender(); + if(!mbrs.contains(mbr)) { + rsp.setValue(null); + if(rsp.setSuspected(true)) { + num_suspected++; + changed=true; + } + } + } + if(changed) + completed.signalAll(); + } + finally { + lock.unlock(); + } + if(changed) + checkCompletion(this); + } + + + /* -------------------- End of Interface RspCollector ----------------------------------- */ + + + + /** Returns the results as a RspList */ + public RspList getResults() { + RspList list=new RspList(); + for(Rsp rsp: responses) + list.put(rsp.getSender(), rsp); + return list; + } + + + + public RspList get() throws InterruptedException, ExecutionException { + lock.lock(); + try { + waitForResults(0); + } + finally { + lock.unlock(); + } + return getResults(); + } + + public RspList get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + boolean ok; + lock.lock(); + try { + ok=waitForResults(unit.toMillis(timeout)); + } + finally { + lock.unlock(); + } + if(!ok) + throw new TimeoutException(); + return getResults(); + } + + public String toString() { + StringBuilder ret=new StringBuilder(128); + ret.append(super.toString()); + + lock.lock(); + try { + if(!(responses.length == 0)) { + ret.append(", entries:\n"); + for(Rsp rsp: responses) { + Address mbr=rsp.getSender(); + ret.append(mbr).append(": ").append(rsp).append("\n"); + } + } + } + finally { + lock.unlock(); + } + return ret.toString(); + } + + + + + /* --------------------------------- Private Methods -------------------------------------*/ + + private static int determineMajority(int i) { + return i < 2? i : (i / 2) + 1; + } + + + + + private void sendRequest(List
    targetMembers, long requestId, RequestOptions options) throws Exception { + try { + if(log.isTraceEnabled()) log.trace(new StringBuilder("sending request (id=").append(req_id).append(')')); + if(corr != null) { + corr.sendRequest(requestId, targetMembers, request_msg, options.getMode() == GET_NONE? null : this, options); + } + else { + if(options.getAnycasting()) { + for(Address mbr: targetMembers) { + Message copy=request_msg.copy(true); + copy.setDest(mbr); + transport.send(copy); + } + } + else { + transport.send(request_msg); + } + } + } + catch(Exception ex) { + if(corr != null) + corr.done(requestId); + throw ex; + } + } + + + @GuardedBy("lock") + protected boolean responsesComplete() { + if(done) + return true; + + final int num_total=responses.length; + + switch(options.getMode()) { + case GET_FIRST: + if(num_received > 0) + return true; + if(num_suspected >= num_total) + // e.g. 2 members, and both suspected + return true; + break; + case GET_ALL: + return num_received + num_suspected >= num_total; + case GET_MAJORITY: + int majority=determineMajority(num_total); + if(num_received + num_suspected >= majority) + return true; + break; + case GET_ABS_MAJORITY: + majority=determineMajority(num_total); + if(num_received >= majority) + return true; + break; + case GET_N: + if(expected_mbrs >= num_total) { + return responsesComplete(); + } + return num_received >= expected_mbrs || num_received + num_not_received < expected_mbrs && num_received + num_suspected >= expected_mbrs; + case GET_NONE: + return true; + default : + if(log.isErrorEnabled()) log.error("rsp_mode " + options.getMode() + " unknown !"); + break; + } + return false; + } + + + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/mux/Muxer.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/mux/Muxer.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/mux/Muxer.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/mux/Muxer.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,44 @@ +package org.jgroups.blocks.mux; + +/** + * Allows registration/deregistrator of multiplexed handlers by mux id. + * @author Paul Ferraro + */ +public interface Muxer { + + /** + * Registers the specified handler to handle messages containing a mux header with the specified mux identifier. + * @param id a mux id + * @param handler a handler for the specified id + */ + void add(short id, T handler); + + /** + * Gets the handler registered under the specified id + * @param id a mux id + * @return the handler, or null if no handler is registered under + * id + */ + T get(short id); + + /** + * Unregisters the handler associated with the specifed mux identifier + * @param id a mux id + */ + void remove(short id); + + /** + * Gets the handler for messages that have no mux header. + * + * @return the default handler, or null if no default handler + * has been set + */ + T getDefaultHandler(); + + /** + * Sets the handler for messages that have no mux header. + * + * @param handler a handler for messages that have no mux header + */ + void setDefaultHandler(T handler); +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/mux/MuxHeader.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/mux/MuxHeader.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/mux/MuxHeader.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/mux/MuxHeader.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,41 @@ +package org.jgroups.blocks.mux; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import org.jgroups.Global; +import org.jgroups.Header; + +/** + * Header that identifies the target handler for multiplexed dispatches. + * @author Bela Ban + * @author Paul Ferraro + */ +public class MuxHeader extends Header { + + private short id; + + public MuxHeader() { + } + + public MuxHeader(short id) { + this.id = id; + } + + public short getId() { + return id; + } + + public int size() { + return Global.SHORT_SIZE; + } + + public void writeTo(DataOutputStream out) throws IOException { + out.writeShort(id); + } + + public void readFrom(DataInputStream in) throws IOException { + id = in.readShort(); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/mux/MuxMessageDispatcher.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/mux/MuxMessageDispatcher.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/mux/MuxMessageDispatcher.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/mux/MuxMessageDispatcher.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,91 @@ +package org.jgroups.blocks.mux; + +import java.util.Collection; + +import org.jgroups.Address; +import org.jgroups.Channel; +import org.jgroups.MembershipListener; +import org.jgroups.Message; +import org.jgroups.MessageListener; +import org.jgroups.UpHandler; +import org.jgroups.blocks.GroupRequest; +import org.jgroups.blocks.MessageDispatcher; +import org.jgroups.blocks.RequestCorrelator; +import org.jgroups.blocks.RequestHandler; +import org.jgroups.blocks.RequestOptions; +import org.jgroups.blocks.RspFilter; + +/** + * A multiplexed message dispatcher. + * When used in conjunction with a MuxUpHandler, allows multiple dispatchers to use the same channel. + *
    + * Usage:
    + * + * Channel c = new JChannel(...);
    + * c.setUpHandler(new MuxUpHandler());
    + *
    + * MessageDispatcher d1 = new MuxMessageDispatcher((short) 1, c, ...);
    + * MessageDispatcher d2 = new MuxMessageDispatcher((short) 2, c, ...);
    + *
    + * c.connect(...);
    + *
    + * @author Paul Ferraro + */ +public class MuxMessageDispatcher extends MessageDispatcher { + + private final short scope_id; + + public MuxMessageDispatcher(short scopeId) { + this.scope_id = scopeId; + } + + public MuxMessageDispatcher(short scopeId, Channel channel, MessageListener messageListener, MembershipListener membershipListener, RequestHandler handler) { + this(scopeId); + + setMessageListener(messageListener); + setMembershipListener(membershipListener); + setChannel(channel); + setRequestHandler(handler); + start(); + } + + private Muxer getMuxer() { + UpHandler handler = channel.getUpHandler(); + return ((handler != null) && (handler instanceof MuxUpHandler)) ? (MuxUpHandler) handler : null; + } + + @Override + protected RequestCorrelator createRequestCorrelator(Object transport, RequestHandler handler, Address localAddr) { + // We can't set the scope of the request correlator here + // since this method is called from start() triggered in the + // MessageDispatcher constructor, when this.scope is not yet defined + return new MuxRequestCorrelator(scope_id, transport, handler, localAddr); + } + + @Override + public void start() { + super.start(); + Muxer muxer = this.getMuxer(); + if (muxer != null) { + muxer.add(scope_id, this.getProtocolAdapter()); + } + } + + @Override + public void stop() { + Muxer muxer = this.getMuxer(); + if (muxer != null) { + muxer.remove(scope_id); + } + super.stop(); + } + + @Override + protected GroupRequest cast(Collection
    dests, Message msg, RequestOptions options, boolean blockForResults) { + RspFilter filter=options.getRspFilter(); + RequestOptions newOptions = new RequestOptions(options.getMode(), options.getTimeout(), options.getAnycasting(), + (filter != null) ? new NoMuxHandlerRspFilter(filter) : new NoMuxHandlerRspFilter(), + options.getFlags()); + return super.cast(dests, msg, newOptions, blockForResults); + } +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/mux/MuxRequestCorrelator.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/mux/MuxRequestCorrelator.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/mux/MuxRequestCorrelator.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/mux/MuxRequestCorrelator.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,46 @@ +package org.jgroups.blocks.mux; + +import java.util.Collection; + +import org.jgroups.Address; +import org.jgroups.Message; +import org.jgroups.blocks.RequestCorrelator; +import org.jgroups.blocks.RequestHandler; +import org.jgroups.blocks.RspCollector; +import org.jgroups.blocks.RequestOptions; +import org.jgroups.conf.ClassConfigurator; + +/** + * A request correlator that adds a mux header to incoming and outgoing messages. + * @author Bela Ban + * @author Paul Ferraro + * @author Brian Stansberry + */ +public class MuxRequestCorrelator extends RequestCorrelator { + + protected final static short MUX_ID = ClassConfigurator.getProtocolId(MuxRequestCorrelator.class); + private final org.jgroups.Header header; + + public MuxRequestCorrelator(short id, Object transport, RequestHandler handler, Address localAddr) { + super(ClassConfigurator.getProtocolId(RequestCorrelator.class), transport, handler, localAddr); + this.header = new MuxHeader(id); + } + + @Override + public void sendRequest(long requestId, Collection
    dest_mbrs, Message msg, RspCollector coll, RequestOptions options) throws Exception { + msg.putHeader(MUX_ID, header); + super.sendRequest(requestId, dest_mbrs, msg, coll, options); + } + + @Override + public void sendUnicastRequest(long id, Address target, Message msg, RspCollector coll) throws Exception { + msg.putHeader(MUX_ID, header); + super.sendUnicastRequest(id, target, msg, coll); + } + + @Override + protected void prepareResponse(Message rsp) { + rsp.putHeader(MUX_ID, header); + super.prepareResponse(rsp); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/mux/MuxRpcDispatcher.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/mux/MuxRpcDispatcher.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/mux/MuxRpcDispatcher.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/mux/MuxRpcDispatcher.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,111 @@ +package org.jgroups.blocks.mux; + +import java.util.Collection; + +import org.jgroups.Address; +import org.jgroups.Channel; +import org.jgroups.MembershipListener; +import org.jgroups.Message; +import org.jgroups.MessageListener; +import org.jgroups.UpHandler; +import org.jgroups.blocks.GroupRequest; +import org.jgroups.blocks.MethodLookup; +import org.jgroups.blocks.RequestCorrelator; +import org.jgroups.blocks.RequestHandler; +import org.jgroups.blocks.RequestOptions; +import org.jgroups.blocks.RpcDispatcher; +import org.jgroups.blocks.RspFilter; + +/** + * A multiplexed message dispatcher. + * When used in conjunction with a MuxUpHandler, allows multiple dispatchers to use the same channel. + *
    + * Usage:
    + * + * Channel c = new JChannel(...);
    + * c.setUpHandler(new MuxUpHandler());
    + *
    + * RpcDispatcher d1 = new MuxRpcDispatcher((short) 1, c, ...);
    + * RpcDispatcher d2 = new MuxRpcDispatcher((short) 2, c, ...);
    + *
    + * c.connect(...);
    + *
    + * @author Bela Ban + * @author Paul Ferraro + */ +public class MuxRpcDispatcher extends RpcDispatcher { + + private final short scope_id; + + public MuxRpcDispatcher(short scopeId) { + super(); + this.scope_id = scopeId; + } + + public MuxRpcDispatcher(short scopeId, Channel channel, MessageListener messageListener, MembershipListener membershipListener, Object serverObject) { + this(scopeId); + + setMessageListener(messageListener); + setMembershipListener(membershipListener); + setServerObject(serverObject); + setChannel(channel); + channel.addChannelListener(this); + start(); + } + + public MuxRpcDispatcher(short scopeId, Channel channel, MessageListener messageListener, MembershipListener membershipListener, Object serverObject, MethodLookup method_lookup) { + this(scopeId); + + setMethodLookup(method_lookup); + setMessageListener(messageListener); + setMembershipListener(membershipListener); + setServerObject(serverObject); + setChannel(channel); + channel.addChannelListener(this); + start(); + } + + private Muxer getMuxer() { + UpHandler handler = channel.getUpHandler(); + return ((handler != null) && (handler instanceof MuxUpHandler)) ? (MuxUpHandler) handler : null; + } + + @Override + protected RequestCorrelator createRequestCorrelator(Object transport, RequestHandler handler, Address localAddr) { + // We can't set the scope of the request correlator here + // since this method is called from start() triggered in the + // MessageDispatcher constructor, when this.scope is not yet defined + return new MuxRequestCorrelator(scope_id, transport, handler, localAddr); + } + + @Override + public void start() { + super.start(); + Muxer muxer = this.getMuxer(); + if (muxer != null) { + muxer.add(scope_id, this.getProtocolAdapter()); + } + } + + @Override + public void stop() { + Muxer muxer = this.getMuxer(); + if (muxer != null) { + muxer.remove(scope_id); + } + super.stop(); + } + + /** + @Override + protected GroupRequest cast(Collection
    dests, Message msg, RequestOptions options, boolean blockForResults) { + RspFilter filter = options.getRspFilter(); + return super.cast(dests, msg, options.setRspFilter((filter != null) ? new NoMuxHandlerRspFilter(filter) : new NoMuxHandlerRspFilter()), blockForResults); + }*/ + + @Override + protected GroupRequest cast(Collection
    dests, Message msg, RequestOptions options, boolean blockForResults) { + RspFilter filter = options.getRspFilter(); + return super.cast(dests, msg, options.setRspFilter(NoMuxHandlerRspFilter.createInstance(filter)), blockForResults); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/mux/MuxUpHandler.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/mux/MuxUpHandler.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/mux/MuxUpHandler.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/mux/MuxUpHandler.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,164 @@ +package org.jgroups.blocks.mux; + +import org.jgroups.Event; +import org.jgroups.Message; +import org.jgroups.UpHandler; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.util.ImmutableReference; +import org.jgroups.util.Util; + +import java.util.Map; + +/** + * Allows up handler multiplexing. + * + * @author Bela Ban + * @author Paul Ferraro + */ +public class MuxUpHandler implements UpHandler, Muxer { + + protected final Log log=LogFactory.getLog(getClass()); + private final Map handlers=Util.createConcurrentMap(); + private volatile UpHandler defaultHandler; + private volatile Event lastFlushEvent; + private final Object flushMutex = new Object(); + + /** + * Creates a multiplexing up handler, with no default handler. + */ + public MuxUpHandler() { + this.defaultHandler = null; + } + + /** + * Creates a multiplexing up handler using the specified default handler. + * @param defaultHandler a default up handler to handle messages with no {@link MuxHeader} + */ + public MuxUpHandler(UpHandler defaultHandler) { + this.defaultHandler = defaultHandler; + } + + /** + * {@inheritDoc} + * @see org.jgroups.blocks.mux.Muxer#add(short, java.lang.Object) + */ + @Override + public void add(short id, UpHandler handler) { + synchronized (flushMutex) + { + if (lastFlushEvent != null) + { + handler.up(lastFlushEvent); + } + handlers.put(id, handler); + } + } + + /** + * {@inheritDoc} + * @see org.jgroups.blocks.mux.Muxer#get(short) + */ + @Override + public UpHandler get(short id) { + return handlers.get(id); + } + + /** + * {@inheritDoc} + * @see org.jgroups.blocks.mux.Muxer#remove(short) + */ + @Override + public void remove(short id) { + handlers.remove(id); + } + + @Override + public UpHandler getDefaultHandler() { + return defaultHandler; + } + + @Override + public void setDefaultHandler(UpHandler handler) { + this.defaultHandler = handler; + } + + /** + * {@inheritDoc} + * @see org.jgroups.UpHandler#up(org.jgroups.Event) + */ + @Override + public Object up(Event evt) { + switch (evt.getType()) { + case Event.MSG: { + Message msg = (Message) evt.getArg(); + MuxHeader hdr = (MuxHeader) msg.getHeader(MuxRequestCorrelator.MUX_ID); + if (hdr != null) { + short id = hdr.getId(); + UpHandler handler = handlers.get(id); + return (handler != null) ? handler.up(evt) : new NoMuxHandler(id); + } + break; + } + case Event.GET_APPLSTATE: + case Event.GET_STATE_OK: + case Event.STATE_TRANSFER_OUTPUTSTREAM: + case Event.STATE_TRANSFER_INPUTSTREAM: { + ImmutableReference wrapper = handleStateTransferEvent(evt); + if (wrapper != null) + { + return wrapper.get(); + } + break; + } + case Event.BLOCK: + case Event.UNBLOCK: { + synchronized (flushMutex) + { + this.lastFlushEvent = evt; + passToAllHandlers(evt); + break; + } + } + case Event.VIEW_CHANGE: + case Event.SET_LOCAL_ADDRESS: + case Event.SUSPECT: { + passToAllHandlers(evt); + break; + } + default: { + passToAllHandlers(evt); + break; + } + } + + return (defaultHandler != null) ? defaultHandler.up(evt) : null; + } + + /** + * Extension point for subclasses called by up() when an event + * related to state transfer is received, allowing the subclass + * to override the default behavior of passing the event to the + * default up handler. + * + * @return an AtomicReference containing the return value for the event + * if the event was handled and no further processing + * should be done in up(), or null if up() needs to + * handle the event. If the event was handled but the return value + * is null, an AtomicReference initialized to + * null should be returned. This default + * implementation always returns null + */ + protected ImmutableReference handleStateTransferEvent(Event evt) { + return null; + } + + + + private void passToAllHandlers(Event evt) + { + for (UpHandler handler: handlers.values()) { + handler.up(evt); + } + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/mux/NoMuxHandler.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/mux/NoMuxHandler.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/mux/NoMuxHandler.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/mux/NoMuxHandler.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,33 @@ +package org.jgroups.blocks.mux; + +import java.io.Serializable; + +/** + * Returned by {@link MuxUpHandler} when a message is received with a specific {@link MuxHeader}, but no corresponding handler is registered. + * @author Paul Ferraro + */ +public class NoMuxHandler implements Serializable { + private static final long serialVersionUID = -694135384125080323L; + + private short id; + + public NoMuxHandler(short id) { + this.id = id; + } + + public short getId() { + return id; + } + + @Override + public boolean equals(Object object) { + if ((object == null) || !(object instanceof NoMuxHandler)) return false; + NoMuxHandler handler = (NoMuxHandler) object; + return (id == handler.id); + } + + @Override + public int hashCode() { + return id; + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/mux/NoMuxHandlerRspFilter.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/mux/NoMuxHandlerRspFilter.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/mux/NoMuxHandlerRspFilter.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/mux/NoMuxHandlerRspFilter.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,41 @@ +package org.jgroups.blocks.mux; + +import org.jgroups.Address; +import org.jgroups.blocks.RspFilter; + +/** + * Response filter that reject any {@link NoMuxHandler} responses. + * @author Paul Ferraro + */ +public class NoMuxHandlerRspFilter implements RspFilter { + + private final RspFilter filter; + + public NoMuxHandlerRspFilter() { + this.filter = null; + } + + public NoMuxHandlerRspFilter(RspFilter filter) { + this.filter = filter; + } + + public static RspFilter createInstance(RspFilter filter) { + if(filter instanceof NoMuxHandlerRspFilter) + return filter; + return new NoMuxHandlerRspFilter(filter) ; + } + + public RspFilter getFilter() { + return filter; + } + + @Override + public boolean isAcceptable(Object response, Address sender) { + return !(response instanceof NoMuxHandler) && ((filter == null) || filter.isAcceptable(response, sender)); + } + + @Override + public boolean needMoreResponses() { + return (filter == null) || filter.needMoreResponses(); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/NotificationBus.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/NotificationBus.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/NotificationBus.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/NotificationBus.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,10 +1,9 @@ -// $Id: NotificationBus.java,v 1.17 2008/04/08 14:49:06 belaban Exp $ package org.jgroups.blocks; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.*; import org.jgroups.annotations.Unsupported; import org.jgroups.util.Promise; @@ -24,8 +23,10 @@ * application programmers do not have to provide their own channel. * * @author Bela Ban + * @deprecated Will be pulled in 3.0, use a JChannel directly instead */ @Unsupported(comment="provided as an example, use at your own risk") +@Deprecated public class NotificationBus implements Receiver { final Vector members=new Vector(); Channel channel=null; @@ -86,7 +87,7 @@ public Address getLocalAddress() { if(local_addr != null) return local_addr; if(channel != null) - local_addr=channel.getLocalAddress(); + local_addr=channel.getAddress(); return local_addr; } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/PartitionedHashMap.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/PartitionedHashMap.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/PartitionedHashMap.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/PartitionedHashMap.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,24 +1,23 @@ package org.jgroups.blocks; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.Address; import org.jgroups.JChannel; import org.jgroups.MembershipListener; import org.jgroups.View; -import org.jgroups.util.Util; import org.jgroups.annotations.Experimental; import org.jgroups.annotations.ManagedAttribute; import org.jgroups.annotations.ManagedOperation; import org.jgroups.annotations.Unsupported; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.util.Util; -import java.lang.reflect.Method; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; import java.io.DataInputStream; -import java.io.ByteArrayInputStream; +import java.io.DataOutputStream; +import java.lang.reflect.Method; +import java.util.*; /** Hashmap which distributes its keys and values across the cluster. A PUT/GET/REMOVE computes the cluster node to which * or from which to get/set the key/value from a hash of the key and then forwards the request to the remote cluster node. @@ -35,7 +34,6 @@ *
  20. Documentation, comparison to memcached * * @author Bela Ban - * @version $Id: PartitionedHashMap.java,v 1.16 2008/09/03 14:17:57 belaban Exp $ */ @Experimental @Unsupported public class PartitionedHashMap implements MembershipListener { @@ -72,7 +70,7 @@ private static final short GET = 2; private static final short REMOVE = 3; - protected static Map methods=new ConcurrentHashMap(8); + protected static Map methods=Util.createConcurrentMap(8); static { try { @@ -223,7 +221,7 @@ }); ch.connect(cluster_name); - local_addr=ch.getLocalAddress(); + local_addr=ch.getAddress(); view=ch.getView(); } @@ -240,7 +238,7 @@ Address node=hash_function.hash(key, members_without_me); if(!node.equals(local_addr)) { Cache.Value val=entry.getValue(); - sendPut(node, key, val.getValue(), val.getExpirationTime(), true); + sendPut(node, key, val.getValue(), val.getTimeout(), true); if(log.isTraceEnabled()) log.trace("migrated " + key + " from " + local_addr + " to " + node); } @@ -302,8 +300,8 @@ } if(val != null) { V retval=val.getValue(); - if(l1_cache != null && val.getExpirationTime() >= 0) - l1_cache.put(key, retval, val.getExpirationTime()); + if(l1_cache != null && val.getTimeout() >= 0) + l1_cache.put(key, retval, val.getTimeout()); return retval; } return null; @@ -405,7 +403,7 @@ Address node=getNode(key); if(!node.equals(local_addr)) { Cache.Value val=entry.getValue(); - put(key, val.getValue(), val.getExpirationTime()); + put(key, val.getValue(), val.getTimeout()); l2_cache.remove(key); if(log.isTraceEnabled()) log.trace("migrated " + key + " from " + local_addr + " to " + node); @@ -553,17 +551,22 @@ private static class CustomMarshaller implements RpcDispatcher.Marshaller { + static final byte NULL = 0; static final byte OBJ = 1; static final byte METHOD_CALL = 2; static final byte VALUE = 3; public byte[] objectToByteBuffer(Object obj) throws Exception { - if(obj == null) - return null; + ByteArrayOutputStream out_stream=new ByteArrayOutputStream(35); DataOutputStream out=new DataOutputStream(out_stream); try { + if(obj == null) { + out_stream.write(NULL); + out_stream.flush(); + return out_stream.toByteArray(); + } if(obj instanceof MethodCall) { out.writeByte(METHOD_CALL); MethodCall call=(MethodCall)obj; @@ -582,7 +585,7 @@ else if(obj instanceof Cache.Value) { Cache.Value value=(Cache.Value)obj; out.writeByte(VALUE); - out.writeLong(value.getExpirationTime()); + out.writeLong(value.getTimeout()); Util.objectToStream(value.getValue(), out); } else { @@ -603,6 +606,8 @@ DataInputStream in=new DataInputStream(new ByteArrayInputStream(buf)); byte type=in.readByte(); + if(type == NULL) + return null; if(type == METHOD_CALL) { short id=in.readShort(); short length=in.readShort(); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/PullPushAdapter.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/PullPushAdapter.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/PullPushAdapter.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/PullPushAdapter.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,17 +1,14 @@ -// $Id: PullPushAdapter.java,v 1.25 2007/05/01 10:55:18 belaban Exp $ package org.jgroups.blocks; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.*; +import org.jgroups.conf.ClassConfigurator; import org.jgroups.util.Util; -import java.io.IOException; -import java.io.ObjectInput; -import java.io.ObjectOutput; -import java.io.Serializable; +import java.io.*; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -34,14 +31,14 @@ * @version $Revision * @deprecated Use {@link org.jgroups.Receiver} instead, this class will be removed in JGroups 3.0 */ -public class PullPushAdapter implements Runnable, ChannelListener { +public class PullPushAdapter extends ChannelListenerAdapter implements Runnable { protected Transport transport=null; protected MessageListener listener=null; // main message receiver protected final List membership_listeners=new ArrayList(); protected Thread receiver_thread=null; protected final HashMap listeners=new HashMap(); // keys=identifier (Serializable), values=MessageListeners protected final Log log=LogFactory.getLog(getClass()); - static final String PULL_HEADER="PULL_HEADER"; + static final short PULL_HEADER_ID=ClassConfigurator.getProtocolId(PullPushAdapter.class); public PullPushAdapter(Transport transport) { @@ -125,7 +122,7 @@ if(identifier == null) transport.send(msg); else { - msg.putHeader(PULL_HEADER, new PullHeader(identifier)); + msg.putHeader(PULL_HEADER_ID, new PullHeader(identifier)); transport.send(msg); } } @@ -302,7 +299,7 @@ } } catch(ChannelNotConnectedException conn) { - Address local_addr=((Channel)transport).getLocalAddress(); + Address local_addr=((Channel)transport).getAddress(); if(log.isTraceEnabled()) log.trace('[' + (local_addr == null ? "" : local_addr.toString()) + "] channel not connected, exception is " + conn); Util.sleep(1000); @@ -310,7 +307,7 @@ break; } catch(ChannelClosedException closed_ex) { - Address local_addr=((Channel)transport).getLocalAddress(); + Address local_addr=((Channel)transport).getAddress(); if(log.isTraceEnabled()) log.trace('[' + (local_addr == null ? "" : local_addr.toString()) + "] channel closed, exception is " + closed_ex); // Util.sleep(1000); @@ -329,7 +326,7 @@ * listener */ protected void handleMessage(Message msg) { - PullHeader hdr=(PullHeader)msg.getHeader(PULL_HEADER); + PullHeader hdr=(PullHeader)msg.getHeader(PULL_HEADER_ID); Serializable identifier; MessageListener l; @@ -422,23 +419,12 @@ public void channelClosed(Channel channel) { } - public void channelShunned() { - if(log.isTraceEnabled()) - log.trace("channel is shunned"); - } - - public void channelReconnected(Address addr) { - start(); - } - - public static final class PullHeader extends Header { Serializable identifier=null; public PullHeader() { - ; // used by externalization } public PullHeader(Serializable identifier) { @@ -461,12 +447,22 @@ return "PullHeader"; } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeObject(identifier); + public void writeTo(DataOutputStream out) throws IOException { + try { + Util.objectToStream(identifier, out); + } + catch(Exception e) { + throw new IOException(e); + } } - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - identifier=(Serializable)in.readObject(); + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + try { + identifier=(Serializable)Util.objectFromStream(in); + } + catch(Exception e) { + throw new IOException(e); + } } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/ReplCache.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/ReplCache.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/ReplCache.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/ReplCache.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,136 +1,1008 @@ package org.jgroups.blocks; -import org.jgroups.util.Streamable; +import org.jgroups.Address; +import org.jgroups.JChannel; +import org.jgroups.MembershipListener; +import org.jgroups.View; +import org.jgroups.annotations.Experimental; +import org.jgroups.annotations.ManagedAttribute; +import org.jgroups.annotations.ManagedOperation; +import org.jgroups.annotations.Unsupported; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.util.Rsp; +import org.jgroups.util.RspList; +import org.jgroups.util.TimeScheduler; +import org.jgroups.util.Util; + +import java.io.*; +import java.lang.reflect.Method; +import java.util.*; +import java.util.concurrent.TimeUnit; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ConcurrentHashMap; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.DataInputStream; /** * Cache which allows for replication factors per data items; the factor determines how many replicas * of a key/value we create across the cluster.
    * See doc/design/ReplCache.txt for details. * @author Bela Ban - * @version $Id: ReplCache.java,v 1.2 2008/12/23 16:52:25 belaban Exp $ */ -public class ReplCache { - private final ConcurrentMap cache=new ConcurrentHashMap(); +@Experimental @Unsupported +public class ReplCache implements MembershipListener, Cache.ChangeListener { + + /** The cache in which all entries are located. The value is a tuple, consisting of the replication count and the + * actual value */ + private Cache> l2_cache=new Cache>(); + + /** The local bounded cache, to speed up access to frequently accessed entries. Can be disabled or enabled */ + private Cache l1_cache=null; + + private static final Log log=LogFactory.getLog(ReplCache.class); + private JChannel ch=null; + private Address local_addr=null; + private View view; + private RpcDispatcher disp=null; + @ManagedAttribute(writable=true) + private String props="udp.xml"; + @ManagedAttribute(writable=true) + private String cluster_name="ReplCache-Cluster"; + @ManagedAttribute(writable=true) + private long call_timeout=1000L; + @ManagedAttribute(writable=true) + private long caching_time=30000L; // in milliseconds. -1 means don't cache, 0 means cache forever (or until changed) + + @ManagedAttribute + private short default_replication_count=1; // no replication by default + + private HashFunction hash_function=null; + + private HashFunctionFactory hash_function_factory=new HashFunctionFactory() { + public HashFunction create() { + return new ConsistentHashFunction(); + } + }; + + private Set membership_listeners=new HashSet(); + + private Set change_listeners=new HashSet(); + + /** On a view change, if a member P1 detects that for any given key K, P1 is not the owner of K, then + * it will compute the new owner P2 and transfer ownership for all Ks for which P2 is the new owner. P1 + * will then also evict those keys from its L2 cache */ + @ManagedAttribute(writable=true) + private boolean migrate_data=true; + + private static final short PUT = 1; + private static final short PUT_FORCE = 2; + private static final short GET = 3; + private static final short REMOVE = 4; + private static final short REMOVE_MANY = 5; + + protected static Map methods=Util.createConcurrentMap(8); + private TimeScheduler timer; + + + + + static { + try { + methods.put(PUT, ReplCache.class.getMethod("_put", + Object.class, + Object.class, + short.class, + long.class)); + methods.put(PUT_FORCE, ReplCache.class.getMethod("_put", + Object.class, + Object.class, + short.class, + long.class, boolean.class)); + methods.put(GET, ReplCache.class.getMethod("_get", + Object.class)); + methods.put(REMOVE, ReplCache.class.getMethod("_remove", Object.class)); + methods.put(REMOVE_MANY, ReplCache.class.getMethod("_removeMany", Set.class)); + } + catch(NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + + public interface HashFunction { + + /** + * Function that, given a key and a replication count, returns replication_count number of different + * addresses of nodes. + * @param key + * @param replication_count + * @return + */ + List
    hash(K key, short replication_count); + + /** + * When the topology changes, this method will be called. Implementations will typically cache the node list + * @param nodes + */ + void installNodes(List
    nodes); + } + + + public interface HashFunctionFactory { + HashFunction create(); + } + + + public ReplCache(String props, String cluster_name) { + this.props=props; + this.cluster_name=cluster_name; + } + + public String getProps() { + return props; + } + + public void setProps(String props) { + this.props=props; + } + + public Address getLocalAddress() { + return local_addr; + } + + @ManagedAttribute + public String getLocalAddressAsString() { + return local_addr != null? local_addr.toString() : "null"; + } + + @ManagedAttribute + public String getView() { + return view != null? view.toString() : "null"; + } + + @ManagedAttribute + public int getClusterSize() { + return view != null? view.size() : 0; + } + + @ManagedAttribute + public boolean isL1CacheEnabled() { + return l1_cache != null; + } + + public String getClusterName() { + return cluster_name; + } + + public void setClusterName(String cluster_name) { + this.cluster_name=cluster_name; + } + + public long getCallTimeout() { + return call_timeout; + } + + public void setCallTimeout(long call_timeout) { + this.call_timeout=call_timeout; + } + + public long getCachingTime() { + return caching_time; + } + + public void setCachingTime(long caching_time) { + this.caching_time=caching_time; + } + + public boolean isMigrateData() { + return migrate_data; + } + + public void setMigrateData(boolean migrate_data) { + this.migrate_data=migrate_data; + } + + public short getDefaultReplicationCount() { + return default_replication_count; + } + + public void setDefaultReplicationCount(short default_replication_count) { + this.default_replication_count=default_replication_count; + } + + public HashFunction getHashFunction() { + return hash_function; + } + + public void setHashFunction(HashFunction hash_function) { + this.hash_function=hash_function; + } + + public HashFunctionFactory getHashFunctionFactory() { + return hash_function_factory; + } + + public void setHashFunctionFactory(HashFunctionFactory hash_function_factory) { + this.hash_function_factory=hash_function_factory; + } + + public void addMembershipListener(MembershipListener l) { + membership_listeners.add(l); + } + + public void removeMembershipListener(MembershipListener l) { + membership_listeners.remove(l); + } + + public void addChangeListener(ChangeListener l) { + change_listeners.add(l); + } + + public void removeChangeListener(ChangeListener l) { + change_listeners.remove(l); + } + + public Cache getL1Cache() { + return l1_cache; + } + + public void setL1Cache(Cache cache) { + if(l1_cache != null) + l1_cache.stop(); + l1_cache=cache; + } + + public Cache> getL2Cache() { + return l2_cache; + } + + public void setL2Cache(Cache> cache) { + if(cache != null) { + l2_cache.stop(); + l2_cache=cache; + } + } + + + @ManagedOperation + public void start() throws Exception { + if(hash_function_factory != null) { + hash_function=hash_function_factory.create(); + } + if(hash_function == null) + hash_function=new ConsistentHashFunction(); + + ch=new JChannel(props); + disp=new RpcDispatcher(ch, null, this, this); + RpcDispatcher.Marshaller marshaller=new CustomMarshaller(); + disp.setRequestMarshaller(marshaller); + disp.setResponseMarshaller(marshaller); + disp.setMethodLookup(new MethodLookup() { + public Method findMethod(short id) { + return methods.get(id); + } + }); + + ch.connect(cluster_name); + local_addr=ch.getAddress(); + view=ch.getView(); + timer=ch.getProtocolStack().getTransport().getTimer(); + + l2_cache.addChangeListener(this); + } + + @ManagedOperation + public void stop() { + if(l1_cache != null) + l1_cache.stop(); + if(migrate_data) { + List
    members_without_me=new ArrayList
    (view.getMembers()); + members_without_me.remove(local_addr); + + HashFunction tmp_hash_function=hash_function_factory.create(); + tmp_hash_function.installNodes(members_without_me); + + for(Map.Entry>> entry: l2_cache.entrySet()) { + K key=entry.getKey(); + Cache.Value> val=entry.getValue(); + if(val == null) + continue; + Value tmp=val.getValue(); + if(tmp == null) + continue; + short repl_count=tmp.getReplicationCount(); + if(repl_count != 1) // we only handle keys which are not replicated and which are stored by us + continue; + + List
    nodes=tmp_hash_function.hash(key, repl_count); + if(nodes == null || nodes.isEmpty()) + continue; + if(!nodes.contains(local_addr)) { + Address dest=nodes.get(0); // should only have 1 element anyway + move(dest, key, tmp.getVal(), repl_count, val.getTimeout(), true); + _remove(key); + } + } + } + l2_cache.removeChangeListener(this); + l2_cache.stop(); + disp.stop(); + ch.close(); + } + /** * Places a key/value pair into one or several nodes in the cluster. * @param key The key, needs to be serializable * @param val The value, needs to be serializable - * @param repl_factor Number of replicas. + * @param repl_count Number of replicas. The total number of times a data item should be present in a cluster. + * Needs to be > 0 *
      *
    • -1: create key/val in all the nodes in the cluster - *
    • 0: create key/val only in one node in the cluster, picked by computing the consistent hash of KEY - *
    • K > 0: create key/val in those nodes in the cluster which match the consistent hashes created for KEY - *
    + *
  21. 1: create key/val only in one node in the cluster, picked by computing the consistent hash of KEY + *
  22. K > 1: create key/val in those nodes in the cluster which match the consistent hashes created for KEY + * * @param timeout Expiration time for key/value. *
      *
    • -1: don't cache at all in the L1 cache *
    • 0: cache forever, until removed or evicted because we need space for newer elements *
    • > 0: number of milliseconds to keep an idle element in the cache. An element is idle when not accessed. *
    + * @param synchronous Whether or not to block until all cluster nodes have applied the change */ - public void put(K key, V val, short repl_factor, long timeout) { + @ManagedOperation + public void put(K key, V val, short repl_count, long timeout, boolean synchronous) { + if(repl_count == 0) { + if(log.isWarnEnabled()) + log.warn("repl_count of 0 is invalid, data will not be stored in the cluster"); + return; + } + mcastPut(key, val, repl_count, timeout, synchronous); + if(l1_cache != null && timeout >= 0) + l1_cache.put(key, val, timeout); + } + + /** + * Places a key/value pair into one or several nodes in the cluster. + * @param key The key, needs to be serializable + * @param val The value, needs to be serializable + * @param repl_count Number of replicas. The total number of times a data item should be present in a cluster. + * Needs to be > 0 + *
      + *
    • -1: create key/val in all the nodes in the cluster + *
    • 1: create key/val only in one node in the cluster, picked by computing the consistent hash of KEY + *
    • K > 1: create key/val in those nodes in the cluster which match the consistent hashes created for KEY + *
    + * @param timeout Expiration time for key/value. + *
      + *
    • -1: don't cache at all in the L1 cache + *
    • 0: cache forever, until removed or evicted because we need space for newer elements + *
    • > 0: number of milliseconds to keep an idle element in the cache. An element is idle when not accessed. + *
    + */ + @ManagedOperation + public void put(K key, V val, short repl_count, long timeout) { + put(key, val, repl_count, timeout, false); // don't block (asynchronous put) by default } + + + @ManagedOperation + public void put(K key, V val) { + put(key, val, default_replication_count, caching_time); + } + + + /** * Returns the value associated with key * @param key The key, has to be serializable * @return The value associated with key, or null */ + @ManagedOperation public V get(K key) { - return null; + + // 1. Try the L1 cache first + if(l1_cache != null) { + V val=l1_cache.get(key); + if(val != null) { + if(log.isTraceEnabled()) + log.trace("returned value " + val + " for " + key + " from L1 cache"); + return val; + } + } + + // 2. Try the local cache + Cache.Value> val=l2_cache.getEntry(key); + Value tmp; + if(val != null) { + tmp=val.getValue(); + if(tmp !=null) { + V real_value=tmp.getVal(); + if(real_value != null && l1_cache != null && val.getTimeout() >= 0) + l1_cache.put(key, real_value, val.getTimeout()); + return tmp.getVal(); + } + } + + // 3. Execute a cluster wide GET + try { + RspList rsps=disp.callRemoteMethods(null, + new MethodCall(GET, new Object[]{key}), + GroupRequest.GET_ALL, + call_timeout); + for(Rsp rsp: rsps.values()) { + Object obj=rsp.getValue(); + if(obj == null || obj instanceof Throwable) + continue; + val=(Cache.Value>)rsp.getValue(); + if(val != null) { + tmp=val.getValue(); + if(tmp != null) { + V real_value=tmp.getVal(); + if(real_value != null && l1_cache != null && val.getTimeout() >= 0) + l1_cache.put(key, real_value, val.getTimeout()); + return real_value; + } + } + } + return null; + } + catch(Throwable t) { + if(log.isWarnEnabled()) + log.warn("get() failed", t); + return null; + } } /** * Removes key in all nodes in the cluster, both from their local hashmaps and L1 caches - * @param key The key, needs to be serializable + * @param key The key, needs to be serializable */ + @ManagedOperation public void remove(K key) { - + remove(key, false); // by default we use asynchronous removals } - private static class Message implements Streamable { - static enum Type {PUT, GET, REMOVE}; - private Type type; - private K key; - private V val; - private short repl_factor=-1; // default is replicate everywhere - private long timeout=30000; - - private Message(Type type, K key, V val, short repl_factor, long timeout) { - this.type=type; - this.key=key; - this.val=val; - this.repl_factor=repl_factor; - this.timeout=timeout; + /** + * Removes key in all nodes in the cluster, both from their local hashmaps and L1 caches + * @param key The key, needs to be serializable + */ + @ManagedOperation + public void remove(K key, boolean synchronous) { + try { + disp.callRemoteMethods(null, new MethodCall(REMOVE, new Object[]{key}), + synchronous? GroupRequest.GET_ALL : GroupRequest.GET_NONE, call_timeout); + if(l1_cache != null) + l1_cache.remove(key); + } + catch(Throwable t) { + if(log.isWarnEnabled()) + log.warn("remove() failed", t); + } + } + + + /** + * Removes all keys and values in the L2 and L1 caches + */ + @ManagedOperation + public void clear() { + Set keys=new HashSet(l2_cache.getInternalMap().keySet()); + mcastClear(keys, false); + } + + public V _put(K key, V val, short repl_count, long timeout) { + return _put(key, val, repl_count, timeout, false); + } + + /** + * + * @param key + * @param val + * @param repl_count + * @param timeout + * @param force Skips acceptance checking and simply adds the key/value + * @return + */ + public V _put(K key, V val, short repl_count, long timeout, boolean force) { + + if(!force) { + + // check if we need to host the data + boolean accept=repl_count == -1; + + if(!accept) { + if(view != null && repl_count >= view.size()) { + accept=true; + } + else { + List
    selected_hosts=hash_function != null? hash_function.hash(key, repl_count) : null; + if(selected_hosts != null) { + if(log.isTraceEnabled()) + log.trace("local=" + local_addr + ", hosts=" + selected_hosts); + for(Address addr: selected_hosts) { + if(addr.equals(local_addr)) { + accept=true; + break; + } + } + } + if(!accept) + return null; + } + } } - private Message(Type type, K key) { - this.type=type; - this.key=key; + if(log.isTraceEnabled()) + log.trace("_put(" + key + ", " + val + ", " + repl_count + ", " + timeout + ")"); + + Value value=new Value(val, repl_count); + Value retval=l2_cache.put(key, value, timeout); + + if(l1_cache != null) + l1_cache.remove(key); + + notifyChangeListeners(); + + return retval != null? retval.getVal() : null; + } + + public Cache.Value> _get(K key) { + if(log.isTraceEnabled()) + log.trace("_get(" + key + ")"); + return l2_cache.getEntry(key); + } + + public V _remove(K key) { + if(log.isTraceEnabled()) + log.trace("_remove(" + key + ")"); + Value retval=l2_cache.remove(key); + if(l1_cache != null) + l1_cache.remove(key); + notifyChangeListeners(); + return retval != null? retval.getVal() : null; + } + + + public void _removeMany(Set keys) { + if(log.isTraceEnabled()) + log.trace("_removeMany(): " + keys.size() + " entries"); + for(K key: keys) + _remove(key); + } + + + + + + public void viewAccepted(final View new_view) { + final List
    old_nodes=this.view != null? new ArrayList
    (this.view.getMembers()) : null; + + this.view=new_view; + if(log.isInfoEnabled()) + log.info("new view: " + new_view); + + if(hash_function != null) + hash_function.installNodes(new_view.getMembers()); + + for(MembershipListener l: membership_listeners) + l.viewAccepted(new_view); + + if(old_nodes != null) { + timer.schedule(new Runnable() { + public void run() { + rebalance(old_nodes, new ArrayList
    (new_view.getMembers())); + } + }, 100, TimeUnit.MILLISECONDS); } + } - public static Message createPutMessage(K key, V val, short repl_factor, long timeout) { - return new Message(Type.PUT, key, val, repl_factor, timeout); + + + public void suspect(Address suspected_mbr) { + } + + public void block() { + } + + public void changed() { + notifyChangeListeners(); + } + + public String toString() { + StringBuilder sb=new StringBuilder(); + if(l1_cache != null) + sb.append("L1 cache: " + l1_cache.getSize() + " entries"); + sb.append("\nL2 cache: " + l2_cache.getSize() + " entries()"); + return sb.toString(); + } + + + @ManagedOperation + public String dump() { + StringBuilder sb=new StringBuilder(); + if(l1_cache != null) { + sb.append("L1 cache:\n").append(l1_cache.dump()); } + sb.append("\nL2 cache:\n").append(l2_cache.dump()); + + return sb.toString(); + } - public static Message createGetMessage(K key) { - return new Message(Type.GET, key); + private void notifyChangeListeners() { + for(ChangeListener l: change_listeners) { + try { + l.changed(); + } + catch(Throwable t) { + if(log.isErrorEnabled()) + log.error("failed notifying change listener", t); + } } + } + - public static Message createRemoveMessage(K key) { - return new Message(Type.REMOVE, key); + + private void rebalance(List
    old_nodes, List
    new_nodes) { + HashFunction old_func=hash_function_factory.create(); + old_func.installNodes(old_nodes); + + HashFunction new_func=hash_function_factory.create(); + new_func.installNodes(new_nodes); + + boolean is_coord=Util.isCoordinator(ch); + + List keys=new ArrayList(l2_cache.getInternalMap().keySet()); + + for(K key: keys) { + Cache.Value> val=l2_cache.getEntry(key); + if(log.isTraceEnabled()) + log.trace("==== rebalancing " + key); + if(val == null) { + if(log.isWarnEnabled()) + log.warn(key + " has no value associated; ignoring"); + continue; + } + Value tmp=val.getValue(); + if(tmp == null) { + if(log.isWarnEnabled()) + log.warn(key + " has no value associated; ignoring"); + continue; + } + V real_value=tmp.getVal(); + short repl_count=tmp.getReplicationCount(); + List
    new_mbrs=Util.newMembers(old_nodes, new_nodes); + + if(repl_count == -1) { + if(is_coord) { + for(Address new_mbr: new_mbrs) { + move(new_mbr, key, real_value, repl_count, val.getTimeout(), false); + } + } + } + else if(repl_count == 1) { + List
    tmp_nodes=new_func.hash(key, repl_count); + if(!tmp_nodes.isEmpty()) { + Address mbr=tmp_nodes.get(0); + if(!mbr.equals(local_addr)) { + move(mbr, key, real_value, repl_count, val.getTimeout(), false); + _remove(key); + } + } + } + else if(repl_count > 1) { + List
    tmp_old=old_func.hash(key, repl_count); + List
    tmp_new=new_func.hash(key, repl_count); + + if(log.isTraceEnabled()) + log.trace("old nodes: " + tmp_old + "\nnew nodes: " + tmp_new); + if(tmp_old != null && tmp_new != null && tmp_old.equals(tmp_new)) + continue; + mcastPut(key, real_value, repl_count, val.getTimeout(), false); + if(tmp_new != null && !tmp_new.contains(local_addr)) { + _remove(key); + } + } + else { + throw new IllegalStateException("replication count is invalid (" + repl_count + ")"); + } } + } + public void mcastEntries() { + for(Map.Entry>> entry: l2_cache.entrySet()) { + K key=entry.getKey(); + Cache.Value> val=entry.getValue(); + if(val == null) { + if(log.isWarnEnabled()) + log.warn(key + " has no value associated; ignoring"); + continue; + } + Value tmp=val.getValue(); + if(tmp == null) { + if(log.isWarnEnabled()) + log.warn(key + " has no value associated; ignoring"); + continue; + } + V real_value=tmp.getVal(); + short repl_count=tmp.getReplicationCount(); - public String toString() { - return "type=" + type + - ", key=" + key + - ", val=" + val + - ", repl_factor=" + repl_factor + - ", timeout=" + timeout; + if(repl_count > 1) { + _remove(key); + mcastPut(key, real_value, repl_count, val.getTimeout(), false); + } } + } + private void mcastPut(K key, V val, short repl_count, long caching_time, boolean synchronous) { + try { + int mode=synchronous? GroupRequest.GET_ALL : GroupRequest.GET_NONE; + disp.callRemoteMethods(null, new MethodCall(PUT, new Object[]{key, val, repl_count, caching_time}), mode, call_timeout); + } + catch(Throwable t) { + if(log.isWarnEnabled()) + log.warn("put() failed", t); + } + } - public void writeTo(DataOutputStream out) throws IOException { - switch(type) { - case PUT: - out.writeByte(1); - break; - case GET: - out.writeByte(2); - break; - case REMOVE: - out.writeByte(3); + private void mcastClear(Set keys, boolean synchronous) { + try { + int mode=synchronous? GroupRequest.GET_ALL : GroupRequest.GET_NONE; + disp.callRemoteMethods(null, new MethodCall(REMOVE_MANY, new Object[]{keys}), mode, call_timeout); + } + catch(Throwable t) { + if(log.isWarnEnabled()) + log.warn("clear() failed", t); + } + } + + + private void move(Address dest, K key, V val, short repl_count, long caching_time, boolean synchronous) { + try { + int mode=synchronous? GroupRequest.GET_ALL : GroupRequest.GET_NONE; + disp.callRemoteMethod(dest, new MethodCall(PUT_FORCE, new Object[]{key, val, repl_count, caching_time, true}), + mode, call_timeout); + } + catch(Throwable t) { + if(log.isWarnEnabled()) + log.warn("move() failed", t); + } + } + + + public static interface ChangeListener { + void changed(); + } + + public static class ConsistentHashFunction implements HashFunction { + private SortedMap nodes=new TreeMap(); + private final static int HASH_SPACE=2000; // must be > max number of nodes in a cluster + private final static int FACTOR=3737; // to better spread the node out across the space + + public List
    hash(K key, short replication_count) { + int hash=Math.abs(key.hashCode()); + int index=hash % HASH_SPACE; + + Set
    results=new LinkedHashSet
    (); + List
    retval=new ArrayList
    (); + + SortedMap tailmap=nodes.tailMap((short)index); + int count=0; + + for(Map.Entry entry: tailmap.entrySet()) { + Address val=entry.getValue(); + results.add(val); + if(++count >= replication_count) break; - default: - throw new IllegalStateException("type " + type + " is not handled"); } + + if(count < replication_count) { + for(Map.Entry entry: nodes.entrySet()) { + Address val=entry.getValue(); + results.add(val); + if(++count >= replication_count) + break; + } + } + + retval.addAll(results); + return retval; } - public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { - byte tmp=in.readByte(); - switch(tmp) { - case 1: - type=Type.PUT; - break; - case 2: - type=Type.GET; - break; - case 3: - type=Type.REMOVE; - break; - default: - throw new IllegalStateException("type " + tmp + " is not handled"); + public void installNodes(List
    new_nodes) { + nodes.clear(); + for(Address node: new_nodes) { + int hash=Math.abs(node.hashCode() * FACTOR) % HASH_SPACE; + for(int i=hash; i < hash + HASH_SPACE; i++) { + short new_index=(short)(i % HASH_SPACE); + if(!nodes.containsKey(new_index)) { + nodes.put(new_index, node); + break; + } + } + } + + if(log.isTraceEnabled()) { + StringBuilder sb=new StringBuilder("node mappings:\n"); + for(Map.Entry entry: nodes.entrySet()) { + sb.append(entry.getKey() + ": " + entry.getValue()).append("\n"); + } + log.trace(sb); + } + } + + } + + + /** + * Uses arrays to store hash values of addresses, plus addresses. + */ +/* public static class ArrayBasedConsistentHashFunction extends MembershipListenerAdapter implements HashFunction { + Object[] nodes=null; + private final static int HASH_SPACE=2000; // must be > max number of nodes in a cluster + + public Address hash(K key, List
    members) { + int hash=Math.abs(key.hashCode()); + int index=hash % HASH_SPACE; + + if(members != null && !members.isEmpty()) { + Object[] tmp=new Object[nodes.length]; + System.arraycopy(nodes, 0, tmp, 0, nodes.length); + for(int i=0; i < tmp.length; i+=2) { + if(!members.contains(tmp[i+1])) { + tmp[i]=tmp[i+1]=null; + } + } + return findFirst(tmp, index); + } + return findFirst(nodes, index); + } + + public void viewAccepted(View new_view) { + nodes=new Object[new_view.size() * 2]; + int index=0; + for(Address node: new_view.getMembers()) { + int hash=Math.abs(node.hashCode()) % HASH_SPACE; + nodes[index++]=hash; + nodes[index++]=node; + } + + if(log.isTraceEnabled()) { + StringBuilder sb=new StringBuilder("node mappings:\n"); + for(int i=0; i < nodes.length; i+=2) { + sb.append(nodes[i] + ": " + nodes[i+1]).append("\n"); + } + log.trace(sb); + } + } + + public void suspect(Address suspected_mbr) { + } + + public void block() { + } + + private static Address findFirst(Object[] array, int index) { + Address retval=null; + if(array == null) + return null; + + for(int i=0; i < array.length; i+=2) { + if(array[i] == null) + continue; + if(array[i+1] != null) + retval=(Address)array[i+1]; + if(((Integer)array[i]) >= index) + return (Address)array[i+1]; + } + return retval; + } + }*/ + + + + public static class Value implements Serializable { + private final V val; + private final short replication_count; + private static final long serialVersionUID=-2892941069742740027L; + + + public Value(V val, short replication_count) { + this.val=val; + this.replication_count=replication_count; + } + + public V getVal() { + return val; + } + + + public short getReplicationCount() { + return replication_count; + } + + public String toString() { + return val + " (" + replication_count + ")"; + } + } + + + private static class CustomMarshaller implements RpcDispatcher.Marshaller { + static final byte NULL = 0; + static final byte OBJ = 1; + static final byte METHOD_CALL = 2; + static final byte VALUE = 3; + + + public byte[] objectToByteBuffer(Object obj) throws Exception { + ByteArrayOutputStream out_stream=new ByteArrayOutputStream(35); + DataOutputStream out=new DataOutputStream(out_stream); + try { + if(obj == null) { + out_stream.write(NULL); + out_stream.flush(); + return out_stream.toByteArray(); + } + + if(obj instanceof MethodCall) { + out.writeByte(METHOD_CALL); + MethodCall call=(MethodCall)obj; + out.writeShort(call.getId()); + Object[] args=call.getArgs(); + if(args == null || args.length == 0) { + out.writeShort(0); + } + else { + out.writeShort(args.length); + for(int i=0; i < args.length; i++) { + Util.objectToStream(args[i], out); + } + } + } + else if(obj instanceof Cache.Value) { + Cache.Value value=(Cache.Value)obj; + out.writeByte(VALUE); + out.writeLong(value.getTimeout()); + Util.objectToStream(value.getValue(), out); + } + else { + out.writeByte(OBJ); + Util.objectToStream(obj, out); + } + out.flush(); + return out_stream.toByteArray(); + } + finally { + Util.close(out); + } + } + + public Object objectFromByteBuffer(byte[] buf) throws Exception { + if(buf == null) + return null; + + DataInputStream in=new DataInputStream(new ByteArrayInputStream(buf)); + byte type=in.readByte(); + if(type == NULL) + return null; + if(type == METHOD_CALL) { + short id=in.readShort(); + short length=in.readShort(); + Object[] args=length > 0? new Object[length] : null; + if(args != null) { + for(int i=0; i < args.length; i++) + args[i]=Util.objectFromStream(in); + } + return new MethodCall(id, args); + } + else if(type == VALUE) { + long expiration_time=in.readLong(); + Object obj=Util.objectFromStream(in); + return new Cache.Value(obj, expiration_time); } + else + return Util.objectFromStream(in); } } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/ReplicatedHashMap.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/ReplicatedHashMap.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/ReplicatedHashMap.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/ReplicatedHashMap.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,9 +1,9 @@ package org.jgroups.blocks; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.*; import org.jgroups.annotations.Unsupported; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.persistence.PersistenceFactory; import org.jgroups.persistence.PersistenceManager; import org.jgroups.util.Promise; @@ -41,7 +41,6 @@ * into one class * * @author Bela Ban - * @version $Id: ReplicatedHashMap.java,v 1.18 2008/05/13 12:33:53 belaban Exp $ */ @Unsupported(comment="Use JBossCache instead") public class ReplicatedHashMap extends @@ -52,7 +51,7 @@ void entryRemoved(K key); - void viewChange(View view, Vector
    new_mbrs, Vector
    old_mbrs); + void viewChange(View view, Vector
    mbrs_joined, Vector
    mbrs_left); void contentsSet(Map new_entries); @@ -119,7 +118,7 @@ * Whether updates across the cluster should be asynchronous (default) or * synchronous) */ - protected int update_mode=GroupRequest.GET_NONE; + protected int update_mode=Request.GET_NONE; /** * For blocking updates only: the max time to wait (0 == forever) @@ -350,7 +349,7 @@ } public Address getLocalAddress() { - return channel != null? channel.getLocalAddress() : null; + return channel != null? channel.getAddress() : null; } public String getClusterName() { @@ -466,7 +465,7 @@ public void putAll(Map m) { if(send_message) { try { - MethodCall call=new MethodCall(PUT_ALL, new Object[] { m }); + MethodCall call=new MethodCall(PUT_ALL, m); disp.callRemoteMethods(null, call, update_mode, timeout); } catch(Throwable t) { @@ -486,7 +485,7 @@ //if true, propagate action to the group if(send_message) { try { - MethodCall call=new MethodCall(CLEAR, null); + MethodCall call=new MethodCall(CLEAR); disp.callRemoteMethods(null, call, update_mode, timeout); } catch(Exception e) { @@ -514,7 +513,7 @@ if(send_message) { try { - MethodCall call=new MethodCall(REMOVE, new Object[] { key }); + MethodCall call=new MethodCall(REMOVE, key); disp.callRemoteMethods(null, call, update_mode, timeout); } catch(Exception e) { @@ -539,7 +538,7 @@ if(send_message) { try { - MethodCall call=new MethodCall(REMOVE_IF_EQUALS, new Object[] { key, value }); + MethodCall call=new MethodCall(REMOVE_IF_EQUALS, key, value); disp.callRemoteMethods(null, call, update_mode, timeout); } catch(Exception e) { @@ -564,9 +563,7 @@ if(send_message) { try { - MethodCall call=new MethodCall(REPLACE_IF_EQUALS, new Object[] { key, - oldValue, - newValue }); + MethodCall call=new MethodCall(REPLACE_IF_EQUALS, key, oldValue, newValue); disp.callRemoteMethods(null, call, update_mode, timeout); } catch(Exception e) { @@ -597,7 +594,7 @@ if(send_message) { try { - MethodCall call=new MethodCall(REPLACE_IF_EXISTS, new Object[] { key, value }); + MethodCall call=new MethodCall(REPLACE_IF_EXISTS, key, value); disp.callRemoteMethods(null, call, update_mode, timeout); } catch(Exception e) { @@ -806,63 +803,8 @@ state_promise.setResult(Boolean.TRUE); } - /*------------------- Membership Changes ----------------------*/ - - public void viewAccepted(View new_view) { - Vector
    new_mbrs=new_view.getMembers(); - - if(new_mbrs != null) { - sendViewChangeNotifications(new_view, new_mbrs, new Vector
    (members)); // notifies observers (joined, left) - members.clear(); - members.addAll(new_mbrs); - } - //if size is bigger than one, there are more peers in the group - //otherwise there is only one server. - send_message=members.size() > 1; - } - - /** - * Called when a member is suspected - */ - public void suspect(Address suspected_mbr) { - ; - } - - /** - * Block sending and receiving of messages until ViewAccepted is called - */ - public void block() {} - - void sendViewChangeNotifications(View view, Vector
    new_mbrs, Vector
    old_mbrs) { - Vector
    joined, left; - - if((notifs.isEmpty()) || (old_mbrs == null) - || (new_mbrs == null) - || (old_mbrs.isEmpty()) - || (new_mbrs.isEmpty())) - return; - - // 1. Compute set of members that joined: all that are in new_mbrs, but not in old_mbrs - joined=new Vector
    (); - for(Address mbr:new_mbrs) { - if(!old_mbrs.contains(mbr)) - joined.addElement(mbr); - } - // 2. Compute set of members that left: all that were in old_mbrs, but not in new_mbrs - left=new Vector
    (); - for(Address mbr:old_mbrs) { - if(!new_mbrs.contains(mbr)) { - left.addElement(mbr); - } - } - - for(Notification notif:notifs) { - notif.viewChange(view, joined, left); - } - } - - public byte[] getState(String state_id) { + public byte[] getState(String state_id) { // not implemented return null; } @@ -919,6 +861,63 @@ public void setState(String state_id, InputStream istream) {} + /*------------------- Membership Changes ----------------------*/ + + public void viewAccepted(View new_view) { + Vector
    new_mbrs=new_view.getMembers(); + + if(new_mbrs != null) { + sendViewChangeNotifications(new_view, new_mbrs, new Vector
    (members)); // notifies observers (joined, left) + members.clear(); + members.addAll(new_mbrs); + } + //if size is bigger than one, there are more peers in the group + //otherwise there is only one server. + send_message=members.size() > 1; + } + + /** + * Called when a member is suspected + */ + public void suspect(Address suspected_mbr) { + ; + } + + /** + * Block sending and receiving of messages until ViewAccepted is called + */ + public void block() {} + + void sendViewChangeNotifications(View view, Vector
    new_mbrs, Vector
    old_mbrs) { + Vector
    joined, left; + + if((notifs.isEmpty()) || (old_mbrs == null) + || (new_mbrs == null) + || (old_mbrs.isEmpty()) + || (new_mbrs.isEmpty())) + return; + + // 1. Compute set of members that joined: all that are in new_mbrs, but not in old_mbrs + joined=new Vector
    (); + for(Address mbr:new_mbrs) { + if(!old_mbrs.contains(mbr)) + joined.addElement(mbr); + } + + // 2. Compute set of members that left: all that were in old_mbrs, but not in new_mbrs + left=new Vector
    (); + for(Address mbr:old_mbrs) { + if(!new_mbrs.contains(mbr)) { + left.addElement(mbr); + } + } + + for(Notification notif:notifs) { + notif.viewChange(view, joined, left); + } + } + + public void unblock() {} /** diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/ReplicatedHashtable.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/ReplicatedHashtable.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/ReplicatedHashtable.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/ReplicatedHashtable.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,512 +0,0 @@ -// $Id: ReplicatedHashtable.java,v 1.18 2008/04/08 14:41:22 belaban Exp $ - -package org.jgroups.blocks; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jgroups.*; -import org.jgroups.annotations.Unsupported; -import org.jgroups.util.Util; - -import java.io.Serializable; -import java.util.*; - -/** - * Provides the abstraction of a java.util.Hashtable that is replicated at several - * locations. Any change to the hashtable (clear, put, remove etc) will transparently be - * propagated to all replicas in the group. All read-only methods will always access the - * local replica.

    - * Both keys and values added to the hashtable must be serializable, the reason - * being that they will be sent across the network to all replicas of the group. Having said - * this, it is now for example possible to add RMI remote objects to the hashtable as they - * are derived from java.rmi.server.RemoteObject which in turn is serializable. - * This allows to lookup shared distributed objects by their name and invoke methods on them, - * regardless of one's onw location. A ReplicatedHashtable thus allows to - * implement a distributed naming service in just a couple of lines.

    - * An instance of this class will contact an existing member of the group to fetch its - * initial state.

    - * Contrary to DistributedHashtable, this class does not make use of RpcDispatcher (and RequestCorrelator) - * but uses plain asynchronous messages instead. - * @author Bela Ban - * @author Alfonso Olias-Sanz - * @deprecated Use {@link org.jgroups.blocks.ReplicatedHashMap} instead - */ -@Unsupported -public class ReplicatedHashtable extends Hashtable implements MessageListener, MembershipListener { - - public interface Notification { - void entrySet(Object key, Object value); - - void entryRemoved(Object key); - - void viewChange(Vector new_mbrs, Vector old_mbrs); - - void contentsSet(Map new_entries); - } - - public interface StateTransferListener { - void stateTransferStarted(); - - void stateTransferCompleted(boolean success); - } - - transient Channel channel; - transient PullPushAdapter adapter=null; - final transient Vector notifs=new Vector(); - // to be notified when mbrship changes - final transient Vector members=new Vector(); // keeps track of all DHTs - final transient List state_transfer_listeners=new ArrayList(); - transient boolean state_transfer_running=false; - - /** Determines when the updates have to be sent across the network, avoids sending unnecessary - * messages when there are no member in the group */ - private transient boolean send_message=false; - - protected final transient Log log=LogFactory.getLog(this.getClass()); - - /** - * Creates a ReplicatedHashtable - * @param groupname The name of the group to join - * @param factory The ChannelFactory which will be used to create a channel - * @param properties The property string to be used to define the channel - * @param state_timeout The time to wait until state is retrieved in milliseconds. A value of 0 means wait forever. - */ - public ReplicatedHashtable(String groupname, ChannelFactory factory, StateTransferListener l, String properties, long state_timeout) { - if(l != null) - addStateTransferListener(l); - try { - channel=factory != null ? factory.createChannel((Object)properties) : new JChannel(properties); - channel.connect(groupname); - adapter=new PullPushAdapter(channel, this, this); - adapter.setListener(this); - getInitState(channel, state_timeout); - } - catch(Exception e) { - if(log.isErrorEnabled()) log.error("exception=" + e); - } - } - - private void getInitState(Channel channel, long state_timeout) throws ChannelClosedException, ChannelNotConnectedException { - try { - notifyStateTransferStarted(); - boolean rc=channel.getState(null, state_timeout); - if(rc) { - if(log.isInfoEnabled()) log.info("state was retrieved successfully"); - } - else { - if(log.isInfoEnabled()) log.info("state could not be retrieved (first member)"); - notifyStateTransferCompleted(false); - } - } - catch(ChannelClosedException ex) { - notifyStateTransferCompleted(false); - throw ex; - } - catch(ChannelNotConnectedException ex2) { - notifyStateTransferCompleted(false); - throw ex2; - } - } - - public ReplicatedHashtable(String groupname, ChannelFactory factory, String properties, long state_timeout) { - this(groupname, factory, null, properties, state_timeout); - } - - public ReplicatedHashtable(JChannel channel, long state_timeout) throws ChannelClosedException, ChannelNotConnectedException { - this(channel, null, state_timeout); - } - - public ReplicatedHashtable(JChannel channel, StateTransferListener l, long state_timeout) throws ChannelClosedException, ChannelNotConnectedException { - this.channel=channel; - this.adapter=new PullPushAdapter(channel, this, this); - this.adapter.setListener(this); - if(l != null) - addStateTransferListener(l); - getInitState(channel, state_timeout); -// boolean rc=channel.getState(null, state_timeout); -// if(rc) -// if(log.isInfoEnabled()) log.info("state was retrieved successfully"); -// else -// if(log.isInfoEnabled()) log.info("state could not be retrieved (first member)"); - } - - public boolean stateTransferRunning() { - return state_transfer_running; - } - - public Address getLocalAddress() { - return channel != null ? channel.getLocalAddress() : null; - } - - public Channel getChannel() { - return channel; - } - - public void addNotifier(Notification n) { - if(!notifs.contains(n)) - notifs.addElement(n); - } - - public final void addStateTransferListener(StateTransferListener l) { - if(l != null && !(state_transfer_listeners.contains(l))) - state_transfer_listeners.add(l); - } - - public void removeStateTransferListener(StateTransferListener l) { - if(l != null) - state_transfer_listeners.remove(l); - } - - /** - * Maps the specified key to the specified value in the hashtable. Neither of both parameters can be null - * @param key - the hashtable key - * @param value - the value - * @return the previous value of the specified key in this hashtable, or null if it did not have one - */ - public Object put(Object key, Object value) { - Message msg; - Object prev_val=null; - prev_val=get(key); - - //Changes done by - //if true, send message to the group - if(send_message == true) { - try { - msg=new Message(null, null, new Request(Request.PUT, key, value)); - channel.send(msg); - //return prev_val; - } - catch(Exception e) { - //return null; - } - } - else { - super.put(key, value); - //don't have to do prev_val = super.put(..) as is done at the beginning - } - return prev_val; - } - - /** - * Copies all of the mappings from the specified Map to this Hashtable These mappings will replace any mappings that this Hashtable had for any of the keys currently in the specified Map. - * @param m - Mappings to be stored in this map - */ - public void putAll(Map m) { - Message msg; - //Changes done by - //if true, send message to the group - if(send_message == true) { - try { - msg=new Message(null, null, new Request(Request.PUT_ALL, null, m)); - channel.send(msg); - } - catch(Exception e) { - if(log.isErrorEnabled()) log.error("exception=" + e); - } - } - else { - super.putAll(m); - } - } - - /** - * Clears this hashtable so that it contains no keys - */ - public void clear() { - Message msg; - //Changes done by - //if true, send message to the group - if(send_message == true) { - try { - msg=new Message(null, null, new Request(Request.CLEAR, null, null)); - channel.send(msg); - } - catch(Exception e) { - if(log.isErrorEnabled()) log.error("exception=" + e); - } - } - else { - super.clear(); - } - } - - /** - * Removes the key (and its corresponding value) from the Hashtable. - * @param key - the key to be removed. - * @return the value to which the key had been mapped in this hashtable, or null if the key did not have a mapping. - */ - public Object remove(Object key) { - Message msg; - Object retval=null; - retval=get(key); - - //Changes done by - //if true, propagate action to the group - if(send_message == true) { - try { - msg=new Message(null, null, new Request(Request.REMOVE, key, null)); - channel.send(msg); - //return retval; - } - catch(Exception e) { - //return null; - } - } - else { - super.remove(key); - //don't have to do retval = super.remove(..) as is done at the beginning - } - return retval; - } - - /*------------------------ Callbacks -----------------------*/ - Object _put(Object key, Object value) { - Object retval=super.put(key, value); - for(int i=0; i < notifs.size(); i++) - ((Notification)notifs.elementAt(i)).entrySet(key, value); - return retval; - } - - void _clear() { - super.clear(); - } - - Object _remove(Object key) { - Object retval=super.remove(key); - for(int i=0; i < notifs.size(); i++) - ((Notification)notifs.elementAt(i)).entryRemoved(key); - return retval; - } - - /** - * @see java.util.Map#putAll(java.util.Map) - */ - public void _putAll(Map m) { - if(m == null) - return; - //######## The same way as in the DistributedHashtable - // Calling the method below seems okay, but would result in ... deadlock ! - // The reason is that Map.putAll() calls put(), which we override, which results in - // lock contention for the map. - // ---> super.putAll(m); <--- CULPRIT !!!@#$%$ - // That said let's do it the stupid way: - //######## The same way as in the DistributedHashtable - Map.Entry entry; - for(Iterator it=m.entrySet().iterator(); it.hasNext();) { - entry=(Map.Entry)it.next(); - super.put(entry.getKey(), entry.getValue()); - } - - for(int i=0; i < notifs.size(); i++) - ((Notification)notifs.elementAt(i)).contentsSet(m); - } - /*----------------------------------------------------------*/ - - /*-------------------- MessageListener ----------------------*/ - - public void receive(Message msg) { - Request req=null; - - if(msg == null) - return; - req=(Request)msg.getObject(); - if(req == null) - return; - switch(req.req_type) { - case Request.PUT: - if(req.key != null && req.val != null) - _put(req.key, req.val); - break; - case Request.REMOVE: - if(req.key != null) - _remove(req.key); - break; - case Request.CLEAR: - _clear(); - break; - - case Request.PUT_ALL: - if(req.val != null) - _putAll((Map)req.val); - break; - default : - // error - } - } - - public byte[] getState() { - Object key, val; - Hashtable copy=new Hashtable(); - - for(Enumeration e=keys(); e.hasMoreElements();) { - key=e.nextElement(); - val=get(key); - copy.put(key, val); - } - try { - return Util.objectToByteBuffer(copy); - } - catch(Exception ex) { - if(log.isErrorEnabled()) log.error("exception marshalling state: " + ex); - return null; - } - } - - public void setState(byte[] new_state) { - Hashtable new_copy; - Object key; - - try { - new_copy=(Hashtable)Util.objectFromByteBuffer(new_state); - if(new_copy == null) { - notifyStateTransferCompleted(true); - return; - } - } - catch(Throwable ex) { - if(log.isErrorEnabled()) log.error("exception unmarshalling state: " + ex); - notifyStateTransferCompleted(false); - return; - } - - _clear(); // remove all elements - for(Enumeration e=new_copy.keys(); e.hasMoreElements();) { - key=e.nextElement(); - _put(key, new_copy.get(key)); - } - notifyStateTransferCompleted(true); - } - - /*-------------------- End of MessageListener ----------------------*/ - - /*----------------------- MembershipListener ------------------------*/ - - public void viewAccepted(View new_view) { - Vector new_mbrs=new_view.getMembers(); - - if(new_mbrs != null) { - sendViewChangeNotifications(new_mbrs, members); - // notifies observers (joined, left) - members.removeAllElements(); - for(int i=0; i < new_mbrs.size(); i++) - members.addElement(new_mbrs.elementAt(i)); - } - //if size is bigger than one, there are more peers in the group - //otherwise there is only one server. - if(members.size() > 1) { - send_message=true; - } - else { - send_message=false; - } - } - - /** Called when a member is suspected */ - public void suspect(Address suspected_mbr) { - ; - } - - /** Block sending and receiving of messages until ViewAccepted is called */ - public void block() { - } - - /*------------------- End of MembershipListener ----------------------*/ - - void sendViewChangeNotifications(Vector new_mbrs, Vector old_mbrs) { - Vector joined, left; - Object mbr; - Notification n; - - if(notifs.size() == 0 || old_mbrs == null || new_mbrs == null || old_mbrs.size() == 0 || new_mbrs.size() == 0) - return; - - // 1. Compute set of members that joined: all that are in new_mbrs, but not in old_mbrs - joined=new Vector(); - for(int i=0; i < new_mbrs.size(); i++) { - mbr=new_mbrs.elementAt(i); - if(!old_mbrs.contains(mbr)) - joined.addElement(mbr); - } - - // 2. Compute set of members that left: all that were in old_mbrs, but not in new_mbrs - left=new Vector(); - for(int i=0; i < old_mbrs.size(); i++) { - mbr=old_mbrs.elementAt(i); - if(!new_mbrs.contains(mbr)) { - left.addElement(mbr); - } - } - - for(int i=0; i < notifs.size(); i++) { - n=(Notification)notifs.elementAt(i); - n.viewChange(joined, left); - } - } - - void notifyStateTransferStarted() { - state_transfer_running=true; - for(Iterator it=state_transfer_listeners.iterator(); it.hasNext();) { - StateTransferListener listener=(StateTransferListener)it.next(); - try { - listener.stateTransferStarted(); - } - catch(Throwable t) { - } - } - } - - void notifyStateTransferCompleted(boolean success) { - state_transfer_running=false; - for(Iterator it=state_transfer_listeners.iterator(); it.hasNext();) { - StateTransferListener listener=(StateTransferListener)it.next(); - try { - listener.stateTransferCompleted(success); - } - catch(Throwable t) { - } - } - } - - private static class Request implements Serializable { - static final int PUT=1; - static final int REMOVE=2; - static final int CLEAR=3; - static final int PUT_ALL=4; - - int req_type=0; - Object key=null; - Object val=null; - - Request(int req_type, Object key, Object val) { - this.req_type=req_type; - this.key=key; - this.val=val; - } - - public String toString() { - StringBuilder sb=new StringBuilder(); - sb.append(type2String(req_type)); - if(key != null) - sb.append("\nkey=" + key); - if(val != null) - sb.append("\nval=" + val); - return sb.toString(); - } - - String type2String(int t) { - switch(t) { - case PUT: - return "PUT"; - case REMOVE: - return "REMOVE"; - case CLEAR: - return "CLEAR"; - case PUT_ALL: - return "PUT_ALL"; - default : - return ""; - } - } - - } -} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/ReplicatedMap.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/ReplicatedMap.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/ReplicatedMap.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/ReplicatedMap.java 2011-10-18 11:22:35.000000000 +0000 @@ -6,7 +6,6 @@ /** * @author Bela Ban - * @version $Id: ReplicatedMap.java,v 1.2 2007/08/22 10:06:42 belaban Exp $ */ public interface ReplicatedMap extends ConcurrentMap { V _put(K key, V value); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/ReplicatedTree2.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/ReplicatedTree2.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/ReplicatedTree2.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/ReplicatedTree2.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,1239 +0,0 @@ -// $Id: ReplicatedTree2.java,v 1.1 2008/11/10 15:31:33 belaban Exp $ - -package org.jgroups.blocks; - - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jgroups.*; -import org.jgroups.annotations.Unsupported; -import org.jgroups.jmx.JmxConfigurator; -import org.jgroups.util.Queue; -import org.jgroups.util.QueueClosedException; -import org.jgroups.util.Util; - -import javax.management.MBeanServer; -import java.io.Serializable; -import java.util.*; - - - - -/** - * A tree-like structure that is replicated across several members. Updates will be multicast to all group - * members reliably and in the same order. - * @author Bela Ban Jan 17 2002 - * @author Alfonso Olias-Sanz - */ -@Unsupported -public class ReplicatedTree2 implements Runnable, MessageListener, MembershipListener { - public static final String SEPARATOR="/"; - final static int INDENT=4; - Node root=new NodeImpl(SEPARATOR, SEPARATOR, null, null); - final Vector listeners=new Vector(); - final Queue request_queue=new Queue(); - Thread request_handler=null; - JChannel channel=null; - PullPushAdapter adapter=null; - String groupname="ReplicatedTree-Group"; - final Vector members=new Vector(); - long state_fetch_timeout=10000; - boolean jmx=false; - - protected final Log log=LogFactory.getLog(this.getClass()); - - - /** Whether or not to use remote calls. If false, all methods will be invoked directly on this - instance rather than sending a message to all replicas and only then invoking the method. - Useful for testing */ - boolean remote_calls=true; - String props="UDP(mcast_addr=224.0.0.36;mcast_port=55566;ip_ttl=32;" + - "mcast_send_buf_size=150000;mcast_recv_buf_size=80000):" + - "PING(timeout=2000;num_initial_members=3):" + - "MERGE2(min_interval=5000;max_interval=10000):" + - "FD_SOCK:" + - "VERIFY_SUSPECT(timeout=1500):" + - "pbcast.STABLE(desired_avg_gossip=20000):" + - "pbcast.NAKACK(gc_lag=50;retransmit_timeout=600,1200,2400,4800):" + - "UNICAST(timeout=5000):" + - "FRAG(frag_size=16000;down_thread=false;up_thread=false):" + - "pbcast.GMS(join_timeout=5000;" + - "shun=false;print_local_addr=true):" + - "pbcast.STATE_TRANSFER"; - // "PERF(details=true)"; - - /** Determines when the updates have to be sent across the network, avoids sending unnecessary - * messages when there are no member in the group */ - private boolean send_message = false; - - - - public interface ReplicatedTreeListener { - void nodeAdded(String fqn); - - void nodeRemoved(String fqn); - - void nodeModified(String fqn); - - void viewChange(View new_view); // might be MergeView after merging - } - - - /** - * Creates a channel with the given properties. Connects to the channel, then creates a PullPushAdapter - * and starts it - */ - public ReplicatedTree2(String groupname, String props, long state_fetch_timeout) throws Exception { - if(groupname != null) - this.groupname=groupname; - if(props != null) - this.props=props; - this.state_fetch_timeout=state_fetch_timeout; - channel=new JChannel(this.props); - channel.connect(this.groupname); - start(); - } - - public ReplicatedTree2(String groupname, String props, long state_fetch_timeout, boolean jmx) throws Exception { - if(groupname != null) - this.groupname=groupname; - if(props != null) - this.props=props; - this.jmx=jmx; - this.state_fetch_timeout=state_fetch_timeout; - channel=new JChannel(this.props); - channel.connect(this.groupname); - if(jmx) { - MBeanServer server=Util.getMBeanServer(); - if(server == null) - throw new Exception("No MBeanServers found; need to run with an MBeanServer present, or inside JDK 5"); - JmxConfigurator.registerChannel(channel, server, "jgroups", channel.getClusterName() , true); - } - start(); - } - - public ReplicatedTree2() { - } - - - /** - * Expects an already connected channel. Creates a PullPushAdapter and starts it - */ - public ReplicatedTree2(JChannel channel) throws Exception { - this.channel=channel; - start(); - } - - - public void setRemoteCalls(boolean flag) { - remote_calls=flag; - } - - public void setRootNode(Node n) { - root=n; - } - - public Address getLocalAddress() { - return channel != null? channel.getLocalAddress() : null; - } - - public Vector getMembers() { - return members; - } - - - /** - * Fetch the group state from the current coordinator. If successful, this will trigger setState(). - */ - public void fetchState(long timeout) throws ChannelClosedException, ChannelNotConnectedException { - boolean rc=channel.getState(null, timeout); - if(log.isInfoEnabled()) { - if(rc) - log.info("state was retrieved successfully"); - else - log.info("state could not be retrieved (first member)"); - } - } - - - public void addReplicatedTreeListener(ReplicatedTreeListener listener) { - if(!listeners.contains(listener)) - listeners.addElement(listener); - } - - - public void removeReplicatedTreeListener(ReplicatedTreeListener listener) { - listeners.removeElement(listener); - } - - - public final void start() throws Exception { - if(request_handler == null) { - request_handler=new Thread(this, "ReplicatedTree2.RequestHandler thread"); - request_handler.setDaemon(true); - request_handler.start(); - } - adapter=new PullPushAdapter(channel, this, this); - adapter.setListener(this); - boolean rc=channel.getState(null, state_fetch_timeout); - - if(log.isInfoEnabled()) { - if(rc) - log.info("state was retrieved successfully"); - else - log.info("state could not be retrieved (first member)"); - } - } - - - public void stop() { - if(request_handler != null && request_handler.isAlive()) { - request_queue.close(true); - request_handler=null; - } - - request_handler=null; - if(channel != null) { - channel.close(); - } - if(adapter != null) { - adapter.stop(); - adapter=null; - } - channel=null; - } - - - /** - * Adds a new node to the tree and sets its data. If the node doesn not yet exist, it will be created. - * Also, parent nodes will be created if not existent. If the node already has data, then the new data - * will override the old one. If the node already existed, a nodeModified() notification will be generated. - * Otherwise a nodeCreated() motification will be emitted. - * @param fqn The fully qualified name of the new node - * @param data The new data. May be null if no data should be set in the node. - */ - public void put(String fqn, HashMap data) { - if(!remote_calls) { - _put(fqn, data); - return; - } - - //Changes done by - //if true, propagate action to the group - if(send_message == true) { - if(channel == null) { - if(log.isErrorEnabled()) log.error("channel is null, cannot broadcast PUT request"); - return; - } - try { - channel.send( - new Message( - null, - null, - new Request(Request.PUT, fqn, data))); - } - catch(Exception ex) { - if(log.isErrorEnabled()) log.error("failure bcasting PUT request: " + ex); - } - } - else { - _put(fqn, data); - } - } - - - /** - * Adds a key and value to a given node. If the node doesn't exist, it will be created. If the node - * already existed, a nodeModified() notification will be generated. Otherwise a - * nodeCreated() motification will be emitted. - * @param fqn The fully qualified name of the node - * @param key The key - * @param value The value - */ - public void put(String fqn, String key, Object value) { - if(!remote_calls) { - _put(fqn, key, value); - return; - } - - //Changes done by - //if true, propagate action to the group - if(send_message == true) { - - if(channel == null) { - if(log.isErrorEnabled()) log.error("channel is null, cannot broadcast PUT request"); - return; - } - try { - channel.send( - new Message( - null, - null, - new Request(Request.PUT, fqn, key, value))); - } - catch(Exception ex) { - if(log.isErrorEnabled()) log.error("failure bcasting PUT request: " + ex); - } - } - else { - _put(fqn, key, value); - } - } - - - /** - * Removes the node from the tree. - * @param fqn The fully qualified name of the node. - */ - public void remove(String fqn) { - if(!remote_calls) { - _remove(fqn); - return; - } - //Changes done by - //if true, propagate action to the group - if(send_message == true) { - if(channel == null) { - if(log.isErrorEnabled()) log.error("channel is null, cannot broadcast REMOVE request"); - return; - } - try { - channel.send( - new Message(null, null, new Request(Request.REMOVE, fqn))); - } - catch(Exception ex) { - if(log.isErrorEnabled()) log.error("failure bcasting REMOVE request: " + ex); - } - } - else { - _remove(fqn); - } - } - - - /** - * Removes key from the node's hashmap - * @param fqn The fullly qualified name of the node - * @param key The key to be removed - */ - public void remove(String fqn, String key) { - if(!remote_calls) { - _remove(fqn, key); - return; - } - //Changes done by - //if true, propagate action to the group - if(send_message == true) { - if(channel == null) { - if(log.isErrorEnabled()) log.error("channel is null, cannot broadcast REMOVE request"); - return; - } - try { - channel.send( - new Message( - null, - null, - new Request(Request.REMOVE, fqn, key))); - } - catch(Exception ex) { - if(log.isErrorEnabled()) log.error("failure bcasting REMOVE request: " + ex); - } - } - else { - _remove(fqn, key); - } - } - - - /** - * Checks whether a given node exists in the tree - * @param fqn The fully qualified name of the node - * @return boolean Whether or not the node exists - */ - public boolean exists(String fqn) { - if(fqn == null) return false; - return findNode(fqn) != null; - } - - - /** - * Gets the keys of the data map. Returns all keys as Strings. Returns null if node - * does not exist. - * @param fqn The fully qualified name of the node - * @return Set A set of keys (as Strings) - */ - public Set getKeys(String fqn) { - Node n=findNode(fqn); - Map data; - - if(n == null) return null; - data=n.getData(); - if(data == null) return null; - return data.keySet(); - } - - - /** - * Finds a node given its name and returns the value associated with a given key in its data - * map. Returns null if the node was not found in the tree or the key was not found in the hashmap. - * @param fqn The fully qualified name of the node. - * @param key The key. - */ - public Object get(String fqn, String key) { - Node n=findNode(fqn); - - if(n == null) return null; - return n.getData(key); - } - - - /** - * Returns the data hashmap for a given node. This method can only be used by callers that are inside - * the same package. The reason is that callers must not modify the return value, as these modifications - * would not be replicated, thus rendering the replicas inconsistent. - * @param fqn The fully qualified name of the node - * @return HashMap The data hashmap for the given node - */ - Map get(String fqn) { - Node n=findNode(fqn); - - if(n == null) return null; - return n.getData(); - } - - - /** - * Prints a representation of the node defined by fqn. Output includes name, fqn and - * data. - */ - public String print(String fqn) { - Node n=findNode(fqn); - if(n == null) return null; - return n.toString(); - } - - - /** - * Returns all children of a given node - * @param fqn The fully qualified name of the node - * @return Set A list of child names (as Strings) - */ - public Set getChildrenNames(String fqn) { - Node n=findNode(fqn); - Map m; - - if(n == null) return null; - m=n.getChildren(); - if(m != null) - return m.keySet(); - else - return null; - } - - -// public String toString() { -// StringBuilder sb=new StringBuilder(); -// -// return sb.toString(); -// } - - /** - * Returns the name of the group that the DistributedTree is connected to - * @return String - */ - public String getGroupName() {return groupname;} - - /** - * Returns the Channel the DistributedTree is connected to - * @return Channel - */ - public Channel getChannel() {return channel;} - - /** - * Returns the number of current members joined to the group - * @return int - */ - public int getGroupMembersNumber() {return members.size();} - - - - - /* --------------------- Callbacks -------------------------- */ - - - public void _put(String fqn, HashMap data) { - Node n; - StringHolder child_name=new StringHolder(); - boolean child_exists=false; - - if(fqn == null) return; - n=findParentNode(fqn, child_name, true); // create all nodes if they don't exist - if(child_name.getValue() != null) { - child_exists=n.childExists(child_name.getValue()); - n.createChild(child_name.getValue(), fqn, n, data); - } - else { - child_exists=true; - n.setData(data); - } - if(child_exists) - notifyNodeModified(fqn); - else - notifyNodeAdded(fqn); - } - - - public void _put(String fqn, String key, Object value) { - Node n; - boolean child_exists=false; - - if(fqn == null || key == null || value == null) return; - List elements=Util.split(fqn, '/'); - - String child_name=elements.get(elements.size() -1); - - n=findParentNode(elements, true); - if(child_name != null) { - child_exists=n.childExists(child_name); - n.createChild(child_name, fqn, n, key, value); - } - else { - child_exists=true; - n.setData(key, value); - } - if(child_exists) - notifyNodeModified(fqn); - else - notifyNodeAdded(fqn); - } - - - public void _remove(String fqn) { - Node n; - StringHolder child_name=new StringHolder(); - - if(fqn == null) return; - if(fqn.equals(SEPARATOR)) { - root.removeAll(); - notifyNodeRemoved(fqn); - return; - } - n=findParentNode(fqn, child_name, false); - if(n == null) return; - n.removeChild(child_name.getValue(), fqn); - notifyNodeRemoved(fqn); - } - - - public void _remove(String fqn, String key) { - Node n; - - if(fqn == null || key == null) return; - n=findNode(fqn); - if(n != null) - n.removeData(key); - } - - - public void _removeData(String fqn) { - Node n; - - if(fqn == null) return; - n=findNode(fqn); - if(n != null) - n.removeData(); - } - - - /* ----------------- End of Callbacks ---------------------- */ - - - - - - - /*-------------------- MessageListener ----------------------*/ - - /** Callback. Process the contents of the message; typically an _add() or _set() request */ - public void receive(Message msg) { - Request req=null; - - if(msg == null || msg.getLength() == 0) - return; - try { - req=(Request)msg.getObject(); - request_queue.add(req); - } - catch(QueueClosedException queue_closed_ex) { - if(log.isErrorEnabled()) log.error("request queue is null"); - } - catch(Exception ex) { - if(log.isErrorEnabled()) log.error("failed unmarshalling request: " + ex); - } - } - - /** Return a copy of the current cache (tree) */ - public byte[] getState() { - try { - return Util.objectToByteBuffer(root.clone()); - } - catch(Throwable ex) { - if(log.isErrorEnabled()) log.error("exception returning cache: " + ex); - return null; - } - } - - /** Set the cache (tree) to this value */ - public void setState(byte[] new_state) { - Node new_root=null; - Object obj; - - if(new_state == null) { - if(log.isInfoEnabled()) log.info("new cache is null"); - return; - } - try { - obj=Util.objectFromByteBuffer(new_state); - new_root=(Node)((Node)obj).clone(); - root=new_root; - notifyAllNodesCreated(root); - } - catch(Throwable ex) { - if(log.isErrorEnabled()) log.error("could not set cache: " + ex); - } - } - - /*-------------------- End of MessageListener ----------------------*/ - - - - - - /*----------------------- MembershipListener ------------------------*/ - - public void viewAccepted(View new_view) { - Vector new_mbrs=new_view.getMembers(); - - // todo: if MergeView, fetch and reconcile state from coordinator - // actually maybe this is best left up to the application ? we just notify them and let - // the appl handle it ? - - if(new_mbrs != null) { - notifyViewChange(new_view); - members.removeAllElements(); - for(int i=0; i < new_mbrs.size(); i++) - members.addElement(new_mbrs.elementAt(i)); - } - //if size is bigger than one, there are more peers in the group - //otherwise there is only one server. - send_message=members.size() > 1; - } - - - /** Called when a member is suspected */ - public void suspect(Address suspected_mbr) { - ; - } - - - /** Block sending and receiving of messages until viewAccepted() is called */ - public void block() { - } - - /*------------------- End of MembershipListener ----------------------*/ - - - - /** Request handler thread */ - public void run() { - Request req; - String fqn=null; - - while(request_handler != null) { - try { - req=(Request)request_queue.remove(0); - fqn=req.fqn; - switch(req.type) { - case Request.PUT: - if(req.key != null && req.value != null) - _put(fqn, req.key, req.value); - else - _put(fqn, req.data); - break; - case Request.REMOVE: - if(req.key != null) - _remove(fqn, req.key); - else - _remove(fqn); - break; - default: - if(log.isErrorEnabled()) log.error("type " + req.type + " unknown"); - break; - } - } - catch(QueueClosedException queue_closed_ex) { - request_handler=null; - break; - } - catch(Throwable other_ex) { - if(log.isWarnEnabled()) log.warn("exception processing request: " + other_ex); - } - } - } - - - /** - * Find the node just above the one indicated by fqn. This is needed in many cases, - * e.g. to add a new node or remove an existing node. - * @param fqn The fully qualified name of the node. - * @param child_name Will be filled with the name of the child when this method returns. The child name - * is the last relative name of the fqn, e.g. in "/a/b/c" it would be "c". - * @param create_if_not_exists Create parent nodes along the way if they don't exist. Otherwise, this method - * will return when a node cannot be found. - */ - Node findParentNode(String fqn, StringHolder child_name, boolean create_if_not_exists) { - Node curr=root, node; - StringTokenizer tok; - String name; - StringBuilder sb=null; - - if(fqn == null || fqn.equals(SEPARATOR) || "".equals(fqn)) - return curr; - - sb=new StringBuilder(); - tok=new StringTokenizer(fqn, SEPARATOR); - while(tok.countTokens() > 1) { - name=tok.nextToken(); - sb.append(SEPARATOR).append(name); - node=curr.getChild(name); - if(node == null && create_if_not_exists) - node=curr.createChild(name, sb.toString(), null, null); - if(node == null) - return null; - else - curr=node; - } - - if(tok.countTokens() > 0 && child_name != null) - child_name.setValue(tok.nextToken()); - return curr; - } - - - - Node findParentNode(List fqn, boolean create_if_not_exists) { - if(fqn == null || fqn.isEmpty()) - return root; - - Node curr=root, node; - StringBuilder sb=new StringBuilder(); - for(int i=0; i < fqn.size() -1; i++) { - String name=fqn.get(i); - sb.append(SEPARATOR).append(name); - node=curr.getChild(name); - if(node == null && create_if_not_exists) - node=curr.createChild(name, sb.toString(), null, null); - if(node == null) - return null; - else - curr=node; - } - - return curr; - } - - - /** - * Returns the node at fqn. This method should not be used by clients (therefore it is package-private): - * it is only used internally (for navigation). C++ 'friend' would come in handy here... - * @param fqn The fully qualified name of the node - * @return Node The node at fqn - */ - Node findNode(String fqn) { - StringHolder sh=new StringHolder(); - Node n=findParentNode(fqn, sh, false); - String child_name=sh.getValue(); - - if(fqn == null || fqn.equals(SEPARATOR) || "".equals(fqn)) - return root; - - if(n == null || child_name == null) - return null; - else - return n.getChild(child_name); - } - - - void notifyNodeAdded(String fqn) { - for(int i=0; i < listeners.size(); i++) - ((ReplicatedTreeListener)listeners.elementAt(i)).nodeAdded(fqn); - } - - void notifyNodeRemoved(String fqn) { - for(int i=0; i < listeners.size(); i++) - ((ReplicatedTreeListener)listeners.elementAt(i)).nodeRemoved(fqn); - } - - void notifyNodeModified(String fqn) { - for(int i=0; i < listeners.size(); i++) - ((ReplicatedTreeListener)listeners.elementAt(i)).nodeModified(fqn); - } - - void notifyViewChange(View v) { - for(int i=0; i < listeners.size(); i++) - ((ReplicatedTreeListener)listeners.elementAt(i)).viewChange(v); - } - - /** Generates NodeAdded notifications for all nodes of the tree. This is called whenever the tree is - initially retrieved (state transfer) */ - void notifyAllNodesCreated(Node curr) { - Node n; - Map children; - - if(curr == null) return; - notifyNodeAdded(curr.getFqn()); - if((children=curr.getChildren()) != null) { - for(Iterator it=children.values().iterator(); it.hasNext();) { - n=(Node)it.next(); - notifyAllNodesCreated(n); - } - } - } - - public static Map createMap() { - return new HashMap(); - } - - public static Map createMap(Map map) { - return new HashMap(map); - } - - - public static interface Node extends Serializable { - void setData(Map data); - - void setData(String key, Object value); - - Map getData(); - - String getFqn(); - - Object getData(String key); - - boolean childExists(String child_name); - - Node createChild(String child_name, String fqn, Node parent, HashMap data); - - Node createChild(String child_name, String fqn, Node parent, String key, Object value); - - Node getChild(String child_name); - - Map getChildren(); - - void setChildren(Map children); - - void removeData(String key); - - void removeData(); - - void removeChild(String child_name, String fqn); - - void removeAll(); - - void print(StringBuilder sb, int indent); - - void printIndent(StringBuilder sb, int indent); - - public Object clone() throws CloneNotSupportedException; - } - - public static class NodeImpl implements Node { - String name=null; // relative name (e.g. "Security") - String fqn=null; // fully qualified name (e.g. "/federations/fed1/servers/Security") - Map children=null; // keys: child name, value: Node - Map data=null; // data for current node - private static final long serialVersionUID = -3077676554440038890L; - // Address creator=null; // member that created this node (needed ?) - - - private NodeImpl(String child_name, String fqn, Node parent, Map data) { - name=child_name; - this.fqn=fqn; - if(data != null) this.data=createMap(data); - } - - private NodeImpl(String child_name, String fqn, Node parent, String key, Object value) { - name=child_name; - this.fqn=fqn; - if(data == null) data=createMap(); - data.put(key, value); - } - - public void setData(Map data) { - if(data == null) return; - if(this.data == null) - this.data=createMap(); - this.data.putAll(data); - } - - public void setData(String key, Object value) { - if(this.data == null) - this.data=createMap(); - this.data.put(key, value); - } - - public Map getData() { - return data; - } - - public String getFqn() { - return fqn; - } - - public Object getData(String key) { - return data != null? data.get(key) : null; - } - - - public boolean childExists(String child_name) { - return child_name != null && children != null && children.containsKey(child_name); - } - - - public Node createChild(String child_name, String fqn, Node parent, HashMap data) { - Node child=null; - - if(child_name == null) return null; - if(children == null) children=createMap(); - child=(Node)children.get(child_name); - if(child != null) - child.setData(data); - else { - child=new NodeImpl(child_name, fqn, parent, data); - children.put(child_name, child); - } - return child; - } - - public Node createChild(String child_name, String fqn, Node parent, String key, Object value) { - Node child=null; - - if(child_name == null) return null; - if(children == null) children=createMap(); - child=(Node)children.get(child_name); - if(child != null) - child.setData(key, value); - else { - child=new LeafNodeOneKey(key, value); - children.put(child_name, child); - } - return child; - } - - - public Node getChild(String child_name) { - return child_name == null? null : children == null? null : (Node)children.get(child_name); - } - - public Map getChildren() { - return children; - } - - public void setChildren(Map children) { - this.children=children; - } - - public void removeData(String key) { - if(data != null) - data.remove(key); - } - - public void removeData() { - if(data != null) - data.clear(); - } - - public void removeChild(String child_name, String fqn) { - if(child_name != null && children != null && children.containsKey(child_name)) { - children.remove(child_name); - } - } - - public void removeAll() { - if(children != null) - children.clear(); - } - - public void print(StringBuilder sb, int indent) { - printIndent(sb, indent); - sb.append(SEPARATOR).append(name); - if(children != null && !children.isEmpty()) { - Collection values=children.values(); - for(Iterator it=values.iterator(); it.hasNext();) { - sb.append('\n'); - ((Node)it.next()).print(sb, indent + INDENT); - } - } - } - - public void printIndent(StringBuilder sb, int indent) { - if(sb != null) { - for(int i=0; i < indent; i++) - sb.append(' '); - } - } - - - public String toString() { - StringBuilder sb=new StringBuilder(); - if(name != null) sb.append("\nname=" + name); - if(fqn != null) sb.append("\nfqn=" + fqn); - if(data != null) sb.append("\ndata=" + data); - return sb.toString(); - } - - - public Object clone() throws CloneNotSupportedException { - Node n=new NodeImpl(name, fqn, null, data); - if(children != null) n.setChildren(createMap(children)); - return n; - } - - } - - public static class LeafNodeOneKey implements Node { - private static final long serialVersionUID=-8848955385138463778L; - - private String key; - private Object val; - - - private LeafNodeOneKey(Map data) { - if(data == null) - return; - if(data.size() > 1) - throw new IllegalArgumentException("map can have only 1 key/value pair"); - Iterator it=data.entrySet().iterator(); - Map.Entry entry=(Map.Entry)it.next(); - key=(String)entry.getKey(); - val=entry.getValue(); - } - - private LeafNodeOneKey(String key, Object value) { - this.key=key; - this.val=value; - } - - - public void setData(Map data) { - if(data == null) - return; - if(data.size() > 1) - throw new IllegalArgumentException("map can have only 1 key/value pair"); - Iterator it=data.entrySet().iterator(); - Map.Entry entry=(Map.Entry)it.next(); - key=(String)entry.getKey(); - val=entry.getValue(); - } - - public void setData(String key, Object value) { - this.key=key; - this.val=value; - } - - public Map getData() { - Map retval=createMap(); - retval.put(key, val); - return retval; - } - - public String getFqn() { - return null; - } - - public Object getData(String key) { - return val; - } - - - public boolean childExists(String child_name) { - return false; - } - - - public Node createChild(String child_name, String fqn, Node parent, HashMap data) { - throw new UnsupportedOperationException(); - } - - public Node createChild(String child_name, String fqn, Node parent, String key, Object value) { - throw new UnsupportedOperationException(); - } - - - public Node getChild(String child_name) { - return null; - } - - public Map getChildren() { - return null; - } - - public void setChildren(Map children) { - throw new UnsupportedOperationException(); - } - - public void removeData(String key) { - if(key != null && this.key != null && this.key.equals(key)) { - key=null; - val=null; - } - } - - public void removeData() { - key=null; - val=null; - } - - public void removeChild(String child_name, String fqn) { - } - - public void removeAll() { - } - - public void print(StringBuilder sb, int indent) { - } - - public void printIndent(StringBuilder sb, int indent) { - if(sb != null) { - for(int i=0; i < indent; i++) - sb.append(' '); - } - } - - - public String toString() { - StringBuilder sb=new StringBuilder(); - sb.append(key).append("=").append(val); - return sb.toString(); - } - - - public Object clone() throws CloneNotSupportedException { - return new LeafNodeOneKey(key, val); - } - - } - - - private static class StringHolder { - String s=null; - - private StringHolder() { - } - - void setValue(String s) { - this.s=s; - } - - String getValue() { - return s; - } - } - - - /** - * Class used to multicast add(), remove() and set() methods to all members. - */ - private static class Request implements Serializable { - static final int PUT=1; - static final int REMOVE=2; - - int type=0; - String fqn=null; - String key=null; - Object value=null; - HashMap data=null; - private static final long serialVersionUID = 7772753222127676782L; - - private Request(int type, String fqn) { - this.type=type; - this.fqn=fqn; - } - - private Request(int type, String fqn, HashMap data) { - this(type, fqn); - this.data=data; - } - - private Request(int type, String fqn, String key) { - this(type, fqn); - this.key=key; - } - - private Request(int type, String fqn, String key, Object value) { - this(type, fqn); - this.key=key; - this.value=value; - } - - public String toString() { - StringBuilder sb=new StringBuilder(); - sb.append(type2String(type)).append(" ("); - if(fqn != null) sb.append(" fqn=" + fqn); - switch(type) { - case PUT: - if(data != null) sb.append(", data=" + data); - if(key != null) sb.append(", key=" + key); - if(value != null) sb.append(", value=" + value); - break; - case REMOVE: - if(key != null) sb.append(", key=" + key); - break; - default: - break; - } - sb.append(')'); - return sb.toString(); - } - - static String type2String(int t) { - switch(t) { - case PUT: - return "PUT"; - case REMOVE: - return "REMOVE"; - default: - return "UNKNOWN"; - } - } - - } - - - - - - static class MyListener implements ReplicatedTreeListener { - - public void nodeAdded(String fqn) { - System.out.println("** node added: " + fqn); - } - - public void nodeRemoved(String fqn) { - System.out.println("** node removed: " + fqn); - } - - public void nodeModified(String fqn) { - System.out.println("** node modified: " + fqn); - } - - public void viewChange(View new_view) { - System.out.println("** view change: " + new_view); - } - - } - - -} - - diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/ReplicatedTree.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/ReplicatedTree.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/ReplicatedTree.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/ReplicatedTree.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,15 +1,12 @@ -// $Id: ReplicatedTree.java,v 1.18 2008/04/08 14:41:22 belaban Exp $ package org.jgroups.blocks; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.*; import org.jgroups.annotations.Unsupported; import org.jgroups.jmx.JmxConfigurator; -import org.jgroups.util.Queue; -import org.jgroups.util.QueueClosedException; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.util.Util; import javax.management.MBeanServer; @@ -26,17 +23,14 @@ * @author Alfonso Olias-Sanz */ @Unsupported -public class ReplicatedTree implements Runnable, MessageListener, MembershipListener { +public class ReplicatedTree extends ReceiverAdapter { public static final String SEPARATOR="/"; final static int INDENT=4; Node root=new Node(SEPARATOR, SEPARATOR, null, null); - final Vector listeners=new Vector(); - final Queue request_queue=new Queue(); - Thread request_handler=null; + final Vector listeners=new Vector(); JChannel channel=null; - PullPushAdapter adapter=null; String groupname="ReplicatedTree-Group"; - final Vector members=new Vector(); + final Vector

    members=new Vector
    (); long state_fetch_timeout=10000; boolean jmx=false; @@ -47,20 +41,7 @@ instance rather than sending a message to all replicas and only then invoking the method. Useful for testing */ boolean remote_calls=true; - String props="UDP(mcast_addr=224.0.0.36;mcast_port=55566;ip_ttl=32;" + - "mcast_send_buf_size=150000;mcast_recv_buf_size=80000):" + - "PING(timeout=2000;num_initial_members=3):" + - "MERGE2(min_interval=5000;max_interval=10000):" + - "FD_SOCK:" + - "VERIFY_SUSPECT(timeout=1500):" + - "pbcast.STABLE(desired_avg_gossip=20000):" + - "pbcast.NAKACK(gc_lag=50;retransmit_timeout=600,1200,2400,4800):" + - "UNICAST(timeout=5000):" + - "FRAG(frag_size=16000;down_thread=false;up_thread=false):" + - "pbcast.GMS(join_timeout=5000;" + - "shun=false;print_local_addr=true):" + - "pbcast.STATE_TRANSFER"; - // "PERF(details=true)"; + String props="udp.xml"; /** Determines when the updates have to be sent across the network, avoids sending unnecessary * messages when there are no member in the group */ @@ -90,6 +71,7 @@ this.props=props; this.state_fetch_timeout=state_fetch_timeout; channel=new JChannel(this.props); + channel.setReceiver(this); channel.connect(this.groupname); start(); } @@ -102,7 +84,9 @@ this.jmx=jmx; this.state_fetch_timeout=state_fetch_timeout; channel=new JChannel(this.props); + channel.setReceiver(this); channel.connect(this.groupname); + if(jmx) { MBeanServer server=Util.getMBeanServer(); if(server == null) @@ -121,6 +105,7 @@ */ public ReplicatedTree(JChannel channel) throws Exception { this.channel=channel; + channel.setReceiver(this); start(); } @@ -134,10 +119,10 @@ } public Address getLocalAddress() { - return channel != null? channel.getLocalAddress() : null; + return channel != null? channel.getAddress() : null; } - public Vector getMembers() { + public Vector
    getMembers() { return members; } @@ -168,15 +153,7 @@ public final void start() throws Exception { - if(request_handler == null) { - request_handler=new Thread(this, "ReplicatedTree.RequestHandler thread"); - request_handler.setDaemon(true); - request_handler.start(); - } - adapter=new PullPushAdapter(channel, this, this); - adapter.setListener(this); boolean rc=channel.getState(null, state_fetch_timeout); - if(log.isInfoEnabled()) { if(rc) log.info("state was retrieved successfully"); @@ -187,20 +164,7 @@ public void stop() { - if(request_handler != null && request_handler.isAlive()) { - request_queue.close(true); - request_handler=null; - } - - request_handler=null; - if(channel != null) { - channel.close(); - } - if(adapter != null) { - adapter.stop(); - adapter=null; - } - channel=null; + Util.close(channel); } @@ -394,7 +358,7 @@ * @param fqn The fully qualified name of the node * @return HashMap The data hashmap for the given node */ - HashMap get(String fqn) { + Map get(String fqn) { Node n=findNode(fqn); if(n == null) return null; @@ -571,10 +535,25 @@ return; try { req=(Request)msg.getObject(); - request_queue.add(req); - } - catch(QueueClosedException queue_closed_ex) { - if(log.isErrorEnabled()) log.error("request queue is null"); + + String fqn=req.fqn; + switch(req.type) { + case Request.PUT: + if(req.key != null && req.value != null) + _put(fqn, req.key, req.value); + else + _put(fqn, req.data); + break; + case Request.REMOVE: + if(req.key != null) + _remove(fqn, req.key); + else + _remove(fqn); + break; + default: + if(log.isErrorEnabled()) log.error("type " + req.type + " unknown"); + break; + } } catch(Exception ex) { if(log.isErrorEnabled()) log.error("failed unmarshalling request: " + ex); @@ -621,11 +600,10 @@ /*----------------------- MembershipListener ------------------------*/ public void viewAccepted(View new_view) { - Vector new_mbrs=new_view.getMembers(); + Vector
    new_mbrs=new_view.getMembers(); // todo: if MergeView, fetch and reconcile state from coordinator - // actually maybe this is best left up to the application ? we just notify them and let - // the appl handle it ? + // actually maybe this is best left up to the application ? we just notify them and let the appl handle it ? if(new_mbrs != null) { notifyViewChange(new_view); @@ -638,58 +616,10 @@ send_message=members.size() > 1; } - - /** Called when a member is suspected */ - public void suspect(Address suspected_mbr) { - ; - } - - - /** Block sending and receiving of messages until viewAccepted() is called */ - public void block() { - } - /*------------------- End of MembershipListener ----------------------*/ - /** Request handler thread */ - public void run() { - Request req; - String fqn=null; - - while(request_handler != null) { - try { - req=(Request)request_queue.remove(0); - fqn=req.fqn; - switch(req.type) { - case Request.PUT: - if(req.key != null && req.value != null) - _put(fqn, req.key, req.value); - else - _put(fqn, req.data); - break; - case Request.REMOVE: - if(req.key != null) - _remove(fqn, req.key); - else - _remove(fqn); - break; - default: - if(log.isErrorEnabled()) log.error("type " + req.type + " unknown"); - break; - } - } - catch(QueueClosedException queue_closed_ex) { - request_handler=null; - break; - } - catch(Throwable other_ex) { - if(log.isWarnEnabled()) log.warn("exception processing request: " + other_ex); - } - } - } - /** * Find the node just above the one indicated by fqn. This is needed in many cases, @@ -752,22 +682,22 @@ void notifyNodeAdded(String fqn) { for(int i=0; i < listeners.size(); i++) - ((ReplicatedTreeListener)listeners.elementAt(i)).nodeAdded(fqn); + listeners.elementAt(i).nodeAdded(fqn); } void notifyNodeRemoved(String fqn) { for(int i=0; i < listeners.size(); i++) - ((ReplicatedTreeListener)listeners.elementAt(i)).nodeRemoved(fqn); + listeners.elementAt(i).nodeRemoved(fqn); } void notifyNodeModified(String fqn) { for(int i=0; i < listeners.size(); i++) - ((ReplicatedTreeListener)listeners.elementAt(i)).nodeModified(fqn); + listeners.elementAt(i).nodeModified(fqn); } void notifyViewChange(View v) { for(int i=0; i < listeners.size(); i++) - ((ReplicatedTreeListener)listeners.elementAt(i)).viewChange(v); + listeners.elementAt(i).viewChange(v); } /** Generates NodeAdded notifications for all nodes of the tree. This is called whenever the tree is @@ -791,41 +721,40 @@ String name=null; // relative name (e.g. "Security") String fqn=null; // fully qualified name (e.g. "/federations/fed1/servers/Security") Node parent=null; // parent node - TreeMap children=null; // keys: child name, value: Node - HashMap data=null; // data for current node + TreeMap children=null; // keys: child name, value: Node + Map data=null; // data for current node private static final long serialVersionUID = -3077676554440038890L; - // Address creator=null; // member that created this node (needed ?) - private Node(String child_name, String fqn, Node parent, HashMap data) { + private Node(String child_name, String fqn, Node parent, Map data) { name=child_name; this.fqn=fqn; this.parent=parent; - if(data != null) this.data=(HashMap)data.clone(); + if(data != null) this.data=(HashMap)((HashMap)data).clone(); } private Node(String child_name, String fqn, Node parent, String key, Object value) { name=child_name; this.fqn=fqn; this.parent=parent; - if(data == null) data=new HashMap(); + if(data == null) data=new HashMap(); data.put(key, value); } void setData(Map data) { if(data == null) return; if(this.data == null) - this.data=new HashMap(); + this.data=new HashMap(); this.data.putAll(data); } void setData(String key, Object value) { if(this.data == null) - this.data=new HashMap(); + this.data=new HashMap(); this.data.put(key, value); } - HashMap getData() { + Map getData() { return data; } @@ -835,17 +764,16 @@ boolean childExists(String child_name) { - if(child_name == null) return false; - return children != null && children.containsKey(child_name); + return child_name != null && children != null && children.containsKey(child_name); } - Node createChild(String child_name, String fqn, Node parent, HashMap data) { + Node createChild(String child_name, String fqn, Node parent, HashMap data) { Node child=null; if(child_name == null) return null; - if(children == null) children=new TreeMap(); - child=(Node)children.get(child_name); + if(children == null) children=new TreeMap(); + child=children.get(child_name); if(child != null) child.setData(data); else { @@ -859,7 +787,7 @@ Node child=null; if(child_name == null) return null; - if(children == null) children=new TreeMap(); + if(children == null) children=new TreeMap(); child=(Node)children.get(child_name); if(child != null) child.setData(key, value); @@ -875,7 +803,7 @@ return child_name == null? null : children == null? null : (Node)children.get(child_name); } - Map getChildren() { + Map getChildren() { return children; } @@ -903,7 +831,7 @@ void print(StringBuilder sb, int indent) { printIndent(sb, indent); sb.append(SEPARATOR).append(name); - if(children != null && children.size() > 0) { + if(children != null && !children.isEmpty()) { Collection values=children.values(); for(Iterator it=values.iterator(); it.hasNext();) { sb.append('\n'); @@ -912,7 +840,7 @@ } } - void printIndent(StringBuilder sb, int indent) { + static void printIndent(StringBuilder sb, int indent) { if(sb != null) { for(int i=0; i < indent; i++) sb.append(' '); @@ -1041,7 +969,7 @@ "UNICAST(timeout=5000):" + "FRAG(frag_size=16000;down_thread=false;up_thread=false):" + "pbcast.GMS(join_timeout=5000;" + - "shun=false;print_local_addr=true):" + + "print_local_addr=true):" + "pbcast.STATE_TRANSFER"; // "PERF(details=true)"; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/RequestCorrelator.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/RequestCorrelator.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/RequestCorrelator.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/RequestCorrelator.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,22 +1,19 @@ -// $Id: RequestCorrelator.java,v 1.47 2008/11/17 13:38:48 belaban Exp $ package org.jgroups.blocks; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.*; +import org.jgroups.conf.ClassConfigurator; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.protocols.TP; import org.jgroups.stack.Protocol; import org.jgroups.util.Buffer; -import org.jgroups.util.Streamable; import org.jgroups.util.Util; -import java.io.*; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.HashMap; -import java.util.concurrent.ConcurrentHashMap; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.*; import java.util.concurrent.ConcurrentMap; @@ -42,8 +39,10 @@ /** The protocol layer to use to pass up/down messages. Can be either a Protocol or a Transport */ protected Object transport=null; - /** The table of pending requests (keys=Long (request IDs), values=RequestEntry) */ - protected final ConcurrentMap requests=new ConcurrentHashMap(); + /** + * The table of pending requests (keys=Long (request IDs), values=RequestEntry) + */ + protected final ConcurrentMap requests=Util.createConcurrentMap(); /** The handler for the incoming requests. It is called from inside the dispatcher thread */ @@ -53,7 +52,7 @@ protected RpcDispatcher.Marshaller2 marshaller=null; /** makes the instance unique (together with IDs) */ - protected String name=null; + protected short id=ClassConfigurator.getProtocolId(this.getClass()); /** The address of this group member */ protected Address local_addr=null; @@ -80,22 +79,36 @@ * @param handler Request handler. Method handle(Message) * will be called when a request is received. */ + @Deprecated public RequestCorrelator(String name, Object transport, RequestHandler handler) { - this.name = name; this.transport = transport; request_handler = handler; start(); } - + @Deprecated public RequestCorrelator(String name, Object transport, RequestHandler handler, Address local_addr) { - this.name = name; this.transport = transport; this.local_addr=local_addr; request_handler = handler; start(); } + public RequestCorrelator(short id, Object transport, RequestHandler handler, Address local_addr) { + this.id = id; + this.transport = transport; + this.local_addr = local_addr; + request_handler = handler; + start(); + } + + public RequestCorrelator(Object transport, RequestHandler handler, Address local_addr) { + this.transport = transport; + this.local_addr = local_addr; + request_handler = handler; + start(); + } + /** * Constructor. Uses transport to send messages. If handler @@ -117,44 +130,43 @@ * order to solve deadlocks. Slows down processing a little bit when * enabled due to runtime checks involved. */ + @Deprecated public RequestCorrelator(String name, Object transport, RequestHandler handler, boolean deadlock_detection) { - this.name = name; this.transport = transport; request_handler = handler; start(); } - + @Deprecated public RequestCorrelator(String name, Object transport, RequestHandler handler, boolean deadlock_detection, boolean concurrent_processing) { - this.name = name; this.transport = transport; request_handler = handler; start(); } + @Deprecated public RequestCorrelator(String name, Object transport, RequestHandler handler, boolean deadlock_detection, Address local_addr) { - this.name = name; this.transport = transport; this.local_addr = local_addr; request_handler = handler; start(); } + @Deprecated public RequestCorrelator(String name, Object transport, RequestHandler handler, boolean deadlock_detection, Address local_addr, boolean concurrent_processing) { - this.name = name; this.transport = transport; this.local_addr = local_addr; request_handler = handler; start(); } + @Deprecated public RequestCorrelator(String name, Object transport, RequestHandler handler, Address local_addr, boolean concurrent_processing) { - this.name = name; this.transport = transport; this.local_addr = local_addr; request_handler = handler; @@ -188,6 +200,7 @@ /** * Helper method for {@link #sendRequest(long,List,Message,RspCollector)}. */ + @Deprecated public void sendRequest(long id, Message msg, RspCollector coll) throws Exception { sendRequest(id, null, msg, coll); } @@ -207,54 +220,46 @@ } public void sendRequest(long id, List
    dest_mbrs, Message msg, RspCollector coll) throws Exception { - sendRequest(id, dest_mbrs, msg, coll, false); + sendRequest(id, dest_mbrs, msg, coll, new RequestOptions().setAnycasting(false)); } /** - * Send a request to a group. If no response collector is given, no - * responses are expected (making the call asynchronous). + * Sends a request to a group. If no response collector is given, no responses are expected (making the call asynchronous) * - * @param id The request ID. Must be unique for this JVM (e.g. current - * time in millisecs) + * @param id The request ID. Must be unique for this JVM (e.g. current time in millisecs) * @param dest_mbrs The list of members who should receive the call. Usually a group RPC * is sent via multicast, but a receiver drops the request if its own address * is not in this list. Will not be used if it is null. * @param msg The request to be sent. The body of the message carries * the request data * - * @param coll A response collector (usually the object that invokes - * this method). Its methods receiveResponse() and - * suspect() will be invoked when a message has been received + * @param coll A response collector (usually the object that invokes this method). Its methods + * receiveResponse() and suspect() will be invoked when a message has been received * or a member is suspected, respectively. */ - public void sendRequest(long id, List
    dest_mbrs, Message msg, RspCollector coll, boolean use_anycasting) throws Exception { - Header hdr; - + public void sendRequest(long id, Collection
    dest_mbrs, Message msg, RspCollector coll, RequestOptions options) throws Exception { if(transport == null) { if(log.isWarnEnabled()) log.warn("transport is not available !"); return; } - // i. Create the request correlator header and add it to the - // msg - // ii. If a reply is expected (sync call / 'coll != null'), add a - // coresponding entry in the pending requests table + // i. Create the request correlator header and add it to the msg + // ii. If a reply is expected (coll != null), add a coresponding entry in the pending requests table // iii. If deadlock detection is enabled, set/update the call stack - // iv. Pass the msg down to the protocol layer below - hdr=new Header(Header.REQ, id, (coll != null), name); - hdr.dest_mbrs=dest_mbrs; + // iv. Pass the msg down to the protocol layer below + Header hdr=options.hasExclusionList()? + new MultiDestinationHeader(Header.REQ, id, (coll != null), this.id, options.getExclusionList()) + : new Header(Header.REQ, id, (coll != null), this.id); - if (coll != null) { + msg.putHeader(this.id, hdr); + + if(coll != null) addEntry(hdr.id, coll); - } - msg.putHeader(name, hdr); if(transport instanceof Protocol) { - if(use_anycasting) { - Message copy; - for(Iterator it=dest_mbrs.iterator(); it.hasNext();) { - Address mbr=(Address)it.next(); - copy=msg.copy(true); + if(options.getAnycasting()) { + for(Address mbr: dest_mbrs) { + Message copy=msg.copy(true); copy.setDest(mbr); ((Protocol)transport).down(new Event(Event.MSG, copy)); } @@ -264,11 +269,9 @@ } } else if(transport instanceof Transport) { - if(use_anycasting) { - Message copy; - for(Iterator it=dest_mbrs.iterator(); it.hasNext();) { - Address mbr=(Address)it.next(); - copy=msg.copy(true); + if(options.getAnycasting()) { + for(Address mbr: dest_mbrs) { + Message copy=msg.copy(true); copy.setDest(mbr); ((Transport)transport).send(copy); } @@ -281,13 +284,47 @@ throw new IllegalStateException("transport has to be either a Transport or a Protocol, however it is a " + transport.getClass()); } + /** + * Sends a request to a single destination + * @param id + * @param target + * @param msg + * @param coll + * @throws Exception + */ + public void sendUnicastRequest(long id, Address target, Message msg, RspCollector coll) throws Exception { + if(transport == null) { + if(log.isWarnEnabled()) log.warn("transport is not available !"); + return; + } + + // i. Create the request correlator header and add it to the msg + // ii. If a reply is expected (coll != null), add a coresponding entry in the pending requests table + // iii. If deadlock detection is enabled, set/update the call stack + // iv. Pass the msg down to the protocol layer below + Header hdr=new Header(Header.REQ, id, (coll != null), this.id); + msg.putHeader(this.id, hdr); + + if(coll != null) + addEntry(hdr.id, coll); + + if(transport instanceof Protocol) { + ((Protocol)transport).down(new Event(Event.MSG, msg)); + } + else if(transport instanceof Transport) { + ((Transport)transport).send(msg); + } + else + throw new IllegalStateException("transport has to be either a Transport or a Protocol, however it is a " + + transport.getClass()); + } + /** - * Used to signal that a certain request may be garbage collected as - * all responses have been received. + * Used to signal that a certain request may be garbage collected as all responses have been received. */ public void done(long id) { removeEntry(id); @@ -404,31 +441,32 @@ // ii. Check whether the message was sent by a request correlator with // the same name (there may be multiple request correlators in the same // protocol stack...) - Header hdr=(Header)msg.getHeader(name); + Header hdr=(Header)msg.getHeader(this.id); if(hdr == null) return false; - if(hdr.corrName == null || !hdr.corrName.equals(name)) { + if(hdr.corrId != this.id) { if(log.isTraceEnabled()) { - log.trace(new StringBuilder("name of request correlator header (").append(hdr.corrName). - append(") is different from ours (").append(name).append("). Msg not accepted, passed up")); + log.trace(new StringBuilder("id of request correlator header (").append(hdr.corrId). + append(") is different from ours (").append(this.id).append("). Msg not accepted, passed up")); } return false; } - // If the header contains a destination list, and we are not part of it, then we discard the - // request (was addressed to other members) - java.util.List dests=hdr.dest_mbrs; - if(dests != null && local_addr != null && !dests.contains(local_addr)) { - if(log.isTraceEnabled()) { - log.trace(new StringBuilder("discarded request from ").append(msg.getSrc()). - append(" as we are not part of destination list (local_addr="). - append(local_addr).append(", hdr=").append(hdr).append(')')); + if(hdr instanceof MultiDestinationHeader) { + // If the header contains an exclusion list, and we are part of it, then we discard the + // request (was addressed to other members) + java.util.Collection exclusion_list=((MultiDestinationHeader)hdr).exclusion_list; + if(exclusion_list != null && local_addr != null && exclusion_list.contains(local_addr)) { + if(log.isTraceEnabled()) { + log.trace(new StringBuilder("discarded request from ").append(msg.getSrc()). + append(" as we are in the exclusion list (local_addr="). + append(local_addr).append(", hdr=").append(hdr).append(')')); + } + return true; // don't pass this message further up } - return true; // don't pass this message further up } - // [Header.REQ]: // i. If there is no request handler, discard // ii. Check whether priority: if synchronous and call stack contains @@ -451,11 +489,10 @@ break; case Header.RSP: - msg.getHeader(name); - RspCollector coll=requests.get(Long.valueOf(hdr.id)); + RspCollector coll=requests.get(hdr.id); if(coll != null) { Address sender=msg.getSrc(); - Object retval=null; + Object retval; byte[] buf=msg.getBuffer(); int offset=msg.getOffset(), length=msg.getLength(); try { @@ -471,7 +508,7 @@ break; default: - msg.getHeader(name); + msg.getHeader(this.id); if(log.isErrorEnabled()) log.error("header's type is neither REQ nor RSP !"); break; } @@ -505,12 +542,10 @@ * @param id the id of the RequestEntry to remove */ private void removeEntry(long id) { - Long id_obj = new Long(id); - // changed by bela Feb 28 2003 (bug fix for 690606) // changed back to use synchronization by bela June 27 2003 (bug fix for #761804), // we can do this because we now copy for iteration (viewChange() and suspect()) - requests.remove(id_obj); + requests.remove(id); } @@ -569,13 +604,22 @@ } rsp=req.makeReply(); + prepareResponse(rsp); rsp.setFlag(Message.OOB); + rsp.setFlag(Message.DONT_BUNDLE); + if(req.isFlagSet(Message.NO_FC)) + rsp.setFlag(Message.NO_FC); + if(req.isFlagSet(Message.NO_RELIABILITY)) + rsp.setFlag(Message.NO_RELIABILITY); + if(req.isFlagSet(Message.NO_TOTAL_ORDER)) + rsp.setFlag(Message.NO_TOTAL_ORDER); + if(rsp_buf instanceof Buffer) rsp.setBuffer((Buffer)rsp_buf); else if (rsp_buf instanceof byte[]) rsp.setBuffer((byte[])rsp_buf); - rsp_hdr=new Header(Header.RSP, hdr.id, false, name); - rsp.putHeader(name, rsp_hdr); + rsp_hdr=new Header(Header.RSP, hdr.id, false, this.id); + rsp.putHeader(this.id, rsp_hdr); if(log.isTraceEnabled()) log.trace(new StringBuilder("sending rsp for ").append(rsp_hdr.id).append(" to ").append(rsp.getDest())); @@ -593,6 +637,9 @@ } } + protected void prepareResponse(Message rsp) { + ; + } // ....................................................................... @@ -603,27 +650,22 @@ /** * The header for RequestCorrelator messages */ - public static final class Header extends org.jgroups.Header implements Streamable { + public static class Header extends org.jgroups.Header { public static final byte REQ = 0; public static final byte RSP = 1; /** Type of header: request or reply */ - public byte type=REQ; + public byte type; /** - * The id of this request to distinguish among other requests from - * the same RequestCorrelator */ - public long id=0; + * The id of this request to distinguish among other requests from the same RequestCorrelator */ + public long id; /** msg is synchronous if true */ - public boolean rsp_expected=true; + public boolean rsp_expected; - /** The unique name of the associated RequestCorrelator */ - public String corrName=null; + /** The unique ID of the associated RequestCorrelator */ + public short corrId; - /** Contains a list of members who should receive the request (others will drop). Ignored if null */ - public java.util.List
    dest_mbrs=null; - - private static final long serialVersionUID=7550174166881969250L; /** @@ -636,95 +678,88 @@ * @param id id of this header relative to ids of other requests * originating from the same correlator * @param rsp_expected whether it's a sync or async request - * @param name the name of the RequestCorrelator from which + * @param corr_id The ID of the RequestCorrelator from which */ - public Header(byte type, long id, boolean rsp_expected, String name) { + public Header(byte type, long id, boolean rsp_expected, short corr_id) { this.type = type; this.id = id; this.rsp_expected = rsp_expected; - this.corrName = name; + this.corrId = corr_id; } /** */ public String toString() { StringBuilder ret=new StringBuilder(); - ret.append("[Header: name=" + corrName + ", type="); + ret.append("id=" + corrId + ", type="); ret.append(type == REQ ? "REQ" : type == RSP ? "RSP" : ""); ret.append(", id=" + id); - ret.append(", rsp_expected=" + rsp_expected + ']'); - if(dest_mbrs != null) - ret.append(", dest_mbrs=").append(dest_mbrs); + ret.append(", rsp_expected=" + rsp_expected); return ret.toString(); } - public void writeExternal(ObjectOutput out) throws IOException { + public void writeTo(DataOutputStream out) throws IOException { out.writeByte(type); out.writeLong(id); out.writeBoolean(rsp_expected); - if(corrName != null) { - out.writeBoolean(true); - out.writeUTF(corrName); - } - else { - out.writeBoolean(false); - } - out.writeObject(dest_mbrs); + out.writeShort(corrId); } + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + type=in.readByte(); + id=in.readLong(); + rsp_expected=in.readBoolean(); + corrId=in.readShort(); + } - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - type = in.readByte(); - id = in.readLong(); - rsp_expected = in.readBoolean(); - if(in.readBoolean()) - corrName = in.readUTF(); - dest_mbrs=(java.util.List
    )in.readObject(); + public int size() { + return Global.BYTE_SIZE // type + + Global.LONG_SIZE // id + + Global.BYTE_SIZE // rsp_expected + + Global.SHORT_SIZE; // corrId } + } - public void writeTo(DataOutputStream out) throws IOException { - out.writeByte(type); - out.writeLong(id); - out.writeBoolean(rsp_expected); - if(corrName != null) { - out.writeBoolean(true); - out.writeUTF(corrName); - } - else { - out.writeBoolean(false); - } - Util.writeAddresses(dest_mbrs, out); + + public static final class MultiDestinationHeader extends Header { + /** Contains a list of members who should not receive the request (others will drop). Ignored if null */ + public java.util.Collection exclusion_list; + + public MultiDestinationHeader() { } - public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { - boolean present; - type=in.readByte(); - id=in.readLong(); - rsp_expected=in.readBoolean(); + public MultiDestinationHeader(byte type, long id, boolean rsp_expected, short corr_id, Collection
    exclusion_list) { + super(type, id, rsp_expected, corr_id); + this.exclusion_list=exclusion_list; + } - present=in.readBoolean(); - if(present) - corrName=in.readUTF(); - dest_mbrs=(List
    )Util.readAddresses(in, java.util.LinkedList.class); + public void writeTo(DataOutputStream out) throws IOException { + super.writeTo(out); + Util.writeAddresses(exclusion_list, out); + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + super.readFrom(in); + exclusion_list=Util.readAddresses(in, LinkedList.class); } public int size() { - int retval=Global.BYTE_SIZE // type - + Global.LONG_SIZE // id - + Global.BYTE_SIZE; // rsp_expected - - retval+=Global.BYTE_SIZE; // presence for corrName - if(corrName != null) - retval+=corrName.length() +2; // UTF + return (int)(super.size() + Util.size(exclusion_list)); + } - retval+=Util.size(dest_mbrs); - return retval; + public String toString() { + String str=super.toString(); + if(exclusion_list != null) + str=str+ ", exclusion_list=" + exclusion_list; + return str; } } + + private static class MyProbeHandler implements TP.ProbeHandler { private final ConcurrentMap requests; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/RequestHandler.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/RequestHandler.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/RequestHandler.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/RequestHandler.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: RequestHandler.java,v 1.1.1.1 2003/09/09 01:24:08 belaban Exp $ package org.jgroups.blocks; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/Request.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/Request.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/Request.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/Request.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,272 @@ +package org.jgroups.blocks; + + +import org.jgroups.Address; +import org.jgroups.Message; +import org.jgroups.Transport; +import org.jgroups.View; +import org.jgroups.annotations.GuardedBy; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.util.Command; +import org.jgroups.util.FutureListener; +import org.jgroups.util.NotifyingFuture; + +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + + +/** + * Abstract class for a unicast or multicast request + * + * @author Bela Ban + */ +public abstract class Request implements RspCollector, Command, NotifyingFuture { + /** return only first response */ + public static final int GET_FIRST=1; + + /** return all responses */ + public static final int GET_ALL=2; + + /** return majority (of all non-faulty members) */ + public static final int GET_MAJORITY=3; + + /** return majority (of all members, may block) */ + public static final int GET_ABS_MAJORITY=4; + + /** return n responses (may block) */ + @Deprecated public static final int GET_N=5; + + /** return no response (async call) */ + public static final int GET_NONE=6; + + + protected static final Log log=LogFactory.getLog(Request.class); + + /** To generate unique request IDs (see getRequestId()) */ + protected static final AtomicLong REQUEST_ID=new AtomicLong(1); + + protected final Lock lock=new ReentrantLock(); + + /** Is set as soon as the request has received all required responses */ + protected final Condition completed=lock.newCondition(); + + protected final Message request_msg; + protected final RequestCorrelator corr; // either use RequestCorrelator or ... + protected final Transport transport; // Transport (one of them has to be non-null) + + protected final RequestOptions options; + + protected volatile boolean done; + protected boolean block_for_results=true; + protected final long req_id; // request ID for this request + + protected volatile FutureListener listener; + + + + @Deprecated + public Request(Message request, RequestCorrelator corr, Transport transport, RspFilter filter, int mode, long timeout) { + this(request, corr, transport, new RequestOptions(mode, timeout, false, filter)); + } + + public Request(Message request, RequestCorrelator corr, Transport transport, RequestOptions options) { + this.request_msg=request; + this.corr=corr; + this.transport=transport; + this.options=options; + this.req_id=getRequestId(); + } + + + public void setResponseFilter(RspFilter filter) { + options.setRspFilter(filter); + } + + public boolean getBlockForResults() { + return block_for_results; + } + + public void setBlockForResults(boolean block_for_results) { + this.block_for_results=block_for_results; + } + + public NotifyingFuture setListener(FutureListener listener) { + this.listener=listener; + if(done) + listener.futureDone(this); + return this; + } + + public boolean execute() throws Exception { + if(corr == null && transport == null) { + if(log.isErrorEnabled()) log.error("both corr and transport are null, cannot send group request"); + return false; + } + + sendRequest(); + if(!block_for_results || options.getMode() == GET_NONE) + return true; + + lock.lock(); + try { + return responsesComplete(options.getTimeout()); + } + finally { + done=true; + lock.unlock(); + } + } + + protected abstract void sendRequest() throws Exception; + + public abstract void receiveResponse(Object response_value, Address sender); + + public abstract void viewChange(View new_view); + + public abstract void suspect(Address mbr); + + protected abstract boolean responsesComplete(); + + + public boolean getResponsesComplete() { + lock.lock(); + try { + return responsesComplete(); + } + finally { + lock.unlock(); + } + } + + + public boolean cancel(boolean mayInterruptIfRunning) { + lock.lock(); + try { + boolean retval=!done; + done=true; + if(corr != null) + corr.done(req_id); + completed.signalAll(); + return retval; + } + finally { + lock.unlock(); + } + } + + public boolean isCancelled() { + lock.lock(); + try { + return done; + } + finally { + lock.unlock(); + } + } + + + public boolean isDone() { + return done; + } + + + public String toString() { + StringBuilder ret=new StringBuilder(128); + ret.append(super.toString()); + ret.append("req_id=").append(req_id).append(", mode=" + modeToString(options.getMode())); + return ret.toString(); + } + + + /* --------------------------------- Private Methods -------------------------------------*/ + + + protected void checkCompletion(Future future) { + if(listener != null && responsesComplete()) + listener.futureDone(future); + } + + /** Generates a new unique request ID */ + protected static long getRequestId() { + return REQUEST_ID.incrementAndGet(); + } + + /** This method runs with lock locked (called by execute()). */ + @GuardedBy("lock") + protected boolean responsesComplete(long timeout) throws InterruptedException { + if(timeout <= 0) { + while(!done) { /* Wait for responses: */ + if(responsesComplete()) { + if(corr != null) + corr.done(req_id); + return true; + } + completed.await(); + } + return responsesComplete(); + } + else { + long start_time=System.currentTimeMillis(); + long timeout_time=start_time + timeout; + while(timeout > 0 && !done) { /* Wait for responses: */ + if(responsesComplete()) { + if(corr != null) + corr.done(req_id); + return true; + } + timeout=timeout_time - System.currentTimeMillis(); + if(timeout > 0) { + completed.await(timeout, TimeUnit.MILLISECONDS); + } + } + if(corr != null) + corr.done(req_id); + return responsesComplete(); + } + } + + @GuardedBy("lock") + protected boolean waitForResults(long timeout) { + if(timeout <= 0) { + while(true) { /* Wait for responses: */ + if(responsesComplete()) + return true; + try {completed.await();} catch(Exception e) {} + } + } + else { + long start_time=System.currentTimeMillis(); + long timeout_time=start_time + timeout; + while(timeout > 0) { /* Wait for responses: */ + if(responsesComplete()) + return true; + timeout=timeout_time - System.currentTimeMillis(); + if(timeout > 0) { + try {completed.await(timeout, TimeUnit.MILLISECONDS);} catch(Exception e) {} + } + } + return false; + } + } + + + + public static String modeToString(int m) { + switch(m) { + case GET_FIRST: return "GET_FIRST"; + case GET_ALL: return "GET_ALL"; + case GET_MAJORITY: return "GET_MAJORITY"; + case GET_ABS_MAJORITY: return "GET_ABS_MAJORITY"; + case GET_N: return "GET_N"; + case GET_NONE: return "GET_NONE"; + default: return " (" + m + ")"; + } + } + + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/RequestOptions.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/RequestOptions.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/RequestOptions.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/RequestOptions.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,198 @@ +package org.jgroups.blocks; + +import org.jgroups.Message; +import org.jgroups.Address; +import org.jgroups.util.Util; + +import java.util.*; + +/** Class which captures a bunch of options relevant to remote method invocation or message sending + * @author Bela Ban + * @since 2.10 + */ +public class RequestOptions { + /** The mode of a request. Defined in GroupRequest e.g. GET_NONE, GET_ALL */ + private int mode=Request.GET_NONE; + + /** The max time (in ms) for a blocking call. 0 blocks until all responses have been received (if mode = GET_ALL) */ + private long timeout; // used when mode != GET_NONE + + /** Turns on anycasting; this results in multiple unicasts rather than a multicast for group calls */ + private boolean use_anycasting; + + /** Allows for filtering of responses */ + private RspFilter rsp_filter; + + /** The scope of a message, allows for concurrent delivery of messages from the same sender */ + private short scope; + + /** The flags set in the message in which a request is sent */ + private byte flags; // Message.OOB, Message.DONT_BUNDLE etc + + /** A list of members which should be excluded from a call */ + private Set
    exclusion_list; + + /** When options are sealed, subsequent modifications will throw an exception */ + protected boolean sealed=false; + + + @Deprecated public static final RequestOptions SYNC; + + @Deprecated public static final RequestOptions ASYNC; + + static { + SYNC=new RequestOptions(Request.GET_ALL, 5000).seal(); + ASYNC=new RequestOptions(Request.GET_NONE, 5000).seal(); + } + + + public RequestOptions() { + } + + public RequestOptions(int mode, long timeout, boolean use_anycasting, RspFilter rsp_filter, byte flags) { + this.mode=mode; + this.timeout=timeout; + this.use_anycasting=use_anycasting; + this.rsp_filter=rsp_filter; + this.flags=flags; + } + + public RequestOptions(int mode, long timeout, boolean use_anycasting, RspFilter rsp_filter) { + this(mode, timeout, use_anycasting, rsp_filter, (byte)0); + } + + public RequestOptions(int mode, long timeout) { + this(mode, timeout, false, null); + } + + public RequestOptions(RequestOptions opts) { + this.mode=opts.mode; + this.timeout=opts.timeout; + this.use_anycasting=opts.use_anycasting; + this.rsp_filter=opts.rsp_filter; + this.scope=opts.scope; + this.flags=opts.flags; + this.exclusion_list=opts.exclusion_list; + this.sealed=opts.sealed; + } + + + public static RequestOptions SYNC() {return new RequestOptions(Request.GET_ALL, 5000);} + public static RequestOptions ASYNC() {return new RequestOptions(Request.GET_NONE, 5000);} + + + public int getMode() { + return mode; + } + + public RequestOptions setMode(int mode) { + checkSealed(); + this.mode=mode; + return this; + } + + public long getTimeout() { + return timeout; + } + + public RequestOptions setTimeout(long timeout) { + checkSealed(); + this.timeout=timeout; + return this; + } + + public boolean getAnycasting() { + return use_anycasting; + } + + public RequestOptions setAnycasting(boolean use_anycasting) { + checkSealed(); + this.use_anycasting=use_anycasting; + return this; + } + + public short getScope() { + return scope; + } + + public RequestOptions setScope(short scope) { + checkSealed(); + this.scope=scope; + return this; + } + + public RspFilter getRspFilter() { + return rsp_filter; + } + + public RequestOptions setRspFilter(RspFilter rsp_filter) { + checkSealed(); + this.rsp_filter=rsp_filter; + return this; + } + + public byte getFlags() { + return flags; + } + + public RequestOptions setFlags(byte flags) { + checkSealed(); + this.flags=Util.setFlag(this.flags, flags); + return this; + } + + public RequestOptions clearFlags(byte flags) { + checkSealed(); + this.flags=Util.clearFlags(this.flags, flags); + return this; + } + + public boolean hasExclusionList() { + return exclusion_list != null && !exclusion_list.isEmpty(); + } + + public Collection
    getExclusionList() { + if(exclusion_list == null) + return exclusion_list; + else + return Collections.unmodifiableCollection(exclusion_list); + } + + public RequestOptions setExclusionList(Address ... mbrs) { + checkSealed(); + if(exclusion_list == null) + exclusion_list=new HashSet
    (); + else + exclusion_list.clear(); + exclusion_list.addAll(Arrays.asList(mbrs)); + return this; + } + + /** Seals options against subsequent modifications + * @deprecated Will get removed together with SYNC and ASYNC in 3.0*/ + @Deprecated + public RequestOptions seal() { + sealed=true; + return this; + } + + public String toString() { + StringBuilder sb=new StringBuilder(); + sb.append("mode=" + Request.modeToString(mode)); + sb.append(", timeout=" + timeout); + if(use_anycasting) + sb.append(", anycasting=true"); + sb.append(", flags=" + Message.flagsToString(flags)); + if(scope > 0) + sb.append(", scope=" + scope); + if(exclusion_list != null) + sb.append(", exclusion list: " + Util.print(exclusion_list)); + return sb.toString(); + } + + + protected void checkSealed() { + if(sealed) + throw new IllegalStateException("options are sealed, cannot modify them; use a new instance of RequestOptions"); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/RpcDispatcher.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/RpcDispatcher.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/RpcDispatcher.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/RpcDispatcher.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,22 +1,13 @@ -// $Id: RpcDispatcher.java,v 1.36 2008/10/10 14:53:30 belaban Exp $ package org.jgroups.blocks; import org.jgroups.*; -import org.jgroups.util.RspList; -import org.jgroups.util.Util; -import org.jgroups.util.Buffer; +import org.jgroups.util.*; import java.io.Serializable; import java.lang.reflect.Method; -import java.lang.IllegalArgumentException ; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Vector; - - +import java.util.*; /** @@ -37,7 +28,7 @@ /** Marshaller to marshal responses at the receiver(s) and unmarshal responses at the caller */ protected Marshaller2 rsp_marshaller=null; - protected final List additionalChannelListeners=new ArrayList(); + protected final List additionalChannelListeners=new ArrayList(); protected MethodLookup method_lookup=null; @@ -52,6 +43,7 @@ } + @Deprecated public RpcDispatcher(Channel channel, MessageListener l, MembershipListener l2, Object server_obj, boolean deadlock_detection) { super(channel, l, l2); @@ -59,6 +51,7 @@ this.server_obj=server_obj; } + @Deprecated public RpcDispatcher(Channel channel, MessageListener l, MembershipListener l2, Object server_obj, boolean deadlock_detection, boolean concurrent_processing) { super(channel, l, l2, false, concurrent_processing); @@ -67,7 +60,7 @@ } - + @Deprecated public RpcDispatcher(PullPushAdapter adapter, Serializable id, MessageListener l, MembershipListener l2, Object server_obj) { super(adapter, id, l, l2); @@ -144,7 +137,7 @@ } - public String getName() {return "RpcDispatcher";} + public static String getName() {return "RpcDispatcher";} public Marshaller getRequestMarshaller() {return req_marshaller;} @@ -190,78 +183,123 @@ } - public RspList castMessage(Vector dests, Message msg, int mode, long timeout) { - if(log.isErrorEnabled()) log.error("this method should not be used with " + - "RpcDispatcher, but MessageDispatcher. Returning null"); - return null; - } - - public Object sendMessage(Message msg, int mode, long timeout) throws TimeoutException, SuspectedException { - if(log.isErrorEnabled()) log.error("this method should not be used with " + - "RpcDispatcher, but MessageDispatcher. Returning null"); - return null; - } - - - public RspList callRemoteMethods(Vector dests, String method_name, Object[] args, + @Deprecated + public RspList callRemoteMethods(Vector
    dests, String method_name, Object[] args, Class[] types, int mode, long timeout) { return callRemoteMethods(dests, method_name, args, types, mode, timeout, false); } - - public RspList callRemoteMethods(Vector dests, String method_name, Object[] args, + @Deprecated + public RspList callRemoteMethods(Vector
    dests, String method_name, Object[] args, Class[] types, int mode, long timeout, boolean use_anycasting) { return callRemoteMethods(dests, method_name, args, types, mode, timeout, use_anycasting, null); } - public RspList callRemoteMethods(Vector dests, String method_name, Object[] args, + @Deprecated + public RspList callRemoteMethods(Vector
    dests, String method_name, Object[] args, Class[] types, int mode, long timeout, boolean use_anycasting, RspFilter filter) { MethodCall method_call=new MethodCall(method_name, args, types); - return callRemoteMethods(dests, method_call, mode, timeout, use_anycasting, false, filter); + return callRemoteMethods(dests, method_call, + new RequestOptions(mode, timeout, use_anycasting, filter, (byte)0)); } + public RspList callRemoteMethods(Collection
    dests, String method_name, Object[] args, + Class[] types, RequestOptions options) { + MethodCall method_call=new MethodCall(method_name, args, types); + return callRemoteMethods(dests, method_call, options); + } - public RspList callRemoteMethods(Vector dests, String method_name, Object[] args, + @Deprecated + public RspList callRemoteMethods(Vector
    dests, String method_name, Object[] args, String[] signature, int mode, long timeout) { return callRemoteMethods(dests, method_name, args, signature, mode, timeout, false); } - - public RspList callRemoteMethods(Vector dests, String method_name, Object[] args, + @Deprecated + public RspList callRemoteMethods(Vector
    dests, String method_name, Object[] args, String[] signature, int mode, long timeout, boolean use_anycasting) { MethodCall method_call=new MethodCall(method_name, args, signature); - return callRemoteMethods(dests, method_call, mode, timeout, use_anycasting); + return callRemoteMethods(dests, method_call, new RequestOptions(mode, timeout, use_anycasting, null, (byte)0)); } - - public RspList callRemoteMethods(Vector dests, MethodCall method_call, int mode, long timeout) { - return callRemoteMethods(dests, method_call, mode, timeout, false); + @Deprecated + public RspList callRemoteMethods(Vector
    dests, MethodCall method_call, int mode, long timeout) { + return callRemoteMethods(dests, method_call, new RequestOptions().setMode(mode).setTimeout(timeout)); } - public RspList callRemoteMethods(Vector dests, MethodCall method_call, int mode, long timeout, boolean use_anycasting) { - return callRemoteMethods(dests, method_call, mode, timeout, use_anycasting, false); + + /** + * Invokes a method in all members contained in dests (or all members if dests is null). + * @param dests A list of addresses. If null, the method will be invoked on all cluster members + * @param method_call The method (plus args) to be invoked + * @param options A collection of call options, e.g. sync versus async, timeout etc + * @return RspList A list of return values and flags (suspected, not received) per member + * @since 2.9 + */ + public RspList callRemoteMethods(Collection
    dests, MethodCall method_call, RequestOptions options) { + if(dests != null && dests.isEmpty()) { // don't send if dest list is empty + if(log.isTraceEnabled()) + log.trace(new StringBuilder("destination list of ").append(method_call.getName()). + append("() is empty: no need to send message")); + return RspList.EMPTY_RSP_LIST; + } + + if(log.isTraceEnabled()) + log.trace(new StringBuilder("dests=").append(dests).append(", method_call=").append(method_call). + append(", options=").append(options)); + + Object buf; + try { + buf=req_marshaller != null? req_marshaller.objectToBuffer(method_call) : Util.objectToByteBuffer(method_call); + } + catch(Exception e) { + // if(log.isErrorEnabled()) log.error("exception", e); + // we will change this in 3.0 to add the exception to the signature + // (see http://jira.jboss.com/jira/browse/JGRP-193). The reason for a RTE is that we cannot change the + // signature in 2.3, otherwise 2.3 would be *not* API compatible to prev releases + throw new RuntimeException("failure to marshal argument(s)", e); + } + + Message msg=new Message(); + if(buf instanceof Buffer) + msg.setBuffer((Buffer)buf); + else + msg.setBuffer((byte[])buf); + + msg.setFlag(options.getFlags()); + if(options.getScope() > 0) + msg.setScope(options.getScope()); + + RspList retval=super.castMessage(dests, msg, options); + if(log.isTraceEnabled()) log.trace("responses: " + retval); + return retval; } - public RspList callRemoteMethods(Vector dests, MethodCall method_call, int mode, long timeout, - boolean use_anycasting, boolean oob) { - return callRemoteMethods(dests, method_call, mode, timeout, use_anycasting, oob, null); + @Deprecated + public NotifyingFuture callRemoteMethodsWithFuture(Vector
    dests, MethodCall method_call, int mode, long timeout, + boolean use_anycasting, boolean oob, RspFilter filter) { + RequestOptions options=new RequestOptions(mode, timeout, use_anycasting, filter); + if(oob) options.setFlags(Message.OOB); + return callRemoteMethodsWithFuture(dests, method_call, options); } + @Deprecated + public NotifyingFuture callRemoteMethodsWithFuture(Vector
    dests, MethodCall method_call) { + return callRemoteMethodsWithFuture(dests, method_call, new RequestOptions()); + } - public RspList callRemoteMethods(Vector dests, MethodCall method_call, int mode, long timeout, - boolean use_anycasting, boolean oob, RspFilter filter) { - if(dests != null && dests.isEmpty()) { - // don't send if dest list is empty + public NotifyingFuture callRemoteMethodsWithFuture(Collection
    dests, MethodCall method_call, RequestOptions options) { + if(dests != null && dests.isEmpty()) { // don't send if dest list is empty if(log.isTraceEnabled()) log.trace(new StringBuilder("destination list of ").append(method_call.getName()). append("() is empty: no need to send message")); - return new RspList(); + return new NullFuture(RspList.EMPTY_RSP_LIST); } if(log.isTraceEnabled()) log.trace(new StringBuilder("dests=").append(dests).append(", method_call=").append(method_call). - append(", mode=").append(mode).append(", timeout=").append(timeout)); + append(", options=").append(options)); Object buf; try { @@ -280,14 +318,17 @@ msg.setBuffer((Buffer)buf); else msg.setBuffer((byte[])buf); - if(oob) - msg.setFlag(Message.OOB); - RspList retval=super.castMessage(dests, msg, mode, timeout, use_anycasting, filter); + msg.setFlag(options.getFlags()); + if(options.getScope() > 0) + msg.setScope(options.getScope()); + + NotifyingFuture retval=super.castMessageWithFuture(dests, msg, options); if(log.isTraceEnabled()) log.trace("responses: " + retval); return retval; } + @Deprecated public Object callRemoteMethod(Address dest, String method_name, Object[] args, Class[] types, int mode, long timeout) throws Throwable { MethodCall method_call=new MethodCall(method_name, args, types); @@ -295,38 +336,84 @@ } public Object callRemoteMethod(Address dest, String method_name, Object[] args, + Class[] types, RequestOptions options) throws Throwable { + MethodCall method_call=new MethodCall(method_name, args, types); + return callRemoteMethod(dest, method_call, options); + } + + @Deprecated + public Object callRemoteMethod(Address dest, String method_name, Object[] args, String[] signature, int mode, long timeout) throws Throwable { MethodCall method_call=new MethodCall(method_name, args, signature); return callRemoteMethod(dest, method_call, mode, timeout); } + @Deprecated public Object callRemoteMethod(Address dest, MethodCall method_call, int mode, long timeout) throws Throwable { return callRemoteMethod(dest, method_call, mode, timeout, false); } + @Deprecated public Object callRemoteMethod(Address dest, MethodCall method_call, int mode, long timeout, boolean oob) throws Throwable { - Object buf=null; - Message msg=null; - Object retval=null; + RequestOptions options=new RequestOptions(mode, timeout, false, null); + if(oob) options.setFlags(Message.OOB); + return callRemoteMethod(dest, method_call, options); + } + @Deprecated + public Object callRemoteMethod(Address dest, MethodCall call) throws Throwable { + return callRemoteMethod(dest, call, new RequestOptions()); + } + + public Object callRemoteMethod(Address dest, MethodCall call, RequestOptions options) throws Throwable { if(log.isTraceEnabled()) - log.trace("dest=" + dest + ", method_call=" + method_call + ", mode=" + mode + ", timeout=" + timeout); + log.trace("dest=" + dest + ", method_call=" + call + ", options=" + options); - buf=req_marshaller != null? req_marshaller.objectToBuffer(method_call) : Util.objectToByteBuffer(method_call); - msg=new Message(dest, null, null); + Object buf=req_marshaller != null? req_marshaller.objectToBuffer(call) : Util.objectToByteBuffer(call); + Message msg=new Message(dest, null, null); if(buf instanceof Buffer) msg.setBuffer((Buffer)buf); else msg.setBuffer((byte[])buf); - if(oob) - msg.setFlag(Message.OOB); - retval=super.sendMessage(msg, mode, timeout); + msg.setFlag(options.getFlags()); + if(options.getScope() > 0) + msg.setScope(options.getScope()); + + Object retval=super.sendMessage(msg, options); if(log.isTraceEnabled()) log.trace("retval: " + retval); if(retval instanceof Throwable) throw (Throwable)retval; return retval; } + @Deprecated + public NotifyingFuture callRemoteMethodWithFuture(Address dest, MethodCall method_call, int mode, long timeout, boolean oob) throws Throwable { + RequestOptions options=new RequestOptions(mode, timeout, false, null); + if(oob) options.setFlags(Message.OOB); + return callRemoteMethodWithFuture(dest, method_call, options); + } + + @Deprecated + public NotifyingFuture callRemoteMethodWithFuture(Address dest, MethodCall call) throws Throwable { + return callRemoteMethodWithFuture(dest, call, new RequestOptions()); + } + + public NotifyingFuture callRemoteMethodWithFuture(Address dest, MethodCall call, RequestOptions options) throws Throwable { + if(log.isTraceEnabled()) + log.trace("dest=" + dest + ", method_call=" + call + ", options=" + options); + + Object buf=req_marshaller != null? req_marshaller.objectToBuffer(call) : Util.objectToByteBuffer(call); + Message msg=new Message(dest, null, null); + if(buf instanceof Buffer) + msg.setBuffer((Buffer)buf); + else + msg.setBuffer((byte[])buf); + msg.setFlag(options.getFlags()); + if(options.getScope() > 0) + msg.setScope(options.getScope()); + return super.sendMessageWithFuture(msg, options); + } + protected void correlatorStarted() { if(corr != null) @@ -339,7 +426,7 @@ * Use MethodCall.invoke() to do this. Return result. */ public Object handle(Message req) { - Object body=null; + Object body; MethodCall method_call; if(server_obj == null) { @@ -380,7 +467,7 @@ throw new Exception("MethodCall uses ID=" + method_call.getId() + ", but method_lookup has not been set"); Method m=method_lookup.findMethod(method_call.getId()); if(m == null) - throw new Exception("no method foudn for " + method_call.getId()); + throw new Exception("no method found for " + method_call.getId()); method_call.setMethod(m); } @@ -397,7 +484,6 @@ * @return true if the listener was added or false if the listener was already in the list. */ public boolean addChannelListener(ChannelListener l) { - synchronized(additionalChannelListeners) { if (additionalChannelListeners.contains(l)) { return false; @@ -473,37 +559,9 @@ } public void channelShunned() { - - synchronized(additionalChannelListeners) { - for(Iterator i = additionalChannelListeners.iterator(); i.hasNext(); ) { - ChannelListener l = (ChannelListener)i.next(); - try { - l.channelShunned(); - } - catch(Throwable t) { - log.warn("channel listener failed", t); - } - } - } } public void channelReconnected(Address new_addr) { - if(log.isTraceEnabled()) - log.trace("channel has been rejoined, old local_addr=" + local_addr + ", new local_addr=" + new_addr); - this.local_addr=new_addr; - start(); - - synchronized(additionalChannelListeners) { - for(Iterator i = additionalChannelListeners.iterator(); i.hasNext(); ) { - ChannelListener l = (ChannelListener)i.next(); - try { - l.channelReconnected(new_addr); - } - catch(Throwable t) { - log.warn("channel listener failed", t); - } - } - } } /* ----------------------------------------------------------------------- */ diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/RspCollector.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/RspCollector.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/RspCollector.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/RspCollector.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: RspCollector.java,v 1.4 2007/02/16 09:06:57 belaban Exp $ package org.jgroups.blocks; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/RspFilter.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/RspFilter.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/RspFilter.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/RspFilter.java 2011-10-18 11:22:35.000000000 +0000 @@ -8,7 +8,6 @@ * request will not return (assuming timeout is 0) when the first response has been received, but when the filter * passed * @author Bela Ban - * @version $Id: RspFilter.java,v 1.2 2008/11/25 10:00:35 belaban Exp $ */ public interface RspFilter { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/TCPConnectionMap.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/TCPConnectionMap.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/TCPConnectionMap.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/TCPConnectionMap.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,42 +1,34 @@ package org.jgroups.blocks; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.BindException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.NetworkInterface; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketAddress; -import java.net.SocketException; +import org.jgroups.Address; +import org.jgroups.Global; +import org.jgroups.Version; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.stack.IpAddress; +import org.jgroups.util.DefaultSocketFactory; +import org.jgroups.util.SocketFactory; +import org.jgroups.util.ThreadFactory; +import org.jgroups.util.Util; + +import java.io.*; +import java.net.*; import java.util.Collection; +import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jgroups.Address; -import org.jgroups.Version; -import org.jgroups.stack.IpAddress; -import org.jgroups.util.PortsManager; -import org.jgroups.util.ThreadFactory; -import org.jgroups.util.Util; - public class TCPConnectionMap{ private final Mapper mapper; private final InetAddress bind_addr; private final Address local_addr; // bind_addr + port of srv_sock - private final ThreadGroup thread_group=new ThreadGroup(Util.getGlobalThreadGroup(),"ConnectionTable"); + private final ThreadGroup thread_group=new ThreadGroup(Util.getGlobalThreadGroup(),"ConnectionMap"); private final ServerSocket srv_sock; - private final Receiver receiver; + private Receiver receiver; private final long conn_expire_time; private final Log log=LogFactory.getLog(getClass()); private int recv_buf_size=120000; @@ -46,36 +38,54 @@ private boolean tcp_nodelay=false; private int linger=-1; private final Thread acceptor; - private PortsManager pm=null; - private final AtomicBoolean running = new AtomicBoolean(false); - private volatile boolean use_send_queues=false; + private volatile boolean use_send_queues=false; + protected SocketFactory socket_factory=new DefaultSocketFactory(); + + + public TCPConnectionMap(String service_name, + ThreadFactory f, + SocketFactory socket_factory, + Receiver r, + InetAddress bind_addr, + InetAddress external_addr, + int srv_port, + int max_port + ) throws Exception { + this(service_name, f,socket_factory, r,bind_addr,external_addr,srv_port,max_port,0,0); + } - public TCPConnectionMap(ThreadFactory f, + public TCPConnectionMap(String service_name, + ThreadFactory f, Receiver r, InetAddress bind_addr, InetAddress external_addr, int srv_port, int max_port, - PortsManager pm) throws Exception { - this(f,r,bind_addr,external_addr,srv_port,max_port,0,0,pm); + long reaper_interval, + long conn_expire_time + ) throws Exception { + this(service_name, f, null, r, bind_addr, external_addr, srv_port, max_port, reaper_interval, conn_expire_time); } - public TCPConnectionMap(ThreadFactory f, + public TCPConnectionMap(String service_name, + ThreadFactory f, + SocketFactory socket_factory, Receiver r, InetAddress bind_addr, InetAddress external_addr, int srv_port, int max_port, long reaper_interval, - long conn_expire_time, - PortsManager pm) throws Exception { + long conn_expire_time + ) throws Exception { this.mapper = new Mapper(f,reaper_interval); this.receiver=r; - this.bind_addr=bind_addr; - this.pm=pm; + this.bind_addr=bind_addr; this.conn_expire_time = conn_expire_time; - this.srv_sock=createServerSocket(srv_port, max_port); + if(socket_factory != null) + this.socket_factory=socket_factory; + this.srv_sock=Util.createServerSocket(this.socket_factory, service_name, bind_addr, srv_port, max_port); if(external_addr != null) local_addr=new IpAddress(external_addr, srv_sock.getLocalPort()); @@ -83,14 +93,38 @@ local_addr=new IpAddress(bind_addr, srv_sock.getLocalPort()); else local_addr=new IpAddress(srv_sock.getLocalPort()); - - acceptor=f.newThread(thread_group, new ConnectionAcceptor(),"ConnectionTable.Acceptor"); - } + + acceptor=f.newThread(thread_group, new ConnectionAcceptor(),"ConnectionMap.Acceptor"); + } public Address getLocalAddress() { return local_addr; } + public Receiver getReceiver() { + return receiver; + } + + public void setReceiver(Receiver receiver) { + this.receiver=receiver; + } + + public SocketFactory getSocketFactory() { + return socket_factory; + } + + public void setSocketFactory(SocketFactory socket_factory) { + this.socket_factory=socket_factory; + } + + public void addConnectionMapListener(AbstractConnectionMap.ConnectionMapListener l) { + mapper.addConnectionMapListener(l); + } + + public void removeConnectionMapListener(AbstractConnectionMap.ConnectionMapListener l) { + mapper.removeConnectionMapListener(l); + } + /** * Calls the receiver callback. We do not serialize access to this method, * and it may be called concurrently by several Connection handler threads. @@ -125,20 +159,17 @@ // 1. Try to obtain correct Connection (or create one if not yet existent) TCPConnection conn; - try { - conn=mapper.getConnection(dest); - } - catch(Throwable ex) { - throw new Exception("connection to " + dest + " could not be established", ex); - } + conn=mapper.getConnection(dest); // 2. Send the message using that connection - try { - conn.send(data, offset, length); - } - catch(Exception ex) { - mapper.removeConnection(dest); - throw ex; + if(conn != null) { + try { + conn.send(data, offset, length); + } + catch(Exception ex) { + mapper.removeConnection(dest); + throw ex; + } } } @@ -151,52 +182,17 @@ public void stop() { if(running.compareAndSet(true, false)) { - if(pm != null) { - pm.updatePort(srv_sock.getLocalPort()); + try { + getSocketFactory().close(srv_sock); + } + catch(IOException e) { } - Util.close(srv_sock); Util.interruptAndWaitToDie(acceptor); mapper.stop(); } } - /** - * Finds first available port starting at start_port and returns server - * socket. Will not bind to port >end_port. Sets srv_port - */ - protected ServerSocket createServerSocket(int start_port, int end_port) throws Exception { - ServerSocket ret=null; - while(true) { - try { - if(start_port > 0 && pm != null) - start_port=pm.getNextAvailablePort(start_port); - if(bind_addr == null) - ret=new ServerSocket(start_port); - else { - // changed (bela Sept 7 2007): we accept connections on all NICs - ret=new ServerSocket(start_port, 20, null); - } - } - catch(BindException bind_ex) { - if(start_port == end_port) - throw new BindException("No available port to bind to"); - if(bind_addr != null) { - NetworkInterface nic=NetworkInterface.getByInetAddress(bind_addr); - if(nic == null) - log.warn("bind_addr " + bind_addr + " is not a valid interface: " + bind_ex); - } - start_port++; - continue; - } - catch(IOException io_ex) { - if(log.isErrorEnabled()) - log.error("exception is " + io_ex); - } - break; - } - return ret; - } private void setSocketParameters(Socket client_sock) throws SocketException { if(log.isTraceEnabled()) @@ -324,7 +320,15 @@ public int getNumConnections() { return mapper.getNumConnections(); - } + } + + public boolean connectionEstablishedTo(Address addr) { + return mapper.connectionEstablishedTo(addr); + } + + public String printConnections() { + return mapper.printConnections(); + } public void retainAll(Collection
    members) { mapper.retainAll(members); @@ -337,8 +341,17 @@ public int getSenderQueueSize() { return send_queue_size; } + + public String toString() { + StringBuilder ret=new StringBuilder(); + ret.append("local_addr=" + local_addr).append("\n"); + ret.append("connections (" + mapper.size() + "):\n"); + ret.append(mapper.toString()); + ret.append('\n'); + return ret.toString(); + } - private class TCPConnection implements Connection { + public class TCPConnection implements Connection { private final Socket sock; // socket to/from peer (result of srv_sock.accept() or new Socket()) private final Lock send_lock=new ReentrantLock(); // serialize send() @@ -350,14 +363,16 @@ private final int peer_addr_read_timeout=2000; // max time in milliseconds to block on reading peer address private long last_access=System.currentTimeMillis(); // last time a message was sent or received private Sender sender; + private ConnectionPeerReceiver connectionPeerReceiver; + private AtomicBoolean active = new AtomicBoolean(false); TCPConnection(Address peer_addr) throws Exception { if(peer_addr == null) throw new IllegalArgumentException("Invalid parameter peer_addr="+ peer_addr); SocketAddress destAddr=new InetSocketAddress(((IpAddress)peer_addr).getIpAddress(),((IpAddress)peer_addr).getPort()); - this.sock=new Socket(); + this.sock=socket_factory.createSocket(Global.TCP_SOCK); this.sock.bind(new InetSocketAddress(bind_addr, 0)); - this.sock.connect(destAddr, sock_conn_timeout); + Util.connect(this.sock, destAddr, sock_conn_timeout); setSocketParameters(sock); this.out=new DataOutputStream(new BufferedOutputStream(sock.getOutputStream())); this.in=new DataInputStream(new BufferedInputStream(sock.getInputStream())); @@ -384,13 +399,15 @@ } private void start(ThreadFactory f) { - - Thread t=f.newThread(new ConnectionPeerReceiver(),"Connection.Receiver [" + getSockAddress() + "]"); - t.start(); - - if(isSenderUsed()) { - sender = new Sender(f,getSenderQueueSize()); - sender.start(); + //only start once.... + if(active.compareAndSet(false, true)) { + connectionPeerReceiver = new ConnectionPeerReceiver(f); + connectionPeerReceiver.start(); + + if(isSenderUsed()) { + sender = new Sender(f,getSenderQueueSize()); + sender.start(); + } } } @@ -420,20 +437,16 @@ * @param offset * @param length */ - private void send(byte[] data, int offset, int length) throws Exception{ - if(isSenderUsed()) { - try { - // we need to copy the byte[] buffer here because the original buffer might get changed meanwhile - byte[] tmp=new byte[length]; - System.arraycopy(data, offset, tmp, 0, length); - sender.getQueue().put(tmp); - } - catch(InterruptedException e) { - Thread.currentThread().interrupt(); - } + private void send(byte[] data, int offset, int length) throws Exception { + if (isSenderUsed()) { + // we need to copy the byte[] buffer here because the original buffer might get + // changed meanwhile + byte[] tmp = new byte[length]; + System.arraycopy(data, offset, tmp, 0, length); + sender.addToQueue(tmp); + } else { + _send(data, offset, length, true); } - else - _send(data, offset, length, true); } /** @@ -483,9 +496,9 @@ try { // read the cookie first byte[] input_cookie=new byte[cookie.length]; - in.read(input_cookie, 0, input_cookie.length); + in.readFully(input_cookie, 0, input_cookie.length); if(!matchCookie(input_cookie)) - throw new SocketException("ConnectionTable.Connection.readPeerAddress(): cookie read by " + getLocalAddress() + throw new SocketException("ConnectionMap.Connection.readPeerAddress(): cookie read by " + getLocalAddress() + " does not match own cookie; terminating connection"); // then read the version short version=in.readShort(); @@ -493,13 +506,10 @@ if(!Version.isBinaryCompatible(version) ) { if(log.isWarnEnabled()) log.warn(new StringBuilder("packet from ").append(client_sock.getInetAddress()) - .append(':') - .append(client_sock.getPort()) - .append(" has different version (") - .append(Version.print(version)) - .append(") from ours (") - .append(Version.printVersion()) - .append("). This may cause problems")); + .append(':').append(client_sock.getPort()).append(" has different version (") + .append(Version.print(version)).append(") from ours (") + .append(Version.printVersion()) + .append("). This may cause problems").toString()); } Address client_peer_addr=new IpAddress(); client_peer_addr.readFrom(in); @@ -538,30 +548,58 @@ } private class ConnectionPeerReceiver implements Runnable { + private Thread recv; + private final AtomicBoolean receiving = new AtomicBoolean(false); + + + public ConnectionPeerReceiver(ThreadFactory f) { + recv = f.newThread(this,"Connection.Receiver [" + getSockAddress() + "]"); + } + + + public void start() { + if(receiving.compareAndSet(false, true)) { + recv.start(); + } + } + + public void stop() { + if(receiving.compareAndSet(true, false)) { + recv.interrupt(); + } + } - public void run() { - byte[] buf=new byte[256]; // start with 256, increase as we go - int len=0; + public boolean isRunning() { + return receiving.get(); + } + + public boolean canRun() { + return isRunning() && isConnected(); + } - while(isOpen()) { - try { - len=in.readInt(); - if(len > buf.length) - buf=new byte[len]; - in.readFully(buf, 0, len); - updateLastAccessed(); - receiver.receive(peer_addr, buf, 0, len); - } - catch(OutOfMemoryError mem_ex) { - break; // continue; - } - catch(IOException io_ex) { - break; - } - catch(Throwable e) { + public void run() { + try { + while(!Thread.currentThread().isInterrupted() && canRun()) { + try { + int len=in.readInt(); + byte[] buf=new byte[len]; + in.readFully(buf, 0, len); + updateLastAccessed(); + receiver.receive(peer_addr, buf, 0, len); + } + catch(OutOfMemoryError mem_ex) { + break; // continue; + } + catch(IOException io_ex) { + break; + } + catch(Throwable e) { + } } } - Util.close(TCPConnection.this); + finally{ + Util.close(TCPConnection.this); + } } } @@ -569,45 +607,64 @@ final BlockingQueue send_queue; final Thread runner; + private final AtomicBoolean running= new AtomicBoolean(false); public Sender(ThreadFactory tf,int send_queue_size) { this.runner=tf.newThread(this, "Connection.Sender [" + getSockAddress() + "]"); this.send_queue=new LinkedBlockingQueue(send_queue_size); } - - public BlockingQueue getQueue() { - return send_queue; + + public void addToQueue(byte[] data) throws Exception { + if(canRun()) + send_queue.put(data); } public void start() { - runner.start(); + if(running.compareAndSet(false, true)) { + runner.start(); + } } public void stop() { - runner.interrupt(); + if(running.compareAndSet(true, false)) { + runner.interrupt(); + } + } + + public boolean isRunning() { + return running.get(); + } + + public boolean canRun() { + return isRunning() && isConnected(); } public void run() { - while(!Thread.currentThread().isInterrupted() && isOpen()) { - byte[] data=null; - try { - data=send_queue.take(); - } - catch(InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } - - if(data != null) { + try { + while(!Thread.currentThread().isInterrupted() && canRun()) { + byte[] data=null; try { - _send(data, 0, data.length, false); + data=send_queue.take(); } - catch(Exception ignored) { + catch(InterruptedException e) { + // Thread.currentThread().interrupt(); + break; } - } + + if(data != null) { + try { + _send(data, 0, data.length, false); + } + catch(Throwable ignored) { + } + } + } } + finally { + Util.close(TCPConnection.this); + } if(log.isTraceEnabled()) - log.trace("ConnectionTable.Connection.Sender thread terminated"); + log.trace("TCPConnection.Sender thread terminated at " + local_addr); } } @@ -643,31 +700,36 @@ } public boolean isExpired(long now) { - if(getConnectionExpiryTimeout() > 0) { - return now - last_access >= getConnectionExpiryTimeout(); - } - else { - return false; - } + return getConnectionExpiryTimeout() > 0 && now - last_access >= getConnectionExpiryTimeout(); + } + + public boolean isConnected() { + return !sock.isClosed() && sock.isConnected(); } public boolean isOpen() { - return !sock.isClosed() && sock.isConnected(); + return isConnected() + && (!isSenderUsed() || sender.isRunning()) + && (connectionPeerReceiver != null && connectionPeerReceiver.isRunning()); } - public void close() throws IOException { + public void close() throws IOException { + //can close even if start was never called... send_lock.lock(); try { - Util.close(sock); - Util.close(out); - Util.close(in); - if(isSenderUsed()){ + connectionPeerReceiver.stop(); + if (isSenderUsed()) { sender.stop(); } - } - finally { - send_lock.unlock(); - } + try { + socket_factory.close(sock); + } + catch(Throwable t) {} + Util.close(out); + Util.close(in); + } finally { + send_lock.unlock(); + } mapper.notifyConnectionClosed(peer_addr); } } @@ -683,23 +745,56 @@ } public TCPConnection getConnection(Address dest) throws Exception { - TCPConnection conn=null; + TCPConnection conn = null; getLock().lock(); try { conn=conns.get(dest); - if(conn == null) { - conn=new TCPConnection(dest); + if(conn != null && conn.isOpen()) + return conn; + try { + conn = new TCPConnection(dest); conn.start(getThreadFactory()); addConnection(dest, conn); - if(log.isTraceEnabled()) + if (log.isTraceEnabled()) log.trace("created socket to " + dest); } + catch(Exception ex) { + if(log.isTraceEnabled()) + log.trace("failed creating connection to " + dest); + } + } finally { + getLock().unlock(); + } + return conn; + } + + public boolean connectionEstablishedTo(Address address) { + lock.lock(); + try { + TCPConnection conn=conns.get(address); + return conn != null && conn.isConnected(); + } + finally { + lock.unlock(); + } + } + + public int size() {return conns.size();} + + public String toString() { + StringBuilder sb=new StringBuilder(); + + getLock().lock(); + try { + for(Map.Entry entry: conns.entrySet()) { + sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); + } + return sb.toString(); } finally { getLock().unlock(); } - return conn; - } - } + } + } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/TwoPhaseVotingAdapter.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/TwoPhaseVotingAdapter.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/TwoPhaseVotingAdapter.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/TwoPhaseVotingAdapter.java 2011-10-18 11:22:35.000000000 +0000 @@ -11,7 +11,6 @@ * * @author Roman Rokytskyy (rrokytskyy@acm.org) * @author Robert Schaffar-Taurok (robert@fusion.at) - * @version $Id: TwoPhaseVotingAdapter.java,v 1.4 2005/06/08 15:56:54 publicnmi Exp $ */ public class TwoPhaseVotingAdapter { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/UnicastRequest.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/UnicastRequest.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/UnicastRequest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/UnicastRequest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,204 @@ +package org.jgroups.blocks; + + +import org.jgroups.Address; +import org.jgroups.Message; +import org.jgroups.Transport; +import org.jgroups.View; +import org.jgroups.annotations.GuardedBy; +import org.jgroups.util.Rsp; + +import java.util.Collection; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + + +/** + * Sends a request to a single target destination + * + * @author Bela Ban + */ +public class UnicastRequest extends Request { + protected final Rsp result; + protected final Address target; + + + + /** + @param timeout Time to wait for responses (ms). A value of <= 0 means wait indefinitely + (e.g. if a suspicion service is available; timeouts are not needed). + */ + public UnicastRequest(Message m, RequestCorrelator corr, Address target, RequestOptions options) { + super(m, corr, null, options); + this.target=target; + result=new Rsp(target); + } + + + /** + * @param timeout Time to wait for responses (ms). A value of <= 0 means wait indefinitely + * (e.g. if a suspicion service is available; timeouts are not needed). + */ + public UnicastRequest(Message m, Transport transport, Address target, RequestOptions options) { + super(m, null, transport, options); + this.target=target; + result=new Rsp(target); + } + + + protected void sendRequest() throws Exception { + try { + if(log.isTraceEnabled()) log.trace(new StringBuilder("sending request (id=").append(req_id).append(')')); + if(corr != null) { + corr.sendUnicastRequest(req_id, target, request_msg, options.getMode() == GET_NONE? null : this); + } + else { + transport.send(request_msg); + } + } + catch(Exception ex) { + if(corr != null) + corr.done(req_id); + throw ex; + } + } + + + /* ---------------------- Interface RspCollector -------------------------- */ + /** + * Callback (called by RequestCorrelator or Transport). + * Adds a response to the response table. When all responses have been received, + * execute() returns. + */ + public void receiveResponse(Object response_value, Address sender) { + RspFilter rsp_filter=options.getRspFilter(); + + lock.lock(); + try { + if(done) + return; + if(!result.wasReceived()) { + boolean responseReceived=(rsp_filter == null) || rsp_filter.isAcceptable(response_value, sender); + result.setValue((T)response_value); + result.setReceived(responseReceived); + if(log.isTraceEnabled()) + log.trace(new StringBuilder("received response for request ").append(req_id) + .append(", sender=").append(sender).append(", val=").append(response_value)); + } + done=rsp_filter == null? responsesComplete() : !rsp_filter.needMoreResponses(); + if(done && corr != null) + corr.done(req_id); + } + finally { + completed.signalAll(); // wakes up execute() + lock.unlock(); + } + checkCompletion(this); + } + + + /** + * Callback (called by RequestCorrelator or Transport). + * Report to GroupRequest that a member is reported as faulty (suspected). + * This method would probably be called when getting a suspect message from a failure detector + * (where available). It is used to exclude faulty members from the response list. + */ + public void suspect(Address suspected_member) { + if(suspected_member == null || !suspected_member.equals(target)) + return; + + lock.lock(); + try { + if(done) + return; + if(result != null && !result.wasReceived()) + result.setSuspected(true); + done=true; + if(corr != null) + corr.done(req_id); + completed.signalAll(); + } + finally { + lock.unlock(); + } + checkCompletion(this); + } + + + /** + * If the target address is not a member of the new view, we'll mark the response as not received and unblock + * the caller of execute() + */ + public void viewChange(View new_view) { + Collection
    mbrs=new_view != null? new_view.getMembers() : null; + if(mbrs == null) + return; + + lock.lock(); + try { + if(!mbrs.contains(target)) { + result.setReceived(false); + done=true; + if(corr != null) + corr.done(req_id); + completed.signalAll(); + } + } + finally { + lock.unlock(); + } + + checkCompletion(this); + } + + + /* -------------------- End of Interface RspCollector ----------------------------------- */ + + public Rsp getResult() { + return result; + } + + + public T get() throws InterruptedException, ExecutionException { + lock.lock(); + try { + waitForResults(0); + return result.getValue(); + } + finally { + lock.unlock(); + } + } + + public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + boolean ok; + lock.lock(); + try { + ok=waitForResults(unit.toMillis(timeout)); + } + finally { + lock.unlock(); + } + if(!ok) + throw new TimeoutException(); + return result.getValue(); + } + + public String toString() { + StringBuilder ret=new StringBuilder(128); + ret.append(super.toString()); + ret.append(", target=" + target); + return ret.toString(); + } + + + + @GuardedBy("lock") + protected boolean responsesComplete() { + return done || options.getMode() == GET_NONE || result.wasReceived() || result.wasSuspected(); + } + + + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/UpdateException.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/UpdateException.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/UpdateException.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/UpdateException.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: UpdateException.java,v 1.2 2006/11/13 17:42:10 bstansberry Exp $ package org.jgroups.blocks; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/VoteResponseProcessor.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/VoteResponseProcessor.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/VoteResponseProcessor.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/VoteResponseProcessor.java 2011-10-18 11:22:35.000000000 +0000 @@ -11,7 +11,6 @@ * See the source of {@link org.jgroups.blocks.DistributedLockManager} for an example implementation. * * @author Robert Schaffar-Taurok (robert@fusion.at) - * @version $Id: VoteResponseProcessor.java,v 1.2 2005/07/17 11:36:40 chrislott Exp $ */ public interface VoteResponseProcessor { /** diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/VotingAdapter.java libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/VotingAdapter.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/blocks/VotingAdapter.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/blocks/VotingAdapter.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,7 +1,7 @@ package org.jgroups.blocks; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.*; import org.jgroups.util.Rsp; import org.jgroups.util.RspList; @@ -27,7 +27,6 @@ * * @author Roman Rokytskyy (rrokytskyy@acm.org) * @author Robert Schaffar-Taurok (robert@fusion.at) - * @version $Id: VotingAdapter.java,v 1.10 2006/09/27 12:42:53 belaban Exp $ */ public class VotingAdapter implements MessageListener, MembershipListener, VoteResponseProcessor { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/ChannelClosedException.java libjgroups-java-2.12.2.Final/src/org/jgroups/ChannelClosedException.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/ChannelClosedException.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/ChannelClosedException.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: ChannelClosedException.java,v 1.4 2006/11/13 17:42:11 bstansberry Exp $ package org.jgroups; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/ChannelException.java libjgroups-java-2.12.2.Final/src/org/jgroups/ChannelException.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/ChannelException.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/ChannelException.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: ChannelException.java,v 1.7 2006/11/13 17:42:11 bstansberry Exp $ package org.jgroups; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/ChannelFactory.java libjgroups-java-2.12.2.Final/src/org/jgroups/ChannelFactory.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/ChannelFactory.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/ChannelFactory.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: ChannelFactory.java,v 1.12 2008/02/27 16:58:44 belaban Exp $ package org.jgroups; @@ -13,8 +12,9 @@ * creation methodologies. * * @see JChannelFactory - * + * @deprecated Might get removed in 3.0. Use your own method of injecting channels */ +@Deprecated public interface ChannelFactory { /** diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/Channel.java libjgroups-java-2.12.2.Final/src/org/jgroups/Channel.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/Channel.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/Channel.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,12 +1,14 @@ -// $Id: Channel.java,v 1.46 2008/05/28 15:32:42 belaban Exp $ package org.jgroups; -import org.apache.commons.logging.Log; +import org.jgroups.logging.Log; import org.jgroups.annotations.MBean; import org.jgroups.annotations.ManagedOperation; +import org.jgroups.stack.Protocol; import org.jgroups.stack.ProtocolStack; +import org.jgroups.util.DefaultSocketFactory; +import org.jgroups.util.SocketFactory; import java.io.Serializable; import java.util.*; @@ -47,18 +49,25 @@ */ @MBean(description="Channel") public abstract class Channel implements Transport { + @Deprecated public static final int BLOCK=0; + @Deprecated public static final int VIEW=1; + @Deprecated public static final int SUSPECT=2; public static final int LOCAL=3; + @Deprecated public static final int GET_STATE_EVENTS=4; + @Deprecated public static final int AUTO_RECONNECT=5; + @Deprecated public static final int AUTO_GETSTATE=6; protected UpHandler up_handler=null; // when set, all events are passed to it ! protected Set channel_listeners=null; protected Receiver receiver=null; + protected SocketFactory socket_factory=new DefaultSocketFactory(); protected abstract Log getLog(); @@ -66,6 +75,18 @@ public abstract ProtocolStack getProtocolStack(); + public SocketFactory getSocketFactory() { + return socket_factory; + } + + public void setSocketFactory(SocketFactory factory) { + socket_factory=factory; + ProtocolStack stack=getProtocolStack(); + Protocol prot=stack != null? stack.getTopProtocol() : null; + if(prot != null) + prot.setSocketFactory(factory); + } + /** Connects the channel to a group. The client is now able to receive group messages, views and block events (depending on the options set) and to send @@ -123,6 +144,7 @@ /** Shuts down the channel without disconnecting if connected, stops all the threads */ + @Deprecated abstract public void shutdown(); @@ -207,6 +229,12 @@ abstract public void send(Address dst, Address src, Serializable obj) throws ChannelNotConnectedException, ChannelClosedException; + abstract public void send(Address dst, Address src, byte[] buf) throws ChannelNotConnectedException, + ChannelClosedException; + + abstract public void send(Address dst, Address src, byte[] buf, int offset, int length) throws ChannelNotConnectedException, + ChannelClosedException; + /** Access to event mechanism of channels. Enables to send and receive events, used by building @@ -302,14 +330,45 @@ /** Returns the channel's own address. The result of calling this method on an unconnected channel is implementation defined (may return null). Calling this method on a closed - channel returns null. - - @return The channel's address. Generated by the underlying transport, and opaque. - Addresses can be used as destination in the Send operation. + channel returns null. Addresses can be used as destination in the send() operation. + @return The channel's address (opaque) + @deprecated Use {@link #getAddress()} instead */ abstract public Address getLocalAddress(); + + /** + Returns the channel's own address. The result of calling this method on an unconnected + channel is implementation defined (may return null). Calling this method on a closed + channel returns null. Successor to {@link #getAddress()}. Addresses can be used as destination + in the send() operation. + @return The channel's address (opaque) + */ + abstract public Address getAddress(); + + /** + * Returns the logical name of this channel if set. + * @return The logical name or null (if not set) + */ + abstract public String getName(); + + /** + * Returns the logical name of a given member. The lookup is from the local cache of logical address + * / logical name mappings and no remote communication is performed. + * @param member + * @return + */ + abstract public String getName(Address member); + + + /** + * Sets the logical name for the channel. The name will stay associated with this channel for the channel's + * lifetime (until close() is called). This method should be called before calling connect().
    + * @param name + */ + abstract public void setName(String name); + /** Returns the group address of the group of which the channel is a member. This is the object that was the argument to connect(). Calling this method on a closed @@ -341,6 +400,9 @@ this.up_handler=up_handler; } + public UpHandler getUpHandler() { + return up_handler; + } /** Allows to be notified when a channel event such as connect, disconnect or close occurs. @@ -382,6 +444,10 @@ receiver=r; } + public Receiver getReceiver() { + return receiver; + } + /** Sets an option. The following options are currently recognized:
      @@ -552,29 +618,6 @@ } } - protected void notifyChannelShunned() { - if(channel_listeners == null) return; - for(ChannelListener channelListener: channel_listeners) { - try { - channelListener.channelShunned(); - } - catch(Throwable t) { - getLog().error("exception in channelShunned() callback", t); - } - } - } - - protected void notifyChannelReconnected(Address addr) { - if(channel_listeners == null) return; - for(ChannelListener channelListener: channel_listeners) { - try { - channelListener.channelReconnected(addr); - } - catch(Throwable t) { - getLog().error("exception in channelReconnected() callback", t); - } - } - } - + } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/ChannelListenerAdapter.java libjgroups-java-2.12.2.Final/src/org/jgroups/ChannelListenerAdapter.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/ChannelListenerAdapter.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/ChannelListenerAdapter.java 2011-10-18 11:22:35.000000000 +0000 @@ -3,7 +3,6 @@ /** * Class which implements {@link org.jgroups.ChannelListener} * @author Bela Ban - * @version $Id: ChannelListenerAdapter.java,v 1.1 2007/08/21 09:23:08 belaban Exp $ */ public class ChannelListenerAdapter implements ChannelListener { public void channelConnected(Channel channel) { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/ChannelListener.java libjgroups-java-2.12.2.Final/src/org/jgroups/ChannelListener.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/ChannelListener.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/ChannelListener.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: ChannelListener.java,v 1.1.1.1 2003/09/09 01:24:07 belaban Exp $ package org.jgroups; @@ -11,6 +10,6 @@ void channelConnected(Channel channel); void channelDisconnected(Channel channel); void channelClosed(Channel channel); - void channelShunned(); - void channelReconnected(Address addr); + @Deprecated void channelShunned(); + @Deprecated void channelReconnected(Address addr); } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/ChannelNotConnectedException.java libjgroups-java-2.12.2.Final/src/org/jgroups/ChannelNotConnectedException.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/ChannelNotConnectedException.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/ChannelNotConnectedException.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: ChannelNotConnectedException.java,v 1.3 2006/11/13 17:42:11 bstansberry Exp $ package org.jgroups; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/client/StompConnection.java libjgroups-java-2.12.2.Final/src/org/jgroups/client/StompConnection.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/client/StompConnection.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/client/StompConnection.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,397 @@ +package org.jgroups.client; + +import org.jgroups.annotations.Experimental; +import org.jgroups.annotations.Unsupported; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.protocols.STOMP; +import org.jgroups.util.Util; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.*; +import java.util.*; + +/** + * STOMP client to access the STOMP [1] protocol. Note that the full STOMP protocol is not implemented, e.g. transactions + * are currently not supported. + *

      + * The interactive client can be started with -h HOST -p PORT, which are the hostname and port of a JGroups server, running + * with STOMP in its stack configuration. The interactive client supports automatic failover to a different server if + * the currently connected-to server crashes, and a simple syntax for sending STOMP messages: + *

      + * subscribe DEST // example: subscribe /topics/a
      + * send DEST message // example: send /topics/a Hello world
      + * 
      + *

      + * [1] http://stomp.codehaus.org/Protocol + * @author Bela Ban + */ +@Experimental @Unsupported +public class StompConnection implements Runnable { + protected Socket sock; + protected DataInputStream in; + protected DataOutputStream out; + + // collection of server addresses, we can pick any one to connect to + protected final Set server_destinations=new HashSet(); + + protected final Set listeners=new HashSet(); + + protected final Set subscriptions=new HashSet(); + + protected Thread runner; + + protected volatile boolean running=true; + + protected String session_id; + + protected final Log log=LogFactory.getLog(getClass()); + + + /** + * @param dest IP address + ':' + port, e.g. "192.168.1.5:8787" + */ + public StompConnection(String dest) { + server_destinations.add(dest); + } + + public String getSessionId() {return session_id;} + + + public void addListener(Listener listener) { + if(listener != null) + listeners.add(listener); + } + + public void removeListener(Listener listener) { + if(listener != null) + listeners.add(listener); + } + + + public void connect(String userid, String password) throws IOException { + String dest; + + if(isConnected()) + return; + while((dest=pickRandomDestination()) != null) { + try { + connect(dest); + if(log.isDebugEnabled()) + log.debug("connected to " + dest); + break; + } + catch(IOException ex) { + if(log.isErrorEnabled()) + log.error("failed connecting to " + dest); + close(); + server_destinations.remove(dest); + } + } + if(!isConnected()) + throw new IOException("no target server available"); + + StringBuilder sb=new StringBuilder(); + sb.append(STOMP.ClientVerb.CONNECT.name()).append("\n"); + if(userid != null) + sb.append("login: ").append(userid).append("\n"); + if(password != null) + sb.append("passcode: ").append(password).append("\n"); + sb.append("\n"); + + out.write(sb.toString().getBytes()); + out.write(STOMP.NULL_BYTE); + out.flush(); + } + + + public void reconnect() throws IOException { + if(!running) + return; + connect(); + for(String subscription: subscriptions) + subscribe(subscription); + if(log.isDebugEnabled()) { + log.debug("reconnected to " + sock.getInetAddress().getHostAddress() + ":" + sock.getPort()); + if(!subscriptions.isEmpty()) + log.debug("re-subscribed to " + subscriptions); + } + + } + + + + public void connect() throws IOException { + connect(null, null); + } + + public void disconnect() { + running=false; + close(); + } + + public void subscribe(String destination) { + if(destination == null) + return; + subscriptions.add(destination); + + StringBuilder sb=new StringBuilder(); + sb.append(STOMP.ClientVerb.SUBSCRIBE.name()).append("\n"); + sb.append("destination: ").append(destination).append("\n"); + sb.append("\n"); + + try { + out.write(sb.toString().getBytes()); + out.write(STOMP.NULL_BYTE); + out.flush(); + } + catch(IOException ex) { + log.error("failed subscribing to " + destination + ": " + ex); + } + } + + public void unsubscribe(String destination) { + if(destination == null) + return; + subscriptions.remove(destination); + + StringBuilder sb=new StringBuilder(); + sb.append(STOMP.ClientVerb.UNSUBSCRIBE.name()).append("\n"); + sb.append("destination: ").append(destination).append("\n"); + sb.append("\n"); + + try { + out.write(sb.toString().getBytes()); + out.write(STOMP.NULL_BYTE); + out.flush(); + } + catch(IOException ex) { + log.error("failed unsubscribing from " + destination + ": " + ex); + } + } + + public void send(String destination, byte[] buf, int offset, int length, String ... headers) { + StringBuilder sb=new StringBuilder(); + sb.append(STOMP.ClientVerb.SEND.name()).append("\n"); + if(destination != null) + sb.append("destination: ").append(destination).append("\n"); + if(buf != null) + sb.append("content-length: ").append(length).append("\n"); + if(headers != null && headers.length % 2 == 0) { // must be even + for(int i=0; i < headers.length; i++) + sb.append(headers[i]).append(": ").append(headers[++i]).append("\n"); + } + sb.append("\n"); + + try { + out.write(sb.toString().getBytes()); + if(buf != null) + out.write(buf, offset, length); + out.write(STOMP.NULL_BYTE); + out.flush(); + } + catch(IOException ex) { + log.error("failed sending message to server: " + ex); + } + } + + /** + * Sends an INFO without body + */ + public void send(String destination, String ... headers) { + send(destination, null, 0, 0, headers); + } + + public void send(String destination, byte[] buf, int offset, int length) { + send(destination, buf, offset, length, (String[])null); + } + + public void send(String destination, byte[] buf) { + send(destination, buf, 0, buf.length); + } + + public void run() { + while(isConnected() && running) { + try { + STOMP.Frame frame=STOMP.readFrame(in); + if(frame != null) { + STOMP.ServerVerb verb=STOMP.ServerVerb.valueOf(frame.getVerb()); + if(log.isTraceEnabled()) + log.trace("frame: " + frame); + switch(verb) { + case MESSAGE: + byte[] buf=frame.getBody(); + notifyListeners(frame.getHeaders(), buf, 0, buf != null? buf.length : 0); + break; + case CONNECTED: + String sess_id=frame.getHeaders().get("session-id"); + if(sess_id != null) { + this.session_id=sess_id; + } + break; + case ERROR: + break; + case INFO: + notifyListeners(frame.getHeaders()); + String endpoints=frame.getHeaders().get("endpoints"); + if(endpoints != null) { + List list=Util.parseCommaDelimitedStrings(endpoints); + if(list != null) { + boolean changed=server_destinations.addAll(list); + if(changed && log.isDebugEnabled()) + log.debug("INFO: new server target list: " + server_destinations); + } + } + break; + case RECEIPT: + break; + default: + throw new IllegalArgumentException("verb " + verb + " is not known"); + } + } + } + catch(IOException e) { + close(); + try { + reconnect(); + } + catch(IOException e1) { + log.warn("failed to reconnect; runner thread terminated, cause: " + e1); + } + } + catch(Throwable t) { + log.error("failure reading frame", t); + } + } + } + + protected void notifyListeners(Map headers, byte[] buf, int offset, int length) { + for(Listener listener: listeners) { + try { + listener.onMessage(headers, buf, offset, length); + } + catch(Throwable t) { + log.error("failed calling listener", t); + } + } + } + + protected void notifyListeners(Map info) { + for(Listener listener: listeners) { + try { + listener.onInfo(info); + } + catch(Throwable t) { + log.error("failed calling listener", t); + } + } + } + + protected String pickRandomDestination() { + return server_destinations.isEmpty()? null : server_destinations.iterator().next(); + } + + protected void connect(String dest) throws IOException { + SocketAddress saddr=parse(dest); + sock=new Socket(); + sock.connect(saddr); + in=new DataInputStream(sock.getInputStream()); + out=new DataOutputStream(sock.getOutputStream()); + startRunner(); + } + + protected static SocketAddress parse(String dest) throws UnknownHostException { + int index=dest.lastIndexOf(":"); + String host=dest.substring(0, index); + int port=Integer.parseInt(dest.substring(index+1)); + return new InetSocketAddress(host, port); + } + + protected void close() { + Util.close(in); + Util.close(out); + Util.close(sock); + } + + public boolean isConnected() { + return sock != null && sock.isConnected() && !sock.isClosed(); + } + + protected synchronized void startRunner() { + if(runner == null || !runner.isAlive()) { + runner=new Thread(this, "StompConnection receiver"); + runner.start(); + } + } + + + + public static interface Listener { + void onMessage(Map headers, byte[] buf, int offset, int length); + void onInfo(Map information); + } + + + public static void main(String[] args) throws IOException { + String host="localhost"; + String port="8787"; + + for(int i=0; i < args.length; i++) { + if(args[i].equals("-h")) { + host=args[++i]; + continue; + } + if(args[i].equals("-p")) { + port=args[++i]; + continue; + } + System.out.println("StompConnection [-h host] [-p port]"); + return; + } + StompConnection conn=new StompConnection(host+ ":" + port); + conn.addListener(new Listener() { + + public void onMessage(Map headers, byte[] buf, int offset, int length) { + System.out.println("<< " + new String(buf, offset, length) + ", headers: " + headers); + } + + public void onInfo(Map information) { + System.out.println("<< INFO: " + information); + } + }); + conn.connect(); + + while(conn.isConnected()) { + try { + String line=Util.readStringFromStdin(": "); + if(line.startsWith("subscribe")) { + String dest=line.substring("subscribe".length()).trim(); + conn.subscribe(dest); + } + else if(line.startsWith("unsubscribe")) { + String dest=line.substring("unsubscribe".length()).trim(); + conn.unsubscribe(dest); + } + else if(line.startsWith("send")) { + String rest=line.substring("send".length()).trim(); + + int index=rest.indexOf(' '); + if(index != -1) { + String dest=rest.substring(0, index); + String body=rest.substring(index+1); + byte[] buf=body.getBytes(); + conn.send(dest, buf, 0, buf.length); + } + } + else if(line.startsWith("disconnect")) { + conn.disconnect(); + } + } + catch(Exception e) { + e.printStackTrace(); + } + } + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/conf/ClassConfigurator.java libjgroups-java-2.12.2.Final/src/org/jgroups/conf/ClassConfigurator.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/conf/ClassConfigurator.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/conf/ClassConfigurator.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,19 +1,24 @@ -// $Id: ClassConfigurator.java,v 1.25 2008/02/25 16:24:07 belaban Exp $ package org.jgroups.conf; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.ChannelException; import org.jgroups.Global; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.util.Tuple; import org.jgroups.util.Util; - -import java.util.Iterator; -import java.util.Map; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.concurrent.ConcurrentHashMap; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; /** * This class will be replaced with the class that read info @@ -27,14 +32,21 @@ * * @author Filip Hanik * @author Bela Ban - * @see MagicNumberReader */ public class ClassConfigurator { + public static final String MAGIC_NUMBER_FILE = "jg-magic-map.xml"; + public static final String PROTOCOL_ID_FILE = "jg-protocol-ids.xml"; private static final short MIN_CUSTOM_MAGIC_NUMBER=1024; + private static final short MIN_CUSTOM_PROTOCOL_ID=512; + + // this is where we store magic numbers; contains data from jg-magic-map.xml + private static final Map classMap=Util.createConcurrentMap(150, 0.75f, 150); // key=Class, value=magic number + private static final Map magicMap=Util.createConcurrentMap(150, 0.75f, 150); // key=magic number, value=Class + + /** Contains data read from jg-protocol-ids.xml */ + private static final Map protocol_ids=Util.createConcurrentMap(150, 0.75f, 150); + private static final Map protocol_names=Util.createConcurrentMap(150, 0.75f, 150); - //this is where we store magic numbers - private static final Map classMap=new ConcurrentHashMap(); // key=Class, value=magic number - private static final Map magicMap=new ConcurrentHashMap(); // key=magic number, value=Class protected static final Log log=LogFactory.getLog(ClassConfigurator.class); @@ -53,45 +65,53 @@ protected static void init() throws ChannelException { try { // make sure we have a class for DocumentBuilderFactory - // getClass().getClassLoader().loadClass("javax.xml.parsers.DocumentBuilderFactory"); Util.loadClass("javax.xml.parsers.DocumentBuilderFactory", ClassConfigurator.class); - MagicNumberReader reader=new MagicNumberReader(); - - // PropertyPermission not granted if running in an untrusted environment with JNLP. - try { - String mnfile=Util.getProperty(new String[]{Global.MAGIC_NUMBER_FILE, "org.jgroups.conf.magicNumberFile"}, - null, null, false, null); - if(mnfile != null) { - if(log.isDebugEnabled()) log.debug("Using " + mnfile + " as magic number file"); - reader.setFilename(mnfile); - } + String magic_number_file=null, protocol_id_file=null; + try { // PropertyPermission not granted if running in an untrusted environment with JNLP + magic_number_file=Util.getProperty(new String[]{Global.MAGIC_NUMBER_FILE, "org.jgroups.conf.magicNumberFile"}, + null, null, false, MAGIC_NUMBER_FILE); + protocol_id_file=Util.getProperty(new String[]{Global.PROTOCOL_ID_FILE, "org.jgroups.conf.protocolIDFile"}, + null, null, false, PROTOCOL_ID_FILE); + if(log.isDebugEnabled()) log.debug("Using " + magic_number_file + " as magic number file and " + + protocol_id_file + " for protocol IDs"); } catch (SecurityException ex){ } - ClassMap[] mapping=reader.readMagicNumberMapping(); - if(mapping != null) { - Short m; - for(int i=0; i < mapping.length; i++) { - m=new Short(mapping[i].getMagicNumber()); - try { - Class clazz=mapping[i].getClassForMap(); - if(magicMap.containsKey(m)) { - throw new ChannelException("magic key " + m + " (" + clazz.getName() + ')' + - " is already in map; please make sure that " + - "all magic keys are unique"); - } - else { - magicMap.put(m, clazz); - classMap.put(clazz, m); - } - } - catch(ClassNotFoundException cnf) { - throw new ChannelException("failed loading class", cnf); - } + // Read jg-magic-map.xml + List> mapping=readMappings(magic_number_file); + for(Tuple tuple: mapping) { + short m=tuple.getVal1(); + try { + Class clazz=Util.loadClass(tuple.getVal2(), ClassConfigurator.class); + if(magicMap.containsKey(m)) + throw new ChannelException("key " + m + " (" + clazz.getName() + ')' + + " is already in magic map; please make sure that all keys are unique"); + + magicMap.put(m, clazz); + classMap.put(clazz, m); + } + catch(ClassNotFoundException cnf) { + throw new ChannelException("failed loading class", cnf); + } + } + + // Read jg-protocol-ids.xml + mapping=readMappings(protocol_id_file); + for(Tuple tuple: mapping) { + short m=tuple.getVal1(); + try { + Class clazz=Util.loadClass(tuple.getVal2(), ClassConfigurator.class); + if(protocol_ids.containsKey(clazz)) + throw new ChannelException("ID " + m + " (" + clazz.getName() + ')' + + " is already in protocol-ID map; please make sure that all protocol IDs are unique"); + protocol_ids.put(clazz, m); + protocol_names.put(m, clazz); + } + catch(ClassNotFoundException cnf) { + throw new ChannelException("failed loading class", cnf); } - if(log.isDebugEnabled()) log.debug("mapping is:\n" + printMagicMap()); } } catch(ChannelException ex) { @@ -103,18 +123,6 @@ } -// public static ClassConfigurator getInstance(boolean init) throws ChannelException { -// if(instance == null) { -// instance=new ClassConfigurator(); -// if(init) -// instance.init(); -// } -// return instance; -// } -// -// public static ClassConfigurator getInstance() throws ChannelException { -// return getInstance(false); -// } /** * Method to register a user-defined header with jg-magic-map at runtime @@ -133,6 +141,16 @@ classMap.put(clazz, magic); } + + public static void addProtocol(short id, Class protocol) { + if(id <= MIN_CUSTOM_PROTOCOL_ID) + throw new IllegalArgumentException("protocol ID (" + id + ") needs to be greater than " + MIN_CUSTOM_PROTOCOL_ID); + if(protocol_ids.containsKey(protocol)) + throw new IllegalArgumentException("Protocol " + protocol + " is already present"); + protocol_ids.put(protocol, id); + } + + /** * Returns a class for a magic number. * Returns null if no class is found @@ -176,6 +194,17 @@ } + public static short getProtocolId(Class protocol) { + Short retval=protocol_ids.get(protocol); + if(retval != null) + return retval; + return 0; + } + + + public static Class getProtocol(short id) { + return protocol_names.get(id); + } public String toString() { @@ -204,5 +233,61 @@ } + /** + * try to read the magic number configuration file as a Resource form the classpath using getResourceAsStream + * if this fails this method tries to read the configuration file from mMagicNumberFile using a FileInputStream (not in classpath but somewhere else in the disk) + * + * @return an array of ClassMap objects that where parsed from the file (if found) or an empty array if file not found or had en exception + */ + protected static List> readMappings(String name) throws Exception { + InputStream stream; + try { + stream=Util.getResourceAsStream(name, ClassConfigurator.class); + // try to load the map from file even if it is not a Resource in the class path + if(stream == null) { + if(log.isTraceEnabled()) + log.trace("Could not read " + name + " from the CLASSPATH, will try to read it from file"); + stream=new FileInputStream(name); + } + } + catch(Exception x) { + throw new ChannelException(name + " not found. Please make sure it is on the classpath", x); + } + return parse(stream); + } + + protected static List> parse(InputStream stream) throws Exception { + DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance(); + factory.setValidating(false); // for now + DocumentBuilder builder=factory.newDocumentBuilder(); + Document document=builder.parse(stream); + NodeList class_list=document.getElementsByTagName("class"); + List> list=new LinkedList>(); + for(int i=0; i < class_list.getLength(); i++) { + if(class_list.item(i).getNodeType() == Node.ELEMENT_NODE) { + list.add(parseClassData(class_list.item(i))); + } + } + return list; + } + + protected static Tuple parseClassData(Node protocol) throws java.io.IOException { + try { + protocol.normalize(); + NamedNodeMap attrs=protocol.getAttributes(); + String clazzname; + String magicnumber; + + magicnumber=attrs.getNamedItem("id").getNodeValue(); + clazzname=attrs.getNamedItem("name").getNodeValue(); + return new Tuple(Short.valueOf(magicnumber), clazzname); + } + catch(Exception x) { + IOException tmp=new IOException(); + tmp.initCause(x); + throw tmp; + } + } + } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/conf/ClassMap.java libjgroups-java-2.12.2.Final/src/org/jgroups/conf/ClassMap.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/conf/ClassMap.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/conf/ClassMap.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,54 +0,0 @@ - -package org.jgroups.conf; - -import org.jgroups.util.Util; - - -/** - * Maintains mapping between magic number and class - * - * @author Filip Hanik (filip@filip.net) - * @author Bela Ban - * @version $Id: ClassMap.java,v 1.6 2008/01/23 15:32:42 belaban Exp $ - */ -public class ClassMap { - private final String mClassname; - private final short mMagicNumber; - - public ClassMap(String clazz, short magicnumber) { - mClassname=clazz; - mMagicNumber=magicnumber; - } - - public int hashCode() { - return getMagicNumber(); - } - - public String getClassName() { - return mClassname; - } - - public short getMagicNumber() { - return mMagicNumber; - } - - - /** - * Returns the Class object for this class
      - */ - public Class getClassForMap() throws ClassNotFoundException { - return Util.loadClass(getClassName(), this.getClass()); - } - - - public boolean equals(Object another) { - if(another instanceof ClassMap) { - ClassMap obj=(ClassMap)another; - return getClassName().equals(obj.getClassName()); - } - else - return false; - } - - -} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/conf/ConfiguratorFactory.java libjgroups-java-2.12.2.Final/src/org/jgroups/conf/ConfiguratorFactory.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/conf/ConfiguratorFactory.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/conf/ConfiguratorFactory.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,8 +1,8 @@ package org.jgroups.conf; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.ChannelException; import org.jgroups.JChannel; import org.jgroups.util.Util; @@ -12,6 +12,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.security.AccessControlException; +import java.util.List; import java.util.Map; /** @@ -25,7 +26,6 @@ * * @author Filip Hanik (
      filip@filip.net) * @author Bela Ban - * @version $Id: ConfiguratorFactory.java,v 1.28 2008/11/28 14:45:18 belaban Exp $ */ public class ConfiguratorFactory { public static final String JAXP_MISSING_ERROR_MSG= @@ -408,28 +408,19 @@ * @param configurator */ public static void substituteVariables(ProtocolStackConfigurator configurator) { - - ProtocolData[] protocols=null; - try { - protocols=configurator.getProtocolStack(); - - for(ProtocolData protocol:protocols) { - if(protocol != null) { - Map parms=protocol.getParameters(); - ProtocolParameter parm; - for(Map.Entry entry:parms.entrySet()) { - parm=entry.getValue(); - String val=parm.getValue(); - String replacement=Util.substituteVariable(val); - if(!replacement.equals(val)) { - parm.setValue(replacement); - } + List protocols=configurator.getProtocolStack(); + for(ProtocolConfiguration data: protocols) { + if(data != null) { + Map parms=data.getProperties(); + for(Map.Entry entry:parms.entrySet()) { + String val=entry.getValue(); + String replacement=Util.substituteVariable(val); + if(!replacement.equals(val)) { + entry.setValue(replacement); } } } } - catch(Exception ignored) { - } } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/conf/MagicNumberReader.java libjgroups-java-2.12.2.Final/src/org/jgroups/conf/MagicNumberReader.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/conf/MagicNumberReader.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/conf/MagicNumberReader.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,96 +0,0 @@ - -package org.jgroups.conf; - -/** - * Reads and maintains mapping between magic numbers and classes - * @author Filip Hanik (filip@filip.net) - * @author Bela Ban - * @version $Id: MagicNumberReader.java,v 1.16 2008/02/25 16:24:06 belaban Exp $ - */ - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jgroups.ChannelException; -import org.jgroups.util.Util; -import org.w3c.dom.Document; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - -public class MagicNumberReader { - public static final String MAGIC_NUMBER_FILE="jg-magic-map.xml"; - - public String mMagicNumberFile=MAGIC_NUMBER_FILE; - - protected static final Log log=LogFactory.getLog(MagicNumberReader.class); - - public void setFilename(String file) { - mMagicNumberFile=file; - } - - /** - * try to read the magic number configuration file as a Resource form the classpath using getResourceAsStream - * if this fails this method tries to read the configuration file from mMagicNumberFile using a FileInputStream (not in classpath but somewhere else in the disk) - * - * @return an array of ClassMap objects that where parsed from the file (if found) or an empty array if file not found or had en exception - */ - public ClassMap[] readMagicNumberMapping() throws Exception { - InputStream stream=null; - try { - stream=Util.getResourceAsStream(mMagicNumberFile, this.getClass()); - // try to load the map from file even if it is not a Resource in the class path - if(stream == null) { - if(log.isTraceEnabled()) - log.trace("Could not read " + mMagicNumberFile + " as Resource from the CLASSPATH, will try to read it from file."); - stream=new FileInputStream(mMagicNumberFile); - } - } - catch(Exception x) { - throw new ChannelException(mMagicNumberFile + " not found. Please make sure it is on the classpath.", x); - } - return parse(stream); - } - - protected static ClassMap[] parse(InputStream stream) throws Exception { - DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance(); - factory.setValidating(false); //for now - DocumentBuilder builder=factory.newDocumentBuilder(); - Document document=builder.parse(stream); - NodeList class_list=document.getElementsByTagName("class"); - java.util.Vector v=new java.util.Vector(); - for(int i=0; i < class_list.getLength(); i++) { - if(class_list.item(i).getNodeType() == Node.ELEMENT_NODE) { - v.addElement(parseClassData(class_list.item(i))); - } - } - ClassMap[] data=new ClassMap[v.size()]; - v.copyInto(data); - return data; - } - - protected static ClassMap parseClassData(Node protocol) throws java.io.IOException { - try { - protocol.normalize(); - NamedNodeMap attrs=protocol.getAttributes(); - String clazzname=null; - String magicnumber=null; - - magicnumber=attrs.getNamedItem("id").getNodeValue(); - clazzname=attrs.getNamedItem("name").getNodeValue(); - return new ClassMap(clazzname, Short.valueOf(magicnumber)); - } - catch(Exception x) { - IOException tmp=new IOException(); - tmp.initCause(x); - throw tmp; - } - } - - -} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/conf/PlainConfigurator.java libjgroups-java-2.12.2.Final/src/org/jgroups/conf/PlainConfigurator.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/conf/PlainConfigurator.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/conf/PlainConfigurator.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,44 +1,45 @@ -// $Id: PlainConfigurator.java,v 1.2 2004/09/23 16:29:14 belaban Exp $ package org.jgroups.conf; +import org.jgroups.stack.Configurator; + +import java.util.List; + /** * A ProtocolStackConfigurator for the old style properties. *
      * Old style properties are referred to as the property string used by channel earlier * they look like this PROTOCOL(param=value;param=value):PROTOCOL:PROTOCOL
      * All it does is that it holds the string, it currently doesn't parse it at all. - * * @author Filip Hanik (
      filip@filip.net) + * @author Bela Ban * @version 1.0 */ -public class PlainConfigurator implements ProtocolStackConfigurator -{ +public class PlainConfigurator implements ProtocolStackConfigurator { private final String mProperties; - + /** * Instantiates a PlainConfigurator with old style properties */ - public PlainConfigurator(String properties) - { - mProperties = properties; + public PlainConfigurator(String properties) { + mProperties=properties; } - + /** * returns the old style protocol string */ - public String getProtocolStackString() - { + public String getProtocolStackString() { return mProperties; } + - /** - * Throws a UnsupportedOperationException all the time. No parsing implemented. - */ - public ProtocolData[] getProtocolStack() - { - /**todo: Implement this org.jgroups.conf.ProtocolStackConfigurator method*/ - throw new java.lang.UnsupportedOperationException("Method getProtocolStack() not yet implemented."); + public List getProtocolStack() { + try { + return Configurator.parseConfigurations(mProperties); + } + catch(Exception e) { + throw new RuntimeException(e); + } } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/conf/PropertyConverter.java libjgroups-java-2.12.2.Final/src/org/jgroups/conf/PropertyConverter.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/conf/PropertyConverter.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/conf/PropertyConverter.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,7 +1,5 @@ package org.jgroups.conf; -import java.util.Properties; - import org.jgroups.annotations.Property; /** @@ -13,10 +11,9 @@ * @see Property * * @author Vladimir Blagojevic - * @version $Id: PropertyConverter.java,v 1.3 2008/06/02 08:01:49 belaban Exp $ */ public interface PropertyConverter { - Object convert(Class propertyFieldType, Properties props, String propertyValue) throws Exception; + Object convert(Object obj, Class propertyFieldType, String propertyName, String propertyValue, boolean check_scope) throws Exception; /** * Converts the value to a string. The default is to simply invoke Object.toString(), however, some objects need diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/conf/PropertyConverters.java libjgroups-java-2.12.2.Final/src/org/jgroups/conf/PropertyConverters.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/conf/PropertyConverters.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/conf/PropertyConverters.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,67 +1,133 @@ package org.jgroups.conf; -import java.util.Properties; -import java.util.List; -import java.net.InetAddress; -import java.net.NetworkInterface; - +import org.jgroups.Global; +import org.jgroups.View; +import org.jgroups.stack.Configurator; +import org.jgroups.stack.Protocol; +import org.jgroups.util.StackType; import org.jgroups.util.Util; +import java.lang.reflect.Field; +import java.net.*; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.concurrent.Callable; + /** * Groups a set of standard PropertyConverter(s) supplied by JGroups. * *

      - * Third parties can provide their own converters if such need arises by - * implementing {@link PropertyConverter} interface and by specifying that - * converter as converter on a specific Property annotation of a field or a - * method instance. + * Third parties can provide their own converters if such need arises by implementing + * {@link PropertyConverter} interface and by specifying that converter as converter on a specific + * Property annotation of a field or a method instance. * * @author Vladimir Blagojevic - * @version $Id: PropertyConverters.java,v 1.6 2008/06/02 08:13:25 belaban Exp $ */ public class PropertyConverters { public static class NetworkInterfaceList implements PropertyConverter { - public Object convert(Class propertyFieldType, Properties props, String propertyValue) throws Exception { + public Object convert(Object obj, Class propertyFieldType, String propertyName, String propertyValue, boolean check_scope) throws Exception { return Util.parseInterfaceList(propertyValue); } public String toString(Object value) { List list=(List)value; - StringBuilder sb=new StringBuilder(); - boolean first=true; - for(NetworkInterface intf: list) { - if(first) - first=false; - else - sb.append(","); - sb.append(intf.getName()); - } - return sb.toString(); + return Util.print(list); } } - public static class BindAddress implements PropertyConverter { + public static class FlushInvoker implements PropertyConverter { - public Object convert(Class propertyFieldType, Properties props, String propertyValue) throws Exception { - return Util.getBindAddress(props); + public Object convert(Object obj, Class propertyFieldType, String propertyName, String propertyValue, boolean check_scope) throws Exception { + if (propertyValue == null) { + return null; + } else { + Class> invoker = (Class>) Class.forName(propertyValue); + invoker.getDeclaredConstructor(View.class); + return invoker; + } + } + + public String toString(Object value) { + return value.getClass().getName(); + } + + } + + public static class InitialHosts implements PropertyConverter { + + public Object convert(Object obj, Class propertyFieldType, String propertyName, String prop_val, boolean check_scope) throws Exception { + int port_range = getPortRange((Protocol)obj) ; + return Util.parseCommaDelimitedHosts(prop_val, port_range); + } + + public String toString(Object value) { + return value.getClass().getName(); + } + + private static int getPortRange(Protocol protocol) throws Exception { + Field f = protocol.getClass().getDeclaredField("port_range") ; + return ((Integer) Configurator.getField(f,protocol)).intValue(); + } + } + + public static class InitialHosts2 implements PropertyConverter { + + public Object convert(Object obj, Class propertyFieldType, String propertyName, String prop_val, boolean check_scope) throws Exception { + // port range is 1 + return Util.parseCommaDelimitedHosts2(prop_val, 1); + } + + public String toString(Object value) { + return value.getClass().getName(); + } + } + + public static class BindInterface implements PropertyConverter { + + public Object convert(Object obj, Class propertyFieldType, String propertyName, String propertyValue, boolean check_scope) throws Exception { + + // get the existing bind address - possibly null + InetAddress old_bind_addr = (InetAddress)Configurator.getValueFromProtocol((Protocol)obj, "bind_addr"); + + // apply a bind interface constraint + InetAddress new_bind_addr = Util.validateBindAddressFromInterface(old_bind_addr, propertyValue); + + if (new_bind_addr != null) + setBindAddress((Protocol)obj, new_bind_addr) ; + + // if no bind_interface specified, set it to the empty string to avoid exception + // from @Property processing + if (propertyValue != null) + return propertyValue ; + else + return "" ; } + + private static void setBindAddress(Protocol protocol, InetAddress bind_addr) throws Exception { + Field f=Util.getField(protocol.getClass(), "bind_addr"); + Configurator.setField(f, protocol, bind_addr) ; + } + + + // return a String version of the converted value public String toString(Object value) { - InetAddress val=(InetAddress)value; - return val.getHostAddress(); + return (String) value ; } } public static class LongArray implements PropertyConverter { - public Object convert(Class propertyFieldType, Properties props, String propertyValue) throws Exception { + public Object convert(Object obj, Class propertyFieldType, String propertyName, String propertyValue, boolean check_scope) throws Exception { long tmp [] = Util.parseCommaDelimitedLongs(propertyValue); if(tmp != null && tmp.length > 0){ return tmp; }else{ - throw new Exception ("Invalid long array specified in " + propertyValue); + // throw new Exception ("Invalid long array specified in " + propertyValue); + return null; } } @@ -84,33 +150,109 @@ public static class Default implements PropertyConverter { - public Object convert(Class propertyFieldType, Properties props, String propertyValue) throws Exception { + static final String prefix; + + static { + String tmp="FF0e::"; + try { + tmp=System.getProperty(Global.IPV6_MCAST_PREFIX); + } + catch(Throwable t) { + tmp="FF0e::"; + } + prefix=tmp != null? tmp : "FF0e::"; + } + + public Object convert(Object obj, Class propertyFieldType, String propertyName, String propertyValue, boolean check_scope) throws Exception { if(propertyValue == null) throw new NullPointerException("Property value cannot be null"); if(Boolean.TYPE.equals(propertyFieldType)) { return Boolean.parseBoolean(propertyValue); - } - else if(Integer.TYPE.equals(propertyFieldType)) { - return Integer.parseInt(propertyValue); - } - else if(Long.TYPE.equals(propertyFieldType)) { - return Long.parseLong(propertyValue); - } - else if(Byte.TYPE.equals(propertyFieldType)) { + } else if (Integer.TYPE.equals(propertyFieldType)) { + // return Integer.parseInt(propertyValue); + return Util.readBytesInteger(propertyValue); + } else if (Long.TYPE.equals(propertyFieldType)) { + // return Long.parseLong(propertyValue); + return Util.readBytesLong(propertyValue); + } else if (Byte.TYPE.equals(propertyFieldType)) { return Byte.parseByte(propertyValue); - } - else if(Double.TYPE.equals(propertyFieldType)) { - return Double.parseDouble(propertyValue); - } - else if(Short.TYPE.equals(propertyFieldType)) { + } else if (Double.TYPE.equals(propertyFieldType)) { + // return Double.parseDouble(propertyValue); + return Util.readBytesDouble(propertyValue); + } else if (Short.TYPE.equals(propertyFieldType)) { return Short.parseShort(propertyValue); - } - else if(Float.TYPE.equals(propertyFieldType)) { + } else if (Float.TYPE.equals(propertyFieldType)) { return Float.parseFloat(propertyValue); + } else if(InetAddress.class.equals(propertyFieldType)) { + + InetAddress retval=null; + Util.AddressScope addr_scope=null; + try { + addr_scope=Util.AddressScope.valueOf(propertyValue.toUpperCase()); + } + catch(Throwable ex) { + } + + if(addr_scope != null) + retval=Util.getAddress(addr_scope); + else + retval=InetAddress.getByName(propertyValue); + + if(retval instanceof Inet4Address && retval.isMulticastAddress() && Util.getIpStackType() == StackType.IPv6) { + String tmp=prefix + propertyValue; + retval=InetAddress.getByName(tmp); + return retval; + } + + + if(check_scope && retval instanceof Inet6Address && retval.isLinkLocalAddress()) { + // check scope + Inet6Address addr=(Inet6Address)retval; + int scope=addr.getScopeId(); + if(scope == 0) { + // fix scope + Inet6Address ret=getScopedInetAddress(addr); + if(ret != null) { + retval=ret; + } + } + } + return retval; } return propertyValue; } + + protected static Inet6Address getScopedInetAddress(Inet6Address addr) { + if(addr == null) + return null; + Enumeration en; + List retval=new ArrayList(); + + try { + en=NetworkInterface.getNetworkInterfaces(); + while(en.hasMoreElements()) { + NetworkInterface intf=en.nextElement(); + Enumeration addrs=intf.getInetAddresses(); + while(addrs.hasMoreElements()) { + InetAddress address=addrs.nextElement(); + if(address.isLinkLocalAddress() && address instanceof Inet6Address && + address.equals(addr) && ((Inet6Address)address).getScopeId() != 0) { + retval.add(address); + } + } + } + if(retval.size() == 1) { + return (Inet6Address)retval.get(0); + } + else + return null; + } + catch(SocketException e) { + return null; + } + } + public String toString(Object value) { return value != null? value.toString() : null; } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/conf/PropertyHelper.java libjgroups-java-2.12.2.Final/src/org/jgroups/conf/PropertyHelper.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/conf/PropertyHelper.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/conf/PropertyHelper.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,173 @@ +package org.jgroups.conf ; + +import org.jgroups.annotations.Property; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.stack.Configurator; +import org.jgroups.stack.Protocol; +import org.jgroups.util.Util; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Map; + + /* + * A class of static methods for performing commonly used functions with @Property annotations. + */ + public class PropertyHelper { + + protected static final Log log=LogFactory.getLog(PropertyHelper.class); + + public static String getPropertyName(Field field, Map props) throws IllegalArgumentException { + if (field == null) { + throw new IllegalArgumentException("Cannot get property name: field is null") ; + } + if (props == null) { + throw new IllegalArgumentException("Cannot get property name: properties map is null") ; + } + Property annotation=field.getAnnotation(Property.class); + if (annotation == null) { + throw new IllegalArgumentException("Cannot get property name for field " + + field.getName() + " which is not annotated with @Property") ; + } + String propertyName=field.getName(); + if(props.containsKey(annotation.name())) { + propertyName=annotation.name(); + boolean isDeprecated=annotation.deprecatedMessage().length() > 0; + if(isDeprecated && log.isWarnEnabled()) { + log.warn(propertyName + " has been deprecated: " + annotation.deprecatedMessage()); + } + } + return propertyName ; + } + + public static String getPropertyName(Method method) throws IllegalArgumentException { + if (method == null) { + throw new IllegalArgumentException("Cannot get property name: field is null") ; + } + Property annotation=method.getAnnotation(Property.class); + if (annotation == null) { + throw new IllegalArgumentException("Cannot get property name for method " + + method.getName() + " which is not annotated with @Property") ; + } + String propertyName=annotation.name().length() > 0? annotation.name() : method.getName(); + propertyName=Util.methodNameToAttributeName(propertyName); + return propertyName ; + } + + public static Object getConvertedValue(Object obj, Field field, Map props, String prop, boolean check_scope) throws Exception { + if (obj == null) + throw new IllegalArgumentException("Cannot get converted value: Object is null") ; + if (field == null) + throw new IllegalArgumentException("Cannot get converted value: Field is null") ; + if (props == null) + throw new IllegalArgumentException("Cannot get converted value: Properties is null") ; + + Property annotation=field.getAnnotation(Property.class); + if (annotation == null) { + throw new IllegalArgumentException("Cannot get property name for field " + + field.getName() + " which is not annotated with @Property") ; + } + String propertyName = getPropertyName(field, props) ; + String name = obj instanceof Protocol? ((Protocol)obj).getName() : obj.getClass().getName(); + + PropertyConverter propertyConverter=(PropertyConverter)annotation.converter().newInstance(); + if(propertyConverter == null) { + throw new Exception("Could not find property converter for field " + propertyName + + " in " + name); + } + Object converted = null ; + try { + String tmp=obj instanceof Protocol? ((Protocol)obj).getName() + "." + propertyName : propertyName; + converted=propertyConverter.convert(obj, field.getType(), tmp, prop, check_scope); + } + catch(Exception e) { + throw new Exception("Conversion of " + propertyName + " in " + name + + " with original property value " + prop + " failed", e); + } + return converted ; + } + + + public static Object getConvertedValue(Object obj, Field field, String value, boolean check_scope) throws Exception { + if(obj == null) + throw new IllegalArgumentException("Cannot get converted value: Object is null"); + if(field == null) + throw new IllegalArgumentException("Cannot get converted value: Field is null"); + + Property annotation=field.getAnnotation(Property.class); + if(annotation == null) { + throw new IllegalArgumentException("Cannot get property name for field " + + field.getName() + " which is not annotated with @Property"); + } + String propertyName=field.getName(); + String name=obj instanceof Protocol? ((Protocol)obj).getName() : obj.getClass().getName(); + + PropertyConverter propertyConverter=(PropertyConverter)annotation.converter().newInstance(); + if(propertyConverter == null) { + throw new Exception("Could not find property converter for field " + propertyName + + " in " + name); + } + Object converted=null; + try { + String tmp=obj instanceof Protocol? ((Protocol)obj).getName() + "." + propertyName : propertyName; + converted=propertyConverter.convert(obj, field.getType(), tmp, value, check_scope); + } + catch(Exception e) { + throw new Exception("Conversion of " + propertyName + " in " + name + + " with original property value " + value + " failed", e); + } + return converted; + } + + + public static Object getConvertedValue(Object obj, Method method, Map props, String prop, boolean check_scope) throws Exception { + if (obj == null) { + throw new IllegalArgumentException("Cannot get converted value: Object is null") ; + } + if (method == null) { + throw new IllegalArgumentException("Cannot get converted value: Method is null") ; + } + if (!Configurator.isSetPropertyMethod(method)) { + throw new IllegalArgumentException("Cannot get converted value: Method is not set property method") ; + } + if (props == null) { + throw new IllegalArgumentException("Cannot get converted value: Properties is null") ; + } + Property annotation=method.getAnnotation(Property.class); + if (annotation == null) { + throw new IllegalArgumentException("Cannot get property name for method " + + method.getName() + " which is not annotated with @Property") ; + } + String propertyName = getPropertyName(method) ; + String name = obj instanceof Protocol? ((Protocol)obj).getName() : obj.getClass().getName(); + PropertyConverter propertyConverter=(PropertyConverter)annotation.converter().newInstance(); + if(propertyConverter == null) { + throw new Exception("Could not find property converter for method " + propertyName + + " in " + name); + } + Object converted = null ; + try { + String tmp=obj instanceof Protocol? ((Protocol)obj).getName() + "." + propertyName : propertyName; + converted=propertyConverter.convert(obj, method.getParameterTypes()[0], tmp, prop, check_scope); + } + catch(Exception e) { + throw new Exception("Conversion of " + propertyName + " in " + name + + " with original property value " + prop + " failed. Exception is " +e, e); + } + return converted ; + } + + public static boolean usesDefaultConverter(Field field) throws IllegalArgumentException { + if (field == null) { + throw new IllegalArgumentException("Cannot check converter: field is null") ; + } + Property annotation=field.getAnnotation(Property.class); + if (annotation == null) { + throw new IllegalArgumentException("Cannot check converter for field " + + field.getName() + " which is not annotated with @Property") ; + } + return annotation.converter().equals(PropertyConverters.Default.class) ; + } + + } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/conf/ProtocolConfiguration.java libjgroups-java-2.12.2.Final/src/org/jgroups/conf/ProtocolConfiguration.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/conf/ProtocolConfiguration.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/conf/ProtocolConfiguration.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,191 @@ +package org.jgroups.conf; + +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.util.Util; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + + +/** + * Parses and encapsulates the specification for 1 protocol of the protocol stack, e.g. + * UNICAST(timeout=5000) + * @author Bela Ban + */ +public class ProtocolConfiguration { + private final String protocol_name; + private String properties_str; + private final Map properties=new HashMap(); + public static final String protocol_prefix="org.jgroups.protocols"; + public static final Log log=LogFactory.getLog(ProtocolConfiguration.class); + + + /** + * Creates a new ProtocolConfiguration. + * @param config_str The configuration specification for the protocol, e.g. + *

      VERIFY_SUSPECT(timeout=1500)
      + */ + public ProtocolConfiguration(String config_str) throws Exception { + int index=config_str.indexOf('('); // e.g. "UDP(in_port=3333)" + int end_index=config_str.lastIndexOf(')'); + + if(index == -1) { + protocol_name=config_str; + properties_str=""; + } + else { + if(end_index == -1) { + throw new Exception("Configurator.ProtocolConfiguration(): closing ')' " + + "not found in " + config_str + ": properties cannot be set !"); + } + else { + properties_str=config_str.substring(index + 1, end_index); + protocol_name=config_str.substring(0, index); + } + } + parsePropertiesString(properties); + } + + public ProtocolConfiguration(String protocol_name, Map properties) { + this.protocol_name=protocol_name; + if(!properties.isEmpty()) + this.properties.putAll(properties); + } + + public String getProtocolName() { + return protocol_name; + } + + public Map getProperties() { + return properties; + } + + public String getPropertiesString() { + return properties_str; + } + + public Map getOriginalProperties() throws Exception { + Map props=new HashMap(); + parsePropertiesString(props); + return props; + } + + + + public void substituteVariables() { + for(Iterator> it=properties.entrySet().iterator(); it.hasNext();) { + Map.Entry entry=it.next(); + String key=entry.getKey(); + String val=entry.getValue(); + String tmp=Util.substituteVariable(val); + if(!val.equals(tmp)) { + properties.put(key, tmp); + } + else { + if(tmp.contains("${")) { + if(log.isWarnEnabled()) + log.warn("variable \"" + val + "\" in " + protocol_name + " could not be substituted; " + + key + " is removed from properties"); + it.remove(); + } + } + } + properties_str=propertiesToString(); + } + + + public String toString() { + StringBuilder retval=new StringBuilder(); + if(protocol_name == null) + retval.append(""); + else + retval.append(protocol_name); + if(properties != null) + retval.append("(" + Util.print(properties) + ')'); + return retval.toString(); + } + + public String propertiesToString() { + return Util.printMapWithDelimiter(properties, ";"); + } + + public String getProtocolString(boolean new_format) { + return new_format? getProtocolStringNewXml() : getProtocolString(); + } + + public String getProtocolString() { + StringBuilder buf=new StringBuilder(protocol_name); + if(!properties.isEmpty()) { + boolean first=true; + buf.append('('); + for(Map.Entry entry: properties.entrySet()) { + String key=entry.getKey(); + String val=entry.getValue(); + if(first) + first=false; + else + buf.append(';'); + buf.append(getParameterString(key, val)); + } + buf.append(')'); + } + return buf.toString(); + } + + + public String getProtocolStringNewXml() { + StringBuilder buf=new StringBuilder(protocol_name + ' '); + if(!properties.isEmpty()) { + boolean first=true; + for(Map.Entry entry: properties.entrySet()) { + String key=entry.getKey(); + String val=entry.getValue(); + if(first) + first=false; + else + buf.append(' '); + buf.append(getParameterStringXml(key, val)); + } + } + return buf.toString(); + } + + protected static String getParameterString(String name, String value) { + StringBuilder buf=new StringBuilder(name); + if(value != null) + buf.append('=').append(value); + return buf.toString(); + } + + protected static String getParameterStringXml(String name, String val) { + StringBuilder buf=new StringBuilder(name); + if(val != null) + buf.append("=\"").append(val).append('\"'); + return buf.toString(); + } + + + + protected void parsePropertiesString(Map properties) throws Exception { + int index=0; + + /* "in_port=5555;out_port=6666" */ + if(properties_str.length() > 0) { + String[] components=properties_str.split(";"); + for(String property : components) { + String name, value; + index=property.indexOf('='); + if(index == -1) { + throw new Exception("Configurator.ProtocolConfiguration(): '=' not found in " + property + + " of " + protocol_name); + } + name=property.substring(0, index); + value=property.substring(index + 1, property.length()); + properties.put(name, value); + } + } + } + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/conf/ProtocolData.java libjgroups-java-2.12.2.Final/src/org/jgroups/conf/ProtocolData.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/conf/ProtocolData.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/conf/ProtocolData.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,136 +0,0 @@ - -package org.jgroups.conf; - -/** - * Data holder for protocol - * @author Filip Hanik (filip@filip.net) - * @author Bela Ban - * @version $Id: ProtocolData.java,v 1.9 2008/11/04 08:23:04 belaban Exp $ - */ - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -public class ProtocolData { - /** Map of property keys and values */ - private final Map mParameters=new HashMap(); - private final String mProtocolName; - private final String mClassName; - - /** - * - * @param protocolName - * @param description - * @param className - * @param params - * @deprecated Use {@link #ProtocolData(String, String, ProtocolParameter[])} instead - */ - @Deprecated - public ProtocolData(String protocolName, - String description, - String className, - ProtocolParameter[] params) { - mProtocolName=protocolName; - mClassName=className; - if(params != null) { - for(int i=0; i < params.length; i++) { - mParameters.put(params[i].getName(), params[i]); - } - } - } - - public ProtocolData(String protocolName, - String className, - ProtocolParameter[] params) { - mProtocolName=protocolName; - mClassName=className; - if(params != null) { - for(int i=0; i < params.length; i++) { - mParameters.put(params[i].getName(), params[i]); - } - } - } - - public String getClassName() { - return mClassName; - } - - public String getProtocolName() { - return mProtocolName; - } - - @Deprecated - public static String getDescription() { - return "n/a"; - } - - public Map getParameters() { - return mParameters; - } - - @Deprecated - public static boolean isOverride() { - return false; - } - - public ProtocolParameter[] getParametersAsArray() { - ProtocolParameter[] result=new ProtocolParameter[mParameters.size()]; - Iterator it=mParameters.keySet().iterator(); - for(int i=0; i < result.length; i++) { - String key=(String)it.next(); - result[i]=mParameters.get(key); - } - return result; - } - - - public void override(ProtocolParameter[] params) { - for(int i=0; i < params.length; i++) - mParameters.put(params[i].getName(), params[i]); - } - - public String getProtocolString(boolean new_format) { - return new_format? getProtocolStringNewXml() : getProtocolString(); - } - - public String getProtocolString() { - StringBuilder buf=new StringBuilder(mClassName); - if(!mParameters.isEmpty()) { - buf.append('('); - Iterator i=mParameters.keySet().iterator(); - while(i.hasNext()) { - String key=(String)i.next(); - ProtocolParameter param=mParameters.get(key); - buf.append(param.getParameterString()); - if(i.hasNext()) buf.append(';'); - } - buf.append(')'); - } - return buf.toString(); - } - - public String getProtocolStringNewXml() { - StringBuilder buf=new StringBuilder(mClassName + ' '); - if(!mParameters.isEmpty()) { - Iterator i=mParameters.keySet().iterator(); - while(i.hasNext()) { - String key=(String)i.next(); - ProtocolParameter param=mParameters.get(key); - buf.append(param.getParameterStringXml()); - if(i.hasNext()) buf.append(' '); - } - } - return buf.toString(); - } - - public int hashCode() { - return mProtocolName.hashCode(); - } - - public boolean equals(Object another) { - return another instanceof ProtocolData && getProtocolName().equals(((ProtocolData)another).getProtocolName()); - } - - -} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/conf/ProtocolParameter.java libjgroups-java-2.12.2.Final/src/org/jgroups/conf/ProtocolParameter.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/conf/ProtocolParameter.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/conf/ProtocolParameter.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,60 +0,0 @@ - -package org.jgroups.conf; - -/** - * Data holder for protocol data - * - * @author Filip Hanik (filip@filip.net) - * @author Bela Ban - * @version $Id: ProtocolParameter.java,v 1.6 2007/12/03 13:17:08 belaban Exp $ - */ - -public class ProtocolParameter { - - private final String mParameterName; - private String mParameterValue; - - public ProtocolParameter(String parameterName, String parameterValue) { - mParameterName=parameterName; - mParameterValue=parameterValue; - } - - public String getName() { - return mParameterName; - } - - public String getValue() { - return mParameterValue; - } - - public void setValue(String replacement) { - mParameterValue=replacement; - } - - public int hashCode() { - if(mParameterName != null) - return mParameterName.hashCode(); - else - return -1; - } - - public boolean equals(Object another) { - return another instanceof ProtocolParameter && getName().equals(((ProtocolParameter)another).getName()); - } - - public String getParameterString() { - StringBuilder buf=new StringBuilder(mParameterName); - if(mParameterValue != null) - buf.append('=').append(mParameterValue); - return buf.toString(); - } - - public String getParameterStringXml() { - StringBuilder buf=new StringBuilder(mParameterName); - if(mParameterValue != null) - buf.append("=\"").append(mParameterValue).append('\"'); - return buf.toString(); - } - - -} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/conf/ProtocolStackConfigurator.java libjgroups-java-2.12.2.Final/src/org/jgroups/conf/ProtocolStackConfigurator.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/conf/ProtocolStackConfigurator.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/conf/ProtocolStackConfigurator.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,14 +1,15 @@ -// $Id: ProtocolStackConfigurator.java,v 1.1.1.1 2003/09/09 01:24:08 belaban Exp $ package org.jgroups.conf; +import java.util.List; + /** * @author Filip Hanik (filip@filip.net) + * @author Bela Ban * @version 1.0 */ -public interface ProtocolStackConfigurator -{ - String getProtocolStackString(); - ProtocolData[] getProtocolStack(); +public interface ProtocolStackConfigurator { + String getProtocolStackString(); + List getProtocolStack(); } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/conf/XmlConfigurator.java libjgroups-java-2.12.2.Final/src/org/jgroups/conf/XmlConfigurator.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/conf/XmlConfigurator.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/conf/XmlConfigurator.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,18 +1,22 @@ -// $Id: XmlConfigurator.java,v 1.20 2008/11/04 08:23:04 belaban Exp $ package org.jgroups.conf; /** * Uses XML to configure a protocol stack - * @author Filip Hanik (filip@filip.net) - * @version 1.0 + * @author Vladimir Blagojevic */ -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.Global; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.w3c.dom.*; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; import org.jgroups.stack.Configurator; -import org.jgroups.stack.Configurator.ProtocolConfiguration; +import org.jgroups.util.Util; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -22,58 +26,60 @@ import java.io.InputStream; import java.net.URL; import java.util.*; -import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicReference; public class XmlConfigurator implements ProtocolStackConfigurator { - public static final String ATTR_NAME="name"; - public static final String ATTR_VALUE="value"; - public static final String ATTR_INHERIT="inherit"; - public static final String ELMT_PROT_OVERRIDE="protocol-override"; - public static final String ELMT_PROT="protocol"; - public static final String ELMT_PROT_NAME="protocol-name"; - public static final String ELMT_CLASS="class-name"; - public static final String ELMT_DESCRIPTION="description"; - public static final String ELMT_PROT_PARAMS="protocol-params"; - - private final ArrayList mProtocolStack=new ArrayList(); - private final String mStackName; - protected static final Log log=LogFactory.getLog(XmlConfigurator.class); - - protected XmlConfigurator(String stackName,ProtocolData[] protocols) { - mStackName=stackName; - mProtocolStack.addAll(Arrays.asList(protocols)); - } - - protected XmlConfigurator(String stackName) { - this(stackName, new ProtocolData[0]); + private static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; + private static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"; + private final List configuration=new ArrayList(); + protected static final Log log=LogFactory.getLog(XmlConfigurator.class); + + + protected XmlConfigurator(List protocols) { + configuration.addAll(protocols); } public static XmlConfigurator getInstance(URL url) throws java.io.IOException { - return getInstance(url.openStream()); + return getInstance(url, null); } public static XmlConfigurator getInstance(InputStream stream) throws java.io.IOException { - return parse(stream); + return getInstance(stream, null); } public static XmlConfigurator getInstance(Element el) throws java.io.IOException { return parse(el); } + public static XmlConfigurator getInstance(URL url, Boolean validate) throws java.io.IOException { + InputStream is = url.openStream(); + try { + return getInstance(is, validate); + } finally { + try { + is.close(); + } catch (IOException e) { + log.warn("Failed to close InputStream", e); + } + } + } + + public static XmlConfigurator getInstance(InputStream stream, Boolean validate) throws java.io.IOException { + return parse(stream, validate); + } + /** * - * @param convert - * If false: print old plain output, else print new XML - * format + * @param convert If false: print old plain output, else print new XML format * @return String with protocol stack in specified format */ public String getProtocolStackString(boolean convert) { StringBuilder buf=new StringBuilder(); - Iterator it=mProtocolStack.iterator(); + Iterator it=configuration.iterator(); if(convert) buf.append("\n"); while(it.hasNext()) { - ProtocolData d=it.next(); + ProtocolConfiguration d=it.next(); if(convert) buf.append(" <"); buf.append(d.getProtocolString(convert)); @@ -95,68 +101,108 @@ return getProtocolStackString(false); } - public ProtocolData[] getProtocolStack() { - return mProtocolStack.toArray(new ProtocolData[mProtocolStack.size()]); + public List getProtocolStack() { + return configuration; } - public String getName() { - return mStackName; - } - public void override(ProtocolData data) throws IOException { - int index=mProtocolStack.indexOf(data); - if(index < 0) - throw new IOException("You can not override a protocol that doesn't exist"); - ProtocolData source=mProtocolStack.get(index); - source.override(data.getParametersAsArray()); - } - public void add(ProtocolData data) { - mProtocolStack.add(data); - } - - - - protected static XmlConfigurator parse(InputStream stream) throws java.io.IOException { + protected static XmlConfigurator parse(InputStream stream, Boolean validate) throws java.io.IOException { /** - * CAUTION: crappy code ahead ! I (bela) am not an XML expert, so the - * code below is pretty amateurish... But it seems to work, and it is - * executed only on startup, so no perf loss on the critical path. If - * somebody wants to improve this, please be my guest. + * CAUTION: crappy code ahead ! I (bela) am not an XML expert, so the code below is pretty + * amateurish... But it seems to work, and it is executed only on startup, so no perf loss + * on the critical path. If somebody wants to improve this, please be my guest. */ try { - DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance(); - factory.setValidating(false); //for now - DocumentBuilder builder=factory.newDocumentBuilder(); - Document document=builder.parse(stream); + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + boolean validation = false; + String tmp = Util.getProperty(new String[] { Global.XML_VALIDATION }, null, null, false, null); + if (tmp != null) { + validation = Boolean.valueOf(tmp).booleanValue(); + } else if (validate != null) { + validation = validate.booleanValue(); + } + factory.setValidating(validation); + factory.setNamespaceAware(validation); + if (validation) { + factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA); + } + + DocumentBuilder builder = factory.newDocumentBuilder(); + builder.setEntityResolver(new EntityResolver() { + + public InputSource resolveEntity(String publicId, String systemId) throws IOException { + if (systemId != null && systemId.startsWith("http://www.jgroups.org/schema/JGroups-")) { + String schemaName = systemId.substring("http://www.jgroups.org/".length()); + InputStream schemaIs = getAsInputStreamFromClassLoader(schemaName); + if (schemaIs == null) { + throw new IOException("Schema not found from classloader: " + schemaName); + } + InputSource source = new InputSource(schemaIs); + source.setPublicId(publicId); + source.setSystemId(systemId); + return source; + } + return null; + } + }); + // Use AtomicReference to allow make variable final, not for atomicity + // We store only last exception + final AtomicReference exceptionRef = new AtomicReference(); + builder.setErrorHandler(new ErrorHandler() { + + public void warning(SAXParseException exception) throws SAXException { + log.warn("Warning during parse", exception); + } + + public void fatalError(SAXParseException exception) throws SAXException { + log.fatal("Error during parse", exception); + exceptionRef.set(exception); + } + + public void error(SAXParseException exception) throws SAXException { + log.error("Error during parse", exception); + exceptionRef.set(exception); + } + }); + Document document = builder.parse(stream); + if (exceptionRef.get() != null) { + throw exceptionRef.get(); + } // The root element of the document should be the "config" element, // but the parser(Element) method checks this so a check is not // needed here. - Element configElement=document.getDocumentElement(); + Element configElement = document.getDocumentElement(); return parse(configElement); - } - catch(Exception x) { - if(x instanceof java.io.IOException) - throw (java.io.IOException)x; + } catch (Exception x) { + if (x instanceof java.io.IOException) + throw (java.io.IOException) x; else { - IOException tmp=new IOException(); + IOException tmp = new IOException(); tmp.initCause(x); throw tmp; } } } + private static InputStream getAsInputStreamFromClassLoader(String filename) { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + InputStream is = cl == null ? null : cl.getResourceAsStream(filename); + if (is == null) { + // check system class loader + is = XmlConfigurator.class.getClassLoader().getResourceAsStream(filename); + } + return is; + } + protected static XmlConfigurator parse(Element root_element) throws java.io.IOException { XmlConfigurator configurator=null; - - /** LinkedList */ - LinkedList prot_data=new LinkedList(); + final LinkedList prot_data=new LinkedList(); /** - * CAUTION: crappy code ahead ! I (bela) am not an XML expert, so the - * code below is pretty amateurish... But it seems to work, and it is - * executed only on startup, so no perf loss on the critical path. If + * CAUTION: crappy code ahead ! I (bela) am not an XML expert, so the code below is pretty amateurish... + * But it seems to work, and it is executed only on startup, so no perf loss on the critical path. If * somebody wants to improve this, please be my guest. */ try { @@ -174,9 +220,8 @@ continue; Element tag=(Element)node; - String protocol=tag.getTagName(); - // System.out.println("protocol: " + protocol); - LinkedList tmp=new LinkedList(); + String protocol=tag.getTagName(); + Map params=new HashMap(); NamedNodeMap attrs=tag.getAttributes(); int attrLength=attrs.getLength(); @@ -184,20 +229,12 @@ Attr attr=(Attr)attrs.item(a); String name=attr.getName(); String value=attr.getValue(); - // System.out.println(" name=" + name + ", value=" + value); - tmp.add(new ProtocolParameter(name, value)); + params.put(name, value); } - ProtocolParameter[] params=new ProtocolParameter[tmp.size()]; - for(int j=0;j < tmp.size();j++) - params[j]=tmp.get(j); - ProtocolData data=new ProtocolData(protocol, protocol, params); - prot_data.add(data); + ProtocolConfiguration cfg=new ProtocolConfiguration(protocol, params); + prot_data.add(cfg); } - - ProtocolData[] data=new ProtocolData[(prot_data.size())]; - for(int k=0;k < prot_data.size();k++) - data[k]=prot_data.get(k); - configurator=new XmlConfigurator("bla", data); + configurator=new XmlConfigurator(prot_data); } catch(Exception x) { if(x instanceof java.io.IOException) @@ -213,30 +250,6 @@ - protected static ProtocolParameter[] parseProtocolParameters(Element protparams) throws IOException { - try { - Vector v=new Vector(); - protparams.normalize(); - NodeList parameters=protparams.getChildNodes(); - for(int i=0;i < parameters.getLength();i++) { - if(parameters.item(i).getNodeType() == Node.ELEMENT_NODE) { - String pname=parameters.item(i).getAttributes().getNamedItem(ATTR_NAME).getNodeValue(); - String pvalue=parameters.item(i).getAttributes().getNamedItem(ATTR_VALUE).getNodeValue(); - ProtocolParameter p=new ProtocolParameter(pname, pvalue); - v.addElement(p); - }//end if - }//for - ProtocolParameter[] result=new ProtocolParameter[v.size()]; - v.copyInto(result); - return result; - } - catch(Exception x) { - IOException tmp=new IOException(); - tmp.initCause(x); - throw tmp; - } - } - public static void main(String[] args) throws Exception { String input_file=null; XmlConfigurator conf; @@ -275,16 +288,9 @@ input=Thread.currentThread().getContextClassLoader().getResourceAsStream(input_file); if(old_format) { - Configurator config=new Configurator(); String cfg=inputAsString(input); - Vector tmp=config.parseConfigurations(cfg); + List tmp=Configurator.parseConfigurations(cfg); System.out.println(dump(tmp)); - - // conf=XmlConfigurator.getInstanceOldFormat(input); - // output=conf.getProtocolStackString(true); - // output=replace(output, "org.jgroups.protocols.", ""); - // System.out.println(getTitle(input_file)); - // System.out.println('\n' + output); } else { conf=XmlConfigurator.getInstance(input); @@ -297,35 +303,26 @@ } } - /** - * @param tmp - * Vector of Configurator.ProtocolConfiguration - * @return String (XML format) - */ - private static String dump(Vector tmp) { + + private static String dump(Collection configs) { StringBuilder sb=new StringBuilder(); String indent=" "; sb.append("\n"); - for(Iterator it=tmp.iterator();it.hasNext();) { - Configurator.ProtocolConfiguration cfg=it.next(); + for(ProtocolConfiguration cfg: configs) { sb.append(indent).append("<").append(cfg.getProtocolName()); - Properties props=cfg.getProperties(); + Map props=cfg.getProperties(); if(props.isEmpty()) { sb.append(" />\n"); } else { sb.append("\n").append(indent).append(indent); - for(Iterator> it2=props.entrySet().iterator();it2.hasNext();) { - Entry entry=it2.next(); - String key=(String)entry.getKey(); - String val=(String)entry.getValue(); + for(Map.Entry entry: props.entrySet()) { + String key=entry.getKey(); + String val=entry.getValue(); key=trim(key); val=trim(val); - sb.append(key).append("=\"").append(val).append("\""); - if(it2.hasNext()) { - sb.append("\n").append(indent).append(indent); - } + sb.append(key).append("=\"").append(val).append("\" "); } sb.append(" />\n"); } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/debug/Profiler.java libjgroups-java-2.12.2.Final/src/org/jgroups/debug/Profiler.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/debug/Profiler.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/debug/Profiler.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,9 +1,8 @@ -// $Id: Profiler.java,v 1.6 2005/05/30 16:14:35 belaban Exp $ package org.jgroups.debug; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import java.io.FileOutputStream; import java.io.OutputStream; @@ -43,7 +42,7 @@ os=new FileOutputStream("profiler.dat"); } catch(Exception e) { - log.error(e); + log.error(e.toString()); } } @@ -56,7 +55,7 @@ os=new FileOutputStream(filename); } catch(Exception e) { - log.error(e); + log.error(e.toString()); } } @@ -94,7 +93,7 @@ os.write("-----------------------------------------------------------------\n\n".getBytes()); } catch(Exception e) { - log.error(e); + log.error(e.toString()); } for(Enumeration e=entries.keys(); e.hasMoreElements();) { key=(String)e.nextElement(); @@ -104,7 +103,7 @@ val.tot_time + ' ' + trim(val.avg) + '\n').getBytes()); } catch(Exception ex) { - log.error(ex); + log.error(ex.toString()); } } } @@ -147,7 +146,7 @@ Profiler.dump(); } catch(Exception e) { - log.error(e); + log.error(e.toString()); } } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/debug/ProtocolTester.java libjgroups-java-2.12.2.Final/src/org/jgroups/debug/ProtocolTester.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/debug/ProtocolTester.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/debug/ProtocolTester.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,13 +1,12 @@ -// $Id: ProtocolTester.java,v 1.16 2008/07/30 15:10:20 vlada Exp $ package org.jgroups.debug; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.Event; import org.jgroups.JChannel; import org.jgroups.Message; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.stack.Configurator; import org.jgroups.stack.Protocol; import org.jgroups.stack.ProtocolStack; @@ -16,7 +15,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Vector; -import java.util.ArrayList; /** @@ -46,8 +44,8 @@ config=new Configurator(); JChannel mock_channel=new JChannel() {}; - ProtocolStack stack=new ProtocolStack(mock_channel,props); - stack.setup(); + ProtocolStack stack=new ProtocolStack(mock_channel); + stack.setup(Configurator.parseConfigurations(props)); stack.insertProtocol(harness, ProtocolStack.ABOVE, stack.getTopProtocol().getClass()); bottom=stack.getBottomProtocol(); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/debug/Simulator.java libjgroups-java-2.12.2.Final/src/org/jgroups/debug/Simulator.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/debug/Simulator.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/debug/Simulator.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,13 +1,10 @@ package org.jgroups.debug; -import org.jgroups.Address; -import org.jgroups.ChannelException; -import org.jgroups.Event; -import org.jgroups.Message; -import org.jgroups.View; +import org.jgroups.*; import org.jgroups.protocols.TP; import org.jgroups.stack.Protocol; import org.jgroups.stack.ProtocolStack; +import org.jgroups.util.DefaultTimeScheduler; import org.jgroups.util.Queue; import org.jgroups.util.QueueClosedException; import org.jgroups.util.TimeScheduler; @@ -20,12 +17,11 @@ /** * Tests one or more protocols independently. Look at org.jgroups.tests.FCTest for an example of how to use it. * @author Bela Ban - * @version $Id: Simulator.java,v 1.14 2008/10/23 16:59:40 rachmatowicz Exp $ */ public class Simulator { private Protocol[] protStack=null; private ProtocolAdapter ad=new ProtocolAdapter(); - ProtocolStack prot_stack=null; + private ProtocolStack prot_stack=null; private Receiver r=null; private Protocol top=null, bottom=null; private Queue send_queue=new Queue(); @@ -51,20 +47,20 @@ void receive(Event evt); } - public void setProtocolStack(Protocol[] stack) { + public ProtocolStack getProtocolStack() { + return prot_stack; + } + + public void setProtocolStack(Protocol[] stack) { this.protStack=stack; this.protStack[0].setUpProtocol(ad); this.protStack[this.protStack.length-1].setDownProtocol(ad); top=protStack[0]; bottom=this.protStack[this.protStack.length-1]; - try { - prot_stack=new ProtocolStack(); - } catch (ChannelException e) { - e.printStackTrace(); - } + prot_stack=new ProtocolStack(); - if(protStack.length > 1) { + if(protStack.length > 1) { for(int i=0; i < protStack.length; i++) { Protocol p1=protStack[i]; p1.setProtocolStack(prot_stack); @@ -150,13 +146,16 @@ p.init(); } + // bottom.up(new Event(Event.SET_LOCAL_ADDRESS, local_addr)); + protStack[0].down(new Event(Event.SET_LOCAL_ADDRESS, local_addr)); + for(int i=0; i < protStack.length; i++) { Protocol p=protStack[i]; p.start(); } // moved event processing to follow stack init (JGRP-843) - bottom.up(new Event(Event.SET_LOCAL_ADDRESS, local_addr)); + if(view != null) { Event view_evt=new Event(Event.VIEW_CHANGE, view); bottom.up(view_evt); @@ -177,12 +176,7 @@ send_thread=null; send_queue.close(false); if(ad != null) { - try { - ad.getTimer().stop(); - } - catch(InterruptedException e) { - e.printStackTrace(); - } + ad.getTimer().stop(); } } @@ -284,7 +278,7 @@ Iterator it = droppedMessages.iterator(); while (it.hasNext()) { - DropMessage d = (DropMessage) it.next() ; + DropMessage d =it.next(); if (d.drop(msg, dest)) return true ; } @@ -357,10 +351,14 @@ class ProtocolAdapter extends TP { ProtocolAdapter() { - timer=new TimeScheduler(); + timer=new DefaultTimeScheduler(); } - public TimeScheduler getTimer() { + public boolean supportsMulticasting() { + return false; + } + + public TimeScheduler getTimer() { return timer; } @@ -372,23 +370,21 @@ return "ProtocolAdapter"; } - public void sendToAllMembers(byte[] data, int offset, int length) throws Exception { + public void sendMulticast(byte[] data, int offset, int length) throws Exception { } - public void sendToSingleMember(Address dest, byte[] data, int offset, int length) throws Exception { + public void sendUnicast(PhysicalAddress dest, byte[] data, int offset, int length) throws Exception { } public String getInfo() { return null; } - public void postUnmarshalling(Message msg, Address dest, Address src, boolean multicast) { - } + protected PhysicalAddress getPhysicalAddress() { + throw new UnsupportedOperationException("not implemented"); + } - public void postUnmarshallingList(Message msg, Address dest, boolean multicast) { - } - - public void init() throws Exception { + public void init() throws Exception { super.init(); } @@ -407,7 +403,7 @@ } return null; } - } + } class SendThread extends Thread { @@ -443,7 +439,7 @@ } } else { - s=(Simulator)addrTable.get(dst); + s=addrTable.get(dst); if(s != null) { // inject drop faults here if (!senderDropFault(msg,dst)) { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/applets/DrawApplet.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/applets/DrawApplet.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/applets/DrawApplet.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/applets/DrawApplet.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: DrawApplet.java,v 1.5 2005/05/30 16:14:36 belaban Exp $ package org.jgroups.demos.applets; @@ -15,8 +14,8 @@ import java.util.Random; import java.util.Vector; import org.jgroups.*; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; @@ -68,7 +67,7 @@ channel.connect(groupname); } catch(Exception e) { - log.error(e); + log.error(e.toString()); } receiver=new Thread(this, "DrawThread"); receiver.start(); @@ -152,7 +151,7 @@ leave_button.setForeground(Color.blue); } catch(Exception e) { - log.error(e); + log.error(e.toString()); return; } } @@ -197,7 +196,7 @@ break; } catch(Exception e) { - log.error(e); + log.error(e.toString()); } if(graphics != null) { graphics.setColor(new Color(r, g, b)); @@ -234,7 +233,7 @@ out.reset(); } catch(Exception ex) { - log.error(ex); + log.error(ex.toString()); } } @@ -264,7 +263,7 @@ outstream.flush(); } catch(Exception ex) { - log.error(ex); + log.error(ex.toString()); } } @@ -283,7 +282,7 @@ setVisible(false); } catch(Exception ex) { - log.error(ex); + log.error(ex.toString()); } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/CausalDemo.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/CausalDemo.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/CausalDemo.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/CausalDemo.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,9 +1,8 @@ -// $Id: CausalDemo.java,v 1.7 2007/11/19 16:11:07 belaban Exp $ package org.jgroups.demos; import org.jgroups.*; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import java.io.Serializable; import java.util.Random; @@ -61,7 +60,7 @@ channel.connect("CausalGroup"); System.out.println("View:" + channel.getView()); if (starter) - channel.send(new Message(null, null, new CausalMessage("A", (Address) channel.getView().getMembers().get(0)))); + channel.send(new Message(null, null, new CausalMessage("A", channel.getView().getMembers().get(0)))); } catch (Exception e) @@ -119,12 +118,12 @@ listAlphabet(); //am I chosen to transmit next letter? - if (cm.member.equals(channel.getLocalAddress())) + if (cm.member.equals(channel.getAddress())) { int nextTarget = r.nextInt(members.size()); //chose someone other than yourself - while (nextTarget == members.indexOf(channel.getLocalAddress())) + while (nextTarget == members.indexOf(channel.getAddress())) { nextTarget = r.nextInt(members.size()); } @@ -144,7 +143,7 @@ } catch (Exception e) { - log.error(e); + log.error(e.toString()); } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/ChatCore.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/ChatCore.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/ChatCore.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/ChatCore.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,164 +0,0 @@ -package org.jgroups.demos; - -// prepend 'bridge.' to the next 2 imports if -// you want to use this class w/ JGroups-ME -import java.util.Iterator; -import java.util.LinkedList; - -import org.jgroups.Address; -import org.jgroups.Channel; -import org.jgroups.JChannel; -import org.jgroups.MembershipListener; -import org.jgroups.Message; -import org.jgroups.MessageListener; -import org.jgroups.View; -import org.jgroups.blocks.PullPushAdapter; -import org.jgroups.util.Util; - -public abstract class ChatCore implements MessageListener, MembershipListener { - Channel channel; - - PullPushAdapter ad; - - Thread mainThread; - - static final String group_name = "ChatGroup"; - - String props = null; - - String username = null; - - LinkedList history = new LinkedList(); - - public ChatCore(String props) { - this.props = props; - try { - username = System.getProperty("user.name"); - } catch (Throwable t) { - } - } - - abstract void post(String msg); - - public void link() { - - try { - channel = new JChannel(props); - channel.setOpt(Channel.AUTO_RECONNECT, Boolean.TRUE); - channel.setOpt(Channel.AUTO_GETSTATE, Boolean.TRUE); - System.out.println("Connecting to " + group_name); - channel.connect(group_name); - ad = new PullPushAdapter(channel, this, this); - channel.getState(null, 5000); - } catch (Exception e) { - e.printStackTrace(); - post(e.toString()); - } - - } - - public void dumpHist() { - - if (!history.isEmpty()) { - for (Iterator it = history.iterator(); it.hasNext();) { - String s = (String) it.next(); - post(s + "\n"); - System.err.print(s + "\n"); - } - } - } - - /* -------------------- Interface MessageListener ------------------- */ - - public void receive(Message msg) { - String o; - - try { - o = new String(msg.getBuffer()); - System.err.print(o + " [" + msg.getSrc() + "]\n"); - post(o + " [" + msg.getSrc() + "]\n"); - history.add(o); - } catch (Exception e) { - post("Chat.receive(): " + e); - System.err.print("Chat.receive(): " + e); - } - } - - public byte[] getState() { - try { - return Util.objectToByteBuffer(history); - } catch (Exception e) { - return null; - } - } - - public void setState(byte[] state) { - try { - history = (LinkedList) Util.objectFromByteBuffer(state); - } catch (Exception e) { - e.printStackTrace(); - } - } - - /* ----------------- End of Interface MessageListener --------------- */ - - /* ------------------- Interface MembershipListener ----------------- */ - - public void viewAccepted(View new_view) { - post("Received view " + new_view + '\n'); - System.err.print("Received view " + new_view + '\n'); - } - - public void suspect(Address suspected_mbr) { - } - - public void block() { - } - - public void stop() { - System.out.print("Stopping PullPushAdapter"); - ad.stop(); - System.out.println(" -- done"); - } - - public void disconnect() { - System.out.print("Disconnecting the channel"); - channel.disconnect(); - System.out.println(" -- done"); - } - - public void close() { - System.out.print("Closing the channel"); - channel.close(); - System.out.println(" -- done"); - } - - /* --------------- End of Interface MembershipListener -------------- */ - - protected synchronized void handleLeave() { - try { - stop(); - disconnect(); - close(); - } catch (Exception e) { - e.printStackTrace(); - System.err - .print("Failed leaving the group: " + e.toString() + '\n'); - post("Failed leaving the group: " + e.toString() + '\n'); - } - } - - protected void handleSend(String txt) { - String tmp = username + ": " + txt; - try { - // for the sake of jgroups-me compatibility we stick with - // byte buffers and Streamable *only* (not Serializable) - Message msg = new Message(null, null, tmp.getBytes()); - channel.send(msg); - } catch (Exception e) { - System.err.print("Failed sending message: " + e.toString() + '\n'); - post("Failed sending message: " + e.toString() + '\n'); - } - } - -} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/Chat.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/Chat.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/Chat.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/Chat.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,152 +0,0 @@ -package org.jgroups.demos; - -import java.awt.Frame; -import java.awt.Label; -import java.awt.Rectangle; -import java.awt.TextArea; -import java.awt.TextField; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.WindowEvent; -import java.awt.event.WindowListener; - -import javax.swing.JButton; - -public class Chat extends ChatCore implements MouseListener, WindowListener { - - Frame mainFrame; - - TextArea txtArea; - - TextField txtField; - - Label csLabel; - - JButton leaveButton; - - JButton sendButton; - - JButton clearButton; - - public Chat(String props) { - super(props); - } - - public static void main(String[] args) { - String props = null; - - for (int i = 0; i < args.length; i++) { - if ("-props".equals(args[i])) { - props = args[++i]; - continue; - } - help(); - return; - } - - Chat instance = new Chat(props); - instance.start(); - } - - void post(String msg) { - txtArea.append(msg); - } - - static void help() { - System.out.println("Chat [-help] [-props ]"); - } - - public void start() { - mainFrame = new Frame(); - mainFrame.setLayout(null); - mainFrame.setSize(600, 507); - mainFrame.addWindowListener(this); - - txtArea = new TextArea(); - txtArea.setBounds(12, 36, 550, 348); - txtArea.setEditable(false); - mainFrame.add(txtArea); - - txtField = new TextField(); - txtField.setBounds(100, 392, 400, 30); - mainFrame.add(txtField); - - csLabel = new Label("Send:"); - csLabel.setBounds(12, 392, 85, 30); - mainFrame.add(csLabel); - - leaveButton = new JButton("Leave"); - leaveButton.setBounds(12, 428, 150, 30); - leaveButton.addMouseListener(this); - mainFrame.add(leaveButton); - - sendButton = new JButton("Send"); - sendButton.setBounds(182, 428, 150, 30); - sendButton.addMouseListener(this); - mainFrame.add(sendButton); - - clearButton = new JButton("Clear"); - clearButton.setBounds(340, 428, 150, 30); - clearButton.addMouseListener(this); - mainFrame.add(clearButton); - - link(); - - mainFrame.pack(); - mainFrame.setLocation(15, 25); - mainFrame.setBounds(new Rectangle(580, 480)); - mainFrame.setVisible(true); - mainFrame.show(); - - dumpHist(); - - } - - public void mouseClicked(MouseEvent e) { - Object obj = e.getSource(); - - if (obj == leaveButton) { - handleLeave(); - System.exit(0); - } else if (obj == sendButton) - handleSend(txtField.getText()); - else if (obj == clearButton) - txtArea.setText(""); - } - - public void mouseEntered(MouseEvent e) { - } - - public void mouseExited(MouseEvent e) { - } - - public void mousePressed(MouseEvent e) { - } - - public void mouseReleased(MouseEvent e) { - } - - public void windowActivated(WindowEvent e) { - } - - public void windowClosed(WindowEvent e) { - } - - public void windowClosing(WindowEvent e) { - handleLeave(); - System.exit(0); - } - - public void windowDeactivated(WindowEvent e) { - } - - public void windowDeiconified(WindowEvent e) { - } - - public void windowIconified(WindowEvent e) { - } - - public void windowOpened(WindowEvent e) { - } - -} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/DistributedHashtableDemo.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/DistributedHashtableDemo.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/DistributedHashtableDemo.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/DistributedHashtableDemo.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,284 +0,0 @@ -// $Id: DistributedHashtableDemo.java,v 1.9 2007/11/19 16:07:54 belaban Exp $ - - -package org.jgroups.demos; - - -import org.jgroups.ChannelException; -import org.jgroups.ChannelFactory; -import org.jgroups.JChannelFactory; -import org.jgroups.blocks.DistributedHashtable; -import org.jgroups.persistence.PersistenceFactory; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.WindowEvent; -import java.awt.event.WindowListener; -import java.util.Enumeration; -import java.util.Map; -import java.util.Vector; - - - - -/** - * Uses the DistributedHashtable building block. The latter subclasses java.util.Hashtable and overrides - * the methods that modify the hashtable (e.g. put()). Those methods are multicast to the group, whereas - * read-only methods such as get() use the local copy. A DistributedHashtable is created given the name - * of a group; all hashtables with the same name find each other and form a group. - * @author Bela Ban - */ -public class DistributedHashtableDemo extends Frame implements WindowListener, ActionListener, - DistributedHashtable.Notification { - static final String groupname="HashDemo"; - DistributedHashtable h=null; - final JButton get=new JButton("Get"); - final JButton set=new JButton("Set"); - final JButton quit=new JButton("Quit"); - final JButton get_all=new JButton("All"); - final JButton delete=new JButton("Delete"); - final JLabel stock=new JLabel("Key"); - final JLabel value=new JLabel("Value"); - final JLabel err_msg=new JLabel("Error"); - final JTextField stock_field=new JTextField(); - final JTextField value_field=new JTextField(); - final java.awt.List listbox=new java.awt.List(); - final Font default_font=new Font("Helvetica", Font.PLAIN,12); - - - - - public DistributedHashtableDemo() { - super(); - addWindowListener(this); - } - - private void showMsg(String msg) { - err_msg.setText(msg); - err_msg.setVisible(true); - } - - private void clearMsg() {err_msg.setVisible(false);} - - - private void removeItem() { - int index=listbox.getSelectedIndex(); - if(index == -1) { - showMsg("No item selected in listbox to be deleted !"); - return; - } - String s=listbox.getSelectedItem(); - String key=s.substring(0, s.indexOf(':', 0)); - if(key != null) - h.remove(key); - } - - private void showAll() { - if(listbox.getItemCount() > 0) - listbox.removeAll(); - if(h.size() == 0) - return; - clearMsg(); - String key; - Float val; - - for(Enumeration en=h.keys(); en.hasMoreElements();) { - key=(String)en.nextElement(); - val=(Float)h.get(key); - if(val == null) - continue; - listbox.add(key + ": " + val.toString()); - } - } - - - - - - public void start(ChannelFactory factory, String props, boolean persist) - throws ChannelException { - h=new DistributedHashtable(groupname, factory, props, persist, 10000); - h.addNotifier(this); - - setLayout(null); - setSize(400, 300); - setFont(default_font); - - stock.setBounds(new Rectangle(10, 30, 60, 30)); - value.setBounds(new Rectangle(10, 60, 60, 30)); - stock_field.setBounds(new Rectangle(100, 30, 100, 30)); - value_field.setBounds(new Rectangle(100, 60, 100, 30)); - listbox.setBounds(new Rectangle(210, 30, 150, 160)); - err_msg.setBounds(new Rectangle(10, 200, 350, 30)); - err_msg.setFont(new Font("Helvetica",Font.ITALIC,12)); - err_msg.setForeground(Color.red); - err_msg.setVisible(false); - get.setBounds(new Rectangle(10, 250, 60, 30)); - set.setBounds(new Rectangle(80, 250, 60, 30)); - quit.setBounds(new Rectangle(150, 250, 60, 30)); - get_all.setBounds(new Rectangle(220, 250, 60, 30)); - delete.setBounds(new Rectangle(290, 250, 80, 30)); - - get.addActionListener(this); - set.addActionListener(this); - quit.addActionListener(this); - get_all.addActionListener(this); - delete.addActionListener(this); - - add(stock); add(value); - add(stock_field); add(value_field); - add(err_msg); - add(get); add(set); add(quit); add(get_all); add(delete); - add(listbox); - setTitle("DistributedHashtable Demo"); - showAll(); - // pack(); - setVisible(true); - - -// new Thread() { -// public void run() { -// System.out.println("-- sleeping"); -// Util.sleep(10000); -// for(int i=0; i < 10; i++) { -// System.out.println("-- put()"); -// h.put("Bela#" + i, new Float(i)); -// } - -// while(true) { -// Util.sleep(500); -// System.out.println(h.get("Bela#1")); -// } - -// } -// }.start(); - - - - } - - - - - public void windowActivated(WindowEvent e) {} - public void windowClosed(WindowEvent e) {} - public void windowClosing(WindowEvent e) {System.exit(0);} - public void windowDeactivated(WindowEvent e) {} - public void windowDeiconified(WindowEvent e) {} - public void windowIconified(WindowEvent e) {} - public void windowOpened(WindowEvent e) {} - - - public void actionPerformed(ActionEvent e) { - String command=e.getActionCommand(); - try { - if(command == "Get") { - String stock_name=stock_field.getText(); - if(stock_name == null || stock_name.length() == 0) { - showMsg("Key is empty !"); - return; - } - showMsg("Looking up value for " + stock_name + ':'); - Float val=(Float)h.get(stock_name); - if(val != null) { - value_field.setText(val.toString()); - clearMsg(); - } - else { - value_field.setText(""); - showMsg("Value for " + stock_name + " not found"); - } - } - else if(command == "Set") { - String stock_name=stock_field.getText(); - String stock_val=value_field.getText(); - if(stock_name == null || stock_val == null || stock_name.length() == 0 || - stock_val.length() == 0) { - showMsg("Both key and value have to be present to create a new entry"); - return; - } - Float val=new Float(stock_val); - h.put(stock_name, val); - showMsg("Key " + stock_name + " set to " + val); - } - else if(command == "All") { - showAll(); - } - else if(command == "Quit") { - setVisible(false); - System.exit(0); - } - else if(command == "Delete") - removeItem(); - else - System.out.println("Unknown action"); - } - catch(Exception ex) { - value_field.setText(""); - showMsg(ex.toString()); - } - } - - - - public void entrySet(Object key, Object value) {showAll();} - - public void entryRemoved(Object key) {showAll();} - - public void viewChange(Vector joined, Vector left) { - System.out.println("New members: " + joined + ", left members: " + left); - } - - public void contentsSet(Map m) { - System.out.println("new contents: " + m); - } - - public void contentsCleared() { - System.out.println("contents cleared"); - } - - - public static void main(String args[]) { - DistributedHashtableDemo client=new DistributedHashtableDemo(); - ChannelFactory factory=new JChannelFactory(); - String arg; - boolean persist=false; - - String props="udp.xml"; - - try { - for(int i=0; i < args.length; i++) { - arg=args[i]; - if("-persist".equals(arg) && i+1]"); - } - -} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/DistributedQueueDemo.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/DistributedQueueDemo.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/DistributedQueueDemo.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/DistributedQueueDemo.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: DistributedQueueDemo.java,v 1.7 2007/07/23 08:28:35 belaban Exp $ package org.jgroups.demos; import org.jgroups.ChannelException; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/DistributedTreeDemo.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/DistributedTreeDemo.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/DistributedTreeDemo.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/DistributedTreeDemo.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: DistributedTreeDemo.java,v 1.10 2008/01/22 10:44:34 belaban Exp $ package org.jgroups.demos; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/Draw2Channels.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/Draw2Channels.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/Draw2Channels.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/Draw2Channels.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: Draw2Channels.java,v 1.12 2007/11/19 16:08:27 belaban Exp $ package org.jgroups.demos; @@ -151,8 +150,8 @@ mainFrame.setTitle(" Draw Demo "); return; } - if(control_channel.getLocalAddress() != null) - title+=control_channel.getLocalAddress(); + if(control_channel.getAddress() != null) + title+=control_channel.getAddress(); title+=" (" + member_size + ") mbrs"; mainFrame.setTitle(title); } @@ -355,7 +354,6 @@ } public void channelShunned() { - System.out.println("received EXIT, waiting for ChannelReconnected callback"); } public void channelReconnected(Address addr) { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/DrawCommand.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/DrawCommand.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/DrawCommand.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/DrawCommand.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: DrawCommand.java,v 1.7 2008/01/22 10:44:34 belaban Exp $ package org.jgroups.demos; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/Draw.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/Draw.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/Draw.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/Draw.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: Draw.java,v 1.56 2008/03/10 07:11:57 belaban Exp $ package org.jgroups.demos; @@ -13,6 +12,7 @@ import java.awt.*; import java.awt.event.*; import java.util.*; +import java.util.List; import java.io.*; @@ -38,28 +38,31 @@ boolean jmx; private boolean use_state=false; private long state_timeout=5000; + private boolean use_unicasts=false; + private final List
      members=new ArrayList
      (); public Draw(String props, boolean no_channel, boolean jmx, boolean use_state, long state_timeout, - boolean use_blocking) throws Exception { + boolean use_blocking, boolean use_unicasts, String name) throws Exception { this.no_channel=no_channel; this.jmx=jmx; this.use_state=use_state; this.state_timeout=state_timeout; + this.use_unicasts=use_unicasts; if(no_channel) return; channel=new JChannel(props); + if(name != null) + channel.setName(name); if(use_blocking) channel.setOpt(Channel.BLOCK, Boolean.TRUE); - channel.setOpt(Channel.AUTO_RECONNECT, Boolean.TRUE); channel.setReceiver(this); channel.addChannelListener(this); } public Draw(Channel channel) throws Exception { this.channel=channel; - channel.setOpt(Channel.AUTO_RECONNECT, Boolean.TRUE); channel.setReceiver(this); channel.addChannelListener(this); } @@ -67,7 +70,6 @@ public Draw(Channel channel, boolean use_state, long state_timeout) throws Exception { this.channel=channel; - channel.setOpt(Channel.AUTO_RECONNECT, Boolean.TRUE); channel.setReceiver(this); channel.addChannelListener(this); this.use_state=use_state; @@ -89,11 +91,13 @@ Draw draw=null; String props=null; boolean no_channel=false; - boolean jmx=false; + boolean jmx=true; boolean use_state=false; boolean use_blocking=false; String group_name=null; long state_timeout=5000; + boolean use_unicasts=false; + String name=null; for(int i=0; i < args.length; i++) { if("-help".equals(args[i])) { @@ -109,7 +113,7 @@ continue; } if("-jmx".equals(args[i])) { - jmx=true; + jmx=Boolean.parseBoolean(args[++i]); continue; } if("-groupname".equals(args[i])) { @@ -132,19 +136,30 @@ System.setProperty("jgroups.bind_addr", args[++i]); continue; } + if("-use_unicasts".equals(args[i])) { + use_unicasts=true; + continue; + } + if("-name".equals(args[i])) { + name=args[++i]; + continue; + } help(); return; } try { - draw=new Draw(props, no_channel, jmx, use_state, state_timeout, use_blocking); + draw=new Draw(props, no_channel, jmx, use_state, state_timeout, use_blocking, use_unicasts, name); if(group_name != null) draw.setGroupName(group_name); draw.go(); } catch(Throwable e) { - e.printStackTrace(); + System.err.println("fatal error: " + e.getLocalizedMessage() + ", cause: "); + Throwable t=e.getCause(); + if(t != null) + t.printStackTrace(System.err); System.exit(0); } } @@ -152,11 +167,12 @@ static void help() { System.out.println("\nDraw [-help] [-no_channel] [-props ]" + - " [-groupname ] [-state] [-use_blocking] [-timeout ] [-bind_addr ]"); + " [-groupname ] [-state] [-use_blocking] [-timeout ] [-use_unicasts] " + + "[-bind_addr ] [-jmx ] [-name ]"); System.out.println("-no_channel: doesn't use JGroups at all, any drawing will be relected on the " + - "whiteboard directly"); + "whiteboard directly"); System.out.println("-props: argument can be an old-style protocol stack specification, or it can be " + - "a URL. In the latter case, the protocol specification will be read from the URL\n"); + "a URL. In the latter case, the protocol specification will be read from the URL\n"); } @@ -168,6 +184,13 @@ } + private void sendToAll(byte[] buf) throws Exception { + for(Address mbr: members) { + Message msg=new Message(mbr, null, buf); + channel.send(msg); + } + } + public void go() throws Exception { if(!no_channel && !use_state) { @@ -215,8 +238,8 @@ mainFrame.setTitle(title); } else { - if(channel.getLocalAddress() != null) - tmp+=channel.getLocalAddress(); + if(channel.getAddress() != null) + tmp+=channel.getAddress(); tmp+=" (" + member_size + ")"; mainFrame.setTitle(tmp); } @@ -231,7 +254,7 @@ public void receive(Message msg) { byte[] buf=msg.getRawBuffer(); if(buf == null) { - System.err.println("[" + channel.getLocalAddress() + "] received null buffer from " + msg.getSrc() + + System.err.println("[" + channel.getAddress() + "] received null buffer from " + msg.getSrc() + ", headers: " + msg.printHeaders()); return; } @@ -257,13 +280,33 @@ } public void viewAccepted(View v) { - if(v instanceof MergeView) - System.out.println("** MergeView=" + v); - else - System.out.println("** View=" + v); member_size=v.size(); if(mainFrame != null) setTitle(); + members.clear(); + members.addAll(v.getMembers()); + + if(v instanceof MergeView) { + System.out.println("** MergeView=" + v); + + // This is an example of a simple merge function, which fetches the state from the coordinator + // on a merge and overwrites all of its own state + if(use_state && !members.isEmpty()) { + Address coord=members.get(0); + Address local_addr=channel.getAddress(); + if(local_addr != null && !local_addr.equals(coord)) { + try { + System.out.println("fetching state from " + coord); + channel.getState(coord, 5000); + } + catch(Exception e) { + e.printStackTrace(); + } + } + } + } + else + System.out.println("** View=" + v); } public void block() { @@ -326,7 +369,10 @@ try { byte[] buf=Util.streamableToByteBuffer(comm); - channel.send(new Message(null, null, buf)); + if(use_unicasts) + sendToAll(buf); + else + channel.send(new Message(null, null, buf)); } catch(Exception ex) { System.err.println(ex); @@ -369,19 +415,7 @@ public void channelConnected(Channel channel) { if(jmx) { - MBeanServer server=Util.getMBeanServer(); - if(server != null) { - try { - JmxConfigurator.registerChannel((JChannel)channel, - server, - "jgroups", - channel.getClusterName(), - true); - } - catch(Exception e) { - e.printStackTrace(); - } - } + Util.registerChannel((JChannel)channel, "jgroups"); } } @@ -404,12 +438,9 @@ } public void channelShunned() { - System.out.println("-- received EXIT, waiting for ChannelReconnected callback"); - setTitle(" Draw Demo - shunned "); } public void channelReconnected(Address addr) { - setTitle(); } @@ -455,7 +486,7 @@ return retval; } - + @SuppressWarnings("unchecked") public void setState(byte[] buf) { synchronized(state) { try { @@ -548,8 +579,10 @@ try { byte[] buf=Util.streamableToByteBuffer(comm); - channel.send(new Message(null, null, buf)); - // Thread.yield(); + if(use_unicasts) + sendToAll(buf); + else + channel.send(new Message(null, null, buf)); } catch(Exception ex) { System.err.println(ex); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/DrawRepl.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/DrawRepl.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/DrawRepl.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/DrawRepl.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: DrawRepl.java,v 1.7 2005/07/17 11:36:42 chrislott Exp $ package org.jgroups.demos; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/ExecutionServiceDemo.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/ExecutionServiceDemo.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/ExecutionServiceDemo.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/ExecutionServiceDemo.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,348 @@ +package org.jgroups.demos; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Queue; +import java.util.Random; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.jgroups.JChannel; +import org.jgroups.blocks.executor.ExecutionCompletionService; +import org.jgroups.blocks.executor.ExecutionRunner; +import org.jgroups.blocks.executor.ExecutionService; +import org.jgroups.jmx.JmxConfigurator; +import org.jgroups.util.Streamable; +import org.jgroups.util.Util; + +public class ExecutionServiceDemo { + protected String props; + protected JChannel ch; + protected ExecutionService execution_service; + protected String name; + protected ExecutionRunner runner; + protected int size; + protected boolean printValues; + protected Random random; + + protected ExecutorService executor; + protected Queue> queue; + + public ExecutionServiceDemo(String props, String name, int size) { + this.props=props; + this.name=name; + queue=new ArrayDeque>(); + executor = Executors.newCachedThreadPool(new ThreadFactory() { + + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, "Consumer-" + + poolNumber.getAndIncrement()); + thread.setDaemon(true); + return thread; + } + AtomicInteger poolNumber = new AtomicInteger(); + }); + this.size=size; + } + + public static void main(String[] args) throws Exception { + String props=null; + String name=null; + String size="1000"; + + + for(int i=0; i < args.length; i++) { + if(args[i].equals("-props")) { + props=args[++i]; + continue; + } + if(args[i].equals("-name")) { + name=args[++i]; + continue; + } + + + help(); + return; + } + + ExecutionServiceDemo demo=new ExecutionServiceDemo(props, name, + Integer.valueOf(size)); + demo.start(); + } + + protected static class ByteBufferStreamable implements Streamable { + + protected ByteBuffer buffer; + + public ByteBufferStreamable() { + + } + + protected ByteBufferStreamable(ByteBuffer buffer) { + this.buffer = buffer; + } + + @Override + public void writeTo(DataOutputStream out) throws IOException { + int size = buffer.limit() - buffer.position(); + out.writeInt(size); + out.write(buffer.array(), buffer.position(), size); + } + + @Override + public void readFrom(DataInputStream in) throws IOException, + IllegalAccessException, InstantiationException { + buffer = ByteBuffer.allocate(in.readInt()); + in.readFully(buffer.array()); + } + + } + + public void start() throws Exception { + ch=new JChannel(props); + if(name != null) + ch.setName(name); + execution_service=new ExecutionService(ch); + runner=new ExecutionRunner(ch); + ch.connect("executing-cluster"); + JmxConfigurator.registerChannel(ch, Util.getMBeanServer(), + "execution-service", ch.getClusterName(), true); + + // Start a consumer + queue.add(executor.submit(runner)); + random = new Random(); + printValues = false; + + try { + loop(); + } + catch(Exception e) { + e.printStackTrace(); + } + finally { + Util.close(ch); + } + } + + public static class SortingByteCallable implements Callable, Streamable { + public SortingByteCallable() { + } + public SortingByteCallable(byte[] bytes, int offset, int size) { + buffer = ByteBuffer.wrap(bytes, offset, size); + } + + @Override + public ByteBufferStreamable call() throws Exception { + Arrays.sort(buffer.array(), buffer.position(), buffer.limit()); + return new ByteBufferStreamable(buffer); + } + + protected ByteBuffer buffer; + + // We copy over as a single array with no offset + @Override + public void writeTo(DataOutputStream out) throws IOException { + Util.writeStreamable(new ByteBufferStreamable(buffer), out); + } + + @Override + public void readFrom(DataInputStream in) throws IOException, + IllegalAccessException, InstantiationException { + buffer = ((ByteBufferStreamable)Util.readStreamable( + ByteBufferStreamable.class, in)).buffer; + } + } + + /** + * Sorts 2 byte arrys into a larger byte array + * + * @author wburns + */ + public static class SortingTwoByteCallable implements Callable, Streamable { + protected ByteBuffer bytes1; + protected ByteBuffer bytes2; + + public SortingTwoByteCallable() { + + } + public SortingTwoByteCallable(ByteBufferStreamable bytes1, ByteBufferStreamable bytes2) { + this.bytes1=bytes1.buffer; + this.bytes2=bytes2.buffer; + } + + @Override + public ByteBufferStreamable call() throws Exception { + ByteBuffer results = ByteBuffer.allocate(bytes1.remaining() + + bytes2.remaining()); + int i = bytes1.position(); + int j = bytes2.position(); + byte[] byteArray1 = bytes1.array(); + byte[] byteArray2 = bytes2.array(); + int byte1Max = bytes1.limit(); + int byte2Max = bytes2.limit(); + while (i < byte1Max && j < byte2Max) { + if (byteArray1[i] < byteArray2[j]) { + results.put(byteArray1[i++]); + } + else { + results.put(byteArray2[j++]); + } + } + if (i < byte1Max) { + results.put(byteArray1, i, byte1Max - i); + } + else if (j < byte2Max) { + results.put(byteArray2, j, byte2Max - j); + } + results.flip(); + return new ByteBufferStreamable(results); + } + + @Override + public void writeTo(DataOutputStream out) throws IOException { + Util.writeStreamable(new ByteBufferStreamable(bytes1), out); + Util.writeStreamable(new ByteBufferStreamable(bytes2), out); + } + + @Override + public void readFrom(DataInputStream in) throws IOException, + IllegalAccessException, InstantiationException { + bytes1 = ((ByteBufferStreamable)Util.readStreamable( + ByteBufferStreamable.class, in)).buffer; + + bytes2 = ((ByteBufferStreamable)Util.readStreamable( + ByteBufferStreamable.class, in)).buffer; + } + } + + protected void loop() throws Exception { + while(ch.isConnected()) { + String line=Util.readStringFromStdin(": "); + if(line.startsWith("quit") || line.startsWith("exit")) + break; + + if(line.startsWith("submit")) { + int randomNumbers = Integer.parseInt(line.substring("submit".length()).trim()); + // Parse numbers and break into parts + byte[] numbers = new byte[randomNumbers]; + + for (int i = 0; i < randomNumbers; ++i) { + numbers[i] = (byte)random.nextInt(256); + } + + if (printValues) + System.out.println("Original Numbers: " + + Arrays.toString(numbers)); + + ExecutionCompletionService completion = + new ExecutionCompletionService(execution_service); + + long beginDistributed = System.nanoTime(); + int chunks = numbers.length / size; + for (int i = 0; i < chunks; ++i) { + completion.submit(new SortingByteCallable(numbers, size * i, size)); + } + + int futureNumber = chunks; + int leftOver = numbers.length % size; + if (leftOver != 0) { + completion.submit(new SortingByteCallable(numbers, numbers.length - leftOver, leftOver)); + futureNumber++; + } + + Future finalValue; + if (futureNumber > 1) { + Future result = null; + while (true) { + result = completion.take(); + if (--futureNumber >= 1) { + Future result2 = completion.take(); + completion.submit(new SortingTwoByteCallable(result.get(), result2.get())); + } + else { + break; + } + } + + finalValue = result; + } + else { + finalValue = completion.take(); + } + + ByteBufferStreamable results = finalValue.get(); + + long totalDistributed = System.nanoTime() - beginDistributed; + if (printValues) { + System.out.println("Sorted values: " + Arrays.toString( + results.buffer.array())); + } + System.out.println("Distributed Sort Took: " + Util.printTime(totalDistributed, TimeUnit.NANOSECONDS)); + + long beginLocal = System.nanoTime(); + Arrays.sort(numbers); + System.out.println(" Local Sort Took: " + Util.printTime((System.nanoTime() - beginLocal), TimeUnit.NANOSECONDS)); + } + else if(line.startsWith("consumer")) { + // Parse stop start and add or remove + if (line.contains("start")) { + queue.add(executor.submit(runner)); + System.out.println("Started Consumer - running " + queue.size() + " consumers"); + } + else if (line.contains("stop")) { + queue.remove().cancel(true); + System.out.println("Stopped Consumer - running " + queue.size() + " consumers"); + } + else { + System.out.println("Consumers Running Locally: " + queue.size()); + } + } + else if(line.startsWith("size")) { + String thresholdSize = line.substring("size".length()).trim(); + if (thresholdSize.length() > 0) { + int size = Integer.parseInt(thresholdSize); + + this.size = size; + System.out.println("Changed sort threshold size to " + size); + } + else { + System.out.println("Threshold Size: " + size); + } + } + else if(line.startsWith("print")) { + printValues = !printValues; + System.out.println("Print Arrays: " + printValues); + } + else if(line.startsWith("view")) + System.out.println("View: " + ch.getView()); + else if(line.startsWith("help")) + help(); + } + } + + + protected static void help() { + System.out.println("\nExecutionServiceDemo [-props properties] [-name name]\n" + + "Default Values:\n\n" + + "One Consumer\n" + + "Threshold size: 1000\n" + + "Print disabled\n\n" + + "Valid commands:\n\n" + + "submit (amount of numbers to generate)\n" + + "consumer (start) | (stop)\n" + + "size (value)\n" + + "print"); + System.out.println("\nExample:\nsubmit 2000000\nconsumer start\nconsumer stop\nsize 1000000\nprint"); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/JmxDemo.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/JmxDemo.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/JmxDemo.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/JmxDemo.java 2011-10-18 11:22:35.000000000 +0000 @@ -11,7 +11,6 @@ /** * Shows how annotations can be used to expose attributes and operations * @author Bela Ban - * @version $Id: JmxDemo.java,v 1.11 2008/03/13 15:21:46 belaban Exp $ */ @MBean public class JmxDemo { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/KeyStoreGenerator.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/KeyStoreGenerator.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/KeyStoreGenerator.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/KeyStoreGenerator.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,7 +1,8 @@ -//$Id: KeyStoreGenerator.java,v 1.4 2008/10/09 13:25:52 vlada Exp $ package org.jgroups.demos; +import org.jgroups.util.Util; + import java.io.FileOutputStream; import java.io.OutputStream; import java.security.KeyStore; @@ -39,10 +40,8 @@ public static void main(String[] args) { - int i=0, j; + int i=0; String arg=null; - ; - boolean specified=false; while(i < args.length && args[i].startsWith("-")) { arg=args[i++]; @@ -66,8 +65,7 @@ else if(arg.equalsIgnoreCase("--storeName")) { if(i < args.length) { - String temp=args[i++]; - keyStoreName=temp; + keyStoreName=args[i++]; } else { System.out.println("No keystore supplied using default of " + keyStoreName); @@ -112,7 +110,7 @@ } finally { try { - stream.close(); + Util.close(stream); } catch(Exception e) { @@ -122,15 +120,9 @@ } public static SecretKey initSymKey() throws Exception { - KeyGenerator keyGen=null; - // generate secret key - - keyGen=KeyGenerator.getInstance(getAlgorithm(symAlg)); - + KeyGenerator keyGen=KeyGenerator.getInstance(getAlgorithm(symAlg)); keyGen.init(keySize); - SecretKey secretKey=keyGen.generateKey(); - - return secretKey; + return keyGen.generateKey(); } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/LockServiceDemo.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/LockServiceDemo.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/LockServiceDemo.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/LockServiceDemo.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,178 @@ +package org.jgroups.demos; + +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + +import org.jgroups.JChannel; +import org.jgroups.blocks.locking.LockNotification; +import org.jgroups.blocks.locking.LockService; +import org.jgroups.blocks.locking.Owner; +import org.jgroups.jmx.JmxConfigurator; +import org.jgroups.util.Util; + +/** + * Demos the LockService + */ +public class LockServiceDemo implements LockNotification { + protected String props; + protected JChannel ch; + protected LockService lock_service; + protected String name; + + public LockServiceDemo(String props, String name) { + this.props=props; + this.name=name; + } + + public void start() throws Exception { + ch=new JChannel(props); + if(name != null) + ch.setName(name); + lock_service=new LockService(ch); + lock_service.addLockListener(this); + ch.connect("lock-cluster"); + JmxConfigurator.registerChannel(ch, Util.getMBeanServer(), "lock-service", ch.getClusterName(), true); + + try { + loop(); + } + catch(Exception e) { + e.printStackTrace(); + } + finally { + Util.close(ch); + } + } + + public void lockCreated(String name) { + } + + public void lockDeleted(String name) { + } + + public void locked(String lock_name, Owner owner) { + System.out.println("\"" + lock_name + "\" locked by " + owner); + } + + public void unlocked(String lock_name, Owner owner) { + System.out.println("\"" + lock_name + "\" unlocked by " + owner); + } + + public void awaiting(String lock_name, Owner owner) { + System.out.println("awaiting \"" + lock_name + "\" by " + owner); + } + + public void awaited(String lock_name, Owner owner) { + System.out.println("awaited \"" + lock_name + "\" by " + owner); + } + + protected void loop() throws Exception { + List lock_names; + while(ch.isConnected()) { + String line=Util.readStringFromStdin(": "); + if(line.startsWith("quit") || line.startsWith("exit")) + break; + + if(line.startsWith("lock")) { + lock_names=parseLockNames(line.substring("lock".length()).trim()); + for(String lock_name: lock_names) { + Lock lock=lock_service.getLock(lock_name); + lock.lock(); + } + } + else if(line.startsWith("trylock")) { + lock_names=parseLockNames(line.substring("trylock".length()).trim()); + + String tmp=lock_names.get(lock_names.size() -1); + Long timeout=new Long(-1); + try { + timeout=Long.parseLong(tmp); + lock_names.remove(lock_names.size() -1); + } + catch(NumberFormatException e) { + } + + for(String lock_name: lock_names) { + Lock lock=lock_service.getLock(lock_name); + boolean rc; + if(timeout.longValue() < 0) + rc=lock.tryLock(); + else + rc=lock.tryLock(timeout.longValue(), TimeUnit.MILLISECONDS); + if(!rc) + System.err.println("Failed locking \"" + lock_name + "\""); + } + } + else if(line.startsWith("unlock")) { + lock_names=parseLockNames(line.substring("unlock".length()).trim()); + for(String lock_name: lock_names) { + if(lock_name.equalsIgnoreCase("all")) { + lock_service.unlockAll(); + break; + } + else { + Lock lock=lock_service.getLock(lock_name); + if(lock != null) + lock.unlock(); + } + } + } + else if(line.startsWith("view")) + System.out.println("View: " + ch.getView()); + else if(line.startsWith("help")) + help(); + printLocks(); + } + } + + protected static List parseLockNames(String line) { + List lock_names=new ArrayList(); + if(line == null || line.length() == 0) + return lock_names; + StringTokenizer tokenizer=new StringTokenizer(line); + while(tokenizer.hasMoreTokens()) + lock_names.add(tokenizer.nextToken()); + return lock_names; + } + + protected void printLocks() { + System.out.println("\n" + lock_service.printLocks()); + } + + + + public static void main(String[] args) throws Exception { + String props=null; + String name=null; + + for(int i=0; i < args.length; i++) { + if(args[i].equals("-props")) { + props=args[++i]; + continue; + } + if(args[i].equals("-name")) { + name=args[++i]; + continue; + } + + help(); + return; + } + + LockServiceDemo demo=new LockServiceDemo(props, name); + demo.start(); + } + + protected static void help() { + System.out.println("\nLockServiceDemo [-props properties] [-name name]\n" + + "Valid commands:\n\n" + + "lock ()+\n" + + "unlock ( | \"ALL\")+\n" + + "trylock ()+ []\n"); + System.out.println("Example:\nlock lock lock2 lock3\nunlock all\ntrylock bela michelle 300"); + } + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/MemcachedServer.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/MemcachedServer.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/MemcachedServer.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/MemcachedServer.java 2011-10-18 11:22:35.000000000 +0000 @@ -12,7 +12,6 @@ /** Server process which listens for memcached requests and forwards them to an instance of PartitionedHashMap. * Uses MemcachedConnector and PartitionedHashMap. * @author Bela Ban - * @version $Id: MemcachedServer.java,v 1.7 2008/09/03 12:38:49 belaban Exp $ */ public class MemcachedServer { private MemcachedConnector connector; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/NotificationBusDemo.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/NotificationBusDemo.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/NotificationBusDemo.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/NotificationBusDemo.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,11 +1,10 @@ -// $Id: NotificationBusDemo.java,v 1.8 2007/11/19 16:08:27 belaban Exp $ package org.jgroups.demos; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.Address; import org.jgroups.blocks.NotificationBus; @@ -54,12 +53,12 @@ bus.sendNotification(line); } catch(Exception e) { - log.error(e); + log.error(e.toString()); } } } catch(Exception ex) { - log.error(ex); + log.error(ex.toString()); } finally { if(bus != null) diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/PartitionedHashMapDemo.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/PartitionedHashMapDemo.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/PartitionedHashMapDemo.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/PartitionedHashMapDemo.java 2011-10-18 11:22:35.000000000 +0000 @@ -9,7 +9,6 @@ /** * @author Bela Ban - * @version $Id: PartitionedHashMapDemo.java,v 1.3 2008/08/26 06:36:48 belaban Exp $ */ public class PartitionedHashMapDemo { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/ProgrammaticChat.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/ProgrammaticChat.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/ProgrammaticChat.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/ProgrammaticChat.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,62 @@ +package org.jgroups.demos; + +import org.jgroups.JChannel; +import org.jgroups.Message; +import org.jgroups.ReceiverAdapter; +import org.jgroups.View; +import org.jgroups.protocols.*; +import org.jgroups.protocols.pbcast.GMS; +import org.jgroups.protocols.pbcast.NAKACK; +import org.jgroups.protocols.pbcast.STABLE; +import org.jgroups.stack.ProtocolStack; +import org.jgroups.util.Util; + +import java.net.InetAddress; + +/** + * @author Bela Ban + */ +public class ProgrammaticChat { + + public static void main(String[] args) throws Exception { + JChannel ch=new JChannel(false); + ProtocolStack stack=new ProtocolStack(); + ch.setProtocolStack(stack); + stack.addProtocol(new UDP().setValue("bind_addr", InetAddress.getByName("192.168.1.5"))) + .addProtocol(new PING()) + .addProtocol(new MERGE2()) + .addProtocol(new FD_SOCK()) + .addProtocol(new FD_ALL().setValue("timeout", 12000).setValue("interval", 3000)) + .addProtocol(new VERIFY_SUSPECT()) + .addProtocol(new BARRIER()) + .addProtocol(new NAKACK()) + .addProtocol(new UNICAST2()) + .addProtocol(new STABLE()) + .addProtocol(new GMS()) + .addProtocol(new UFC()) + .addProtocol(new MFC()) + .addProtocol(new FRAG2()); + stack.init(); + + ch.setReceiver(new ReceiverAdapter() { + public void viewAccepted(View new_view) { + System.out.println("view: " + new_view); + } + + public void receive(Message msg) { + System.out.println("<< " + msg.getObject() + " [" + msg.getSrc() + "]"); + } + }); + + ch.connect("ChatCluster"); + + + for(;;) { + String line=Util.readStringFromStdin(": "); + ch.send(null, null, line); + } + } + +} + + diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/QuoteClient.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/QuoteClient.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/QuoteClient.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/QuoteClient.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: QuoteClient.java,v 1.11 2006/05/03 08:45:19 belaban Exp $ package org.jgroups.demos; @@ -136,7 +135,6 @@ public void actionPerformed(ActionEvent e) { String command=e.getActionCommand(); RspList rsp_list; - Rsp first_rsp; try { if(command.equals("Get")) { @@ -147,7 +145,7 @@ } showMsg("Looking up value for " + stock_name + ':'); rsp_list=disp.callRemoteMethods(null, "getQuote", new Object[]{stock_name}, - new String[]{String.class.getName()}, + new Class[]{String.class}, GroupRequest.GET_ALL, 10000); Float val=null; @@ -190,7 +188,7 @@ listbox.removeAll(); showMsg("Getting all stocks:"); rsp_list=disp.callRemoteMethods(null, "getAllStocks", - (Object[])null, (Class[])null, + null, (Class[])null, GroupRequest.GET_ALL, 5000); System.out.println("rsp_list is " + rsp_list); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/QuoteServer.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/QuoteServer.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/QuoteServer.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/QuoteServer.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: QuoteServer.java,v 1.11 2007/07/05 08:42:04 belaban Exp $ package org.jgroups.demos; @@ -6,8 +5,8 @@ import org.jgroups.*; import org.jgroups.blocks.RpcDispatcher; import org.jgroups.util.Util; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import java.util.Date; import java.util.Enumeration; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/RelayDemo.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/RelayDemo.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/RelayDemo.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/RelayDemo.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,75 @@ +package org.jgroups.demos; + +import org.jgroups.*; +import org.jgroups.util.Util; + +/** Demos RELAY. Create 2 *separate* clusters with RELAY as top protocol. Each RELAY has bridge_props="tcp.xml" (tcp.xml + * needs to be present). Then start 2 instances in the first cluster and 2 instances in the second cluster. They should + * find each other, and typing in a window should send the text to everyone, plus we should get 4 responses. + * @author Bela Ban + */ +public class RelayDemo { + public static void main(String[] args) throws Exception { + String props="udp.xml"; + String name=null; + + for(int i=0; i < args.length; i++) { + if(args[i].equals("-props")) { + props=args[++i]; + continue; + } + if(args[i].equals("-name")) { + name=args[++i]; + continue; + } + System.out.println("RelayDemo [-props props] [-name name]"); + return; + } + + final JChannel ch=new JChannel(props); + if(name != null) + ch.setName(name); + ch.setReceiver(new ReceiverAdapter() { + public void receive(Message msg) { + Address sender=msg.getSrc(); + System.out.println("<< " + msg.getObject() + " from " + sender); + Address dst=msg.getDest(); + if(dst == null || dst.isMulticastAddress()) { + Message rsp=new Message(msg.getSrc(), null, "this is a response"); + try { + ch.send(rsp); + } + catch(Exception e) { + e.printStackTrace(); + } + } + } + + public void viewAccepted(View new_view) { + System.out.println(print(new_view)); + } + }); + + ch.connect("RelayDemo"); + + for(;;) { + String line=Util.readStringFromStdin(": "); + ch.send(null, null, line); + } + } + + + static String print(View view) { + StringBuilder sb=new StringBuilder(); + boolean first=true; + sb.append(view.getClass().getSimpleName() + ": ").append(view.getViewId()).append(": "); + for(Address mbr: view.getMembers()) { + if(first) + first=false; + else + sb.append(", "); + sb.append(mbr); + } + return sb.toString(); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/RelayDemoRpc.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/RelayDemoRpc.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/RelayDemoRpc.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/RelayDemoRpc.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,105 @@ +package org.jgroups.demos; + +import org.jgroups.*; +import org.jgroups.blocks.MethodCall; +import org.jgroups.blocks.Request; +import org.jgroups.blocks.RequestOptions; +import org.jgroups.blocks.RpcDispatcher; +import org.jgroups.util.Rsp; +import org.jgroups.util.RspList; +import org.jgroups.util.Util; + + +/** Demos RELAY. Create 2 *separate* clusters with RELAY as top protocol. Each RELAY has bridge_props="tcp.xml" (tcp.xml + * needs to be present). Then start 2 instances in the first cluster and 2 instances in the second cluster. They should + * find each other, and typing in a window should send the text to everyone, plus we should get 4 responses. + * @author Bela Ban + */ +public class RelayDemoRpc extends ReceiverAdapter { + protected JChannel ch; + protected RpcDispatcher disp; + protected Address local_addr; + protected View view; + + + + public static void main(String[] args) throws Exception { + String props="udp.xml"; + String name=null; + + + for(int i=0; i < args.length; i++) { + if(args[i].equals("-props")) { + props=args[++i]; + continue; + } + if(args[i].equals("-name")) { + name=args[++i]; + continue; + } + System.out.println("RelayDemo [-props props] [-name name]"); + return; + } + RelayDemoRpc demo=new RelayDemoRpc(); + demo.start(props, name); + } + + public void start(String props, String name) throws Exception { + ch=new JChannel(props); + if(name != null) + ch.setName(name); + disp=new RpcDispatcher(ch, null, this, this); + ch.connect("RelayDemo"); + local_addr=ch.getAddress(); + + MethodCall call=new MethodCall(getClass().getMethod("handleMessage", String.class, Address.class)); + for(;;) { + String line=Util.readStringFromStdin(": "); + call.setArgs(new Object[]{line, local_addr}); + if(line.equalsIgnoreCase("unicast")) { + + for(Address dest: view.getMembers()) { + System.out.println("invoking method in " + dest + ": "); + try { + Object rsp=disp.callRemoteMethod(dest, call, new RequestOptions(Request.GET_ALL, 5000)); + System.out.println("rsp from " + dest + ": " + rsp); + } + catch(Throwable throwable) { + throwable.printStackTrace(); + } + } + continue; + } + + RspList rsps=disp.callRemoteMethods(null, call, new RequestOptions(Request.GET_ALL, 5000).setAnycasting(true)); + for(Rsp rsp: rsps.values()) + System.out.println("<< " + rsp.getValue() + " from " + rsp.getSender()); + } + } + + public static String handleMessage(String msg, Address sender) { + System.out.println("<< " + msg + " from " + sender); + return "this is a response"; + } + + + public void viewAccepted(View new_view) { + System.out.println(print(new_view)); + view=new_view; + } + + + static String print(View view) { + StringBuilder sb=new StringBuilder(); + boolean first=true; + sb.append(view.getClass().getSimpleName() + ": ").append(view.getViewId()).append(": "); + for(Address mbr: view.getMembers()) { + if(first) + first=false; + else + sb.append(", "); + sb.append(mbr); + } + return sb.toString(); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/ReplCacheDemo.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/ReplCacheDemo.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/ReplCacheDemo.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/ReplCacheDemo.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,457 @@ + +package org.jgroups.demos; + + +import org.jgroups.View; +import org.jgroups.blocks.Cache; +import org.jgroups.blocks.MembershipListenerAdapter; +import org.jgroups.blocks.ReplCache; +import org.jgroups.jmx.JmxConfigurator; + +import javax.management.MBeanServer; +import javax.swing.*; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableModel; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.lang.management.ManagementFactory; +import java.util.Map; +import java.util.concurrent.ConcurrentMap; + +/** + * GUI demo of ReplCache + * @author Bela Ban + */ +public class ReplCacheDemo extends JPanel implements ActionListener { + private ReplCache cache; + private static final String BASENAME="replcache"; + + private JFrame frame; + private JTabbedPane root_pane=new JTabbedPane(); + private JTable table; + private JTextField key_field=createTextField(null, 10); + private JTextField value_field=createTextField(null, 10); + private JTextField repl_count_field=createTextField("1", 3); + private JTextField timeout_field=createTextField("0", 5); + private JTextField perf_key_prefix=createTextField("key", 5); + private JTextField perf_num_keys=createTextField("1000", 5); + private JTextField perf_size=createTextField("1000", 5); + private JTextField perf_repl_count_field=createTextField("1", 3); + private JTextField perf_timeout_field=createTextField("0", 5); + private JTextArea status=new JTextArea("Status area", 10, 5); + private JLabel num_elements=new JLabel("0 elements"); + private MyTableModel model=null; + + + public void actionPerformed(ActionEvent event) { + String command = event.getActionCommand(); + + if(command.equals("Put")) { + String key=key_field.getText(); + String value=value_field.getText(); + String repl_count=repl_count_field.getText(); + String timeout=timeout_field.getText(); + + if(key == null || value == null) + return; + + if(repl_count == null) + repl_count="1"; + if(timeout == null) + timeout="0"; + + cache.put(key, value, Short.valueOf(repl_count), Long.valueOf(timeout)); + } + else if(command.equals("Remove")) { + int[] rows=table.getSelectedRows(); + if(rows != null) { + for(int row: rows) { + String key=(String)model.getValueAt(row, 0); + if(key != null) + cache.remove(key); + } + } + } + else if(command.equals("Clear")) { + clear(); + } + else if(command.equals("Rebalance")) { + cache.mcastEntries(); + } + else if(command.equals("Reset")) { + status.setText(""); + } + else if(command.equals("Start")) { + startPerfTest(); + } + else if(command.equals("Stop")) { + + } + else if(command.equals("Exit")) { + if(cache != null) + cache.stop(); + frame.dispose(); + System.exit(1); // or can we break out of mainLoop() somehow else ? + } + } + + private void clear() { + cache.clear(); + } + + private void startPerfTest() { + int num_puts=1000; + int size=1000; + short repl_count=1; + long timeout=0; + String key_prefix="key"; + + String tmp=perf_key_prefix.getText(); + if(tmp != null) + key_prefix=tmp; + tmp=perf_num_keys.getText(); + if(tmp != null) + num_puts=Integer.valueOf(tmp); + tmp=perf_size.getText(); + if(tmp != null) + size=Integer.valueOf(tmp); + tmp=perf_repl_count_field.getText(); + if(tmp != null) + repl_count=Short.valueOf(tmp); + tmp=perf_timeout_field.getText(); + if(tmp != null) + timeout=Long.valueOf(tmp); + + long start=System.currentTimeMillis(); + for(int i=0; i < num_puts; i++) { + String key=key_prefix + "-" + i; + String value="val-" + i; + cache.put(key, value, repl_count, timeout); + } + long diff=System.currentTimeMillis() - start; + status.setText("It took " + diff + " ms to insert " + num_puts + " elements"); + } + + + private void start(String props, String cluster_name, + long rpc_timeout, long caching_time, boolean migrate_data, boolean use_l1_cache, + int l1_max_entries, long l1_reaping_interval, + int l2_max_entries, long l2_reaping_interval) throws Exception { + MBeanServer server=ManagementFactory.getPlatformMBeanServer(); + + cache=new ReplCache(props, cluster_name); + cache.setCallTimeout(rpc_timeout); + cache.setCachingTime(caching_time); + cache.setMigrateData(migrate_data); + JmxConfigurator.register(cache, server, BASENAME + ":name=cache"); + JmxConfigurator.register(cache.getL2Cache(), server, BASENAME + ":name=l2-cache"); + + if(use_l1_cache) { + Cache l1_cache=new Cache(); + cache.setL1Cache(l1_cache); + if(l1_reaping_interval > 0) + l1_cache.enableReaping(l1_reaping_interval); + if(l1_max_entries > 0) + l1_cache.setMaxNumberOfEntries(l1_max_entries); + JmxConfigurator.register(cache.getL1Cache(), server, BASENAME + ":name=l1-cache"); + } + + if(l2_max_entries > 0 || l2_reaping_interval > 0) { + Cache> l2_cache=cache.getL2Cache(); + if(l2_max_entries > 0) + l2_cache.setMaxNumberOfEntries(l2_max_entries); + if(l2_reaping_interval > 0) + l2_cache.enableReaping(l2_reaping_interval); + } + + + Runtime.getRuntime().addShutdownHook(new Thread() { + public void run() { + cache.stop(); + } + }); + + cache.start(); + + model=new MyTableModel(); + model.setMap(cache.getL2Cache().getInternalMap()); + cache.addChangeListener(model); + + frame=new JFrame("ReplCacheDemo"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + table=new MyTable(model); + table.setPreferredScrollableViewportSize(new Dimension(500, 200)); + // table.setFillsViewportHeight(true); // JDK 6 specific + table.setShowGrid(false); + table.setFont(table.getFont().deriveFont(Font.BOLD)); + add(new JScrollPane(table)); + + JPanel key=new JPanel(new FlowLayout(FlowLayout.LEFT)); + key.add(new JLabel("Key ")); + key.add(key_field); + add(key); + + JPanel value=new JPanel(new FlowLayout(FlowLayout.LEFT)); + value.add(new JLabel("Value")); + value.add(value_field); + add(value); + + JPanel repl_count=new JPanel(new FlowLayout(FlowLayout.LEFT)); + repl_count.add(new JLabel("Replication count")); + repl_count.add(repl_count_field); + add(repl_count); + + JPanel timeout=new JPanel(new FlowLayout(FlowLayout.LEFT)); + timeout.add(new JLabel("Timeout")); + timeout.add(timeout_field); + add(timeout); + + JPanel buttons=new JPanel(); + JButton put_button=createButton("Put"); + buttons.add(createButton("Put")); + buttons.add(createButton("Remove")); + buttons.add(createButton("Clear")); + buttons.add(createButton("Rebalance")); + buttons.add(createButton("Exit")); + buttons.add(num_elements); + add(buttons); + setOpaque(true); + + root_pane.addTab("Data", this); + JPanel perf_panel=new JPanel(); + perf_panel.setLayout(new BoxLayout(perf_panel, BoxLayout.Y_AXIS)); + perf_panel.setOpaque(true); + root_pane.addTab("Perf test", perf_panel); + + perf_panel.add(status); + status.setForeground(Color.BLUE); + + JPanel prefix=new JPanel(new FlowLayout(FlowLayout.LEFT)); + prefix.add(new JLabel("Key prefix")); + prefix.add(perf_key_prefix); + perf_panel.add(prefix); + + JPanel keys=new JPanel(new FlowLayout(FlowLayout.LEFT)); + keys.add(new JLabel("Number of keys to insert")); + keys.add(perf_num_keys); + perf_panel.add(keys); + + JPanel size=new JPanel(new FlowLayout(FlowLayout.LEFT)); + size.add(new JLabel("Size of each key (bytes)")); + size.add(perf_size); size.add(new JLabel(" (ignored for now)")); + perf_panel.add(size); + + JPanel perf_repl_count=new JPanel(new FlowLayout(FlowLayout.LEFT)); + perf_repl_count.add(new JLabel("Replication count")); + perf_repl_count.add(perf_repl_count_field); + perf_panel.add(perf_repl_count); + + JPanel perf_timeout=new JPanel(new FlowLayout(FlowLayout.LEFT)); + perf_timeout.add(new JLabel("Timeout")); + perf_timeout.add(perf_timeout_field); + perf_panel.add(perf_timeout); + + JPanel perf_buttons=new JPanel(new FlowLayout(FlowLayout.LEFT)); + perf_buttons.add(createButton("Start")); + perf_buttons.add(createButton("Stop")); + perf_buttons.add(createButton("Reset")); + perf_buttons.add(createButton("Exit")); + perf_panel.add(perf_buttons); + + frame.setContentPane(root_pane); + frame.pack(); + frame.getRootPane().setDefaultButton(put_button); + frame.setVisible(true); + setTitle("ReplCacheDemo"); + + cache.addMembershipListener(new MembershipListenerAdapter() { + public void viewAccepted(View new_view) { + setTitle("ReplCacheDemo"); + } + }); + } + + + + private JButton createButton(String text) { + JButton retval=new JButton(text); + retval.addActionListener(this); + return retval; + } + + private static JTextField createTextField(String name, int length) { + JTextField retval=new JTextField(name, length); + retval.addFocusListener(new MyFocusListener(retval)); + return retval; + } + + + + public static void main(String[] args) throws Exception { + String props="udp.xml"; + String cluster_name="replcache-cluster"; + long rpc_timeout=1500L, caching_time=30000L; + boolean migrate_data=true, use_l1_cache=true; + int l1_max_entries=5000, l2_max_entries=-1; + long l1_reaping_interval=-1, l2_reaping_interval=30000L; + + for(int i=0; i < args.length; i++) { + if(args[i].equals("-props")) { + props=args[++i]; + continue; + } + if(args[i].equals("-cluster_name")) { + cluster_name=args[++i]; + continue; + } + if(args[i].equals("-rpc_timeout")) { + rpc_timeout=Long.parseLong(args[++i]); + continue; + } + if(args[i].equals("-caching_time")) { + caching_time=Long.parseLong(args[++i]); + continue; + } + if(args[i].equals("-migrate_data")) { + migrate_data=Boolean.parseBoolean(args[++i]); + continue; + } + if(args[i].equals("-use_l1_cache")) { + use_l1_cache=Boolean.parseBoolean(args[++i]); + continue; + } + if(args[i].equals("-l1_max_entries")) { + l1_max_entries=Integer.parseInt(args[++i]); + continue; + } + if(args[i].equals("-l1_reaping_interval")) { + l1_reaping_interval=Long.parseLong(args[++i]); + continue; + } + if(args[i].equals("-l2_max_entries")) { + l2_max_entries=Integer.parseInt(args[++i]); + continue; + } + if(args[i].equals("-l2_reaping_interval")) { + l2_reaping_interval=Long.parseLong(args[++i]); + continue; + } + + help(); + return; + } + + ReplCacheDemo demo = new ReplCacheDemo(); + demo.start(props, cluster_name, rpc_timeout, caching_time, + migrate_data, use_l1_cache, l1_max_entries, l1_reaping_interval, + l2_max_entries, l2_reaping_interval); + } + + + void setTitle(String title) { + String local_addr=cache != null? cache.getLocalAddressAsString() : null; + int num_nodes=cache != null? cache.getClusterSize() : 0; + frame.setTitle(title + ": " + local_addr + " (" + num_nodes + ")"); + } + + + private static void help() { + System.out.println("ReplCacheServer [-help] [-props ] [-cluster_name ]" + + "[-rpc_timeout ] [-caching_time ] " + + "[-migrate_data ] [-use_l1_cache ] " + + "[-l1_max_entries ] [-l1_reaping_interval ] " + + "[-l2_max_entries ] [-l2_reaping_interval ] "); + } + + + + private static class MyFocusListener implements FocusListener { + private final JTextField field; + + public MyFocusListener(JTextField field) { + this.field=field; + } + + public void focusGained(FocusEvent e) { + String value=field.getText(); + if(value != null && value.length() > 0) { + field.selectAll(); + } + } + + public void focusLost(FocusEvent e) { + } + } + + private static class MyTable extends JTable { + + private MyTable(TableModel dm) { + super(dm); + } + + public boolean getScrollableTracksViewportHeight() { + Container viewport=getParent(); + return viewport instanceof JViewport && getPreferredSize().height < viewport.getHeight(); + } + + } + + private class MyTableModel extends AbstractTableModel implements ReplCache.ChangeListener { + private ConcurrentMap>> map; + private final String[] columnNames = {"Key", "Value", "Replication Count", "Timeout"}; + private static final long serialVersionUID=1314724464389654329L; + + public void setMap(ConcurrentMap>> map) { + this.map=map; + } + + public int getColumnCount() { + return columnNames.length; + } + + public int getRowCount() { + return map.size(); + } + + public String getColumnName(int col) { + return columnNames[col]; + } + + + public Object getValueAt(int row, int col) { + int count=0; + + for(Map.Entry>> entry: map.entrySet()) { + if(count++ >= row) { + K key=entry.getKey(); + Cache.Value> val=entry.getValue(); + ReplCache.Value tmp=val.getValue(); + switch(col) { + case 0: return key; + case 1: + V value=tmp.getVal(); + return value instanceof byte[]? ((byte[])value).length + " bytes" : value; + case 2: return tmp.getReplicationCount(); + case 3: return val.getTimeout(); + default: return "n/a"; + } + } + } + throw new IllegalArgumentException("row=" + row + ", col=" + col); + + } + + public void changed() { + fireTableDataChanged(); + num_elements.setText(cache.getL2Cache().getSize() + " elements"); + } + } + + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/ReplicatedHashMapDemo.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/ReplicatedHashMapDemo.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/ReplicatedHashMapDemo.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/ReplicatedHashMapDemo.java 2011-10-18 11:22:35.000000000 +0000 @@ -24,7 +24,6 @@ * read-only methods such as get() use the local copy. A ReplicatedtHashMap is created given the name * of a group; all hashmaps with the same name find each other and form a group. * @author Bela Ban - * @version $Id: ReplicatedHashMapDemo.java,v 1.1 2007/07/23 09:27:51 belaban Exp $ */ public class ReplicatedHashMapDemo extends Frame implements WindowListener, ActionListener, ReplicatedHashMap.Notification { @@ -224,7 +223,7 @@ public static void main(String args[]) { ReplicatedHashMapDemo client=new ReplicatedHashMapDemo(); - ChannelFactory factory=new JChannelFactory(); + ChannelFactory factory; String arg; boolean persist=false; @@ -252,6 +251,7 @@ return; } try { + factory=new JChannelFactory(props); client.start(factory, props, persist); } catch(Throwable t) { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/ReplicatedTreeDemo.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/ReplicatedTreeDemo.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/ReplicatedTreeDemo.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/ReplicatedTreeDemo.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: ReplicatedTreeDemo.java,v 1.9 2008/01/22 10:44:34 belaban Exp $ package org.jgroups.demos; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/StompChat.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/StompChat.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/StompChat.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/StompChat.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,439 @@ +package org.jgroups.demos; + +import org.jgroups.client.StompConnection; +import org.jgroups.util.Util; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import java.util.List; + + +/** + * Chat client using STOMP to talk to other clients + */ +public class StompChat implements StompConnection.Listener { + JFrame mainFrame; + TextArea txtArea; + JTextField txtField; + final JLabel csLabel=new JLabel("Send: "), status=new JLabel(""); + JButton leaveButton; + JButton sendButton; + JButton clearButton; + final JLabel cluster=new JLabel("Cluster: "), users_label=new JLabel("Users: "); + + private int num_servers=1; + private int num_clients=0; + protected String username=null; + protected final Set users=new HashSet(); + private final List servers=new ArrayList(); + private final Set clients=new HashSet(); + protected StompConnection stomp_client; + + static enum Destination { + messages("/messages"), + client_joined("/client-joined"); + + final String name; + + Destination(String name) { + this.name=name; + } + } + + // ======================== reserved topic ========================== + public static final String MESSAGES = "/messages"; // headers + body + public static final String CLIENT_JOINED = "/client-joined"; // client: 1234-2532-2665 + public static final String CLIENT_LEFT = "/client-left"; // client: 1432-7263-1002 + public static final String CLIENTS = "/clients"; // clients: 355352,3343,2232 + public static final String USER_JOINED = "/user-joined"; // user: Bela + public static final String USER_LEFT = "/user-left"; // user: Bela + public static final String GET_USERS = "/get-users"; // + public static final String USERS = "/users"; // users: Bela, Michelle + + + // reserved keywords in INFO messages + public static final String ENDPOINTS = "endpoints"; + public static final String VIEW = "view"; + public static final String CLIENTS_KW = "clients"; + public static final String DESTINATION = "destination"; + public static final String USER = "user"; + public static final String USERS_KW = "users"; + public static final String CLIENT = "client"; + + + + + public StompChat(String host, int port, String user) { + stomp_client=new StompConnection(host + ":" + port); + stomp_client.addListener(this); + + username=user; + try { + if(username == null) + username=System.getProperty("user.name"); + } + catch(Throwable t) { + } + } + + + public static void main(String[] args) throws Exception { + String host="localhost"; + int port=8787; + String user=null; + + for(int i=0; i < args.length; i++) { + if(args[i].equals("-host") || args[i].equals("-h")) { + host=args[++i]; + continue; + } + if(args[i].equals("-port") || args[i].equals("-p")) { + port=Integer.parseInt(args[++i]); + continue; + } + if(args[i].equals("-user") || args[i].equals("-name")) { + user=args[++i]; + continue; + } + help(); + return; + } + + StompChat instance=new StompChat(host, port, user); + instance.start(); + } + + void showMessage(String msg) { + txtArea.append(msg + "\n"); + } + + void userJoined(String name) { + users.add(name); + showStatus(name + " joined the chat"); + users_label.setText("Users: " + users); + } + + void userLeft(String name) { + users.remove(name); + showStatus(name + " left the chat"); + users_label.setText("Users: " + users); + } + + void newView(String view) { + cluster.setText("Cluster: " + view); + } + + void usersReceived(Collection users) { + this.users.addAll(users); + users_label.setText("Users: " + this.users); + } + + static void help() { + System.out.println("Chat [-help] [-host ] [-port ] [-user ]"); + } + + public void start() throws Exception { + mainFrame=new JFrame("Chat demo"); + mainFrame.setPreferredSize(new Dimension(600,600)); + mainFrame.setBackground(Color.white); + mainFrame.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + stomp_client.send(USER_LEFT, USER, username); + stomp_client.send(CLIENT_LEFT, CLIENT, username); + stomp_client.disconnect(); + System.exit(0); + } + }); + + connect(); + + Box main_box=Box.createVerticalBox(); + main_box.setBackground(Color.white); + Box input=Box.createHorizontalBox(); // input field + Box buttons=Box.createHorizontalBox(); // for all the buttons + mainFrame.add(main_box); + + main_box.add(Box.createVerticalStrut(10)); + main_box.add(cluster); + cluster.setAlignmentX(Component.LEFT_ALIGNMENT); + main_box.add(Box.createVerticalStrut(10)); + + main_box.add(Box.createVerticalStrut(10)); + main_box.add(users_label); + main_box.add(Box.createVerticalStrut(10)); + + txtArea=new TextArea(); + txtArea.setPreferredSize(new Dimension(550, 500)); + txtArea.setEditable(false); + txtArea.setBackground(Color.white); + main_box.add(txtArea); + + main_box.add(Box.createVerticalStrut(10)); + main_box.add(input); + main_box.add(Box.createVerticalStrut(10)); + main_box.add(buttons); + + csLabel.setPreferredSize(new Dimension(85, 30)); + input.add(csLabel); + + txtField=new JTextField(); + txtField.setPreferredSize(new Dimension(200, 30)); + txtField.setBackground(Color.white); + input.add(txtField); + + + leaveButton=new JButton("Leave"); + leaveButton.setPreferredSize(new Dimension(150, 30)); + buttons.add(leaveButton); + leaveButton.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + stomp_client.send(USER_LEFT, USER, username); + stomp_client.send(CLIENT_LEFT, CLIENT, username); + stomp_client.disconnect(); + System.exit(0); + } + }); + + sendButton=new JButton("Send"); + sendButton.setPreferredSize(new Dimension(150, 30)); + buttons.add(sendButton); + sendButton.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + send(txtField.getText()); + txtField.selectAll(); + } + }); + + clearButton=new JButton("Clear"); + clearButton.setPreferredSize(new Dimension(150, 30)); + clearButton.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + txtArea.setText(""); + } + }); + buttons.add(clearButton); + + status.setForeground(Color.red); + main_box.add(status); + + mainFrame.pack(); + mainFrame.setLocation(15, 25); + Dimension main_frame_size=mainFrame.getSize(); + txtArea.setPreferredSize(new Dimension((int)(main_frame_size.width * 0.9), (int)(main_frame_size.height * 0.8))); + mainFrame.setVisible(true); + txtField.setFocusable(true); + txtField.requestFocusInWindow(); + txtField.setToolTipText("type and then press enter to send"); + txtField.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + String cmd=e.getActionCommand(); + if(cmd != null && cmd.length() > 0) { + send(txtField.getText()); + txtField.selectAll(); + } + } + }); + + sendGetUsers(); + } + + protected void connect() throws Exception { + stomp_client.connect(); + stomp_client.send(USER_JOINED, USER, username); + stomp_client.subscribe(MESSAGES); + stomp_client.subscribe(CLIENT_JOINED); + stomp_client.subscribe(CLIENT_LEFT); + stomp_client.subscribe(CLIENTS); + stomp_client.subscribe(USER_JOINED); + stomp_client.subscribe(USER_LEFT); + stomp_client.subscribe(GET_USERS); + stomp_client.subscribe(USERS); + stomp_client.send(CLIENT_JOINED, CLIENT, username); + stomp_client.send(USER_JOINED, USER, username); + } + + protected void send(String msg) { + try { + String tmp=username + ": " + msg; + byte[] buf=tmp.getBytes(); + stomp_client.send(MESSAGES, buf, 0, buf.length); + } + catch(Exception e) { + System.err.println("Failed sending message: " + e); + } + } + + public void sendGetUsers() { + stomp_client.send(GET_USERS); + } + + protected void showStatus(final String msg) { + new Thread() { + public void run() { + synchronized(status) { + status.setText(msg); + Util.sleep(2000); + status.setText(""); + } + } + }.start(); + } + + + public void onInfo(Map information) { + String view=information.get("view"); + Collection list; + if(view != null) { + list=Util.parseCommaDelimitedStrings(view); + if(list != null) { + num_servers=list.size(); + if(mainFrame != null) + setTitle(); + servers.clear(); + servers.addAll(list); + newView(view); + } + else { + String targets=information.get("endpoints"); + if(targets != null) { + list=Util.parseCommaDelimitedStrings(targets); + if(list != null) { + num_servers=list.size(); + if(mainFrame != null) + setTitle(); + servers.clear(); + servers.addAll(list); + } + } + } + } + + + } + + public void onMessage(Map headers, byte[] buf, int offset, int length) { + String destination=headers.get("destination"); + if(destination == null) + return; + + if(destination.equals(MESSAGES)) { + showMessage(new String(buf, offset, length)); + return; + } + + + if(destination.equals(CLIENT_JOINED)) { + String new_client=headers.get(CLIENT); + if(new_client != null) { + synchronized(clients) { + if(clients.add(new_client)) { + num_clients=clients.size(); + setTitle(); + } + } + stomp_client.send(CLIENTS, null, 0, 0, CLIENTS_KW, getAllClients()); + } + return; + } + + if(destination.equals(CLIENT_LEFT)) { + String left_client=headers.get(CLIENT); + if(left_client != null) { + synchronized(clients) { + if(clients.remove(left_client)) { + num_clients=clients.size(); + setTitle(); + } + } + } + return; + } + + if(destination.equals(CLIENTS)) { + String all_clients=headers.get(CLIENTS_KW); + if(all_clients != null) { + List list=Util.parseCommaDelimitedStrings(all_clients); + if(list != null) { + synchronized(clients) { + if(clients.addAll(list)) { + num_clients=clients.size(); + setTitle(); + } + } + } + } + return; + } + + if(destination.equals(USER_JOINED)) { + String name=headers.get(USER); + if(name != null) + userJoined(name); + return; + } + + if(destination.equals(USER_LEFT)) { + String name=headers.get(USER); + if(name != null) + userLeft(name); + return; + } + + if(destination.equals(GET_USERS)) { + stomp_client.send(USERS, USERS_KW, usersToStr()); + return; + } + + if(destination.equals(USERS)) { + String tmp=headers.get(USERS_KW); + if(tmp != null) { + List list=Util.parseCommaDelimitedStrings(tmp); + if(list != null) + usersReceived(list); + } + } + } + + private String usersToStr() { + StringBuilder sb=new StringBuilder(); + boolean first=true; + for(String user: users) { + if(first) + first=false; + else + sb.append(","); + sb.append(user); + } + return sb.toString(); + } + + + void setTitle() { + if(mainFrame != null) + mainFrame.setTitle(num_servers + " server(s), " + num_clients + " client(s)"); + } + + int getNumberOfClients() { + synchronized(clients) { + return clients.size(); + } + } + + String getAllClients() { + StringBuilder sb=new StringBuilder(); + boolean first=true; + for(String client: clients) { + if(first) + first=false; + else + sb.append(","); + sb.append(client); + } + + return sb.toString(); + } + + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/StompDraw.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/StompDraw.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/StompDraw.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/StompDraw.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,519 @@ + + +package org.jgroups.demos; + + +import org.jgroups.client.StompConnection; +import org.jgroups.util.Util; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.util.*; +import java.util.List; + + +/** + * Simple STOMP demo client. Use -h and -p to connect to *any* JGroups server (has to have STOMP in the config) + *

      + * @author Bela Ban, Oct 17 2001 + */ +public class StompDraw implements StompConnection.Listener, ActionListener { + private int num_servers=1; + private int num_clients=0; + private JFrame mainFrame=null; + private JPanel sub_panel=null; + private DrawPanel panel=null; + private JButton clear_button, leave_button; + private final Random random=new Random(System.currentTimeMillis()); + private final Font default_font=new Font("Helvetica",Font.PLAIN,12); + private final Color draw_color=selectColor(); + private static final Color background_color=Color.white; + private final List servers=new ArrayList(); + private final Set clients=new HashSet(); + + protected StompConnection stomp_client; + protected static final String draw_dest="/topics/draw-demo"; + protected static final String clients_dest="/topics/clients"; + + + public StompDraw(String host, String port) throws Exception { + stomp_client=new StompConnection(host + ":" + port); + stomp_client.addListener(this); + } + + + + public static void main(String[] args) { + StompDraw draw=null; + + String host="localhost", port="8787"; + for(int i=0; i < args.length; i++) { + if("-help".equals(args[i])) { + help(); + return; + } + if("-h".equals(args[i])) { + host=args[++i]; + continue; + } + if("-p".equals(args[i])) { + port=args[++i]; + continue; + } + help(); + return; + } + + try { + draw=new StompDraw(host, port); + draw.go(); + } + catch(Throwable e) { + System.err.println("fatal error: " + e.getLocalizedMessage() + ", cause: "); + Throwable t=e.getCause(); + if(t != null) + t.printStackTrace(System.err); + System.exit(0); + } + } + + + static void help() { + System.out.println("\nDraw [-help] [-no_channel] [-h host] [-port port]"); + } + + + private Color selectColor() { + int red=(Math.abs(random.nextInt()) % 255); + int green=(Math.abs(random.nextInt()) % 255); + int blue=(Math.abs(random.nextInt()) % 255); + return new Color(red, green, blue); + } + + + private void sendToAll(byte[] buf) throws Exception { + if(buf != null) + stomp_client.send(draw_dest, buf, 0, buf.length); + } + + + public void go() throws Exception { + stomp_client.connect(); + stomp_client.subscribe(draw_dest); + stomp_client.subscribe(clients_dest); + + mainFrame=new JFrame(); + mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + panel=new DrawPanel(false); + panel.setBackground(background_color); + sub_panel=new JPanel(); + mainFrame.getContentPane().add("Center", panel); + clear_button=new JButton("Clear"); + clear_button.setFont(default_font); + clear_button.addActionListener(this); + leave_button=new JButton("Leave"); + leave_button.setFont(default_font); + leave_button.addActionListener(this); + sub_panel.add("South", clear_button); + sub_panel.add("South", leave_button); + mainFrame.getContentPane().add("South", sub_panel); + mainFrame.setBackground(background_color); + clear_button.setForeground(Color.blue); + leave_button.setForeground(Color.blue); + mainFrame.pack(); + mainFrame.setLocation(15, 25); + mainFrame.setBounds(new Rectangle(250, 250)); + mainFrame.setVisible(true); + setTitle(); + + String session_id=stomp_client.getSessionId(); + if(session_id != null) + stomp_client.send(clients_dest, null, 0, 0, "client-joined", session_id); + Runtime.getRuntime().addShutdownHook(new Thread() { + public void run() { + StompDraw.this.stop(); + } + }); + } + + + + void setTitle() { + if(mainFrame != null) + mainFrame.setTitle(num_servers + " server(s), " + num_clients + " client(s)"); + } + + int getNumberOfClients() { + synchronized(clients) { + return clients.size(); + } + } + + String getAllClients() { + StringBuilder sb=new StringBuilder(); + boolean first=true; + for(String client: clients) { + if(first) + first=false; + else + sb.append(","); + sb.append(client); + } + + return sb.toString(); + } + + public void onInfo(Map information) { + String view=information.get("view"); + Collection list; + if(view != null) { + list=Util.parseCommaDelimitedStrings(view); + if(list != null) { + num_servers=list.size(); + if(mainFrame != null) + setTitle(); + servers.clear(); + servers.addAll(list); + } + else { + String targets=information.get("endpoints"); + if(targets != null) { + list=Util.parseCommaDelimitedStrings(targets); + if(list != null) { + num_servers=list.size(); + if(mainFrame != null) + setTitle(); + servers.clear(); + servers.addAll(list); + } + } + } + } + } + + public void onMessage(Map headers, byte[] buf, int offset, int length) { + if(buf == null) + return; + String destination=headers.get("destination"); + if(destination != null && destination.equals(clients_dest)) { + String new_client=headers.get("client-joined"); + if(new_client != null) { + synchronized(clients) { + if(clients.add(new_client)) { + num_clients=clients.size(); + setTitle(); + } + } + + stomp_client.send(clients_dest, null, 0, 0, "clients", getAllClients()); + } + + String left_client=headers.get("client-left"); + if(left_client != null) { + synchronized(clients) { + if(clients.remove(left_client)) { + num_clients=clients.size(); + setTitle(); + } + } + } + + String all_clients=headers.get("clients"); + if(all_clients != null) { + List list=Util.parseCommaDelimitedStrings(all_clients); + if(list != null) { + synchronized(clients) { + if(clients.addAll(list)) { + num_clients=clients.size(); + setTitle(); + } + } + } + } + + return; + } + + try { + DrawCommand comm=(DrawCommand)Util.streamableFromByteBuffer(DrawCommand.class, buf, offset, length); + switch(comm.mode) { + case DrawCommand.DRAW: + if(panel != null) + panel.drawPoint(comm); + break; + case DrawCommand.CLEAR: + clearPanel(); + break; + default: + System.err.println("***** received invalid draw command " + comm.mode); + break; + } + } + catch(Exception e) { + e.printStackTrace(); + } + } + + + + + /* --------------- Callbacks --------------- */ + + + + public void clearPanel() { + if(panel != null) + panel.clear(); + } + + public void sendClearPanelMsg() { + DrawCommand comm=new DrawCommand(DrawCommand.CLEAR); + + try { + byte[] buf=Util.streamableToByteBuffer(comm); + sendToAll(buf); + } + catch(Exception ex) { + System.err.println(ex); + } + } + + + public void actionPerformed(ActionEvent e) { + String command=e.getActionCommand(); + if("Clear".equals(command)) { + sendClearPanelMsg(); + } + else if("Leave".equals(command)) { + stop(); + mainFrame.setVisible(false); + mainFrame.dispose(); + } + else + System.out.println("Unknown action"); + } + + + public void stop() { + if(!stomp_client.isConnected()) + return; + String session_id=stomp_client.getSessionId(); + if(session_id != null) { + stomp_client.send(clients_dest, null, 0, 0, "client-left", session_id); + } + stomp_client.disconnect(); + } + + + + + private class DrawPanel extends JPanel implements MouseMotionListener { + final Dimension preferred_size=new Dimension(235, 170); + Image img=null; // for drawing pixels + Dimension d, imgsize=null; + Graphics gr=null; + final Map state; + + + public DrawPanel(boolean use_state) { + if(use_state) + state=new LinkedHashMap(); + else + state=null; + createOffscreenImage(false); + addMouseMotionListener(this); + addComponentListener(new ComponentAdapter() { + public void componentResized(ComponentEvent e) { + if(getWidth() <= 0 || getHeight() <= 0) return; + createOffscreenImage(false); + } + }); + } + + + public byte[] getState() { + byte[] retval=null; + if(state == null) return null; + synchronized(state) { + try { + retval=Util.objectToByteBuffer(state); + } + catch(Exception e) { + e.printStackTrace(); + } + } + return retval; + } + + @SuppressWarnings("unchecked") + public void setState(byte[] buf) { + synchronized(state) { + try { + Map tmp=(Map)Util.objectFromByteBuffer(buf); + state.clear(); + state.putAll(tmp); + System.out.println("received state: " + buf.length + " bytes, " + state.size() + " entries"); + createOffscreenImage(true); + } + catch(Exception e) { + e.printStackTrace(); + } + } + } + + public void writeState(OutputStream outstream) throws IOException { + synchronized(state) { + if(state != null) { + DataOutputStream dos=new DataOutputStream(outstream); + dos.writeInt(state.size()); + Point point; + Color col; + for(Map.Entry entry: state.entrySet()) { + point=entry.getKey(); + col=entry.getValue(); + dos.writeInt(point.x); + dos.writeInt(point.y); + dos.writeInt(col.getRGB()); + } + dos.flush(); + } + } + } + + + public void readState(InputStream instream) throws IOException { + DataInputStream in=new DataInputStream(instream); + Map new_state=new HashMap(); + int num=in.readInt(); + Point point; + Color col; + for(int i=0; i < num; i++) { + point=new Point(in.readInt(), in.readInt()); + col=new Color(in.readInt()); + new_state.put(point, col); + } + + synchronized(state) { + state.clear(); + state.putAll(new_state); + System.out.println("read state: " + state.size() + " entries"); + createOffscreenImage(true); + } + } + + + final void createOffscreenImage(boolean discard_image) { + d=getSize(); + if(discard_image) { + img=null; + imgsize=null; + } + if(img == null || imgsize == null || imgsize.width != d.width || imgsize.height != d.height) { + img=createImage(d.width, d.height); + if(img != null) { + gr=img.getGraphics(); + if(gr != null && state != null) { + drawState(); + } + } + imgsize=d; + } + repaint(); + } + + + /* ---------------------- MouseMotionListener interface------------------------- */ + + public void mouseMoved(MouseEvent e) {} + + public void mouseDragged(MouseEvent e) { + int x=e.getX(), y=e.getY(); + DrawCommand comm=new DrawCommand(DrawCommand.DRAW, x, y, + draw_color.getRed(), draw_color.getGreen(), draw_color.getBlue()); + + try { + byte[] buf=Util.streamableToByteBuffer(comm); + sendToAll(buf); + } + catch(Exception ex) { + System.err.println(ex); + } + } + + /* ------------------- End of MouseMotionListener interface --------------------- */ + + + /** + * Adds pixel to queue and calls repaint() whenever we have MAX_ITEMS pixels in the queue + * or when MAX_TIME msecs have elapsed (whichever comes first). The advantage compared to just calling + * repaint() after adding a pixel to the queue is that repaint() can most often draw multiple points + * at the same time. + */ + public void drawPoint(DrawCommand c) { + if(c == null || gr == null) return; + Color col=new Color(c.r, c.g, c.b); + gr.setColor(col); + gr.fillOval(c.x, c.y, 10, 10); + repaint(); + if(state != null) { + synchronized(state) { + state.put(new Point(c.x, c.y), col); + } + } + } + + + + public void clear() { + if(gr == null) return; + gr.clearRect(0, 0, getSize().width, getSize().height); + repaint(); + if(state != null) { + synchronized(state) { + state.clear(); + } + } + } + + + + + + /** Draw the entire panel from the state */ + public void drawState() { + // clear(); + Map.Entry entry; + Point pt; + Color col; + synchronized(state) { + for(Iterator it=state.entrySet().iterator(); it.hasNext();) { + entry=(Map.Entry)it.next(); + pt=(Point)entry.getKey(); + col=(Color)entry.getValue(); + gr.setColor(col); + gr.fillOval(pt.x, pt.y, 10, 10); + + } + } + repaint(); + } + + + public Dimension getPreferredSize() { + return preferred_size; + } + + + public void paintComponent(Graphics g) { + super.paintComponent(g); + if(img != null) { + g.drawImage(img, 0, 0, null); + } + } + + } + +} + diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/Topology.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/Topology.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/Topology.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/Topology.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: Topology.java,v 1.8 2007/11/19 16:08:27 belaban Exp $ package org.jgroups.demos; @@ -210,7 +209,7 @@ channel=new JChannel(props); channel.connect(channel_name); new PullPushAdapter(channel, this); - my_addr=channel.getLocalAddress(); + my_addr=channel.getAddress(); if(my_addr != null) setTitle(my_addr.toString()); pack(); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/TotalOrder.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/TotalOrder.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/TotalOrder.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/TotalOrder.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: TotalOrder.java,v 1.14 2007/11/19 16:17:45 belaban Exp $ package org.jgroups.demos; @@ -311,7 +310,6 @@ void startReceiver() { if(receiver == null) { receiver=new ReceiverThread(); - receiver.setPriority(Thread.MAX_PRIORITY); receiver.start(); } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/TotalTokenDemo.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/TotalTokenDemo.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/TotalTokenDemo.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/TotalTokenDemo.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -//$Id: TotalTokenDemo.java,v 1.11 2007/11/19 16:19:00 belaban Exp $ package org.jgroups.demos; @@ -37,7 +36,7 @@ * *@author Vladimir Blagojevic vladimir@cs.yorku.ca *@author Ivan Bilenjkij ivan@ibossa.com - *@version $Revision: 1.11 $ + *@version $Revision: 1.13 $ * *@see org.jgroups.protocols.TOTAL_TOKEN * @@ -114,7 +113,7 @@ } receiverThread = new ReceiverThread(); receiverThread.start(); - Address a = channel.getLocalAddress(); + Address a = channel.getAddress(); if(a != null) setTitle(a.toString()); else setTitle("Not connected"); control.connected(); @@ -220,19 +219,13 @@ if (tmp instanceof View) { View vw = (View) tmp; - control.viewNumber.setText("" + vw.getVid().getId()); - control.numMessagesInLastView.setText("" + counter); + control.viewNumber.setText(String.valueOf(vw.getVid().getId())); + control.numMessagesInLastView.setText(String.valueOf(counter)); counter = 0; v.clear(); continue; } - if (tmp instanceof ExitEvent) - { - System.out.println("received EXIT"); - break; - } - if (!(tmp instanceof Message)) continue; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/ViewDemo.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/ViewDemo.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/ViewDemo.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/ViewDemo.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: ViewDemo.java,v 1.15 2007/12/29 16:39:24 belaban Exp $ package org.jgroups.demos; @@ -6,9 +5,6 @@ import org.jgroups.*; import org.jgroups.util.Util; -import java.util.HashMap; -import java.util.Map; - /** * Demos the reception of views using a PullPushAdapter. Just start a number of members, and kill them @@ -32,18 +28,10 @@ - public void start(String props, boolean use_additional_data) throws Exception { + public void start(String props) throws Exception { channel=new JChannel(props); channel.setReceiver(this); - channel.setOpt(Channel.AUTO_RECONNECT, Boolean.TRUE); - - if(use_additional_data) { - Map m=new HashMap(); - m.put("additional_data", "bela".getBytes()); - channel.down(new Event(Event.CONFIG, m)); - } - channel.connect("ViewDemo"); while(true) { @@ -54,7 +42,6 @@ public static void main(String args[]) { ViewDemo t=new ViewDemo(); - boolean use_additional_data=false; String props="udp.xml"; for(int i=0; i < args.length; i++) { @@ -66,10 +53,6 @@ props=args[++i]; continue; } - if("-use_additional_data".equals(args[i])) { - use_additional_data=Boolean.valueOf(args[++i]).booleanValue(); - continue; - } if("-bind_addr".equals(args[i])) { System.setProperty("jgroups.bind_addr", args[++i]); continue; @@ -79,7 +62,7 @@ } try { - t.start(props, use_additional_data); + t.start(props); } catch(Exception e) { e.printStackTrace(); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/wb/GraphPanel.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/wb/GraphPanel.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/wb/GraphPanel.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/wb/GraphPanel.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,11 +1,10 @@ -// $Id: GraphPanel.java,v 1.6 2006/02/16 08:22:25 belaban Exp $ package org.jgroups.demos.wb; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.Address; import org.jgroups.blocks.GroupRequest; import org.jgroups.blocks.MethodCall; @@ -206,7 +205,6 @@ double bestdist = Double.MAX_VALUE, dist; int mod=e.getModifiers(); Node n; - String msg; if((mod & MouseEvent.BUTTON3_MASK) != 0) { @@ -257,7 +255,7 @@ wb.disp.callRemoteMethods(null, call, GroupRequest.GET_ALL, 0); } catch(Exception ex) { - log.error(ex); + log.error(ex.toString()); } pick = null; @@ -284,7 +282,7 @@ wb.disp.callRemoteMethods(null, call, GroupRequest.GET_ALL, 0); } catch(Exception e) { - log.error(e); + log.error(e.toString()); } repaint(); } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/wb/MessageDialog.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/wb/MessageDialog.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/wb/MessageDialog.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/wb/MessageDialog.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: MessageDialog.java,v 1.2 2004/09/23 16:29:34 belaban Exp $ package org.jgroups.demos.wb; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/wb/Node.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/wb/Node.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/wb/Node.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/wb/Node.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: Node.java,v 1.3 2008/01/22 10:44:37 belaban Exp $ package org.jgroups.demos.wb; import org.jgroups.Address; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/wb/SendDialog.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/wb/SendDialog.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/wb/SendDialog.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/wb/SendDialog.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: SendDialog.java,v 1.6 2006/02/16 08:22:36 belaban Exp $ package org.jgroups.demos.wb; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/wb/UserInfoDialog.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/wb/UserInfoDialog.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/wb/UserInfoDialog.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/wb/UserInfoDialog.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: UserInfoDialog.java,v 1.5 2005/05/30 16:14:37 belaban Exp $ package org.jgroups.demos.wb; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/demos/wb/Whiteboard.java libjgroups-java-2.12.2.Final/src/org/jgroups/demos/wb/Whiteboard.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/demos/wb/Whiteboard.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/demos/wb/Whiteboard.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: Whiteboard.java,v 1.7 2008/01/22 10:44:37 belaban Exp $ package org.jgroups.demos.wb; @@ -6,8 +5,8 @@ import org.jgroups.blocks.*; import org.jgroups.blocks.GroupRequest; import org.jgroups.blocks.RpcDispatcher; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import java.applet.Applet; import java.awt.*; @@ -109,7 +108,7 @@ } catch (Exception e) { log.error("Whiteboard.init(): " + e); } - panel.my_addr = channel.getLocalAddress(); + panel.my_addr = channel.getAddress(); UserInfoDialog dlg = new UserInfoDialog(findParent()); @@ -129,7 +128,7 @@ MethodCall call = new MethodCall("removeNode", new Object[] {panel.my_addr}, new String[] {Object.class.getName()}); disp.callRemoteMethods(null, call, GroupRequest.GET_ALL, 0); } catch (Exception e) { - log.error(e); + log.error(e.toString()); } channel.close(); disp = null; @@ -160,7 +159,7 @@ System.exit(0); } } catch (Exception ex) { - log.error(ex); + log.error(ex.toString()); } } else diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/Event.java libjgroups-java-2.12.2.Final/src/org/jgroups/Event.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/Event.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/Event.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,5 +1,3 @@ -// $Id: Event.java,v 1.63 2008/07/30 11:45:52 belaban Exp $ - package org.jgroups; @@ -17,7 +15,8 @@ public static final int SUSPECT = 9; // arg = Address of suspected member public static final int BLOCK = 10; // arg = null (used by FLUSH) public static final int FIND_INITIAL_MBRS = 12; // arg = JoinPromise (or null (merge2)) - public static final int MERGE = 14; // arg = Vector of Objects + public static final int FIND_ALL_VIEWS = 13; // arg = JoinPromise (or null (merge2)) + public static final int MERGE = 14; // arg = Map public static final int TMP_VIEW = 15; // arg = View public static final int BECOME_SERVER = 16; // sent when client has joined group public static final int GET_APPLSTATE = 17; // get state from appl (arg=StateTransferInfo) @@ -27,14 +26,13 @@ public static final int STABLE = 30; // arg = long[] (stable seqnos for mbrs) public static final int GET_DIGEST = 39; // public static final int SET_DIGEST = 41; // arg = Digest - public static final int EXIT = 46; // received when member was forced out of the group + public static final int OVERWRITE_DIGEST = 42; // arg = Digest public static final int UNSUSPECT = 51; // arg = Address (of unsuspected member) public static final int MERGE_DIGEST = 53; // arg = Digest public static final int CONFIG = 56; // arg = Map (config properties) public static final int SUSPEND_STABLE = 65; // arg = Long (max_suspend_time) public static final int RESUME_STABLE = 66; // arg = null - public static final int ENABLE_UNICASTS_TO = 67; // arg = Address (member) - public static final int SUSPEND = 68; // arg = HashMap (used by FLUSH) + public static final int SUSPEND = 68; // arg = List

      (used by FLUSH) public static final int RESUME = 70; // arg = null (used by FLUSH) public static final int STATE_TRANSFER_INPUTSTREAM = 71; // arg=java.io.InputStream subclass public static final int STATE_TRANSFER_OUTPUTSTREAM = 72; // arg=java.io.OutputStream subclass @@ -44,12 +42,22 @@ public static final int CLOSE_BARRIER = 76; // arg = null public static final int OPEN_BARRIER = 77; // arg = null public static final int REBROADCAST = 78; // arg = Digest - public static final int SHUTDOWN = 79; // arg = null (shutdown without closing sockets or cleaning up) public static final int CONNECT_WITH_STATE_TRANSFER = 80; // arg = cluster name (string) - public static final int DISABLE_UNICASTS_TO = 81; // arg = Address (member) - public static final int START_PARTITION = 82; // arg = null; - public static final int STOP_PARTITION = 83; // arg = null; public static final int PREPARE_VIEW = 86; // arg = View + public static final int GET_PHYSICAL_ADDRESS = 87; // arg = Address --> PhysicalAddress + public static final int GET_LOGICAL_PHYSICAL_MAPPINGS = 88; // arg = null --> Map + public static final int SET_PHYSICAL_ADDRESS = 89; // arg = Tuple + public static final int REMOVE_ADDRESS = 90; // arg = Address + public static final int GET_LOCAL_ADDRESS = 91; // arg = null --> UUID (local_addr) + public static final int CONNECT_USE_FLUSH = 92; + public static final int CONNECT_WITH_STATE_TRANSFER_USE_FLUSH = 93; + public static final int SUSPEND_BUT_FAIL = 94; // used in FLUSH testing, no args + public static final int LOCK = 95; // arg=LockInfo + public static final int UNLOCK = 96; // arg=LockInfo + public static final int UNLOCK_ALL = 97; // arg=null + public static final int LOCK_AWAIT = 98; // arg=LockInfo + public static final int LOCK_SIGNAL = 99; // arg=AwaitInfo + public static final int USER_DEFINED = 1000; // arg = @@ -77,6 +85,7 @@ * @param type * @deprecated in order to make an Event immutable */ + @Deprecated public void setType(int type) { throw new IllegalAccessError("setType() has been deprecated, to make Events immutable"); } @@ -85,6 +94,7 @@ return arg; } + @Deprecated public void setArg(Object arg) { throw new IllegalAccessError("setArg() has been deprecated, to make Events immutable"); } @@ -99,26 +109,27 @@ case VIEW_CHANGE: return "VIEW_CHANGE"; case SET_LOCAL_ADDRESS: return "SET_LOCAL_ADDRESS"; case SUSPECT: return "SUSPECT"; - case BLOCK: return "BLOCK"; - case FIND_INITIAL_MBRS: return "FIND_INITIAL_MBRS"; + case BLOCK: return "BLOCK"; + case FIND_INITIAL_MBRS: return "FIND_INITIAL_MBRS"; + case FIND_ALL_VIEWS: return "FIND_ALL_VIEWS"; case TMP_VIEW: return "TMP_VIEW"; case BECOME_SERVER: return "BECOME_SERVER"; case GET_APPLSTATE: return "GET_APPLSTATE"; case GET_STATE: return "GET_STATE"; case GET_STATE_OK: return "GET_STATE_OK"; - case STATE_RECEIVED: return "STATE_RECEIVED"; + case STATE_RECEIVED: return "STATE_RECEIVED"; case STABLE: return "STABLE"; case GET_DIGEST: return "GET_DIGEST"; case SET_DIGEST: return "SET_DIGEST"; - case MERGE: return "MERGE"; // Added by gianlucac@tin.it to support partitions merging in GMS - case EXIT: return "EXIT"; + case OVERWRITE_DIGEST: return "OVERWRITE_DIGEST"; + case MERGE: return "MERGE"; case UNSUSPECT: return "UNSUSPECT"; case MERGE_DIGEST: return "MERGE_DIGEST"; case CONFIG: return "CONFIG"; case SUSPEND_STABLE: return "SUSPEND_STABLE"; case RESUME_STABLE: return "RESUME_STABLE"; - case ENABLE_UNICASTS_TO: return "ENABLE_UNICASTS_TO"; - case SUSPEND: return "SUSPEND"; + case SUSPEND: return "SUSPEND"; + case SUSPEND_BUT_FAIL: return "SUSPEND_BUT_FAIL"; case RESUME: return "RESUME"; case STATE_TRANSFER_INPUTSTREAM: return "STATE_TRANSFER_INPUTSTREAM"; case STATE_TRANSFER_OUTPUTSTREAM:return "STATE_TRANSFER_OUTPUTSTREAM"; @@ -128,14 +139,24 @@ case CLOSE_BARRIER: return "CLOSE_BARRIER"; case OPEN_BARRIER: return "OPEN_BARRIER"; case REBROADCAST: return "REBROADCAST"; - case SHUTDOWN: return "SHUTDOWN"; case CONNECT_WITH_STATE_TRANSFER: return "CONNECT_WITH_STATE_TRANSFER"; - case DISABLE_UNICASTS_TO: return "DISABLE_UNICASTS_TO"; - case START_PARTITION: return "START_PARTITION"; - case STOP_PARTITION: return "STOP_PARTITION"; case PREPARE_VIEW: return "PREPARE_VIEW"; + case GET_PHYSICAL_ADDRESS: return "GET_PHYSICAL_ADDRESS"; + case GET_LOGICAL_PHYSICAL_MAPPINGS: return "GET_LOGICAL_PHYSICAL_MAPPINGS"; + case SET_PHYSICAL_ADDRESS: return "SET_PHYSICAL_ADDRESS"; + case REMOVE_ADDRESS: return "REMOVE_ADDRESS"; + case GET_LOCAL_ADDRESS: return "GET_LOCAL_ADDRESS"; + case CONNECT_USE_FLUSH: return "CONNECT_USE_FLUSH"; + case CONNECT_WITH_STATE_TRANSFER_USE_FLUSH: return "CONNECT_WITH_STATE_TRANSFER_USE_FLUSH"; + case LOCK: return "LOCK"; + case UNLOCK: return "UNLOCK"; + case UNLOCK_ALL: return "UNLOCK_ALL"; + case LOCK_AWAIT: return "LOCK_AWAIT"; + case LOCK_SIGNAL: return "LOCK_SIGNAL"; + case USER_DEFINED: return "USER_DEFINED"; default: return "UNDEFINED(" + t + ")"; + } } @@ -150,4 +171,3 @@ } } - diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/ExitEvent.java libjgroups-java-2.12.2.Final/src/org/jgroups/ExitEvent.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/ExitEvent.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/ExitEvent.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,9 +0,0 @@ -// $Id: ExitEvent.java,v 1.2 2005/07/17 11:38:05 chrislott Exp $ - -package org.jgroups; -/** - * Trivial object that represents an exit event. - */ -public class ExitEvent { - public String toString() {return "ExitEvent";} -} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/ExtendedMembershipListener.java libjgroups-java-2.12.2.Final/src/org/jgroups/ExtendedMembershipListener.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/ExtendedMembershipListener.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/ExtendedMembershipListener.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,13 +2,21 @@ /** * @author Bela Ban - * @version $Id: ExtendedMembershipListener.java,v 1.2 2006/09/27 12:53:22 belaban Exp $ */ public interface ExtendedMembershipListener extends MembershipListener { - /** - * Called after the FLUSH protocol has unblocked previously blocked senders, and messages can be sent again. This - * callback only needs to be implemented if we require a notification of that. - */ - void unblock(); + /** + * Called after the FLUSH protocol has unblocked previously blocked senders, and + * messages can be sent again. This callback only needs to be implemented if we require a + * notification of that. + * + *

      + * Note that during new view installation we provide guarantee that unblock invocation strictly + * follows view installation at some node A belonging to that view . However, some other message + * M may squeeze in between view and unblock callbacks. + * + * For more details see https://jira.jboss.org/jira/browse/JGRP-986 + * + */ + void unblock(); } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/ExtendedMessageListener.java libjgroups-java-2.12.2.Final/src/org/jgroups/ExtendedMessageListener.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/ExtendedMessageListener.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/ExtendedMessageListener.java 2011-10-18 11:22:35.000000000 +0000 @@ -27,7 +27,6 @@ * @see org.jgroups.JChannel#getState(Address, String, long) * @since 2.3 * - * @version $Id: ExtendedMessageListener.java,v 1.4 2006/07/28 07:14:33 belaban Exp $ */ public interface ExtendedMessageListener extends MessageListener { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/ExtendedReceiverAdapter.java libjgroups-java-2.12.2.Final/src/org/jgroups/ExtendedReceiverAdapter.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/ExtendedReceiverAdapter.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/ExtendedReceiverAdapter.java 2011-10-18 11:22:35.000000000 +0000 @@ -7,7 +7,6 @@ /** * @author Bela Ban - * @version $Id: ExtendedReceiverAdapter.java,v 1.6 2006/10/11 14:34:36 belaban Exp $ */ public class ExtendedReceiverAdapter implements ExtendedReceiver { public byte[] getState(String state_id) { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/ExtendedReceiver.java libjgroups-java-2.12.2.Final/src/org/jgroups/ExtendedReceiver.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/ExtendedReceiver.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/ExtendedReceiver.java 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ * Extends Receiver, plus the partial state transfer methods. * This interface will disappear (be merged with Receiver) in 3.0. * @author Bela Ban - * @version $Id: ExtendedReceiver.java,v 1.2 2006/09/27 12:39:14 belaban Exp $ */ public interface ExtendedReceiver extends Receiver, ExtendedMessageListener, ExtendedMembershipListener { } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/GetStateEvent.java libjgroups-java-2.12.2.Final/src/org/jgroups/GetStateEvent.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/GetStateEvent.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/GetStateEvent.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: GetStateEvent.java,v 1.6 2006/03/17 09:04:55 belaban Exp $ package org.jgroups; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/Global.java libjgroups-java-2.12.2.Final/src/org/jgroups/Global.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/Global.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/Global.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,28 +1,39 @@ package org.jgroups; +import org.jgroups.conf.ClassConfigurator; +import org.jgroups.protocols.SCOPE; +import org.jgroups.util.UUID; + /** * Globals used by JGroups packages. * * @author Bela Ban Mar 29, 2004 - * @version $Id: Global.java,v 1.41 2008/10/10 14:53:30 belaban Exp $ */ public class Global { - public static final int BYTE_SIZE = Byte.SIZE / 8; // 1 - public static final int SHORT_SIZE = Short.SIZE / 8; // 2 - public static final int INT_SIZE = Integer.SIZE / 8; // 4 - public static final int LONG_SIZE = Long.SIZE / 8; // 8 + public static final int BYTE_SIZE = Byte.SIZE / 8; // 1 + public static final int SHORT_SIZE = Short.SIZE / 8; // 2 + public static final int INT_SIZE = Integer.SIZE / 8; // 4 + public static final int LONG_SIZE = Long.SIZE / 8; // 8 + public static final int DOUBLE_SIZE = Double.SIZE / 8; // 8; + public static final int FLOAT_SIZE = Float.SIZE / 8; // 4; + + public static final int MAX_DATAGRAM_PACKET_SIZE=1 << 16; public static final Object NULL=new Object(); + public static final short SCOPE_ID=ClassConfigurator.getProtocolId(SCOPE.class); + public static final String IPv4="java.net.preferIPv4Stack"; public static final String IPv6="java.net.preferIPv6Addresses"; + public static final String NON_LOOPBACK_ADDRESS="NON_LOOPBACK_ADDRESS"; + + public static final String BIND_ADDR="jgroups.bind_addr"; public static final String BIND_ADDR_OLD="bind.address"; public static final String BIND_INTERFACE="jgroups.bind_interface"; public static final String IGNORE_BIND_ADDRESS_PROPERTY="jgroups.ignore.bind_addr"; public static final String IGNORE_BIND_ADDRESS_PROPERTY_OLD="ignore.bind.address"; - public static final String MARSHALLING_COMPAT="jgroups.marshalling.compatible"; public static final String TCPPING_INITIAL_HOSTS="jgroups.tcpping.initial_hosts"; @@ -34,10 +45,19 @@ public static final String MPING_MCAST_PORT="jgroups.mping.mcast_port"; public static final String MPING_IP_TTL="jgroups.mping.ip_ttl"; + public static final String BPING_BIND_PORT="jgroups.bping.bind_port"; + + public static final String STOMP_BIND_ADDR="jgroups.stomp.bind_addr"; + public static final String STOMP_ENDPOINT_ADDR="jgroups.stomp.endpoint_addr"; + public static final String MAGIC_NUMBER_FILE="jgroups.conf.magic_number_file"; + public static final String PROTOCOL_ID_FILE="jgroups.conf.protocol_id_file"; public static final String RESOLVE_DNS="jgroups.resolve_dns"; + public static final String PRINT_UUIDS="jgroups.print_uuids"; + public static final String UUID_CACHE_MAX_ELEMENTS="jgroups.uuid_cache.max_elements"; + public static final String UUID_CACHE_MAX_AGE="jgroups.uuid_cache.max_age"; - public static final String CHANNEL_LOCAL_ADDR_TIMEOUT="jgroups.channel.local_addr_timeout"; + public static final String IPV6_MCAST_PREFIX="jgroups.ipmcast.prefix"; public static final String TIMER_NUM_THREADS="jgroups.timer.num_threads"; @@ -46,57 +66,95 @@ public static final String MUX_MAX_THREADS="jgroups.mux.max_threads"; public static final String MUX_KEEPALIVE="jgroups.mux.keepalive_time"; + public static final String USE_JDK_LOGGER="jgroups.use.jdk_logger"; // forces use of the JDK logger + public static final String CUSTOM_LOG_FACTORY="jgroups.logging.log_factory_class"; + + public static final long DEFAULT_FIRST_UNICAST_SEQNO = 1; + /** First ID assigned for building blocks (defined in jg-protocols.xml) */ + public static final short BLOCKS_START_ID=200; + public static final String SINGLETON_NAME="singleton_name"; - public static final long THREADPOOL_SHUTDOWN_WAIT_TIME=3000; - public static final long THREAD_SHUTDOWN_WAIT_TIME=300; + public static final long THREADPOOL_SHUTDOWN_WAIT_TIME=3000; + public static final long THREAD_SHUTDOWN_WAIT_TIME=300; public static final String DUMMY="dummy-"; public static final String PREFIX="org.jgroups.protocols."; + + public static final String XML_VALIDATION="jgroups.xml.validation"; // for TestNG public static final String FUNCTIONAL="functional"; + public static final String TIME_SENSITIVE="time-sensitive"; public static final String STACK_DEPENDENT="stack-dependent"; public static final String STACK_INDEPENDENT="stack-independent"; + public static final String GOSSIP_ROUTER="gossip-router"; public static final String FLUSH="flush"; public static final String INITIAL_MCAST_ADDR="INITIAL_MCAST_ADDR"; public static final String INITIAL_MCAST_PORT="INITIAL_MCAST_PORT"; public static final String INITIAL_TCP_PORT="INITIAL_TCP_PORT"; + public static final String UDP_MCAST_SOCK="jgroups.udp.mcast_sock"; + public static final String UDP_UCAST_SOCK="jgroups.udp.unicast_sock"; + public static final String TCP_SRV_SOCK="jgroups.tcp.srv_sock"; + public static final String TCP_SOCK="jgroups.tcp.sock"; + public static final String TUNNEL_UCAST_SOCK="jgroups.tunnel.ucast_sock"; + public static final String MPING_MCAST_SOCK="jgroups.mping.mcast_sock"; + public static final String BPING_SOCK="jgroups.bping.sock"; + public static final String TP_DIAG_MCAST_SOCK="jgroups.tp.diag.mcast_sock"; + public static final String STREAMING_STATE_TRANSFER_SERVER_SOCK="jgroups.streaming_state_transfer.srv_sock"; + public static final String FD_SOCK_SRV_SOCK="jgroups.fd_sock.srv_sock"; + public static final String BSH_SRV_SOCK="jgroups.bsh.srv_sock"; + public static final String STOMP_SRV_SOCK="jgroups.stomp.srv_sock"; + + public static final String CCHM_INITIAL_CAPACITY="cchm.initial_capacity"; + public static final String CCHM_LOAD_FACTOR="cchm.load_factor"; + public static final String CCHM_CONCURRENCY_LEVEL="cchm.concurrency_level"; + public static final int IPV4_SIZE=4; public static final int IPV6_SIZE=16; + + public static final int SMALL_CLUSTER_SIZE=10; + public static final int NORMAL_CLUSTER_SIZE=20; + public static final int BIG_CLUSTER_SIZE=100; + + + public static boolean getPropertyAsBoolean(String property, boolean defaultValue) { - boolean result = defaultValue; - try{ - String tmp = System.getProperty(property); - if(tmp != null) - result = Boolean.parseBoolean(tmp); - }catch(Throwable t){ - } - return result; + boolean result = defaultValue; + try{ + String tmp = System.getProperty(property); + if(tmp != null) + result = Boolean.parseBoolean(tmp); + } + catch(Throwable t) { + } + return result; } public static long getPropertyAsLong(String property, long defaultValue) { - long result = defaultValue; - try{ - String tmp = System.getProperty(property); - if(tmp != null) - result = Long.parseLong(tmp); - }catch(Throwable t){ - } - return result; + long result = defaultValue; + try{ + String tmp = System.getProperty(property); + if(tmp != null) + result = Long.parseLong(tmp); + } + catch(Throwable t){ + } + return result; } public static int getPropertyAsInteger(String property, int defaultValue) { - int result = defaultValue; - try{ - String tmp = System.getProperty(property); - if(tmp != null) - result = Integer.parseInt(tmp); - }catch(Throwable t){ - } - return result; + int result = defaultValue; + try{ + String tmp = System.getProperty(property); + if(tmp != null) + result = Integer.parseInt(tmp); + } + catch(Throwable t){ + } + return result; } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/Header.java libjgroups-java-2.12.2.Final/src/org/jgroups/Header.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/Header.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/Header.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,20 +1,16 @@ -// $Id: Header.java,v 1.10 2007/05/01 10:55:19 belaban Exp $ package org.jgroups; -import java.io.Externalizable; +import org.jgroups.util.Streamable; /** - Abstract base class for all headers to be added to a Message. - @author Bela Ban + * Abstract base class for all headers to be added to a Message. + * @author Bela Ban */ -public abstract class Header implements Externalizable { - public static final int HDR_OVERHEAD=100; // estimated size of a header (used to estimate the size of the entire msg) - +public abstract class Header implements Streamable { public Header() { - } @@ -25,17 +21,8 @@ * or not to fragment the message. Fragmentation itself will be accurate, because the entire message will actually * be serialized into a byte buffer, so we can determine the exact size. */ - public int size() { - return HDR_OVERHEAD; - } + public abstract int size(); -// public void writeTo(DataOutputStream out) throws IOException { -// ; -// } -// -// public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { -// ; -// } public String toString() { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/JChannelFactory.java libjgroups-java-2.12.2.Final/src/org/jgroups/JChannelFactory.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/JChannelFactory.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/JChannelFactory.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,9 +1,8 @@ -// $Id: JChannelFactory.java,v 1.55 2008/05/29 08:22:06 belaban Exp $ package org.jgroups; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.annotations.MBean; import org.jgroups.annotations.ManagedAttribute; import org.jgroups.annotations.ManagedOperation; @@ -30,7 +29,10 @@ * JChannelFactory creates pure Java implementations of the Channel * interface. * See {@link JChannel} for a discussion of channel properties. + * @see JChannelFactory + * @deprecated Might get removed in 3.0. Use your own method of injecting channels */ +@Deprecated @MBean(description="Factory to create channels") public class JChannelFactory implements ChannelFactory { private ProtocolStackConfigurator configurator; @@ -560,7 +562,7 @@ */ String root_name=root.getNodeName(); if(!PROTOCOL_STACKS.equals(root_name.trim().toLowerCase())) { - String error="XML protocol stack configuration does not start with a '' element; " + + String error="XML protocol stack configuration does not start with a '<" + PROTOCOL_STACKS + ">' element; " + "maybe the XML configuration needs to be converted to the new format ?\n" + "use 'java org.jgroups.conf.XmlConfigurator -new_format' to do so"; throw new IOException("invalid XML configuration: " + error); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/JChannel.java libjgroups-java-2.12.2.Final/src/org/jgroups/JChannel.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/JChannel.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/JChannel.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,33 +1,36 @@ - package org.jgroups; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.annotations.MBean; import org.jgroups.annotations.ManagedAttribute; import org.jgroups.annotations.ManagedOperation; +import org.jgroups.blocks.MethodCall; import org.jgroups.conf.ConfiguratorFactory; +import org.jgroups.conf.ProtocolConfiguration; import org.jgroups.conf.ProtocolStackConfigurator; -import org.jgroups.stack.IpAddress; -import org.jgroups.stack.ProtocolStack; -import org.jgroups.stack.StateTransferInfo; -import org.jgroups.util.*; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.protocols.TP; +import org.jgroups.stack.*; +import org.jgroups.util.*; import org.w3c.dom.Element; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; +import java.lang.reflect.Method; +import java.net.DatagramSocket; +import java.net.ServerSocket; import java.net.URL; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Vector; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Exchanger; + /** * JChannel is a pure Java implementation of Channel. * When a JChannel object is instantiated it automatically sets up the @@ -75,7 +78,6 @@ * the construction of the stack will be aborted. * * @author Bela Ban - * @version $Id: JChannel.java,v 1.209 2008/11/28 14:45:16 belaban Exp $ */ @MBean(description="JGroups channel") public class JChannel extends Channel { @@ -83,10 +85,13 @@ /** The default protocol stack used by the default constructor */ public static final String DEFAULT_PROTOCOL_STACK="udp.xml"; - protected String properties=null; - /*the address of this JChannel instance*/ - private Address local_addr=null; + protected Address local_addr=null; + + protected AddressGenerator address_generator=null; + + protected String name=null; + /*the channel (also know as group) name*/ private String cluster_name=null; // group name /*the latest view of the group membership*/ @@ -96,24 +101,9 @@ /*the protocol stack, used to send and receive messages from the protocol stack*/ private ProtocolStack prot_stack=null; - /** Thread responsible for closing a channel and potentially reconnecting to it (e.g., when shunned). */ - protected CloserThread closer=null; - - /** To wait until a local address has been assigned */ - private final Promise

      local_addr_promise=new Promise
      (); - private final Promise state_promise=new Promise(); private final Exchanger applstate_exchanger=new Exchanger(); - - private final Promise flush_unblock_promise=new Promise(); - - /** wait until we have a non-null local_addr */ - private long LOCAL_ADDR_TIMEOUT=30000; //=Long.parseLong(System.getProperty("local_addr.timeout", "30000")); - /*if the states is fetched automatically, this is the default timeout, 5 secs*/ - private static final long GET_STATE_DEFAULT_TIMEOUT=5000; - /*if FLUSH is used channel waits for UNBLOCK event, this is the default timeout, 5 secs*/ - private static final long FLUSH_UNBLOCK_TIMEOUT=5000; /*flag to indicate whether to receive blocks, if this is set to true, receive_views is set to true*/ @ManagedAttribute(description="Flag indicating whether to receive blocks",writable=true) @@ -124,20 +114,11 @@ @ManagedAttribute(description="Flag indicating whether to receive this channel's own messages",writable=true) private boolean receive_local_msgs=true; - /*flag to indicate whether the channel will reconnect (reopen) when the exit message is received*/ - @ManagedAttribute(description="Toggles whether the channel will reconnect after shun",writable=true) - private boolean auto_reconnect=true; - - /*flag t indicate whether the state is supposed to be retrieved after the channel is reconnected - *setting this to true, automatically forces auto_reconnect to true*/ - @ManagedAttribute(description="Toggles whether state should to be retrieved after reconnect",writable=true) - private boolean auto_getstate=false; - /*channel connected flag*/ - protected boolean connected=false; + protected volatile boolean connected=false; /*channel closed flag*/ - protected boolean closed=false; // close() has been called, channel is unusable + protected volatile boolean closed=false; // close() has been called, channel is unusable /** True if a state transfer protocol is available, false otherwise */ private boolean state_transfer_supported=false; // set by CONFIG event from STATE_TRANSFER protocol @@ -151,9 +132,9 @@ */ protected final Map additional_data=new HashMap(); - protected final ConcurrentMap config=new ConcurrentHashMap(); + protected final ConcurrentMap config=Util.createConcurrentMap(16); - protected final Log log=LogFactory.getLog(getClass()); + protected final Log log=LogFactory.getLog(JChannel.class); /** Collect statistics */ @ManagedAttribute(description="Collect channel statistics",writable=true) @@ -165,9 +146,20 @@ - /** Used by subclass to create a JChannel without a protocol stack, don't use as application programmer */ - protected JChannel(boolean no_op) { - ; + /** + * Creates a JChannel without a protocol stack; used for programmatic creation of channel and protocol stack + * @param create_protocol_stack If true, tthe default configuration will be used. If false, no protocol stack + * will be created + */ + public JChannel(boolean create_protocol_stack) { + if(create_protocol_stack) { + try { + init(ConfiguratorFactory.getStackConfigurator(DEFAULT_PROTOCOL_STACK)); + } + catch(ChannelException e) { + throw new RuntimeException(e); + } + } } /** @@ -277,7 +269,7 @@ if (properties == null) properties = DEFAULT_PROTOCOL_STACK; - ProtocolStackConfigurator c=null; + ProtocolStackConfigurator c; try { c=ConfiguratorFactory.getStackConfigurator(properties); @@ -297,37 +289,35 @@ */ public JChannel(JChannel ch) throws ChannelException { init(ch); - auto_reconnect=ch.auto_reconnect; - auto_getstate=ch.auto_getstate; receive_blocks=ch.receive_blocks; receive_local_msgs=ch.receive_local_msgs; - receive_blocks=ch.receive_blocks; } + /** - * Returns the protocol stack. - * Currently used by Debugger. - * Specific to JChannel, therefore - * not visible in Channel + * Returns the protocol stack */ public ProtocolStack getProtocolStack() { return prot_stack; } + public void setProtocolStack(ProtocolStack stack) { + this.prot_stack=stack; + if(prot_stack != null) + prot_stack.setChannel(this); + } + protected Log getLog() { return log; } /** - * Returns the protocol stack configuration in string format. An example of this property is
      + * Returns the protocol stack configuration in string format. An example of this property is
      * "UDP:PING:FD:STABLE:NAKACK:UNICAST:FRAG:FLUSH:GMS:VIEW_ENFORCER:STATE_TRANSFER:QUEUE" */ public String getProperties() { - String retval=prot_stack != null? prot_stack.printProtocolSpec(true) : null; - if(retval != null) - properties=retval; - return properties; + return prot_stack != null? prot_stack.printProtocolSpec(true) : null; } public boolean statsEnabled() { @@ -360,12 +350,13 @@ @ManagedAttribute public int getTimerThreads() { TimeScheduler timer=getTimer(); - return timer != null? timer.getCorePoolSize() : -1; + return timer != null? timer.getMinThreads() : -1; } + @ManagedOperation public String dumpTimerQueue() { TimeScheduler timer=getTimer(); - return timer != null? timer.dumpTaskQueue() : "String denoting the group name. Cannot be null. + * @exception ChannelException The protocol stack cannot be started + * @exception ChannelClosedException The channel is closed and therefore cannot be used any longer. + * A new channel has to be created first. + */ + @ManagedOperation(description="Connects the channel to a group") + public synchronized void connect(String cluster_name, boolean useFlushIfPresent) throws ChannelException { + if (connected) { + if (log.isTraceEnabled()) + log.trace("already connected to " + cluster_name); return; } + setAddress(); startStack(cluster_name); - if(cluster_name != null) { // only connect if we are not a unicast channel + if (cluster_name != null) { // only connect if we are not a unicast channel - Event connect_event=new Event(Event.CONNECT, cluster_name); - Object res=downcall(connect_event); // waits forever until connected (or channel is closed) - if(res != null && res instanceof Exception) { // the JOIN was rejected by the coordinator - stopStack(true, false); - init(); - throw new ChannelException("connect() failed", (Throwable)res); + Event connect_event; + if (useFlushIfPresent) { + connect_event = new Event(Event.CONNECT_USE_FLUSH, cluster_name); + } else { + connect_event = new Event(Event.CONNECT, cluster_name); } - //if FLUSH is used do not return from connect() until UNBLOCK event is received - if(flushSupported()) { - try { - flush_unblock_promise.getResultWithTimeout(FLUSH_UNBLOCK_TIMEOUT); - } - catch (TimeoutException timeout) { - if(log.isWarnEnabled()) - log.warn(local_addr + " waiting on UNBLOCK after connect() timed out"); - } - } + // waits forever until connected (or channel is closed) + Object res = downcall(connect_event); + if (res != null && res instanceof Exception) { + // the JOIN was rejected by the coordinator + stopStack(true, false); + init(); + throw new ChannelException("connect() failed", (Throwable) res); + } } - connected=true; + connected = true; notifyChannelConnected(this); } - /** * Connects this channel to a group and gets a state from a specified state * provider. @@ -457,58 +464,100 @@ Address target, String state_id, long timeout) throws ChannelException { + connect(cluster_name, target, state_id, timeout,true); + } + + + /** + * Connects this channel to a group and gets a state from a specified state + * provider. + *

      + * + * This method essentially invokes + * connect and getState methods successively. + * If FLUSH protocol is in channel's stack definition only one flush is executed for both connecting and + * fetching state rather than two flushes if we invoke connect and getState in succesion. + * + * If the channel is already connected, an error message will be printed to the error log. + * If the channel is closed a ChannelClosed exception will be thrown. + * + * + * @param cluster_name the cluster name to connect to. Cannot be null. + * @param target the state provider. If null state will be fetched from coordinator, unless this channel is coordinator. + * @param state_id the substate id for partial state transfer. If null entire state will be transferred. + * @param timeout the timeout for state transfer. + * + * @exception ChannelException The protocol stack cannot be started + * @exception ChannelException Connecting to cluster was not successful + * @exception ChannelClosedException The channel is closed and therefore cannot be used any longer. + * A new channel has to be created first. + * @exception StateTransferException State transfer was not successful + * + */ + public synchronized void connect(String cluster_name, + Address target, + String state_id, + long timeout, + boolean useFlushIfPresent) throws ChannelException { if(connected) { - if(log.isTraceEnabled()) log.trace("already connected to " + cluster_name); + if(log.isTraceEnabled()) log.trace("already connected to " + this.cluster_name); return; } + setAddress(); startStack(cluster_name); - boolean stateTransferOk=false; - boolean joinSuccessful=false; + boolean stateTransferOk; + boolean joinSuccessful; boolean canFetchState=false; // only connect if we are not a unicast channel - if(cluster_name != null) { + if(cluster_name == null) + return; - try { - Event connect_event=new Event(Event.CONNECT_WITH_STATE_TRANSFER, cluster_name); - Object res=downcall(connect_event); // waits forever until connected (or channel is closed) - joinSuccessful=!(res != null && res instanceof Exception); - if(!joinSuccessful) { - stopStack(true, false); - init(); - throw new ChannelException("connect() failed", (Throwable)res); - } + try { + Event connect_event; + if(useFlushIfPresent) + connect_event=new Event(Event.CONNECT_WITH_STATE_TRANSFER_USE_FLUSH, cluster_name); + else + connect_event=new Event(Event.CONNECT_WITH_STATE_TRANSFER, cluster_name); + + Object res=downcall(connect_event); // waits forever until connected (or channel is closed) + joinSuccessful=!(res != null && res instanceof Exception); + if(!joinSuccessful) { + stopStack(true, false); + init(); + throw new ChannelException("connect() failed", (Throwable)res); + } - connected=true; - notifyChannelConnected(this); - canFetchState=getView() != null && getView().size() > 1; + connected=true; + notifyChannelConnected(this); + canFetchState=getView() != null && getView().size() > 1; - // if I am not the only member in cluster then - if(canFetchState) { - try { - // fetch state from target - stateTransferOk=getState(target, state_id, timeout, false); - if(!stateTransferOk) { - throw new StateTransferException(getLocalAddress() + " could not fetch state " - + state_id - + " from " - + target); - } - } - catch(Exception e) { - throw new StateTransferException(getLocalAddress() + " could not fetch state " - + state_id - + " from " - + target, e); + // if I am not the only member in cluster then + if(canFetchState) { + try { + // fetch state from target + stateTransferOk=getState(target, state_id, timeout, false); + if(!stateTransferOk) { + throw new StateTransferException(getAddress() + " could not fetch state " + + (state_id == null ? "(full)" : state_id) + " from " + + (target == null ? "(all)" : target)); } } - + catch(Exception e) { + throw new StateTransferException(getAddress() + " could not fetch state " + + (state_id == null ? "(full)" : state_id) + " from " + + (target == null ? "(all)" : target), e); + } } - finally { - if(flushSupported() && canFetchState) - stopFlush(); + + } + finally { + if (flushSupported() && useFlushIfPresent){ + //stopFlush if we fetched the state or failed to connect... + if(canFetchState || !connected) + stopFlush(); } } } @@ -526,7 +575,7 @@ *

    1. Notifies the listener, if the listener is available
      *
    */ - @ManagedOperation(description="Disconnects the channel if it is connected") + @ManagedOperation(description="Disconnects the channel if connected") public synchronized void disconnect() { if(closed) return; @@ -555,11 +604,19 @@ _close(true, true); // by default disconnect before closing channel and close mq } - + /** + * Shuts down a channel without disconnecting. To be used by tests only, don't use for application purposes + * @deprecated Use {@link Util#shutdown(Channel)} instead. This method will be removed in 3.0 + */ @ManagedOperation(description="Shuts down the channel without disconnecting") + @Deprecated public synchronized void shutdown() { - down(new Event(Event.SHUTDOWN)); - _close(false, true); // by default disconnect before closing channel and close mq + try { + Util.shutdown(this); + } + catch(Exception e) { + log.error("failed shutting down channel " + getAddress(), e); + } } /** @@ -570,7 +627,9 @@ *
  23. Sets up the protocol stack by calling ProtocolStack.setup *
  24. Sets the closed flag to false * + * @deprecated With the removal of shunning, this method should not be used anymore */ + @Deprecated public synchronized void open() throws ChannelException { if(!closed) throw new ChannelException("channel is already open"); @@ -579,10 +638,11 @@ mq.reset(); String props=getProperties(); + List configs=Configurator.parseConfigurations(props); // new stack is created on open() - bela June 12 2003 - prot_stack=new ProtocolStack(this, props); - prot_stack.setup(); + prot_stack=new ProtocolStack(this); + prot_stack.setup(configs); closed=false; } catch(Exception e) { @@ -633,17 +693,21 @@ return retval; } + public Map dumpStats(String protocol_name, List attrs) { + return prot_stack.dumpStats(protocol_name, attrs); + } + @ManagedOperation public Map dumpStats(String protocol_name) { - return prot_stack.dumpStats(protocol_name); + return prot_stack.dumpStats(protocol_name, null); } protected Map dumpChannelStats() { Map retval=new HashMap(); - retval.put("sent_msgs", new Long(sent_msgs)); - retval.put("sent_bytes", new Long(sent_bytes)); - retval.put("received_msgs", new Long(received_msgs)); - retval.put("received_bytes", new Long(received_bytes)); + retval.put("sent_msgs", sent_msgs); + retval.put("sent_bytes", sent_bytes); + retval.put("received_msgs", received_msgs); + retval.put("received_bytes", received_bytes); return retval; } @@ -686,6 +750,13 @@ send(new Message(dst, src, obj)); } + public void send(Address dst, Address src, byte[] buf) throws ChannelNotConnectedException, ChannelClosedException { + send(new Message(dst, src, buf)); + } + + public void send(Address dst, Address src, byte[] buf, int offset, int length) throws ChannelNotConnectedException, ChannelClosedException { + send(new Message(dst, src, buf, offset, length)); + } /** * Blocking receive method. @@ -730,6 +801,7 @@ * new view if view is received
    * Does the same thing as JChannel.receive but doesn't remove the object from the * receiver queue + * * @deprecated Use a {@link Receiver} instead */ public Object peek(long timeout) throws ChannelNotConnectedException, ChannelClosedException, TimeoutException { @@ -777,21 +849,54 @@ @ManagedAttribute public static String getVersion() { return Version.printDescription(); - } + } + + @Deprecated + public Address getLocalAddress() { + return getAddress(); + } /** - * returns the local address of the channel - * returns null if the channel is closed + * Returns the local address of the channel (null if the channel is closed) */ - public Address getLocalAddress() { + public Address getAddress() { return closed ? null : local_addr; } - @ManagedAttribute(name="LocalAddress") - public String getLocalAddressAsString() { + @ManagedAttribute(name="Address") + public String getAddressAsString() { return local_addr != null? local_addr.toString() : "n/a"; } + @ManagedAttribute(name="Address (UUID)") + public String getAddressAsUUID() { + return local_addr instanceof UUID? ((UUID)local_addr).toStringLong() : null; + } + + public String getName() { + return name; + } + + public String getName(Address member) { + return member != null? UUID.get(member) : null; + } + + /** + * Sets the logical name for the channel. The name will stay associated with this channel for the channel's + * lifetime (until close() is called). This method should be called before calling connect().
    + * @param name + */ + @ManagedAttribute(writable=true, description="The logical name of this channel. Stays with the channel until " + + "the channel is closed") + public void setName(String name) { + if(name != null) { + this.name=name; + if(local_addr != null) { + UUID.add(local_addr, this.name); + } + } + } + /** * returns the name of the channel * if the channel is not connected or if it is closed it will return null @@ -806,6 +911,24 @@ return closed ? null : !connected ? null : cluster_name; } + /** + * Returns the current {@link AddressGenerator}, or null if none is set + * @return + * @since 2.12 + */ + public AddressGenerator getAddressGenerator() { + return address_generator; + } + + /** + * Sets the new {@link AddressGenerator}. New addresses will be generated using the new generator. This + * should not be done while a channel is connected, but before connecting. + * @param address_generator + * @since 2.12 + */ + public void setAddressGenerator(AddressGenerator address_generator) { + this.address_generator=address_generator; + } /** * Sets a channel option. The options can be one of the following: @@ -848,24 +971,17 @@ switch(option) { case VIEW: - if(log.isWarnEnabled()) - log.warn("option VIEW has been deprecated (it is always true now); this option is ignored"); - break; case SUSPECT: - if(log.isWarnEnabled()) - log.warn("option SUSPECT has been deprecated (it is always true now); this option is ignored"); + case GET_STATE_EVENTS: + case AUTO_RECONNECT: + case AUTO_GETSTATE: break; case BLOCK: if(value instanceof Boolean) receive_blocks=((Boolean)value).booleanValue(); else if(log.isErrorEnabled()) log.error("option " + Channel.option2String(option) + - " (" + value + "): value has to be Boolean"); - break; - - case GET_STATE_EVENTS: - if(log.isWarnEnabled()) - log.warn("option GET_STATE_EVENTS has been deprecated (it is always true now); this option is ignored"); + " (" + value + "): value has to be Boolean"); break; case LOCAL: @@ -873,26 +989,7 @@ receive_local_msgs=((Boolean)value).booleanValue(); else if(log.isErrorEnabled()) log.error("option " + Channel.option2String(option) + - " (" + value + "): value has to be Boolean"); - break; - - case AUTO_RECONNECT: - if(value instanceof Boolean) - auto_reconnect=((Boolean)value).booleanValue(); - else - if(log.isErrorEnabled()) log.error("option " + Channel.option2String(option) + - " (" + value + "): value has to be Boolean"); - break; - - case AUTO_GETSTATE: - if(value instanceof Boolean) { - auto_getstate=((Boolean)value).booleanValue(); - if(auto_getstate) - auto_reconnect=true; - } - else - if(log.isErrorEnabled()) log.error("option " + Channel.option2String(option) + - " (" + value + "): value has to be Boolean"); + " (" + value + "): value has to be Boolean"); break; default: @@ -913,13 +1010,13 @@ case VIEW: return Boolean.TRUE; case BLOCK: - return receive_blocks ? Boolean.TRUE : Boolean.FALSE; + return receive_blocks; case SUSPECT: return Boolean.TRUE; case AUTO_RECONNECT: - return auto_reconnect ? Boolean.TRUE : Boolean.FALSE; + return false; case AUTO_GETSTATE: - return auto_getstate ? Boolean.TRUE : Boolean.FALSE; + return false; case GET_STATE_EVENTS: return Boolean.TRUE; case LOCAL: @@ -1075,7 +1172,63 @@ * if flush is used in this channel and cluster could not be * flushed */ - public boolean getState(Address target, String state_id, long timeout,boolean useFlushIfPresent) throws ChannelNotConnectedException, ChannelClosedException { + public boolean getState(Address target, String state_id, long timeout, + boolean useFlushIfPresent) throws ChannelNotConnectedException, + ChannelClosedException { + + Callable flusher = new Callable() { + public Boolean call() throws Exception { + return Util.startFlush(JChannel.this); + } + }; + return getState(target, state_id, timeout, useFlushIfPresent?flusher:null); + } + + /** + * Retrieves a substate (or partial state) indicated by state_id from the target member. + *

    + * + * State transfer is initiated by invoking getState on this channel, state + * receiver, and sending a GET_STATE message to a target member - state + * provider. State provider passes GET_STATE message to application that is + * using the state provider channel which in turn provides an application + * state to a state receiver. Upon successful installation of a state at + * state receiver this method returns true. + * + * + * @param target + * State provider. If null, coordinator is used + * @param state_id + * The ID of the substate. If null, the entire state will be + * transferred + * @param timeout + * the number of milliseconds to wait for the operation to + * complete successfully. 0 waits until the state has been + * received + * @param flushInvoker + * algorithm invoking flush + * + * @see ExtendedMessageListener#getState(OutputStream) + * @see ExtendedMessageListener#setState(InputStream) + * @see MessageListener#getState() + * @see MessageListener#setState(byte[]) + * + * + * @return true if state transfer was successful, false otherwise + * @throws ChannelNotConnectedException + * if channel was not connected at the time state retrieval + * was initiated + * @throws ChannelClosedException + * if channel was closed at the time state retrieval was + * initiated + * @throws IllegalStateException + * if one of state transfer protocols is not present in this + * channel + * @throws IllegalStateException + * if flush is used in this channel and cluster could not be + * flushed + */ + protected boolean getState(Address target, String state_id, long timeout,Callable flushInvoker) throws ChannelNotConnectedException, ChannelClosedException { checkClosedOrNotConnected(); if(!state_transfer_supported) { throw new IllegalStateException("fetching state will fail as state transfer is not supported. " @@ -1090,15 +1243,23 @@ return false; } - boolean initiateFlush = flushSupported() && useFlushIfPresent; + boolean initiateFlush = flushSupported() && flushInvoker!=null; - if(initiateFlush){ - boolean successfulFlush = startFlush(false); - //http://jira.jboss.com/jira/browse/JGRP-759 - if(!successfulFlush){ - throw new IllegalStateException("Could not flush the cluster and proceed with state retrieval"); - } - } + if (initiateFlush) { + boolean successfulFlush = false; + try { + successfulFlush = flushInvoker.call(); + } + catch (Exception e) { + successfulFlush = false; + // http://jira.jboss.com/jira/browse/JGRP-759 + } + finally { + if (!successfulFlush) { + throw new IllegalStateException("Node "+ local_addr+ " could not flush the cluster for state retrieval"); + } + } + } state_promise.reset(); StateTransferInfo state_info=new StateTransferInfo(target, state_id, timeout); @@ -1210,21 +1371,23 @@ */ // not good: we are only connected when we returned from connect() - bela June 22 2007 - // if(connected == false) { - // connected=true; - // } + // Changed: when a channel gets a view of which it is a member then it should be + // connected even if connect() hasn't returned yet ! (bela Noc 2010) + if(connected == false) { + connected=true; + } break; case Event.CONFIG: - Map config=(Map)evt.getArg(); - if(config != null) { - if(config.containsKey("state_transfer")) { - state_transfer_supported=((Boolean)config.get("state_transfer")).booleanValue(); + Map cfg=(Map)evt.getArg(); + if(cfg != null) { + if(cfg.containsKey("state_transfer")) { + state_transfer_supported=((Boolean)cfg.get("state_transfer")).booleanValue(); } - if(config.containsKey("flush_supported")) { - flush_supported=((Boolean)config.get("flush_supported")).booleanValue(); + if(cfg.containsKey("flush_supported")) { + flush_supported=((Boolean)cfg.get("flush_supported")).booleanValue(); } - config.putAll(config); + cfg.putAll(cfg); } break; @@ -1308,13 +1471,8 @@ } break; - case Event.SET_LOCAL_ADDRESS: - local_addr_promise.setResult((Address)evt.getArg()); - break; - - case Event.EXIT: - handleExit(evt); - return null; // no need to pass event up; already done in handleExit() + case Event.GET_LOCAL_ADDRESS: + return local_addr; default: break; @@ -1322,14 +1480,8 @@ // If UpHandler is installed, pass all events to it and return (UpHandler is e.g. a building block) - if(up_handler != null) { - Object ret=up_handler.up(evt); - - if(type == Event.UNBLOCK){ - flush_unblock_promise.setResult(Boolean.TRUE); - } - return ret; - } + if(up_handler != null) + return up_handler.up(evt); switch(type) { case Event.MSG: @@ -1416,7 +1568,7 @@ case Event.BLOCK: if(!receive_blocks) { // discard if client has not set 'receiving blocks' to 'on' - return Boolean.TRUE; + return true; } if(receiver != null) { @@ -1427,7 +1579,7 @@ if(log.isErrorEnabled()) log.error("failed calling block() in receiver", t); } - return Boolean.TRUE; + return true; } break; case Event.UNBLOCK: @@ -1440,9 +1592,7 @@ if(log.isErrorEnabled()) log.error("failed calling unblock() in receiver", t); } - } - //flip promise - flush_unblock_promise.setResult(Boolean.TRUE); + } return null; default: break; @@ -1490,8 +1640,8 @@ additional_data.putAll(m); if(m.containsKey("additional_data")) { byte[] tmp=(byte[])m.get("additional_data"); - if(local_addr instanceof IpAddress) - ((IpAddress)local_addr).setAdditionalData(tmp); + if(local_addr instanceof UUID) + ((UUID)local_addr).setAdditionalData(tmp); } } } @@ -1516,8 +1666,8 @@ additional_data.putAll(m); if(m.containsKey("additional_data")) { byte[] tmp=(byte[])m.get("additional_data"); - if(local_addr instanceof IpAddress) - ((IpAddress)local_addr).setAdditionalData(tmp); + if(local_addr instanceof UUID) + ((UUID)local_addr).setAdditionalData(tmp); } } } @@ -1544,8 +1694,6 @@ if(details) { sb.append("receive_blocks=").append(receive_blocks).append('\n'); sb.append("receive_local_msgs=").append(receive_local_msgs).append('\n'); - sb.append("auto_reconnect=").append(auto_reconnect).append('\n'); - sb.append("auto_getstate=").append(auto_getstate).append('\n'); sb.append("state_transfer_supported=").append(state_transfer_supported).append('\n'); sb.append("props=").append(getProperties()).append('\n'); } @@ -1560,18 +1708,25 @@ protected final void init(ProtocolStackConfigurator configurator) throws ChannelException { if(log.isInfoEnabled()) log.info("JGroups version: " + Version.description); - - // ConfiguratorFactory.substituteVariables(configurator); // replace vars with system props - String tmp=configurator.getProtocolStackString(); - tmp=Util.substituteVariable(tmp); // replace vars with system props - prot_stack=new ProtocolStack(this, tmp); + List configs; try { - prot_stack.setup(); // Setup protocol stack (creates protocol, calls init() on them) - properties=tmp; + configs=configurator.getProtocolStack(); + for(ProtocolConfiguration config: configs) + config.substituteVariables(); // replace vars with system props } - catch(Throwable e) { - throw new ChannelException("unable to setup the protocol stack: " + e.getMessage(), e); + catch(Exception e) { + throw new ChannelException("unable to parse the protocol configuration", e); + } + + synchronized(Channel.class) { + prot_stack=new ProtocolStack(this); + try { + prot_stack.setup(configs); // Setup protocol stack (creates protocol, calls init() on them) + } + catch(Throwable e) { + throw new ChannelException("unable to setup the protocol stack", e); + } } } @@ -1580,13 +1735,15 @@ throw new IllegalArgumentException("channel is null"); if(log.isInfoEnabled()) log.info("JGroups version: " + Version.description); - prot_stack=new ProtocolStack(this, null); - try { - prot_stack.setup(ch.getProtocolStack()); // Setup protocol stack (creates protocol, calls init() on them) - getProperties(); - } - catch(Throwable e) { - throw new ChannelException("unable to setup the protocol stack: " + e.getMessage(), e); + + synchronized(JChannel.class) { + prot_stack=new ProtocolStack(this); + try { + prot_stack.setup(ch.getProtocolStack()); // Setup protocol stack (creates protocol, calls init() on them) + } + catch(Throwable e) { + throw new ChannelException("unable to setup the protocol stack: " + e.getMessage(), e); + } } } @@ -1596,6 +1753,8 @@ * to be ready for new connect() */ private void init() { + if(local_addr != null) + down(new Event(Event.REMOVE_ADDRESS, local_addr)); local_addr=null; cluster_name=null; my_view=null; @@ -1618,23 +1777,16 @@ else this.cluster_name=cluster_name; + if(socket_factory != null) + prot_stack.getTopProtocol().setSocketFactory(socket_factory); + try { - prot_stack.startStack(cluster_name); // calls start() in all protocols, from top to bottom + prot_stack.startStack(cluster_name, local_addr); // calls start() in all protocols, from top to bottom } catch(Throwable e) { throw new ChannelException("failed to start protocol stack", e); } - String tmp=Util.getProperty(new String[]{Global.CHANNEL_LOCAL_ADDR_TIMEOUT, "local_addr.timeout"}, - null, null, false, "30000"); - LOCAL_ADDR_TIMEOUT=Long.parseLong(tmp); - - /* Wait LOCAL_ADDR_TIMEOUT milliseconds for local_addr to have a non-null value (set by SET_LOCAL_ADDRESS) */ - local_addr=local_addr_promise.getResult(LOCAL_ADDR_TIMEOUT); - if(local_addr == null) { - log.fatal("local_addr is null; cannot connect"); - throw new ChannelException("local_addr is null"); - } /*create a temporary view, assume this channel is the only member and is the coordinator*/ Vector

    t=new Vector
    (1); @@ -1645,6 +1797,30 @@ transport.registerProbeHandler(probe_handler); } + /** + * Generates new UUID and sets local address. Sends down a REMOVE_ADDRESS (if existing address was present) and + * a SET_LOCAL_ADDRESS + */ + protected void setAddress() { + Address old_addr=local_addr; + local_addr=address_generator != null? address_generator.generateAddress() : UUID.randomUUID(); + + byte[] buf=(byte[])additional_data.get("additional_data"); + if(buf != null) + ((UUID)local_addr).setAdditionalData(buf); + + if(old_addr != null) + down(new Event(Event.REMOVE_ADDRESS, old_addr)); + if(name == null || name.length() == 0) // generate a logical name if not set + name=Util.generateLocalName(); + if(name != null && name.length() > 0) + UUID.add(local_addr, name); + + Event evt=new Event(Event.SET_LOCAL_ADDRESS, local_addr); + down(evt); + if(up_handler != null) + up_handler.up(evt); + } /** @@ -1711,8 +1887,6 @@ case Event.STATE_TRANSFER_INPUTSTREAM: info = (StateTransferInfo)evt.getArg(); return new StreamingSetStateEvent(info.inputStream,info.state_id); - case Event.EXIT: - return new ExitEvent(); default: return evt; } @@ -1731,6 +1905,7 @@ * */ protected void _close(boolean disconnect, boolean close_mq) { + Address old_addr=local_addr; if(closed) return; @@ -1745,6 +1920,8 @@ connected=false; notifyChannelClosed(this); init(); // sets local_addr=null; changed March 18 2003 (bela) -- prevented successful rejoining + if(old_addr != null) + UUID.remove(old_addr); } protected void stopStack(boolean stop, boolean destroy) { @@ -1773,24 +1950,6 @@ } - /** - * Creates a separate thread to close the protocol stack. - * This is needed because the thread that called JChannel.up() with the EXIT event would - * hang waiting for up() to return, while up() actually tries to kill that very thread. - * This way, we return immediately and allow the thread to terminate. - */ - private void handleExit(Event evt) { - notifyChannelShunned(); - if(closer != null && !closer.isAlive()) - closer=null; - if(closer == null) { - if(log.isDebugEnabled()) - log.debug("received an EXIT event, will leave the channel"); - closer=new CloserThread(evt); - closer.start(); - } - } - public boolean flushSupported() { return flush_supported; } @@ -1835,7 +1994,7 @@ * @return true if FLUSH completed within the timeout */ public boolean startFlush(List
    flushParticipants,boolean automatic_resume) { - boolean successfulFlush = false; + boolean successfulFlush; if(!flushSupported()){ throw new IllegalStateException("Flush is not supported, add pbcast.FLUSH protocol to your configuration"); } @@ -1873,33 +2032,15 @@ public void stopFlush() { if(!flushSupported()) { throw new IllegalStateException("Flush is not supported, add pbcast.FLUSH protocol to your configuration"); - } - - down(new Event(Event.RESUME)); - - //do not return until UNBLOCK event is received - try { - flush_unblock_promise.getResultWithTimeout(FLUSH_UNBLOCK_TIMEOUT); - } - catch(TimeoutException te) { - log.warn("Timeout waiting for UNBLOCK event at " + getLocalAddress()); - } + } + down(new Event(Event.RESUME)); } public void stopFlush(List
    flushParticipants) { if(!flushSupported()) { throw new IllegalStateException("Flush is not supported, add pbcast.FLUSH protocol to your configuration"); - } - + } down(new Event(Event.RESUME, flushParticipants)); - - // do not return until UNBLOCK event is received - try { - flush_unblock_promise.getResultWithTimeout(FLUSH_UNBLOCK_TIMEOUT); - } - catch(TimeoutException te) { - log.warn("Timeout waiting for UNBLOCK event at " + getLocalAddress()); - } } @Override @@ -1936,14 +2077,23 @@ class MyProbeHandler implements TP.ProbeHandler { public Map handleProbe(String... keys) { - HashMap map=new HashMap(2); + Map map=new HashMap(2); for(String key: keys) { if(key.startsWith("jmx")) { Map tmp_stats; int index=key.indexOf("="); if(index > -1) { - String value=key.substring(index +1); - tmp_stats=dumpStats(value); + List list=null; + String protocol_name=key.substring(index +1); + index=protocol_name.indexOf("."); + if(index > -1) { + String rest=protocol_name; + protocol_name=protocol_name.substring(0, index); + String attrs=rest.substring(index +1); // e.g. "num_sent,msgs,num_received_msgs" + list=Util.parseStringList(attrs, ","); + } + + tmp_stats=dumpStats(protocol_name, list); } else tmp_stats=dumpStats(); @@ -1955,118 +2105,117 @@ Map tmp_info=getInfo(); map.put("info", tmp_info != null? Util.mapToString(tmp_info) : "null"); } + if(key.equals("socks")) { + map.put("socks", getOpenSockets()); + } + if(key.startsWith("invoke") || key.startsWith("op")) { + int index=key.indexOf("="); + if(index != -1) { + try { + handleOperation(map, key.substring(index+1)); + } + catch(Throwable throwable) { + log.error("failed invoking operation " + key.substring(index+1), throwable); + } + } + } + } - map.put("version", Version.description + ", cvs=\"" + Version.cvs + "\""); + map.put("version", Version.description); if(my_view != null && !map.containsKey("view")) map.put("view", my_view.toString()); - map.put("local_addr", local_addr != null? local_addr.toString() : "null"); + map.put("local_addr", getAddressAsString() + " [" + getAddressAsUUID() + "]"); + PhysicalAddress physical_addr=(PhysicalAddress)downcall(new Event(Event.GET_PHYSICAL_ADDRESS, local_addr)); + if(physical_addr != null) + map.put("physical_addr", physical_addr.toString()); map.put("cluster", getClusterName()); - map.put("member", getLocalAddressAsString() + " (" + getClusterName() + ")"); return map; } public String[] supportedKeys() { - return new String[]{"jmx", "info"}; - } - } - - class CloserThread extends Thread { - final Event exitEvent; - - CloserThread(Event evt) { - super(Util.getGlobalThreadGroup(), "CloserThread"); - this.exitEvent=evt; - setDaemon(true); + return new String[]{"jmx", "info", "invoke=[]", "\nop=[]", "socks"}; } + String getOpenSockets() { + Map socks=getSocketFactory().getSockets(); + TP transport=getProtocolStack().getTransport(); + if(transport != null && transport.isSingleton()) { + Map tmp=transport.getSocketFactory().getSockets(); + if(tmp != null) + socks.putAll(tmp); + } - public void run() { - try { - String old_cluster_name=cluster_name; // remember because close() will null it - if(log.isDebugEnabled()) - log.debug("CloserThread - closing the channel " + local_addr); - - //consider closing and connecting as one operation to prevent - //other threads from intervleaving - synchronized(JChannel.this){ - _close(false, false); // do not disconnect before closing channel, do not close mq (yet !) - - if(up_handler != null) - up_handler.up(exitEvent); - else { - try { - if(receiver == null) - mq.add(exitEvent); - } - catch(Exception ex) { - if(log.isErrorEnabled()) log.error(ex); - } - } - - - Util.sleep(500); // give the mq thread a bit of time to deliver EXIT to the application - try { - mq.close(false); + StringBuilder sb=new StringBuilder(); + if(socks != null) { + for(Map.Entry entry: socks.entrySet()) { + Object key=entry.getKey(); + if(key instanceof ServerSocket) { + ServerSocket tmp=(ServerSocket)key; + sb.append(tmp.getInetAddress()).append(":").append(tmp.getLocalPort()) + .append(" ").append(entry.getValue()).append(" [tcp]"); + } + else if(key instanceof DatagramSocket) { + DatagramSocket sock=(DatagramSocket)key; + sb.append(sock.getLocalAddress()).append(":").append(sock.getLocalPort()) + .append(" ").append(entry.getValue()).append(" [udp]"); } - catch(Exception ex) { - } - - if(auto_reconnect) { - try { - if(log.isDebugEnabled()) log.debug("CloserThread - reconnecting to group " + old_cluster_name); - open(); - if(additional_data != null) { - // send previously set additional_data down the stack - other protocols (e.g. TP) use it - Map m=new HashMap(additional_data); - down(new Event(Event.CONFIG, m)); - } - } - catch(Exception ex) { - if(log.isErrorEnabled()) log.error(ex); - return; - } - - while(!connected) { - try { - connect(old_cluster_name); - notifyChannelReconnected(local_addr); - } - catch(Exception ex) { - if(log.isErrorEnabled()) log.error("CloserThread - failure reconnecting to channel, retrying", ex); - //release the lock on channel so other threads can potentially close or shutdown - JChannel.this.wait(1000); - - if(!isOpen()){ - //other thread closed the channel in the meantime, give up from reconnecting - break; - } - } - } + else { + sb.append(key).append(" ").append(entry.getValue()); } + sb.append("\n"); } + } + return sb.toString(); + } - boolean canFetchState = auto_getstate && state_transfer_supported && connected; - if(canFetchState) { - if(log.isDebugEnabled()) - log.debug("fetching the state (auto_getstate=true)"); - - boolean rc=JChannel.this.getState(null, GET_STATE_DEFAULT_TIMEOUT); - if(log.isDebugEnabled()) { - if(rc) - log.debug("CloserThread - state was retrieved successfully"); - else - log.debug("CloserThread - state transfer failed"); - } - } + /** + * Invokes an operation and puts the return value into map + * @param map + * @param operation Protocol.OperationName[args], e.g. STABLE.foo[arg1 arg2 arg3] + */ + private void handleOperation(Map map, String operation) throws Throwable { + int index=operation.indexOf("."); + if(index == -1) + throw new IllegalArgumentException("operation " + operation + " is missing the protocol name"); + String prot_name=operation.substring(0, index); + Protocol prot=prot_stack.findProtocol(prot_name); + if(prot == null) + throw new IllegalArgumentException("protocol " + prot_name + " not found"); + + int args_index=operation.indexOf("["); + String method_name; + if(args_index != -1) + method_name=operation.substring(index +1, args_index).trim(); + else + method_name=operation.substring(index+1).trim(); + + String[] args=null; + if(args_index != -1) { + int end_index=operation.indexOf("]"); + if(end_index == -1) + throw new IllegalArgumentException("] not found"); + List str_args=Util.parseCommaDelimitedStrings(operation.substring(args_index + 1, end_index)); + Object[] strings=str_args.toArray(); + args=new String[strings.length]; + for(int i=0; i < strings.length; i++) + args[i]=(String)strings[i]; + } + Method method=MethodCall.findMethod(prot.getClass(), method_name, args); + MethodCall call=new MethodCall(method); + Object[] converted_args=null; + if(args != null) { + converted_args=new Object[args.length]; + Class[] types=method.getParameterTypes(); + for(int i=0; i < args.length; i++) + converted_args[i]=MethodCall.convert(args[i], types[i]); } - catch(Exception ex) { - if(log.isErrorEnabled()) log.error("exception: " + ex); - } - finally { - closer=null; - } + Object retval=call.invoke(prot, converted_args); + if(retval != null) + map.put(prot_name + "." + method_name, retval.toString()); } } + + } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/jmx/JmxConfigurator.java libjgroups-java-2.12.2.Final/src/org/jgroups/jmx/JmxConfigurator.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/jmx/JmxConfigurator.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/jmx/JmxConfigurator.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,157 +1,196 @@ package org.jgroups.jmx; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.JChannel; +import org.jgroups.JChannelFactory; import org.jgroups.annotations.MBean; +import org.jgroups.annotations.ManagedAttribute; +import org.jgroups.annotations.ManagedOperation; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.stack.Protocol; import org.jgroups.stack.ProtocolStack; import javax.management.*; - -import java.lang.management.ManagementFactory; -import java.util.Vector; -import java.util.Set; import java.util.Iterator; +import java.util.List; +import java.util.Set; /** - * @author Bela Ban - * @version $Id: JmxConfigurator.java,v 1.13 2008/03/13 02:42:57 vlada Exp $ + * @author Bela Ban, Vladimir Blagojevic */ public class JmxConfigurator { - static final Log log=LogFactory.getLog(JmxConfigurator.class); + static final Log log = LogFactory.getLog(JmxConfigurator.class); /** - * Registers an already created channel with the MBeanServer. Creates an - * org.jgroups.jmx.JChannel which delegates to the org.jgroups.JChannel and - * registers it. Optionally, this method will also try to create one MBean - * proxy for each protocol in the channel's protocol stack, and register it - * as well. + * Registers an already created channel with the given MBeanServer. Wraps instance of JChannel + * with DynamicMBean and delegates all calls to the actual JChannel wrapped. + *

    + * Optionally, this method will also wrap each protocol in the given channel with DynamicMBean + * and register it as well. * * @param channel * @param server * @param domain - * Has to be a JMX ObjectName of the domain, e.g. - * DefaultDomain:name=JGroups + * Has to be a JMX ObjectName of the domain, e.g. DefaultDomain:name=JGroups * @param register_protocols - * @return org.jgroups.jmx.JChannel for the specified org.jgroups.JChannel */ - public static void registerChannel(org.jgroups.JChannel channel, - MBeanServer server, - String domain, - String cluster_name, - boolean register_protocols) throws Exception { - if(cluster_name == null) - cluster_name=channel != null? channel.getClusterName() : null; - if(cluster_name == null) - cluster_name="null"; - - if(register_protocols) { - ProtocolStack stack=channel.getProtocolStack(); - Vector protocols=stack.getProtocols(); - for(Protocol p:protocols) { - register(p, - ManagementFactory.getPlatformMBeanServer(), - getProtocolRegistrationName(cluster_name, domain, p)); + public static void registerChannel(JChannel channel, MBeanServer server, String domain, + String cluster_name, boolean register_protocols) throws Exception { + + if(channel == null) + throw new NullPointerException("channel cannot be null"); + if (cluster_name == null) + cluster_name=channel.getClusterName(); + if (cluster_name == null) + cluster_name = "null"; + + cluster_name=ObjectName.quote(cluster_name); + + if (register_protocols) { + ProtocolStack stack = channel.getProtocolStack(); + List protocols = stack.getProtocols(); + for (Protocol p : protocols) { + if (p.getClass().isAnnotationPresent(MBean.class)) { + register(p, server, getProtocolRegistrationName(cluster_name, domain, p)); + } } } register(channel, server, getChannelRegistrationName(channel, domain, cluster_name)); } /** - * Registers an already created channel with the MBeanServer. Creates an - * org.jgroups.jmx.JChannel which delegates to the org.jgroups.JChannel and - * registers it. + * Registers an already created channel with the given MBeanServer. Wraps instance of JChannel + * with DynamicMBean and delegates all calls to the actual JChannel wrapped. + *

    + * This method will also wrap each protocol in the given channel with DynamicMBean and register + * it as well. * * @param channel * @param server - * @param name - * The JMX ObjectName - * @return org.jgroups.jmx.JChannel for the specified org.jgroups.JChannel + * @param domain + * Has to be a JMX ObjectName of the domain, e.g. DefaultDomain:name=JGroups */ - public static void registerChannel(org.jgroups.JChannel channel, MBeanServer server, String name) throws Exception { + public static void registerChannel(JChannel channel, MBeanServer server, String name) + throws Exception { registerChannel(channel, server, "jgroups", name, true); } public static void unregisterChannel(MBeanServer server, ObjectName name) throws Exception { - if(server != null) + if (server != null) server.unregisterMBean(name); } public static void unregisterChannel(MBeanServer server, String name) throws Exception { - if(server != null) + if (server != null) server.unregisterMBean(new ObjectName(name)); } - public static void unregisterChannel(org.jgroups.JChannel c, - MBeanServer server, - String clusterName) throws Exception { - - ProtocolStack stack=c.getProtocolStack(); - Vector protocols=stack.getProtocols(); - for(Protocol p:protocols) { - if(p.getClass().isAnnotationPresent(MBean.class)) { + public static void unregisterChannel(JChannel c, MBeanServer server, String clusterName) throws Exception { + unregisterChannel(c, server, "jgroups", clusterName); + } + + + public static void unregisterChannel(JChannel c, MBeanServer server, String domain, String clusterName) + throws Exception { + + if(clusterName != null) + clusterName=ObjectName.quote(clusterName); + + ProtocolStack stack = c.getProtocolStack(); + List protocols = stack.getProtocols(); + for (Protocol p : protocols) { + if (p.getClass().isAnnotationPresent(MBean.class)) { try { - unregister(p, - ManagementFactory.getPlatformMBeanServer(), - getProtocolRegistrationName(clusterName, "jgroups", p)); - } - catch(MBeanRegistrationException e) { - if(log.isWarnEnabled()) { + unregister(p, server, getProtocolRegistrationName(clusterName, domain, p)); + } catch (MBeanRegistrationException e) { + if (log.isWarnEnabled()) { log.warn("MBean unregistration failed " + e); } } } } - unregister(c, server, getChannelRegistrationName(clusterName)); + unregister(c, server, getChannelRegistrationName(c, domain, clusterName)); } - public static void registerChannelFactory(org.jgroups.JChannelFactory factory, - MBeanServer server, - String name) throws Exception { + public static void registerChannelFactory(JChannelFactory factory, MBeanServer server, + String name) throws Exception { register(factory, server, name); } - public static void unRegisterChannelFactory(org.jgroups.JChannelFactory factory, - MBeanServer server, - String name) throws Exception { + public static void unRegisterChannelFactory(JChannelFactory factory, MBeanServer server, + String name) throws Exception { unregister(factory, server, name); - } + } - public static void register(Object obj, MBeanServer server, String name) throws MBeanRegistrationException, - MalformedObjectNameException { + public static void register(Object obj, MBeanServer server, String name) + throws MBeanRegistrationException, MalformedObjectNameException { internalRegister(obj, server, name); - } - - public static void unregister(Object obj, MBeanServer server, String name) throws MBeanRegistrationException, - MalformedObjectNameException { + } + + public static void unregister(Object obj, MBeanServer server, String name) + throws MBeanRegistrationException, MalformedObjectNameException { internalUnregister(obj, server, name); } - private static void internalRegister(Object obj, MBeanServer server, String name) throws MalformedObjectNameException, - MBeanRegistrationException { + @Deprecated + public DynamicMBean asDynamicMBean(JChannel ch) { + return new ResourceDMBean(ch); + } + + @Deprecated + public DynamicMBean asDynamicMBean(Protocol p) { + return new ResourceDMBean(p); + } + + + + /** + * Wrap JChannel with DynamicMBean interface. All annotated attributes and methods will be + * exposed through DynamicMBean API. + * + * @see ManagedAttribute + * @see ManagedOperation + * + * @param ch channel to be wrapped + * @return Channel ch wrapped as a DynamicBean + */ + public static DynamicMBean wrap(JChannel ch) { + return new ResourceDMBean(ch); + } + + /** + * Wrap Protocol with DynamicMBean interface. All annotated attributes and methods will be + * exposed through DynamicMBean API. + * + * @see ManagedAttribute + * @see ManagedOperation + * + * @param p protocol to be wrapped + * @return Protocol p as a DynamicMBean + */ + public static DynamicMBean wrap(Protocol p) { + return new ResourceDMBean(p); + } + + private static void internalRegister(Object obj, MBeanServer server, String name) + throws MalformedObjectNameException, MBeanRegistrationException { - if(obj == null) + if (obj == null) throw new IllegalArgumentException("Object being registered cannot be null"); - if(server == null) - throw new IllegalArgumentException("MBean server used for registeration cannot be null"); - + if (server == null) + throw new IllegalArgumentException("MBean server used for registeration cannot be null"); + try { - ObjectName objName=getObjectName(obj, name); - ResourceDMBean res=new ResourceDMBean(obj); + ObjectName objName = getObjectName(obj, name); + ResourceDMBean res = new ResourceDMBean(obj); server.registerMBean(res, objName); - - if(log.isDebugEnabled()) { - log.debug("register MBean " + objName + " completed"); - } - } - catch(InstanceAlreadyExistsException e) { - if(log.isErrorEnabled()) { + } catch (InstanceAlreadyExistsException e) { + if (log.isErrorEnabled()) { log.error("register MBean failed " + e.getMessage()); } throw new MBeanRegistrationException(e, "The @MBean objectName is not unique"); - } - catch(NotCompliantMBeanException e) { - if(log.isErrorEnabled()) { + } catch (NotCompliantMBeanException e) { + if (log.isErrorEnabled()) { log.error("register MBean failed " + e.getMessage()); } throw new MBeanRegistrationException(e); @@ -159,30 +198,24 @@ } - private static void internalUnregister(Object obj, MBeanServer server, String name) throws MBeanRegistrationException { + private static void internalUnregister(Object obj, MBeanServer server, String name) + throws MBeanRegistrationException { try { - if(name != null && name.length() > 0) { + if (name != null && name.length() > 0) { server.unregisterMBean(new ObjectName(name)); - } - else if(obj != null) { + } else if (obj != null) { server.unregisterMBean(getObjectName(obj, null)); - } - else { + } else { throw new MBeanRegistrationException(null, - "Cannot find MBean name from @MBean or passed in value"); + "Cannot find MBean name from @MBean or passed in value"); } - if(log.isDebugEnabled()) { - log.debug("unregister MBean" + name + " completed"); - } - } - catch(InstanceNotFoundException infe) { - if(log.isErrorEnabled()) { + } catch (InstanceNotFoundException infe) { + if (log.isErrorEnabled()) { log.error("unregister MBean failed " + infe.getMessage()); } throw new MBeanRegistrationException(infe); - } - catch(MalformedObjectNameException e) { - if(log.isErrorEnabled()) { + } catch (MalformedObjectNameException e) { + if (log.isErrorEnabled()) { log.error("unregister MBean failed " + e.getMessage()); } throw new MBeanRegistrationException(e); @@ -190,40 +223,32 @@ } private static ObjectName getObjectName(Object obj, String name) throws MalformedObjectNameException { - MBean resource=obj.getClass().getAnnotation(MBean.class); - if(name != null && name.length() > 0) { + MBean resource = obj.getClass().getAnnotation(MBean.class); + if (name != null && name.length() > 0) { return new ObjectName(name); - } - else if(resource.objectName() != null && resource.objectName().length() > 0) { + } else if (resource.objectName() != null && resource.objectName().length() > 0) { return new ObjectName(resource.objectName()); - } - else { - throw new MalformedObjectNameException("Instance " + obj - + " of a class " - + obj.getClass() - + " does not have a valid object name"); + } else { + throw new MalformedObjectNameException(obj + " of class " + obj.getClass() + " has an invalid object name"); } } + /** * Unregisters object_name and everything under it * * @param object_name */ public static void unregister(MBeanServer server, String object_name) throws Exception { - Set mbeans=server.queryNames(new ObjectName(object_name), null); - if(mbeans != null) { - ObjectName name; - for(Iterator it=mbeans.iterator();it.hasNext();) { - name=(ObjectName)it.next(); - server.unregisterMBean(name); + Set mbeans = server.queryNames(new ObjectName(object_name), null); + if (mbeans != null) { + for (Iterator it = mbeans.iterator(); it.hasNext();) { + server.unregisterMBean(it.next()); } } } - private static String getChannelRegistrationName(org.jgroups.JChannel c, - String domain, - String clusterName) { + private static String getChannelRegistrationName(JChannel c, String domain, String clusterName) { return domain + ":type=channel,cluster=" + clusterName; } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/jmx/ResourceDMBean.java libjgroups-java-2.12.2.Final/src/org/jgroups/jmx/ResourceDMBean.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/jmx/ResourceDMBean.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/jmx/ResourceDMBean.java 2011-10-18 11:22:35.000000000 +0000 @@ -7,8 +7,6 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.management.Attribute; import javax.management.AttributeList; @@ -19,11 +17,13 @@ import javax.management.MBeanOperationInfo; import javax.management.ReflectionException; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.annotations.MBean; import org.jgroups.annotations.ManagedAttribute; import org.jgroups.annotations.ManagedOperation; +import org.jgroups.annotations.Property; +import org.jgroups.util.Util; /** * @@ -31,7 +31,6 @@ * * @author Chris Mills * @author Vladimir Blagojevic - * @version $Id: ResourceDMBean.java,v 1.27 2008/04/28 13:43:11 vlada Exp $ * @see ManagedAttribute * @see ManagedOperation * @see MBean @@ -60,7 +59,6 @@ private final List ops=new ArrayList(); public ResourceDMBean(Object instance) { - if(instance == null) throw new NullPointerException("Cannot make an MBean wrapper for null instance"); @@ -71,33 +69,14 @@ attrInfo=new MBeanAttributeInfo[atts.size()]; int i=0; - log.info("Processing class " + instance.getClass()); - log.info("Attributes are:"); MBeanAttributeInfo info=null; for(AttributeEntry entry:atts.values()) { info=entry.getInfo(); attrInfo[i++]=info; - log.info("Attribute " + info.getName() - + "[r=" - + info.isReadable() - + ",w=" - + info.isWritable() - + ",is=" - + info.isIs() - + ",type=" - + info.getType() - + "]"); } opInfo=new MBeanOperationInfo[ops.size()]; ops.toArray(opInfo); - - if(ops.size() > 0) - log.info("Operations are:"); - for(MBeanOperationInfo op:opInfo) { - log.info("Operation " + op.getReturnType() + " " + op.getName()); - } - } Object getObject() { @@ -108,12 +87,9 @@ MBean mbean=getObject().getClass().getAnnotation(MBean.class); if(mbean != null && mbean.description() != null && mbean.description().trim().length() > 0) { description=mbean.description(); - if(log.isDebugEnabled()) { - log.debug("@MBean description set - " + mbean.description()); - } MBeanAttributeInfo info=new MBeanAttributeInfo(ResourceDMBean.MBEAN_DESCRITION, "java.lang.String", - "@MBean description", + "MBean description", true, false, false); @@ -141,14 +117,7 @@ public synchronized Object getAttribute(String name) { if(name == null || name.length() == 0) throw new NullPointerException("Invalid attribute requested " + name); - - if(log.isDebugEnabled()) { - log.debug("getAttribute called for " + name); - } Attribute attr=getNamedAttribute(name); - if(log.isDebugEnabled()) { - log.debug("getAttribute value found " + attr.getValue()); - } return attr.getValue(); } @@ -178,18 +147,12 @@ for(int i=0;i < list.size();i++) { Attribute attr=(Attribute)list.get(i); - if(log.isDebugEnabled()) { - log.debug("Attribute name " + attr.getName() + " new value is " + attr.getValue()); - } - if(setNamedAttribute(attr)) { results.add(attr); } else { if(log.isWarnEnabled()) { - log.debug("Failed to update attribute name " + attr.getName() - + " with value " - + attr.getValue()); + log.warn("Failed to update attribute name " + attr.getName() + " with value " + attr.getValue()); } } } @@ -199,9 +162,6 @@ public Object invoke(String name, Object[] args, String[] sig) throws MBeanException, ReflectionException { try { - if(log.isDebugEnabled()) { - log.debug("Invoke method called on " + name); - } Class[] classes=new Class[sig.length]; for(int i=0;i < classes.length;i++) { classes[i]=getClassForName(sig[i]); @@ -216,8 +176,7 @@ public static Class getClassForName(String name) throws ClassNotFoundException { try { - Class c=Class.forName(name); - return c; + return Class.forName(name); } catch(ClassNotFoundException cnfe) { //Could be a primitive - let's check @@ -231,172 +190,229 @@ } private void findMethods() { - //find all methods but don't include methods from Object class + //find all methods but don't include methods from Object class List methods = new ArrayList(Arrays.asList(getObject().getClass().getMethods())); List objectMethods = new ArrayList(Arrays.asList(Object.class.getMethods())); methods.removeAll(objectMethods); - + for(Method method:methods) { //does method have @ManagedAttribute annotation? - ManagedAttribute attr=method.getAnnotation(ManagedAttribute.class); - if(attr != null) { - String methodName=method.getName(); - if(!methodName.startsWith("get") && !methodName.startsWith("set") - && !methodName.startsWith("is")) { - if(log.isWarnEnabled()) - log.warn("method name " + methodName - + " doesn't start with \"get\", \"set\", or \"is\"" - + ", but is annotated with @ManagedAttribute: will be ignored"); + if(method.isAnnotationPresent(ManagedAttribute.class) || method.isAnnotationPresent(Property.class)) { + exposeManagedAttribute(method); + } + //or @ManagedOperation + else if (method.isAnnotationPresent(ManagedOperation.class) || isMBeanAnnotationPresentWithExposeAll()){ + exposeManagedOperation(method); + } + } + } + + /** find all methods but don't include methods from Object class */ + /*private void findMethods() { + for(Class clazz=getObject().getClass();clazz != null; clazz=clazz.getSuperclass()) { + if(clazz.equals(Object.class)) + break; + + Method[] methods=clazz.getDeclaredMethods(); + for(Method method: methods) { + //does method have @ManagedAttribute annotation? + if(method.isAnnotationPresent(ManagedAttribute.class) || method.isAnnotationPresent(Property.class)) { + exposeManagedAttribute(method); + } + //or @ManagedOperation + else if (method.isAnnotationPresent(ManagedOperation.class) || isMBeanAnnotationPresentWithExposeAll()){ + exposeManagedOperation(method); + } + } + } + }*/ + + private void exposeManagedOperation(Method method) { + ManagedOperation op=method.getAnnotation(ManagedOperation.class); + String attName=method.getName(); + if(isSetMethod(method) || isGetMethod(method)) { + attName=attName.substring(3); + } + else if(isIsMethod(method)) { + attName=attName.substring(2); + } + //expose unless we already exposed matching attribute field + boolean isAlreadyExposed=atts.containsKey(attName); + if(!isAlreadyExposed) { + ops.add(new MBeanOperationInfo(op != null? op.description() : "", method)); + } + } + + private void exposeManagedAttribute(Method method){ + String methodName=method.getName(); + if(!methodName.startsWith("get") && !methodName.startsWith("set") && !methodName.startsWith("is")) { + if(log.isWarnEnabled()) + log.warn("method name " + methodName + " doesn't start with \"get\", \"set\", or \"is\"" + + ", but is annotated with @ManagedAttribute: will be ignored"); + return; + } + ManagedAttribute attr = method.getAnnotation(ManagedAttribute.class); + Property prop=method.getAnnotation(Property.class); + + boolean expose_prop=prop != null && prop.exposeAsManagedAttribute(); + boolean expose=attr != null || expose_prop; + if(!expose) + return; + + // Is name field of @ManagedAttributed used? + String attributeName=attr != null? attr.name() : null; + if(attributeName != null && attributeName.trim().length() > 0) + attributeName=attributeName.trim(); + else + attributeName=null; + + String descr=attr != null ? attr.description() : prop != null? prop.description() : null; + + boolean writeAttribute=false; + MBeanAttributeInfo info=null; + if(isSetMethod(method)) { // setter + attributeName=(attributeName==null)?methodName.substring(3):attributeName; + info=new MBeanAttributeInfo(attributeName, + method.getParameterTypes()[0].getCanonicalName(), + descr, + true, + true, + false); + writeAttribute=true; + } + else { // getter + if(method.getParameterTypes().length == 0 && method.getReturnType() != java.lang.Void.TYPE) { + boolean hasSetter=atts.containsKey(attributeName); + //we found is method + if(methodName.startsWith("is")) { + attributeName=(attributeName==null)?methodName.substring(2):attributeName; + info=new MBeanAttributeInfo(attributeName, + method.getReturnType().getCanonicalName(), + descr, + true, + hasSetter, + true); } else { - MBeanAttributeInfo info=null; - //Is name field of @ManagedAttributed used? - String attributeName=attr.name().length()>0?attr.name().trim():null; - boolean writeAttribute=false; - if(isSetMethod(method)) { // setter - attributeName=(attributeName==null)?methodName.substring(3):attributeName; - info=new MBeanAttributeInfo(attributeName, - method.getParameterTypes()[0].getCanonicalName(), - attr.description(), - true, - true, - false); - writeAttribute=true; - } - else { // getter - if(method.getParameterTypes().length == 0 && method.getReturnType() != java.lang.Void.TYPE) { - boolean hasSetter=atts.containsKey(attributeName); - //we found is method - if(methodName.startsWith("is")) { - attributeName=(attributeName==null)?methodName.substring(2):attributeName; - info=new MBeanAttributeInfo(attributeName, - method.getReturnType().getCanonicalName(), - attr.description(), - true, - hasSetter, - true); - } - else { - //this has to be get - attributeName=(attributeName==null)?methodName.substring(3):attributeName; - info=new MBeanAttributeInfo(attributeName, - method.getReturnType().getCanonicalName(), - attr.description(), - true, - hasSetter, - false); - } - } - else { - if(log.isWarnEnabled()) { - log.warn("Method " + method.getName() - + " must have a valid return type and zero parameters"); - } - continue; - } - } - - if(log.isDebugEnabled()) { - log.debug("@Attr found for method " + method.getName() - + " and registered as " - + attributeName); - } - - AttributeEntry ae=atts.get(attributeName); - //is it a read method? - if(!writeAttribute) { - //we already have annotated field as read - if(ae instanceof FieldAttributeEntry && ae.getInfo().isReadable()) { - log.warn("not adding annotated method " + method - + " since we already have read attribute"); - } - //we already have annotated set method - else if(ae instanceof MethodAttributeEntry) { - MethodAttributeEntry mae=(MethodAttributeEntry)ae; - if(mae.hasSetMethod()) { - atts.put(attributeName, - new MethodAttributeEntry(mae.getInfo(), mae.getSetMethod(), method)); - } - } //we don't have such entry - else { - atts.put(attributeName, new MethodAttributeEntry(info, null, method)); - } - }//is it a set method? - else { - if(ae instanceof FieldAttributeEntry) { - //we already have annotated field as write - if(ae.getInfo().isWritable()) { - log.warn("Not adding annotated method " + methodName - + " since we already have writable attribute"); - } - else { - //we already have annotated field as read - //lets make the field writable - Field f = ((FieldAttributeEntry)ae).getField(); - MBeanAttributeInfo i=new MBeanAttributeInfo(ae.getInfo().getName(), - f.getType().getCanonicalName(), - attr.description(), - true, - Modifier.isFinal(f.getModifiers())? false: true, - false); - atts.put(attributeName,new FieldAttributeEntry(i,f)); - } - } - //we already have annotated getOrIs method - else if(ae instanceof MethodAttributeEntry) { - MethodAttributeEntry mae=(MethodAttributeEntry)ae; - if(mae.hasIsOrGetMethod()) { - atts.put(attributeName, - new MethodAttributeEntry(info, - method, - mae.getIsOrGetMethod())); - } - } //we don't have such entry - else { - atts.put(attributeName, new MethodAttributeEntry(info, method, null)); - } - } + // this has to be get + attributeName=(attributeName==null)?methodName.substring(3):attributeName; + info=new MBeanAttributeInfo(attributeName, + method.getReturnType().getCanonicalName(), + descr, + true, + hasSetter, + false); } } - else if (method.isAnnotationPresent(ManagedOperation.class) || isMBeanAnnotationPresentWithExposeAll()){ - ManagedOperation op=method.getAnnotation(ManagedOperation.class); - String attName=method.getName(); - if(isSetMethod(method) || isGetMethod(method)) { - attName=attName.substring(3); - } - else if(isIsMethod(method)) { - attName=attName.substring(2); - } - //expose unless we already exposed matching attribute field - boolean isAlreadyExposed=atts.containsKey(attName); - if(!isAlreadyExposed) { - ops.add(new MBeanOperationInfo(op != null? op.description() : "", method)); - if(log.isDebugEnabled()) { - log.debug("@Operation found for method " + method.getName()); - } - } - } + else { + if(log.isWarnEnabled()) { + log.warn("Method " + method.getName() + " must have a valid return type and zero parameters"); + } + //silently skip this method + return; + } + } + + AttributeEntry ae=atts.get(attributeName); + //is it a read method? + if(!writeAttribute) { + //we already have annotated field as read + if(ae instanceof FieldAttributeEntry && ae.getInfo().isReadable()) { + log.warn("not adding annotated method " + method + " since we already have read attribute"); + } + //we already have annotated set method + else if(ae instanceof MethodAttributeEntry) { + MethodAttributeEntry mae=(MethodAttributeEntry)ae; + if(mae.hasSetMethod()) { + atts.put(attributeName, + new MethodAttributeEntry(mae.getInfo(), mae.getSetMethod(), method)); + } + } //we don't have such entry + else { + atts.put(attributeName, new MethodAttributeEntry(info, findSetter(obj.getClass(), attributeName), method)); + } + } //is it a set method? + else { + if(ae instanceof FieldAttributeEntry) { + //we already have annotated field as write + if(ae.getInfo().isWritable()) { + log.warn("Not adding annotated method " + methodName + " since we already have writable attribute"); + } + else { + //we already have annotated field as read + //lets make the field writable + Field f = ((FieldAttributeEntry)ae).getField(); + MBeanAttributeInfo i=new MBeanAttributeInfo(ae.getInfo().getName(), + f.getType().getCanonicalName(), + descr, + true, + !Modifier.isFinal(f.getModifiers()), + false); + atts.put(attributeName,new FieldAttributeEntry(i,f)); + } + } + //we already have annotated getOrIs method + else if(ae instanceof MethodAttributeEntry) { + MethodAttributeEntry mae=(MethodAttributeEntry)ae; + if(mae.hasIsOrGetMethod()) { + atts.put(attributeName, + new MethodAttributeEntry(info, + method, + mae.getIsOrGetMethod())); + } + } // we don't have such entry + else { + atts.put(attributeName, new MethodAttributeEntry(info, method, findGetter(obj.getClass(), attributeName))); + } } } + - private boolean isSetMethod(Method method) { + private static boolean isSetMethod(Method method) { return(method.getName().startsWith("set") && method.getParameterTypes().length == 1 && method.getReturnType() == java.lang.Void.TYPE); } - private boolean isGetMethod(Method method) { + private static boolean isGetMethod(Method method) { return(method.getParameterTypes().length == 0 && method.getReturnType() != java.lang.Void.TYPE && method.getName().startsWith("get")); } - private boolean isIsMethod(Method method) { + private static boolean isIsMethod(Method method) { return(method.getParameterTypes().length == 0 && (method.getReturnType() == boolean.class || method.getReturnType() == Boolean.class) && method.getName().startsWith("is")); } + public static Method findGetter(Class clazz, String name) { + try { + return clazz.getMethod("get" + name); + } + catch(NoSuchMethodException e) { + } + + try { + return clazz.getMethod("is" + name); + } + catch(NoSuchMethodException ex) { + } + return null; + } + + + public static Method findSetter(Class clazz, String name) { + try { + return clazz.getMethod("set" + name); + } + catch(NoSuchMethodException e) { + } + + return null; + } + + private void findFields() { //traverse class hierarchy and find all annotated fields for(Class clazz=getObject().getClass();clazz != null; clazz=clazz.getSuperclass()) { @@ -404,19 +420,23 @@ Field[] fields=clazz.getDeclaredFields(); for(Field field:fields) { ManagedAttribute attr=field.getAnnotation(ManagedAttribute.class); - if(attr != null) { - String fieldName = renameToJavaCodingConvention(field.getName()); + Property prop=field.getAnnotation(Property.class); + boolean expose_prop=prop != null && prop.exposeAsManagedAttribute(); + boolean expose=attr != null || expose_prop; + + if(expose) { + String fieldName = Util.attributeNameToMethodName(field.getName()); + String descr=attr != null? attr.description() : prop.description(); + boolean writable=attr != null? attr.writable() : prop.writable(); + MBeanAttributeInfo info=new MBeanAttributeInfo(fieldName, field.getType().getCanonicalName(), - attr.description(), + descr, true, - Modifier.isFinal(field.getModifiers())? false: attr.writable(), + !Modifier.isFinal(field.getModifiers()) && writable, false); atts.put(fieldName, new FieldAttributeEntry(info, field)); - if(log.isDebugEnabled()) { - log.debug("@Attr found for field " + field.getName()); - } } } } @@ -430,19 +450,8 @@ else { AttributeEntry entry=atts.get(name); if(entry != null) { - MBeanAttributeInfo i=entry.getInfo(); try { result=new Attribute(name, entry.invoke(null)); - if(log.isDebugEnabled()) - log.debug("Attribute " + name - + " has r=" - + i.isReadable() - + ",w=" - + i.isWritable() - + ",is=" - + i.isIs() - + " and value " - + result.getValue()); } catch(Exception e) { log.warn("Exception while reading value of attribute " + name, e); @@ -457,11 +466,6 @@ private boolean setNamedAttribute(Attribute attribute) { boolean result=false; - if(log.isDebugEnabled()) - log.debug("Invoking set on attribute " + attribute.getName() - + " with value " - + attribute.getValue()); - AttributeEntry entry=atts.get(attribute.getName()); if(entry != null) { try { @@ -481,31 +485,7 @@ } - private String renameToJavaCodingConvention(String fieldName) { - if(fieldName.contains("_")) { - Pattern p=Pattern.compile("_."); - Matcher m=p.matcher(fieldName); - StringBuffer sb=new StringBuffer(); - while(m.find()) { - m.appendReplacement(sb, fieldName.substring(m.end() - 1, m.end()).toUpperCase()); - } - m.appendTail(sb); - char first=sb.charAt(0); - if(Character.isLowerCase(first)) { - sb.setCharAt(0, Character.toUpperCase(first)); - } - return sb.toString(); - } - else { - if(Character.isLowerCase(fieldName.charAt(0))) { - return fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); - } - else { - return fieldName; - } - } - } - + private boolean isMBeanAnnotationPresentWithExposeAll(){ Class c=getObject().getClass(); return c.isAnnotationPresent(MBean.class) && c.getAnnotation(MBean.class).exposeAll(); @@ -530,9 +510,9 @@ public Object invoke(Attribute a) throws Exception { if(a == null && isOrGetmethod != null) - return isOrGetmethod.invoke(getObject(), new Object[] {}); + return isOrGetmethod.invoke(getObject()); else if(a != null && setMethod != null) - return setMethod.invoke(getObject(), new Object[] { a.getValue() }); + return setMethod.invoke(getObject(), a.getValue()); else return null; } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/logging/CustomLogFactory.java libjgroups-java-2.12.2.Final/src/org/jgroups/logging/CustomLogFactory.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/logging/CustomLogFactory.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/logging/CustomLogFactory.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,13 @@ +package org.jgroups.logging; + +/** + * An extension interface allowing to plug in a custom log provider. Set the + * jgroups.logging.log_factory_class system property with the fully + * qualified class name of your implementation in order for JGroups to use it + */ +public interface CustomLogFactory { + + Log getLog(Class clazz); + + Log getLog(String category); +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/logging/JDKLogImpl.java libjgroups-java-2.12.2.Final/src/org/jgroups/logging/JDKLogImpl.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/logging/JDKLogImpl.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/logging/JDKLogImpl.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,127 @@ +package org.jgroups.logging; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Logger that delivers messages to a JDK logger + * @author Manik Surtani + * @author Bela Ban + * @since 2.8 + */ +public class JDKLogImpl implements Log { + private final Logger logger; + + public JDKLogImpl(String category) { + logger=Logger.getLogger(category); + } + + public JDKLogImpl(Class category) { + logger=Logger.getLogger(category.getName()); // fix for https://jira.jboss.org/browse/JGRP-1224 + } + + public boolean isTraceEnabled() { + return logger.isLoggable(Level.FINER); + } + + public boolean isDebugEnabled() { + return logger.isLoggable(Level.FINE); + } + + public boolean isInfoEnabled() { + return logger.isLoggable(Level.INFO); + } + + public boolean isWarnEnabled() { + return logger.isLoggable(Level.WARNING); + } + + public boolean isErrorEnabled() { + return logger.isLoggable(Level.SEVERE); + } + + public boolean isFatalEnabled() { + return logger.isLoggable(Level.SEVERE); + } + + public void trace(String msg) { + logger.log(Level.FINER, msg); + } + + public void trace(Object msg) { + logger.log(Level.FINER, msg.toString()); + } + + public void debug(String msg) { + logger.log(Level.FINE, msg); + } + + public void info(String msg) { + logger.log(Level.INFO, msg); + } + + public void warn(String msg) { + logger.log(Level.WARNING, msg); + } + + public void error(String msg) { + logger.log(Level.SEVERE, msg); + } + + public void fatal(String msg) { + logger.log(Level.SEVERE, msg); + } + + public void trace(Object msg, Throwable t) { + logger.log(Level.FINER, msg.toString(), t); + } + + public void trace(String msg, Throwable t) { + logger.log(Level.FINER, msg, t); + } + + public void debug(String msg, Throwable t) { + logger.log(Level.FINE, msg, t); + } + + public void info(String msg, Throwable t) { + logger.log(Level.INFO, msg, t); + } + + public void warn(String msg, Throwable t) { + logger.log(Level.WARNING, msg, t); + } + + public void error(String msg, Throwable t) { + logger.log(Level.SEVERE, msg, t); + } + + public void fatal(String msg, Throwable t) { + logger.log(Level.SEVERE, msg, t); + } + + public String getLevel() { + Level level=logger.getLevel(); + return level != null? level.toString() : "off"; + } + + public void setLevel(String level) { + Level new_level=strToLevel(level); + if(new_level != null) + logger.setLevel(new_level); + } + + private static Level strToLevel(String level) { + if(level == null) return null; + level=level.toLowerCase().trim(); + if(level.equals("fatal")) return Level.SEVERE; + if(level.equals("error")) return Level.SEVERE; + if(level.equals("warn")) return Level.WARNING; + if(level.equals("warning")) return Level.WARNING; + if(level.equals("info")) return Level.INFO; + if(level.equals("debug")) return Level.FINE; + if(level.equals("trace")) return Level.FINER; + return null; + } + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/logging/Log4JLogImpl.java libjgroups-java-2.12.2.Final/src/org/jgroups/logging/Log4JLogImpl.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/logging/Log4JLogImpl.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/logging/Log4JLogImpl.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,138 @@ +package org.jgroups.logging; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; + +/** + * Logger that delivers messages to a Log4J logger + * + * @author Manik Surtani + * @author Bela Ban + * @since 2.8 + */ +public class Log4JLogImpl implements Log { + + private static final String FQCN = Log4JLogImpl.class.getName(); + + private final Logger logger; + + public Log4JLogImpl(String category) { + logger = Logger.getLogger(category); + } + + public Log4JLogImpl(Class category) { + logger = Logger.getLogger(category); + } + + public boolean isFatalEnabled() { + return logger.isEnabledFor(Level.FATAL); + } + + public boolean isErrorEnabled() { + return logger.isEnabledFor(Level.ERROR); + } + + public boolean isWarnEnabled() { + return logger.isEnabledFor(Level.WARN); + } + + public boolean isInfoEnabled() { + return logger.isInfoEnabled(); + } + + public boolean isDebugEnabled() { + return logger.isDebugEnabled(); + } + + public boolean isTraceEnabled() { + return logger.isTraceEnabled(); + } + + public void debug(String msg) { + logger.log(FQCN, Level.DEBUG, msg, null); + } + + public void debug(String msg, Throwable throwable) { + logger.log(FQCN, Level.DEBUG, msg, throwable); + } + + public void error(String msg) { + logger.log(FQCN, Level.ERROR, msg, null); + } + + public void error(String msg, Throwable throwable) { + logger.log(FQCN, Level.ERROR, msg, throwable); + } + + public void fatal(String msg) { + logger.log(FQCN, Level.FATAL, msg, null); + } + + public void fatal(String msg, Throwable throwable) { + logger.log(FQCN, Level.FATAL, msg, throwable); + } + + public void info(String msg) { + logger.log(FQCN, Level.INFO, msg, null); + } + + public void info(String msg, Throwable throwable) { + logger.log(FQCN, Level.INFO, msg, throwable); + } + + public void trace(Object msg) { + logger.log(FQCN, Level.TRACE, msg, null); + } + + public void trace(Object msg, Throwable throwable) { + logger.log(FQCN, Level.TRACE, msg, throwable); + } + + public void trace(String msg) { + logger.log(FQCN, Level.TRACE, msg, null); + } + + public void trace(String msg, Throwable throwable) { + logger.log(FQCN, Level.TRACE, msg, throwable); + } + + public void warn(String msg) { + logger.log(FQCN, Level.WARN, msg, null); + } + + public void warn(String msg, Throwable throwable) { + logger.log(FQCN, Level.WARN, msg, throwable); + } + + public String getLevel() { + Level level = logger.getLevel(); + return level != null ? level.toString() : "off"; + } + + public void setLevel(String level) { + Level new_level = strToLevel(level); + if (new_level != null) + logger.setLevel(new_level); + } + + private static Level strToLevel(String level) { + if (level == null) + return null; + level = level.toLowerCase().trim(); + if (level.equals("fatal")) + return Level.FATAL; + if (level.equals("error")) + return Level.ERROR; + if (level.equals("warn")) + return Level.WARN; + if (level.equals("warning")) + return Level.WARN; + if (level.equals("info")) + return Level.INFO; + if (level.equals("debug")) + return Level.DEBUG; + if (level.equals("trace")) + return Level.TRACE; + return null; + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/logging/LogFactory.java libjgroups-java-2.12.2.Final/src/org/jgroups/logging/LogFactory.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/logging/LogFactory.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/logging/LogFactory.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,68 @@ +package org.jgroups.logging; + +import org.jgroups.Global; + + +/** + * Factory that creates {@link Log} instances. + * + * @author Manik Surtani + * @author Bela Ban + * @since 4.0 + */ +public class LogFactory { + public static final boolean IS_LOG4J_AVAILABLE; + + private static final CustomLogFactory custom_log_factory; + + static { + String customLogFactoryClass=System.getProperty(Global.CUSTOM_LOG_FACTORY); + CustomLogFactory customLogFactoryX=null; + if(customLogFactoryClass != null) { + try { + // customLogFactoryX=(CustomLogFactory)Util.loadClass(customLogFactoryClass, null).newInstance(); + customLogFactoryX=(CustomLogFactory)Class.forName(customLogFactoryClass).newInstance(); + } + catch(Exception e) { + // failed to create the custom log factory, ignore? + } + } + custom_log_factory=customLogFactoryX; + boolean available; + try { + Class.forName("org.apache.log4j.Logger"); + available=true; + } + catch(ClassNotFoundException cnfe) { + available=false; + } + IS_LOG4J_AVAILABLE=available; + } + + public static Log getLog(Class clazz) { + if(custom_log_factory != null) + return custom_log_factory.getLog(clazz); + + // this call is not executed frequently, so we don't need to move the check for USE_JDK_LOGGER to class init time + final boolean use_jdk_logger=Boolean.parseBoolean(System.getProperty(Global.USE_JDK_LOGGER)); + if(IS_LOG4J_AVAILABLE && !use_jdk_logger) { + return new Log4JLogImpl(clazz); + } + else { + return new JDKLogImpl(clazz); + } + } + + public static Log getLog(String category) { + if(custom_log_factory != null) + return custom_log_factory.getLog(category); + + final boolean use_jdk_logger=Boolean.parseBoolean(System.getProperty(Global.USE_JDK_LOGGER)); + if(IS_LOG4J_AVAILABLE && !use_jdk_logger) { + return new Log4JLogImpl(category); + } + else { + return new JDKLogImpl(category); + } + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/logging/Log.java libjgroups-java-2.12.2.Final/src/org/jgroups/logging/Log.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/logging/Log.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/logging/Log.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,45 @@ +package org.jgroups.logging; + +/** + * Simple logging wrapper for log4j or JDK logging. Code originally copied from Infinispan + * + * @author Manik Surtani + * @author Bela Ban + * @since 2.8 + */ +public interface Log { + boolean isFatalEnabled(); + boolean isErrorEnabled(); + boolean isWarnEnabled(); + boolean isInfoEnabled(); + boolean isDebugEnabled(); + boolean isTraceEnabled(); + + void debug(String msg); + void debug(String msg, Throwable throwable); + void error(String msg); + void error(String msg, Throwable throwable); + void fatal(String msg); + void fatal(String msg, Throwable throwable); + void info(String msg); + void info(String msg, Throwable throwable); + void trace(Object msg); + void trace(Object msg, Throwable throwable); + void trace(String msg); + void trace(String msg, Throwable throwable); + void warn(String msg); + void warn(String msg, Throwable throwable); + + + // Advanced methods + + /** + * Sets the level of a logger. This method is used to dynamically change the logging level of a running system, + * e.g. via JMX. The appender of a level needs to exist. + * @param level The new level. Valid values are "fatal", "error", "warn", "info", "debug", "trace" + * (capitalization not relevant) + */ + void setLevel(String level); + + String getLevel(); +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/Membership.java libjgroups-java-2.12.2.Final/src/org/jgroups/Membership.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/Membership.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/Membership.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,10 +1,9 @@ -// $Id: Membership.java,v 1.12 2008/01/10 08:07:26 belaban Exp $ package org.jgroups; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import java.util.*; @@ -72,6 +71,10 @@ } } + public void add(Address ... mbrs) { + for(Address mbr: mbrs) + add(mbr); + } /** * Adds a list of members to this membership diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/MembershipListener.java libjgroups-java-2.12.2.Final/src/org/jgroups/MembershipListener.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/MembershipListener.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/MembershipListener.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: MembershipListener.java,v 1.8 2007/07/21 06:21:55 belaban Exp $ package org.jgroups; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/MergeView.java libjgroups-java-2.12.2.Final/src/org/jgroups/MergeView.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/MergeView.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/MergeView.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: MergeView.java,v 1.8 2007/03/12 10:51:46 belaban Exp $ package org.jgroups; @@ -74,6 +73,10 @@ } + public View copy() { + return (MergeView)clone(); + } + public String toString() { StringBuilder sb=new StringBuilder(); sb.append("MergeView::").append(super.toString()).append(", subgroups=").append(subgroups); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/Message.java libjgroups-java-2.12.2.Final/src/org/jgroups/Message.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/Message.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/Message.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,19 +2,18 @@ package org.jgroups; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.conf.ClassConfigurator; -import org.jgroups.stack.IpAddress; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.util.Buffer; import org.jgroups.util.Headers; import org.jgroups.util.Streamable; import org.jgroups.util.Util; -import java.io.*; -import java.util.HashSet; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; import java.util.Map; -import java.util.Set; /** @@ -26,47 +25,51 @@ * The byte buffer can point to a reference, and we can subset it using index and length. However, * when the message is serialized, we only write the bytes between index and length. * @author Bela Ban - * @version $Id: Message.java,v 1.94 2008/11/11 09:30:08 belaban Exp $ */ public class Message implements Streamable { - protected Address dest_addr=null; - protected Address src_addr=null; + protected Address dest_addr; + protected Address src_addr; /** The payload */ - private byte[] buf=null; + private byte[] buf; /** The index into the payload (usually 0) */ - protected int offset=0; + protected int offset; /** The number of bytes in the buffer (usually buf.length is buf not equal to null). */ - protected int length=0; + protected int length; /** All headers are placed here */ protected Headers headers; - private byte flags; + private volatile byte flags; + + private volatile byte transient_flags; // transient_flags is neither marshalled nor copied protected static final Log log=LogFactory.getLog(Message.class); - static final Set nonStreamableHeaders=new HashSet(); - static final byte DEST_SET = 1; - static final byte SRC_SET = 2; - static final byte BUF_SET = 4; - // static final byte HDRS_SET=8; // bela July 15 2005: not needed, we always create headers - static final byte IPADDR_DEST = 16; - static final byte IPADDR_SRC = 32; - static final byte SRC_HOST_NULL = 64; - - - // =========================== Flags ============================== - public static final byte OOB = 1; - public static final byte LOW_PRIO = 2; // not yet sure if we want this flag... - public static final byte HIGH_PRIO = 4; // not yet sure if we want this flag... + static final byte DEST_SET = 1 << 0; + static final byte SRC_SET = 1 << 1; + static final byte BUF_SET = 1 << 2; + + + // =============================== Flags ==================================== + public static final byte OOB = 1 << 0; // message is out-of-band + public static final byte DONT_BUNDLE = 1 << 1; // don't bundle message at the transport + public static final byte NO_FC = 1 << 2; // bypass flow control + public static final byte SCOPED = 1 << 3; // when a message has a scope + // the following 2 flags might get removed in 3.x, when https://jira.jboss.org/browse/JGRP-1250 is resolved + public static final byte NO_RELIABILITY = 1 << 4; // bypass UNICAST(2) and NAKACK + public static final byte NO_TOTAL_ORDER = 1 << 5; // bypass total order (e.g. SEQUENCER) + public static final byte NO_RELAY = 1 << 6; // bypass relaying (RELAY) + // =========================== Transient flags ============================== + public static final byte OOB_DELIVERED = 1 << 0; // OOB which has already been delivered up the stack + @@ -121,13 +124,12 @@ * @param dest Address of receiver. If it is null then the message sent to the group. * Otherwise, it contains a single destination and is sent to that member.

    * @param src Address of sender - * @param obj The object will be serialized into the byte buffer. Object - * has to be serializable ! The resulting buffer must not be modified + * @param obj The object will be marshalled into the byte buffer. Obj to be serializable (e.g. implementing + * Serializable, Externalizable or Streamable, or be a basic type (e.g. Integer, Short etc)).! + * The resulting buffer must not be modified * (e.g. buf[0]=0 is not allowed), since we don't copy the contents on clopy() or clone().

    - * Note that this is a convenience method and JGroups will use default Java serialization to - * serialize obj into a byte buffer. */ - public Message(Address dest, Address src, Serializable obj) { + public Message(Address dest, Address src, Object obj) { this(dest); setSrc(src); setObject(obj); @@ -245,7 +247,7 @@ /** Returns a reference to the headers hashmap, which is immutable. Any attempt to * modify the returned map will cause a runtime exception */ - public Map getHeaders() { + public Map getHeaders() { return headers.getHeaders(); } @@ -258,9 +260,11 @@ } /** - * Takes an object and uses Java serialization to generate the byte[] buffer which is set in the message. + * Takes an object and uses Java serialization to generate the byte[] buffer which is set in the message. Parameter + * 'obj' has to be serializable (e.g. implementing Serializable, Externalizable or Streamable, or be a basic + * type (e.g. Integer, Short etc)). */ - final public void setObject(Serializable obj) { + final public void setObject(Object obj) { if(obj == null) return; try { byte[] tmp=Util.objectToByteBuffer(obj); @@ -272,7 +276,7 @@ } /** - * Uses Java serialization to create an object from the buffer of the message. Note that this is dangerous when + * Uses custom serialization to create an object from the buffer of the message. Note that this is dangerous when * using your own classloader, e.g. inside of an application server ! Most likely, JGroups will use the system * classloader to deserialize the buffer into an object, whereas (for example) a web application will want to * use the webapp's classloader, resulting in a ClassCastException. The recommended way is for the application to @@ -298,13 +302,53 @@ public void clearFlag(byte flag) { if(flag > Byte.MAX_VALUE || flag < 0) throw new IllegalArgumentException("flag has to be >= 0 and <= " + Byte.MAX_VALUE); -// if(isFlagSet(flag)) { -// flags ^= flag; -// } flags &= ~flag; } public boolean isFlagSet(byte flag) { + return isFlagSet(flags, flag); + } + + /** + * Same as {@link #setFlag(byte)} but transient flags are never marshalled + * @param flag + */ + public void setTransientFlag(byte flag) { + if(flag > Byte.MAX_VALUE || flag < 0) + throw new IllegalArgumentException("flag has to be >= 0 and <= " + Byte.MAX_VALUE); + transient_flags |= flag; + } + + /** + * Atomically checks if a given flag is set and - if not - sets it. When multiple threads concurrently call this + * method with the same flag, only one of them will be able to set the flag + * @param flag + * @return True if the flag could be set, false if not (was already set) + */ + public boolean setTransientFlagIfAbsent(byte flag) { + if(flag > Byte.MAX_VALUE || flag < 0) + throw new IllegalArgumentException("flag has to be >= 0 and <= " + Byte.MAX_VALUE); + synchronized(this) { + if(isTransientFlagSet(flag)) + return false; + else + setTransientFlag(flag); + return true; + } + } + + public void clearTransientFlag(byte flag) { + if(flag > Byte.MAX_VALUE || flag < 0) + throw new IllegalArgumentException("flag has to be >= 0 and <= " + Byte.MAX_VALUE); + transient_flags &= ~flag; + } + + public boolean isTransientFlagSet(byte flag) { + return isFlagSet(transient_flags, flag); + } + + + protected static boolean isFlagSet(byte flags, byte flag) { return (flags & flag) == flag; } @@ -312,41 +356,58 @@ return flags; } + public byte getTransientFlags() { + return transient_flags; + } + + + public void setScope(short scope) { + Util.setScope(this, scope); + } + + public short getScope() { + return Util.getScope(this); + } /*---------------------- Used by protocol layers ----------------------*/ - /** Puts a header given a key into the hashmap. Overwrites potential existing entry. */ - public void putHeader(String key, Header hdr) { - headers.putHeader(key, hdr); + /** Puts a header given an ID into the hashmap. Overwrites potential existing entry. */ + public void putHeader(short id, Header hdr) { + if(id < 0) + throw new IllegalArgumentException("An ID of " + id + " is invalid"); + headers.putHeader(id, hdr); } /** * Puts a header given a key into the map, only if the key doesn't exist yet - * @param key + * @param id * @param hdr - * @return the previous value associated with the specified key, or - * null if there was no mapping for the key. - * (A null return can also indicate that the map - * previously associated null with the key, + * @return the previous value associated with the specified key, or null if there was no mapping for the key. + * (A null return can also indicate that the map previously associated null with the key, * if the implementation supports null values.) */ - public Header putHeaderIfAbsent(String key, Header hdr) { - return headers.putHeaderIfAbsent(key, hdr); + public Header putHeaderIfAbsent(short id, Header hdr) { + if(id <= 0) + throw new IllegalArgumentException("An ID of " + id + " is invalid"); + return headers.putHeaderIfAbsent(id, hdr); } /** * * @param key - * @return the header assoaicted with key + * @return the header associated with key * @deprecated Use getHeader() instead. The issue with removing a header is described in * http://jira.jboss.com/jira/browse/JGRP-393 */ - public Header removeHeader(String key) { - return getHeader(key); + public Header removeHeader(short id) { + return getHeader(id); } - public Header getHeader(String key) { - return headers.getHeader(key); + public Header getHeader(short id) { + if(id <= 0) + throw new IllegalArgumentException("An ID of " + id + " is invalid. Add the protocol which calls " + + "getHeader() to jg-protocol-ids.xml"); + return headers.getHeader(id); } /*---------------------------------------------------------------------*/ @@ -362,6 +423,17 @@ * @return Message with specified data */ public Message copy(boolean copy_buffer) { + return copy(copy_buffer, true); + } + + /** + * Create a copy of the message. If offset and length are used (to refer to another buffer), the copy will + * contain only the subset offset and length point to, copying the subset into the new copy. + * @param copy_buffer + * @param copy_headers Copy the headers + * @return Message with specified data + */ + public Message copy(boolean copy_buffer, boolean copy_headers) { Message retval=new Message(false); retval.dest_addr=dest_addr; retval.src_addr=src_addr; @@ -373,7 +445,26 @@ retval.setBuffer(buf, offset, length); } - retval.headers=createHeaders(headers); + retval.headers=copy_headers? createHeaders(headers) : createHeaders(3); + return retval; + } + + /** + * Doesn't copy any headers except for those with ID >= copy_headers_above + * @param copy_buffer + * @param starting_id + * @return A message with headers whose ID are >= starting_id + */ + public Message copy(boolean copy_buffer, short starting_id) { + Message retval=copy(copy_buffer, false); + if(starting_id > 0) { + for(Map.Entry entry: getHeaders().entrySet()) { + short id=entry.getKey(); + if(id >= starting_id) + retval.putHeader(id, entry.getValue()); + } + } + return retval; } @@ -411,7 +502,9 @@ ret.append('0'); ret.append(" bytes"); if(flags > 0) - ret.append(", flags=").append(flagsToString()); + ret.append(", flags=").append(flagsToString(flags)); + if(transient_flags > 0) + ret.append(", transient_flags=" + transientFlagsToString(transient_flags)); ret.append(']'); return ret.toString(); } @@ -433,38 +526,6 @@ } - /** - * Returns size of buffer, plus some constant overhead for src and dest, plus number of headers time - * some estimated size/header. The latter is needed because we don't want to marshal all headers just - * to find out their size requirements. If a header implements Sizeable, the we can get the correct - * size.

    Size estimations don't have to be very accurate since this is mainly used by FRAG to - * determine whether to fragment a message or not. Fragmentation will then serialize the message, - * therefore getting the correct value. - */ - - - /** - * Returns the exact size of the marshalled message. Uses method size() of each header to compute the size, so if - * a Header subclass doesn't implement size() we will use an approximation. However, most relevant header subclasses - * have size() implemented correctly. (See org.jgroups.tests.SizeTest). - * @return The number of bytes for the marshalled message - */ - public long size() { - long retval=Global.BYTE_SIZE // leading byte - + Global.BYTE_SIZE // flags - + length // buffer - + (buf != null? Global.INT_SIZE : 0); // if buf != null 4 bytes for length - - // if(dest_addr != null) - // retval+=dest_addr.size(); - if(src_addr != null) - retval+=(src_addr).size(); - - retval+=Global.SHORT_SIZE; // size (short) - retval+=headers.marshalledSize(); - return retval; - } - public String printObjectHeaders() { return headers.printObjectHeaders(); @@ -482,205 +543,247 @@ public void writeTo(DataOutputStream out) throws IOException { byte leading=0; - if(src_addr != null) { - leading+=SRC_SET; - if(src_addr instanceof IpAddress) { - leading+=IPADDR_SRC; - if(((IpAddress)src_addr).getIpAddress() == null) { - leading+=SRC_HOST_NULL; - } - } - } + if(dest_addr != null) + leading=Util.setFlag(leading, DEST_SET); + + if(src_addr != null) + leading=Util.setFlag(leading, SRC_SET); + if(buf != null) - leading+=BUF_SET; + leading=Util.setFlag(leading, BUF_SET); // 1. write the leading byte first out.write(leading); - // the flags (e.g. OOB, LOW_PRIO) + // 2. the flags (e.g. OOB, LOW_PRIO) out.write(flags); - // 3. src_addr - if(src_addr != null) { - if(src_addr instanceof IpAddress) { - src_addr.writeTo(out); - } - else { - Util.writeAddress(src_addr, out); + // 3. dest_addr + if(dest_addr != null) + Util.writeAddress(dest_addr, out); + + // 4. src_addr + if(src_addr != null) + Util.writeAddress(src_addr, out); + + // 5. buf + if(buf != null) { + out.writeInt(length); + out.write(buf, offset, length); + } + + // 6. headers + int size=headers.size(); + out.writeShort(size); + final short[] ids=headers.getRawIDs(); + final Header[] hdrs=headers.getRawHeaders(); + for(int i=0; i < ids.length; i++) { + if(ids[i] > 0) { + out.writeShort(ids[i]); + writeHeader(hdrs[i], out); } } + } + + /** + * Writes the message to the output stream, but excludes the dest and src addresses unless the src address given + * as argument is different from the message's src address + * @param src + * @param out + * @throws IOException + */ + public void writeToNoAddrs(Address src, DataOutputStream out) throws IOException { + byte leading=0; + + boolean write_src_addr=src == null || src_addr != null && !src_addr.equals(src); + + if(write_src_addr) + leading=Util.setFlag(leading, SRC_SET); + + if(buf != null) + leading=Util.setFlag(leading, BUF_SET); + + // 1. write the leading byte first + out.write(leading); + + // 2. the flags (e.g. OOB, LOW_PRIO) + out.write(flags); - // 4. buf + // 4. src_addr + if(write_src_addr) + Util.writeAddress(src_addr, out); + + // 5. buf if(buf != null) { out.writeInt(length); out.write(buf, offset, length); } - // 5. headers + // 6. headers int size=headers.size(); out.writeShort(size); - final Object[] data=headers.getRawData(); - for(int i=0; i < data.length; i+=2) { - if(data[i] != null) { - out.writeUTF((String)data[i]); - writeHeader((Header)data[i+1], out); + final short[] ids=headers.getRawIDs(); + final Header[] hdrs=headers.getRawHeaders(); + for(int i=0; i < ids.length; i++) { + if(ids[i] > 0) { + out.writeShort(ids[i]); + writeHeader(hdrs[i], out); } } } public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { - int len, leading; - String hdr_name; - Header hdr; - // 1. read the leading byte first - leading=in.readByte(); + byte leading=in.readByte(); + // 2. the flags flags=in.readByte(); - // 2. src_addr - if((leading & SRC_SET) == SRC_SET) { - if((leading & IPADDR_SRC) == IPADDR_SRC) { - src_addr=new IpAddress(); - src_addr.readFrom(in); - } - else { - src_addr=Util.readAddress(in); - } - } - - // 3. buf - if((leading & BUF_SET) == BUF_SET) { - len=in.readInt(); + // 3. dest_addr + if(Util.isFlagSet(leading, DEST_SET)) + dest_addr=Util.readAddress(in); + + // 4. src_addr + if(Util.isFlagSet(leading, SRC_SET)) + src_addr=Util.readAddress(in); + + // 5. buf + if(Util.isFlagSet(leading, BUF_SET)) { + int len=in.readInt(); buf=new byte[len]; - in.read(buf, 0, len); + in.readFully(buf, 0, len); length=len; } - // 4. headers - len=in.readShort(); + // 6. headers + int len=in.readShort(); headers=createHeaders(len); - Object[] data=headers.getRawData(); - int index=0; + + short[] ids=headers.getRawIDs(); + Header[] hdrs=headers.getRawHeaders(); + for(int i=0; i < len; i++) { - hdr_name=in.readUTF(); - data[index++]=hdr_name; - hdr=readHeader(in); - data[index++]=hdr; - // headers.putHeader(hdr_name, hdr); + short id=in.readShort(); + Header hdr=readHeader(in); + ids[i]=id; + hdrs[i]=hdr; } } + /* --------------------------------- End of Interface Streamable ----------------------------- */ - /* --------------------------------- End of Interface Streamable ----------------------------- */ + /** + * Returns the exact size of the marshalled message. Uses method size() of each header to compute the size, so if + * a Header subclass doesn't implement size() we will use an approximation. However, most relevant header subclasses + * have size() implemented correctly. (See org.jgroups.tests.SizeTest). + * @return The number of bytes for the marshalled message + */ + public long size() { + long retval=Global.BYTE_SIZE // leading byte + + Global.BYTE_SIZE; // flags + if(dest_addr != null) + retval+=Util.size(dest_addr); + if(src_addr != null) + retval+=Util.size(src_addr); + if(buf != null) + retval+=Global.INT_SIZE // length (integer) + + length; // number of bytes in the buffer + + retval+=Global.SHORT_SIZE; // number of headers + retval+=headers.marshalledSize(); + return retval; + } /* ----------------------------------- Private methods ------------------------------- */ - private String flagsToString() { + public static String flagsToString(byte flags) { StringBuilder sb=new StringBuilder(); boolean first=true; - if(isFlagSet(OOB)) { + if(isFlagSet(flags, OOB)) { first=false; sb.append("OOB"); } - if(isFlagSet(LOW_PRIO)) { + if(isFlagSet(flags, DONT_BUNDLE)) { + if(!first) + sb.append("|"); + else + first=false; + sb.append("DONT_BUNDLE"); + } + if(isFlagSet(flags, NO_FC)) { + if(!first) + sb.append("|"); + else + first=false; + sb.append("NO_FC"); + } + if(isFlagSet(flags, SCOPED)) { + if(!first) + sb.append("|"); + else + first=false; + sb.append("SCOPED"); + } + if(isFlagSet(flags, NO_RELIABILITY)) { + if(!first) + sb.append("|"); + else + first=false; + sb.append("NO_RELIABILITY"); + } + if(isFlagSet(flags, NO_TOTAL_ORDER)) { if(!first) sb.append("|"); else first=false; - sb.append("LOW_PRIO"); + sb.append("NO_TOTAL_ORDER"); } - if(isFlagSet(HIGH_PRIO)) { + if(isFlagSet(flags, NO_RELAY)) { if(!first) sb.append("|"); else first=false; - sb.append("HIGH_PRIO"); + sb.append("NO_RELAY"); } return sb.toString(); } - private static void writeHeader(Header value, DataOutputStream out) throws IOException { - short magic_number; - String classname; - ObjectOutputStream oos=null; - int size=value.size(); - try { - magic_number=ClassConfigurator.getMagicNumber(value.getClass()); - // write the magic number or the class name - out.writeShort(magic_number); - if(magic_number == -1) { - classname=value.getClass().getName(); - out.writeUTF(classname); - if(log.isWarnEnabled()) - log.warn("magic number for " + classname + " not found, make sure you add your header to " + - "jg-magic-map.xml, or register it programmatically with the ClassConfigurator"); - } - out.writeShort(size); + public static String transientFlagsToString(byte flags) { + StringBuilder sb=new StringBuilder(); + if(isFlagSet(flags, OOB_DELIVERED)) + sb.append("OOB_DELIVERED"); + return sb.toString(); + } - // write the contents - if(value instanceof Streamable) { - ((Streamable)value).writeTo(out); - } - else { - oos=new ObjectOutputStream(out); - value.writeExternal(oos); - if(!nonStreamableHeaders.contains(value.getClass())) { - nonStreamableHeaders.add(value.getClass()); - if(log.isTraceEnabled()) - log.trace("encountered non-Streamable header: " + value.getClass()); - } - } - } - finally { - if(oos != null) - oos.close(); // this is a no-op on ByteArrayOutputStream - } + private static void writeHeader(Header hdr, DataOutputStream out) throws IOException { + short magic_number=ClassConfigurator.getMagicNumber(hdr.getClass()); + out.writeShort(magic_number); + hdr.writeTo(out); } private static Header readHeader(DataInputStream in) throws IOException { - Header hdr; - short magic_number; - String classname; - Class clazz; - ObjectInputStream ois=null; - try { - magic_number=in.readShort(); - if(magic_number != -1) { - clazz=ClassConfigurator.get(magic_number); - if(clazz == null) - throw new IllegalArgumentException("magic number " + magic_number + " is not available in magic map"); - } - else { - classname=in.readUTF(); - clazz=ClassConfigurator.get(classname); - } - - in.readShort(); // we discard the size since we don't use it - - hdr=(Header)clazz.newInstance(); - if(hdr instanceof Streamable) { - ((Streamable)hdr).readFrom(in); - } - else { - ois=new ObjectInputStream(in); - hdr.readExternal(ois); - } + short magic_number=in.readShort(); + Class clazz=ClassConfigurator.get(magic_number); + if(clazz == null) + throw new IllegalArgumentException("magic number " + magic_number + " is not available in magic map"); + + Header hdr=(Header)clazz.newInstance(); + hdr.readFrom(in); + return hdr; } catch(Exception ex) { IOException io_ex=new IOException("failed reading header"); io_ex.initCause(ex); throw io_ex; } - return hdr; } private static Headers createHeaders(int size) { @@ -696,5 +799,4 @@ /* ------------------------------- End of Private methods ---------------------------- */ - } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/MessageListener.java libjgroups-java-2.12.2.Final/src/org/jgroups/MessageListener.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/MessageListener.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/MessageListener.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: MessageListener.java,v 1.2 2005/07/17 11:38:05 chrislott Exp $ package org.jgroups; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/mux/Multiplexer.java libjgroups-java-2.12.2.Final/src/org/jgroups/mux/Multiplexer.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/mux/Multiplexer.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/mux/Multiplexer.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,12 +1,12 @@ package org.jgroups.mux; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.*; import org.jgroups.TimeoutException; import org.jgroups.annotations.Experimental; +import org.jgroups.conf.ClassConfigurator; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.protocols.pbcast.FLUSH; -import org.jgroups.stack.ProtocolStack; import org.jgroups.stack.StateTransferInfo; import org.jgroups.util.*; import org.jgroups.util.ThreadFactory; @@ -37,7 +37,6 @@ * @author Bela Ban, Vladimir Blagojevic * @see MuxChannel * @see Channel - * @version $Id: Multiplexer.java,v 1.105 2008/05/29 08:22:05 belaban Exp $ */ @Experimental(comment="because of impedance mismatches between a MuxChannel and JChannel, this might get deprecated " + "in the future. The replacement would be a shared transport (see the documentation for details)") @@ -47,13 +46,13 @@ private static final Log log=LogFactory.getLog(Multiplexer.class); private static final String SEPARATOR="::"; private static final short SEPARATOR_LEN=(short)SEPARATOR.length(); - private static final String NAME="MUX"; + private static final short ID=ClassConfigurator.getProtocolId(Multiplexer.class); /** * Map. Maintains the mapping between service IDs and * their associated MuxChannels */ - private final ConcurrentMap services=new ConcurrentHashMap(); + private final ConcurrentMap services=Util.createConcurrentMap(); private final JChannel channel; /** Thread pool to concurrently process messages sent to different services */ @@ -68,7 +67,7 @@ /** To collect service acks from Multiplexers */ private final AckCollector service_ack_collector=new AckCollector(); - + protected long service_ack_timeout = 2000; /** Cluster view */ @@ -101,13 +100,10 @@ public Multiplexer(JChannel channel) { if(channel == null || !channel.isOpen()) - throw new IllegalArgumentException("Channel " + channel - + " cannot be used for Multiplexer"); - + throw new IllegalArgumentException("Channel " + channel + " cannot be used for Multiplexer"); this.channel=channel; - this.channel.addChannelListener(new MultiplexerChannelListener()); this.channel.setUpHandler(this); - this.channel.setOpt(Channel.BLOCK, Boolean.TRUE); // we want to handle BLOCK events ourselves + this.channel.setOpt(Channel.BLOCK, Boolean.TRUE); // we want to handle BLOCK events ourselves //thread pool is enabled by default boolean use_thread_pool=Global.getPropertyAsBoolean(Global.MUX_ENABLED, true); @@ -142,7 +138,7 @@ public void setServicesResponseTimeout(long services_rsp_timeout) { this.service_response_timeout=services_rsp_timeout; } - + public long getServiceAckTimeout() { return service_ack_timeout; @@ -155,7 +151,7 @@ /** * Returns a copy of the current view minus the nodes on which * service service_id is not running - * + * * @param service_id * @return The service view */ @@ -223,7 +219,7 @@ ThreadFactory factory=new DefaultThreadFactory(Util.getGlobalThreadGroup(), "Multiplexer", false, true); return new ThreadPoolExecutor(min_threads, max_threads, keep_alive, TimeUnit.MILLISECONDS, - new SynchronousQueue(), + new SynchronousQueue(), factory, new ShutdownRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy())); } @@ -244,13 +240,13 @@ /** * Fetches the app states for all service IDs in keys. The keys are a * duplicate list, so it cannot be modified by the caller of this method - * + * * @param keys */ private boolean fetchServiceStates(Address target, Set keys, long timeout) throws ChannelClosedException, ChannelNotConnectedException { boolean rc, all_tranfers_ok=false; - boolean flushStarted=channel.startFlush(false); + boolean flushStarted=Util.startFlush(channel); if(flushStarted) { try { for(String stateId:keys) { @@ -286,7 +282,7 @@ /** * Remove mux header and dispatch to correct MuxChannel - * + * * @param evt * @return */ @@ -294,7 +290,7 @@ switch(evt.getType()) { case Event.MSG: final Message msg=(Message)evt.getArg(); - final MuxHeader hdr=(MuxHeader)msg.getHeader(NAME); + final MuxHeader hdr=(MuxHeader)msg.getHeader(ID); if(hdr == null) { log.error("MuxHeader not present - discarding message " + msg); return null; @@ -354,7 +350,7 @@ services_merged.set(false); } } - else { // regular view + else { // regular view HashMap> payload=(HashMap>)view.getPayload("service_state"); if(payload != null) { synchronized(service_state) { @@ -412,21 +408,13 @@ handleStateResponse(evt, true); break; - case Event.SET_LOCAL_ADDRESS: - passToAllMuxChannels(evt); - break; - case Event.BLOCK: passToAllMuxChannels(evt, true, true); return null; - case Event.UNBLOCK: // process queued-up MergeViews + case Event.UNBLOCK: // process queued-up MergeViews passToAllMuxChannels(evt); break; - case Event.EXIT: - //we are being shunned, close all services - closeAll(); - break; default: passToAllMuxChannels(evt); @@ -530,7 +518,12 @@ if(log.isTraceEnabled()) { log.trace("shutting down underlying JChannel as all MuxChannels are closed"); } - channel.shutdown(); + try { + Util.shutdown(channel); + } + catch(Exception e) { + e.printStackTrace(); + } services.clear(); shutdownThreadPool(); } @@ -538,7 +531,7 @@ } Address getLocalAddress() { - return channel.getLocalAddress(); + return channel.getAddress(); } boolean flushSupported() { @@ -546,7 +539,10 @@ } boolean startFlush(boolean automatic_resume) { - return channel.startFlush(automatic_resume); + boolean result = Util.startFlush(channel); + if(automatic_resume) + channel.stopFlush(); + return result; } void stopFlush() { @@ -579,7 +575,7 @@ * preferredTarget is returned. Otherwise, service view coordinator is * returned if such node exists. If service view is empty for a given * service_id null is returned. - * + * * @param preferredTarget * @param service_id * @return @@ -612,25 +608,26 @@ } return; } - + if(!channel.isOpen() || !channel.isConnected()) { if(log.isWarnEnabled()) { - log.warn("Underlying multiplexer channel " + channel.getLocalAddress() + log.warn("Underlying multiplexer channel " + channel.getAddress() + " is not connected, cannot send ServiceInfo." + ServiceInfo.typeToString(type) + " message"); } return; - } + } Message service_msg=new Message(); - service_msg.putHeader(NAME, new MuxHeader(new ServiceInfo(type, service, host, payload))); + service_msg.putHeader(ID, new MuxHeader(new ServiceInfo(type, service, host, payload))); if(oob) service_msg.setFlag(Message.OOB); if(channel.flushSupported()) - service_msg.putHeader(FLUSH.NAME, new FLUSH.FlushHeader(FLUSH.FlushHeader.FLUSH_BYPASS)); + service_msg.putHeader(ClassConfigurator.getProtocolId(FLUSH.class), + new FLUSH.FlushHeader(FLUSH.FlushHeader.FLUSH_BYPASS)); if(synchronous) { //for synchronous invocation we need to collect acks @@ -643,8 +640,8 @@ } //initialize collector and ... - service_ack_collector.reset(null, muxChannels); - int size=service_ack_collector.size(); + service_ack_collector.reset(muxChannels); + int size=service_ack_collector.size(); //then send a message channel.send(service_msg); @@ -665,14 +662,12 @@ + service_ack_timeout + "ms, missing ACKs from " + service_ack_collector.printMissing() - + " (received=" - + service_ack_collector.printReceived() - + "), local_addr=" + + ", local_addr=" + getLocalAddress()); } } else { - //if asynchronous then fire and forget + //if asynchronous then fire and forget channel.send(service_msg); } } @@ -704,7 +699,7 @@ //JGRP-616 if(mux_ch == null) { if(log.isWarnEnabled()) - log.warn("State provider " + channel.getLocalAddress() + log.warn("State provider " + channel.getAddress() + " does not have service with id " + id + ", returning null state"); @@ -760,7 +755,7 @@ mux_ch=services.get(appl_id); if(mux_ch == null) { - log.error("State receiver " + channel.getLocalAddress() + log.error("State receiver " + channel.getAddress() + " does not have service with id " + appl_id); } @@ -820,7 +815,7 @@ ServiceInfo si=new ServiceInfo(ServiceInfo.ACK, info.service, info.host, null); MuxHeader hdr=new MuxHeader(si); - ack.putHeader(NAME, hdr); + ack.putHeader(ID, hdr); if(channel.isConnected()) channel.send(ack); @@ -934,7 +929,7 @@ * Fetches the service states from everyone else in the cluster. Once all * states have been received and inserted into service_state, compute a * service view (a copy of MergeView) for each service and pass it up - * + * * @param view */ private void handleMergeView(MergeView view) throws Exception { @@ -944,7 +939,7 @@ byte[] data=Util.objectToByteBuffer(new HashSet(services.keySet())); - //loop and keep sending our service list until either + //loop and keep sending our service list until either //we hit timeout or we get notification of merge completed //http://jira.jboss.com/jira/browse/JGRP-665 while(time_to_wait > 0 && !services_merged.get()) { @@ -1055,7 +1050,7 @@ * hosts. Call viewAccepted() on the MuxChannel which corresponds with * service. If no members are removed or added from/to view, this is a * no-op. - * + * * @param hosts * List

    * @return the servicd view (a modified copy of the real view), or null if @@ -1113,52 +1108,7 @@ return null; } - private class MultiplexerChannelListener extends ChannelListenerAdapter { - - //handle reconnecting of services after being shunned and - //then reconnected back - @Override - public void channelReconnected(Address addr) { - if(log.isDebugEnabled()) - log.debug("Reconnecting services " + services.keySet()); - - for(MuxChannel mux_ch:services.values()) { - try { - if(log.isDebugEnabled()) - log.debug("Reconnecting service " + mux_ch.getId()); - mux_ch.open(); - boolean reconnect=((Boolean)mux_ch.getOpt(Channel.AUTO_RECONNECT)).booleanValue(); - boolean getState=((Boolean)mux_ch.getOpt(Channel.AUTO_GETSTATE)).booleanValue(); - boolean fetchAndGetState=reconnect && getState; - if(fetchAndGetState) { - mux_ch.connect(mux_ch.getClusterName(), null, null, 10000); - mux_ch.fireChannelReconnected(mux_ch.getLocalAddress()); - } - else { - if(reconnect) { - mux_ch.connect(mux_ch.getClusterName()); - mux_ch.fireChannelReconnected(mux_ch.getLocalAddress()); - } - if(getState) { - mux_ch.getState(null, 5000); - } - } - } - catch(ChannelException e) { - if(log.isErrorEnabled()) - log.error("MuxChannel reconnect failed " + e); - } - } - } - - @Override - public void channelShunned() { - for(MuxChannel mux_ch:services.values()) { - mux_ch.fireChannelShunned(); - } - } - } private static class Task implements Runnable { Exchanger exchanger; @@ -1217,4 +1167,4 @@ } } -} +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/mux/MuxChannel.java libjgroups-java-2.12.2.Final/src/org/jgroups/mux/MuxChannel.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/mux/MuxChannel.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/mux/MuxChannel.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,6 +1,7 @@ package org.jgroups.mux; import org.jgroups.*; +import org.jgroups.conf.ClassConfigurator; import org.jgroups.annotations.Experimental; import org.jgroups.stack.ProtocolStack; @@ -27,7 +28,6 @@ * @see JChannelFactory#createMultiplexerChannel(String, String) * @see Multiplexer * @since 2.4 - * @version $Id: MuxChannel.java,v 1.54 2008/05/29 08:22:05 belaban Exp $ */ @Experimental(comment="because of impedance mismatches between a MuxChannel and JChannel, this might get deprecated " + "in the future. The replacement would be a shared transport (see the documentation for details)") @@ -37,7 +37,7 @@ /* * Header identifier */ - private static final String name="MUX"; + private static final short ID=ClassConfigurator.getProtocolId(MuxChannel.class); /* * MuxChannel service ID @@ -97,7 +97,7 @@ return mux.getChannel().getClusterName(); } - public Address getLocalAddress() { + public Address getAddress() { return mux.getLocalAddress(); } @@ -344,7 +344,7 @@ } public void send(Message msg) throws ChannelNotConnectedException,ChannelClosedException { - msg.putHeader(name, hdr); + msg.putHeader(ID, hdr); mux.getChannel().send(msg); if(stats) { sent_msgs++; @@ -360,7 +360,7 @@ public void down(Event evt) { if(evt.getType() == Event.MSG) { Message msg=(Message)evt.getArg(); - msg.putHeader(name, hdr); + msg.putHeader(ID, hdr); } mux.getChannel().down(evt); } @@ -368,7 +368,7 @@ public Object downcall(Event evt) { if(evt.getType() == Event.MSG) { Message msg=(Message)evt.getArg(); - msg.putHeader(name, hdr); + msg.putHeader(ID, hdr); } return mux.getChannel().downcall(evt); } @@ -386,7 +386,7 @@ // unless service runs on a specified target node // http://jira.jboss.com/jira/browse/JGRP-401 Address service_view_coordinator=mux.getStateProvider(target, id); - Address tmp=getLocalAddress(); + Address tmp=getAddress(); if(service_view_coordinator != null) target=service_view_coordinator; @@ -399,22 +399,11 @@ else { View serviceView=mux.getServiceView(getId()); boolean fetchState=serviceView != null && serviceView.size() > 1; - if(fetchState) { - return mux.getState(target, my_id, timeout); - } - else { - return false; - } + return fetchState && mux.getState(target, my_id, timeout); } } - void fireChannelShunned(){ - notifyChannelShunned(); - } - - void fireChannelReconnected(Address address){ - notifyChannelReconnected(address); - } + public void returnState(byte[] state) { mux.getChannel().returnState(state, id); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/mux/MuxHeader.java libjgroups-java-2.12.2.Final/src/org/jgroups/mux/MuxHeader.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/mux/MuxHeader.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/mux/MuxHeader.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,7 +2,6 @@ import org.jgroups.Global; import org.jgroups.Header; -import org.jgroups.util.Streamable; import org.jgroups.util.Util; import java.io.*; @@ -10,14 +9,12 @@ /** * Header used for multiplexing and de-multiplexing between service components on top of a Multiplexer (Channel) * @author Bela Ban - * @version $Id: MuxHeader.java,v 1.8 2008/01/22 11:45:53 belaban Exp $ */ -public class MuxHeader extends Header implements Streamable { +public class MuxHeader extends Header { String id=null; /** Used for service state communication between Multiplexers */ ServiceInfo info; - private static final long serialVersionUID=9197570523315316128L; public MuxHeader() { } @@ -34,15 +31,6 @@ return id; } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeUTF(id); - out.writeObject(info); - } - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - id=in.readUTF(); - info=(ServiceInfo)in.readObject(); - } public int size() { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/mux/ServiceInfo.java libjgroups-java-2.12.2.Final/src/org/jgroups/mux/ServiceInfo.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/mux/ServiceInfo.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/mux/ServiceInfo.java 2011-10-18 11:22:35.000000000 +0000 @@ -10,7 +10,6 @@ /** * Class used for service state communication between Multiplexers * @author Bela Ban - * @version $Id: ServiceInfo.java,v 1.8 2008/01/16 06:55:08 vlada Exp $ */ public class ServiceInfo implements Externalizable, Streamable { public static final byte SERVICE_UP = 3; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/persistence/DBPersistenceManager.java libjgroups-java-2.12.2.Final/src/org/jgroups/persistence/DBPersistenceManager.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/persistence/DBPersistenceManager.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/persistence/DBPersistenceManager.java 2011-10-18 11:22:35.000000000 +0000 @@ -11,8 +11,8 @@ */ -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.annotations.Unsupported; import java.io.*; @@ -92,8 +92,8 @@ /** - * used to intitiailize complete DB access. THis method will use - * existing database to create schema (if it doesnt exist) and + * used to initialize complete DB access. This method will use + * existing database to create schema (if it doesn't exist) and * get PersistenceManager in usable condition * @param in * @exception Exception; @@ -519,7 +519,7 @@ */ /** - * This method will be invoked by defauly by each persistence + * This method will be invoked by default by each persistence * manager to read from a default location or one provided by * the caller. * @return void; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/persistence/PersistenceFactory.java libjgroups-java-2.12.2.Final/src/org/jgroups/persistence/PersistenceFactory.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/persistence/PersistenceFactory.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/persistence/PersistenceFactory.java 2011-10-18 11:22:35.000000000 +0000 @@ -8,8 +8,8 @@ */ -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.util.Util; import org.jgroups.annotations.Unsupported; @@ -20,6 +20,7 @@ @Unsupported +@Deprecated public class PersistenceFactory { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/persistence/PersistenceManager.java libjgroups-java-2.12.2.Final/src/org/jgroups/persistence/PersistenceManager.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/persistence/PersistenceManager.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/persistence/PersistenceManager.java 2011-10-18 11:22:35.000000000 +0000 @@ -13,6 +13,7 @@ import java.io.Serializable; import java.util.Map; +@Deprecated @Unsupported public interface PersistenceManager { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/PhysicalAddress.java libjgroups-java-2.12.2.Final/src/org/jgroups/PhysicalAddress.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/PhysicalAddress.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/PhysicalAddress.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,8 @@ +package org.jgroups; + +/** + * Represents a physical (as opposed to logical) address + * @author Bela Ban + */ +public interface PhysicalAddress extends Address { +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/AuthHeader.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/AuthHeader.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/AuthHeader.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/AuthHeader.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,7 +2,6 @@ import org.jgroups.Header; import org.jgroups.auth.AuthToken; -import org.jgroups.util.Streamable; import org.jgroups.util.Util; import java.io.*; @@ -10,9 +9,9 @@ * AuthHeader is a holder object for the token that is passed from the joiner to the coordinator * @author Chris Mills */ -public class AuthHeader extends Header implements Streamable{ +public class AuthHeader extends Header { private AuthToken token=null; - + public AuthHeader(){ } /** @@ -31,13 +30,6 @@ return this.token; } - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - this.token = (AuthToken)in.readObject(); - } - - public void writeExternal(ObjectOutput out) throws IOException { - out.writeObject(this.token); - } public void writeTo(DataOutputStream out) throws IOException { Util.writeAuthToken(this.token, out); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/AUTH.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/AUTH.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/AUTH.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/AUTH.java 2011-10-18 11:22:35.000000000 +0000 @@ -4,6 +4,7 @@ import org.jgroups.Address; import org.jgroups.Event; import org.jgroups.Message; +import org.jgroups.conf.ClassConfigurator; import org.jgroups.annotations.Property; import org.jgroups.auth.AuthToken; import org.jgroups.auth.X509Token; @@ -11,8 +12,8 @@ import org.jgroups.protocols.pbcast.JoinRsp; import org.jgroups.stack.Protocol; -import java.util.List; import java.util.LinkedList; +import java.util.List; /** @@ -22,16 +23,17 @@ */ public class AUTH extends Protocol { - static final String NAME = "AUTH"; - /** * used on the coordinator to authentication joining member requests against */ private AuthToken auth_plugin=null; + private static final short gms_id=ClassConfigurator.getProtocolId(GMS.class); + public AUTH() { + name="AUTH"; } @@ -40,11 +42,10 @@ public void setAuthClass(String class_name) throws Exception { Object obj=Class.forName(class_name).newInstance(); auth_plugin=(AuthToken)obj; + auth_plugin.setAuth(this); } - public final String getName() { - return AUTH.NAME; - } + public String getAuthClass() {return auth_plugin != null? auth_plugin.getClass().getName() : null;} protected List getConfigurableObjects() { List retval=new LinkedList(); @@ -59,33 +60,10 @@ X509Token tmp=(X509Token)auth_plugin; tmp.setCertificate(); } - + auth_plugin.init(); } - /** - * Used to create a failed JOIN_RSP message to pass back down the stack - * @param joiner The Address of the requesting member - * @param message The failure message to send back to the joiner - * @return An Event containing a GmsHeader with a JoinRsp object - */ - private Event createFailureEvent(Address joiner, String message){ - Message msg = new Message(joiner, null, null); - - if(log.isDebugEnabled()){ - log.debug("Creating JoinRsp with failure message - " + message); - } - JoinRsp joinRes = new JoinRsp(message); - //need to specify the error message on the JoinRsp object once it's been changed - GMS.GmsHeader gmsHeader = new GMS.GmsHeader(GMS.GmsHeader.JOIN_RSP, joinRes); - msg.putHeader(GMS.name, gmsHeader); - - if(log.isDebugEnabled()){ - log.debug("GMSHeader created for failure JOIN_RSP"); - } - - return new Event(Event.MSG, msg); - } /** * An event was received from the layer below. Usually the current layer will want to examine @@ -97,65 +75,81 @@ * the stack using up_prot.up(). */ public Object up(Event evt) { - GMS.GmsHeader hdr = isJoinMessage(evt); - if((hdr != null) && (hdr.getType() == GMS.GmsHeader.JOIN_REQ)){ - if(log.isDebugEnabled()){ - log.debug("AUTH got up event"); - } - //we found a join message - now try and get the AUTH Header - Message msg = (Message)evt.getArg(); + GMS.GmsHeader hdr=getGMSHeader(evt); + if(hdr == null) + return up_prot.up(evt); - if((msg.getHeader(AUTH.NAME) != null) && (msg.getHeader(AUTH.NAME) instanceof AuthHeader)){ - AuthHeader authHeader = (AuthHeader)msg.getHeader(AUTH.NAME); + if(hdr.getType() == GMS.GmsHeader.JOIN_REQ || hdr.getType() == GMS.GmsHeader.JOIN_REQ_WITH_STATE_TRANSFER + || hdr.getType() == GMS.GmsHeader.MERGE_REQ) { // we found a join or merge message - now try and get the AUTH Header + Message msg=(Message)evt.getArg(); + if((msg.getHeader(this.id) != null) && (msg.getHeader(this.id) instanceof AuthHeader)) { + AuthHeader authHeader=(AuthHeader)msg.getHeader(this.id); - if(authHeader != null){ + if(authHeader != null) { //Now we have the AUTH Header we need to validate it - if(this.auth_plugin.authenticate(authHeader.getToken(), msg)){ - //valid token - if(log.isDebugEnabled()){ - log.debug("AUTH passing up event"); - } - up_prot.up(evt); - }else{ - //invalid token - if(log.isWarnEnabled()){ - log.warn("AUTH failed to validate AuthHeader token"); - } - sendRejectionMessage(msg.getSrc(), createFailureEvent(msg.getSrc(), "Authentication failed")); + if(this.auth_plugin.authenticate(authHeader.getToken(), msg)) { + return up_prot.up(evt); } - }else{ - //Invalid AUTH Header - need to send failure message - if(log.isWarnEnabled()){ - log.warn("AUTH failed to get valid AuthHeader from Message"); + else { + if(log.isWarnEnabled()) + log.warn("failed to validate AuthHeader token"); + sendRejectionMessage(hdr.getType(), msg.getSrc(), "Authentication failed"); + return null; } - sendRejectionMessage(msg.getSrc(), createFailureEvent(msg.getSrc(), "Failed to find valid AuthHeader in Message")); } - }else{ - if(log.isDebugEnabled()){ - log.debug("No AUTH Header Found"); + else { + //Invalid AUTH Header - need to send failure message + if(log.isWarnEnabled()) + log.warn("AUTH failed to get valid AuthHeader from Message"); + sendRejectionMessage(hdr.getType(), msg.getSrc(), "Failed to find valid AuthHeader in Message"); + return null; } - //should be a failure - sendRejectionMessage(msg.getSrc(), createFailureEvent(msg.getSrc(), "Failed to find an AuthHeader in Message")); } - }else{ - //if debug - if(log.isDebugEnabled()){ - log.debug("Message not a JOIN_REQ - ignoring it"); + else { + sendRejectionMessage(hdr.getType(), msg.getSrc(), "Failed to find an AuthHeader in Message"); + return null; } - return up_prot.up(evt); } - return null; + return up_prot.up(evt); } - private void sendRejectionMessage(Address dest, Event join_rsp) { - if(dest == null) { - log.error("destination is null, cannot send JOIN rejection message to null destination"); - return; + + protected void sendRejectionMessage(byte type, Address dest, String error_msg) { + switch(type) { + case GMS.GmsHeader.JOIN_REQ: + case GMS.GmsHeader.JOIN_REQ_WITH_STATE_TRANSFER: + sendJoinRejectionMessage(dest, error_msg); + break; + case GMS.GmsHeader.MERGE_REQ: + sendMergeRejectionMessage(dest, error_msg); + break; + default: + log.error("type " + type + " unknown"); + break; } - down_prot.down(new Event(Event.ENABLE_UNICASTS_TO, dest)); - down_prot.down(join_rsp); - down_prot.down(new Event(Event.DISABLE_UNICASTS_TO, dest)); + } + + protected void sendJoinRejectionMessage(Address dest, String error_msg) { + if(dest == null) + return; + + Message msg = new Message(dest, null, null); + JoinRsp joinRes=new JoinRsp(error_msg); // specify the error message on the JoinRsp + + GMS.GmsHeader gmsHeader=new GMS.GmsHeader(GMS.GmsHeader.JOIN_RSP, joinRes); + msg.putHeader(gms_id, gmsHeader); + down_prot.down(new Event(Event.MSG, msg)); + } + + protected void sendMergeRejectionMessage(Address dest, String error_msg) { + Message msg=new Message(dest, null, null); + msg.setFlag(Message.OOB); + GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.MERGE_RSP); + hdr.setMergeRejected(true); + msg.putHeader(gms_id, hdr); + if(log.isDebugEnabled()) log.debug("merge response=" + hdr); + down_prot.down(new Event(Event.MSG, msg)); } /** @@ -167,42 +161,30 @@ * a new response event back up the stack using up_prot.up(). */ public Object down(Event evt) { - GMS.GmsHeader hdr = isJoinMessage(evt); - if((hdr != null) && (hdr.getType() == GMS.GmsHeader.JOIN_REQ)){ - if(log.isDebugEnabled()){ - log.debug("AUTH got down event"); - } + GMS.GmsHeader hdr = getGMSHeader(evt); + if((hdr != null) && (hdr.getType() == GMS.GmsHeader.JOIN_REQ + || hdr.getType() == GMS.GmsHeader.JOIN_REQ_WITH_STATE_TRANSFER + || hdr.getType() == GMS.GmsHeader.MERGE_REQ)) { //we found a join request message - now add an AUTH Header Message msg = (Message)evt.getArg(); AuthHeader authHeader = new AuthHeader(); authHeader.setToken(this.auth_plugin); - msg.putHeader(AUTH.NAME, authHeader); - - if(log.isDebugEnabled()){ - log.debug("AUTH passing down event"); - } + msg.putHeader(this.id, authHeader); } - - if((hdr != null) && (hdr.getType() == GMS.GmsHeader.JOIN_RSP)){ - if(log.isDebugEnabled()){ - log.debug(hdr.toString()); - } - } - return down_prot.down(evt); } /** - * Used to check if the message type is a Gms message + * Get the header from a GMS message * @param evt The event object passed in to AUTH * @return A GmsHeader object or null if the event contains a message of a different type */ - private static GMS.GmsHeader isJoinMessage(Event evt){ + private static GMS.GmsHeader getGMSHeader(Event evt){ Message msg; switch(evt.getType()){ case Event.MSG: msg = (Message)evt.getArg(); - Object obj = msg.getHeader("GMS"); + Object obj = msg.getHeader(gms_id); if(obj == null || !(obj instanceof GMS.GmsHeader)){ return null; } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/BARRIER.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/BARRIER.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/BARRIER.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/BARRIER.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,15 +1,17 @@ package org.jgroups.protocols; import org.jgroups.Event; +import org.jgroups.Message; +import org.jgroups.annotations.MBean; import org.jgroups.annotations.ManagedAttribute; import org.jgroups.annotations.Property; import org.jgroups.stack.Protocol; import org.jgroups.util.TimeScheduler; +import org.jgroups.util.Util; -import java.util.Set; import java.util.HashSet; import java.util.Iterator; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -26,12 +28,11 @@ * When an OPEN_BARRIER event is received, we simply open the barrier again and let all messages pass in the up * direction. This is done by releasing the WL. * @author Bela Ban - * @version $Id: BARRIER.java,v 1.13 2008/10/21 13:20:06 vlada Exp $ */ +@MBean(description="Blocks all multicast threads when closed") public class BARRIER extends Protocol { - @Property(description="Max time barrier can be closed. Default is 60000 msec") - @ManagedAttribute(writable=true,description="Max time barrier can be closed. Default is 60000 msec") + @Property(description="Max time barrier can be closed. Default is 60000 ms") long max_close_time=60000; // how long can the barrier stay closed (in ms) ? 0 means forever final Lock lock=new ReentrantLock(); final AtomicBoolean barrier_closed=new AtomicBoolean(false); @@ -39,16 +40,12 @@ /** signals to waiting threads that the barrier is open again */ Condition barrier_opened=lock.newCondition(); Condition no_msgs_pending=lock.newCondition(); - ConcurrentMap in_flight_threads=new ConcurrentHashMap(); + ConcurrentMap in_flight_threads=Util.createConcurrentMap(); Future barrier_opener_future=null; TimeScheduler timer; private static final Object NULL=new Object(); - public String getName() { - return "BARRIER"; - } - @ManagedAttribute public boolean isClosed() { return barrier_closed.get(); @@ -99,6 +96,9 @@ public Object up(Event evt) { switch(evt.getType()) { case Event.MSG: + Message msg=(Message)evt.getArg(); + if(msg.getDest() != null) // https://issues.jboss.org/browse/JGRP-1341: let unicast messages pass + return up_prot.up(evt); Thread current_thread=Thread.currentThread(); in_flight_threads.put(current_thread, NULL); if(barrier_closed.get()) { @@ -125,16 +125,16 @@ return up_prot.up(evt); } finally { - lock.lock(); - try { - if(in_flight_threads.remove(current_thread) == NULL && - in_flight_threads.isEmpty() && - barrier_closed.get()) { + if(in_flight_threads.remove(current_thread) == NULL && + barrier_closed.get() && + in_flight_threads.isEmpty()) { + lock.lock(); + try { no_msgs_pending.signalAll(); } - } - finally { - lock.unlock(); + finally { + lock.unlock(); + } } } case Event.CLOSE_BARRIER: @@ -169,14 +169,14 @@ } } if(!in_flight_threads.isEmpty()) { - try { + try { no_msgs_pending.await(1000, TimeUnit.MILLISECONDS); - } - catch(InterruptedException e) { + } + catch(InterruptedException e) { + } } } } - } finally { for(Thread thread: threads) in_flight_threads.put(thread, NULL); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/BasicTCP.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/BasicTCP.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/BasicTCP.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/BasicTCP.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,154 +2,97 @@ import org.jgroups.Address; import org.jgroups.Event; -import org.jgroups.Message; -import org.jgroups.annotations.ManagedAttribute; +import org.jgroups.PhysicalAddress; +import org.jgroups.annotations.DeprecatedProperty; import org.jgroups.annotations.Property; import org.jgroups.stack.Protocol; -import org.jgroups.util.BoundedList; -import org.jgroups.util.Util; import java.net.InetAddress; -import java.net.UnknownHostException; import java.util.Collection; +import java.util.HashSet; import java.util.Set; /** - * Shared base class for tcpip protocols + * Shared base class for TCP protocols * @author Scott Marlow */ +@DeprecatedProperty(names={"suspect_on_send_failure", "skip_suspected_members"}) public abstract class BasicTCP extends TP { /* ----------------------------------------- Properties -------------------------------------------------- */ - - @Property(description="Should unicast messages to suspected members be dropped. Default is false") - boolean skip_suspected_members=true; - - @Property(description="If cannot send a message to P (on an exception), should SUSPECT message be raised. Default is false") - boolean suspect_on_send_failure=false; - - @ManagedAttribute(description="Reaper interval", writable=true) @Property(description="Reaper interval in msec. Default is 0 (no reaping)") protected long reaper_interval=0; // time in msecs between connection reaps - @ManagedAttribute(description="Connection expiration time", writable=true) - @Property(description="Max time connection can be idle before being reaped") + @Property(description="Max time connection can be idle before being reaped (in ms)") protected long conn_expire_time=0; // max time a conn can be idle before being reaped - @Property(description="Should separate send queues be used for each connection. Default is true") + @Property(description="Should separate send queues be used for each connection") boolean use_send_queues=true; - @Property(description="Max number of messages in a send queue. Default is 10000 messages") + @Property(description="Max number of messages in a send queue") int send_queue_size=10000; - @Property(description="Receiver buffer size in bytes. Default is 150000 bytes") + @Property(description="Receiver buffer size in bytes") int recv_buf_size=150000; - @Property(description="Send buffer size in bytes. Default is 150000 bytes") + @Property(description="Send buffer size in bytes") int send_buf_size=150000; - @Property(description="Max time allowed for a socket creation in ConnectionTable. Default is 2000 msec") - int sock_conn_timeout=2000; // max time in millis for a socket creation in ConnectionTable + @Property(description="Max time allowed for a socket creation in connection table") + int sock_conn_timeout=2000; // max time in millis for a socket creation in connection table - @Property(description="Max time to block on reading of peer address. Default is 1000 msec") + @Property(description="Max time to block on reading of peer address") int peer_addr_read_timeout=1000; // max time to block on reading of peer address - @Property(description="Should TCP no delay flag be turned on. Default is false") - boolean tcp_nodelay=false; + @Property(description="Should TCP no delay flag be turned on") + boolean tcp_nodelay=true; @Property(description="SO_LINGER in msec. Default of -1 disables it") int linger=-1; // SO_LINGER (number of ms, -1 disables it) + @Property(description="Use \"external_addr\" if you have hosts on different networks, behind " + + "firewalls. On each firewall, set up a port forwarding rule (sometimes called \"virtual server\") to " + + "the local IP (e.g. 192.168.1.100) of the host then on each host, set \"external_addr\" TCP transport " + + "parameter to the external (public IP) address of the firewall.") + InetAddress external_addr = null ; /* --------------------------------------------- Fields ------------------------------------------------------ */ - /** - * List the maintains the currently suspected members. This is used so we - * don't send too many SUSPECT events up the stack (one per message !) - */ - final BoundedList
    suspected_mbrs=new BoundedList
    (20); - - protected InetAddress external_addr=null; // the IP address which is broadcast to other group members - - protected BasicTCP() { super(); } - + + public boolean supportsMulticasting() { + return false; + } + public long getReaperInterval() {return reaper_interval;} public void setReaperInterval(long reaper_interval) {this.reaper_interval=reaper_interval;} public long getConnExpireTime() {return conn_expire_time;} public void setConnExpireTime(long conn_expire_time) {this.conn_expire_time=conn_expire_time;} - @Property(name="external_addr", description="Use \"external_addr\" if you have hosts on different networks, behind " + - "firewalls. On each firewall, set up a port forwarding rule (sometimes called \"virtual server\") to " + - "the local IP (e.g. 192.168.1.100) of the host then on each host, set \"external_addr\" TCP transport " + - "parameter to the external (public IP) address of the firewall. ") - public void setExternalAddress(String addr) throws UnknownHostException { - external_addr=InetAddress.getByName(addr); - } - public void init() throws Exception { super.init(); - Util.checkBufferSize(getName() + ".recv_buf_size", recv_buf_size); - Util.checkBufferSize(getName() + ".send_buf_size", send_buf_size); - if(!isSingleton() && bind_port <= 0) { - Protocol dynamic_discovery_prot=stack.findProtocol("MPING"); - if(dynamic_discovery_prot == null) - dynamic_discovery_prot=stack.findProtocol("TCPGOSSIP"); - - if(dynamic_discovery_prot != null) { - if(log.isDebugEnabled()) - log.debug("dynamic discovery is present (" + dynamic_discovery_prot + "), so start_port=" + bind_port + " is okay"); - } - else { + Discovery discovery_prot=(Discovery)stack.findProtocol(Discovery.class); + if(discovery_prot != null && !discovery_prot.isDynamic()) throw new IllegalArgumentException("start_port cannot be set to " + bind_port + - ", as no dynamic discovery protocol (e.g. MPING or TCPGOSSIP) has been detected."); - } + ", as no dynamic discovery protocol (e.g. MPING or TCPGOSSIP) has been detected."); } } - - public void sendToAllMembers(byte[] data, int offset, int length) throws Exception { - Set
    mbrs; - - synchronized(members) { - mbrs=(Set
    )members.clone(); - } - for(Address dest: mbrs) { - sendToSingleMember(dest, data, offset, length); - } + public void sendMulticast(byte[] data, int offset, int length) throws Exception { + sendToAllPhysicalAddresses(data, offset, length); } - public void sendToSingleMember(Address dest, byte[] data, int offset, int length) throws Exception { + public void sendUnicast(PhysicalAddress dest, byte[] data, int offset, int length) throws Exception { if(log.isTraceEnabled()) log.trace("dest=" + dest + " (" + length + " bytes)"); - if(skip_suspected_members) { - if(suspected_mbrs.contains(dest)) { - if(log.isTraceEnabled()) - log.trace("will not send unicast message to " + dest + " as it is currently suspected"); - return; - } - } - - try { - send(dest, data, offset, length); - } - catch(Exception e) { - if(log.isTraceEnabled()) - log.trace("failure sending message to " + dest, e); - if(suspect_on_send_failure && members.contains(dest)) { - if(!suspected_mbrs.contains(dest)) { - suspected_mbrs.add(dest); - up_prot.up(new Event(Event.SUSPECT, dest)); - } - } - } + send(dest, data, offset, length); } public String getInfo() { @@ -158,16 +101,6 @@ return sb.toString(); } - public void postUnmarshalling(Message msg, Address dest, Address src, boolean multicast) { - if(multicast) - msg.setDest(null); - else - msg.setDest(dest); - } - - public void postUnmarshallingList(Message msg, Address dest, boolean multicast) { - postUnmarshalling(msg, dest, null, multicast); - } public abstract String printConnections(); @@ -175,20 +108,21 @@ public abstract void retainAll(Collection
    members); - /** ConnectionTable.Receiver interface */ + /** ConnectionMap.Receiver interface */ public void receive(Address sender, byte[] data, int offset, int length) { - receive(local_addr, sender, data, offset, length); + super.receive(sender, data, offset, length); } protected Object handleDownEvent(Event evt) { Object ret=super.handleDownEvent(evt); if(evt.getType() == Event.VIEW_CHANGE) { - suspected_mbrs.clear(); - retainAll(members); // remove all connections from the ConnectionTable which are not members - } - else if(evt.getType() == Event.UNSUSPECT) { - Address suspected_mbr=(Address)evt.getArg(); - suspected_mbrs.remove(suspected_mbr); + Set
    physical_mbrs=new HashSet
    (); + for(Address addr: members) { + PhysicalAddress physical_addr=getPhysicalAddressFromCache(addr); + if(physical_addr != null) + physical_mbrs.add(physical_addr); + } + retainAll(physical_mbrs); // remove all connections which are not members } return ret; } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/BPING.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/BPING.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/BPING.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/BPING.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,171 @@ +package org.jgroups.protocols; + +import org.jgroups.Event; +import org.jgroups.Global; +import org.jgroups.Message; +import org.jgroups.annotations.Experimental; +import org.jgroups.annotations.Property; +import org.jgroups.util.Buffer; +import org.jgroups.util.ExposedByteArrayInputStream; +import org.jgroups.util.ExposedByteArrayOutputStream; +import org.jgroups.util.Util; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.SocketException; + +/** + * Broadcast PING. Uses UDP broadcasts to discover initial membership. This protocol is useless in IPv6 environments, as + * IPv6 has no notion of broadcast addresses. Use IP multicasts instead (e.g. PING or MPING) when running in IPv6. + * @author Bela Ban + * @since 2.12 + */ +@Experimental +public class BPING extends PING implements Runnable { + + + /* ----------------------------------------- Properties -------------------------------------------------- */ + + @Property(description="Target address for broadcasts. This should be restricted to the local subnet, e.g. 192.168.1.255") + protected String dest="255.255.255.255"; + + @Property(description="Port for discovery packets", systemProperty=Global.BPING_BIND_PORT) + protected int bind_port=8555; + + @Property(description="Sends discovery packets to ports 8555 to (8555+port_range)") + protected int port_range=5; + + + + /* --------------------------------------------- Fields ------------------------------------------------------ */ + protected DatagramSocket sock=null; + protected volatile Thread receiver=null; + protected InetAddress dest_addr; + + + + public BPING() { + } + + public int getBindPort() { + return bind_port; + } + + public void setBindPort(int bind_port) { + this.bind_port=bind_port; + } + + + + public void init() throws Exception { + super.init(); + dest_addr=InetAddress.getByName(dest); + if(log.isDebugEnabled()) + log.debug("listening on " + bind_port); + } + + public void start() throws Exception { + for(int i=bind_port; i < bind_port+port_range; i++) { + try { + sock=getSocketFactory().createDatagramSocket(Global.BPING_SOCK, i); + break; + } + catch(Throwable t) { + if(i >= bind_port+port_range-1) + throw new RuntimeException("failed to open a port in range [" + bind_port + " - " + (bind_port+port_range) + "]", t); + } + } + + sock.setBroadcast(true); + startReceiver(); + super.start(); + } + + + private void startReceiver() { + if(receiver == null || !receiver.isAlive()) { + receiver=new Thread(Util.getGlobalThreadGroup(), this, "ReceiverThread"); + receiver.setDaemon(true); + receiver.start(); + if(log.isTraceEnabled()) + log.trace("receiver thread started"); + } + } + + public void stop() { + Util.close(sock); + sock=null; + receiver=null; + super.stop(); + } + + void sendMcastDiscoveryRequest(Message msg) { + DataOutputStream out=null; + + try { + if(msg.getSrc() == null) + msg.setSrc(local_addr); + ExposedByteArrayOutputStream out_stream=new ExposedByteArrayOutputStream(128); + out=new DataOutputStream(out_stream); + msg.writeTo(out); + out.flush(); // flushes contents to out_stream + Buffer buf=new Buffer(out_stream.getRawBuffer(), 0, out_stream.size()); + + discovery_reception.reset(); + for(int i=bind_port; i < bind_port+port_range; i++) { + DatagramPacket packet=new DatagramPacket(buf.getBuf(), buf.getOffset(), buf.getLength(), dest_addr, i); + sock.send(packet); + } + + waitForDiscoveryRequestReception(); + } + catch(IOException ex) { + log.error("failed sending discovery request", ex); + } + finally { + Util.close(out); + } + } + + + + public void run() { + final byte[] receive_buf=new byte[65535]; + DatagramPacket packet=new DatagramPacket(receive_buf, receive_buf.length); + byte[] data; + ByteArrayInputStream inp_stream; + DataInputStream inp=null; + Message msg; + + while(sock != null && receiver != null && Thread.currentThread().equals(receiver)) { + packet.setData(receive_buf, 0, receive_buf.length); + try { + sock.receive(packet); + data=packet.getData(); + inp_stream=new ExposedByteArrayInputStream(data, 0, data.length); + inp=new DataInputStream(inp_stream); + msg=new Message(); + msg.readFrom(inp); + up(new Event(Event.MSG, msg)); + } + catch(SocketException socketEx) { + break; + } + catch(Throwable ex) { + log.error("failed receiving packet (from " + packet.getSocketAddress() + ")", ex); + } + finally { + Util.close(inp); + } + } + if(log.isTraceEnabled()) + log.trace("receiver thread terminated"); + } + + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/BSH.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/BSH.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/BSH.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/BSH.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,24 +1,21 @@ -// $Id: BSH.java,v 1.20 2008/04/08 14:51:21 belaban Exp $ package org.jgroups.protocols; import bsh.EvalError; import bsh.Interpreter; -import org.jgroups.Address; -import org.jgroups.Event; -import org.jgroups.Header; -import org.jgroups.Message; import org.jgroups.annotations.Experimental; +import org.jgroups.annotations.Property; +import org.jgroups.annotations.Unsupported; import org.jgroups.stack.Protocol; import org.jgroups.util.Util; +import org.jgroups.Global; -import java.io.IOException; -import java.io.ObjectInput; -import java.io.ObjectOutput; -import java.io.Serializable; - - +import java.io.*; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; /** @@ -31,197 +28,108 @@ * Time: 1:57:07 PM * @author Bela Ban */ -@Experimental -public class BSH extends Protocol { - static final String name="BSH"; - Interpreter interpreter=null; - - public BSH() { - } +@Experimental @Unsupported +public class BSH extends Protocol implements Runnable { + protected Interpreter interpreter=null; + protected ServerSocket srv_sock; + protected Thread acceptor; + protected final List sockets=new ArrayList(); - public String getName() { - return name; - } + @Property(description="Port on which the interpreter should listen for requests. 0 is an ephemeral port") + int bind_port=0; - public void init() throws Exception { + public BSH() { } public void start() throws Exception { + srv_sock=Util.createServerSocket(getSocketFactory(), Global.BSH_SRV_SOCK, null, bind_port); + log.info("Server socket listening at " + srv_sock.getLocalSocketAddress()); + acceptor=new Thread(this); + acceptor.start(); } public void stop() { - if(interpreter != null) - destroyInterpreter(); - } - - public void destroy() { - } + Util.close(srv_sock); + if(acceptor != null && acceptor.isAlive()) + acceptor.interrupt(); - public Object up(Event evt) { - Header h; - Message msg; - int type; - - if(evt.getType() == Event.MSG) { - msg=(Message)evt.getArg(); - h=msg.getHeader(name); - if(h instanceof BshHeader) { - type=((BshHeader)h).type; - switch(type) { - case BshHeader.REQ: - handleRequest(msg.getSrc(), msg.getBuffer()); - return null; - case BshHeader.RSP: - msg.putHeader(name, h); - up_prot.up(evt); - return null; - case BshHeader.DESTROY_INTERPRETER: - destroyInterpreter(); - return null; - default: - if(log.isErrorEnabled()) log.error("header type was not REQ as expected (was " + type + ')'); - return null; - } - } + Util.sleep(500); + if(!sockets.isEmpty()) { + for(Socket sock: sockets) + Util.close(sock); } - return up_prot.up(evt); } - void handleRequest(Address sender, byte[] buf) { - Object retval; - String code; + public void run() { + while(srv_sock != null && !srv_sock.isClosed()) { + try { + final Socket sock=srv_sock.accept(); + sockets.add(sock); + createInterpreter(); - if(buf == null) { - if(log.isErrorEnabled()) log.error("buffer was null"); - return; + new Thread() { + public void run() { + try { + InputStream input=sock.getInputStream(); + OutputStream out=sock.getOutputStream(); + BufferedReader reader=new BufferedReader(new InputStreamReader(input)); + + while(!sock.isClosed()) { + String line=reader.readLine(); + if(line == null || line.length() == 0) + continue; + try { + Object retval=interpreter.eval(line); + if(retval != null) { + String rsp=retval.toString(); + byte[] buf=rsp.getBytes(); + out.write(buf, 0, buf.length); + out.flush(); + } + if(log.isTraceEnabled()) { + log.trace(line); + if(retval != null) + log.trace(retval); + } + } + catch(EvalError evalError) { + evalError.printStackTrace(); + } + } + } + catch(IOException e) { + e.printStackTrace(); + } + finally { + Util.close(sock); + sockets.remove(sock); + } + } + }.start(); + } + catch(IOException e) { + } } + } - code=new String(buf); + synchronized void createInterpreter() { // create interpreter just-in-time if(interpreter == null) { interpreter=new Interpreter(); - - if(log.isInfoEnabled()) log.info("beanshell interpreter was created"); try { interpreter.set("bsh_prot", this); - - if(log.isInfoEnabled()) log.info("set \"bsh_prot\" to " + this); } - catch(EvalError err) { - if(log.isErrorEnabled()) log.error("failed setting \"bsh_prot\": " + err); + catch(EvalError evalError) { } - } - - try { - retval=interpreter.eval(code); - - if(log.isInfoEnabled()) log.info("eval: \"" + code + - "\", retval=" + retval); - } - catch(EvalError ex) { - if(log.isErrorEnabled()) log.error("error is " + Util.getStackTrace(ex)); - retval=ex; - } - - if(sender != null) { - Message rsp=new Message(sender, null, null); - - // serialize the object if serializable, otherwise just send string - // representation - if(retval != null) { - if(retval instanceof Serializable) - rsp.setObject((Serializable)retval); - else - rsp.setObject(retval.toString()); - } - - - if(log.isInfoEnabled()) log.info("sending back response " + - retval + " to " + rsp.getDest()); - rsp.putHeader(name, new BshHeader(BshHeader.RSP)); - down_prot.down(new Event(Event.MSG, rsp)); - } - } - - -/* --------------------------- Callbacks ---------------------------- */ -// public Object eval(String code) throws Exception { -// Object retval=null; -// try { -// retval=interpreter.eval(code); -// -// if(log.isInfoEnabled()) log.info("BSH.eval()", "eval: \"" + code + -// "\", retval=" + retval); -// if(retval != null && !(retval instanceof Serializable)) { -// if(log.isErrorEnabled()) log.error("BSH.eval", "return value " + retval + -// " is not serializable, cannot be sent back " + -// "(returning null)"); -// return null; -// } -// return retval; -// } -// catch(EvalError ex) { -// if(log.isErrorEnabled()) log.error("BSH.eval()", "error is " + Util.getStackTrace(ex)); -// return ex; -// } -// } -// - public void destroyInterpreter() { - interpreter=null; // allow it to be garbage collected - - if(log.isInfoEnabled()) log.info("beanshell interpreter was destroyed"); } - /* ------------------------ End of Callbacks ------------------------ */ - - public static class BshHeader extends Header { - public static final int REQ=1; - public static final int RSP=2; - public static final int DESTROY_INTERPRETER=3; - int type=REQ; - - - public BshHeader() { - } - public BshHeader(int type) { - this.type=type; - } - - public int size() { - return 10; - } - - public String toString() { - StringBuilder sb=new StringBuilder(); - if(type == REQ) - sb.append("REQ"); - else - if(type == RSP) - sb.append("RSP"); - else - if(type == DESTROY_INTERPRETER) - sb.append("DESTROY_INTERPRETER"); - else - sb.append(""); - return sb.toString(); - } - - public void writeExternal(ObjectOutput out) throws IOException { - out.writeInt(type); - } - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - type=in.readInt(); - } - - } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/CAUSAL.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/CAUSAL.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/CAUSAL.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/CAUSAL.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,1062 +0,0 @@ - -package org.jgroups.protocols; - -import org.jgroups.*; -import org.jgroups.annotations.Experimental; -import org.jgroups.annotations.Property; -import org.jgroups.stack.Protocol; -import org.jgroups.util.Streamable; - -import java.io.*; -import java.util.*; - -/**

    - * Implements casual ordering layer using vector clocks. - *

    - *

    - * Causal protocol layer guarantees that if message m0 multicasted - * by a process group member p0 causes process group member - * p1 to multicast message m1 then all other remaining process group - * members in a current view will receive messages in order m0 - * followed by m1. - *

    - *

    - * First time encountered, causal order seems very similar to FIFO order but - * there is an important distinction. While FIFO order gurantees that - * if process group member p0 multicasts m0 followed by m1 the messages - * will be delivered in order m0,m1 to all other group members, causal - * order expands this notion of an order from a single group member "space" - * to a whole group space i.e if p0 sends message m0 which causes member - * p1 to send message m1 then all other group members are guaranteed to - * receive m0 followed by m1. - *

    - *

    - * Causal protocol layer achieves this ordering type by introducing sense of - * a time in a group using vector clocks. The idea is very simple. Each message - * is labeled by a vector, contained in a causal header, representing the number of - * prior causal messages received by the sending group member. Vector time of [3,5,2,4] in - * a group of four members [p0,p1,p2,p3] means that process p0 has sent 3 messages - * and has received 5,2 and 4 messages from a member p1,p2 and p3 respectively. - *

    - *

    - * Each member increases its counter by 1 when it sends a message. When receiving - * message mi from a member pi , (where pi != pj) containing vector time VT(mi), - * process pj delays delivery of a message mi until: - *

    - *

    - * for every k:1..n - * - * VT(mi)[k] == VT(pj)[k] + 1 if k=i, - * VT(mi)[k] <= VT(pj)[k] otherwise - *

    - *

    - * After the next causal message is delivered at process group pj, VT(pj) is - * updated as follows: - *

    - *

    - * for every k:1...n VT(pj)[k] == max(VT(mi)[k],VT(pj)[k]) - *

    - * Note that this protocol is experimental and has never been tested extensively ! - * @author Vladimir Blagojevic vladimir@cs.yorku.ca - * @version $Id: CAUSAL.java,v 1.21 2008/05/08 09:46:42 vlada Exp $ - * - **/ - -@Experimental -public class CAUSAL extends Protocol -{ - - public static final class CausalHeader extends Header implements Streamable { - - /** - * Comment for serialVersionUID - */ - private static final long serialVersionUID = 3760846744526927667L; - - /** - * vector timestamp of this header/message - */ - private TransportedVectorTime t = null; - - /** - *used for externalization - */ - public CausalHeader() { - } - - public CausalHeader(TransportedVectorTime timeVector) { - t = timeVector; - } - - /** - *Returns a vector timestamp carreid by this header - *@return Vector timestamp contained in this header - */ - public TransportedVectorTime getVectorTime() { - return t; - } - - /** - * Size of this vector timestamp estimation, used in fragmetation - * @return headersize in bytes - */ - public int size() - { - int retval=Global.BYTE_SIZE; - if(t == null) - return retval; - retval+=t.senderPosition; - if(t.values != null) { - retval+=t.values.length * Global.INT_SIZE; - } - return retval; - } - - /** - * Manual serialization - */ - public void writeExternal(ObjectOutput out) throws IOException - { - out.writeObject(t); - } - - /** - * Manual deserialization - */ - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException - { - t = (TransportedVectorTime) in.readObject(); - } - - public void writeTo(DataOutputStream out) throws IOException { - if(t == null) - { - out.writeBoolean(false); - return; - } - out.writeBoolean(true); - - out.writeInt(t.senderPosition); - - int values[]=t.values; - - int len=values.length; - out.writeInt(len); - for(int i=0;i=len) - throw new InstantiationException("sender position="+t.senderPosition+", values length="+len); - - t.values=new int[len]; - for(int i=0;iserialVersionUID. - */ - private static final long serialVersionUID = 3257569486185183289L; - - public final static String NAME="CAUSAL_NEWVIEW_HEADER"; - - /** - * New view id. - */ - private ViewId newViewId; - - /** - * Sender local time. - */ - private int localTime; - - private boolean complete; - - public CausalNewViewHeader(ViewId newViewId, int localTime, boolean complete) { - this.newViewId=newViewId; - this.localTime=localTime; - this.complete=complete; - } - - /** - * Used for externalization. - */ - public CausalNewViewHeader() { - } - - public ViewId getNewViewId() { - return newViewId; - } - - public int getLocalTime() { - return localTime; - } - - public boolean isComplete() { - return complete; - } - - /** - * Size of this vector timestamp estimation, used in fragmentation. - * @return headersize in bytes - */ - public int size() { - /*why 231, don't know but these are this values I get when - flattening the object into byte buffer*/ - return 231; - } - - /** - * Manual serialization - */ - public void writeExternal(ObjectOutput out) throws IOException { - out.writeObject(newViewId); - out.writeInt(localTime); - out.writeBoolean(complete); - } - - /** - * Manual deserialization - */ - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - newViewId=(ViewId)in.readObject(); - localTime=in.readInt(); - complete=in.readBoolean(); - } - - public void writeTo(DataOutputStream out) throws IOException { - newViewId.writeTo(out); - out.writeInt(localTime); - out.writeBoolean(complete); - } - - public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { - newViewId=new ViewId(); - newViewId.readFrom(in); - localTime=in.readInt(); - complete=in.readBoolean(); - } - - public String toString() { - return '[' + NAME + ':' + newViewId + "; @" + localTime + (complete?"; complete]":"; incomplete]"); - } - - } - - public static final class MissingIndexesMessage implements Externalizable, Streamable { - - /** - * Comment for serialVersionUID - */ - private static final long serialVersionUID = 3257007644266213432L; - - /** - * Member indexes the sender is waiting the local time from. - */ - private int missingTimeIndexes[]; - - /** - * Member indexes the sender is waiting the completion from. - */ - private int missingCompletionIndexes[]; - - public MissingIndexesMessage(Collection missingLocalTimes, Collection missingCompletions) { - missingTimeIndexes=new int[missingLocalTimes.size()]; - int i=0; - for(Iterator it=missingLocalTimes.iterator();it.hasNext();) - missingTimeIndexes[i++]=((Integer)it.next()).intValue(); - - missingCompletionIndexes=new int[missingCompletions.size()]; - i=0; - for(Iterator it=missingCompletions.iterator();it.hasNext();) - missingCompletionIndexes[i++]=((Integer)it.next()).intValue(); - } - - /** - * Used for externalization. - */ - public MissingIndexesMessage() { - } - - public int[] getMissingTimeIndexes() { - return missingTimeIndexes; - } - - public int[] getMissingCompletionIndexes() { - return missingCompletionIndexes; - } - - private static void writeIntArray(DataOutput out, int array[]) throws IOException { - out.writeInt(array.length); - for(int i=0;i0) sb.append(", "); - sb.append(i).append(':').append(members[i]); - } - sb.append(" - local is ").append(localIndex); - return sb.toString(); - } - - } - - private final class NewCausalView { - - private final ActiveCausalView active; - - private final InternalView view; - - private final int timeVector[]; - - private final TreeSet missingTimes=new TreeSet(), missingCompletions=new TreeSet(); - - public NewCausalView(ActiveCausalView active, InternalView view) { - this.active=active; - this.view=view; - - // Setup time vector - timeVector=new int[view.size()]; - - if (view.getLocalIndex()>=timeVector.length) - throw new IllegalStateException("View: "+view+" timevector.length="+timeVector.length); - - for(int i=0;i=initialTimeVector.length) - throw new IllegalStateException("View: "+view+" timevector.length="+initialTimeVector.length); - } - - public InternalView getView() { - return view; - } - - public ViewId getViewId() { - return view.getViewId(); - } - - public int getLocalIndex() { - return view.getLocalIndex(); - } - - public synchronized int getLocalTime() { - return timeVector[view.getLocalIndex()]; - } - - /** - * Increment local time. - */ - public synchronized void increment() { - timeVector[view.getLocalIndex()]++; - } - - /** - * Returns a minimal lightweight representation of this Vector Time - * suitable for network transport. - * @return lightweight representation of this VectorTime in the - * form of TransportedVectorTime object - */ - public synchronized TransportedVectorTime getTransportedVectorTime() { - // Need to make a copy of the time vector - int tmpTimeVector[]=new int[this.timeVector.length]; - System.arraycopy(this.timeVector, 0, tmpTimeVector, 0, tmpTimeVector.length); - - return new TransportedVectorTime(view.getLocalIndex(), tmpTimeVector); - } - - public synchronized boolean isCausallyNext(TransportedVectorTime vector) { - int senderIndex = vector.getSenderIndex(); - - if (senderIndex == view.getLocalIndex()) return true; - - int[] otherTimeVector = vector.getValues(); - - if (otherTimeVector.length!=timeVector.length) { - if(log.isWarnEnabled()) - log.warn("isCausallyNext: got message with wrong time vector length: "+otherTimeVector.length+ - ", expected: "+timeVector.length); - return true; - } - - boolean nextCausalFromSender = false; - boolean nextCausal = true; - - for (int i=0;i timeVector[i]) nextCausal = false; - } - - return (nextCausalFromSender && nextCausal); - } - - public synchronized void max(TransportedVectorTime vector) { - int otherTimeVector[]=vector.getValues(); - - if (otherTimeVector.length!=timeVector.length) { - if(log.isWarnEnabled()) - log.warn("max: got message with wrong time vector length: "+otherTimeVector.length+", expected: "+ - timeVector.length); - return; - } - - for(int i=0;itimeVector[i]) timeVector[i]=otherTimeVector[i]; - } - } - - public synchronized void setFinalTimeVector(InternalView newView, int startTimeVector[]) { - finalTimeVector=new int[timeVector.length]; - System.arraycopy(timeVector, 0, finalTimeVector, 0, timeVector.length); - - for(int i=0;i0) sb.append(", "); - sb.append(timeVector[i]); - } - return sb.toString(); - } - - public String toString() { - return "ActiveCausalView["+view+']'; - } - - } - - private final class NewViewThread extends Thread { - - private final NewCausalView newView; - - private boolean updateRequested=false; - - NewViewThread(NewCausalView newView) { - super(newView.getViewId().toString()); - this.newView=newView; - setDaemon(true); - } - - NewCausalView getCausalView() { - return newView; - } - - public void run() { - boolean sendUpdate=false, complete=false; - LinkedList flush=null; - - for(;;) { - Message update=null; - - synchronized(lock) { - if(this != newViewThread) { - break; - } - - if(newView.hasMissingTimes()) { - sendUpdate=true; - } - else - if(currentView == null || (!currentView.getViewId().equals(newView.getViewId()) && currentView.hasEnded())) - { - currentView=new ActiveCausalView(newView.getView(), newView.timeVector); - complete=true; - newView.setMemberCompleted(localAddress); - - if(log.isTraceEnabled()) - log.trace("Set up new active view: " + currentView + " @ " + currentView.timeVectorString()); - } - - if(newView.hasMissingCompletions()) { - sendUpdate=true; - } - else { - newViewThread=null; - enabled=true; - flush=(LinkedList)downwardWaitingQueue.clone(); - downwardWaitingQueue.clear(); - - if(log.isTraceEnabled()) - log.trace("Done synchronizing, enabled view: " + currentView + " @ " + currentView.timeVectorString()); - - break; - } - - if(sendUpdate) { - update=new Message(null, localAddress, null); - update.putHeader(CausalNewViewHeader.NAME - , new CausalNewViewHeader(newView.getViewId(), newView.getLocalTime(), complete)); - update.setObject(new MissingIndexesMessage(newView.getMissingTimes(), newView.getMissingCompletions())); - } - } - - if(update != null) { - if(log.isTraceEnabled()) - log.trace("Sending sync update"); - down_prot.down(new Event(Event.MSG, update)); - } - - synchronized(this) { - // Wait for 50ms - try { - wait(500); - } - catch(InterruptedException e) { - Thread.currentThread().interrupt(); // set interrupt flag again - // Ignore - log.warn("Interrupted?!?", e); - } - - sendUpdate=updateRequested; - updateRequested=false; - } - } - - if (flush!=null) { - int n=flush.size(); - if (log.isDebugEnabled()) log.debug("Flushing "+n+" messages down..."); - - while(!flush.isEmpty()) { - Event evt=(Event)flush.removeFirst(); - down(evt); - } - - if (log.isDebugEnabled()) log.debug("Done flushing "+n+" messages down..."); - } - } - - void updateRequested() { - synchronized(this) { - updateRequested=true; - } - } - - } - - - private final Object lock=new Object(); - - /** - * Local address. - */ - private Address localAddress; - - /** - * Queue containing upward messages waiting for delivery i.e causal order. - */ - private final LinkedList upwardWaitingQueue=new LinkedList(); - - /** - * Queue containing downward messages waiting for sending. - */ - private final LinkedList downwardWaitingQueue=new LinkedList(); - - private boolean enabled=false; - - /** - * The active view (including its time vector), if any. - */ - private ActiveCausalView currentView; - - private NewViewThread newViewThread; - - @Property - private boolean debug=false; - - /** - * Default constructor. - */ - public CAUSAL() { - } - - /** - * Adds a vectortimestamp to a sorted queue - * @param tvt A vector time stamp - */ - private void addToDelayQueue(TransportedVectorTime tvt) - { - ListIterator i = upwardWaitingQueue.listIterator(0); - TransportedVectorTime current = null; - while (i.hasNext()) - { - current = (TransportedVectorTime) i.next(); - if (tvt.lessThanOrEqual(current)) - { - upwardWaitingQueue.add(i.previousIndex(), tvt); - return; - } - } - upwardWaitingQueue.add(tvt); - } - - // Must be called sync'd on lock - private void disable() { - enabled=false; - } - - // Must be called sync'd on lock - private boolean isEnabled() { - return enabled; - } - -// // Must be called sync'd on lock -// private boolean tryEnable() { -// if (currentView!=null && !currentView.hasEnded()) return false; -// -// currentView=new ActiveCausalView(newView.getView(), newView.timeVector); -// newView=null; -// enabled=true; -// -// // FIXME send all waiting messages. -// -// return true; -// } - - /** - * Process a downward event. - * @param evt The event. - */ - public Object down(Event evt) { - try { - // If not a MSG, just pass down. - if (evt.getType()!=Event.MSG) { - return down_prot.down(evt); - } - - Message msg = (Message) evt.getArg(); - - // If unicast, just pass down. - if (msg.getDest()!=null && ! msg.getDest().isMulticastAddress()) { - return down_prot.down(evt); - } - - // Multicast MSG: - // - if enabled, get the next time vector, add it and pass down; - // - otherwise, add to the downward waiting queue. - TransportedVectorTime tvt=null; - - synchronized(lock) { - if (isEnabled()) { - currentView.increment(); - tvt=currentView.getTransportedVectorTime(); - if (log.isTraceEnabled()) log.trace("Sent 1 down message @ "+currentView.timeVectorString()); - } else { - if (log.isTraceEnabled()) log.trace("Enqueued 1 down message..."); - downwardWaitingQueue.add(evt); - } - } - - if (tvt!=null) { - msg.putHeader(getName(), new CausalHeader(tvt)); - return down_prot.down(evt); - } - } catch (RuntimeException e) { - if (debug) log.error("*** down: "+e.getMessage(), e); - throw e; - } - - return null; - } - - /** - * Process an upward event. - * @param evt The event. - */ - public Object up(Event evt) { - try { - switch (evt.getType()) { - case Event.SET_LOCAL_ADDRESS: - upSetLocalAddress(evt); - break; - case Event.VIEW_CHANGE: - upViewChange(evt); - break; - case Event.MSG: - return upMsg(evt); - default: - return up_prot.up(evt); - } - } catch (RuntimeException e) { - if (debug) log.error("*** up: "+e.getMessage(), e); - throw e; - } - return null; - } - - private void upSetLocalAddress(Event evt) { - localAddress = (Address) evt.getArg(); - up_prot.up(evt); - } - - /** - * Process a VIEW_CHANGE event. - * @param evt The event. - */ - private void upViewChange(Event evt) - { - View view=(View)evt.getArg(); - InternalView iView=new InternalView(view.getVid(), view.getMembers(), localAddress); - if(log.isDebugEnabled()) - log.debug("New view: "+view); - - synchronized(lock) { - // Disable sending - disable(); - - // Create new causal view - NewCausalView newView=new NewCausalView(currentView, iView); - if (currentView!=null) { - currentView.clearFinalTimeVector(); - newView.setMemberLocalTime(localAddress, currentView.getLocalTime()); - } else { - newView.setMemberLocalTime(localAddress, 0); - } - - if (log.isTraceEnabled()) log.trace("Starting synchronization thread for "+newView); - - newViewThread=new NewViewThread(newView); - newViewThread.start(); - } - - up_prot.up(evt); - } - - private Object upMsg(Event evt) { - Message msg = (Message) evt.getArg(); - Address src=msg.getSrc(); - - // Check for a causal new view header - Object obj = msg.getHeader(CausalNewViewHeader.NAME); - - if (obj instanceof CausalNewViewHeader) { - processNewViewSynchronization(src, (CausalNewViewHeader)obj, msg.getObject()); - return null; - } - - obj = msg.getHeader(getName()); - - if (!(obj instanceof CausalHeader)) { - if((msg.getDest() == null || msg.getDest().isMulticastAddress()) - && log.isErrorEnabled()) log.error("NO CAUSAL.Header found"); - return up_prot.up(evt); - } - - TransportedVectorTime messageVector = ((CausalHeader)obj).getVectorTime(); - - synchronized (lock) { - if (currentView==null||currentView.getView().getIndex(src)<0) { - if (log.isDebugEnabled()) log.debug("Discarding "+obj+" from "+msg.getSrc()); - return null; - } - - if (currentView.isCausallyNext(messageVector)) { - if (log.isTraceEnabled()) log.trace("passing up message "+msg+", headers are "+msg.printHeaders()+", local vector is "+currentView.timeVectorString()); - up_prot.up(evt); - currentView.max(messageVector); - } else { - if (log.isTraceEnabled()) log.trace("queuing message "+msg+", headers are "+msg.printHeaders()); - messageVector.setAssociatedMessage(msg); - addToDelayQueue(messageVector); - } - - TransportedVectorTime queuedVector = null; - - while ((!upwardWaitingQueue.isEmpty()) && - currentView.isCausallyNext((queuedVector = (TransportedVectorTime) upwardWaitingQueue.getFirst()))) { - upwardWaitingQueue.remove(queuedVector); - Message tmp=queuedVector.getAssociatedMessage(); - if (log.isTraceEnabled()) log.trace("released message "+tmp+", headers are "+tmp.printHeaders()); - up_prot.up(new Event(Event.MSG, tmp)); - currentView.max(queuedVector); - } - } - return null; - } - - /** - * - */ - private void processNewViewSynchronization(Address src, CausalNewViewHeader header, Object object) { - // If from ourselves, ignore. - if (localAddress.equals(src)) return; - - MissingIndexesMessage content=(MissingIndexesMessage)object; - - if(log.isTraceEnabled()) - log.trace("Got sync update from "+src); - - synchronized(lock) { - if (newViewThread==null) { - if (currentView!=null&¤tView.getView().getViewId().equals(header.newViewId)) { - // Somebody's late... - int localIndex=currentView.getLocalIndex(); - - if (Arrays.binarySearch(content.getMissingCompletionIndexes(), localIndex)>=0) { - Message update=new Message(null, localAddress, null); - update.putHeader(CausalNewViewHeader.NAME - , new CausalNewViewHeader(currentView.getView().getViewId(), 0, true)); // It has the time already - update.setObject(new MissingIndexesMessage(Collections.EMPTY_LIST, Collections.EMPTY_LIST)); - - down_prot.down(new Event(Event.MSG, update)); - } - - if (Arrays.binarySearch(content.getMissingTimeIndexes(), localIndex)>=0) { - - } - } else { - // Somebody's early... - disable(); - } - return; - } - - if (!newViewThread.getCausalView().getViewId().equals(header.newViewId)) return; - - if (log.isTraceEnabled()) log.trace("From "+src+": "+header); - - // Update the local time and completion status for the source. - newViewThread.getCausalView().setMemberLocalTime(src, header.localTime); - if (header.isComplete()) newViewThread.getCausalView().setMemberCompleted(src); - - // Check the requested times and completions - int localIndex=newViewThread.getCausalView().getLocalIndex(); - - if ( Arrays.binarySearch(content.getMissingTimeIndexes(), localIndex)>=0 - || Arrays.binarySearch(content.getMissingCompletionIndexes(), localIndex)>=0 ) { - newViewThread.updateRequested(); - } - } - } - - /** - * Returns a name of this stack, each stackhas to have unique name - * @return stack's name - CAUSAL - */ - public String getName() { - return "CAUSAL"; - } - -} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/CENTRAL_EXECUTOR.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/CENTRAL_EXECUTOR.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/CENTRAL_EXECUTOR.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/CENTRAL_EXECUTOR.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,227 @@ +package org.jgroups.protocols; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.jgroups.Address; +import org.jgroups.View; +import org.jgroups.annotations.Experimental; +import org.jgroups.annotations.ManagedAttribute; +import org.jgroups.annotations.Property; +import org.jgroups.util.Util; + + +/** + * This is a central executor service where each request is sent to the coordinator + * for either a task or a current waiting thread. + * + * @author wburns + * @since 2.12.0 + */ +@Experimental +public class CENTRAL_EXECUTOR extends Executing { + + @Property(description="Number of backups to the coordinator. Queue State gets replicated to these nodes as well") + protected int num_backups=1; + + protected Address coord; + + @ManagedAttribute + protected boolean is_coord; + + protected final List
    backups=new ArrayList
    (); + + + public CENTRAL_EXECUTOR() { + super(); + } + + public Address getCoord() { + return coord; + } + + public boolean isCoord() { + return is_coord; + } + + @ManagedAttribute + public String getCoordinator() { + return coord != null? coord.toString() : "n/a"; + } + + public int getNumberOfBackups() { + return num_backups; + } + + public void setNumberOfBackups(int num_backups) { + this.num_backups=num_backups; + } + + @ManagedAttribute + public String getBackups() { + return backups != null? backups.toString() : null; + } + + public void handleView(View view) { + Address oldCoord = coord; + if(view.size() > 0) { + coord=view.getMembers().firstElement(); + is_coord=coord.equals(local_addr); + if(log.isDebugEnabled()) + log.debug("local_addr=" + local_addr + ", coord=" + coord + ", is_coord=" + is_coord); + } + + // If we got a new coordinator we have to send all the requests for + // tasks and consumers again just incase they were missed when + // coordinator went down + // We are okay with duplicates since we don't add multiple times. + // We also have a problem that if a task/consumer was picked up as the + // consumer is changing we may have duplicates. But this is technically + // okay in that an extra consumer will reject and an extra task will just + // be ran and return nowhere, but at least we won't lose data. + if (oldCoord != coord) { + for (Long requests : _requestId.values()) { + sendToCoordinator(Type.RUN_REQUEST, requests, local_addr); + } + + for (Long requests : _consumerId.keySet()) { + sendToCoordinator(Type.CONSUMER_READY, requests, local_addr); + } + } + + if(num_backups > 0) { + if (is_coord) { + List
    new_backups=Util.pickNext(view.getMembers(), local_addr, num_backups); + List
    new_members=null; + synchronized(backups) { + if(!backups.equals(new_backups)) { + new_members=new ArrayList
    (new_backups); + new_members.removeAll(backups); + backups.clear(); + backups.addAll(new_backups); + } + } + + if(new_members != null && !new_members.isEmpty()) + copyQueueTo(new_members); + } + // We keep what backups we have ourselves, so that when we become + // the coordinator we don't update them again. Technically we can + // send multiple requests but don't if to prevent more message being + // sent. + else { + List
    possiblebackups = Util.pickNext(view.getMembers(), + coord, num_backups); + + boolean foundMyself = false; + List
    myBackups = new ArrayList
    (); + for (Address backup : possiblebackups) { + if (foundMyself) { + myBackups.add(backup); + } + else if (backup.equals(local_addr)) { + foundMyself = true; + } + } + + synchronized (backups) { + backups.clear(); + backups.addAll(myBackups); + } + } + } + + // Need to run this last so the backups are updated + super.handleView(view); + } + + protected void updateBackups(Type type, Owner obj) { + synchronized(backups) { + for(Address backup: backups) + sendRequest(backup, type, obj.getRequestId(), obj.getAddress()); + } + } + + protected void copyQueueTo(List
    new_joiners) { + Set copyRequests; + Set copyConsumers; + + _consumerLock.lock(); + try { + copyRequests = new HashSet(_runRequests); + copyConsumers = new HashSet(_consumersAvailable); + } + finally { + _consumerLock.unlock(); + } + + if(log.isTraceEnabled()) + log.trace("copying queue to " + new_joiners); + for(Address joiner: new_joiners) { + for(Owner address: copyRequests) { + sendRequest(joiner, Type.CREATE_RUN_REQUEST, + address.getRequestId(), address.getAddress()); + } + + for(Owner address: copyConsumers) { + sendRequest(joiner, Type.CREATE_CONSUMER_READY, + address.getRequestId(), address.getAddress()); + } + } + } + + // @see org.jgroups.protocols.Executing#sendToCoordinator(org.jgroups.protocols.Executing.Type, long, org.jgroups.Address) + @Override + protected void sendToCoordinator(Type type, final long requestId, final Address value) { + if (is_coord) { + if(log.isTraceEnabled()) + log.trace("[redirect] <--> [" + local_addr + "] " + + type.name() + " [" + value + + (requestId != -1 ? " request id: " + requestId : "") + + "]"); + switch(type) { + case RUN_REQUEST: + handleTaskRequest(requestId, value); + break; + case CONSUMER_READY: + handleConsumerReadyRequest(requestId, value); + break; + case CONSUMER_UNREADY: + handleConsumerUnreadyRequest(requestId, value); + break; + }; + } + else + sendRequest(coord, type, requestId, value); + } + + // @see org.jgroups.protocols.Executing#sendNewRunRequest(org.jgroups.protocols.Executing.Owner) + @Override + protected void sendNewRunRequest(Owner sender) { + if(is_coord) + updateBackups(Type.CREATE_RUN_REQUEST, sender); + } + + // @see org.jgroups.protocols.Executing#sendRemoveRunRequest(org.jgroups.protocols.Executing.Owner) + @Override + protected void sendRemoveRunRequest(Owner sender) { + if(is_coord) + updateBackups(Type.DELETE_RUN_REQUEST, sender); + } + + // @see org.jgroups.protocols.Executing#sendNewConsumerRequest(org.jgroups.protocols.Executing.Owner) + @Override + protected void sendNewConsumerRequest(Owner sender) { + if(is_coord) + updateBackups(Type.CREATE_CONSUMER_READY, sender); + } + + // @see org.jgroups.protocols.Executing#sendRemoveConsumerRequest(org.jgroups.protocols.Executing.Owner) + @Override + protected void sendRemoveConsumerRequest(Owner sender) { + if(is_coord) + updateBackups(Type.DELETE_CONSUMER_READY, sender); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/CENTRAL_LOCK.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/CENTRAL_LOCK.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/CENTRAL_LOCK.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/CENTRAL_LOCK.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,219 @@ +package org.jgroups.protocols; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; + +import org.jgroups.Address; +import org.jgroups.View; +import org.jgroups.annotations.Experimental; +import org.jgroups.annotations.ManagedAttribute; +import org.jgroups.annotations.Property; +import org.jgroups.blocks.locking.LockNotification; +import org.jgroups.blocks.locking.Owner; +import org.jgroups.util.Util; + + +/** + * Implementation of a locking protocol which acquires locks by contacting the coordinator.

    Because the + * coordinator maintains all locks, no total order configuration is required.

    + * CENTRAL_LOCK has all members send lock and unlock requests to a central coordinator. The coordinator has a queue for + * incoming requests, and grants locks based on order of arrival. To prevent all acquired locks from being forgotten + * when the coordinator crashes, setting num_backups lets the coordinator backup lock information to a number of + * backup nodes. Valid values for num_backups are 0 (no backup) to N-1, e.g. in a cluster of 4, we can have only 3 backup + * nodes.

    + * Say we have a cluster of {A,B,C,D,E} and num_backups=1. A is the coordinator, and A updates all locks (and released + * locks) in B as well. When A crashes, everybody falls over to B for sending lock and unlock requests. + * B in turn copies all existing locks over to C and - when locks are acquired or released - forwards this + * information to C as well. + *

    + * An alternative is also the {@link org.jgroups.protocols.PEER_LOCK} protocol. + * @author Bela Ban + * @since 2.12 + * @see Locking + * @see PEER_LOCK + */ +@Experimental +public class CENTRAL_LOCK extends Locking implements LockNotification { + + @Property(description="Number of backups to the coordinator. Server locks get replicated to these nodes as well") + protected int num_backups=1; + + protected Address coord; + + @ManagedAttribute + protected boolean is_coord; + + protected final List

    backups=new ArrayList
    (); + + + public CENTRAL_LOCK() { + super(); + addLockListener(this); + } + + public Address getCoord() { + return coord; + } + + public boolean isCoord() { + return is_coord; + } + + @ManagedAttribute + public String getCoordinator() { + return coord != null? coord.toString() : "n/a"; + } + + public int getNumberOfBackups() { + return num_backups; + } + + public void setNumberOfBackups(int num_backups) { + this.num_backups=num_backups; + } + + @ManagedAttribute + public String getBackups() { + return backups != null? backups.toString() : null; + } + + protected void sendGrantLockRequest(String lock_name, Owner owner, long timeout, boolean is_trylock) { + if(coord != null) + sendRequest(coord, Type.GRANT_LOCK, lock_name, owner, timeout, is_trylock); + } + + protected void sendReleaseLockRequest(String lock_name, Owner owner) { + if(coord != null) + sendRequest(coord, Type.RELEASE_LOCK, lock_name, owner, 0, false); + } + + protected void sendCreateLockRequest(Address dest, String lock_name, Owner owner) { + sendRequest(dest, Type.CREATE_LOCK, lock_name, owner, 0, false); + } + + protected void sendDeleteLockRequest(Address dest, String lock_name) { + sendRequest(dest, Type.DELETE_LOCK, lock_name, null, 0, false); + } + + @Override + protected void sendAwaitConditionRequest(String lock_name, Owner owner) { + sendRequest(coord, Type.LOCK_AWAIT, lock_name, owner, 0, false); + } + + @Override + protected void sendSignalConditionRequest(String lock_name, boolean all) { + sendRequest(coord, all ? Type.COND_SIG_ALL : Type.COND_SIG, lock_name, null, 0, false); + } + + @Override + protected void sendDeleteAwaitConditionRequest(String lock_name, Owner owner) { + sendRequest(coord, Type.DELETE_LOCK_AWAIT, lock_name, owner, 0, false); + } + + public void handleView(View view) { + super.handleView(view); + Address old_coord=coord; + if(view.size() > 0) { + coord=view.getMembers().firstElement(); + is_coord=coord.equals(local_addr); + if(log.isDebugEnabled()) + log.debug("local_addr=" + local_addr + ", coord=" + coord + ", is_coord=" + is_coord); + } + + if(is_coord && num_backups > 0) { + List
    new_backups=Util.pickNext(view.getMembers(), local_addr, num_backups); + List
    copy_locks_list=null; + synchronized(backups) { + if(!backups.equals(new_backups)) { + copy_locks_list=new ArrayList
    (new_backups); + copy_locks_list.removeAll(backups); + backups.clear(); + backups.addAll(new_backups); + } + } + + if(copy_locks_list != null && !copy_locks_list.isEmpty()) + copyLocksTo(copy_locks_list); + } + + // For all non-acquired client locks, send the GRANT_LOCK request to the new coordinator (if changed) + if(old_coord != null && !old_coord.equals(coord)) { + Map> copy; + synchronized(client_locks) { + copy=new HashMap>(client_locks); + } + if(!copy.isEmpty()) { + for(Map map: copy.values()) { + for(ClientLock lock: map.values()) { + if(!lock.acquired && !lock.denied) + sendGrantLockRequest(lock.name, lock.owner, lock.timeout, lock.is_trylock); + } + } + } + } + } + + public void lockCreated(String name) { + } + + public void lockDeleted(String name) { + } + + public void locked(String lock_name, Owner owner) { + if(is_coord) + updateBackups(Type.CREATE_LOCK, lock_name, owner); + } + + public void unlocked(String lock_name, Owner owner) { + if(is_coord) + updateBackups(Type.DELETE_LOCK, lock_name, owner); + } + + public void awaiting(String lock_name, Owner owner) { + if(is_coord) + updateBackups(Type.CREATE_AWAITER, lock_name, owner); + } + + public void awaited(String lock_name, Owner owner) { + if(is_coord) + updateBackups(Type.DELETE_AWAITER, lock_name, owner); + } + + protected void updateBackups(Type type, String lock_name, Owner owner) { + synchronized(backups) { + for(Address backup: backups) + sendRequest(backup, type, lock_name, owner, 0, false); + } + } + + + + protected void copyLocksTo(List
    new_joiners) { + Map copy; + + synchronized(server_locks) { + copy=new HashMap(server_locks); + } + + if(log.isTraceEnabled()) + log.trace("copying locks to " + new_joiners); + for(Map.Entry entry: copy.entrySet()) { + for(Address joiner: new_joiners) { + ServerLock lock = entry.getValue(); + if (lock.current_owner != null) { + sendCreateLockRequest(joiner, entry.getKey(), entry.getValue().current_owner); + } + synchronized (lock.condition) { + Queue queue = lock.condition.queue; + for (Owner owner : queue) { + sendAwaitConditionRequest(lock.lock_name, owner); + } + } + } + } + } +} + diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/COMPRESS.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/COMPRESS.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/COMPRESS.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/COMPRESS.java 2011-10-18 11:22:35.000000000 +0000 @@ -4,13 +4,13 @@ import org.jgroups.Global; import org.jgroups.Header; import org.jgroups.Message; +import org.jgroups.annotations.MBean; import org.jgroups.annotations.Property; import org.jgroups.stack.Protocol; -import org.jgroups.util.Streamable; import java.io.*; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; import java.util.zip.DataFormatException; import java.util.zip.Deflater; import java.util.zip.Inflater; @@ -21,14 +21,10 @@ * fragmentation protocol (e.g. FRAG). * * @author Bela Ban - * @version $Id: COMPRESS.java,v 1.22 2008/11/28 05:52:11 belaban Exp $ */ +@MBean(description="Compresses messages to send and uncompresses received messages") public class COMPRESS extends Protocol { - - - private final static String name="COMPRESS"; - - + /* ----------------------------------------- Properties -------------------------------------------------- */ @Property(description="Compression level 0-9 (0=no compression, 9=best compression). Default is 9") @@ -54,11 +50,6 @@ } - public String getName() { - return name; - } - - public void init() throws Exception { deflater_pool=new ArrayBlockingQueue(pool_size); for(int i=0; i < pool_size; i++) { @@ -101,12 +92,19 @@ deflater.finish(); deflater.deflate(compressed_payload); compressed_size=deflater.getTotalOut(); - byte[] new_payload=new byte[compressed_size]; - System.arraycopy(compressed_payload, 0, new_payload, 0, compressed_size); - msg.setBuffer(new_payload); - msg.putHeader(name, new CompressHeader(length)); - if(log.isTraceEnabled()) - log.trace("compressed payload from " + length + " bytes to " + compressed_size + " bytes"); + + if ( compressed_size < length ) { // JGRP-1000 + byte[] new_payload=new byte[compressed_size]; + System.arraycopy(compressed_payload, 0, new_payload, 0, compressed_size); + msg.setBuffer(new_payload); + msg.putHeader(this.id, new CompressHeader(length)); + if(log.isTraceEnabled()) + log.trace("compressed payload from " + length + " bytes to " + compressed_size + " bytes"); + } + else { + if(log.isTraceEnabled()) + log.trace("Skipping compression since the compressed message is larger than the original"); + } } catch(InterruptedException e) { Thread.currentThread().interrupt(); // set interrupt flag again @@ -130,7 +128,7 @@ public Object up(Event evt) { if(evt.getType() == Event.MSG) { Message msg=(Message)evt.getArg(); - CompressHeader hdr=(CompressHeader)msg.getHeader(name); + CompressHeader hdr=(CompressHeader)msg.getHeader(this.id); if(hdr != null) { byte[] compressed_payload=msg.getRawBuffer(); if(compressed_payload != null && compressed_payload.length > 0) { @@ -175,7 +173,7 @@ - public static class CompressHeader extends Header implements Streamable { + public static class CompressHeader extends Header { int original_size=0; public CompressHeader() { @@ -186,19 +184,10 @@ original_size=s; } - public int size() { return Global.INT_SIZE; } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeInt(original_size); - } - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - original_size=in.readInt(); - } - public void writeTo(DataOutputStream out) throws IOException { out.writeInt(original_size); } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/DAISYCHAIN.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/DAISYCHAIN.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/DAISYCHAIN.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/DAISYCHAIN.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,243 @@ +package org.jgroups.protocols; + +import org.jgroups.*; +import org.jgroups.annotations.*; +import org.jgroups.stack.Protocol; +import org.jgroups.util.ConcurrentLinkedBlockingQueue; +import org.jgroups.util.Util; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executor; + +/** + * Implementation of daisy chaining. Multicast messages are sent to our neighbor, which sends them to its neighbor etc. + * A TTL restricts the number of times a message is forwarded. The advantage of daisy chaining is that - for + * point-to-point transports such as TCP - we can avoid the N-1 issue: when A sends a multicast message to 10 + * members, it needs to send it 9 times. With daisy chaining, it sends it 1 time, and in the next round, can already + * send another message. This leads to much better throughput, see the ref in the JIRA.

    + * Should be inserted just above MERGE2, in TCP based configurations. + * JIRA: https://jira.jboss.org/browse/JGRP-1021 + * @author Bela Ban + * @since 2.11 + */ +@Experimental +@MBean(description="Protocol just above the transport which disseminates multicasts via daisy chaining") +public class DAISYCHAIN extends Protocol { + + + /* ----------------------------------------- Properties -------------------------------------------------- */ + @Property(description="Loop back multicast messages") + boolean loopback=true; + + @Property(description="The number of messages in the forward queue. This queue is used to host messages that " + + "need to be forwarded by us on behalf of our neighbor") + int forward_queue_size=10000; + + @Property(description="The number of messages in the send queue. This queue is used to host messages that need " + + "to be sent") + int send_queue_size=10000; + + /* --------------------------------------------- Fields ------------------------------------------------------ */ + protected Address local_addr, next; + protected int view_size=0; + protected Executor default_pool=null; + protected Executor oob_pool=null; + protected BlockingQueue send_queue; + protected BlockingQueue forward_queue; + protected volatile boolean forward=false; // flipped between true and false, to ensure fairness + protected volatile boolean running=true; + + @ManagedAttribute + public int msgs_forwarded=0; + + @ManagedAttribute + public int msgs_sent=0; + + @ManagedAttribute + public int getElementsInForwardQueue() {return forward_queue.size();} + + @ManagedAttribute + public int getElementsInSendQueue() {return send_queue.size();} + + public void init() throws Exception { + default_pool=getTransport().getDefaultThreadPool(); + oob_pool=getTransport().getOOBThreadPool(); + send_queue=new ConcurrentLinkedBlockingQueue(send_queue_size); + forward_queue=new ConcurrentLinkedBlockingQueue(forward_queue_size); + } + + public void start() throws Exception { + super.start(); + running=true; + } + + public void stop() { + super.stop(); + running=false; + } + + public Object down(final Event evt) { + switch(evt.getType()) { + case Event.MSG: + final Message msg=(Message)evt.getArg(); + Address dest=msg.getDest(); + if(dest != null && !dest.isMulticastAddress()) + break; // only process multicast messages + + if(next == null) // view hasn't been received yet, use the normal transport + break; + + // we need to copy the message, as we cannot do a msg.setSrc(next): the next retransmission + // would use 'next' as destination ! + Message copy=msg.copy(true); + short hdr_ttl=(short)(loopback? view_size -1 : view_size); + DaisyHeader hdr=new DaisyHeader(hdr_ttl); + copy.setDest(next); + copy.putHeader(getId(), hdr); + + try { + msgs_sent++; + send_queue.put(copy); + } + catch(InterruptedException e) { + Thread.currentThread().interrupt(); + return null; + } + + if(loopback) { + if(log.isTraceEnabled()) log.trace(new StringBuilder("looping back message ").append(msg)); + if(msg.getSrc() == null) + msg.setSrc(local_addr); + + Executor pool=msg.isFlagSet(Message.OOB)? oob_pool : default_pool; + pool.execute(new Runnable() { + public void run() { + up_prot.up(evt); + } + }); + } + + return processQueues(); + + + case Event.VIEW_CHANGE: + handleView((View)evt.getArg()); + break; + + case Event.TMP_VIEW: + view_size=((View)evt.getArg()).size(); + break; + + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; + } + return down_prot.down(evt); + } + + + public Object up(Event evt) { + switch(evt.getType()) { + case Event.MSG: + Message msg=(Message)evt.getArg(); + DaisyHeader hdr=(DaisyHeader)msg.getHeader(getId()); + if(hdr == null) + break; + + // 1. forward the message to the next in line if ttl > 0 + short ttl=hdr.getTTL(); + if(log.isTraceEnabled()) + log.trace(local_addr + ": received message from " + msg.getSrc() + " with ttl=" + ttl); + if(--ttl > 0) { + Message copy=msg.copy(true); + copy.setDest(next); + copy.putHeader(getId(), new DaisyHeader(ttl)); + msgs_forwarded++; + if(forward_queue.offer(copy)) // we don't want incoming threads to block + processQueues(); + } + + // 2. Pass up + msg.setDest(null); + break; + } + return up_prot.up(evt); + } + + + protected Object processQueues() { + int cnt=0; + while(running && cnt++ < 10000) { // cnt is a second line of defense against loops and should never be used ! + try { + Message msg=forward? forward_queue.poll() : send_queue.poll(); + if(msg == null) { + msg=forward? send_queue.poll() : forward_queue.poll(); + if(msg == null) + continue; + } + if(log.isTraceEnabled()) { + DaisyHeader hdr=(DaisyHeader)msg.getHeader(getId()); + log.trace(local_addr + ": " + (forward? " forwarding" : " sending") + " message with ttl=" + hdr.getTTL() + " to " + next); + } + return down_prot.down(new Event(Event.MSG, msg)); + } + catch(Throwable t) { + log.error("failed sending message down", t); + return null; + } + finally { + forward=!forward; + } + } + return null; + } + + + protected void handleView(View view) { + view_size=view.size(); + Address tmp=Util.pickNext(view.getMembers(), local_addr); + if(tmp != null && !tmp.equals(local_addr)) { + next=tmp; + if(log.isDebugEnabled()) + log.debug("next=" + next); + } + } + + + public static class DaisyHeader extends Header { + private short ttl; + + public DaisyHeader() { + } + + public DaisyHeader(short ttl) { + this.ttl=ttl; + } + + public short getTTL() {return ttl;} + + public void setTTL(short ttl) { + this.ttl=ttl; + } + + public int size() { + return Global.SHORT_SIZE; + } + + public void writeTo(DataOutputStream out) throws IOException { + out.writeShort(ttl); + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + ttl=in.readShort(); + } + + public String toString() { + return "ttl=" + ttl; + } + } + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/DELAY.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/DELAY.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/DELAY.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/DELAY.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: DELAY.java,v 1.13 2008/10/23 16:59:41 rachmatowicz Exp $ package org.jgroups.protocols; @@ -23,12 +22,6 @@ @Property int out_delay=0; - /** - * All protocol names have to be unique ! - */ - public String getName() { - return "DELAY"; - } public int getInDelay() { return in_delay ; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/DELAY_JOIN_REQ.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/DELAY_JOIN_REQ.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/DELAY_JOIN_REQ.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/DELAY_JOIN_REQ.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,6 +2,7 @@ import org.jgroups.Event; import org.jgroups.Message; +import org.jgroups.conf.ClassConfigurator; import org.jgroups.annotations.Property; import org.jgroups.annotations.Unsupported; import org.jgroups.protocols.pbcast.GMS; @@ -13,7 +14,6 @@ /** * Discards 2 JOIN-REQs then accepts 1, then discards 2 more and so on * @author Bela Ban - * @version $Id: DELAY_JOIN_REQ.java,v 1.4 2008/10/21 12:10:30 vlada Exp $ */ @Unsupported public class DELAY_JOIN_REQ extends Protocol { @@ -21,9 +21,7 @@ @Property private long delay=4000; - public String getName() { - return "DELAY_JOIN_REQ"; - } + private static final short gms_id=ClassConfigurator.getProtocolId(GMS.class); public long getDelay() { return delay; @@ -37,7 +35,7 @@ switch(evt.getType()) { case Event.MSG: Message msg=(Message)evt.getArg(); - final GMS.GmsHeader hdr=(GMS.GmsHeader)msg.getHeader("GMS"); + final GMS.GmsHeader hdr=(GMS.GmsHeader)msg.getHeader(gms_id); if(hdr != null) { switch(hdr.getType()) { case GMS.GmsHeader.JOIN_REQ: diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/DISCARD.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/DISCARD.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/DISCARD.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/DISCARD.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,22 +1,18 @@ -// $Id: DISCARD.java,v 1.26 2008/11/27 15:42:02 vlada Exp $ package org.jgroups.protocols; -import org.jgroups.Address; +import org.jgroups.*; import org.jgroups.Event; -import org.jgroups.Header; -import org.jgroups.Message; -import org.jgroups.annotations.Property; -import org.jgroups.annotations.Unsupported; +import org.jgroups.annotations.*; import org.jgroups.stack.Protocol; -import org.jgroups.util.Streamable; import org.jgroups.util.Util; +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.io.*; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; /** @@ -25,33 +21,42 @@ * all messages (not very useful). */ @Unsupported +@MBean(description="Discards messages") public class DISCARD extends Protocol { @Property double up=0.0; // probability of dropping up msgs + @Property double down=0.0; // probability of dropping down msgs + @Property boolean excludeItself=true; // if true don't discard messages sent/received in this stack Address localAddress; - int num_down=0, num_up=0; + + @ManagedAttribute(description="Number of dropped down messages",name="droppedDownMessages") + int num_down=0; + + @ManagedAttribute(description="Number of dropped up messages",name="droppedUpMessages") + int num_up=0; final Set

    ignoredMembers = new HashSet
    (); + + + final Collection
    members=new ArrayList
    (); + + @ManagedAttribute(description="drop all messages (up or down)", writable=true) boolean discard_all=false; - // number of subsequent unicasts to drop in the down direction + @ManagedAttribute(description="Number of subsequent unicasts to drop in the down direction",writable=true) int drop_down_unicasts=0; - // number of subsequent multicasts to drop in the down direction + @ManagedAttribute(description="Number of subsequent multicasts to drop in the down direction",writable=true) int drop_down_multicasts=0; - - /** - * All protocol names have to be unique ! - */ - public String getName() { - return "DISCARD"; - } + private DiscardDialog discard_dialog=null; + @Property(name="gui", description="use a GUI or not") + protected boolean use_gui=false; public boolean isDiscardAll() { return discard_all; @@ -67,6 +72,8 @@ public void setLocalAddress(Address localAddress){ this.localAddress =localAddress; + if(discard_dialog != null) + discard_dialog.setTitle(localAddress != null? localAddress.toString() : "n/a"); } public void setExcludeItself(boolean excludeItself) { @@ -112,19 +119,51 @@ /** Messages from this sender will get dropped */ public void addIgnoreMember(Address sender) {ignoredMembers.add(sender);} + public void removeIgnoredMember(Address member) {ignoredMembers.remove(member);} + public void resetIgnoredMembers() {ignoredMembers.clear();} + @ManagedOperation + public void startGui() { + if(discard_dialog == null) { + discard_dialog=new DiscardDialog(); + discard_dialog.init(); + discard_dialog.setTitle(localAddress != null? localAddress.toString() : "n/a"); + discard_dialog.handleView(members); + } + } + + @ManagedOperation + public void stopGui() { + if(discard_dialog != null) + discard_dialog.dispose(); + discard_dialog=null; + } + public void start() throws Exception { super.start(); + if(use_gui) { + discard_dialog=new DiscardDialog(); + discard_dialog.init(); + } + } + + public void stop() { + super.stop(); + if(discard_dialog != null) + discard_dialog.dispose(); } public Object up(Event evt) { Message msg; double r; - if(evt.getType() == Event.SET_LOCAL_ADDRESS) + if(evt.getType() == Event.SET_LOCAL_ADDRESS) { localAddress=(Address)evt.getArg(); + if(discard_dialog != null) + discard_dialog.setTitle("Discard dialog (" + localAddress + ")"); + } if(evt.getType() == Event.MSG) { msg=(Message)evt.getArg(); @@ -134,7 +173,7 @@ return null; } - DiscardHeader dh = (DiscardHeader) msg.getHeader(getName()); + DiscardHeader dh = (DiscardHeader) msg.getHeader(this.id); if (dh != null) { ignoredMembers.clear(); ignoredMembers.addAll(dh.dropMessages); @@ -144,7 +183,7 @@ boolean dropMessage=ignoredMembers.contains(sender); if (dropMessage) { if (log.isTraceEnabled()) - log.trace("dropping message from " + sender); + log.trace(localAddress + ": dropping message from " + sender); num_up++; return null; } @@ -157,7 +196,7 @@ log.trace("excluding itself"); } else { if (log.isTraceEnabled()) - log.trace("dropping message from " + sender); + log.trace(localAddress + ": dropping message from " + sender); num_up++; return null; } @@ -174,7 +213,8 @@ Message msg; double r; - if(evt.getType() == Event.MSG) { + switch(evt.getType()) { + case Event.MSG: msg=(Message)evt.getArg(); Address dest=msg.getDest(); boolean multicast=dest == null || dest.isMulticastAddress(); @@ -214,6 +254,22 @@ } } } + break; + case Event.VIEW_CHANGE: + View view=(View)evt.getArg(); + Vector
    mbrs=view.getMembers(); + members.clear(); + members.addAll(mbrs); + ignoredMembers.retainAll(mbrs); // remove all non members + if(discard_dialog != null) + discard_dialog.handleView(mbrs); + break; + + case Event.SET_LOCAL_ADDRESS: + localAddress=(Address)evt.getArg(); + if(discard_dialog != null) + discard_dialog.setTitle("Discard dialog (" + localAddress + ")"); + break; } return down_prot.down(evt); @@ -238,17 +294,10 @@ num_down=num_up=0; } - public Map dumpStats() { - Map m=new HashMap(2); - m.put("num_dropped_down", new Integer(num_down)); - m.put("num_dropped_up", new Integer(num_up)); - return m; - } - - public static class DiscardHeader extends Header implements Streamable { + + public static class DiscardHeader extends Header { private final Set
    dropMessages; - private static final long serialVersionUID=-2149735838082082084L; public DiscardHeader() { this.dropMessages= new HashSet
    (); @@ -281,14 +330,87 @@ } - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - Set
    tmp = (Set
    ) in.readObject(); - dropMessages.clear(); - dropMessages.addAll(tmp); - } + public int size() { + return (int)Util.size(dropMessages); + } + } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeObject(dropMessages); - } - } -} + + private class DiscardDialog extends JFrame implements ActionListener { + private JButton start_discarding_button=new JButton("start discarding"); + private JButton stop_discarding_button=new JButton("stop discarding"); + JPanel checkboxes=new JPanel(); + + + private DiscardDialog() { + } + + void init() { + getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); + checkboxes.setLayout(new BoxLayout(checkboxes, BoxLayout.Y_AXIS)); + getContentPane().add(start_discarding_button); + getContentPane().add(stop_discarding_button); + start_discarding_button.addActionListener(this); + stop_discarding_button.addActionListener(this); + getContentPane().add(checkboxes); + pack(); + setVisible(true); + setTitle(localAddress != null? localAddress.toString() : "n/a"); + } + + + public void actionPerformed(ActionEvent e) { + String command=e.getActionCommand(); + if(command.startsWith("start")) { + discard_all=true; + } + else if(command.startsWith("stop")) { + discard_all=false; + Component[] comps=checkboxes.getComponents(); + for(Component c: comps) { + if(c instanceof JCheckBox) { + ((JCheckBox)c).setSelected(false); + } + } + } + } + + void handleView(Collection
    mbrs) { + checkboxes.removeAll(); + for(final Address addr: mbrs) { + final MyCheckBox box=new MyCheckBox("discard traffic from " + addr, addr); + box.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if(box.isSelected()) { + ignoredMembers.add(addr); + } + else { + ignoredMembers.remove(addr); + } + } + }); + checkboxes.add(box); + } + + for(Component comp: checkboxes.getComponents()) { + MyCheckBox box=(MyCheckBox)comp; + if(ignoredMembers.contains(box.mbr)) + box.setSelected(true); + } + pack(); + } + } + + private static class MyCheckBox extends JCheckBox { + final Address mbr; + + public MyCheckBox(String name, Address member) { + super(name); + this.mbr=member; + } + + public String toString() { + return super.toString() + " [mbr=" + mbr + "]"; + } + } +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/DISCARD_PAYLOAD.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/DISCARD_PAYLOAD.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/DISCARD_PAYLOAD.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/DISCARD_PAYLOAD.java 2011-10-18 11:22:35.000000000 +0000 @@ -8,10 +8,9 @@ /** * Discards a message whose sequence number (in the payload, as a Long) matches seqno 2 times, - * before passing it up. Used for unit testing + * before passing it down. Used for unit testing * of OOB messages * @author Bela Ban - * @version $Id: DISCARD_PAYLOAD.java,v 1.8 2008/10/21 12:10:30 vlada Exp $ */ @Unsupported public class DISCARD_PAYLOAD extends Protocol { @@ -24,11 +23,7 @@ public DISCARD_PAYLOAD() { } - public String getName() { - return "DISCARD_PAYLOAD"; - } - - public Object up(Event evt) { + public Object down(Event evt) { if(evt.getType() == Event.MSG) { Message msg=(Message)evt.getArg(); if(msg.getLength() > 0) { @@ -46,7 +41,7 @@ } } if(val == duplicate) { // inject a duplicate message - super.up(evt); // pass it up, will passed up a second time by the default up_prot.up(evt) + super.down(evt); // pass it down, will passed down a second time by the default down_prot.down(evt) } } } @@ -55,6 +50,6 @@ } } } - return up_prot.up(evt); + return down_prot.down(evt); } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/Discovery.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/Discovery.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/Discovery.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/Discovery.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,16 +1,16 @@ - package org.jgroups.protocols; import org.jgroups.*; import org.jgroups.annotations.MBean; import org.jgroups.annotations.ManagedAttribute; import org.jgroups.annotations.Property; +import org.jgroups.annotations.ManagedOperation; import org.jgroups.protocols.pbcast.JoinRsp; import org.jgroups.stack.Protocol; -import org.jgroups.util.Promise; -import org.jgroups.util.TimeScheduler; -import org.jgroups.util.Util; +import org.jgroups.util.*; +import org.jgroups.util.UUID; +import java.io.InterruptedIOException; import java.util.*; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -39,7 +39,6 @@ * * * @author Bela Ban - * @version $Id: Discovery.java,v 1.52 2008/10/21 12:37:10 vlada Exp $ */ @MBean public abstract class Discovery extends Protocol { @@ -48,47 +47,59 @@ /* ----------------------------------------- Properties -------------------------------------------------- */ @Property(description="Timeout to wait for the initial members. Default is 3000 msec") - @ManagedAttribute(description="Timeout (ms) to wait for the initial members", writable=true) - long timeout=3000; + protected long timeout=3000; @Property(description="Minimum number of initial members to get a response from. Default is 2") - @ManagedAttribute(description="Minimum number of initial members to get a response from", writable=true) - int num_initial_members=2; + protected int num_initial_members=2; - @Property(description="Minimum number of server responses (PingRsp.isServer()=true). If this value is " + + @Property(description="Minimum number of server responses (PingData.isServer()=true). If this value is " + "greater than 0, we'll ignore num_initial_members") - @ManagedAttribute(writable=true, description="Minimum number of server responses (PingRsp.isServer()=true). " + - "If this value is greater than 0, we'll ignore num_initial_members") - int num_initial_srv_members=0; + protected int num_initial_srv_members=0; @Property(description="Return from the discovery phase as soon as we have 1 coordinator response") - @ManagedAttribute(writable=true, description="Return from the discovery phase as soon as we have 1 coordinator response") - boolean break_on_coord_rsp=true; + protected boolean break_on_coord_rsp=true; @Property(description="Number of discovery requests to be sent distributed over timeout. Default is 2") - @ManagedAttribute(description="Number of discovery requests to be sent (min=1), " + "distributed over timeout ms", writable=true) - int num_ping_requests=2; + protected int num_ping_requests=2; + + @Property(description="Whether or not to return the entire logical-physical address cache mappings on a " + + "discovery request, or not. Default is false, except for TCPPING") + protected boolean return_entire_cache=false; + + @Property(description="Only members with a rank <= max_rank will send a discovery response. 1 means only the " + + "coordinator will reply. 0 disables this; everyone replies. JIRA: https://jira.jboss.org/browse/JGRP-1181") + protected int max_rank=0; + + @Property(description="If greater than 0, we'll wait a random number of milliseconds in range [0..stagger_timeout] " + + "before sending a discovery response. This prevents traffic spikes in large clusters when everyone sends their " + + "discovery response at the same time") + protected long stagger_timeout=0; - /* --------------------------------------------- JMX ------------------------------------------------------ */ @ManagedAttribute(description="Total number of discovery requests sent ") - int num_discovery_requests=0; + protected int num_discovery_requests=0; + /** The largest cluster size found so far (gets reset on stop()) */ + @ManagedAttribute + protected volatile int max_found_members=0; + + @ManagedAttribute + protected int rank=0; /* --------------------------------------------- Fields ------------------------------------------------------ */ - - - private volatile boolean is_server=false; + + protected volatile boolean is_server=false; protected TimeScheduler timer=null; - private final Vector
    members=new Vector
    (11); + protected View view; + protected final Vector
    members=new Vector
    (11); protected Address local_addr=null; protected String group_addr=null; - private final Set ping_responses=new HashSet(); - private final PingSenderTask sender=new PingSenderTask(); + protected final Set ping_responses=new HashSet(); + protected final PingSenderTask sender=new PingSenderTask(); @@ -96,14 +107,21 @@ timer=getTransport().getTimer(); if(timer == null) throw new Exception("timer cannot be retrieved from protocol stack"); + if(max_rank > 0) + return_entire_cache=true; + if(stagger_timeout < 0) + throw new IllegalArgumentException("stagger_timeout cannot be negative"); + if(stagger_timeout > timeout / num_ping_requests) { + log.debug("stagger_timeout (" + stagger_timeout + ") was greater than timeout (" + timeout/num_ping_requests + + "); setting it to " + timeout/num_ping_requests + " ms"); + stagger_timeout=timeout / num_ping_requests; + } } - /** Called after local_addr was set */ - public void localAddressSet(Address addr) { - } + public abstract void sendGetMembersRequest(String cluster_name, Promise promise, ViewId view_id) throws Exception; - public abstract void sendGetMembersRequest(String cluster_name); + public abstract boolean isDynamic(); public void handleDisconnect() { @@ -112,6 +130,10 @@ public void handleConnect() { } + public void discoveryRequestReceived(Address sender, String logical_name, Collection physical_addrs) { + + } + public long getTimeout() { return timeout; } @@ -140,10 +162,19 @@ return num_discovery_requests; } + @ManagedAttribute + public String getView() {return view != null? view.getViewId().toString() : "null";} + + public ViewId getViewId() { + return view != null? view.getViewId() : null; + } + public Vector providedUpServices() { - Vector ret=new Vector(1); - ret.addElement(new Integer(Event.FIND_INITIAL_MBRS)); + Vector ret=new Vector(3); + ret.addElement(Event.FIND_INITIAL_MBRS); + ret.addElement(Event.FIND_ALL_VIEWS); + ret.addElement(Event.GET_PHYSICAL_ADDRESS); return ret; } @@ -157,28 +188,41 @@ } public void stop() { - is_server=false; + is_server=false; + max_found_members=0; } + /** - * Finds the initial membership: sends a GET_MBRS_REQ to all members, waits 'timeout' ms or - * until 'num_initial_members' have been retrieved - * @return List + * Finds initial members + * @param promise + * @return */ - public List findInitialMembers(Promise promise) { + public List findInitialMembers(Promise promise) { + return findMembers(promise, num_initial_members, break_on_coord_rsp, null); + } + + public List findAllViews(Promise promise) { + int num_expected_mbrs=Math.max(max_found_members, Math.max(num_initial_members, view != null? view.size() : num_initial_members)); + max_found_members=Math.max(max_found_members, num_expected_mbrs); + return findMembers(promise, num_expected_mbrs, false, getViewId()); + } + + protected List findMembers(Promise promise, int num_expected_rsps, + boolean break_on_coord, ViewId view_id) { num_discovery_requests++; - final Responses rsps=new Responses(num_initial_members, num_initial_srv_members, break_on_coord_rsp, promise); + final Responses rsps=new Responses(num_expected_rsps, num_initial_srv_members, break_on_coord, promise); synchronized(ping_responses) { ping_responses.add(rsps); } - sender.start(group_addr); + sender.start(group_addr, promise, view_id); try { return rsps.get(timeout); } catch(Exception e) { - return new LinkedList(); + return new LinkedList(); } finally { sender.stop(); @@ -189,17 +233,33 @@ } + + @ManagedOperation(description="Runs the discovery protocol to find initial members") public String findInitialMembersAsString() { - List results=findInitialMembers(null); + List results=findInitialMembers(null); if(results == null || results.isEmpty()) return ""; StringBuilder sb=new StringBuilder(); - for(PingRsp rsp: results) { + for(PingData rsp: results) { sb.append(rsp).append("\n"); } return sb.toString(); } + @ManagedOperation(description="Runs the discovery protocol to find all views") + public String findAllViewsAsString() { + List rsps=findAllViews(null); + if(rsps == null || rsps.isEmpty()) return ""; + StringBuilder sb=new StringBuilder(); + for(PingData data: rsps) { + View v=data.getView(); + if(v != null) + sb.append(v).append("\n"); + } + return sb.toString(); + } + + /** * An event was received from the layer below. Usually the current layer will want to examine * the event type and - depending on its type - perform some computation @@ -222,87 +282,136 @@ * @param evt - the event that has been sent from the layer below */ + @SuppressWarnings("unchecked") public Object up(Event evt) { - Message msg, rsp_msg; - PingHeader rsp_hdr; - PingRsp rsp; - Address coord; - + switch(evt.getType()) { - case Event.MSG: - msg=(Message)evt.getArg(); - PingHeader hdr=(PingHeader)msg.getHeader(getName()); - if(hdr == null) { - return up_prot.up(evt); - } + case Event.MSG: + Message msg=(Message)evt.getArg(); + PingHeader hdr=(PingHeader)msg.getHeader(this.id); + if(hdr == null) + return up_prot.up(evt); + + PingData data=hdr.data; + Address logical_addr=data != null? data.getAddress() : null; + + switch(hdr.type) { + + case PingHeader.GET_MBRS_REQ: // return Rsp(local_addr, coord) + if(group_addr == null || hdr.cluster_name == null) { + if(log.isWarnEnabled()) + log.warn("group_addr (" + group_addr + ") or cluster_name of header (" + hdr.cluster_name + + ") is null; passing up discovery request from " + msg.getSrc() + ", but this should not" + + " be the case"); + } + else { + if(!group_addr.equals(hdr.cluster_name)) { + if(log.isWarnEnabled()) + log.warn("discarding discovery request for cluster '" + hdr.cluster_name + "' from " + + msg.getSrc() + "; our cluster name is '" + group_addr + "'. " + + "Please separate your clusters cleanly."); + return null; + } + } - switch(hdr.type) { + // add physical address and logical name of the discovery sender (if available) to the cache + if(data != null) { + if(logical_addr == null) + logical_addr=msg.getSrc(); + Collection physical_addrs=data.getPhysicalAddrs(); + PhysicalAddress physical_addr=physical_addrs != null && !physical_addrs.isEmpty()? physical_addrs.iterator().next() : null; + if(logical_addr != null && physical_addr != null) + down(new Event(Event.SET_PHYSICAL_ADDRESS, new Tuple(logical_addr, physical_addr))); + if(logical_addr != null && data.getLogicalName() != null) + UUID.add(logical_addr, data.getLogicalName()); + discoveryRequestReceived(msg.getSrc(), data.getLogicalName(), physical_addrs); + } - case PingHeader.GET_MBRS_REQ: // return Rsp(local_addr, coord) - if(local_addr != null && msg.getSrc() != null && local_addr.equals(msg.getSrc())) { - return null; - } + if(max_rank > 0 && rank > 0 && rank > max_rank) // https://jira.jboss.org/browse/JGRP-1181 + return null; - if(group_addr == null || hdr.cluster_name == null) { - if(log.isWarnEnabled()) - log.warn("group_addr (" + group_addr + ") or cluster_name of header (" + hdr.cluster_name - + ") is null; passing up discovery request from " + msg.getSrc() + ", but this should not" + - " be the case"); - } - else { - if(!group_addr.equals(hdr.cluster_name)) { - if(log.isWarnEnabled()) - log.warn("discarding discovery request for cluster '" + hdr.cluster_name + "' from " + - msg.getSrc() + "; our cluster name is '" + group_addr + "'. " + - "Please separate your clusters cleanly."); + if(return_entire_cache && hdr.view_id == null && rank != 0) { + Map cache=(Map)down(new Event(Event.GET_LOGICAL_PHYSICAL_MAPPINGS)); + if(cache != null) { + for(Map.Entry entry: cache.entrySet()) { + Address addr=entry.getKey(); + PhysicalAddress physical_addr=entry.getValue(); + sendDiscoveryResponse(addr, Arrays.asList(physical_addr), is_server, + hdr.view_id != null, UUID.get(addr), msg.getSrc()); + } + } + } + else { + if(hdr.view_id != null) { + + // If the discovery request is merge-triggered, and we the ViewId shipped with it + // is the same as ours, we don't respond (JGRP-1315). + ViewId my_view_id=view != null? view.getViewId() : null; + if(my_view_id != null && Util.sameViewId(my_view_id, hdr.view_id)) + return null; + } + + List physical_addrs=hdr.view_id != null? null : + Arrays.asList((PhysicalAddress)down(new Event(Event.GET_PHYSICAL_ADDRESS, local_addr))); + sendDiscoveryResponse(local_addr, physical_addrs, is_server, hdr.view_id != null, + UUID.get(local_addr), msg.getSrc()); + } return null; - } - } - synchronized(members) { - coord=!members.isEmpty()? members.firstElement() : local_addr; - } + case PingHeader.GET_MBRS_RSP: // add response to vector and notify waiting thread + // add physical address (if available) to transport's cache + if(data != null) { + Address response_sender=msg.getSrc(); + if(logical_addr == null) + logical_addr=msg.getSrc(); + Collection physical_addrs=data.getPhysicalAddrs(); + PhysicalAddress physical_addr=physical_addrs != null && !physical_addrs.isEmpty()? + physical_addrs.iterator().next() : null; + if(logical_addr != null && physical_addr != null) + down(new Event(Event.SET_PHYSICAL_ADDRESS, new Tuple(logical_addr, physical_addr))); + if(logical_addr != null && data.getLogicalName() != null) + UUID.add(logical_addr, data.getLogicalName()); + + if(log.isTraceEnabled()) + log.trace("received GET_MBRS_RSP from " + response_sender + ": " + data); + boolean overwrite=logical_addr != null && logical_addr.equals(response_sender); + synchronized(ping_responses) { + for(Responses response: ping_responses) { + response.addResponse(data, overwrite); + } + } + } + return null; - PingRsp ping_rsp=new PingRsp(local_addr, coord, is_server); - rsp_msg=new Message(msg.getSrc(), null, null); - rsp_msg.setFlag(Message.OOB); - rsp_hdr=new PingHeader(PingHeader.GET_MBRS_RSP, ping_rsp); - rsp_msg.putHeader(getName(), rsp_hdr); - if(log.isTraceEnabled()) - log.trace("received GET_MBRS_REQ from " + msg.getSrc() + ", sending response " + rsp_hdr); - down_prot.down(new Event(Event.MSG, rsp_msg)); - return null; + default: + if(log.isWarnEnabled()) log.warn("got PING header with unknown type (" + hdr.type + ')'); + return null; + } - case PingHeader.GET_MBRS_RSP: // add response to vector and notify waiting thread - rsp=hdr.arg; - if(log.isTraceEnabled()) - log.trace("received GET_MBRS_RSP, rsp=" + rsp); - - synchronized(ping_responses) { - for(Responses rsps: ping_responses) - rsps.addResponse(rsp); + case Event.GET_PHYSICAL_ADDRESS: + try { + sendGetMembersRequest(group_addr, null, null); + } + catch(InterruptedIOException ie) { + if(log.isWarnEnabled()){ + log.warn("Discovery request for cluster " + group_addr + " interrupted"); + } + Thread.currentThread().interrupt(); + } + catch(Exception ex) { + if(log.isErrorEnabled()) + log.error("failed sending discovery request", ex); } return null; - default: - if(log.isWarnEnabled()) log.warn("got PING header with unknown type (" + hdr.type + ')'); - return null; - } - case Event.SET_LOCAL_ADDRESS: - up_prot.up(evt); - local_addr=(Address)evt.getArg(); - localAddressSet(local_addr); - break; - - default: - up_prot.up(evt); // Pass up to the layer above us - break; + case Event.FIND_INITIAL_MBRS: // sent by transport + return findInitialMembers(null); } - return null; + return up_prot.up(evt); } @@ -319,48 +428,75 @@ * Event.BECOME_SERVER - called after client has joined and is fully working group member * Event.CONNECT, Event.DISCONNECT. */ + @SuppressWarnings("unchecked") public Object down(Event evt) { switch(evt.getType()) { - case Event.FIND_INITIAL_MBRS: // sent by GMS layer, pass up a GET_MBRS_OK event - // sends the GET_MBRS_REQ to all members, waits 'timeout' ms or until 'num_initial_members' have been retrieved - long start=System.currentTimeMillis(); - List rsps=findInitialMembers((Promise)evt.getArg()); - long diff=System.currentTimeMillis() - start; - if(log.isTraceEnabled()) - log.trace("discovery took "+ diff + " ms: responses: " + Util.printPingRsps(rsps)); - return rsps; - - case Event.TMP_VIEW: - case Event.VIEW_CHANGE: - Vector
    tmp; - if((tmp=((View)evt.getArg()).getMembers()) != null) { - synchronized(members) { - members.clear(); - members.addAll(tmp); + case Event.FIND_INITIAL_MBRS: // sent by GMS layer + case Event.FIND_ALL_VIEWS: + // sends the GET_MBRS_REQ to all members, waits 'timeout' ms or until 'num_initial_members' have been retrieved + long start=System.currentTimeMillis(); + boolean find_all_views=evt.getType() == Event.FIND_ALL_VIEWS; + Promise promise=(Promise)evt.getArg(); + List rsps=find_all_views? findAllViews(promise) : findInitialMembers(promise); + long diff=System.currentTimeMillis() - start; + if(log.isTraceEnabled()) + log.trace("discovery took "+ diff + " ms: responses: " + Util.printPingData(rsps)); + return rsps; + + case Event.TMP_VIEW: + case Event.VIEW_CHANGE: + Vector
    tmp; + view=(View)evt.getArg(); + if((tmp=view.getMembers()) != null) { + synchronized(members) { + members.clear(); + members.addAll(tmp); + } } - } - return down_prot.down(evt); + rank=Util.getRank(view, local_addr); + if(ergonomics) { + int size=view.size(); + if(size <= Global.SMALL_CLUSTER_SIZE) { + max_rank=0; + return_entire_cache=false; + } + else if(size <= Global.NORMAL_CLUSTER_SIZE) { + max_rank=size / 5; + return_entire_cache=true; + } + else { + max_rank=Math.min(size / 5, 10); + return_entire_cache=true; + } + } + return down_prot.down(evt); - case Event.BECOME_SERVER: // called after client has joined and is fully working group member - down_prot.down(evt); - is_server=true; - return null; + case Event.BECOME_SERVER: // called after client has joined and is fully working group member + down_prot.down(evt); + is_server=true; + return null; - case Event.CONNECT: - case Event.CONNECT_WITH_STATE_TRANSFER: - group_addr=(String)evt.getArg(); - Object ret=down_prot.down(evt); - handleConnect(); - return ret; - - case Event.DISCONNECT: - handleDisconnect(); - return down_prot.down(evt); + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + return down_prot.down(evt); + + case Event.CONNECT: + case Event.CONNECT_WITH_STATE_TRANSFER: + case Event.CONNECT_USE_FLUSH: + case Event.CONNECT_WITH_STATE_TRANSFER_USE_FLUSH: + group_addr=(String)evt.getArg(); + Object ret=down_prot.down(evt); + handleConnect(); + return ret; + + case Event.DISCONNECT: + handleDisconnect(); + return down_prot.down(evt); - default: - return down_prot.down(evt); // Pass on to the layer below us + default: + return down_prot.down(evt); // Pass on to the layer below us } } @@ -370,32 +506,94 @@ @SuppressWarnings("unchecked") - protected final View makeView(Vector mbrs) { - Address coord; - long id; - ViewId view_id=new ViewId(local_addr); - - coord=view_id.getCoordAddress(); - id=view_id.getId(); - return new View(coord, id, mbrs); + protected final View makeView(Vector
    mbrs) { + return new View(new ViewId(local_addr), mbrs); } - + /** + * Creates a byte[] representation of the PingData, but DISCARDING the view it contains. + * @param data the PingData instance to serialize. + * @return + */ + protected byte[] serializeWithoutView(PingData data) { + final PingData clone = new PingData(data.getAddress(), null, data.isServer(), data.getLogicalName(), data.getPhysicalAddrs()); + try { + return Util.streamableToByteBuffer(clone); + } + catch(Exception e) { + log.error("Error", e); + return null; + } + } + + protected PingData deserialize(final byte[] data) { + try { + return (PingData)Util.streamableFromByteBuffer(PingData.class, data); + } + catch(Exception e) { + log.error("Error", e); + return null; + } + } + + + protected void sendDiscoveryResponse(Address logical_addr, List physical_addrs, + boolean is_server, boolean return_view_only, String logical_name, final Address sender) { + PingData data; + if(return_view_only) { + // data=new PingData(logical_addr, view, is_server, logical_name, physical_addrs); + data=new PingData(logical_addr, view, is_server, null, null); + } + else { + ViewId view_id=view != null? view.getViewId() : null; + data=new PingData(logical_addr, null, view_id, is_server, logical_name, physical_addrs); + } + + final Message rsp_msg=new Message(sender, null, null); + rsp_msg.setFlag(Message.OOB); + final PingHeader rsp_hdr=new PingHeader(PingHeader.GET_MBRS_RSP, data); + rsp_msg.putHeader(this.id, rsp_hdr); + + if(stagger_timeout > 0) { + int view_size=view != null? view.size() : 10; + long sleep_time=rank == 0? Util.random(stagger_timeout) + : stagger_timeout * rank / view_size - (stagger_timeout / view_size); + timer.schedule(new Runnable() { + public void run() { + if(log.isTraceEnabled()) + log.trace("received GET_MBRS_REQ from " + sender + ", sending staggered response " + rsp_hdr); + down_prot.down(new Event(Event.MSG, rsp_msg)); + } + }, sleep_time, TimeUnit.MILLISECONDS); + return; + } + + if(log.isTraceEnabled()) + log.trace("received GET_MBRS_REQ from " + sender + ", sending response " + rsp_hdr); + down_prot.down(new Event(Event.MSG, rsp_msg)); + } - class PingSenderTask { - private Future senderFuture; + + protected class PingSenderTask { + protected Future senderFuture; public PingSenderTask() {} - public synchronized void start(final String cluster_name) { + public synchronized void start(final String cluster_name, final Promise promise, final ViewId view_id) { long delay = (long)(timeout / (double)num_ping_requests); if(senderFuture == null || senderFuture.isDone()) { senderFuture=timer.scheduleWithFixedDelay(new Runnable() { public void run() { try { - sendGetMembersRequest(cluster_name); + sendGetMembersRequest(cluster_name, promise, view_id); + } + catch(InterruptedIOException ie) { + ; + } + catch(InterruptedException ex) { + ; } - catch(Exception ex) { + catch(Throwable ex) { if(log.isErrorEnabled()) log.error("failed sending discovery request", ex); } @@ -413,9 +611,9 @@ } - private static class Responses { + protected static class Responses { final Promise promise; - final List ping_rsps=new LinkedList(); + final List ping_rsps=new ArrayList(); final int num_expected_rsps; final int num_expected_srv_rsps; final boolean break_on_coord_rsp; @@ -427,22 +625,40 @@ this.promise=promise != null? promise : new Promise(); } - public void addResponse(PingRsp rsp) { + public void addResponse(PingData rsp) { + addResponse(rsp, false); + } + + public void addResponse(PingData rsp, boolean overwrite) { if(rsp == null) return; promise.getLock().lock(); try { - if(!ping_rsps.contains(rsp)) { + if(overwrite) + ping_rsps.remove(rsp); + + // https://jira.jboss.org/jira/browse/JGRP-1179 + int index=ping_rsps.indexOf(rsp); + if(index == -1) { ping_rsps.add(rsp); promise.getCond().signalAll(); } + else if(rsp.isCoord()) { + PingData pr=ping_rsps.get(index); + + // Check if the already existing element is not server + if(!pr.isCoord()) { + ping_rsps.set(index, rsp); + promise.getCond().signalAll(); + } + } } finally { promise.getLock().unlock(); } } - public List get(long timeout) throws InterruptedException{ + public List get(long timeout) throws InterruptedException{ long start_time=System.currentTimeMillis(), time_to_wait=timeout; promise.getLock().lock(); @@ -452,38 +668,38 @@ if(num_expected_srv_rsps > 0) { int received_srv_rsps=getNumServerResponses(ping_rsps); if(received_srv_rsps >= num_expected_srv_rsps) - return new LinkedList(ping_rsps); + return new LinkedList(ping_rsps); } else if(ping_rsps.size() >= num_expected_rsps) { - return new LinkedList(ping_rsps); + return new LinkedList(ping_rsps); } if(break_on_coord_rsp && containsCoordinatorResponse(ping_rsps)) - return new LinkedList(ping_rsps); + return new LinkedList(ping_rsps); promise.getCond().await(time_to_wait, TimeUnit.MILLISECONDS); time_to_wait=timeout - (System.currentTimeMillis() - start_time); } - return new LinkedList(ping_rsps); + return new LinkedList(ping_rsps); } finally { promise.getLock().unlock(); } } - private static int getNumServerResponses(List rsps) { + protected static int getNumServerResponses(Collection rsps) { int cnt=0; - for(PingRsp rsp: rsps) { + for(PingData rsp: rsps) { if(rsp.isServer()) cnt++; } return cnt; } - private static boolean containsCoordinatorResponse(List rsps) { + protected static boolean containsCoordinatorResponse(Collection rsps) { if(rsps == null || rsps.isEmpty()) return false; - for(PingRsp rsp: rsps) { + for(PingData rsp: rsps) { if(rsp.isCoord()) return true; } @@ -491,4 +707,4 @@ } } -} +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/DUMMY_TP.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/DUMMY_TP.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/DUMMY_TP.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/DUMMY_TP.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,75 +0,0 @@ -// $Id: DUMMY_TP.java,v 1.8 2008/10/21 12:10:30 vlada Exp $ - -package org.jgroups.protocols; - - -import org.jgroups.Address; -import org.jgroups.Event; -import org.jgroups.annotations.Unsupported; -import org.jgroups.stack.Protocol; - - -/** - * Dummy transport, returns a fake local address and responds to CONNECT. - * Compared to LOOPBACK, this discards everything - * @author Bela Ban - * @version $Id: DUMMY_TP.java,v 1.8 2008/10/21 12:10:30 vlada Exp $ - */ -@Unsupported -public class DUMMY_TP extends Protocol { - private Address local_addr=null; - - public DUMMY_TP() { - } - - - public String toString() { - return "Protocol DUMMY_TP (local address: " + local_addr + ')'; - } - - - /*------------------------------ Protocol interface ------------------------------ */ - - public String getName() { - return "DUMMY_TP"; - } - - - - - public void init() throws Exception { - local_addr=new org.jgroups.stack.IpAddress("localhost", 10000); // fake address - } - - public void start() throws Exception { - up_prot.up(new Event(Event.SET_LOCAL_ADDRESS, local_addr)); - } - - - /** - * Caller by the layer above this layer. Usually we just put this Message - * into the send queue and let one or more worker threads handle it. A worker thread - * then removes the Message from the send queue, performs a conversion and adds the - * modified Message to the send queue of the layer below it, by calling Down). - */ - public Object down(Event evt) { - - switch(evt.getType()) { - - case Event.CONNECT: - return null; - - case Event.DISCONNECT: - return null; - } - return down_prot.down(evt); - } - - - - /*--------------------------- End of Protocol interface -------------------------- */ - - - - -} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/DUPL.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/DUPL.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/DUPL.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/DUPL.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,7 +2,6 @@ import org.jgroups.stack.Protocol; import org.jgroups.annotations.Property; -import org.jgroups.annotations.ManagedAttribute; import org.jgroups.annotations.Unsupported; import org.jgroups.Event; import org.jgroups.Message; @@ -10,7 +9,6 @@ /** Duplicates outgoing or incoming messages by copying them * @author Bela Ban - * @version $Id: DUPL.java,v 1.4 2008/10/21 12:10:30 vlada Exp $ */ @Unsupported public class DUPL extends Protocol { @@ -18,16 +16,16 @@ private static enum Direction {UP,DOWN}; - @Property @ManagedAttribute(description="Number of copies of each incoming message (0=no copies)",writable=true) + @Property(description="Number of copies of each incoming message (0=no copies)") protected int incoming_copies=1; - @Property @ManagedAttribute(description="Number of copies of each outgoing message (0=no copies)",writable=true) + @Property(description="Number of copies of each outgoing message (0=no copies)") protected int outgoing_copies=1; - @Property @ManagedAttribute(description="Whether or not to copy unicast messages",writable=true) + @Property(description="Whether or not to copy unicast messages") protected boolean copy_unicast_msgs=true; - @Property @ManagedAttribute(description="Whether or not to copy multicast messages",writable=true) + @Property(description="Whether or not to copy multicast messages") protected boolean copy_multicast_msgs=true; @@ -41,10 +39,38 @@ this.outgoing_copies=outgoing_copies; } - public String getName() { - return "DUPL"; + + public int getIncomingCopies() { + return incoming_copies; + } + + public void setIncomingCopies(int incoming_copies) { + this.incoming_copies=incoming_copies; + } + + public int getOutgoingCopies() { + return outgoing_copies; + } + + public void setOutgoingCopies(int outgoing_copies) { + this.outgoing_copies=outgoing_copies; } + public boolean isCopyUnicastMsgs() { + return copy_unicast_msgs; + } + + public void setCopyUnicastMsgs(boolean copy_unicast_msgs) { + this.copy_unicast_msgs=copy_unicast_msgs; + } + + public boolean isCopyMulticastMsgs() { + return copy_multicast_msgs; + } + + public void setCopyMulticastMsgs(boolean copy_multicast_msgs) { + this.copy_multicast_msgs=copy_multicast_msgs; + } public Object down(Event evt) { boolean copy=(copy_multicast_msgs || copy_unicast_msgs) && outgoing_copies > 0; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/ENCRYPT.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/ENCRYPT.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/ENCRYPT.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/ENCRYPT.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,12 +1,11 @@ -// $Id: ENCRYPT.java,v 1.48 2008/10/21 10:03:52 vlada Exp $ package org.jgroups.protocols; import org.jgroups.*; +import org.jgroups.annotations.GuardedBy; import org.jgroups.annotations.Property; import org.jgroups.stack.Protocol; import org.jgroups.util.QueueClosedException; -import org.jgroups.util.Streamable; import org.jgroups.util.Util; import javax.crypto.*; @@ -19,10 +18,13 @@ import java.security.cert.CertificateException; import java.security.spec.X509EncodedKeySpec; import java.util.Map; +import java.util.Vector; import java.util.WeakHashMap; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; /** * ENCRYPT layer. Encrypt and decrypt the group communication in JGroups @@ -127,7 +129,8 @@ @Property(name="asym_provider", description="Cryptographic Service Provider. Default is Bouncy Castle Provider") String asymProvider=null; - static final String symProvider=null; + @Property(name="sym_provider", description="Cryptographic Service Provider. Default is Bouncy Castle Provider") + String symProvider=null; @Property(name="asym_algorithm", description="Cipher engine transformation for asymmetric algorithm. Default is RSA") String asymAlgorithm="RSA"; @@ -165,8 +168,13 @@ // needed because we do simultaneous encode/decode with these ciphers - which // would be a threading issue Cipher symEncodingCipher; + + @GuardedBy("decrypt_lock") Cipher symDecodingCipher; + /** To synchronize access to symDecodingCipher */ + protected final Lock decrypt_lock=new ReentrantLock(); + // version filed for secret key private String symVersion=null; // dhared secret key to encrypt/decrypt messages @@ -196,10 +204,6 @@ public ENCRYPT() {} - public String getName() { - return "ENCRYPT"; - } - public void setObserver(Observer o) { observer=o; } @@ -339,8 +343,15 @@ if(log.isInfoEnabled()) log.info(" Initializing symmetric ciphers"); - symEncodingCipher=Cipher.getInstance(algorithm); - symDecodingCipher=Cipher.getInstance(algorithm); + if(symProvider != null && symProvider.trim().length() > 0) { + symEncodingCipher=Cipher.getInstance(algorithm, symProvider); + symDecodingCipher=Cipher.getInstance(algorithm, symProvider); + } + else { + symEncodingCipher=Cipher.getInstance(algorithm); + symDecodingCipher=Cipher.getInstance(algorithm); + } + symEncodingCipher.init(Cipher.ENCRYPT_MODE, secret); symDecodingCipher.init(Cipher.DECRYPT_MODE, secret); @@ -351,7 +362,8 @@ symVersion=new String(digest.digest(), "UTF-8"); if(log.isInfoEnabled()) { - // log.info(" Initialized symmetric ciphers with secret key (" + symVersion.length() + " bytes) " +symVersion); + log.info(" Initialized symmetric ciphers with secret key (" + symVersion.length() + " bytes)"); + /* StringBuilder sb=new StringBuilder(" Initialized symmetric ciphers with secret key (" + symVersion.length() + " bytes) "); char[] arr=symVersion.toCharArray(); @@ -360,6 +372,7 @@ sb.append((int)c); } log.info(sb.toString()); + */ } } @@ -384,7 +397,11 @@ // set up the Cipher to decrypt secret key responses encrypted with our key - asymCipher=Cipher.getInstance(asymAlgorithm); + if(asymProvider != null && asymProvider.trim().length() > 0) + asymCipher=Cipher.getInstance(asymAlgorithm, asymProvider); + else + asymCipher=Cipher.getInstance(asymAlgorithm); + asymCipher.init(Cipher.DECRYPT_MODE, Kpair.getPrivate()); if(log.isInfoEnabled()) @@ -399,13 +416,6 @@ */ public Object up(Event evt) { switch(evt.getType()) { - - // we need to know what our address is - case Event.SET_LOCAL_ADDRESS: - local_addr=(Address)evt.getArg(); - if(log.isDebugEnabled()) - log.debug("set local address to " + local_addr); - break; case Event.VIEW_CHANGE: View view=(View)evt.getArg(); if(log.isInfoEnabled()) @@ -448,7 +458,8 @@ private synchronized void handleViewChange(View view, boolean makeServer) { // if view is a bit broken set me as keyserver - if(view.getMembers() == null || view.getMembers().get(0) == null) { + Vector
    members = view.getMembers(); + if (members == null || members.isEmpty() || members.get(0) == null) { becomeKeyServer(local_addr); return; } @@ -520,12 +531,12 @@ return; } - if(msg.getLength() == 0) { + if(msg.getLength() == 0 && !encrypt_entire_message) { passItUp(evt); return; } - EncryptHeader hdr=(EncryptHeader)msg.getHeader(EncryptHeader.KEY); + EncryptHeader hdr=(EncryptHeader)msg.getHeader(this.id); // try and get the encryption header if(hdr == null) { @@ -704,7 +715,7 @@ * @throws Exception */ private Message decryptMessage(Cipher cipher, Message msg) throws Exception { - EncryptHeader hdr=(EncryptHeader)msg.getHeader(EncryptHeader.KEY); + EncryptHeader hdr=(EncryptHeader)msg.getHeader(this.id); if(!hdr.getVersion().equals(getSymVersion())) { log.warn("attempting to use stored cipher as message does not uses current encryption version "); cipher=keyMap.get(hdr.getVersion()); @@ -726,13 +737,22 @@ } } - private static Message _decrypt(Cipher cipher, Message msg, boolean decrypt_entire_msg) throws Exception { + private Message _decrypt(Cipher cipher, Message msg, boolean decrypt_entire_msg) throws Exception { + byte[] decrypted_msg; + + decrypt_lock.lock(); + try { + decrypted_msg=cipher.doFinal(msg.getRawBuffer(), msg.getOffset(), msg.getLength()); + } + finally { + decrypt_lock.unlock(); + } + if(!decrypt_entire_msg) { - msg.setBuffer(cipher.doFinal(msg.getRawBuffer(), msg.getOffset(), msg.getLength())); + msg.setBuffer(decrypted_msg); return msg; } - byte[] decrypted_msg=cipher.doFinal(msg.getRawBuffer(), msg.getOffset(), msg.getLength()); Message ret=(Message)Util.streamableFromByteBuffer(Message.class, decrypted_msg); if(ret.getDest() == null) ret.setDest(msg.getDest()); @@ -754,14 +774,19 @@ IllegalBlockSizeException, BadPaddingException, NoSuchPaddingException, - NoSuchAlgorithmException { + NoSuchAlgorithmException, + NoSuchProviderException { Message newMsg; if(log.isDebugEnabled()) log.debug("encoding shared key "); // create a cipher with peer's public key - Cipher tmp=Cipher.getInstance(asymAlgorithm); + Cipher tmp; + if (asymProvider != null && asymProvider.trim().length() > 0) + tmp=Cipher.getInstance(asymAlgorithm, asymProvider); + else + tmp=Cipher.getInstance(asymAlgorithm); tmp.init(Cipher.ENCRYPT_MODE, pubKey); //encrypt current secret key @@ -774,73 +799,25 @@ newMsg=new Message(source, local_addr, encryptedKey); - newMsg.putHeader(EncryptHeader.KEY, new EncryptHeader(EncryptHeader.SECRETKEY, - getSymVersion())); + newMsg.putHeader(this.id, new EncryptHeader(EncryptHeader.SECRETKEY, getSymVersion())); if(log.isDebugEnabled()) log.debug(" Sending version " + getSymVersion() + " encoded key to client"); passItDown(new Event(Event.MSG, newMsg)); } - /** - * @param msg - * @return - */ - // private PublicKey handleKeyRequest(Message msg) - // { - // Message newMsg; - // if (log.isDebugEnabled()) - // log.debug("Request for key recieved"); - // - // //SW log the clients encoded public key so we can - // // see if they match - // if (log.isDebugEnabled()) - // log.debug("Got peer's encoded public key:" - // + formatArray(msg.getBuffer())); - // - // PublicKey pubKey = generatePubKey(msg.getBuffer()); - // - // //SW log the clients resulting public key so we can - // // see if it is created correctly - // if (log.isDebugEnabled()) - // log.debug("Generated requestors public key" + pubKey); - // - // /* - // * SW why do we send this as the client does not use it ? - although we - // * could make use to provide some authentication later on rahter than - // * just encryption send server's publicKey - // */ - // newMsg = new Message(msg.getSrc(), local_addr, Kpair.getPublic() - // .getEncoded()); - // - // //SW Log out our public key in encoded format so we - // // can match with the client debugging to - // // see if they match - // if (log.isInfoEnabled()) - // log.debug("encoded key is " - // + formatArray(Kpair.getPublic().getEncoded())); - // - // - // newMsg.putHeader(EncryptHeader.KEY, new EncryptHeader( - // EncryptHeader.SERVER_PUBKEY, getSymVersion())); - // - // - // down_prot.down(new Event(Event.MSG, newMsg)); - // return pubKey; - // } + /** * @return Message */ - private Message sendKeyRequest() { // send client's public key to server and request // server's public key Message newMsg=new Message(keyServerAddr, local_addr, Kpair.getPublic().getEncoded()); - newMsg.putHeader(EncryptHeader.KEY, new EncryptHeader(EncryptHeader.KEY_REQUEST, - getSymVersion())); + newMsg.putHeader(this.id, new EncryptHeader(EncryptHeader.KEY_REQUEST, getSymVersion())); passItDown(new Event(Event.MSG, newMsg)); return newMsg; } @@ -883,6 +860,13 @@ handleViewChange(view, false); } break; + + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + if(log.isDebugEnabled()) + log.debug("set local address to " + local_addr); + break; + case Event.TMP_VIEW: view=(View)evt.getArg(); if(log.isInfoEnabled()) @@ -928,7 +912,7 @@ } Message msg=(Message)evt.getArg(); - if(msg.getLength() == 0) { + if(msg.getLength() == 0 && !encrypt_entire_message) { passItDown(evt); return; } @@ -944,14 +928,15 @@ serialized_msg.length); Message tmp=msg.copy(false); // we need to preserve headers which may already be present tmp.setBuffer(encrypted_msg); - tmp.setSrc(local_addr); - tmp.putHeader(EncryptHeader.KEY, hdr); + if(tmp.getSrc() == null) + tmp.setSrc(local_addr); + tmp.putHeader(this.id, hdr); passItDown(new Event(Event.MSG, tmp)); return; } // put our encrypt header on the message - msg.putHeader(EncryptHeader.KEY, hdr); + msg.putHeader(this.id, hdr); // copy neeeded because same message (object) may be retransmitted -> no double encryption Message msgEncrypted=msg.copy(false); @@ -969,24 +954,32 @@ * @return * @throws Exception */ - private static byte[] encryptMessage(Cipher cipher, byte[] plain, int offset, int length) throws Exception { + private synchronized byte[] encryptMessage(Cipher cipher, byte[] plain, int offset, int length) throws Exception { return cipher.doFinal(plain, offset, length); } private SecretKeySpec decodeKey(byte[] encodedKey) throws Exception { // try and decode secrey key sent from keyserver - byte[] keyBytes=asymCipher.doFinal(encodedKey); + byte[] keyBytes; + + synchronized(this) { + keyBytes=asymCipher.doFinal(encodedKey); + } SecretKeySpec keySpec=null; try { keySpec=new SecretKeySpec(keyBytes, getAlgorithm(symAlgorithm)); // test reconstituted key to see if valid - Cipher temp=Cipher.getInstance(symAlgorithm); + Cipher temp; + if (symProvider != null && symProvider.trim().length() > 0) + temp=Cipher.getInstance(symAlgorithm, symProvider); + else + temp=Cipher.getInstance(symAlgorithm); temp.init(Cipher.SECRET_KEY, keySpec); } catch(Exception e) { - log.fatal(e); + log.fatal(e.toString()); keySpec=null; } return keySpec; @@ -1082,7 +1075,7 @@ /** * @return Returns the symProvider. */ - protected static String getSymProvider() { + protected String getSymProvider() { return symProvider; } @@ -1174,7 +1167,7 @@ this.keyServerAddr=keyServerAddr; } - public static class EncryptHeader extends org.jgroups.Header implements Streamable { + public static class EncryptHeader extends org.jgroups.Header { short type; public static final short ENCRYPT=0; public static final short KEY_REQUEST=1; @@ -1182,13 +1175,8 @@ public static final short SECRETKEY=3; public static final short SECRETKEY_READY=4; - // adding key for Message object purpose - static final String KEY="encrypt"; - String version; - boolean encrypt_entire_msg=false; - private static final long serialVersionUID=-776547091297455976L; public EncryptHeader() {} @@ -1203,15 +1191,6 @@ this.version=version; } - public void writeExternal(java.io.ObjectOutput out) throws IOException { - out.writeShort(type); - out.writeObject(version); - } - - public void readExternal(java.io.ObjectInput in) throws IOException,ClassNotFoundException { - type=in.readShort(); - version=(String)in.readObject(); - } public void writeTo(DataOutputStream out) throws IOException { out.writeShort(type); @@ -1219,9 +1198,7 @@ out.writeBoolean(encrypt_entire_msg); } - public void readFrom(DataInputStream in) throws IOException, - IllegalAccessException, - InstantiationException { + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { type=in.readShort(); version=Util.readString(in); encrypt_entire_msg=in.readBoolean(); @@ -1241,14 +1218,6 @@ return retval; } - /* - * (non-Javadoc) - * - * @see java.lang.Object#equals(java.lang.Object) - */ - public boolean equals(Object obj) { - return obj instanceof EncryptHeader && ((((EncryptHeader)obj).getType() == type) && ((((EncryptHeader)obj).getVersion().equals(version)))); - } /** * @return Returns the type. @@ -1264,4 +1233,4 @@ return version; } } -} \ No newline at end of file +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/EXAMPLE.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/EXAMPLE.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/EXAMPLE.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/EXAMPLE.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,10 +1,11 @@ -// $Id: EXAMPLE.java,v 1.8 2008/10/21 12:10:30 vlada Exp $ package org.jgroups.protocols; +import org.jgroups.Address; import org.jgroups.Event; import org.jgroups.Message; import org.jgroups.View; +import org.jgroups.annotations.MBean; import org.jgroups.annotations.Unsupported; import org.jgroups.stack.Protocol; @@ -29,15 +30,9 @@ * Example of a protocol layer. Contains no real functionality, can be used as a template. */ @Unsupported +@MBean(description="Sample protocol") public class EXAMPLE extends Protocol { - final Vector members=new Vector(); - - /** - * All protocol names have to be unique ! - */ - public String getName() { - return "EXAMPLE"; - } + final Vector
    members=new Vector
    (); /** @@ -48,12 +43,10 @@ public Object up(Event evt) { - Message msg; - switch(evt.getType()) { case Event.MSG: - msg=(Message)evt.getArg(); + Message msg=(Message)evt.getArg(); // Do something with the event, e.g. extract the message and remove a header. // Optionally pass up break; @@ -68,7 +61,7 @@ switch(evt.getType()) { case Event.TMP_VIEW: case Event.VIEW_CHANGE: - Vector new_members=((View)evt.getArg()).getMembers(); + Vector
    new_members=((View)evt.getArg()).getMembers(); synchronized(members) { members.removeAllElements(); if(new_members != null && !new_members.isEmpty()) diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/Executing.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/Executing.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/Executing.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/Executing.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,968 @@ +package org.jgroups.protocols; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.Externalizable; +import java.io.IOException; +import java.io.NotSerializableException; +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.RunnableFuture; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.jgroups.Address; +import org.jgroups.Event; +import org.jgroups.Header; +import org.jgroups.Message; +import org.jgroups.View; +import org.jgroups.annotations.MBean; +import org.jgroups.annotations.ManagedAttribute; +import org.jgroups.annotations.Property; +import org.jgroups.blocks.executor.ExecutionService.DistributedFuture; +import org.jgroups.blocks.executor.ExecutorEvent; +import org.jgroups.blocks.executor.ExecutorNotification; +import org.jgroups.stack.Protocol; +import org.jgroups.util.Streamable; +import org.jgroups.util.Util; + +/** + * This is the base protocol used for executions. + * @author wburns + * @see org.jgroups.protocols.CENTRAL_EXECUTOR + */ +@MBean(description="Based class for executor service functionality") +abstract public class Executing extends Protocol { + + @Property(description="bypasses message bundling if set") + protected boolean bypass_bundling=true; + + + protected Address local_addr; + + protected View view; + + /** + * This is a queue on the client side that holds all of the tasks that + * are awaiting a consumer to pick them up + */ + protected final Queue _awaitingConsumer = + new ConcurrentLinkedQueue(); + + /** + * This is a map on the client side showing for all of the current pending + * requests + */ + protected final ConcurrentMap _requestId = + new ConcurrentHashMap(); + + /** + * This is essentially a set on the consumer side of id's of all the threads + * currently running as consumers. This is basically a set, but since + * there is no ConcurrentHashSet we use a phoney value + */ + protected final ConcurrentMap _consumerId = + new ConcurrentHashMap(); + + protected final ConcurrentMap, ExecutorNotification> notifiers = + new ConcurrentHashMap, ExecutorNotification>(); + + /** + * This is a map on the server side that shows which owner is currently + * tied to the runnable so we can return to them the results + */ + protected final Map _running; + + /** + * This is a map on the client side that shows for which + * owner(consumer, request) the runnable they are currently using. This + * also allows us to set the values on a future when finished. + */ + protected final Map _awaitingReturn; + + /** + * This is a server side queue of all the tasks to pass off. Currently + * there will never be tasks waiting to put in. If a task is put in and doesn't have a + * respective take at the same time that task is rejected. + */ + protected BlockingQueue _tasks = new SynchronousQueue(); + + /** + * This is a server side map to show which threads are running for a + * given runnable. This is used to interrupt those threads if needed. + */ + protected final ConcurrentMap _runnableThreads = + new ConcurrentHashMap(); + + /** + * This lock is to protect the incoming run requests and the incoming + * consumer queues + */ + protected Lock _consumerLock = new ReentrantLock(); + + /** + * This is stored on the coordinator side. This queue holds all of the + * addresses that currently want to run something. If this queue has + * elements the consumer queue must be empty. + */ + protected Queue _runRequests = new ArrayDeque(); + + /** + * This is stored on the coordinator side. This queue holds all of the + * addresses that currently are able to run something. If this queue has + * elements the run request queue must be empty. + */ + protected Queue _consumersAvailable = new ArrayDeque(); + + protected static enum Type { + RUN_REQUEST, // request to coordinator from client to tell of a new task request + CONSUMER_READY, // request to coordinator from server to tell of a new consumer ready + CONSUMER_UNREADY, // request to coordinator from server to tell of a consumer stopping + CONSUMER_FOUND, // response to client from coordinator of the consumer to send the task to + RUN_SUBMITTED, // request to consumer from client the task to run + RUN_REJECTED, // response to client from the consumer due to the consumer being gone (usually because the runner was stopped) + RESULT_EXCEPTION, // response to client from the consumer when an exception was encountered + RESULT_SUCCESS, // response to client from the consumer when a value is returned + INTERRUPT_RUN, // request to consumer from client to interrupt the task + CREATE_RUN_REQUEST, // request to backups from coordinator to create a new task request. Used by CENTRAL_LOCKING + CREATE_CONSUMER_READY, // request to backups from coordinator to create a new consumer ready. Used by CENTRAL_LOCKING + DELETE_RUN_REQUEST, // request to backups from coordinator to delete a task request. Used by CENTRAL_LOCKING + DELETE_CONSUMER_READY // request to backups from coordinator to delete a consumer ready. Used by CENTRAL_LOCKING + } + + public Executing() { + _awaitingReturn = Collections.synchronizedMap(new HashMap()); + _running = Collections.synchronizedMap(new HashMap()); + } + + + public boolean getBypassBundling() { + return bypass_bundling; + } + + public void setBypassBundling(boolean bypass_bundling) { + this.bypass_bundling=bypass_bundling; + } + + public void addExecutorListener(Future future, + ExecutorNotification listener) { + if(listener != null) + notifiers.put(future, listener); + } + + @ManagedAttribute + public String getAddress() { + return local_addr != null? local_addr.toString() : null; + } + + @ManagedAttribute + public String getView() { + return view != null? view.toString() : null; + } + + + public Object down(Event evt) { + switch(evt.getType()) { + case ExecutorEvent.TASK_SUBMIT: + Runnable runnable = (Runnable)evt.getArg(); + _awaitingConsumer.add(runnable); + // We are limited to a number of concurrent request id's + // equal to 2^63-1. This is quite large and if it + // overflows it will still be positive + long requestId = Math.abs(counter.getAndIncrement()); + _requestId.put(runnable, requestId); + sendToCoordinator(Type.RUN_REQUEST, requestId, local_addr); + break; + case ExecutorEvent.CONSUMER_READY: + Thread currentThread = Thread.currentThread(); + long id = currentThread.getId(); + _consumerId.put(id, new Object()); + sendToCoordinator(Type.CONSUMER_READY, id, local_addr); + try { + // Unfortunately we can't start taking before we send + // a message, therefore we have to do a timed poll on + // _tasks below to make sure that we have time to call take + runnable = _tasks.take(); + _runnableThreads.put(runnable, currentThread); + return runnable; + } + catch (InterruptedException e) { + sendToCoordinator(Type.CONSUMER_UNREADY, id, local_addr); + Thread.currentThread().interrupt(); + } + finally { + _consumerId.remove(id); + } + break; + case ExecutorEvent.TASK_COMPLETE: + Object arg = evt.getArg(); + Throwable throwable = null; + if (arg instanceof Object[]) { + Object[] array = (Object[])arg; + runnable = (Runnable)array[0]; + throwable = (Throwable)array[1]; + } + else { + runnable = (Runnable)arg; + } + Owner owner = _running.remove(runnable); + // This won't remove anything if owner doesn't come back + _runnableThreads.remove(runnable); + + Object value = null; + boolean exception = false; + if (throwable != null) { + // InterruptedException is special telling us that + // we interrupted the thread while waiting but still got + // a task therefore we have to reject it. + if (throwable instanceof InterruptedException) { + sendRequest(owner.address, Type.RUN_REJECTED, owner.requestId, null); + break; + } + value = throwable; + exception = true; + } + else if (runnable instanceof RunnableFuture) { + RunnableFuture future = (RunnableFuture)runnable; + + boolean interrupted = false; + boolean gotValue = false; + + // We have the value, before we interrupt at least get it! + while (!gotValue) { + try { + value = future.get(); + gotValue = true; + } + catch (InterruptedException e) { + interrupted = true; + } + catch (ExecutionException e) { + value = e.getCause(); + exception = true; + gotValue = true; + } + } + + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + + if (owner != null) { + final Type type; + final Object valueToSend; + if (value == null) { + type = Type.RESULT_SUCCESS; + valueToSend = value; + } + // Both serializable values and exceptions would go in here + else if (value instanceof Serializable || + value instanceof Externalizable || + value instanceof Streamable) { + type = exception ? Type.RESULT_EXCEPTION : Type.RESULT_SUCCESS; + valueToSend = value; + } + // This would happen if the value wasn't serializable, + // so we have to send back to the client that the class + // wasn't serializable + else { + type = Type.RESULT_EXCEPTION; + valueToSend = new NotSerializableException( + value.getClass().getName()); + } + + if (local_addr.equals(owner.getAddress())) { + if(log.isTraceEnabled()) + log.trace("[redirect] <--> [" + local_addr + "] " + + type.name() + " [" + value + + (owner.requestId != -1 ? " request id: " + + owner.requestId : "") + + "]"); + final Owner finalOwner = owner; + if (type == Type.RESULT_SUCCESS) { + handleValueResponse(local_addr, + finalOwner.requestId, valueToSend); + } + else if (type == Type.RESULT_EXCEPTION){ + handleExceptionResponse(local_addr, + finalOwner.requestId, (Throwable)valueToSend); + } + } + else { + sendRequest(owner.getAddress(), type, owner.requestId, + valueToSend); + } + } + else { + if (log.isTraceEnabled()) { + log.trace("Could not return result - most likely because it was interrupted"); + } + } + break; + case ExecutorEvent.TASK_CANCEL: + Object[] array = (Object[])evt.getArg(); + runnable = (Runnable)array[0]; + + if (_awaitingConsumer.remove(runnable)) { + _requestId.remove(runnable); + if (log.isTraceEnabled()) + log.trace("Cancelled task " + runnable + " before it was picked up"); + return Boolean.TRUE; + } + // This is guaranteed to not be null so don't take cost of auto unboxing + else if (array[1] == Boolean.TRUE) { + owner = removeKeyForValue(_awaitingReturn, runnable); + if (owner != null) { + Long requestIdValue = _requestId.remove(runnable); + // We only cancel if the requestId is still available + // this means the result hasn't been returned yet and + // we still have a chance to interrupt + if (requestIdValue != null) { + if (requestIdValue != owner.getRequestId()) { + log.warn("Cancelling requestId didn't match waiting"); + } + sendRequest(owner.getAddress(), Type.INTERRUPT_RUN, + owner.getRequestId(), null); + } + } + else { + if (log.isTraceEnabled()) + log.warn("Couldn't interrupt server task: " + runnable); + } + ExecutorNotification notification = notifiers.remove(runnable); + if (notification != null) { + notification.interrupted(runnable); + } + return Boolean.TRUE; + } + else { + return Boolean.FALSE; + } + case ExecutorEvent.ALL_TASK_CANCEL: + array = (Object[])evt.getArg(); + + // This is a RunnableFuture so this cast is okay + @SuppressWarnings("unchecked") + Set runnables = (Set)array[0]; + Boolean booleanValue = (Boolean)array[1]; + + List notRan = new ArrayList(); + + for (Runnable cancelRunnable : runnables) { + // Removed from the consumer + if (!_awaitingConsumer.remove(cancelRunnable) && + booleanValue == Boolean.TRUE) { + synchronized (_awaitingReturn) { + owner = removeKeyForValue(_awaitingReturn, cancelRunnable); + if (owner != null) { + Long requestIdValue = _requestId.remove(cancelRunnable); + if (requestIdValue != owner.getRequestId()) { + log.warn("Cancelling requestId didn't match waiting"); + } + sendRequest(owner.getAddress(), Type.INTERRUPT_RUN, + owner.getRequestId(), null); + } + ExecutorNotification notification = notifiers.remove(cancelRunnable); + if (notification != null) { + log.trace("Notifying listener"); + notification.interrupted(cancelRunnable); + } + } + } + else { + _requestId.remove(cancelRunnable); + notRan.add(cancelRunnable); + } + } + return notRan; + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; + + case Event.VIEW_CHANGE: + handleView((View)evt.getArg()); + break; + } + return down_prot.down(evt); + } + + protected static V removeKeyForValue(Map map, K value) { + synchronized (map) { + Iterator> iter = + map.entrySet().iterator(); + while (iter.hasNext()) { + Entry entry = iter.next(); + if (entry.getValue().equals(value)) { + iter.remove(); + return entry.getKey(); + } + } + } + + return null; + } + + public Object up(Event evt) { + switch(evt.getType()) { + case Event.MSG: + Message msg=(Message)evt.getArg(); + ExecutorHeader hdr=(ExecutorHeader)msg.getHeader(id); + if(hdr == null) + break; + + Request req=(Request)msg.getObject(); + if(log.isTraceEnabled()) + log.trace("[" + local_addr + "] <-- [" + msg.getSrc() + "] " + req); + switch(req.type) { + case RUN_REQUEST: + handleTaskRequest(req.request, (Address)req.object); + break; + case CONSUMER_READY: + handleConsumerReadyRequest(req.request, (Address)req.object); + break; + case CONSUMER_UNREADY: + handleConsumerUnreadyRequest(req.request, (Address)req.object); + break; + case CONSUMER_FOUND: + handleConsumerFoundResponse(req.request, (Address)req.object); + break; + case RUN_SUBMITTED: + Object objectToRun = req.object; + Runnable runnable; + if (objectToRun instanceof Runnable) { + runnable = (Runnable)objectToRun; + } + else if (objectToRun instanceof Callable) { + @SuppressWarnings("unchecked") + Callable callable = (Callable)objectToRun; + runnable = new FutureTask(callable); + } + else { + log.error("Request of type " + req.type + + " sent an object of " + objectToRun + " which is invalid"); + break; + } + + handleTaskSubmittedRequest(runnable, msg.getSrc(), + req.request); + break; + case RUN_REJECTED: + // We could make requests local for this, but is it really worth it + handleTaskRejectedResponse(msg.getSrc(), req.request); + break; + case RESULT_SUCCESS: + handleValueResponse(msg.getSrc(), req.request, req.object); + break; + case RESULT_EXCEPTION: + handleExceptionResponse(msg.getSrc(), req.request, + (Throwable)req.object); + break; + case INTERRUPT_RUN: + // We could make requests local for this, but is it really worth it + handleInterruptRequest(msg.getSrc(), req.request); + break; + case CREATE_CONSUMER_READY: + Owner owner = new Owner((Address)req.object, req.request); + handleNewConsumer(owner); + break; + case CREATE_RUN_REQUEST: + owner = new Owner((Address)req.object, req.request); + handleNewRunRequest(owner); + break; + case DELETE_CONSUMER_READY: + owner = new Owner((Address)req.object, req.request); + handleRemoveConsumer(owner); + break; + case DELETE_RUN_REQUEST: + owner = new Owner((Address)req.object, req.request); + handleRemoveRunRequest(owner); + break; + default: + log.error("Request of type " + req.type + " not known"); + break; + } + return null; + + case Event.VIEW_CHANGE: + handleView((View)evt.getArg()); + break; + } + return up_prot.up(evt); + } + + protected void handleView(View view) { + this.view=view; + if(log.isDebugEnabled()) + log.debug("view=" + view); + List
    members=view.getMembers(); + + _consumerLock.lock(); + try { + // This removes the consumers that were registered that are now gone + Iterator iterator = _consumersAvailable.iterator(); + while (iterator.hasNext()) { + Owner owner = iterator.next(); + if (!members.contains(owner.getAddress())) { + iterator.remove(); + sendRemoveConsumerRequest(owner); + } + } + + // This removes the tasks that those requestors are gone + iterator = _runRequests.iterator(); + while (iterator.hasNext()) { + Owner owner = iterator.next(); + if (!members.contains(owner.getAddress())) { + iterator.remove(); + sendRemoveRunRequest(owner); + } + } + + for (Entry entry :_awaitingReturn.entrySet()) { + // The person currently servicing our request has gone down + // without completing so we have to keep our request alive by + // sending ours back to the coordinator + Owner owner = entry.getKey(); + if (!members.contains(owner.getAddress())) { + sendToCoordinator(Type.RUN_REQUEST, owner.getRequestId(), + owner.getAddress()); + Runnable runnable = entry.getValue(); + _requestId.put(runnable, owner.getRequestId()); + _awaitingConsumer.add(runnable); + } + } + } + finally { + _consumerLock.unlock(); + } + } + + abstract protected void sendToCoordinator(Type type, long requestId, Address address); + abstract protected void sendNewRunRequest(Owner source); + abstract protected void sendRemoveRunRequest(Owner source); + abstract protected void sendNewConsumerRequest(Owner source); + abstract protected void sendRemoveConsumerRequest(Owner source); + + protected void handleTaskRequest(long requestId, Address address) { + final Owner consumer; + Owner source = new Owner(address, requestId); + _consumerLock.lock(); + try { + consumer = _consumersAvailable.poll(); + // We don't add duplicate run requests - this allows for resubmission + // if it is thought the message may have been dropped + if (consumer == null && !_runRequests.contains(source)) { + _runRequests.add(source); + } + } + finally { + _consumerLock.unlock(); + } + + if (consumer != null) { + sendRequest(source.getAddress(), Type.CONSUMER_FOUND, + consumer.getRequestId(), consumer.getAddress()); + sendRemoveConsumerRequest(consumer); + } + else { + sendNewRunRequest(source); + } + } + + protected void handleConsumerReadyRequest(long requestId, Address address) { + Owner requestor; + final Owner source = new Owner(address, requestId); + _consumerLock.lock(); + try { + requestor = _runRequests.poll(); + // We don't add duplicate consumers - this allows for resubmission + // if it is thought the message may have been dropped + if (requestor == null && !_consumersAvailable.contains(source)) { + _consumersAvailable.add(source); + } + } + finally { + _consumerLock.unlock(); + } + + if (requestor != null) { + sendRequest(requestor.getAddress(), Type.CONSUMER_FOUND, + source.getRequestId(), source.getAddress()); + sendRemoveRunRequest(requestor); + } + else { + sendNewConsumerRequest(source); + } + } + + protected void handleConsumerUnreadyRequest(long requestId, Address address) { + Owner consumer = new Owner(address, requestId); + _consumersAvailable.remove(consumer); + sendRemoveConsumerRequest(consumer); + } + + protected void handleConsumerFoundResponse(long request, Address address) { + final Runnable runnable = _awaitingConsumer.poll(); + // This is a representation of the server side owner running our task. + Owner owner = new Owner(address, request); + if (runnable == null) { + // For some reason we don't have a runnable anymore + // so we have to send back to the coordinator that + // the consumer is still available. The runnable + // would be removed on a cancel + sendToCoordinator(Type.CONSUMER_READY, owner.getRequestId(), + owner.getAddress()); + } + else { + final Long requestId = _requestId.get(runnable); + owner = new Owner(address, requestId); + _awaitingReturn.put(owner, runnable); + // If local we pass along without serializing + if (local_addr.equals(owner.getAddress())) { + handleTaskSubmittedRequest(runnable, local_addr, requestId); + } + else { + if (runnable instanceof DistributedFuture) { + Callable callable = ((DistributedFuture)runnable).getCallable(); + sendRequest(owner.getAddress(), Type.RUN_SUBMITTED, + requestId, callable); + } + else { + sendRequest(owner.getAddress(), Type.RUN_SUBMITTED, + requestId, runnable); + } + } + } + } + + protected void handleTaskSubmittedRequest(Runnable runnable, Address source, + long requestId) { + // We store in our map so that when that task is + // finished so that we can send back to the owner + // with the results + _running.put(runnable, new Owner(source, requestId)); + // We give the task to the thread that is now waiting for it to be returned + // If we can't offer then we have to respond back to + // caller that we can't handle it. They must have + // gotten our address when we had a consumer, but + // they went away between then and now. + boolean received = false; + try { + /** + * We offer it a while before rejecting it. This is required + * in case if the _tasks.take() call isn't registered quick + * enough after sending the Type.CONSUMER_READY message + */ + received = _tasks.offer(runnable, 1000, TimeUnit.SECONDS); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + System.out.println("Interrupted while handing off"); + } + + if (!received) { + // If we couldn't hand off the task we have to tell the client + // and also reupdate the coordinator that our consumer is ready + sendRequest(source, Type.RUN_REJECTED, requestId, null); + _running.remove(runnable); + } + } + + protected void handleTaskRejectedResponse(Address source, long requestId) { + Runnable runnable = _awaitingReturn.remove(new Owner( + source, requestId)); + if (runnable != null) { + _awaitingConsumer.add(runnable); + Long taskRequestId = _requestId.get(runnable); + if (taskRequestId != requestId) { + log.warn("Task Request Id doesn't match in rejection"); + } + sendToCoordinator(Type.RUN_REQUEST, taskRequestId, local_addr); + } + else { + log.error("error resubmitting task for request-id: " + requestId); + } + } + + protected void handleValueResponse(Address source, long requestId, Object value) { + Runnable runnable = _awaitingReturn.remove( + new Owner(source, requestId)); + + if (runnable != null) { + _requestId.remove(runnable); + } + // We can only notify of success if it was a future + if (runnable instanceof RunnableFuture) { + RunnableFuture future = (RunnableFuture)runnable; + ExecutorNotification notifier = notifiers.remove(future); + if (notifier != null) { + notifier.resultReturned(value); + } + } + else { + log.warn("Runnable was not found in awaiting"); + } + } + + protected void handleExceptionResponse(Address source, long requestId, Throwable throwable) { + Runnable runnable = _awaitingReturn.remove( + new Owner(source, requestId)); + + if (runnable != null) { + _requestId.remove(runnable); + } + // We can only notify of exception if it was a future + if (runnable instanceof RunnableFuture) { + RunnableFuture future = (RunnableFuture)runnable; + ExecutorNotification notifier = notifiers.remove(future); + if (notifier != null) { + notifier.throwableEncountered(throwable); + } + } + else { + // All we can do is log the error since their is no + // way to return this to the user since they don't + // have a future object. + log.error("Runtime Error encountered from " + + "Cluster execute(Runnable) method", + throwable); + } + } + + protected void handleInterruptRequest(Address source, long requestId) { + Owner owner = new Owner(source, requestId); + Runnable runnable = removeKeyForValue(_running, owner); + if (runnable != null) { + Thread thread = _runnableThreads.remove(runnable); + thread.interrupt(); + } + else { + if (log.isTraceEnabled()) + log.trace("Message could not be interrupted due to it already returned"); + } + } + + protected void handleNewRunRequest(Owner sender) { + _consumerLock.lock(); + try { + if (!_runRequests.contains(sender)) { + _runRequests.add(sender); + } + } + finally { + _consumerLock.unlock(); + } + } + + protected void handleRemoveRunRequest(Owner sender) { + _consumerLock.lock(); + try { + _runRequests.remove(sender); + } + finally { + _consumerLock.unlock(); + } + } + + protected void handleNewConsumer(Owner sender) { + _consumerLock.lock(); + try { + if (!_consumersAvailable.contains(sender)) { + _consumersAvailable.add(sender); + } + } + finally { + _consumerLock.unlock(); + } + } + + protected void handleRemoveConsumer(Owner sender) { + _consumerLock.lock(); + try { + _consumersAvailable.remove(sender); + } + finally { + _consumerLock.unlock(); + } + } + + protected void sendRequest(Address dest, Type type, long requestId, Object object) { + Request req=new Request(type, object, requestId); + Message msg=new Message(dest, null, req); + msg.putHeader(id, new ExecutorHeader()); + if(bypass_bundling) + msg.setFlag(Message.DONT_BUNDLE); + if(log.isTraceEnabled()) + log.trace("[" + local_addr + "] --> [" + (dest == null? "ALL" : dest) + "] " + req); + try { + down_prot.down(new Event(Event.MSG, msg)); + } + catch(Exception ex) { + log.error("failed sending " + type + " request: " + ex); + } + } + + /** + * This keeps track of all the requests we send. This is used so that + * the response doesn't have to send back the future but instead the counter + * We just let this roll over + */ + protected static final AtomicLong counter = new AtomicLong(); + + protected static class Request implements Streamable { + protected Type type; + protected Object object; + protected long request; + + public Request() { + } + + public Request(Type type, Object object, long request) { + this.type=type; + this.object=object; + this.request=request; + } + + public void writeTo(DataOutputStream out) throws IOException { + out.writeByte(type.ordinal()); + // We can't use Util.writeObject since it's size is limited to 2^15-1 + try { + if (object instanceof Streamable) { + out.writeShort(-1); + Util.writeGenericStreamable((Streamable)object, out); + } + else { + byte[] bytes = Util.objectToByteBuffer(object); + out.writeInt(bytes.length); + out.write(bytes); + } + } + catch (IOException e) { + throw e; + } + catch (Exception e) { + throw new IOException("Exception encountered while serializing execution request", e); + } + out.writeLong(request); + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + type=Type.values()[in.readByte()]; + // We can't use Util.readObject since it's size is limited to 2^15-1 + try { + short first = in.readShort(); + if (first == -1) { + object = Util.readGenericStreamable(in); + } + else { + ByteBuffer bb = ByteBuffer.allocate(4); + bb.putShort(first); + bb.putShort(in.readShort()); + + int size = bb.getInt(0); + byte[] bytes = new byte[size]; + in.readFully(bytes, 0, size); + object = Util.objectFromByteBuffer(bytes); + } + } + catch (IOException e) { + throw e; + } + catch (Exception e) { + throw new IOException("Exception encountered while serializing execution request", e); + } + request=in.readLong(); + } + + public String toString() { + return type.name() + " [" + object + (request != -1 ? " request id: " + request : "") + "]"; + } + } + + + public static class ExecutorHeader extends Header { + + public ExecutorHeader() { + } + + public int size() { + return 0; + } + + public void writeTo(DataOutputStream out) throws IOException { + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + } + } + + public static class Owner { + protected final Address address; + protected final long requestId; + + public Owner(Address address, long requestId) { + this.address=address; + this.requestId=requestId; + } + + public Address getAddress() { + return address; + } + + public long getRequestId() { + return requestId; + } + + // @see java.lang.Object#hashCode() + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((address == null) ? 0 : address.hashCode()); + result = prime * result + (int) (requestId ^ (requestId >>> 32)); + return result; + } + + // @see java.lang.Object#equals(java.lang.Object) + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + Owner other = (Owner) obj; + if (address == null) { + if (other.address != null) return false; + } + else if (!address.equals(other.address)) return false; + if (requestId != other.requestId) return false; + return true; + } + + public String toString() { + return address + "::" + requestId; + } + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FcHeader.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FcHeader.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FcHeader.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FcHeader.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,50 @@ +package org.jgroups.protocols; + +import org.jgroups.Global; +import org.jgroups.Header; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Header used by various flow control protocols + * @author Bela Ban + */ +public class FcHeader extends Header { + public static final byte REPLENISH=1; + public static final byte CREDIT_REQUEST=2; // the sender of the message is the requester + + byte type=REPLENISH; + + public FcHeader() { + + } + + public FcHeader(byte type) { + this.type=type; + } + + public int size() { + return Global.BYTE_SIZE; + } + + public void writeTo(DataOutputStream out) throws IOException { + out.writeByte(type); + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + type=in.readByte(); + } + + public String toString() { + switch(type) { + case REPLENISH: + return "REPLENISH"; + case CREDIT_REQUEST: + return "CREDIT_REQUEST"; + default: + return ""; + } + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FC.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FC.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FC.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FC.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,19 +1,16 @@ package org.jgroups.protocols; -import org.jgroups.*; -import org.jgroups.annotations.GuardedBy; -import org.jgroups.annotations.MBean; -import org.jgroups.annotations.ManagedAttribute; -import org.jgroups.annotations.ManagedOperation; -import org.jgroups.annotations.Property; +import org.jgroups.Address; +import org.jgroups.Event; +import org.jgroups.Message; +import org.jgroups.View; +import org.jgroups.annotations.*; import org.jgroups.stack.Protocol; import org.jgroups.util.BoundedList; -import org.jgroups.util.Streamable; import org.jgroups.util.Util; -import java.io.*; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; @@ -38,33 +35,26 @@ *
  25. Receivers don't send the full credits (max_credits), but rather tha actual number of bytes received *
      * @author Bela Ban - * @version $Id: FC.java,v 1.101 2008/10/10 13:46:12 vlada Exp $ */ @MBean(description="Simple flow control protocol based on a credit system") public class FC extends Protocol { private final static FcHeader REPLENISH_HDR=new FcHeader(FcHeader.REPLENISH); private final static FcHeader CREDIT_REQUEST_HDR=new FcHeader(FcHeader.CREDIT_REQUEST); - private final static String name="FC"; - + /* ----------------------------------------- Properties -------------------------------------------------- */ /** - * Max number of bytes to send per receiver until an ack must - * be received before continuing sending + * Max number of bytes to send per receiver until an ack must be received before continuing sending */ - @ManagedAttribute(description="Max number of bytes to send per receiver until an ack must " + - "be received before continuing sending",writable=true) - @Property(description="Max number of bytes to send per receiver until an ack must be received to proceed. Default is 500000 bytes") + @Property(description="Max number of bytes to send per receiver until an ack must be received to proceed. Default is 500000 bytes") private long max_credits=500000; /** * Max time (in milliseconds) to block. If credit hasn't been received after max_block_time, we send - * a REPLENISHMENT request to the members from which we expect credits. A value <= 0 means to - * wait forever. + * a REPLENISHMENT request to the members from which we expect credits. A value <= 0 means to wait forever. */ - @ManagedAttribute(description="Max time (in milliseconds) to block",writable=true) @Property(description="Max time (in milliseconds) to block. Default is 5000 msec") private long max_block_time=5000; @@ -84,19 +74,20 @@ /** - * If credits fall below this limit, we send more credits to the sender. (We also send when - * credits are exhausted (0 credits left)) - */ - @ManagedAttribute(description="If credits fall below this limit, we send more credits to the sender",writable=true) - @Property(description="If credits fall below this limit, we send more credits to the sender. Default is 0.25") - private double min_threshold=0.25; + * If we've received (min_threshold * max_credits) bytes from P, we send more credits to P. Example: if + * max_credits is 1'000'000, and min_threshold 0.25, then we send ca. 250'000 credits to P once we've + * received 250'000 bytes from P. + */ + @Property(description="The threshold (as a percentage of max_credits) at which a receiver sends more credits to " + + "a sender. Example: if max_credits is 1'000'000, and min_threshold 0.25, then we send ca. 250'000 credits " + + "to P once we've received 250'000 bytes from P") + private double min_threshold=0.60; /** * Computed as max_credits times min_theshold. If explicitly set, this will * override the above computation */ - @ManagedAttribute(description="Computed as max_credits x min_theshold",writable=true) - @Property(description="Computed as max_credits x min_theshold unless explicitely set") + @Property(description="Computed as max_credits x min_theshold unless explicitly set") private long min_credits=0; /** @@ -105,8 +96,9 @@ * Set to false by default in 2.5 because we have OOB messages for credit replenishments - this flag should not be set * to true if the concurrent stack is used */ - @Property - private boolean ignore_synchronous_response=false; + @Property(description="Does not block a down message if it is a result of handling an up message in the" + + "same thread. Fixes JGRP-928") + private boolean ignore_synchronous_response=true; @@ -131,29 +123,25 @@ * number of credits is decremented by the message size. A HashMap rather than a ConcurrentHashMap is * currently used as there might be null values */ - @GuardedBy("sent_lock") - private final Map sent=new HashMap(11); + @GuardedBy("lock") + private final ConcurrentMap sent=Util.createConcurrentMap(); /** - * Map: keys are members, values are credits left (in bytes). + * Keeps track of credits / member at the receiver's side. Keys are members, values are credits left (in bytes). * For each receive, the credits for the sender are decremented by the size of the received message. - * When the credits are 0, we refill and send a CREDIT message to the sender. Sender blocks until CREDIT - * is received after reaching min_credits credits. + * When the credits fall below the threshold, we refill and send a REPLENISH message to the sender. + * The sender blocks until REPLENISH message is received. */ - @GuardedBy("received_lock") - private final Map received=new ConcurrentHashMap(11); + private final ConcurrentMap received=Util.createConcurrentMap(); /** * List of members from whom we expect credits */ - @GuardedBy("sent_lock") + @GuardedBy("lock") private final Set
      creditors=new HashSet
      (11); - /** Peers who have asked for credit that we didn't have */ - private final Set
      pending_requesters=new HashSet
      (11); - /** * Whether FC is still running, this is set to false when the protocol terminates (on stop()) */ @@ -166,35 +154,32 @@ /** * the lowest credits of any destination (sent_msgs) */ - @GuardedBy("sent_lock") + @GuardedBy("lock") + @ManagedAttribute(writable=false) private long lowest_credit=max_credits; /** Lock protecting sent credits table and some other vars (creditors for example) */ - private final Lock sent_lock=new ReentrantLock(); - - /** Lock protecting received credits table */ - private final Lock received_lock=new ReentrantLock(); + private final Lock lock=new ReentrantLock(); /** Mutex to block on down() */ - private final Condition credits_available=sent_lock.newCondition(); + private final Condition credits_available=lock.newCondition(); /** * Thread that carries messages through up() and shouldn't be blocked * in down() if ignore_synchronous_response==true. JGRP-465. */ - private Thread ignore_thread; + private final ThreadLocal ignore_thread=new ThreadLocal() { + protected Boolean initialValue() { + return false; + } + }; /** Last time a credit request was sent. Used to prevent credit request storms */ - @GuardedBy("sent_lock") + @GuardedBy("lock") private long last_credit_request=0; - - public final String getName() { - return name; - } - public void resetStats() { super.resetStats(); num_blockings=0; @@ -240,8 +225,7 @@ max_block_time=t; } - @ManagedAttribute(writable=true) - @Property(description="Max times to block for the listed messages sizes (Message.getLength())") + @Property(description="Max times to block for the listed messages sizes (Message.getLength()). Example: \"1000:10,5000:30,10000:500\"") public void setMaxBlockTimes(String str) { if(str == null) return; Long prev_key=null, prev_val=null; @@ -276,7 +260,6 @@ log.debug("max_block_times: " + max_block_times); } - @ManagedAttribute public String getMaxBlockTimes() { if(max_block_times == null) return "n/a"; StringBuilder sb=new StringBuilder(); @@ -340,10 +323,15 @@ return sb.toString(); } + @ManagedOperation(description="Prints the creditors") + public String printCreditors() { + return creditors.toString(); + } + public Map dumpStats() { Map retval=super.dumpStats(); retval.put("senders", printMap(sent)); - retval.put("receivers", printMap(received)); + retval.put("receivers", printMap(received)); return retval; } @@ -371,21 +359,20 @@ */ @ManagedOperation(description="Unblock a sender") public void unblock() { - sent_lock.lock(); + lock.lock(); try { if(log.isTraceEnabled()) log.trace("unblocking the sender and replenishing all members, creditors are " + creditors); - for(Map.Entry entry: sent.entrySet()) { - entry.setValue(max_credits); - } + for(Map.Entry entry: sent.entrySet()) + entry.getValue().set(max_credits); lowest_credit=computeLowestCredit(sent); creditors.clear(); credits_available.signalAll(); } finally { - sent_lock.unlock(); + lock.unlock(); } } @@ -393,8 +380,7 @@ boolean min_credits_set = min_credits != 0; if(!min_credits_set) min_credits=(long)(max_credits * min_threshold); - - Util.checkBufferSize("FC.max_credits", max_credits); + lowest_credit=max_credits; } public void start() throws Exception { @@ -404,34 +390,41 @@ "a fragmentation protocol, due to http://jira.jboss.com/jira/browse/JGRP-590"); } - sent_lock.lock(); + lock.lock(); try { running=true; lowest_credit=max_credits; } finally { - sent_lock.unlock(); + lock.unlock(); } } public void stop() { super.stop(); - sent_lock.lock(); + lock.lock(); try { running=false; - ignore_thread=null; + ignore_thread.set(false); credits_available.signalAll(); // notify all threads waiting on the mutex that we are done } finally { - sent_lock.unlock(); + lock.unlock(); } } + @SuppressWarnings("unchecked") public Object down(Event evt) { switch(evt.getType()) { case Event.MSG: - return handleDownMessage(evt); + Message msg=(Message)evt.getArg(); + if(msg.isFlagSet(Message.NO_FC)) + break; + int length=msg.getLength(); + if(length == 0) + break; + return handleDownMessage(evt, msg, length); case Event.CONFIG: handleConfigEvent((Map)evt.getArg()); break; @@ -443,6 +436,7 @@ } + @SuppressWarnings("unchecked") public Object up(Event evt) { switch(evt.getType()) { @@ -450,11 +444,10 @@ // JGRP-465. We only deal with msgs to avoid having to use a concurrent collection; ignore views, // suspicions, etc which can come up on unusual threads. - if(ignore_thread == null && ignore_synchronous_response) - ignore_thread=Thread.currentThread(); - Message msg=(Message)evt.getArg(); - FcHeader hdr=(FcHeader)msg.getHeader(name); + if(msg.isFlagSet(Message.NO_FC)) + break; + FcHeader hdr=(FcHeader)msg.getHeader(this.id); if(hdr != null) { switch(hdr.type) { case FcHeader.REPLENISH: @@ -465,7 +458,8 @@ num_credit_requests_received++; Address sender=msg.getSrc(); Long sent_credits=(Long)msg.getObject(); - handleCreditRequest(received, received_lock, sender, sent_credits); + if(sent_credits != null) + handleCreditRequest(received, sender, sent_credits.longValue()); break; default: log.error("header type " + hdr.type + " not known"); @@ -473,17 +467,23 @@ } return null; // don't pass message up } - else { - Address sender=msg.getSrc(); - long new_credits=adjustCredit(received, received_lock, sender, msg.getLength()); - try { - return up_prot.up(evt); - } - finally { - if(new_credits > 0) { - if(log.isTraceEnabled()) log.trace("sending " + new_credits + " credits to " + sender); - sendCredit(sender, new_credits); - } + + Address sender=msg.getSrc(); + long new_credits=adjustCredit(received, sender, msg.getLength()); + + // JGRP-928: changed ignore_thread to a ThreadLocal: multiple threads can access it with the + // introduction of the concurrent stack + if(ignore_synchronous_response) + ignore_thread.set(true); + try { + return up_prot.up(evt); + } + finally { + if(ignore_synchronous_response) + ignore_thread.set(false); // need to revert because the thread is placed back into the pool + if(new_credits > 0) { + if(log.isTraceEnabled()) log.trace("sending " + new_credits + " credits to " + sender); + sendCredit(sender, new_credits); } } @@ -515,9 +515,7 @@ } } - private Object handleDownMessage(Event evt) { - Message msg=(Message)evt.getArg(); - int length=msg.getLength(); + private Object handleDownMessage(final Event evt, final Message msg, int length) { Address dest=msg.getDest(); if(max_block_times != null) { @@ -526,10 +524,10 @@ end_time.set(System.currentTimeMillis() + tmp); } - sent_lock.lock(); + lock.lock(); try { if(length > lowest_credit) { // then block and loop asking for credits until enough credits are available - if(ignore_synchronous_response && ignore_thread == Thread.currentThread()) { // JGRP-465 + if(ignore_synchronous_response && ignore_thread.get()) { // JGRP-465 if(log.isTraceEnabled()) log.trace("bypassing blocking to avoid deadlocking " + Thread.currentThread()); } @@ -538,7 +536,7 @@ long start_blocking=System.currentTimeMillis(); num_blockings++; // we count overall blockings, not blockings for *all* threads if(log.isTraceEnabled()) - log.trace("Starting blocking. lowest_credit=" + lowest_credit + "; msg length =" + length); + log.trace("Blocking (lowest_credit=" + lowest_credit + "; length=" + length + ")"); while(length > lowest_credit && running) { try { @@ -546,7 +544,7 @@ if(max_block_times != null) { Long tmp=end_time.get(); if(tmp != null) { - // Negative value means we don't wait at all ! If the end_time already elapsed + // A negative block_time means we don't wait at all ! If the end_time already elapsed // (because we waited for other threads to get processed), the message will not // block at all and get sent immediately block_time=tmp - start_blocking; @@ -554,7 +552,7 @@ } boolean rc=credits_available.await(block_time, TimeUnit.MILLISECONDS); - if(rc || length <= lowest_credit || !running) + if(length <= lowest_credit || rc || !running) break; // if we use max_block_times, then we do *not* send credit requests, even if we run @@ -570,33 +568,26 @@ // a credit request storm last_credit_request=System.currentTimeMillis(); - // we need to send the credit requests down *without* holding the sent_lock, otherwise we might + // we need to send the credit requests down *without* holding the lock, otherwise we might // run into the deadlock described in http://jira.jboss.com/jira/browse/JGRP-292 - Map sent_copy=new HashMap(sent); + Map sent_copy=new HashMap(sent); sent_copy.keySet().retainAll(creditors); - sent_lock.unlock(); + lock.unlock(); try { - // System.out.println(new Date() + " --> credit request"); - for(Map.Entry entry: sent_copy.entrySet()) { - sendCreditRequest(entry.getKey(), entry.getValue()); - } + for(Map.Entry entry: sent_copy.entrySet()) + sendCreditRequest(entry.getKey(), entry.getValue().get()); } finally { - sent_lock.lock(); + lock.lock(); } } } catch(InterruptedException e) { - // set the interrupted flag again, so the caller's thread can handle the interrupt as well - - // bela June 15 2007: don't do this as this will trigger an infinite loop !! + // bela June 15 2007: don't interrupt the thread again, as this will trigger an infinite loop !! // (http://jira.jboss.com/jira/browse/JGRP-536) // Thread.currentThread().interrupt(); } } - // if(!running) // don't send the message if not running anymore - // return null; - long block_time=System.currentTimeMillis() - start_blocking; if(log.isTraceEnabled()) log.trace("total time blocked: " + block_time + " ms"); @@ -610,7 +601,7 @@ lowest_credit=Math.min(tmp, lowest_credit); } finally { - sent_lock.unlock(); + lock.unlock(); } // send message - either after regular processing, or after blocking (when enough credits available again) @@ -619,25 +610,21 @@ /** * Checks whether one member (unicast msg) or all members (multicast msg) have enough credits. Add those - * that don't to the creditors list. Called with sent_lock held + * that don't to the creditors list. Called with lock held * @param dest * @param length */ private void determineCreditors(Address dest, int length) { boolean multicast=dest == null || dest.isMulticastAddress(); - Address mbr; - Long credits; if(multicast) { - for(Map.Entry entry: sent.entrySet()) { - mbr=entry.getKey(); - credits=entry.getValue(); - if(credits <= length) - creditors.add(mbr); + for(Map.Entry entry: sent.entrySet()) { + if(entry.getValue().get() <= length) + creditors.add(entry.getKey()); } } else { - credits=sent.get(dest); - if(credits != null && credits <= length) + Credit cred=sent.get(dest); + if(cred != null && cred.get() <= length) creditors.add(dest); } } @@ -650,34 +637,22 @@ * @param credits * @return The lowest number of credits left, or -1 if a unicast member was not found */ - private long decrementCredit(Map m, Address dest, long credits) { + private long decrementCredit(Map map, Address dest, long credits) { boolean multicast=dest == null || dest.isMulticastAddress(); - long lowest=max_credits, new_credit; - Long val; + long lowest=max_credits; if(multicast) { - if(m.isEmpty()) + if(map.isEmpty()) return -1; - for(Map.Entry entry: m.entrySet()) { - val=entry.getValue(); - new_credit=val - credits; - entry.setValue(new_credit); - lowest=Math.min(new_credit, lowest); - } + for(Credit cred: map.values()) + lowest=Math.min(cred.decrement(credits), lowest); return lowest; } else { - val=m.get(dest); - if(val != null) { - lowest=val; - lowest-=credits; - m.put(dest, lowest); - if(log.isTraceEnabled()) - log.trace("sender " + dest + " minus " + credits - + " credits, " + lowest + " remaining"); - return lowest; + Credit cred=map.get(dest); + if(cred != null) + return lowest=cred.decrement(credits); } - } return -1; } @@ -686,140 +661,79 @@ if(sender == null) return; StringBuilder sb=null; - sent_lock.lock(); + lock.lock(); try { - Long old_credit=sent.get(sender); - if(old_credit == null) + Credit cred=sent.get(sender); + if(cred == null) return; - Long new_credit=Math.min(max_credits, old_credit + increase.longValue()); + long new_credit=Math.min(max_credits, cred.get() + increase.longValue()); if(log.isTraceEnabled()) { sb=new StringBuilder(); - sb.append("received credit from ").append(sender).append(", old credit was ").append(old_credit) + sb.append("received credit from ").append(sender).append(", old credit was ").append(cred) .append(", new credits are ").append(new_credit).append(".\nCreditors before are: ").append(creditors); } - sent.put(sender, new_credit); + cred.increment(increase.longValue()); + lowest_credit=computeLowestCredit(sent); - // boolean was_empty=true; - if(!creditors.isEmpty()) { // we are blocked because we expect credit from one or more members - // was_empty=false; - creditors.remove(sender); - if(log.isTraceEnabled()) { - sb.append("\nCreditors after removal of ").append(sender).append(" are: ").append(creditors); - log.trace(sb); - } - } - if(creditors.isEmpty()) {// && !was_empty) { + if(!creditors.isEmpty() && creditors.remove(sender) && creditors.isEmpty()) credits_available.signalAll(); } - } finally { - sent_lock.unlock(); + lock.unlock(); } } - private static long computeLowestCredit(Map m) { - Collection credits=m.values(); // List of Longs (credits) - return Collections.min(credits); + private static long computeLowestCredit(Map m) { + Collection credits=m.values(); + return Collections.min(credits).get(); } /** - * Check whether sender has enough credits left. If not, send him some more + * Check whether sender has enough credits left. If not, send it some more * @param map The hashmap to use - * @param lock The lock which can be used to lock map * @param sender The address of the sender * @param length The number of bytes received by this message. We don't care about the size of the headers for * the purpose of flow control * @return long Number of credits to be sent. Greater than 0 if credits needs to be sent, 0 otherwise */ - private long adjustCredit(Map map, final Lock lock, Address sender, int length) { - if(sender == null) { - if(log.isErrorEnabled()) log.error("src is null"); + private long adjustCredit(Map map, Address sender, int length) { + if(sender == null || length == 0) return 0; - } - if(length == 0) - return 0; // no effect + Credit cred=map.get(sender); + if(cred == null) + return 0; - lock.lock(); - try { - long remaining_cred=decrementCredit(map, sender, length); - if(log.isTraceEnabled()) - log.trace("sender " + sender + " minus " + length - + " credits, " + remaining_cred + " remaining"); - if(remaining_cred == -1) - return 0; - long credit_response=max_credits - remaining_cred; - if(credit_response >= min_credits) { - map.put(sender, max_credits); - return credit_response; // this will trigger sending of new credits as we have received more than min_credits bytes from src - } - } - finally { - lock.unlock(); - } - return 0; + if(log.isTraceEnabled()) + log.trace("sender " + sender + " minus " + length + " credits, " + (cred.get() - length) + " remaining"); + + return cred.decrementAndGet(length); } /** * @param map The map to modify - * @param lock The lock to lock map * @param sender The sender who requests credits * @param left_credits Number of bytes that the sender has left to send messages to us */ - private void handleCreditRequest(Map map, Lock lock, Address sender, Long left_credits) { + private void handleCreditRequest(Map map, Address sender, long left_credits) { if(sender == null) return; - long credit_response=0; + Credit cred=map.get(sender); + if(cred == null) return; + long credit_response=Math.min(max_credits - left_credits, max_credits); - lock.lock(); - try { - Long old_credit=map.get(sender); - if(old_credit != null) { - credit_response=Math.min(max_credits, max_credits - old_credit); - } - - if(credit_response > 0) { - if(log.isTraceEnabled()) - log.trace("received credit request from " + sender + ": sending " + credit_response + " credits"); - map.put(sender, max_credits); - pending_requesters.remove(sender); - } - else { - if(pending_requesters.contains(sender)) { - // a sender might have negative credits, e.g. -20000. If we subtracted -20000 from max_credits, - // we'd end up with max_credits + 20000, and send too many credits back. So if the sender's - // credits is negative, we simply send max_credits back - long credits_left=Math.max(0, left_credits.longValue()); - credit_response=max_credits - credits_left; - // credit_response = max_credits; - map.put(sender, max_credits); - pending_requesters.remove(sender); - if(log.isWarnEnabled()) - log.warn("Received two credit requests from " + sender + - " without any intervening messages; sending " + credit_response + " credits"); - } - else { - pending_requesters.add(sender); - if(log.isTraceEnabled()) - log.trace("received credit request from " + sender + " but have no credits available"); - } - } - } - finally { - lock.unlock(); - } - - if(credit_response > 0) - sendCredit(sender, credit_response); + if(log.isTraceEnabled()) + log.trace("received credit request from " + sender + ": sending " + credit_response + " credits"); + cred.set(max_credits); + sendCredit(sender, credit_response); } private void sendCredit(Address dest, long credit) { if(log.isTraceEnabled()) - log.trace("replentished " + dest + " with " + credit - + " credits"); + log.trace("replenishing " + dest + " with " + credit + " credits"); Number number; if(credit < Integer.MAX_VALUE) number=(int)credit; @@ -827,7 +741,7 @@ number=credit; Message msg=new Message(dest, null, number); msg.setFlag(Message.OOB); - msg.putHeader(name, REPLENISH_HDR); + msg.putHeader(this.id, REPLENISH_HDR); down_prot.down(new Event(Event.MSG, msg)); num_credit_responses_sent++; } @@ -842,7 +756,7 @@ if(log.isTraceEnabled()) log.trace("sending credit request to " + dest); Message msg=new Message(dest, null, credits_left); - msg.putHeader(name, CREDIT_REQUEST_HDR); + msg.putHeader(this.id, CREDIT_REQUEST_HDR); down_prot.down(new Event(Event.MSG, msg)); num_credit_requests_sent++; } @@ -853,16 +767,15 @@ if(mbrs == null) return; if(log.isTraceEnabled()) log.trace("new membership: " + mbrs); - sent_lock.lock(); - received_lock.lock(); + lock.lock(); try { // add members not in membership to received and sent hashmap (with full credits) for(int i=0; i < mbrs.size(); i++) { addr=mbrs.elementAt(i); if(!received.containsKey(addr)) - received.put(addr, max_credits); + received.put(addr, new Credit(max_credits)); if(!sent.containsKey(addr)) - sent.put(addr, max_credits); + sent.put(addr, new Credit(max_credits)); } // remove members that left for(Iterator
      it=received.keySet().iterator(); it.hasNext();) { @@ -878,11 +791,6 @@ it.remove(); // modified the underlying map } - // remove all creditors which are not in the new view - /*for(Address creditor: creditors) { - if(!mbrs.contains(creditor)) - creditors.remove(creditor); - }*/ // fixed http://jira.jboss.com/jira/browse/JGRP-754 (CCME) for(Iterator
      it=creditors.iterator(); it.hasNext();) { Address creditor=it.next(); @@ -897,66 +805,61 @@ } } finally { - sent_lock.unlock(); - received_lock.unlock(); + lock.unlock(); } } - private static String printMap(Map m) { + private static String printMap(Map m) { StringBuilder sb=new StringBuilder(); - for(Map.Entry entry: m.entrySet()) { + for(Map.Entry entry: m.entrySet()) { sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); } return sb.toString(); } - public static class FcHeader extends Header implements Streamable { - public static final byte REPLENISH=1; - public static final byte CREDIT_REQUEST=2; // the sender of the message is the requester - byte type=REPLENISH; - private static final long serialVersionUID=8226510881574318828L; - - public FcHeader() { + private class Credit implements Comparable { + private long credits_left; + private Credit(long credits) { + this.credits_left=credits; } - public FcHeader(byte type) { - this.type=type; + private synchronized long decrementAndGet(long credits) { + credits_left=Math.max(0, credits_left - credits); + long credit_response=max_credits - credits_left; + if(credit_response >= min_credits) { + credits_left=max_credits; + return credit_response; + } + return 0; } - public int size() { - return Global.BYTE_SIZE; + private synchronized long decrement(long credits) { + return credits_left=Math.max(0, credits_left - credits); } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeByte(type); - } + private synchronized long get() {return credits_left;} - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - type=in.readByte(); - } + private synchronized void set(long new_credits) {credits_left=Math.min(max_credits, new_credits);} - public void writeTo(DataOutputStream out) throws IOException { - out.writeByte(type); + private synchronized long increment(long credits) { + return credits_left=Math.min(max_credits, credits_left + credits); } - public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { - type=in.readByte(); + public String toString() { + return String.valueOf(credits_left); } - public String toString() { - switch(type) { - case REPLENISH: - return "REPLENISH"; - case CREDIT_REQUEST: - return "CREDIT_REQUEST"; - default: - return ""; - } + public int compareTo(Object o) { + Credit other=(Credit)o; + return credits_left < other.credits_left ? -1 : credits_left > other.credits_left ? 1 : 0; } } + + + } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FD_ALL.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FD_ALL.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FD_ALL.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FD_ALL.java 2011-10-18 11:22:35.000000000 +0000 @@ -4,55 +4,44 @@ import org.jgroups.annotations.*; import org.jgroups.stack.Protocol; import org.jgroups.util.BoundedList; -import org.jgroups.util.Streamable; import org.jgroups.util.TimeScheduler; import org.jgroups.util.Util; -import java.io.*; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; import java.util.*; import java.util.Map.Entry; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** - * Failure detection based on simple heartbeat protocol. Every member - * periodically multicasts a heartbeat. Every member also maintains a table of - * all members (minus itself). When data or a heartbeat from P are received, we - * reset the timestamp for P to the current time. Periodically, we check for - * expired members, and suspect those. + * Failure detection based on simple heartbeat protocol. Every member periodically multicasts a heartbeat. + * Every member also maintains a table of all members (minus itself). When data or a heartbeat from P is received, + * we reset the timestamp for P to the current time. Periodically, we check for expired members, and suspect those.

      + * Reduced number of messages exchanged on suspect event: https://jira.jboss.org/browse/JGRP-1241 * * @author Bela Ban - * @version $Id: FD_ALL.java,v 1.25 2008/10/22 10:29:15 belaban Exp $ */ @MBean(description="Failure detection based on simple heartbeat protocol") +@DeprecatedProperty(names={"shun"}) public class FD_ALL extends Protocol { - private final static String name="FD_ALL"; - /* ----------------------------------------- Properties -------------------------------------------------- */ - @Property(description="Interval in which a HEARTBEAT is sent to the cluster. Default is 3000 msec") - @ManagedAttribute(description="Interval in which a HEARTBEAT is sent to the cluster. Default is 3000 msec", writable=true) + @Property(description="Interval in which a HEARTBEAT is sent to the cluster") long interval=3000; - @Property(description="Timeout after which a node P is suspected if neither a heartbeat nor data were received from P. Default is 5000 msec") - @ManagedAttribute(description="Timeout after which a node P is suspected if neither a heartbeat nor data were received from P", writable=true) - long timeout=5000; - + @Property(description="Timeout after which a node P is suspected if neither a heartbeat nor data were received from P") + long timeout=10000; + @Property(description="Treat messages received from members as heartbeats. Note that this means we're updating " + "a value in a hashmap every time a message is passing up the stack through FD_ALL, which is costly. Default is false") boolean msg_counts_as_heartbeat=false; - @Property(description="Shun switch. Default is true") - @ManagedAttribute(description="Shun switch. Default is true", writable=true) - boolean shun=true; - - /* --------------------------------------------- JMX ------------------------------------------------------ */ - @ManagedAttribute(description="Number of heartbeats sent") protected int num_heartbeats_sent; @@ -65,28 +54,27 @@ /* --------------------------------------------- Fields ------------------------------------------------------ */ - - /** Map of addresses and timestamps of last updates */ - private final Map timestamps=new ConcurrentHashMap(); + // Map of addresses and timestamps of last updates + private final Map timestamps=Util.createConcurrentMap(); private Address local_addr=null; - private final List
      members=Collections.synchronizedList(new ArrayList
      ()); + private final List
      members=new ArrayList
      (); + + protected final Set
      suspected_mbrs=new HashSet
      (); private TimeScheduler timer=null; // task which multicasts HEARTBEAT message after 'interval' ms @GuardedBy("lock") - private ScheduledFuture heartbeat_sender_future=null; + private Future heartbeat_sender_future=null; // task which checks for members exceeding timeout and suspects them @GuardedBy("lock") - private ScheduledFuture timeout_checker_future=null; + private Future timeout_checker_future=null; private final BoundedList
      suspect_history=new BoundedList
      (20); - private final Map invalid_pingers=new HashMap(7); // keys=Address, val=Integer (number of pings from suspected mbrs) - private final Lock lock=new ReentrantLock(); @@ -95,11 +83,12 @@ public FD_ALL() {} - public String getName() {return FD_ALL.name;} @ManagedAttribute(description="Member address") public String getLocalAddress() {return local_addr != null? local_addr.toString() : "null";} @ManagedAttribute(description="Lists members of a cluster") public String getMembers() {return members.toString();} + @ManagedAttribute(description="Currently suspected members") + public String getSuspectedMembers() {return suspected_mbrs.toString();} public int getHeartbeatsSent() {return num_heartbeats_sent;} public int getHeartbeatsReceived() {return num_heartbeats_received;} public int getSuspectEventsSent() {return num_suspect_events;} @@ -107,8 +96,10 @@ public void setTimeout(long timeout) {this.timeout=timeout;} public long getInterval() {return interval;} public void setInterval(long interval) {this.interval=interval;} - public boolean isShun() {return shun;} - public void setShun(boolean flag) {this.shun=flag;} + @Deprecated + public static boolean isShun() {return false;} + @Deprecated + public void setShun(boolean flag) {} @ManagedAttribute(description="Are heartbeat tasks running") public boolean isRunning() { @@ -132,7 +123,17 @@ @ManagedOperation(description="Prints timestamps") public String printTimestamps() { - return printTimeStamps(); + return _printTimestamps(); + } + + @ManagedOperation(description="Stops checking for crashed members") + public void stopFailureDetection() { + stopTimeoutChecker(); + } + + @ManagedOperation(description="Resumes checking for crashed members") + public void startFailureDetection() { + startTimeoutChecker(); } @@ -152,60 +153,26 @@ public void stop() { stopHeartbeatSender(); stopTimeoutChecker(); + suspected_mbrs.clear(); } public Object up(Event evt) { - Message msg; - Header hdr; - Address sender = null; - switch(evt.getType()) { - - case Event.SET_LOCAL_ADDRESS: - local_addr=(Address)evt.getArg(); - break; - case Event.MSG: - msg=(Message)evt.getArg(); - hdr=(Header)msg.getHeader(getName()); - if(msg_counts_as_heartbeat) - update(msg.getSrc()); // update when data is received too ? maybe a bit costly - if(hdr == null) - break; // message did not originate from FD_ALL layer, just pass up - - switch(hdr.type) { - case Header.HEARTBEAT: - sender=msg.getSrc(); - if(sender.equals(local_addr)) - break; - - - // Shun the sender of a HEARTBEAT message if that sender is not a member. This will cause - // the sender to leave the group (and possibly rejoin it later) - if(shun && !members.contains(sender)) { - shunInvalidHeartbeatSender(sender); - break; - } - - update(sender); // updates the heartbeat entry for 'sender' - num_heartbeats_received++; - break; // don't pass up ! - - case Header.SUSPECT: - if(log.isTraceEnabled()) log.trace("[SUSPECT] suspect hdr is " + hdr); - down_prot.down(new Event(Event.SUSPECT, hdr.suspected_mbr)); - up_prot.up(new Event(Event.SUSPECT, hdr.suspected_mbr)); - break; - - case Header.NOT_MEMBER: - if(shun) { - if(log.isDebugEnabled()) log.debug("Received NOT_MEMBER event from " + sender + " I'm being shunned; exiting"); - up_prot.up(new Event(Event.EXIT)); - } - break; + Message msg=(Message)evt.getArg(); + Address sender=msg.getSrc(); + + Header hdr=msg.getHeader(this.id); + if(hdr != null) { + update(sender); // updates the heartbeat entry for 'sender' + num_heartbeats_received++; + return null; // consume heartbeat message, do not pass to the layer above + } else if(msg_counts_as_heartbeat) { + // message did not originate from FD_ALL layer, but still count as heartbeat + update(sender); // update when data is received too ? maybe a bit costly } - return null; + break; // pass message to the layer above } return up_prot.up(evt); // pass up to the layer above us } @@ -218,6 +185,9 @@ View v=(View)evt.getArg(); handleViewChange(v); return null; + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; } return down_prot.down(evt); } @@ -289,20 +259,19 @@ private void handleViewChange(View v) { - Vector
      mbrs=v.getMembers(); - boolean has_at_least_two=mbrs.size() > 1; + List
      mbrs=v.getMembers(); - members.clear(); - members.addAll(mbrs); + synchronized(this) { + members.clear(); + members.addAll(mbrs); + suspected_mbrs.retainAll(mbrs); + timestamps.keySet().retainAll(mbrs); + } - Set
      keys=timestamps.keySet(); - keys.retainAll(mbrs); // remove all nodes which have left the cluster - for(Address member:mbrs) + for(Address member: mbrs) update(member); - invalid_pingers.clear(); - - if(has_at_least_two) { + if(mbrs.size() > 1) { startHeartbeatSender(); startTimeoutChecker(); } @@ -313,40 +282,9 @@ } - /** - * If sender is not a member, send a NOT_MEMBER to sender (after n pings - * received) - */ - private void shunInvalidHeartbeatSender(Address sender) { - int num_pings=0; - Message shun_msg; - - if(invalid_pingers.containsKey(sender)) { - num_pings=invalid_pingers.get(sender).intValue(); - if(num_pings >= 3) { - if(log.isDebugEnabled()) - log.debug(sender + " is not in " + members + " ! Shunning it"); - shun_msg=new Message(sender, null, null); - shun_msg.setFlag(Message.OOB); - shun_msg.putHeader(getName(), new Header(Header.NOT_MEMBER)); - down_prot.down(new Event(Event.MSG, shun_msg)); - invalid_pingers.remove(sender); - } - else { - num_pings++; - invalid_pingers.put(sender, new Integer(num_pings)); - } - } - else { - num_pings++; - invalid_pingers.put(sender, Integer.valueOf(num_pings)); - } - } - - private String printTimeStamps() { + private String _printTimestamps() { StringBuilder sb=new StringBuilder(); - long current_time=System.currentTimeMillis(); for(Iterator> it=timestamps.entrySet().iterator(); it.hasNext();) { Entry entry=it.next(); @@ -356,80 +294,43 @@ return sb.toString(); } - void suspect(Address mbr) { - Message suspect_msg=new Message(); - suspect_msg.setFlag(Message.OOB); - Header hdr=new Header(Header.SUSPECT, mbr); - suspect_msg.putHeader(getName(), hdr); - down_prot.down(new Event(Event.MSG, suspect_msg)); - num_suspect_events++; - suspect_history.add(mbr); - } - - - public static class Header extends org.jgroups.Header implements Streamable { - public static final byte HEARTBEAT = 0; - public static final byte SUSPECT = 1; - public static final byte NOT_MEMBER = 2; // received as response by pinged mbr when we are not a member - - - byte type=Header.HEARTBEAT; - Address suspected_mbr=null; - - - /** used for externalization */ - public Header() { - } - - public Header(byte type) { - this.type=type; - } - - public Header(byte type, Address suspect) { - this(type); - this.suspected_mbr=suspect; - } - - - public String toString() { - switch(type) { - case FD_ALL.Header.HEARTBEAT: - return "heartbeat"; - case FD_ALL.Header.SUSPECT: - return "SUSPECT (suspected_mbr=" + suspected_mbr + ")"; - case FD_ALL.Header.NOT_MEMBER: - return "NOT_MEMBER"; - default: - return "unknown type (" + type + ")"; + void suspect(List
      suspects) { + if(suspects == null) + return; + + num_suspect_events+=suspects.size(); + + final List
      eligible_mbrs=new ArrayList
      (); + synchronized(this) { + for(Address suspect: suspects) { + suspect_history.add(suspect); + suspected_mbrs.add(suspect); } + eligible_mbrs.addAll(members); + eligible_mbrs.removeAll(suspected_mbrs); } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeByte(type); - out.writeObject(suspected_mbr); - } - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - type=in.readByte(); - suspected_mbr=(Address)in.readObject(); - } - - public int size() { - int retval=Global.BYTE_SIZE; // type - retval+=Util.size(suspected_mbr); - return retval; - } - - public void writeTo(DataOutputStream out) throws IOException { - out.writeByte(type); - Util.writeAddress(suspected_mbr, out); + // Check if we're coord, then send up the stack + if(local_addr != null && !eligible_mbrs.isEmpty()) { + Address first=eligible_mbrs.get(0); + if(local_addr.equals(first)) { + if(log.isDebugEnabled()) + log.debug("suspecting " + suspected_mbrs); + for(Address suspect: suspects) { + up_prot.up(new Event(Event.SUSPECT, suspect)); + down_prot.down(new Event(Event.SUSPECT, suspect)); + } + } } + } - public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { - type=in.readByte(); - suspected_mbr=Util.readAddress(in); - } + public static class HeartbeatHeader extends Header { + public HeartbeatHeader() {} + public String toString() {return "heartbeat";} + public int size() {return 0;} + public void writeTo(DataOutputStream out) throws IOException {} + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException {} } @@ -437,15 +338,11 @@ * Class which periodically multicasts a HEARTBEAT message to the cluster */ class HeartbeatSender implements Runnable { - public void run() { Message heartbeat=new Message(); // send to all heartbeat.setFlag(Message.OOB); - Header hdr=new Header(Header.HEARTBEAT); - heartbeat.putHeader(getName(), hdr); + heartbeat.putHeader(id, new HeartbeatHeader()); down_prot.down(new Event(Event.MSG, heartbeat)); - if(log.isTraceEnabled()) - log.trace(local_addr + " sent heartbeat to cluster"); num_heartbeats_sent++; } } @@ -454,10 +351,7 @@ class TimeoutChecker implements Runnable { public void run() { - - if(log.isTraceEnabled()) - log.trace("checking for expired senders, table is:\n" + printTimeStamps()); - + List
      suspects=new LinkedList
      (); long current_time=System.currentTimeMillis(), diff; for(Iterator> it=timestamps.entrySet().iterator(); it.hasNext();) { Entry entry=it.next(); @@ -469,11 +363,14 @@ } diff=current_time - val.longValue(); if(diff > timeout) { - if(log.isTraceEnabled()) - log.trace("haven't received a heartbeat from " + key + " for " + diff + " ms, suspecting it"); - suspect(key); + if(log.isDebugEnabled()) + log.debug("haven't received a heartbeat from " + key + " for " + diff + + " ms, adding it to suspect list"); + suspects.add(key); } } + if(!suspects.isEmpty()) + suspect(suspects); } } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FD_ICMP.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FD_ICMP.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FD_ICMP.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FD_ICMP.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,7 +1,9 @@ package org.jgroups.protocols; import org.jgroups.Event; +import org.jgroups.Global; import org.jgroups.annotations.Experimental; +import org.jgroups.annotations.LocalAddress; import org.jgroups.annotations.Property; import org.jgroups.conf.PropertyConverters; import org.jgroups.stack.IpAddress; @@ -22,7 +24,6 @@ * won't work... * * @author Bela Ban - * @version $Id: FD_ICMP.java,v 1.11 2008/06/02 15:50:09 vlada Exp $ */ @Experimental public class FD_ICMP extends FD { @@ -30,9 +31,18 @@ /** network interface to be used to send the ICMP packets */ private NetworkInterface intf=null; - @Property(converter=PropertyConverters.BindAddress.class) - private InetAddress bind_addr; - + @LocalAddress + @Property(name="bind_addr", + description="The NIC on which the ServerSocket should listen on. " + + "The following special values are also recognized: GLOBAL, SITE_LOCAL, LINK_LOCAL and NON_LOOPBACK", + systemProperty={Global.BIND_ADDR, Global.BIND_ADDR_OLD}, + defaultValueIPv4=Global.NON_LOOPBACK_ADDRESS, defaultValueIPv6=Global.NON_LOOPBACK_ADDRESS) + private InetAddress bind_addr=null ; + + @Property(name="bind_interface", converter=PropertyConverters.BindInterface.class, + description="The interface (NIC) which should be used by this transport", dependsUpon="bind_addr") + protected String bind_interface_str=null; + private Method is_reacheable; /** Time-to-live for InetAddress.isReachable() */ @@ -40,11 +50,6 @@ private int ttl=32; - - public String getName() { - return "FD_ICMP"; - } - public void init() throws Exception { super.init(); if(bind_addr != null) @@ -52,10 +57,7 @@ try { Class is_reacheable_class=Util.loadClass("java.net.InetAddress", this.getClass()); - is_reacheable=is_reacheable_class.getMethod("isReachable", - new Class[] { NetworkInterface.class, - int.class, - int.class }); + is_reacheable=is_reacheable_class.getMethod("isReachable", NetworkInterface.class, int.class, int.class); } catch(ClassNotFoundException e) { // should never happen since we require JDK 1.5 @@ -112,7 +114,7 @@ if(log.isTraceEnabled()) log.trace("pinging " + host + " (ping_dest=" + ping_dest + ") using interface " + intf); start=System.currentTimeMillis(); - Boolean rc=(Boolean)is_reacheable.invoke(host, new Object[]{intf, new Integer(ttl), new Integer((int)timeout)}); + Boolean rc=(Boolean)is_reacheable.invoke(host, intf, new Integer(ttl), new Integer((int)timeout)); stop=System.currentTimeMillis(); num_heartbeats++; if(rc.booleanValue()) { // success diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FD.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FD.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FD.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FD.java 2011-10-18 11:22:35.000000000 +0000 @@ -3,13 +3,11 @@ import org.jgroups.*; -import org.jgroups.annotations.GuardedBy; -import org.jgroups.annotations.MBean; -import org.jgroups.annotations.ManagedAttribute; -import org.jgroups.annotations.ManagedOperation; -import org.jgroups.annotations.Property; +import org.jgroups.annotations.*; import org.jgroups.stack.Protocol; -import org.jgroups.util.*; +import org.jgroups.util.BoundedList; +import org.jgroups.util.TimeScheduler; +import org.jgroups.util.Util; import java.io.*; import java.util.*; @@ -36,32 +34,19 @@ * When a message is received from the monitored neighbor member, it causes the * pinger thread to 'skip' sending the next are-you-alive message. Thus, traffic * is reduced. - *

      - * When we receive a ping from a member that's not in the membership list, we - * shun it by sending it a NOT_MEMBER message. That member will then leave the - * group (and possibly rejoin). This is only done if shun is - * true. - * + * * @author Bela Ban - * @version $Id: FD.java,v 1.73 2008/10/21 12:16:15 vlada Exp $ */ @MBean(description="Failure detection based on simple heartbeat protocol") +@DeprecatedProperty(names={"shun"}) public class FD extends Protocol { - private final static String name="FD"; - /* ----------------------------------------- Properties -------------------------------------------------- */ @Property(description="Timeout to suspect a node P if neither a heartbeat nor data were received from P. Default is 3000 msec") - @ManagedAttribute(description="Timeout in msec to suspect a node P if neither a heartbeat nor data were received from P", writable=true) - long timeout=3000; - - @Property(description="Shun switch. Default is true") - @ManagedAttribute(description="Shun switch", writable=true) - boolean shun=true; + long timeout=3000; - @Property(description="Number of times to send heartbeat. Default is 2") - @ManagedAttribute(description="Number of times to send a are-you-alive msg", writable=true) + @Property(description="Number of times to send an are-you-alive message") int max_tries=2; @@ -98,9 +83,6 @@ @GuardedBy("lock") protected final List

      pingable_mbrs=new ArrayList
      (); - @GuardedBy("lock") - private final Map invalid_pingers=new HashMap(7); - private TimeScheduler timer=null; @GuardedBy("lock") @@ -109,8 +91,6 @@ /** Transmits SUSPECT message until view change or UNSUSPECT is received */ protected final Broadcaster bcast_task=new Broadcaster(); - - public String getName() {return name;} @ManagedAttribute(description="Member address") public String getLocalAddress() {return local_addr != null? local_addr.toString() : "null";} @ManagedAttribute(description="List of cluster members") @@ -128,8 +108,10 @@ public int getMaxTries() {return max_tries;} public void setMaxTries(int max_tries) {this.max_tries=max_tries;} public int getCurrentNumTries() {return num_tries;} - public boolean isShun() {return shun;} - public void setShun(boolean flag) {this.shun=flag;} + @Deprecated + public static boolean isShun() {return false;} + @Deprecated + public void setShun(boolean flag) {} @ManagedOperation(description="Print suspect history") public String printSuspectHistory() { StringBuilder sb=new StringBuilder(); @@ -181,12 +163,27 @@ return retval; } + + protected Monitor createMonitor() { + return new Monitor(); + } + + @ManagedOperation(description="Stops checking for crashed members") + public void stopFailureDetection() { + stopMonitor(); + } + + @ManagedOperation(description="Resumes checking for crashed members") + public void startFailureDetection() { + startMonitor(); + } + /** Requires lock to held by caller */ @GuardedBy("lock") private void startMonitor() { if(monitor_future == null || monitor_future.isDone()) { last_ack=System.currentTimeMillis(); // start from scratch - monitor_future=timer.scheduleWithFixedDelay(new Monitor(), timeout, timeout, TimeUnit.MILLISECONDS); + monitor_future=timer.scheduleWithFixedDelay(createMonitor(), timeout, timeout, TimeUnit.MILLISECONDS); num_tries=0; } } @@ -226,14 +223,9 @@ public Object up(Event evt) { switch(evt.getType()) { - - case Event.SET_LOCAL_ADDRESS: - local_addr=(Address)evt.getArg(); - break; - case Event.MSG: Message msg=(Message)evt.getArg(); - FdHeader hdr=(FdHeader)msg.getHeader(name); + FdHeader hdr=(FdHeader)msg.getHeader(this.id); if(hdr == null) { updateTimestamp(msg.getSrc()); break; // message did not originate from FD layer, just pass up @@ -245,11 +237,6 @@ if(log.isTraceEnabled()) log.trace("received are-you-alive from " + hb_sender + ", sending response"); sendHeartbeatResponse(hb_sender); - - // 2. Shun the sender of a HEARTBEAT message if that sender is not a member. This will cause - // the sender to leave the group (and possibly rejoin it later) - if(shun) - shunInvalidHeartbeatSender(hb_sender); break; // don't pass up ! case FdHeader.HEARTBEAT_ACK: // heartbeat ack @@ -259,9 +246,8 @@ case FdHeader.SUSPECT: if(hdr.mbrs != null) { if(log.isTraceEnabled()) log.trace("[SUSPECT] suspect hdr is " + hdr); - for(int i=0; i < hdr.mbrs.size(); i++) { - Address m=hdr.mbrs.elementAt(i); - if(local_addr != null && m.equals(local_addr)) { + for(Address mbr: hdr.mbrs) { + if(local_addr != null && mbr.equals(local_addr)) { if(log.isWarnEnabled()) log.warn("I was suspected by " + msg.getSrc() + "; ignoring the SUSPECT " + "message and sending back a HEARTBEAT_ACK"); @@ -271,25 +257,18 @@ else { lock.lock(); try { - pingable_mbrs.remove(m); + pingable_mbrs.remove(mbr); restartMonitor(); } finally { lock.unlock(); } } - up_prot.up(new Event(Event.SUSPECT, m)); - down_prot.down(new Event(Event.SUSPECT, m)); + up_prot.up(new Event(Event.SUSPECT, mbr)); + down_prot.down(new Event(Event.SUSPECT, mbr)); } } break; - - case FdHeader.NOT_MEMBER: - if(shun) { - if(log.isDebugEnabled()) log.debug("[NOT_MEMBER] I'm being shunned; exiting"); - up_prot.up(new Event(Event.EXIT)); - } - break; } return null; } @@ -323,6 +302,10 @@ case Event.UNSUSPECT: unsuspect((Address)evt.getArg()); return down_prot.down(evt); + + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; } return down_prot.down(evt); } @@ -333,7 +316,7 @@ hb_ack.setFlag(Message.OOB); FdHeader tmp_hdr=new FdHeader(FdHeader.HEARTBEAT_ACK); tmp_hdr.from=local_addr; - hb_ack.putHeader(name, tmp_hdr); + hb_ack.putHeader(this.id, tmp_hdr); down_prot.down(new Event(Event.MSG, hb_ack)); } @@ -354,83 +337,41 @@ @GuardedBy("lock") private void updateTimestamp(Address sender) { - lock.lock(); - try { - if(ping_dest != null && sender != null && ping_dest.equals(sender)) { - last_ack=System.currentTimeMillis(); - if(log.isTraceEnabled()) - log.trace("received msg from " + sender + " (counts as ack)"); + if(ping_dest != null && sender != null && ping_dest.equals(sender)) { + long tmp=System.currentTimeMillis(); + lock.lock(); + try { + last_ack=tmp; num_tries=0; } + finally { + lock.unlock(); + } } - finally { - lock.unlock(); - } - } - /** - * If sender is not a member, send a NOT_MEMBER to sender (after n pings received) - */ - private void shunInvalidHeartbeatSender(Address hb_sender) { - int num_pings=0; - Message shun_msg=null; - - lock.lock(); - try { - if(hb_sender != null && members != null && !members.contains(hb_sender)) { - if(invalid_pingers.containsKey(hb_sender)) { - num_pings=invalid_pingers.get(hb_sender).intValue(); - if(num_pings >= max_tries) { - if(log.isDebugEnabled()) - log.debug(hb_sender + " is not in " + members + " ! Shunning it"); - shun_msg=new Message(hb_sender, null, null); - shun_msg.setFlag(Message.OOB); - shun_msg.putHeader(name, new FdHeader(FdHeader.NOT_MEMBER)); - invalid_pingers.remove(hb_sender); - } - else { - num_pings++; - invalid_pingers.put(hb_sender, new Integer(num_pings)); - } - } - else { - num_pings++; - invalid_pingers.put(hb_sender, new Integer(num_pings)); - } - } - } - finally { - lock.unlock(); - } - - if(shun_msg != null) - down_prot.down(new Event(Event.MSG, shun_msg)); - } - public static class FdHeader extends Header implements Streamable { + public static class FdHeader extends Header { public static final byte HEARTBEAT=0; public static final byte HEARTBEAT_ACK=1; public static final byte SUSPECT=2; - public static final byte NOT_MEMBER=3; // received as response by pinged mbr when we are not a member byte type=HEARTBEAT; - Vector
      mbrs=null; + Collection
      mbrs=null; Address from=null; // member who detected that suspected_mbr has failed - private static final long serialVersionUID=-6387039473828820899L; public FdHeader() { - } // used for externalization + } public FdHeader(byte type) { this.type=type; } - public FdHeader(byte type, Vector
      mbrs, Address from) { + public FdHeader(byte type, Collection
      mbrs, Address from) { this(type); this.mbrs=mbrs; this.from=from; @@ -445,42 +386,11 @@ return "heartbeat ack"; case SUSPECT: return "SUSPECT (suspected_mbrs=" + mbrs + ", from=" + from + ")"; - case NOT_MEMBER: - return "NOT_MEMBER"; default: return "unknown type (" + type + ")"; } } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeByte(type); - if(mbrs == null) - out.writeBoolean(false); - else { - out.writeBoolean(true); - out.writeInt(mbrs.size()); - for(Iterator
      it=mbrs.iterator(); it.hasNext();) { - Address addr=it.next(); - out.writeObject(addr); - } - } - out.writeObject(from); - } - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - type=in.readByte(); - boolean mbrs_not_null=in.readBoolean(); - if(mbrs_not_null) { - int len=in.readInt(); - mbrs=new Vector
      (11); - for(int i=0; i < len; i++) { - Address addr=(Address)in.readObject(); - mbrs.add(addr); - } - } - from=(Address)in.readObject(); - } - public int size() { int retval=Global.BYTE_SIZE; // type @@ -497,10 +407,9 @@ } - public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { type=in.readByte(); - mbrs=(Vector
      )Util.readAddresses(in, Vector.class); + mbrs=(Collection
      )Util.readAddresses(in, Vector.class); from=Util.readAddress(in); } @@ -533,7 +442,7 @@ // 1. send heartbeat request hb_req=new Message(dest, null, null); hb_req.setFlag(Message.OOB); - hb_req.putHeader(name, new FdHeader(FdHeader.HEARTBEAT)); // send heartbeat request + hb_req.putHeader(id, new FdHeader(FdHeader.HEARTBEAT)); // send heartbeat request if(log.isDebugEnabled()) log.debug("sending are-you-alive msg to " + dest + " (own address=" + local_addr + ')'); down_prot.down(new Event(Event.MSG, hb_req)); @@ -694,7 +603,7 @@ } suspect_msg=new Message(); // mcast SUSPECT to all members suspect_msg.setFlag(Message.OOB); - suspect_msg.putHeader(name, hdr); + suspect_msg.putHeader(id, hdr); if(log.isDebugEnabled()) log.debug("broadcasting SUSPECT message [suspected_mbrs=" + suspected_members + "] to group"); down_prot.down(new Event(Event.MSG, suspect_msg)); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FD_PING.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FD_PING.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FD_PING.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FD_PING.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,10 +1,10 @@ package org.jgroups.protocols; -import org.apache.commons.logging.Log; import org.jgroups.annotations.Property; import org.jgroups.annotations.Unsupported; import org.jgroups.stack.IpAddress; import org.jgroups.util.Util; +import org.jgroups.logging.Log; import java.io.IOException; import java.io.InputStream; @@ -15,7 +15,6 @@ * taking 1 argument; the host name of the host to be pinged. Property 'cmd' determines the program to be executed * (use a fully qualified name if the program is not on the path). * @author Bela Ban - * @version $Id: FD_PING.java,v 1.9 2008/12/05 09:22:28 belaban Exp $ */ @Unsupported public class FD_PING extends FD { @@ -27,10 +26,6 @@ @Property(description="Write the stdout of the command to the log. Default is true") boolean verbose=true; - public String getName() { - return "FD_PING"; - } - protected Monitor createMonitor() { return new PingMonitor(); } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FD_SIMPLE.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FD_SIMPLE.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FD_SIMPLE.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FD_SIMPLE.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: FD_SIMPLE.java,v 1.25 2008/05/29 14:17:37 vlada Exp $ package org.jgroups.protocols; @@ -7,15 +6,13 @@ import org.jgroups.annotations.Property; import org.jgroups.annotations.Unsupported; import org.jgroups.stack.Protocol; -import org.jgroups.stack.ProtocolStack; import org.jgroups.util.Promise; import org.jgroups.util.TimeScheduler; -import org.jgroups.util.Streamable; import java.io.*; import java.util.HashMap; import java.util.Iterator; -import java.util.Properties; +import java.util.Map; import java.util.Vector; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -30,16 +27,16 @@ * suspected. When a message or a heartbeat are received, the counter is reset to 0. * * @author Bela Ban Aug 2002 - * @version $Revision: 1.25 $ + * @version $Revision: 1.31 $ */ @Unsupported public class FD_SIMPLE extends Protocol { - Address local_addr=null; + Address local_addr=null; TimeScheduler timer=null; final Lock heartbeat_lock=new ReentrantLock(); @GuardedBy("heartbeat_lock") - Future heartbeat_future=null; + Future heartbeat_future=null; HeartbeatTask task; @Property @@ -47,16 +44,11 @@ @Property long timeout=3000; // time (in msecs) to wait for a response to are-you-alive final Vector
      members=new Vector
      (); - final HashMap counters=new HashMap(); // keys=Addresses, vals=Integer (count) + final Map counters=new HashMap(); // keys=Addresses, vals=Integer (count) @Property int max_missed_hbs=5; // max number of missed responses until a member is suspected - static final String name="FD_SIMPLE"; - public String getName() { - return "FD_SIMPLE"; - } - public void init() throws Exception { timer=getTransport().getTimer(); } @@ -83,25 +75,20 @@ boolean counter_reset=false; switch(evt.getType()) { - - case Event.SET_LOCAL_ADDRESS: - local_addr=(Address)evt.getArg(); - break; - case Event.MSG: msg=(Message)evt.getArg(); sender=msg.getSrc(); resetCounter(sender); counter_reset=true; - hdr=(FdHeader)msg.getHeader(name); + hdr=(FdHeader)msg.getHeader(this.id); if(hdr == null) break; switch(hdr.type) { case FdHeader.ARE_YOU_ALIVE: // are-you-alive request, send i-am-alive response rsp=new Message(sender); - rsp.putHeader(name, new FdHeader(FdHeader.I_AM_ALIVE)); + rsp.putHeader(this.id, new FdHeader(FdHeader.I_AM_ALIVE)); down_prot.down(new Event(Event.MSG, rsp)); return null; // don't pass up further @@ -134,6 +121,9 @@ Address key; switch(evt.getType()) { + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; // Start heartbeat thread when we have more than 1 member; stop it when membership drops below 2 case Event.VIEW_CHANGE: @@ -169,11 +159,13 @@ } // remove all keys from 'counters' which are not in this new view - for(Iterator
      it=counters.keySet().iterator(); it.hasNext();) { - key=it.next(); - if(!members.contains(key)) { - if(log.isInfoEnabled()) log.info("removing " + key + " from counters"); - it.remove(); + synchronized(counters) { + for(Iterator
      it=counters.keySet().iterator(); it.hasNext();) { + key=it.next(); + if(!members.contains(key)) { + if(log.isInfoEnabled()) log.info("removing " + key + " from counters"); + it.remove(); + } } } } @@ -239,9 +231,11 @@ StringBuilder sb=new StringBuilder(); Address key; - for(Iterator
      it=counters.keySet().iterator(); it.hasNext();) { - key=it.next(); - sb.append(key).append(": ").append(counters.get(key)).append('\n'); + synchronized(counters) { + for(Iterator
      it=counters.keySet().iterator(); it.hasNext();) { + key=it.next(); + sb.append(key).append(": ").append(counters.get(key)).append('\n'); + } } return sb.toString(); } @@ -253,14 +247,13 @@ - public static class FdHeader extends Header implements Streamable { + public static class FdHeader extends Header { static final byte ARE_YOU_ALIVE=1; // sent periodically to a random member static final byte I_AM_ALIVE=2; // response to above message - byte type=ARE_YOU_ALIVE; - private static final long serialVersionUID=4021056597004641352L; + public FdHeader() { } // used for externalization @@ -281,15 +274,6 @@ } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeByte(type); - } - - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - type=in.readByte(); - } - public int size() { return Global.BYTE_SIZE; } @@ -331,7 +315,7 @@ promise.reset(); msg=new Message(dest); - msg.putHeader(name, new FdHeader(FdHeader.ARE_YOU_ALIVE)); + msg.putHeader(id, new FdHeader(FdHeader.ARE_YOU_ALIVE)); down_prot.down(new Event(Event.MSG, msg)); promise.getResult(timeout); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FD_SOCK.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FD_SOCK.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FD_SOCK.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FD_SOCK.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ - package org.jgroups.protocols; import org.jgroups.*; @@ -7,12 +6,14 @@ import org.jgroups.stack.IpAddress; import org.jgroups.stack.Protocol; import org.jgroups.util.*; -import org.jgroups.util.ThreadFactory; import java.io.*; import java.net.*; import java.util.*; -import java.util.concurrent.*; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; /** @@ -31,22 +32,26 @@ * monitors the client side of the socket connection (to monitor a peer) and another one that manages the * server socket. However, those threads will be idle as long as both peers are running. * @author Bela Ban May 29 2001 - * @version $Id: FD_SOCK.java,v 1.103 2008/12/23 08:10:37 belaban Exp $ */ @MBean(description="Failure detection protocol based on sockets connecting members") @DeprecatedProperty(names={"srv_sock_bind_addr"}) public class FD_SOCK extends Protocol implements Runnable { - - private static final String name="FD_SOCK"; - private static final int NORMAL_TERMINATION=9; private static final int ABNORMAL_TERMINATION=-1; /* ----------------------------------------- Properties -------------------------------------------------- */ - @Property(converter=PropertyConverters.BindAddress.class,description="The NIC on which the ServerSocket should listen on") + @LocalAddress + @Property(description="The NIC on which the ServerSocket should listen on. " + + "The following special values are also recognized: GLOBAL, SITE_LOCAL, LINK_LOCAL and NON_LOOPBACK", + systemProperty={Global.BIND_ADDR, Global.BIND_ADDR_OLD}, + defaultValueIPv4=Global.NON_LOOPBACK_ADDRESS, defaultValueIPv6=Global.NON_LOOPBACK_ADDRESS) InetAddress bind_addr=null; - + + @Property(name="bind_interface", converter=PropertyConverters.BindInterface.class, + description="The interface (NIC) which should be used by this transport", dependsUpon="bind_addr") + protected String bind_interface_str=null; + @Property(description="Timeout for getting socket cache from coordinator. Default is 1000 msec") long get_cache_timeout=1000; @@ -58,12 +63,17 @@ @Property(description="Start port for server socket. Default value of 0 picks a random port") int start_port=0; + + @Property(description="Start port for client socket. Default value of 0 picks a random port") + int client_bind_port=0; + + @Property(description="Number of ports to probe for start_port and client_bind_port") + int port_range=50; @Property(description="Whether to use KEEP_ALIVE on the ping socket or not. Default is true") private boolean keep_alive=true; @Property(description="Max time in millis to wait for ping Socket.connect() to return") - @ManagedAttribute(writable=true, description="Max time in millis to wait for ping Socket.connect() to return. Default is 3000 msec") private int sock_conn_timeout=1000; @@ -79,7 +89,11 @@ private final Vector
      members=new Vector
      (11); // list of group members (updated on VIEW_CHANGE) - private final Vector
      pingable_mbrs=new Vector
      (11); + + protected final Set
      suspected_mbrs=new HashSet
      (); + + private final Vector
      pingable_mbrs=new Vector
      (11); + volatile boolean srv_sock_sent=false; // has own socket been broadcast yet ? /** Used to rendezvous on GET_CACHE and GET_CACHE_RSP */ private final Promise> get_cache_promise=new Promise>(); @@ -96,7 +110,7 @@ private volatile Thread pinger_thread=null; // listens on ping_sock, suspects member if socket is closed /** Cache of member addresses and their ServerSocket addresses */ - private final ConcurrentMap cache=new ConcurrentHashMap(11); + private final ConcurrentMap cache=Util.createConcurrentMap(11); private final Promise ping_addr_promise=new Promise(); // to fetch the ping_addr for ping_dest private final Object sock_mutex=new Object(); // for access to ping_sock, ping_input @@ -110,10 +124,6 @@ public FD_SOCK() { } - public String getName() { - return name; - } - @ManagedAttribute(description="Member address") public String getLocalAddress() {return local_addr != null? local_addr.toString() : "null";} @ManagedAttribute(description="List of cluster members") @@ -125,7 +135,7 @@ @ManagedAttribute(description="Number of suspect event generated") public int getNumSuspectEventsGenerated() {return num_suspect_events;} - public boolean isLog_SuspectedMessages() { + public boolean isLogSuspectedMessages() { return log_suspected_msgs; } @@ -167,6 +177,7 @@ stopPingerThread(); stopServerSocket(true); // graceful close bcast_task.removeAll(); + suspected_mbrs.clear(); } public void resetStats() { @@ -179,13 +190,9 @@ public Object up(Event evt) { switch(evt.getType()) { - case Event.SET_LOCAL_ADDRESS: - local_addr=(Address) evt.getArg(); - break; - case Event.MSG: Message msg=(Message) evt.getArg(); - FdHeader hdr=(FdHeader)msg.getHeader(name); + FdHeader hdr=(FdHeader)msg.getHeader(this.id); if(hdr == null) break; // message did not originate from FD_SOCK layer, just pass up @@ -193,17 +200,9 @@ case FdHeader.SUSPECT: if(hdr.mbrs != null) { - if(log.isTraceEnabled()) log.trace("[SUSPECT] hdr=" + hdr); - for(Address m: hdr.mbrs) { - if(local_addr != null && m.equals(local_addr)) { - if(log.isWarnEnabled() && log_suspected_msgs) - log.warn("I (" + local_addr + ") was suspected by " + msg.getSrc() + - "; ignoring the SUSPECT message"); - continue; - } - up_prot.up(new Event(Event.SUSPECT, m)); - down_prot.down(new Event(Event.SUSPECT, m)); - } + if(log.isTraceEnabled()) + log.trace("received SUSPECT message from " + msg.getSrc() + ": suspects=" + hdr.mbrs); + suspect(hdr.mbrs); } break; @@ -252,7 +251,7 @@ hdr=new FdHeader(FdHeader.GET_CACHE_RSP,new HashMap(cache)); msg=new Message(sender, null, null); msg.setFlag(Message.OOB); - msg.putHeader(name, hdr); + msg.putHeader(this.id, hdr); down_prot.down(new Event(Event.MSG, msg)); break; @@ -285,17 +284,24 @@ break; case Event.CONNECT: - case Event.CONNECT_WITH_STATE_TRANSFER: - Object ret=down_prot.down(evt); - startServerSocket(); + case Event.CONNECT_WITH_STATE_TRANSFER: + case Event.CONNECT_USE_FLUSH: + case Event.CONNECT_WITH_STATE_TRANSFER_USE_FLUSH: + Object ret=down_prot.down(evt); + try { + startServerSocket(); + } + catch(Exception e) { + throw new IllegalArgumentException("failed to start server socket", e); + } return ret; case Event.DISCONNECT: stopServerSocket(true); // graceful close break; - case Event.SHUTDOWN: - stopServerSocket(false); + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address) evt.getArg(); break; case Event.VIEW_CHANGE: @@ -305,6 +311,7 @@ synchronized(this) { members.removeAllElements(); members.addAll(new_mbrs); + suspected_mbrs.retainAll(new_mbrs); cache.keySet().retainAll(members); // remove all entries in 'cache' which are not in the new membership bcast_task.adjustSuspectedMembers(members); pingable_mbrs.removeAllElements(); @@ -383,7 +390,7 @@ if(!setupPingSocket(ping_addr) && isPingerThreadRunning()) { // covers use cases #7 and #8 in ManualTests.txt - if(log.isDebugEnabled()) log.debug("could not create socket to " + ping_dest + "; suspecting " + ping_dest); + if(log.isDebugEnabled()) log.debug("could not create socket to " + ping_dest); broadcastSuspectMessage(ping_dest); pingable_mbrs.removeElement(ping_dest); continue; @@ -429,6 +436,35 @@ /* ----------------------------------- Private Methods -------------------------------------- */ + void suspect(Set
      suspects) { + if(suspects == null) + return; + + final List
      eligible_mbrs=new ArrayList
      (); + synchronized(this) { + for(Address suspect: suspects) { + suspect_history.add(suspect); + suspected_mbrs.add(suspect); + } + eligible_mbrs.addAll(members); + eligible_mbrs.removeAll(suspected_mbrs); + } + + // Check if we're coord, then send up the stack + if(local_addr != null && !eligible_mbrs.isEmpty()) { + Address first=eligible_mbrs.get(0); + if(local_addr.equals(first)) { + if(log.isDebugEnabled()) + log.debug("suspecting " + suspected_mbrs); + for(Address suspect: suspects) { + up_prot.up(new Event(Event.SUSPECT, suspect)); + down_prot.down(new Event(Event.SUSPECT, suspect)); + } + } + } + } + + void handleSocketClose(Exception ex) { teardownPingSocket(); // make sure we have no leftovers if(!regular_sock_close) { // only suspect if socket was not closed regularly (by interruptPingerThread()) @@ -521,15 +557,16 @@ - void startServerSocket() { - srv_sock=Util.createServerSocket(bind_addr, start_port); // grab a random unused port above 10000 + void startServerSocket() throws Exception { + srv_sock=Util.createServerSocket(getSocketFactory(), + Global.FD_SOCK_SRV_SOCK, bind_addr, start_port, start_port+port_range); // grab a random unused port above 10000 srv_sock_addr=new IpAddress(bind_addr, srv_sock.getLocalPort()); if(srv_sock_handler != null) { srv_sock_handler.start(); // won't start if already running } } - void stopServerSocket(boolean graceful) { + public void stopServerSocket(boolean graceful) { if(srv_sock_handler != null) srv_sock_handler.stop(graceful); } @@ -546,9 +583,26 @@ try { SocketAddress destAddr=new InetSocketAddress(dest.getIpAddress(), dest.getPort()); ping_sock=new Socket(); + + int num_bind_attempts=0; + int port=client_bind_port; + for(;;) { + try { + ping_sock.bind(new InetSocketAddress(bind_addr, port)); + break; + } + catch(IOException e) { + if(num_bind_attempts++ > port_range) { + log.error("failed creating client socket to " + dest, e); + throw e; + } + port++; + } + } + ping_sock.setSoLinger(true, 1); ping_sock.setKeepAlive(keep_alive); - ping_sock.connect(destAddr, sock_conn_timeout); + Util.connect(ping_sock, destAddr, sock_conn_timeout); ping_input=ping_sock.getInputStream(); return true; } @@ -596,7 +650,7 @@ hdr=new FdHeader(FdHeader.GET_CACHE); msg=new Message(coord, null, null); msg.setFlag(Message.OOB); - msg.putHeader(name, hdr); + msg.putHeader(this.id, hdr); down_prot.down(new Event(Event.MSG, msg)); result=get_cache_promise.getResult(get_cache_timeout); if(result != null) { @@ -634,7 +688,7 @@ hdr.mbrs.add(suspected_mbr); suspect_msg=new Message(); suspect_msg.setFlag(Message.OOB); - suspect_msg.putHeader(name, hdr); + suspect_msg.putHeader(this.id, hdr); down_prot.down(new Event(Event.MSG, suspect_msg)); // 2. Add to broadcast task and start latter (if not yet running). The task will end when @@ -659,7 +713,7 @@ FdHeader hdr=new FdHeader(FdHeader.I_HAVE_SOCK); hdr.mbr=mbr; hdr.sock_addr=addr; - msg.putHeader(name, hdr); + msg.putHeader(this.id, hdr); down_prot.down(new Event(Event.MSG, msg)); } @@ -688,7 +742,7 @@ ping_addr_req.setFlag(Message.OOB); hdr=new FdHeader(FdHeader.WHO_HAS_SOCK); hdr.mbr=mbr; - ping_addr_req.putHeader(name, hdr); + ping_addr_req.putHeader(this.id, hdr); down_prot.down(new Event(Event.MSG, ping_addr_req)); ret=ping_addr_promise.getResult(500); if(ret != null) { @@ -702,7 +756,7 @@ ping_addr_req.setFlag(Message.OOB); hdr=new FdHeader(FdHeader.WHO_HAS_SOCK); hdr.mbr=mbr; - ping_addr_req.putHeader(name, hdr); + ping_addr_req.putHeader(this.id, hdr); down_prot.down(new Event(Event.MSG, ping_addr_req)); ret=ping_addr_promise.getResult(500); return ret; @@ -746,7 +800,7 @@ /* ------------------------------- End of Private Methods ------------------------------------ */ - public static class FdHeader extends Header implements Streamable { + public static class FdHeader extends Header { public static final byte SUSPECT=10; public static final byte WHO_HAS_SOCK=11; public static final byte I_HAVE_SOCK=12; @@ -759,11 +813,10 @@ IpAddress sock_addr; // set on I_HAVE_SOCK Map cachedAddrs=null; // set on GET_CACHE_RSP Set
      mbrs=null; // set on SUSPECT (list of suspected members) - private static final long serialVersionUID=-7025890133989522764L; public FdHeader() { - } // used for externalization + } public FdHeader(byte type) { this.type=type; @@ -823,22 +876,6 @@ } } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeByte(type); - out.writeObject(mbr); - out.writeObject(sock_addr); - out.writeObject(cachedAddrs); - out.writeObject(mbrs); - } - - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - type=in.readByte(); - mbr=(Address)in.readObject(); - sock_addr=(IpAddress)in.readObject(); - cachedAddrs=(Map)in.readObject(); - mbrs=(Set
      )in.readObject(); - } public int size() { int retval=Global.BYTE_SIZE; // type @@ -961,7 +998,8 @@ final void stop(boolean graceful) { if(acceptor != null && acceptor.isAlive()) { try { - srv_sock.close(); // this will terminate thread, peer will receive SocketException (socket close) + // this will terminate thread, peer will receive SocketException (socket close) + getSocketFactory().close(srv_sock); } catch(Exception ex) { } @@ -1049,7 +1087,7 @@ return; in=client_sock.getInputStream(); } - int b=0; + int b; do { b=in.read(); } @@ -1164,7 +1202,7 @@ } suspect_msg=new Message(); // mcast SUSPECT to all members suspect_msg.setFlag(Message.OOB); - suspect_msg.putHeader(name, hdr); + suspect_msg.putHeader(id, hdr); down_prot.down(new Event(Event.MSG, suspect_msg)); if(log.isTraceEnabled()) log.trace("task done"); } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FILE_PING.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FILE_PING.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FILE_PING.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FILE_PING.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,266 @@ +package org.jgroups.protocols; + +import org.jgroups.*; +import org.jgroups.annotations.Experimental; +import org.jgroups.annotations.Property; +import org.jgroups.util.Promise; +import org.jgroups.util.UUID; +import org.jgroups.util.Util; + +import java.io.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + + +/** + * Simple discovery protocol which uses a file on shared storage such as an SMB share, NFS mount or S3. The local + * address information, e.g. UUID and physical addresses mappings are written to the file and the content is read and + * added to our transport's UUID-PhysicalAddress cache.

      + * The design is at doc/design/FILE_PING.txt + * @author Bela Ban + */ +@Experimental +public class FILE_PING extends Discovery { + protected static final String SUFFIX=".node"; + + /* ----------------------------------------- Properties -------------------------------------------------- */ + + + @Property(description="The absolute path of the shared file") + protected String location=File.separator + "tmp" + File.separator + "jgroups"; + + @Property(description="Interval (in milliseconds) at which the own Address is written. 0 disables it.") + protected long interval=60000; + + + /* --------------------------------------------- Fields ------------------------------------------------------ */ + protected File root_dir=null; + protected FilenameFilter filter; + private Future writer_future; + + + public void init() throws Exception { + super.init(); + createRootDir(); + } + + public void start() throws Exception { + super.start(); + if(interval > 0) + writer_future=timer.scheduleWithFixedDelay(new WriterTask(), interval, interval, TimeUnit.MILLISECONDS); + } + + public void stop() { + if(writer_future != null) { + writer_future.cancel(false); + writer_future=null; + } + super.stop(); + } + + public boolean isDynamic() { + return true; + } + + public void sendGetMembersRequest(String cluster_name, Promise promise, ViewId view_id) throws Exception{ + List existing_mbrs=readAll(cluster_name); + PhysicalAddress physical_addr=(PhysicalAddress)down(new Event(Event.GET_PHYSICAL_ADDRESS, local_addr)); + List physical_addrs=Arrays.asList(physical_addr); + PingData data=new PingData(local_addr, null, false, UUID.get(local_addr), physical_addrs); + + // If we don't find any files, return immediately + if(existing_mbrs.isEmpty()) { + if(promise != null) { + promise.setResult(null); + } + } + else { + + // 1. Send GET_MBRS_REQ message to members listed in the file + for(PingData tmp: existing_mbrs) { + Collection dests=tmp != null? tmp.getPhysicalAddrs() : null; + if(dests == null) + continue; + for(final PhysicalAddress dest: dests) { + if(dest == null || dest.equals(physical_addr)) + continue; + PingHeader hdr=new PingHeader(PingHeader.GET_MBRS_REQ, data, cluster_name); + hdr.view_id=view_id; + final Message msg=new Message(dest); + msg.setFlag(Message.OOB); + msg.putHeader(this.id, hdr); // needs to be getName(), so we might get "MPING" ! + // down_prot.down(new Event(Event.MSG, msg)); + if(log.isTraceEnabled()) + log.trace("[FIND_INITIAL_MBRS] sending PING request to " + msg.getDest()); + timer.execute(new Runnable() { + public void run() { + try { + down_prot.down(new Event(Event.MSG, msg)); + } + catch(Exception ex){ + if(log.isErrorEnabled()) + log.error("failed sending discovery request to " + dest, ex); + } + } + }); + } + } + } + + // Write my own data to file + writeToFile(data, cluster_name); + } + + + public Object down(Event evt) { + Object retval=super.down(evt); + if(evt.getType() == Event.VIEW_CHANGE) + handleView((View)evt.getArg()); + return retval; + } + + protected void createRootDir() { + root_dir=new File(location); + if(root_dir.exists()) { + if(!root_dir.isDirectory()) + throw new IllegalArgumentException("location " + root_dir.getPath() + " is not a directory"); + } + else { + root_dir.mkdirs(); + } + if(!root_dir.exists()) + throw new IllegalArgumentException("location " + root_dir.getPath() + " could not be accessed"); + + filter=new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.endsWith(SUFFIX); + } + }; + } + + // remove all files which are not from the current members + protected void handleView(View view) { + Collection

      mbrs=view.getMembers(); + boolean is_coordinator=!mbrs.isEmpty() && mbrs.iterator().next().equals(local_addr); + if(is_coordinator) { + List data=readAll(group_addr); + for(PingData entry: data) { + Address addr=entry.getAddress(); + if(addr != null && !mbrs.contains(addr)) { + remove(group_addr, addr); + } + } + } + } + + protected void remove(String clustername, Address addr) { + if(clustername == null || addr == null) + return; + + File dir=new File(root_dir, clustername); + if(!dir.exists()) + return; + + try { + String filename=addr instanceof UUID? ((UUID)addr).toStringLong() : addr.toString(); + File file=new File(dir, filename + SUFFIX); + if(log.isTraceEnabled()) + log.trace("removing " + file); + file.delete(); + } + catch(Throwable e) { + log.error("failure removing data", e); + } + } + + /** + * Reads all information from the given directory under clustername + * @return + */ + protected List readAll(String clustername) { + List retval=new ArrayList(); + File dir=new File(root_dir, clustername); + if(!dir.exists()) + dir.mkdir(); + + File[] files=dir.listFiles(filter); + if(files != null) { + for(File file: files) { + PingData data=readFile(file); + if(data == null) { + log.warn("failed reading " + file.getName() + ": removing it"); + file.delete(); + } + else + retval.add(data); + } + } + return retval; + } + + protected static PingData readFile(File file) { + PingData retval=null; + DataInputStream in=null; + + try { + in=new DataInputStream(new FileInputStream(file)); + PingData tmp=new PingData(); + tmp.readFrom(in); + return tmp; + } + catch(Exception e) { + } + finally { + Util.close(in); + } + return retval; + } + + protected void writeToFile(PingData data, String clustername) { + DataOutputStream out=null; + File dir=new File(root_dir, clustername); + if(!dir.exists()) + dir.mkdir(); + + String filename=addressAsString(local_addr); + File file=new File(dir, filename + SUFFIX); + file.deleteOnExit(); + + try { + out=new DataOutputStream(new FileOutputStream(file)); + data.writeTo(out); + } + catch(Exception e) { + } + finally { + Util.close(out); + } + } + + + protected class WriterTask implements Runnable { + public void run() { + PhysicalAddress physical_addr=(PhysicalAddress)down(new Event(Event.GET_PHYSICAL_ADDRESS, local_addr)); + List physical_addrs=Arrays.asList(physical_addr); + PingData data=new PingData(local_addr, null, false, UUID.get(local_addr), physical_addrs); + writeToFile(data, group_addr); + } + } + + protected String addressAsString(Address address) { + if (address == null) { + return ""; + } else if (address instanceof UUID) { + return ((UUID) address).toStringLong(); + } else { + return address.toString(); + } + } + + + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FlowControl.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FlowControl.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FlowControl.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FlowControl.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,618 @@ +package org.jgroups.protocols; + +import org.jgroups.Address; +import org.jgroups.Event; +import org.jgroups.Message; +import org.jgroups.View; +import org.jgroups.annotations.MBean; +import org.jgroups.annotations.ManagedAttribute; +import org.jgroups.annotations.ManagedOperation; +import org.jgroups.annotations.Property; +import org.jgroups.stack.Protocol; +import org.jgroups.util.Util; + +import java.util.*; + + +/** + * Simple flow control protocol based on a credit system. Each sender has a number of credits (bytes + * to send). When the credits have been exhausted, the sender blocks. Each receiver also keeps track of + * how many credits it has received from a sender. When credits for a sender fall below a threshold, + * the receiver sends more credits to the sender. + * + * @author Bela Ban + */ +@MBean(description="Simple flow control protocol based on a credit system") +public abstract class FlowControl extends Protocol { + + protected final static FcHeader REPLENISH_HDR=new FcHeader(FcHeader.REPLENISH); + protected final static FcHeader CREDIT_REQUEST_HDR=new FcHeader(FcHeader.CREDIT_REQUEST); + + + /* ----------------------------------------- Properties -------------------------------------------------- */ + + /** + * Max number of bytes to send per receiver until an ack must be received before continuing sending + */ + @Property(description="Max number of bytes to send per receiver until an ack must be received to proceed") + protected long max_credits=500000; + + /** + * Max time (in milliseconds) to block. If credit hasn't been received after max_block_time, we send + * a REPLENISHMENT request to the members from which we expect credits. A value <= 0 means to wait forever. + */ + @Property(description="Max time (in milliseconds) to block. Default is 5000 msec") + protected long max_block_time=5000; + + /** + * Defines the max number of milliseconds for a message to block before being sent, based on the length of + * the message. The property is defined as a comma-separated list of values (separated by ':'), where the key + * is the size in bytes and the value is the number of milliseconds to block. + * Example: max_block_times="50:1,500:3,1500:5,10000:10,100000:100". This means that messages up to 50 bytes wait + * 1 ms max until they get sent, messages up to 500 bytes 3 ms, and so on. + * If a message's length (size of the payload in bytes) is for example 15'000 bytes, + * FlowControl blocks it for a max of 100 ms. + */ + protected Map max_block_times=null; + + + /** + * If we're down to (min_threshold * max_credits) bytes for P, we send more credits to P. Example: if + * max_credits is 1'000'000, and min_threshold 0.25, then we send ca. 250'000 credits to P once we've got only + * 250'000 credits left for P (we've received 750'000 bytes from P). + */ + @Property(description="The threshold (as a percentage of max_credits) at which a receiver sends more credits to " + + "a sender. Example: if max_credits is 1'000'000, and min_threshold 0.25, then we send ca. 250'000 credits " + + "to P once we've got only 250'000 credits left for P (we've received 750'000 bytes from P)") + protected double min_threshold=0.40; + + /** + * Computed as max_credits times min_theshold. If explicitly set, this will + * override the above computation + */ + @Property(description="Computed as max_credits x min_theshold unless explicitly set") + protected long min_credits=0; + + /** + * Whether an up thread that comes back down should be allowed to bypass blocking if all credits are exhausted. + * Avoids JGRP-465. Set to false by default in 2.5 because we have OOB messages for credit replenishments - + * this flag should not be set to true if the concurrent stack is used + */ + @Property(description="Does not block a down message if it is a result of handling an up message in the" + + "same thread. Fixes JGRP-928") + protected boolean ignore_synchronous_response=true; + + + + + /* --------------------------------------------- JMX ------------------------------------------------------ */ + protected int num_credit_requests_received=0, num_credit_requests_sent=0; + protected int num_credit_responses_sent=0, num_credit_responses_received=0; + + + /* --------------------------------------------- Fields ------------------------------------------------------ */ + + + /** + * Keeps track of credits per member at the receiver. For each message, the credits for the sender are decremented + * by the size of the received message. When the credits fall below the threshold, we refill and send a REPLENISH + * message to the sender. + */ + protected final Map received=Util.createConcurrentMap(); + + + /** Whether FlowControl is still running, this is set to false when the protocol terminates (on stop()) */ + protected volatile boolean running=true; + + + protected boolean frag_size_received=false; + + + + + + /** + * Thread that carries messages through up() and shouldn't be blocked + * in down() if ignore_synchronous_response==true. JGRP-465. + */ + protected final ThreadLocal ignore_thread=new ThreadLocal() { + protected Boolean initialValue() { + return false; + } + }; + + + public void resetStats() { + super.resetStats(); + num_credit_responses_sent=num_credit_responses_received=num_credit_requests_received=num_credit_requests_sent=0; + } + + public long getMaxCredits() { + return max_credits; + } + + public void setMaxCredits(long max_credits) { + this.max_credits=max_credits; + } + + public double getMinThreshold() { + return min_threshold; + } + + public void setMinThreshold(double min_threshold) { + this.min_threshold=min_threshold; + } + + public long getMinCredits() { + return min_credits; + } + + public void setMinCredits(long min_credits) { + this.min_credits=min_credits; + } + + public abstract int getNumberOfBlockings(); + + public long getMaxBlockTime() { + return max_block_time; + } + + public void setMaxBlockTime(long t) { + max_block_time=t; + } + + @Property(description="Max times to block for the listed messages sizes (Message.getLength()). Example: \"1000:10,5000:30,10000:500\"") + public void setMaxBlockTimes(String str) { + if(str == null) return; + Long prev_key=null, prev_val=null; + List vals=Util.parseCommaDelimitedStrings(str); + if(max_block_times == null) + max_block_times=new TreeMap(); + for(String tmp: vals) { + int index=tmp.indexOf(':'); + if(index == -1) + throw new IllegalArgumentException("element '" + tmp + "' is missing a ':' separator"); + Long key=Long.parseLong(tmp.substring(0, index).trim()); + Long val=Long.parseLong(tmp.substring(index +1).trim()); + + // sanity checks: + if(key < 0 || val < 0) + throw new IllegalArgumentException("keys and values must be >= 0"); + + if(prev_key != null) { + if(key <= prev_key) + throw new IllegalArgumentException("keys are not sorted: " + vals); + } + prev_key=key; + + if(prev_val != null) { + if(val <= prev_val) + throw new IllegalArgumentException("values are not sorted: " + vals); + } + prev_val=val; + max_block_times.put(key, val); + } + if(log.isDebugEnabled()) + log.debug("max_block_times: " + max_block_times); + } + + public String getMaxBlockTimes() { + if(max_block_times == null) return "n/a"; + StringBuilder sb=new StringBuilder(); + boolean first=true; + for(Map.Entry entry: max_block_times.entrySet()) { + if(!first) { + sb.append(", "); + } + else { + first=false; + } + sb.append(entry.getKey()).append(":").append(entry.getValue()); + } + return sb.toString(); + } + + + public abstract long getTotalTimeBlocked(); + + @ManagedAttribute(description="Average time spent in a flow control block") + public double getAverageTimeBlocked() { + long number_of_blockings=getNumberOfBlockings(); + return number_of_blockings == 0? 0.0 : getTotalTimeBlocked() / (double)number_of_blockings; + } + + @ManagedAttribute(description="Number of credit requests received") + public int getNumberOfCreditRequestsReceived() { + return num_credit_requests_received; + } + + @ManagedAttribute(description="Number of credit requests sent") + public int getNumberOfCreditRequestsSent() { + return num_credit_requests_sent; + } + + @ManagedAttribute(description="Number of credit responses received") + public int getNumberOfCreditResponsesReceived() { + return num_credit_responses_received; + } + + @ManagedAttribute(description="Number of credit responses sent") + public int getNumberOfCreditResponsesSent() { + return num_credit_responses_sent; + } + + public abstract String printSenderCredits(); + + @ManagedOperation(description="Print receiver credits") + public String printReceiverCredits() { + return printMap(received); + } + + + public String printCredits() { + StringBuilder sb=new StringBuilder(); + sb.append("receivers:\n").append(printMap(received)); + return sb.toString(); + } + + public Map dumpStats() { + Map retval=super.dumpStats(); + retval.put("receivers", printMap(received)); + return retval; + } + + + protected long getMaxBlockTime(long length) { + if(max_block_times == null) + return 0; + Long retval=null; + for(Map.Entry entry: max_block_times.entrySet()) { + retval=entry.getValue(); + if(length <= entry.getKey()) + break; + } + return retval != null? retval : 0; + } + + + /** + * Whether the protocol handles message with dest == null || dest.isMulticastAddress() + * @return + */ + protected abstract boolean handleMulticastMessage(); + + protected abstract void handleCredit(Address sender, long increase); + + + /** + * Allows to unblock all blocked senders from an external program, e.g. JMX + */ + @ManagedOperation(description="Unblocks all senders") + public void unblock() { + ; + } + + public void init() throws Exception { + boolean min_credits_set = min_credits != 0; + if(!min_credits_set) + min_credits=(long)(max_credits * min_threshold); + } + + public void start() throws Exception { + super.start(); + if(!frag_size_received) { + log.warn("No fragmentation protocol was found. When flow control is used, we recommend " + + "a fragmentation protocol, due to http://jira.jboss.com/jira/browse/JGRP-590"); + } + running=true; + } + + public void stop() { + super.stop(); + running=false; + ignore_thread.set(false); + } + + + @SuppressWarnings("unchecked") + public Object down(Event evt) { + switch(evt.getType()) { + case Event.MSG: + Message msg=(Message)evt.getArg(); + if(msg.isFlagSet(Message.NO_FC)) + break; + + Address dest=msg.getDest(); + boolean multicast=dest == null || dest.isMulticastAddress(); + boolean handle_multicasts=handleMulticastMessage(); + boolean process=(handle_multicasts && multicast) || (!handle_multicasts && !multicast); + if(!process) + break; + + int length=msg.getLength(); + if(length == 0) + break; + + if(ignore_synchronous_response && ignore_thread.get()) { // JGRP-465 + if(log.isTraceEnabled()) + log.trace("bypassing flow control because of synchronous response " + Thread.currentThread()); + break; + } + return handleDownMessage(evt, msg, dest, length); + + case Event.CONFIG: + handleConfigEvent((Map)evt.getArg()); + break; + + case Event.VIEW_CHANGE: + handleViewChange(((View)evt.getArg()).getMembers()); + break; + } + return down_prot.down(evt); // this could potentially use the lower protocol's thread which may block + } + + + @SuppressWarnings("unchecked") + public Object up(Event evt) { + switch(evt.getType()) { + + case Event.MSG: + Message msg=(Message)evt.getArg(); + if(msg.isFlagSet(Message.NO_FC)) + break; + + Address dest=msg.getDest(); + boolean multicast=dest == null || dest.isMulticastAddress(); + boolean handle_multicasts=handleMulticastMessage(); + FcHeader hdr=(FcHeader)msg.getHeader(this.id); + boolean process=(handle_multicasts && multicast) || (!handle_multicasts && !multicast) || hdr != null; + if(!process) + break; + + if(hdr != null) { + switch(hdr.type) { + case FcHeader.REPLENISH: + num_credit_responses_received++; + handleCredit(msg.getSrc(), (Long)msg.getObject()); + break; + case FcHeader.CREDIT_REQUEST: + num_credit_requests_received++; + Address sender=msg.getSrc(); + Long requested_credits=(Long)msg.getObject(); + if(requested_credits != null) + handleCreditRequest(received, sender, requested_credits.longValue()); + break; + default: + log.error("header type " + hdr.type + " not known"); + break; + } + return null; // don't pass message up + } + + Address sender=msg.getSrc(); + long new_credits=adjustCredit(received, sender, msg.getLength()); + + // JGRP-928: changed ignore_thread to a ThreadLocal: multiple threads can access it with the + // introduction of the concurrent stack + if(ignore_synchronous_response) + ignore_thread.set(true); + try { + return up_prot.up(evt); + } + finally { + if(ignore_synchronous_response) + ignore_thread.set(false); // need to revert because the thread is placed back into the pool + if(new_credits > 0) + sendCredit(sender, new_credits); + } + + case Event.VIEW_CHANGE: + handleViewChange(((View)evt.getArg()).getMembers()); + break; + + case Event.CONFIG: + Map map=(Map)evt.getArg(); + handleConfigEvent(map); + break; + } + return up_prot.up(evt); + } + + + protected void handleConfigEvent(Map info) { + if(info != null) { + Integer frag_size=(Integer)info.get("frag_size"); + if(frag_size != null) { + if(frag_size > max_credits) { + log.warn("The fragmentation size of the fragmentation protocol is " + frag_size + + ", which is greater than the max credits. While this is not incorrect, " + + "it may cause blockings. Frag size should be less than max_credits " + + "(http://jira.jboss.com/jira/browse/JGRP-590)"); + } + frag_size_received=true; + } + } + } + + + protected abstract Object handleDownMessage(final Event evt, final Message msg, Address dest, int length); + + + + /** + * Check whether sender has enough credits left. If not, send it some more + * @param map The hashmap to use + * @param sender The address of the sender + * @param length The number of bytes received by this message. We don't care about the size of the headers for + * the purpose of flow control + * @return long Number of credits to be sent. Greater than 0 if credits needs to be sent, 0 otherwise + */ + protected long adjustCredit(Map map, Address sender, int length) { + Credit cred; + if(sender == null || length == 0 || (cred=map.get(sender)) == null) + return 0; + if(log.isTraceEnabled()) + log.trace(sender + " used " + length + " credits, " + (cred.get() - length) + " remaining"); + return cred.decrementAndGet(length); + } + + /** + * @param map The map to modify + * @param sender The sender who requests credits + * @param requested_credits Number of bytes that the sender has left to send messages to us + */ + protected void handleCreditRequest(Map map, Address sender, long requested_credits) { + if(requested_credits > 0 && sender != null) { + Credit cred=map.get(sender); + if(cred == null) + return; + if(log.isTraceEnabled()) + log.trace("received credit request from " + sender + ": sending " + requested_credits + " credits"); + cred.increment(requested_credits); + sendCredit(sender, requested_credits); + } + } + + + protected void sendCredit(Address dest, long credits) { + if(log.isTraceEnabled()) + if(log.isTraceEnabled()) log.trace("sending " + credits + " credits to " + dest); + Message msg=new Message(dest, null, new Long(credits)); + msg.setFlag(Message.OOB); + msg.putHeader(this.id, REPLENISH_HDR); + down_prot.down(new Event(Event.MSG, msg)); + num_credit_responses_sent++; + } + + /** + * We cannot send this request as OOB messages, as the credit request needs to queue up behind the regular messages; + * if a receiver cannot process the regular messages, that is a sign that the sender should be throttled ! + * @param dest The member to which we send the credit request + * @param credits_needed The number of bytes (of credits) left for dest + */ + protected void sendCreditRequest(final Address dest, Long credits_needed) { + if(log.isTraceEnabled()) + log.trace("sending request for " + credits_needed + " credits to " + dest); + Message msg=new Message(dest, null, credits_needed); + msg.putHeader(this.id, CREDIT_REQUEST_HDR); + down_prot.down(new Event(Event.MSG, msg)); + num_credit_requests_sent++; + } + + + protected void handleViewChange(Vector
      mbrs) { + if(mbrs == null) return; + if(log.isTraceEnabled()) log.trace("new membership: " + mbrs); + + // add members not in membership to received and sent hashmap (with full credits) + for(Address addr: mbrs) { + if(!received.containsKey(addr)) + received.put(addr, new Credit(max_credits)); + } + // remove members that left + for(Iterator
      it=received.keySet().iterator(); it.hasNext();) { + Address addr=it.next(); + if(!mbrs.contains(addr)) + it.remove(); + } + } + + + + protected static String printMap(Map m) { + StringBuilder sb=new StringBuilder(); + for(Map.Entry entry: m.entrySet()) { + sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); + } + return sb.toString(); + } + + + + protected class Credit { + protected long credits_left; + protected int num_blockings=0; + protected long total_blocking_time=0; + protected long last_credit_request=0; + + + protected Credit(long credits) { + this.credits_left=credits; + } + + + protected synchronized boolean decrementIfEnoughCredits(long credits, long timeout) { + if(decrement(credits)) + return true; + + if(timeout <= 0) + return false; + + long start=System.currentTimeMillis(); + try { + this.wait(timeout); + } + catch(InterruptedException e) { + } + finally { + total_blocking_time+=System.currentTimeMillis() - start; + num_blockings++; + } + + return decrement(credits); + } + + + protected boolean decrement(long credits) { + if(credits <= credits_left) { + credits_left-=credits; + return true; + } + return false; + } + + + protected synchronized long decrementAndGet(long credits) { + credits_left=Math.max(0, credits_left - credits); + if(credits_left <= min_credits) { + long credit_response=Math.min(max_credits, max_credits - credits_left); + credits_left=max_credits; + return credit_response; + } + return 0; + } + + + protected synchronized void increment(long credits) { + credits_left=Math.min(max_credits, credits_left + credits); + notifyAll(); + } + + protected synchronized boolean needToSendCreditRequest() { + long current_time=System.currentTimeMillis(); + if(current_time - last_credit_request >= max_block_time) { + last_credit_request=current_time; + return true; + } + return false; + } + + protected int getNumBlockings() {return num_blockings;} + + protected long getTotalBlockingTime() {return total_blocking_time;} + + protected synchronized long get() {return credits_left;} + + protected synchronized void set(long new_credits) { + credits_left=Math.min(max_credits, new_credits); + notifyAll(); + } + + public String toString() { + return String.valueOf(credits_left); + } + + } + + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FRAG2.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FRAG2.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FRAG2.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FRAG2.java 2011-10-18 11:22:35.000000000 +0000 @@ -9,12 +9,14 @@ import org.jgroups.util.Range; import org.jgroups.util.Util; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Vector; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import java.util.concurrent.atomic.AtomicLong; /** @@ -37,31 +39,24 @@ * message, so we add a constant (200 bytes). * * @author Bela Ban - * @version $Id: FRAG2.java,v 1.47 2008/10/20 10:20:12 belaban Exp $ */ @MBean(description="Fragments messages larger than fragmentation size into smaller packets") +@DeprecatedProperty(names={"overhead"}) public class FRAG2 extends Protocol { - private static final String name="FRAG2"; /* ----------------------------------------- Properties -------------------------------------------------- */ - @Property(description="The max number of bytes in a message. Larger messages will be fragmented. Default is 1500 bytes") - @ManagedAttribute(description="Fragmentation size", writable=true) + @Property(description="The max number of bytes in a message. Larger messages will be fragmented") int frag_size=1500; - @Property(description="Estimate for message overhead. Default is 200 bytes ") - @ManagedAttribute(description="Estimate number of bytes for headers plus src and dest ", writable=true) - int overhead=200; - - /* --------------------------------------------- Fields ------------------------------------------------------ */ /*the fragmentation list contains a fragmentation table per sender *this way it becomes easier to clean up if a sender (member) leaves or crashes */ - private final ConcurrentMap> fragment_list=new ConcurrentHashMap>(11); + private final ConcurrentMap> fragment_list=Util.createConcurrentMap(11); /** Used to assign fragmentation-specific sequence IDs (monotonically increasing) */ private int curr_id=1; @@ -77,15 +72,12 @@ @ManagedAttribute(description="Number of received fragments") AtomicLong num_received_frags=new AtomicLong(0); - - public final String getName() { - return name; - } - public int getFragSize() {return frag_size;} public void setFragSize(int s) {frag_size=s;} - public int getOverhead() {return overhead;} - public void setOverhead(int o) {overhead=o;} + /** @deprecated overhead was removed in 2.6.10 */ + public int getOverhead() {return 0;} + /** @deprecated overhead was removed in 2.6.10 */ + public void setOverhead(int o) {} public long getNumberOfSentMessages() {return num_sent_msgs.get();} public long getNumberOfSentFragments() {return num_sent_frags.get();} public long getNumberOfReceivedMessages() {return num_received_msgs.get();} @@ -100,12 +92,17 @@ super.init(); int old_frag_size=frag_size; - frag_size-=overhead; - if(frag_size <=0) { - throw new Exception("frag_size=" + old_frag_size + ", overhead=" + overhead + - ", new frag_size=" + frag_size + ": new frag_size is invalid"); - } - + if(frag_size <=0) + throw new Exception("frag_size=" + old_frag_size + ", new frag_size=" + frag_size + ": new frag_size is invalid"); + + TP transport=getTransport(); + if(transport != null && transport.isEnableBundling()) { + int max_bundle_size=transport.getMaxBundleSize(); + if(frag_size >= max_bundle_size) + throw new IllegalArgumentException("frag_size (" + frag_size + ") has to be < TP.max_bundle_size (" + + max_bundle_size + ")"); + } + Map info=new HashMap(1); info.put("frag_size", frag_size); up_prot.up(new Event(Event.CONFIG, info)); @@ -168,7 +165,7 @@ case Event.MSG: Message msg=(Message)evt.getArg(); - FragHeader hdr=(FragHeader)msg.getHeader(name); + FragHeader hdr=(FragHeader)msg.getHeader(this.id); if(hdr != null) { // needs to be defragmented unfragment(msg, hdr); // Unfragment and possibly pass up return null; @@ -247,13 +244,14 @@ log.trace(sb.toString()); } - long id=getNextId(); // used as a seqno + long frag_id=getNextId(); // used as a seqno for(int i=0; i < fragments.size(); i++) { Range r=fragments.get(i); - Message frag_msg=msg.copy(false); // don't copy the buffer, only src, dest and headers. But do copy the headers + // don't copy the buffer, only src, dest and headers. Only copy the headers one time ! + Message frag_msg=msg.copy(false, i == 0); frag_msg.setBuffer(buffer, (int)r.low, (int)r.high); - FragHeader hdr=new FragHeader(id, i, num_frags); - frag_msg.putHeader(name, hdr); + FragHeader hdr=new FragHeader(frag_id, i, num_frags); + frag_msg.putHeader(this.id, hdr); down_prot.down(new Event(Event.MSG, frag_msg)); } } @@ -276,7 +274,7 @@ ConcurrentMap frag_table=fragment_list.get(sender); if(frag_table == null) { - frag_table=new ConcurrentHashMap(); + frag_table=Util.createConcurrentMap(16, .075f, 16); ConcurrentMap tmp=fragment_list.putIfAbsent(sender, frag_table); if(tmp != null) // value was already present frag_table=tmp; @@ -387,8 +385,8 @@ return false; } /*then double check just in case*/ - for(int i=0; i < fragments.length; i++) { - if(fragments[i] == null) + for(Message msg: fragments) { + if(msg == null) return false; } /*all fragmentations have been received*/ @@ -409,12 +407,11 @@ int combined_length=0, length, offset; int index=0; - for(Message fragment: fragments) { + for(Message fragment: fragments) combined_length+=fragment.getLength(); - } combined_buffer=new byte[combined_length]; - retval=fragments[0].copy(false); + retval=fragments[0].copy(false); // doesn't copy the payload, but copies the headers for(int i=0; i < fragments.length; i++) { Message fragment=fragments[i]; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FragHeader.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FragHeader.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FragHeader.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FragHeader.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,16 +1,14 @@ package org.jgroups.protocols; -import org.jgroups.Header; import org.jgroups.Global; -import org.jgroups.util.Streamable; +import org.jgroups.Header; import java.io.*; /** * @author Bela Ban - * @version $Id: FragHeader.java,v 1.3 2007/05/01 10:55:10 belaban Exp $ */ -public class FragHeader extends Header implements Streamable { +public class FragHeader extends Header { public long id=0; public int frag_id=0; public int num_frags=0; @@ -29,19 +27,6 @@ return "[id=" + id + ", frag_id=" + frag_id + ", num_frags=" + num_frags + ']'; } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeLong(id); - out.writeInt(frag_id); - out.writeInt(num_frags); - } - - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - id=in.readLong(); - frag_id=in.readInt(); - num_frags=in.readInt(); - } - public void writeTo(DataOutputStream out) throws IOException { out.writeLong(id); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FRAG.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FRAG.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/FRAG.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/FRAG.java 2011-10-18 11:22:35.000000000 +0000 @@ -10,21 +10,27 @@ import org.jgroups.annotations.Property; import org.jgroups.stack.Protocol; import org.jgroups.util.ExposedByteArrayOutputStream; +import org.jgroups.util.ExposedDataOutputStream; import org.jgroups.util.Util; +import org.jgroups.util.ExposedByteArrayInputStream; import java.io.ByteArrayInputStream; import java.io.DataInputStream; -import java.io.DataOutputStream; import java.util.*; import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; /** * Fragmentation layer. Fragments messages larger than FRAG_SIZE into smaller * packets. Reassembles fragmented packets into bigger ones. The fragmentation - * number is prepended to the messages as a header (and removed at the receiving - * side). + * number is added to the messages as a header (and removed at the receiving side). + *

      + * Contrary to {@link org.jgroups.protocols.FRAG2}, FRAG marshals the entire message (including the headers) into + * a byte[] buffer and the fragments that buffer. Because {@link org.jgroups.Message#size()} is called rather than + * {@link org.jgroups.Message#getLength()}, and because of the overhead of marshalling, this will be slower than + * FRAG2. *

      * Each fragment is identified by (a) the sender (part of the message to which * the header is appended), (b) the fragmentation ID (which is unique per FRAG @@ -36,34 +42,29 @@ * * @author Bela Ban * @author Filip Hanik - * @version $Id: FRAG.java,v 1.46 2008/07/22 14:07:45 belaban Exp $ */ @MBean(description="Fragments messages larger than fragmentation size into smaller packets") public class FRAG extends Protocol { - private final static String name="FRAG"; - /* ----------------------------------------- Properties -------------------------------------------------- */ @Property(description="The max number of bytes in a message. Larger messages will be fragmented. Default is 8192 bytes") - @ManagedAttribute(description="Fragmentation size", writable=true) private int frag_size=8192; // conservative value - @Property(description="The max size in bytes for the byte array output buffer") - @ManagedAttribute(description="The max size in bytes the byte array output buffer should be, " + "otherwise we keep memory occupied unnecessarily", writable=true) + @Property(description="The max size in bytes for the byte array output buffer", + deprecatedMessage="not used anymore") + @Deprecated private int max_retained_buffer=70000; /* --------------------------------------------- Fields ------------------------------------------------------ */ - /*the fragmentation list contains a fragmentation table per sender - *this way it becomes easier to clean up if a sender (member) leaves or crashes - */ - private final FragmentationList fragment_list=new FragmentationList(); - private int curr_id=1; - private final ExposedByteArrayOutputStream bos=new ExposedByteArrayOutputStream(1024); - private final Vector

      members=new Vector
      (11); + /** Contains a frag table per sender, this way it becomes easier to clean up if a sender leaves or crashes */ + private final FragmentationList fragment_list=new FragmentationList(); + + private AtomicInteger curr_id=new AtomicInteger(1); + private final Vector
      members=new Vector
      (11); @@ -78,10 +79,6 @@ long num_received_frags=0; - public String getName() { - return name; - } - public int getFragSize() {return frag_size;} public void setFragSize(int s) {frag_size=s;} public long getNumberOfSentMessages() {return num_sent_msgs;} @@ -121,7 +118,7 @@ sb.append(size).append(", will fragment (frag_size=").append(frag_size).append(')'); log.trace(sb.toString()); } - fragment(msg); // Fragment and pass down + fragment(msg, size); // Fragment and pass down return null; } break; @@ -149,7 +146,7 @@ case Event.MSG: Message msg=(Message)evt.getArg(); - FragHeader hdr=(FragHeader)msg.getHeader(name); + FragHeader hdr=(FragHeader)msg.getHeader(this.id); if(hdr != null) { // needs to be defragmented unfragment(msg, hdr); // Unfragment and possibly pass up return null; @@ -181,8 +178,8 @@ members.addAll(new_mbrs); for(Address mbr: left_mbrs){ - //the new view doesn't contain the sender, he must have left, - //hence we will clear all his fragmentation tables + // the new view doesn't contain the sender, it must have left, + // hence we will clear all of itsfragmentation tables fragment_list.remove(mbr); if(log.isTraceEnabled()) log.trace("[VIEW_CHANGE] removed " + mbr + " from fragmentation table"); @@ -201,29 +198,18 @@ * [2344,3,2]{dst,src,buf3} * */ - private void fragment(Message msg) { - DataOutputStream out=null; - byte[] buffer; - byte[] fragments[]; - Event evt; - FragHeader hdr; - Message frag_msg; + private void fragment(Message msg, long size) { Address dest=msg.getDest(), src=msg.getSrc(); - long id=curr_id++; // used as seqnos + long frag_id=curr_id.getAndIncrement(); // used as seqnos int num_frags; try { - // Write message into a byte buffer and fragment it - // Synchronization around bos is needed for concurrent access (http://jira.jboss.com/jira/browse/JGRP-215) - synchronized(bos) { - bos.reset(this.max_retained_buffer); - out=new DataOutputStream(bos); - msg.writeTo(out); - out.flush(); - buffer=bos.getRawBuffer(); - fragments=Util.fragmentBuffer(buffer, frag_size, bos.size()); - } - + // write message into a byte buffer and fragment it + ExposedByteArrayOutputStream out_stream=new ExposedByteArrayOutputStream((int)(size + 50)); + ExposedDataOutputStream dos=new ExposedDataOutputStream(out_stream); + msg.writeTo(dos); + byte[] buffer=out_stream.getRawBuffer(); + byte[][] fragments=Util.fragmentBuffer(buffer, frag_size, dos.size()); num_frags=fragments.length; num_sent_frags+=num_frags; @@ -236,19 +222,16 @@ } for(int i=0; i < num_frags; i++) { - frag_msg=new Message(dest, src, fragments[i]); - hdr=new FragHeader(id, i, num_frags); - frag_msg.putHeader(name, hdr); - evt=new Event(Event.MSG, frag_msg); + Message frag_msg=new Message(dest, src, fragments[i]); + FragHeader hdr=new FragHeader(frag_id, i, num_frags); + frag_msg.putHeader(this.id, hdr); + Event evt=new Event(Event.MSG, frag_msg); down_prot.down(evt); } } catch(Exception e) { log.error("exception occurred trying to fragment message", e); } - finally { - Util.close(out); - } } @@ -260,14 +243,8 @@ * 5. Pass msg up the stack */ private void unfragment(Message msg, FragHeader hdr) { - FragmentationTable frag_table; - Address sender=msg.getSrc(); - Message assembled_msg; - byte[] m; - ByteArrayInputStream bis; - DataInputStream in=null; - - frag_table=fragment_list.get(sender); + Address sender=msg.getSrc(); + FragmentationTable frag_table=fragment_list.get(sender); if(frag_table == null) { frag_table=new FragmentationTable(sender); try { @@ -278,15 +255,16 @@ } } num_received_frags++; - m=frag_table.add(hdr.id, hdr.frag_id, hdr.num_frags, msg.getBuffer()); - if(m != null) { + byte[] buf=frag_table.add(hdr.id, hdr.frag_id, hdr.num_frags, msg.getBuffer()); + if(buf != null) { + DataInputStream in=null; try { - bis=new ByteArrayInputStream(m); + ByteArrayInputStream bis=new ExposedByteArrayInputStream(buf); in=new DataInputStream(bis); - assembled_msg=new Message(false); + Message assembled_msg=new Message(false); assembled_msg.readFrom(in); - if(log.isTraceEnabled()) log.trace("assembled_msg is " + assembled_msg); assembled_msg.setSrc(sender); // needed ? YES, because fragments have a null src !! + if(log.isTraceEnabled()) log.trace("assembled_msg is " + assembled_msg); num_received_msgs++; up_prot.up(new Event(Event.MSG, assembled_msg)); } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/HDRS.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/HDRS.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/HDRS.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/HDRS.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,10 +1,10 @@ -// $Id: HDRS.java,v 1.7 2008/10/21 12:10:30 vlada Exp $ package org.jgroups.protocols; import org.jgroups.Event; import org.jgroups.Message; import org.jgroups.Header; +import org.jgroups.conf.ClassConfigurator; import org.jgroups.annotations.Unsupported; import org.jgroups.stack.Protocol; @@ -16,22 +16,22 @@ */ @Unsupported public class HDRS extends Protocol { - public String getName() {return "HDRS";} - private static void printMessage(Message msg, String label) { StringBuilder sb=new StringBuilder(); sb.append(label).append(":\n"); - Map hdrs=msg.getHeaders(); + Map hdrs=msg.getHeaders(); sb.append(print(msg, hdrs)); System.out.println(sb); } - private static String print(Message msg, Map hdrs) { + private static String print(Message msg, Map hdrs) { StringBuilder sb=new StringBuilder(); int hdrs_size=0; - for(Map.Entry entry: hdrs.entrySet()) { - String name=entry.getKey(); + for(Map.Entry entry: hdrs.entrySet()) { + + Class clazz=ClassConfigurator.getProtocol(entry.getKey()); + String name=clazz != null? clazz.getSimpleName() : null; Header hdr=entry.getValue(); int size=hdr.size(); hdrs_size+=size; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/HTOTAL.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/HTOTAL.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/HTOTAL.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/HTOTAL.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: HTOTAL.java,v 1.10 2008/05/29 14:17:37 vlada Exp $ package org.jgroups.protocols; @@ -6,11 +5,9 @@ import org.jgroups.annotations.Experimental; import org.jgroups.annotations.Property; import org.jgroups.stack.Protocol; -import org.jgroups.util.Streamable; import org.jgroups.util.Util; import java.io.*; -import java.util.Properties; import java.util.Vector; @@ -20,7 +17,6 @@ * etc.

      * This protocol has not yet been completed and is experimental at best ! * @author Bela Ban - * @version $Id: HTOTAL.java,v 1.10 2008/05/29 14:17:37 vlada Exp $ */ @Experimental public class HTOTAL extends Protocol { @@ -38,72 +34,70 @@ public HTOTAL() { } - public final String getName() { - return "HTOTAL"; - } public Object down(Event evt) { switch(evt.getType()) { - case Event.VIEW_CHANGE: - determineCoordinatorAndNextMember((View)evt.getArg()); - break; - case Event.MSG: - Message msg=(Message)evt.getArg(); - Address dest=msg.getDest(); - if(dest == null || dest.isMulticastAddress()) { // only process multipoint messages - if(coord == null) - log.error("coordinator is null, cannot send message to coordinator"); - else { - msg.setSrc(local_addr); - forwardTo(coord, msg); + case Event.VIEW_CHANGE: + determineCoordinatorAndNextMember((View)evt.getArg()); + break; + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; + case Event.MSG: + Message msg=(Message)evt.getArg(); + Address dest=msg.getDest(); + if(dest == null || dest.isMulticastAddress()) { // only process multipoint messages + if(coord == null) + log.error("coordinator is null, cannot send message to coordinator"); + else { + if(msg.getSrc() == null) + msg.setSrc(local_addr); + forwardTo(coord, msg); + } + return null; // handled here, don't pass down by default } - return null; // handled here, don't pass down by default - } - break; + break; } return down_prot.down(evt); } public Object up(Event evt) { switch(evt.getType()) { - case Event.SET_LOCAL_ADDRESS: - local_addr=(Address)evt.getArg(); - break; - case Event.VIEW_CHANGE: - determineCoordinatorAndNextMember((View)evt.getArg()); - break; - case Event.MSG: - Message msg=(Message)evt.getArg(); - HTotalHeader hdr=(HTotalHeader)msg.getHeader(getName()); - - if(hdr == null) - break; // probably a unicast message, just pass it up - - Message copy=msg.copy(false); // do not copy the buffer - if(use_multipoint_forwarding) { - copy.setDest(null); - down_prot.down(new Event(Event.MSG, copy)); - } - else { - if(neighbor != null) { - forwardTo(neighbor, copy); + case Event.VIEW_CHANGE: + determineCoordinatorAndNextMember((View)evt.getArg()); + break; + case Event.MSG: + Message msg=(Message)evt.getArg(); + HTotalHeader hdr=(HTotalHeader)msg.getHeader(this.id); + + if(hdr == null) + break; // probably a unicast message, just pass it up + + Message copy=msg.copy(false); // do not copy the buffer + if(use_multipoint_forwarding) { + copy.setDest(null); + down_prot.down(new Event(Event.MSG, copy)); + } + else { + if(neighbor != null) { + forwardTo(neighbor, copy); + } } - } - msg.setDest(hdr.dest); // set destination to be the original destination - msg.setSrc(hdr.src); // set sender to be the original sender (important for retransmission requests) + msg.setDest(hdr.dest); // set destination to be the original destination + msg.setSrc(hdr.src); // set sender to be the original sender (important for retransmission requests) - return up_prot.up(evt); // <-- we modify msg directly inside evt + return up_prot.up(evt); // <-- we modify msg directly inside evt } return up_prot.up(evt); } private void forwardTo(Address destination, Message msg) { - HTotalHeader hdr=(HTotalHeader)msg.getHeader(getName()); + HTotalHeader hdr=(HTotalHeader)msg.getHeader(this.id); if(hdr == null) { hdr=new HTotalHeader(msg.getDest(), msg.getSrc()); - msg.putHeader(getName(), hdr); + msg.putHeader(this.id, hdr); } msg.setDest(destination); if(log.isTraceEnabled()) @@ -142,7 +136,7 @@ } - public static class HTotalHeader extends Header implements Streamable { + public static class HTotalHeader extends Header { Address dest, src; public HTotalHeader() { @@ -153,16 +147,6 @@ this.src=src; } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeObject(dest); - out.writeObject(src); - } - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - dest=(Address)in.readObject(); - src=(Address)in.readObject(); - } - public void writeTo(DataOutputStream out) throws IOException { Util.writeAddress(dest, out); Util.writeAddress(src, out); @@ -173,6 +157,10 @@ src=Util.readAddress(in); } + public int size() { + return Util.size(dest) + Util.size(src); + } + public String toString() { return "dest=" + dest + ", src=" + src; } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/JDBC_PING.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/JDBC_PING.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/JDBC_PING.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/JDBC_PING.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,376 @@ +package org.jgroups.protocols; + +import org.jgroups.Address; +import org.jgroups.annotations.Property; +import org.jgroups.util.Util; + +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; +import java.sql.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + *

      Discovery protocol using a JDBC connection to a shared database. + * Connection options can be defined as configuration properties, or the JNDI + * name of a DataSource can be provided (avoid providing both).

      + * + *

      Both the schema and the used SQL statements can be customized; make sure + * the order of parameters of such customized SQL statements is maintained and + * that compatible types are used for the columns. The recommended schema uses a + * single table, with two String columns being used primary key (local address, + * cluster name) and a third column to store the serialized form of the objects + * needed by JGroups.

      + * + *

      A default table will be created at first connection, errors during this + * operation are not considered critical. Set the initialize_sql + * to an empty value to prevent this initial table creation, or change it to + * create a customized table.

      + * + * @author Sanne Grinovero + * @since 2.12 + */ +public class JDBC_PING extends FILE_PING { + + /* ----------------------------------------- Properties -------------------------------------------------- */ + + @Property(description = "The JDBC connection URL", writable = false) + protected String connection_url = null; + + @Property(description = "The JDBC connection username", writable = false) + protected String connection_username = null; + + @Property(description = "The JDBC connection password", writable = false) + protected String connection_password = null; + + @Property(description = "The JDBC connection driver name", writable = false) + protected String connection_driver = null; + + @Property(description = "If not empty, this SQL statement will be performed at startup." + + "Customize it to create the needed table on those databases which permit table creation attempt without loosing data, such as " + + "PostgreSQL and MySQL (using IF NOT EXISTS). To allow for creation attempts, errors performing this statement will be logged" + + "but not considered fatal. To avoid any DDL operation, set this to an empty string.") + protected String initialize_sql = + "CREATE TABLE JGROUPSPING (" + + "own_addr varchar(200) NOT NULL, " + + "cluster_name varchar(200) NOT NULL, " + + "ping_data varbinary(5000) DEFAULT NULL, " + + "PRIMARY KEY (own_addr, cluster_name) )"; + + @Property(description = "SQL used to insert a new row. Customizable, but keep the order of parameters and pick compatible types: " + + "1)Own Address, as String 2)Cluster name, as String 3)Serialized PingData as byte[]") + protected String insert_single_sql = "INSERT INTO JGROUPSPING (own_addr, cluster_name, ping_data) values (?, ?, ?)"; + + @Property(description = "SQL used to delete a row. Customizable, but keep the order of parameters and pick compatible types: " + + "1)Own Address, as String 2)Cluster name, as String") + protected String delete_single_sql = "DELETE FROM JGROUPSPING WHERE own_addr=? AND cluster_name=?"; + + @Property(description = "SQL used to fetch all node's PingData. Customizable, but keep the order of parameters and pick compatible types: " + + "only one parameter needed, String compatible, representing the Cluster name. Must return a byte[], the Serialized PingData as" + + " it was stored by the insert_single_sql statement") + protected String select_all_pingdata_sql = "SELECT ping_data FROM JGROUPSPING WHERE cluster_name=?"; + + @Property(description = "To use a DataSource registered in JNDI, specify the JNDI name here. " + + "This is an alternative to all connection_* configuration options: if this property is not empty, then all connection related" + + "properties must be empty.") + protected String datasource_jndi_name; + + /* --------------------------------------------- Fields ------------------------------------------------------ */ + + private DataSource dataSourceFromJNDI = null; + + @Override + public void init() throws Exception { + super.init(); + verifyconfigurationParameters(); + if (stringIsEmpty(datasource_jndi_name)) { + loadDriver(); + } + else { + dataSourceFromJNDI = getDataSourceFromJNDI(datasource_jndi_name.trim()); + } + attemptSchemaInitialization(); + } + + @Override + public void stop() { + try { + deleteSelf(); + } catch (SQLException e) { + log.error("Error while unregistering of our own Address from JDBC_PING database during shutdown", e); + } + super.stop(); + } + + protected void attemptSchemaInitialization() { + if (stringIsEmpty(initialize_sql)) { + log.info("Table creation step skipped: initialize_sql property is missing"); + return; + } + Connection connection = getConnection(); + if (connection != null) { + try { + try { + PreparedStatement preparedStatement = + connection.prepareStatement(initialize_sql); + preparedStatement.execute(); + log.info("Table created for JDBC_PING Discovery Protocol"); + } catch (SQLException e) { + if (log.isDebugEnabled()) { + log.debug("Could not execute initialize_sql statement; not necessarily an error.", e); + } + else { + //avoid printing out the stacktrace + log.info("Could not execute initialize_sql statement; not necessarily an error. Set to debug logging level for details."); + } + } + } finally { + try { + connection.close(); + } catch (SQLException e) { + log.error("Error closing connection", e); + } + } + } + } + + protected void loadDriver() { + if (stringIsEmpty(connection_driver)) { + return; + } + if (log.isDebugEnabled()) { + log.debug("Registering JDBC Driver named '" + connection_driver + "'"); + } + try { + Class.forName(connection_driver); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("JDBC Driver required for JDBC_PING Discovery" + + "protocol could not be loaded: '" + connection_driver + "'"); + } + } + + protected Connection getConnection() { + if (dataSourceFromJNDI == null) { + Connection connection; + try { + connection = DriverManager.getConnection( + connection_url, connection_username, connection_password); + } catch (SQLException e) { + log.error("Could not open connection to database", e); + return null; + } + if (connection == null) { + log.error("Received null connection from the DriverManager!"); + } + return connection; + } + else { + try { + return dataSourceFromJNDI.getConnection(); + } catch (SQLException e) { + log.error("Could not open connection to database", e); + return null; + } + } + } + + @Override + protected void createRootDir() { + // No-Op to prevent file creations from super.init(). + // TODO refactor this class and FILE_PING to have a common parent? + // would also be nice to remove unwanted configuration properties which where inherited. + } + + @Override + protected void remove(String clustername, Address addr) { + final String addressAsString = addressAsString(addr); + try { + delete(clustername, addressAsString); + } catch (SQLException e) { + log.error("Error", e); + } + } + + @Override + protected List readAll(String clustername) { + final Connection connection = getConnection(); + if (connection != null) { + try { + return readAll(connection, clustername); + } catch (SQLException e) { + log.error("Error reading JDBC_PING table", e); + return Collections.emptyList(); + } finally { + closeConnection(connection); + } + } else { + return Collections.emptyList(); + } + } + + protected List readAll(Connection connection, String clustername) throws SQLException { + PreparedStatement ps = connection.prepareStatement(select_all_pingdata_sql); + try { + ps.setString(1, clustername); + ResultSet resultSet = ps.executeQuery(); + ArrayList results = new ArrayList(); + while (resultSet.next()) { + byte[] bytes = resultSet.getBytes(1); + PingData pingData = deserialize(bytes); + results.add(pingData); + } + return results; + } finally { + ps.close(); + } + } + + @Override + protected void writeToFile(PingData data, String clustername) { + final String ownAddress = addressAsString(data.getAddress()); + final Connection connection = getConnection(); + if (connection != null) { + try { + delete(connection, clustername, ownAddress); + insert(connection, data, clustername, ownAddress); + } catch (SQLException e) { + log.error("Error updating JDBC_PING table", e); + } finally { + closeConnection(connection); + } + } + else { + log.error("Failed to store PingData in database"); + } + } + + protected void insert(Connection connection, PingData data, String clustername, String address) throws SQLException { + final byte[] serializedPingData = serializeWithoutView(data); + PreparedStatement ps = connection.prepareStatement(insert_single_sql); + try { + ps.setString(1, address); + ps.setString(2, clustername); + ps.setBytes(3, serializedPingData); + ps.executeUpdate(); + if (log.isDebugEnabled()) + log.debug("Registered " + address + " for clustername " + clustername + " into database."); + } finally { + ps.close(); + } + } + + protected void delete(Connection connection, String clustername, String addressToDelete) throws SQLException { + PreparedStatement ps = connection.prepareStatement(delete_single_sql); + try { + ps.setString(1, addressToDelete); + ps.setString(2, clustername); + ps.executeUpdate(); + if (log.isDebugEnabled()) + log.debug("Removed " + addressToDelete + " for clustername " + clustername + " from database."); + } finally { + ps.close(); + } + } + + protected void delete(String clustername, String addressToDelete) throws SQLException { + final Connection connection = getConnection(); + if (connection != null) { + try { + delete(connection, clustername, addressToDelete); + } catch (SQLException e) { + log.error("Error updating JDBC_PING table", e); + } finally { + closeConnection(connection); + } + } else { + log.error("Failed to delete PingData in database"); + } + } + + protected void deleteSelf() throws SQLException { + final String ownAddress = addressAsString(local_addr); + delete(group_addr, ownAddress); + } + + + protected void closeConnection(final Connection connection) { + try { + connection.close(); + } catch (SQLException e) { + log.error("Error closing connection to JDBC_PING database", e); + } + } + + protected DataSource getDataSourceFromJNDI(String name) { + final DataSource dataSource; + InitialContext ctx = null; + try { + ctx = new InitialContext(); + Object wathever = ctx.lookup(name); + if (wathever == null) { + throw new IllegalArgumentException( + "JNDI name " + name + " is not bound"); + } else if (!(wathever instanceof DataSource)) { + throw new IllegalArgumentException( + "JNDI name " + name + " was found but is not a DataSource"); + } else { + dataSource = (DataSource) wathever; + if (log.isDebugEnabled()) { + log.debug( + "Datasource found via JNDI lookup via name: '"+ name + "'."); + } + return dataSource; + } + } catch (NamingException e) { + throw new IllegalArgumentException( + "Could not lookup datasource " + name, e); + } finally { + if (ctx != null) { + try { + ctx.close(); + } catch (NamingException e) { + log.warn("Failed to close naming context.", e); + } + } + } + } + + protected void verifyconfigurationParameters() { + if (stringIsEmpty(this.connection_url) || + stringIsEmpty(this.connection_driver) || + stringIsEmpty(this.connection_url) || + stringIsEmpty(this.connection_username) ) { + if (stringIsEmpty(this.datasource_jndi_name)) { + throw new IllegalArgumentException("Either the 4 configuration properties starting with 'connection_' or the datasource_jndi_name must be set"); + } + } + if (stringNotEmpty(this.connection_url) || + stringNotEmpty(this.connection_driver) || + stringNotEmpty(this.connection_url) || + stringNotEmpty(this.connection_username) ) { + if (stringNotEmpty(this.datasource_jndi_name)) { + throw new IllegalArgumentException("When using the 'datasource_jndi_name' configuration property, all properties starting with 'connection_' must not be set"); + } + } + if (stringIsEmpty(this.insert_single_sql)) { + throw new IllegalArgumentException("The insert_single_sql configuration property is mandatory"); + } + if (stringIsEmpty(this.delete_single_sql)) { + throw new IllegalArgumentException("The delete_single_sql configuration property is mandatory"); + } + if (stringIsEmpty(this.select_all_pingdata_sql)) { + throw new IllegalArgumentException("The select_all_pingdata_sql configuration property is mandatory"); + } + } + + private static final boolean stringIsEmpty(final String value) { + return value == null || value.trim().length() == 0; + } + + private static final boolean stringNotEmpty(final String value) { + return value != null && value.trim().length() >= 0; + } + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/Locking.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/Locking.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/Locking.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/Locking.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,1312 @@ +package org.jgroups.protocols; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.LockSupport; + +import org.jgroups.Address; +import org.jgroups.Event; +import org.jgroups.Header; +import org.jgroups.Message; +import org.jgroups.View; +import org.jgroups.annotations.MBean; +import org.jgroups.annotations.ManagedAttribute; +import org.jgroups.annotations.ManagedOperation; +import org.jgroups.annotations.Property; +import org.jgroups.blocks.locking.AwaitInfo; +import org.jgroups.blocks.locking.LockInfo; +import org.jgroups.blocks.locking.LockNotification; +import org.jgroups.blocks.locking.Owner; +import org.jgroups.stack.Protocol; +import org.jgroups.util.Streamable; +import org.jgroups.util.Util; + + + +/** + * Base locking protocol, handling most of the protocol communication with other instances. To use distributed locking, + * {@link org.jgroups.blocks.locking.LockService} is placed on a channel. LockService talks to a subclass of Locking + * via events. + * @author Bela Ban + * @since 2.12 + * @see org.jgroups.protocols.CENTRAL_LOCK + * @see org.jgroups.protocols.PEER_LOCK + */ +@MBean(description="Based class for locking functionality") +abstract public class Locking extends Protocol { + + @Property(description="bypasses message bundling if set") + protected boolean bypass_bundling=true; + + + protected Address local_addr; + + protected View view; + + // server side locks + protected final ConcurrentMap server_locks=Util.createConcurrentMap(20); + + // client side locks + protected final Map> client_locks=new HashMap>(); + + protected final Set lock_listeners=new HashSet(); + + + + protected static enum Type { + GRANT_LOCK, // request to acquire a lock + LOCK_GRANTED, // response to sender of GRANT_LOCK on succcessful lock acquisition + LOCK_DENIED, // response to sender of GRANT_LOCK on unsuccessful lock acquisition (e.g. on tryLock()) + RELEASE_LOCK, // request to release a lock + CREATE_LOCK, // request to create a server lock (sent by coordinator to backups). Used by CentralLockService + DELETE_LOCK, // request to delete a server lock (sent by coordinator to backups). Used by CentralLockService + LOCK_AWAIT, // request to await until condition is signaled + COND_SIG, // request to signal awaiting thread + COND_SIG_ALL, // request to signal all awaiting threads + SIG_RET, // response to alert of signal + DELETE_LOCK_AWAIT, // request to delete a waiter + CREATE_AWAITER, // request to create a server lock await (sent by coordinator to backups). Used by CentralLockService + DELETE_AWAITER // request to delete a server lock await (sent by coordinator to backups). Used by CentralLockService + } + + + + public Locking() { + } + + + public boolean getBypassBundling() { + return bypass_bundling; + } + + public void setBypassBundling(boolean bypass_bundling) { + this.bypass_bundling=bypass_bundling; + } + + public void addLockListener(LockNotification listener) { + if(listener != null) + lock_listeners.add(listener); + } + + public void removeLockListener(LockNotification listener) { + if(listener != null) + lock_listeners.remove(listener); + } + + @ManagedAttribute + public String getAddress() { + return local_addr != null? local_addr.toString() : null; + } + + @ManagedAttribute + public String getView() { + return view != null? view.toString() : null; + } + + + + public Object down(Event evt) { + switch(evt.getType()) { + case Event.LOCK: + LockInfo info=(LockInfo)evt.getArg(); + ClientLock lock=getLock(info.getName()); + if(!info.isTrylock()) { + if(info.isLockInterruptibly()) { + try { + lock.lockInterruptibly(); + } + catch(InterruptedException e) { + Thread.currentThread().interrupt(); // has to be checked by caller who has to rethrow ... + } + } + else + lock.lock(); + } + else { + if(info.isUseTimeout()) { + try { + return lock.tryLock(info.getTimeout(), info.getTimeUnit()); + } + catch(InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + else { + return lock.tryLock(); + } + } + return null; + + + case Event.UNLOCK: + info=(LockInfo)evt.getArg(); + lock=getLock(info.getName(), false); + if(lock != null) + lock.unlock(); + return null; + + case Event.UNLOCK_ALL: + unlockAll(); + return null; + case Event.LOCK_AWAIT: + info=(LockInfo)evt.getArg(); + lock=getLock(info.getName(), false); + if (lock == null || !lock.acquired) { + throw new IllegalMonitorStateException(); + } + Condition condition = lock.newCondition(); + if (info.isUseTimeout()) { + try { + return condition.awaitNanos(info.getTimeUnit().toNanos( + info.getTimeout())); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + else if (info.isLockInterruptibly()) { + try { + condition.await(); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + else { + condition.awaitUninterruptibly(); + } + break; + case Event.LOCK_SIGNAL: + AwaitInfo awaitInfo = (AwaitInfo)evt.getArg(); + lock=getLock(awaitInfo.getName(), false); + if (lock == null || !lock.acquired) { + throw new IllegalMonitorStateException(); + } + sendSignalConditionRequest(awaitInfo.getName(), + awaitInfo.isAll()); + break; + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; + + case Event.VIEW_CHANGE: + handleView((View)evt.getArg()); + break; + } + return down_prot.down(evt); + } + + public Object up(Event evt) { + switch(evt.getType()) { + case Event.MSG: + Message msg=(Message)evt.getArg(); + LockingHeader hdr=(LockingHeader)msg.getHeader(id); + if(hdr == null) + break; + + Request req=(Request)msg.getObject(); + if(log.isTraceEnabled()) + log.trace("[" + local_addr + "] <-- [" + msg.getSrc() + "] " + req); + switch(req.type) { + case GRANT_LOCK: + case RELEASE_LOCK: + handleLockRequest(req); + break; + case LOCK_GRANTED: + handleLockGrantedResponse(req.lock_name, req.owner, msg.getSrc()); + break; + case LOCK_DENIED: + handleLockDeniedResponse(req.lock_name, req.owner); + break; + case CREATE_LOCK: + handleCreateLockRequest(req.lock_name, req.owner); + break; + case DELETE_LOCK: + handleDeleteLockRequest(req.lock_name); + break; + case COND_SIG: + case COND_SIG_ALL: + handleSignalRequest(req); + break; + case LOCK_AWAIT: + handleAwaitRequest(req.lock_name, req.owner); + handleLockRequest(req); + break; + case DELETE_LOCK_AWAIT: + handleDeleteAwaitRequest(req.lock_name, req.owner); + break; + case SIG_RET: + handleSignalResponse(req.lock_name, req.owner); + break; + case CREATE_AWAITER: + handleCreateAwaitingRequest(req.lock_name, req.owner); + break; + case DELETE_AWAITER: + handleDeleteAwaitingRequest(req.lock_name, req.owner); + break; + default: + log.error("Request of type " + req.type + " not known"); + break; + } + return null; + + case Event.VIEW_CHANGE: + handleView((View)evt.getArg()); + break; + } + return up_prot.up(evt); + } + + protected ClientLock getLock(String name) { + return getLock(name, getOwner(), true); + } + + protected ClientLock getLock(String name, boolean create_if_absent) { + return getLock(name, getOwner(), create_if_absent); + } + + @ManagedOperation(description="Unlocks all currently held locks") + public void unlockAll() { + List locks=new ArrayList(); + synchronized(client_locks) { + Collection> maps=client_locks.values(); + for(Map map: maps) { + locks.addAll(map.values()); + } + } + for(ClientLock lock: locks) + lock.unlock(); + } + + + @ManagedOperation(description="Dumps all locks") + public String printLocks() { + StringBuilder sb=new StringBuilder(); + sb.append("server locks:\n"); + for(Map.Entry entry: server_locks.entrySet()) { + sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); + } + + sb.append("\nmy locks: "); + synchronized(client_locks) { + boolean first_element=true; + for(Map.Entry> entry: client_locks.entrySet()) { + if(first_element) + first_element=false; + else + sb.append(", "); + sb.append(entry.getKey()).append(" ("); + Map owners=entry.getValue(); + boolean first=true; + for(Map.Entry entry2: owners.entrySet()) { + if(first) + first=false; + else + sb.append(", "); + sb.append(entry2.getKey()); + ClientLock cl=entry2.getValue(); + if(!cl.acquired || cl.denied) + sb.append(", unlocked"); + } + sb.append(")"); + } + } + return sb.toString(); + } + + protected void handleView(View view) { + this.view=view; + if(log.isDebugEnabled()) + log.debug("view=" + view); + List
      members=view.getMembers(); + for(Map.Entry entry: server_locks.entrySet()) { + entry.getValue().handleView(members); + } + for(Map.Entry entry: server_locks.entrySet()) { + ServerLock lock=entry.getValue(); + if(lock.isEmpty() && lock.current_owner == null) + server_locks.remove(entry.getKey()); + } + } + + + + protected ClientLock createLock(String lock_name) { + return new ClientLock(lock_name); + } + + protected Owner getOwner() { + return new Owner(local_addr, Thread.currentThread().getId()); + } + + abstract protected void sendGrantLockRequest(String lock_name, Owner owner, long timeout, boolean is_trylock); + abstract protected void sendReleaseLockRequest(String lock_name, Owner owner); + abstract protected void sendAwaitConditionRequest(String lock_name, Owner owner); + abstract protected void sendSignalConditionRequest(String lock_name, boolean all); + abstract protected void sendDeleteAwaitConditionRequest(String lock_name, Owner owner); + + + protected void sendRequest(Address dest, Type type, String lock_name, Owner owner, long timeout, boolean is_trylock) { + Request req=new Request(type, lock_name, owner, timeout, is_trylock); + Message msg=new Message(dest, null, req); + msg.putHeader(id, new LockingHeader()); + if(bypass_bundling) + msg.setFlag(Message.DONT_BUNDLE); + if(log.isTraceEnabled()) + log.trace("[" + local_addr + "] --> [" + (dest == null? "ALL" : dest) + "] " + req); + try { + down_prot.down(new Event(Event.MSG, msg)); + } + catch(Exception ex) { + log.error("failed sending " + type + " request: " + ex); + } + } + + + protected void sendLockResponse(Type type, Owner dest, String lock_name) { + Request rsp=new Request(type, lock_name, dest, 0); + Message lock_granted_rsp=new Message(dest.getAddress(), null, rsp); + lock_granted_rsp.putHeader(id, new LockingHeader()); + if(bypass_bundling) + lock_granted_rsp.setFlag(Message.DONT_BUNDLE); + + if(log.isTraceEnabled()) + log.trace("[" + local_addr + "] --> [" + dest.getAddress() + "] " + rsp); + + try { + down_prot.down(new Event(Event.MSG, lock_granted_rsp)); + } + catch(Exception ex) { + log.error("failed sending " + type + " message to " + dest + ": " + ex); + } + } + + + protected void sendSignalResponse(Owner dest, String lock_name) { + Request rsp=new Request(Type.SIG_RET, lock_name, dest, 0); + Message lock_granted_rsp=new Message(dest.getAddress(), null, rsp); + lock_granted_rsp.putHeader(id, new LockingHeader()); + if(bypass_bundling) + lock_granted_rsp.setFlag(Message.DONT_BUNDLE); + + if(log.isTraceEnabled()) + log.trace("[" + local_addr + "] --> [" + dest.getAddress() + "] " + rsp); + + try { + down_prot.down(new Event(Event.MSG, lock_granted_rsp)); + } + catch(Exception ex) { + log.error("failed sending " + Type.SIG_RET + " message to " + dest + ": " + ex); + } + } + + + protected void handleLockRequest(Request req) { + ServerLock lock=server_locks.get(req.lock_name); + if(lock == null) { + lock=new ServerLock(req.lock_name); + ServerLock tmp=server_locks.putIfAbsent(req.lock_name, lock); + if(tmp != null) + lock=tmp; + else { + notifyLockCreated(req.lock_name); + } + } + lock.handleRequest(req); + // We remove the lock if there is no waiters or owner + if(lock.isEmpty() && lock.current_owner == null && lock.condition.queue.isEmpty()) { + server_locks.remove(req.lock_name); + } + } + + + protected void handleLockGrantedResponse(String lock_name, Owner owner, Address sender) { + ClientLock lock=getLock(lock_name, owner, false); + if(lock != null) + lock.handleLockGrantedResponse(owner, sender); + } + + protected void handleLockDeniedResponse(String lock_name, Owner owner) { + ClientLock lock=getLock(lock_name, owner, false); + if(lock != null) + lock.lockDenied(); + } + + protected void handleAwaitRequest(String lock_name, Owner owner) { + ServerLock lock=server_locks.get(lock_name); + if (lock != null) { + lock.condition.addWaiter(owner); + } + else { + log.error("Condition await was received but lock was not created. Waiter may block forever"); + } + } + + protected void handleDeleteAwaitRequest(String lock_name, Owner owner) { + ServerLock lock=server_locks.get(lock_name); + if (lock != null) { + lock.condition.removeWaiter(owner); + } + else { + log.error("Condition await delete was received, but lock was gone"); + } + } + + protected void handleSignalResponse(String lock_name, Owner owner) { + ClientLock lock=getLock(lock_name, owner, false); + if(lock != null) { + synchronized (lock.condition) { + lock.condition.signaled(); + } + } + else { + log.error("Condition response was client lock was not present. Ignored signal."); + } + } + + protected void handleSignalRequest(Request req) { + ServerLock lock=server_locks.get(req.lock_name); + if (lock != null) { + lock.handleRequest(req); + } + else { + log.error("Condition signal was received but lock was not created. Couldn't notify anyone."); + } + } + + protected void handleCreateLockRequest(String lock_name, Owner owner) { + synchronized(server_locks) { + server_locks.put(lock_name, new ServerLock(lock_name, owner)); + } + } + + + protected void handleDeleteLockRequest(String lock_name) { + synchronized(server_locks) { + ServerLock lock = server_locks.get(lock_name); + synchronized (lock.condition) { + if (lock.condition.queue.isEmpty()) { + server_locks.remove(lock_name); + } + else { + lock.current_owner = null; + } + } + } + } + + + protected void handleCreateAwaitingRequest(String lock_name, Owner owner) { + synchronized(server_locks) { + ServerLock lock = server_locks.get(lock_name); + if (lock == null) { + lock = new ServerLock(lock_name); + } + lock.condition.queue.add(owner); + } + } + + + protected void handleDeleteAwaitingRequest(String lock_name, Owner owner) { + synchronized(server_locks) { + ServerLock lock = server_locks.get(lock_name); + if (lock != null) { + synchronized (lock.condition) { + lock.condition.queue.remove(owner); + if (lock.condition.queue.isEmpty() && lock.current_owner == null) { + server_locks.remove(lock_name); + } + } + } + } + } + + + protected ClientLock getLock(String name, Owner owner, boolean create_if_absent) { + synchronized(client_locks) { + Map owners=client_locks.get(name); + if(owners == null) { + if(!create_if_absent) + return null; + owners=new HashMap(); + client_locks.put(name, owners); + } + ClientLock lock=owners.get(owner); + if(lock == null) { + if(!create_if_absent) + return null; + lock=createLock(name); + owners.put(owner, lock); + } + return lock; + } + } + + protected void removeClientLock(String lock_name, Owner owner) { + synchronized(client_locks) { + Map owners=client_locks.get(lock_name); + if(owners != null) { + ClientLock lock=owners.remove(owner); + if(lock != null) { + if(owners.isEmpty()) + client_locks.remove(lock_name); + } + } + } + } + + + protected void notifyLockCreated(String lock_name) { + for(LockNotification listener: lock_listeners) { + try { + listener.lockCreated(lock_name); + } + catch(Throwable t) { + log.error("failed notifying " + listener, t); + } + } + } + + protected void notifyLockDeleted(String lock_name) { + for(LockNotification listener: lock_listeners) { + try { + listener.lockDeleted(lock_name); + } + catch(Throwable t) { + log.error("failed notifying " + listener, t); + } + } + } + + protected void notifyLocked(String lock_name, Owner owner) { + for(LockNotification listener: lock_listeners) { + try { + listener.locked(lock_name, owner); + } + catch(Throwable t) { + log.error("failed notifying " + listener, t); + } + } + } + + protected void notifyUnlocked(String lock_name, Owner owner) { + for(LockNotification listener: lock_listeners) { + try { + listener.unlocked(lock_name, owner); + } + catch(Throwable t) { + log.error("failed notifying " + listener, t); + } + } + } + + protected void notifyAwaiting(String lock_name, Owner owner) { + for(LockNotification listener: lock_listeners) { + try { + listener.awaiting(lock_name, owner); + } + catch(Throwable t) { + log.error("failed notifying " + listener, t); + } + } + } + + protected void notifyAwaited(String lock_name, Owner owner) { + for(LockNotification listener: lock_listeners) { + try { + listener.awaited(lock_name, owner); + } + catch(Throwable t) { + log.error("failed notifying " + listener, t); + } + } + } + + + + /** + * Server side queue for handling of lock requests (lock, release). + * @author Bela Ban + */ + protected class ServerLock { + protected final String lock_name; + protected Owner current_owner; + protected final List queue=new ArrayList(); + protected final ServerCondition condition; + + public ServerLock(String lock_name) { + this.lock_name=lock_name; + this.condition=new ServerCondition(this); + } + + protected ServerLock(String lock_name, Owner owner) { + this.lock_name=lock_name; + this.current_owner=owner; + this.condition=new ServerCondition(this); + } + + protected synchronized void handleRequest(Request req) { + switch(req.type) { + case GRANT_LOCK: + if(current_owner == null) { + setOwner(req.owner); + sendLockResponse(Type.LOCK_GRANTED, req.owner, req.lock_name); + } + else { + if(current_owner.equals(req.owner)) { + sendLockResponse(Type.LOCK_GRANTED, req.owner, req.lock_name); + } + else { + if(req.is_trylock && req.timeout <= 0) + sendLockResponse(Type.LOCK_DENIED, req.owner, req.lock_name); + else + addToQueue(req); + } + } + break; + case RELEASE_LOCK: + case LOCK_AWAIT: + if(current_owner == null) + break; + if(current_owner.equals(req.owner)) + setOwner(null); + else + addToQueue(req); + break; + case COND_SIG: + condition.signal(false); + break; + case COND_SIG_ALL: + condition.signal(true); + break; + default: + throw new IllegalArgumentException("type " + req.type + " is invalid here"); + } + + processQueue(); + } + + protected synchronized void handleView(List
      members) { + if(current_owner != null && !members.contains(current_owner.getAddress())) { + Owner tmp=current_owner; + setOwner(null); + if(log.isDebugEnabled()) + log.debug("unlocked \"" + lock_name + "\" because owner " + tmp + " left"); + } + + for(Iterator it=queue.iterator(); it.hasNext();) { + Request req=it.next(); + if(!members.contains(req.owner.getAddress())) + it.remove(); + } + + for(Iterator it=condition.queue.iterator(); it.hasNext();) { + Owner own=it.next(); + if(!members.contains(own.getAddress())) { + it.remove(); + } + } + + processQueue(); + } + + + protected void addToQueue(Request req) { + if(queue.isEmpty()) { + if(req.type == Type.GRANT_LOCK) + queue.add(req); + return; // RELEASE_LOCK is discarded on an empty queue + } + + // at this point the queue is not empty + switch(req.type) { + + // If there is already a lock request from the same owner, discard the new lock request + case GRANT_LOCK: + if(!isRequestPresent(Type.GRANT_LOCK, req.owner)) + queue.add(req); + break; + + case RELEASE_LOCK: + // Release the lock request from the same owner already in the queue + // If there is no lock request, discard the unlock request + removeRequest(Type.GRANT_LOCK, req.owner); + break; + } + } + + /** Checks if a certain request from a given owner is already in the queue */ + protected boolean isRequestPresent(Type type, Owner owner) { + for(Request req: queue) + if(req.type == type && req.owner.equals(owner)) + return true; + return false; + } + + protected void removeRequest(Type type, Owner owner) { + for(Iterator it=queue.iterator(); it.hasNext();) { + Request req=it.next(); + if(req.type == type && req.owner.equals(owner)) + it.remove(); + } + } + + + protected void processQueue() { + if(current_owner == null) { + while(!queue.isEmpty()) { + Request req=queue.remove(0); + if(req.type == Type.GRANT_LOCK) { + setOwner(req.owner); + sendLockResponse(Type.LOCK_GRANTED, req.owner, req.lock_name); + break; + } + } + } + } + + protected void setOwner(Owner owner) { + if(owner == null) { + if(current_owner != null) { + Owner tmp=current_owner; + current_owner=null; + notifyUnlocked(lock_name, tmp); + } + } + else { + current_owner=owner; + notifyLocked(lock_name, owner); + } + } + + public boolean isEmpty() {return queue.isEmpty();} + + public String toString() { + StringBuilder sb=new StringBuilder(); + sb.append(current_owner); + if(!queue.isEmpty()) { + sb.append(", queue: "); + for(Request req: queue) { + sb.append(req.toStringShort()).append(" "); + } + } + return sb.toString(); + } + } + + protected class ServerCondition { + protected final ServerLock lock; + protected final Queue queue=new ArrayDeque(); + + public ServerCondition(ServerLock lock) { + this.lock = lock; + } + + public synchronized void addWaiter(Owner waiter) { + notifyAwaiting(lock.lock_name, waiter); + if (log.isTraceEnabled()) { + log.trace("Waiter [" + waiter + "] was added for " + lock.lock_name); + } + queue.add(waiter); + } + + public synchronized void removeWaiter(Owner waiter) { + notifyAwaited(lock.lock_name, waiter); + if (log.isTraceEnabled()) { + log.trace("Waiter [" + waiter + "] was removed for " + lock.lock_name); + } + queue.remove(waiter); + } + + public synchronized void signal(boolean all) { + if (queue.isEmpty()) { + if (log.isTraceEnabled()) { + log.trace("Signal for [" + lock.lock_name + + "] ignored since, no one is waiting in queue."); + } + } + + Owner entry; + if (all) { + while ((entry = queue.poll()) != null) { + notifyAwaited(lock.lock_name, entry); + if (log.isTraceEnabled()) { + log.trace("Signalled " + entry + " for " + lock.lock_name); + } + sendSignalResponse(entry, lock.lock_name); + } + } + else { + entry = queue.poll(); + if (entry != null) { + notifyAwaited(lock.lock_name, entry); + if (log.isTraceEnabled()) { + log.trace("Signalled " + entry + " for " + lock.lock_name); + } + sendSignalResponse(entry, lock.lock_name); + } + } + } + } + + + + protected class ClientLock implements Lock { + protected final String name; + protected Owner owner; + protected volatile boolean acquired; + protected volatile boolean denied; + protected volatile boolean is_trylock; + protected long timeout; + + protected final ClientCondition condition; + + public ClientLock(String name) { + this.name=name; + this.condition = new ClientCondition(this); + } + + public void lock() { + try { + acquire(false); + } + catch(InterruptedException e) { + // This should never happen + } + } + + public void lockInterruptibly() throws InterruptedException { + acquire(true); + } + + public boolean tryLock() { + try { + return acquireTryLock(0, false); + } + catch(InterruptedException e) { + return false; + } + } + + public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + return acquireTryLock(TimeUnit.MILLISECONDS.convert(time, unit), true); + } + + public synchronized void unlock() { + _unlock(false); + } + + public Condition newCondition() { + // Currently only 1 condition per Lock is supported + return condition; + } + + public String toString() { + return name + " (locked=" + acquired +")"; + } + + protected synchronized void lockGranted() { + acquired=true; + this.notifyAll(); + } + + protected synchronized void lockDenied() { + denied=true; + this.notifyAll(); + } + + protected void handleLockGrantedResponse(Owner owner, Address sender) { + lockGranted(); + } + + protected synchronized void acquire(boolean throwInterrupt) throws InterruptedException { + if(!acquired) { + owner=getOwner(); + sendGrantLockRequest(name, owner, 0, false); + boolean interrupted=false; + while(!acquired) { + try { + this.wait(); + } + catch (InterruptedException e) { + // If we haven't acquired the lock yet and were interrupted, then we have to clean up the lock + // request and throw the exception + if (throwInterrupt && !acquired) { + _unlock(true); + throw e; + } + // If we did get the lock then we will return with the lock and interrupt status. + // If we don't throw exceptions then we just set the interrupt flag and let it loop around + interrupted=true; + } + } + if(interrupted) + Thread.currentThread().interrupt(); + } + } + + protected synchronized void _unlock(boolean force) { + if(!acquired && !denied && !force) + return; + this.timeout=0; + this.is_trylock=false; + sendReleaseLockRequest(name, owner); + acquired=denied=false; + notifyAll(); + + removeClientLock(name, owner); + notifyLockDeleted(name); + owner=null; + } + + protected synchronized boolean acquireTryLock(long timeout, boolean use_timeout) throws InterruptedException { + if(denied) + return false; + if(!acquired) { + is_trylock=true; + this.timeout=timeout; + owner=getOwner(); + sendGrantLockRequest(name, owner, timeout, true); + + long target_time=use_timeout? System.currentTimeMillis() + timeout : 0; + boolean interrupted = false; + while(!acquired && !denied) { + if(use_timeout) { + long wait_time=target_time - System.currentTimeMillis(); + if(wait_time <= 0) + break; + else { + this.timeout=wait_time; + try { + this.wait(wait_time); + } + catch (InterruptedException e) { + // If we were interrupted and haven't received a response yet then we try to + // clean up the lock request and throw the exception + if (!acquired && !denied) { + _unlock(true); + throw e; + } + // In the case that we were told if we acquired or denied the lock then return that, but + // make sure we set the interrupt status + interrupted = true; + } + } + } + else { + try { + this.wait(); + } + catch(InterruptedException e) { + interrupted = true; + } + } + } + if(interrupted) + Thread.currentThread().interrupt(); + } + if(!acquired || denied) + _unlock(true); + return acquired && !denied; + } + } + + protected class ClientCondition implements Condition { + + protected final ClientLock lock; + protected final AtomicBoolean signaled = new AtomicBoolean(false); + /** + * This is okay only having 1 since a client condition is 1 per + * lock_name, thread id combination. + */ + protected volatile AtomicReference parker=new AtomicReference(); + + public ClientCondition(ClientLock lock) { + this.lock = lock; + } + + @Override + public void await() throws InterruptedException { + InterruptedException ex = null; + try { + await(true); + } + catch (InterruptedException e) { + ex = e; + throw ex; + } + finally { + lock.lock(); + + // If we are throwing an InterruptedException + // then clear the interrupt state as well. + if (ex != null) { + Thread.interrupted(); + } + } + } + + @Override + public void awaitUninterruptibly() { + try { + await(false); + } + catch(InterruptedException e) { + // This should never happen + } + finally { + lock.lock(); + } + } + + @Override + public long awaitNanos(long nanosTimeout) throws InterruptedException { + long beforeLock; + InterruptedException ex = null; + try { + beforeLock = await(nanosTimeout) + System.nanoTime(); + } + catch (InterruptedException e) { + ex = e; + throw ex; + } + finally { + lock.lock(); + + // If we are throwing an InterruptedException + // then clear the interrupt state as well. + if (ex != null) { + Thread.interrupted(); + } + } + + return beforeLock - System.nanoTime(); + } + + /** + * Note this wait will only work correctly if the converted value is less + * than 292 years. This is due to the limitation in System.nano and long + * values that can only store up to 292 years (2263 nanoseconds). + * + * For more information please see {@link System#nanoTime()} + */ + @Override + public boolean await(long time, TimeUnit unit) + throws InterruptedException { + return awaitNanos(unit.toNanos(time)) > 0; + } + + @Override + public boolean awaitUntil(Date deadline) throws InterruptedException { + long waitUntilTime = deadline.getTime(); + long currentTime = System.currentTimeMillis(); + + long waitTime = waitUntilTime - currentTime; + if (waitTime > 0) { + return await(waitTime, TimeUnit.MILLISECONDS); + } + else { + return false; + } + } + + protected void await(boolean throwInterrupt) throws InterruptedException { + if(!signaled.get()) { + lock.acquired = false; + sendAwaitConditionRequest(lock.name, lock.owner); + boolean interrupted=false; + while(!signaled.get()) { + parker.set(Thread.currentThread()); + LockSupport.park(this); + + if (Thread.interrupted()) { + // If we were interrupted and haven't received a response yet then we try to + // clean up the lock request and throw the exception + if (!signaled.get()) { + sendDeleteAwaitConditionRequest(lock.name, lock.owner); + throw new InterruptedException(); + } + // In the case that we were signaled and interrupted + // we want to return the signal but still interrupt + // our thread + interrupted = true; + } + } + if(interrupted) + Thread.currentThread().interrupt(); + } + + // We set as if this signal was no released. This way if the + // condition is reused again, but the client condition isn't lost + // we won't think we were signaled immediately + signaled.set(false); + } + + protected long await(long nanoSeconds) throws InterruptedException { + long target_nano=System.nanoTime() + nanoSeconds; + + if(!signaled.get()) { + // We release the lock at the same time as waiting on the + // condition + lock.acquired = false; + sendAwaitConditionRequest(lock.name, lock.owner); + + boolean interrupted = false; + while(!signaled.get()) { + long wait_nano=target_nano - System.nanoTime(); + // If we waited max time break out + if(wait_nano > 0) { + parker.set(Thread.currentThread()); + LockSupport.parkNanos(this, wait_nano); + + if (Thread.interrupted()) { + // If we were interrupted and haven't received a response yet then we try to + // clean up the lock request and throw the exception + if (!signaled.get()) { + sendDeleteAwaitConditionRequest(lock.name, lock.owner); + throw new InterruptedException(); + } + // In the case that we were signaled and interrupted + // we want to return the signal but still interrupt + // our thread + interrupted = true; + } + } + else { + break; + } + } + if(interrupted) + Thread.currentThread().interrupt(); + } + + // We set as if this signal was no released. This way if the + // condition is reused again, but the client condition isn't lost + // we won't think we were signaled immediately + // If we weren't signaled then delete our request + if (!signaled.getAndSet(false)) { + sendDeleteAwaitConditionRequest(lock.name, lock.owner); + } + return target_nano - System.nanoTime(); + } + + @Override + public void signal() { + sendSignalConditionRequest(lock.name, false); + } + + @Override + public void signalAll() { + sendSignalConditionRequest(lock.name, true); + } + + protected void signaled() { + signaled.set(true); + Thread thread = parker.getAndSet(null); + if (thread != null) + LockSupport.unpark(thread); + } + } + + + protected static class Request implements Streamable { + protected Type type; + protected String lock_name; + protected Owner owner; + protected long timeout=0; + protected boolean is_trylock; + + + public Request() { + } + + public Request(Type type, String lock_name, Owner owner, long timeout) { + this.type=type; + this.lock_name=lock_name; + this.owner=owner; + this.timeout=timeout; + } + + public Request(Type type, String lock_name, Owner owner, long timeout, boolean is_trylock) { + this(type, lock_name, owner, timeout); + this.is_trylock=is_trylock; + } + + public void writeTo(DataOutputStream out) throws IOException { + out.writeByte(type.ordinal()); + Util.writeString(lock_name, out); + Util.writeStreamable(owner, out); + out.writeLong(timeout); + out.writeBoolean(is_trylock); + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + type=Type.values()[in.readByte()]; + lock_name=Util.readString(in); + owner=(Owner)Util.readStreamable(Owner.class, in); + timeout=in.readLong(); + is_trylock=in.readBoolean(); + } + + public String toString() { + return type.name() + " [" + lock_name + ", owner=" + owner + (is_trylock? ", trylock " : " ") + + (timeout > 0? "(timeout=" + timeout + ")" : "" + "]"); + } + + public String toStringShort() { + StringBuilder sb=new StringBuilder(); + switch(type) { + case RELEASE_LOCK: + sb.append("U"); + break; + case GRANT_LOCK: + sb.append(is_trylock? "TL" : "L"); + break; + default: + sb.append("N/A"); + break; + } + sb.append("(").append(lock_name).append(",").append(owner); + if(timeout > 0) + sb.append(",").append(timeout); + sb.append(")"); + return sb.toString(); + } + } + + + public static class LockingHeader extends Header { + + public LockingHeader() { + } + + public int size() { + return 0; + } + + public void writeTo(DataOutputStream out) throws IOException { + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + } + } + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/LOOPBACK.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/LOOPBACK.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/LOOPBACK.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/LOOPBACK.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,15 +1,18 @@ -// $Id: LOOPBACK.java,v 1.28 2008/05/21 11:55:50 belaban Exp $ package org.jgroups.protocols; +import java.net.InetAddress; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + import org.jgroups.Address; import org.jgroups.Event; import org.jgroups.Message; +import org.jgroups.PhysicalAddress; import org.jgroups.stack.IpAddress; -import org.jgroups.stack.Protocol; import org.jgroups.util.Util; -import org.jgroups.util.TimeScheduler; /** @@ -17,64 +20,35 @@ */ public class LOOPBACK extends TP { private String group_addr=null; + private final PhysicalAddress physical_addr=new IpAddress(12345); public LOOPBACK() { } + public boolean supportsMulticasting() { + return false; + } public String toString() { return "LOOPBACK(local address: " + local_addr + ')'; } - public void sendToAllMembers(byte[] data, int offset, int length) throws Exception { + public void sendMulticast(byte[] data, int offset, int length) throws Exception { } - public void sendToSingleMember(Address dest, byte[] data, int offset, int length) throws Exception { + public void sendUnicast(PhysicalAddress dest, byte[] data, int offset, int length) throws Exception { } public String getInfo() { return null; } - public void postUnmarshalling(Message msg, Address dest, Address src, boolean multicast) { - } - - public void postUnmarshallingList(Message msg, Address dest, boolean multicast) { + protected PhysicalAddress getPhysicalAddress() { + return physical_addr; } /*------------------------------ Protocol interface ------------------------------ */ - public String getName() { - return "LOOPBACK"; - } - - - - public void init() throws Exception { - super.init(); -// local_addr=new IpAddress("localhost", 10000) { // fake address -// public String toString() { -// return ""; -// } -// }; - - //local_addr=new org.jgroups.stack.IpAddress("localhost", 10000); // fake address - local_addr = new IpAddress(12345); - } - - public void destroy() { - System.out.println("destroy();"); - try { - timer.stop(); - } - catch(InterruptedException e) { - e.printStackTrace(); - } - } - - public void start() throws Exception { - up_prot.up(new Event(Event.SET_LOCAL_ADDRESS, local_addr)); - } /** @@ -104,6 +78,8 @@ case Event.CONNECT: case Event.CONNECT_WITH_STATE_TRANSFER: + case Event.CONNECT_USE_FLUSH: + case Event.CONNECT_WITH_STATE_TRANSFER_USE_FLUSH: group_addr=(String)evt.getArg(); break; } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/MERGE2.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/MERGE2.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/MERGE2.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/MERGE2.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,24 +1,18 @@ -// $Id: MERGE2.java,v 1.52 2008/07/25 19:32:46 vlada Exp $ - package org.jgroups.protocols; -import org.jgroups.Address; -import org.jgroups.Event; -import org.jgroups.View; -import org.jgroups.annotations.DeprecatedProperty; -import org.jgroups.annotations.GuardedBy; -import org.jgroups.annotations.MBean; -import org.jgroups.annotations.ManagedAttribute; -import org.jgroups.annotations.Property; +import org.jgroups.*; +import org.jgroups.annotations.*; import org.jgroups.stack.Protocol; import org.jgroups.util.TimeScheduler; import org.jgroups.util.Util; -import java.util.List; -import java.util.Vector; +import java.util.*; +import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; /** @@ -53,28 +47,51 @@ /* ----------------------------------------- Properties -------------------------------------------------- */ - @ManagedAttribute(description="Minimum time between runs to discover other clusters", writable=true) - @Property(description="Lower bound in msec to run merge protocol. Default is 5000 msec") - private long min_interval=5000; - - @ManagedAttribute(description="Maximum time between runs to discover other clusters", writable=true) - @Property(description="Upper bound in msec to run merge protocol. Default is 20000 msec") - private long max_interval=20000; - + @Property(description="Minimum time in msbetween runs to discover other clusters") + protected long min_interval=5000; + @Property(description="Maximum time in ms between runs to discover other clusters") + protected long max_interval=20000; + + @Property(description="Number of inconsistent views with only 1 coord after a MERGE event is sent up") + protected int inconsistent_view_threshold=1; + + @Property(description="When receiving a multicast message, checks if the sender is member of the cluster. " + + "If not, initiates a merge") + protected boolean merge_fast=true; + + @Property(description="The delay (in milliseconds) after which a merge fast execution is started") + protected long merge_fast_delay=1000; + + /* ---------------------------------------------- JMX -------------------------------------------------------- */ + @ManagedAttribute(writable=false, description="whether or not a merge task is currently running " + + "(should be the case in a coordinator") + public boolean isMergeTaskRunning() { + return task.isRunning(); + } + /* --------------------------------------------- Fields ------------------------------------------------------ */ private Address local_addr=null; + + private View view; + + private final Set
      members=new HashSet
      (); + + private final Set
      merge_candidates=new CopyOnWriteArraySet
      (); private final FindSubgroupsTask task=new FindSubgroupsTask(); private volatile boolean is_coord=false; private TimeScheduler timer; - - - - public MERGE2() { + + @ManagedAttribute(description="Number of inconsistent 1-coord views until a MERGE event is sent up the stack") + private int num_inconsistent_views=0; + + + + public MERGE2() { } @@ -92,11 +109,6 @@ } } - - public String getName() { - return "MERGE2"; - } - public long getMinInterval() { return min_interval; } @@ -119,37 +131,37 @@ return retval; } + /** Discovers members and detects whether we have multiple coordinator. If so, kicks off a merge */ + @ManagedOperation + public void sendMergeSolicitation() { + task.findAndNotify(); + } + + @ManagedOperation public void startMergeTask() {task.start();} + + @ManagedOperation public void stopMergeTask() {task.stop();} public void stop() { is_coord=false; + merge_candidates.clear(); task.stop(); } - - public Object up(Event evt) { - switch(evt.getType()) { - - case Event.SET_LOCAL_ADDRESS: - local_addr=(Address)evt.getArg(); - return up_prot.up(evt); - - default: - return up_prot.up(evt); // Pass up to the layer above us - } - } - - public Object down(Event evt) { switch(evt.getType()) { case Event.VIEW_CHANGE: Object ret=down_prot.down(evt); - Vector
      mbrs=((View)evt.getArg()).getMembers(); + view=(View)evt.getArg(); + Vector
      mbrs=view.getMembers(); if(mbrs == null || mbrs.isEmpty() || local_addr == null) { task.stop(); return ret; } + members.clear(); + members.addAll(mbrs); + merge_candidates.removeAll(members); Address coord=mbrs.elementAt(0); if(coord.equals(local_addr)) { is_coord=true; @@ -165,11 +177,38 @@ } return ret; + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + return down_prot.down(evt); + default: return down_prot.down(evt); // Pass on to the layer below us } - } + } + public Object up(Event evt) { + switch(evt.getType()) { + case Event.MSG: + if(!merge_fast) + break; + Message msg=(Message)evt.getArg(); + Address dest=msg.getDest(); + boolean multicast=dest == null || dest.isMulticastAddress(); + if(!multicast) + break; + final Address sender=msg.getSrc(); + if(!members.contains(sender) && merge_candidates.add(sender)) { + timer.schedule(new Runnable() { + public void run() { + if(!members.contains(sender)) + task.findAndNotify(); + } + }, merge_fast_delay, TimeUnit.MILLISECONDS); + } + break; + } + return up_prot.up(evt); + } /** * Task periodically executing (if role is coordinator). Gets the initial membership and determines @@ -179,14 +218,15 @@ private class FindSubgroupsTask { @GuardedBy("this") private Future future; + private Lock lock=new ReentrantLock(); public synchronized void start() { - if(future == null || future.isDone()) { + if(future == null || future.isDone() || future.isCancelled()) { future=timer.scheduleWithFixedDelay(new Runnable() { public void run() { findAndNotify(); } - }, 2500, computeInterval(), TimeUnit.MILLISECONDS); + }, Math.max(5000L, computeInterval()), computeInterval(), TimeUnit.MILLISECONDS); } } @@ -197,23 +237,77 @@ } } + public synchronized boolean isRunning() { + return future != null && !future.isDone() && !future.isCancelled(); + } + + public void findAndNotify() { - List initial_mbrs=findInitialMembers(); - if(log.isDebugEnabled()) - log.debug(local_addr + " is looking for merge candidates, found initial_mbrs=" + initial_mbrs); - - Vector
      coords=detectMultipleCoordinators(initial_mbrs); - if(coords.size() > 1) { - if(log.isDebugEnabled()) - log.debug(local_addr + " found multiple coordinators: " + coords + "; sending up MERGE event"); - - Event evt=new Event(Event.MERGE, coords); - up_prot.up(evt); + if(lock.tryLock()) { + try { + _findAndNotify(); + } + finally { + lock.unlock(); + } + } + } + + private void _findAndNotify() { + List discovery_rsps=findAllViews(); + + if(log.isTraceEnabled()) { + StringBuilder sb=new StringBuilder(); + sb.append("Discovery results:\n"); + for(PingData data: discovery_rsps) + sb.append("[" + data.getAddress() + "]: coord=" + data.getCoordAddress()).append("\n"); + log.trace(sb); + } + + // Create a map of senders and the views they sent + Map views=getViews(discovery_rsps); + + // A list of different views + List different_views=detectDifferentViews(views); + if(different_views.size() <= 1) { + num_inconsistent_views=0; + return; + } + Collection
      merge_participants=Util.determineMergeParticipants(views); + if(merge_participants.size() == 1) { + if(num_inconsistent_views < inconsistent_view_threshold) { + if(log.isDebugEnabled()) + log.debug("dropping MERGE for inconsistent views " + Util.printViews(different_views) + + " as inconsistent view threshold (" + inconsistent_view_threshold + + ") has not yet been reached (" + num_inconsistent_views + ")"); + num_inconsistent_views++; + return; + } + else + num_inconsistent_views=0; + } + else + num_inconsistent_views=0; + + if(log.isDebugEnabled()) { + StringBuilder sb=new StringBuilder(); + sb.append(local_addr + " found different views : " + Util.printViews(different_views) + + "; sending up MERGE event with merge participants " + merge_participants + ".\n"); + sb.append("Discovery results:\n"); + for(PingData data: discovery_rsps) + sb.append("[" + data.getAddress() + "]: coord=" + data.getCoordAddress()).append("\n"); + log.debug(sb.toString()); + } + Event evt=new Event(Event.MERGE, views); + try { + up_prot.up(evt); + } + catch(Throwable t) { + log.error("failed sending up MERGE event", t); } - if(log.isTraceEnabled()) - log.trace("MERGE2.FindSubgroups thread terminated (local_addr=" + local_addr + ")"); } + /** * Returns a random value within [min_interval - max_interval] */ @@ -221,35 +315,49 @@ return min_interval + Util.random(max_interval - min_interval); } - /** - * Returns a list of PingRsp pairs. - */ - List findInitialMembers() { - PingRsp tmp=new PingRsp(local_addr, local_addr, true); - List retval=(List)down_prot.down(new Event(Event.FIND_INITIAL_MBRS)); - if(retval != null && is_coord && local_addr != null && !retval.contains(tmp)) - retval.add(tmp); + /** Returns a list of PingData with only the view from members around the cluster */ + @SuppressWarnings("unchecked") + private List findAllViews() { + List retval=(List)down_prot.down(new Event(Event.FIND_ALL_VIEWS)); + if(retval == null) return Collections.emptyList(); + if(is_coord && local_addr != null) { + PingData tmp=new PingData(local_addr, view, true); + //let's make sure that we add ourself as a coordinator + if(!retval.contains(tmp)) + retval.add(tmp); + } return retval; } - /** - * Finds out if there is more than 1 coordinator in the initial_mbrs vector (contains PingRsp elements). - * @param initial_mbrs A list of PingRsp pairs - * @return Vector A list of the coordinators (Addresses) found. Will contain just 1 element for a correct - * membership, and more than 1 for multiple coordinators - */ - Vector
      detectMultipleCoordinators(List initial_mbrs) { - Vector
      ret=new Vector
      (11); - if(initial_mbrs != null) { - for(PingRsp response:initial_mbrs) { - if(response.isServer()) { - Address coord=response.getCoordAddress(); - if(!ret.contains(coord)) - ret.add(coord); - } - } + + + public Map getViews(List initial_mbrs) { + Map retval=new HashMap(); + for(PingData response: initial_mbrs) { + if(!response.isServer()) + continue; + Address sender=response.getAddress(); + View view=response.getView(); + if(sender == null || view == null) + continue; + retval.put(sender,view); + } + return retval; + } + + + public List detectDifferentViews(Map map) { + final List ret=new ArrayList(); + for(View view: map.values()) { + if(view == null) + continue; + ViewId vid=view.getVid(); + if(!Util.containsViewId(ret, vid)) + ret.add(view); } return ret; } + + } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/MERGE3.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/MERGE3.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/MERGE3.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/MERGE3.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: MERGE3.java,v 1.21 2008/06/06 15:57:54 vlada Exp $ package org.jgroups.protocols; @@ -7,14 +6,15 @@ import org.jgroups.annotations.DeprecatedProperty; import org.jgroups.annotations.Experimental; import org.jgroups.annotations.Property; +import org.jgroups.annotations.Unsupported; import org.jgroups.stack.Protocol; import org.jgroups.util.TimeScheduler; import org.jgroups.util.Util; -import java.io.IOException; -import java.io.ObjectInput; -import java.io.ObjectOutput; -import java.util.*; +import java.io.*; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; import java.util.concurrent.Future; @@ -27,17 +27,20 @@ * somewhere above this protocol (typically in the GMS protocol).

      * This protocol works as follows: *

        - *
      • If coordinator: periodically broadcast a "I'm the coordinator" message. If a coordinator receives such - * a message, it immediately initiates a merge by sending up a MERGE event + *
      • If coordinator: periodically broadcast its view. If another coordinator receives such a message, and its own + * view differs from the received view, it immediately initiates a merge by sending up a MERGE event, + * containing the received view and its own view *

        * - * Provides: sends MERGE event with list of coordinators up the stack
        + * Provides: sends MERGE event with list of different views up the stack
        * @author Bela Ban, Oct 16 2001 */ -@Experimental +@Experimental @Unsupported @DeprecatedProperty(names={"use_separate_thread"}) public class MERGE3 extends Protocol { Address local_addr=null; + View view; + @Property long min_interval=5000; // minimum time between executions of the FindSubgroups task @Property @@ -46,13 +49,7 @@ final Vector

        mbrs=new Vector
        (); TimeScheduler timer=null; Future announcer_task_future=null; - CoordinatorAnnouncer announcer_task=null; - final Set
        announcements=Collections.synchronizedSet(new HashSet
        ()); - - public String getName() { - return "MERGE3"; - } public void init() throws Exception { timer=getTransport().getTimer(); @@ -73,32 +70,23 @@ case Event.MSG: Message msg=(Message)evt.getArg(); - CoordAnnouncement hdr=(CoordAnnouncement)msg.getHeader(getName()); + CoordAnnouncement hdr=(CoordAnnouncement)msg.getHeader(this.id); if(hdr != null) { - if(hdr.coord_addr != null && is_coord) { - boolean contains; - contains=announcements.contains(hdr.coord_addr); - announcements.add(hdr.coord_addr); - if(log.isDebugEnabled()) { - if(contains) - log.debug("discarded duplicate announcement: " + hdr.coord_addr + - ", announcements=" + announcements); - else - log.debug("received announcement: " + hdr.coord_addr + ", announcements=" + announcements); - } - - if(announcements.size() > 1 && is_coord) { - processAnnouncements(); + if(is_coord) { + ViewId other=hdr.view.getViewId(); + if(!Util.sameViewId(other, view.getViewId())) { + Map views=new HashMap(); + views.put(local_addr, view); + views.put(msg.getSrc(), hdr.view); + if(log.isDebugEnabled()) + log.debug("detected different views (" + Util.printViews(views.values()) + "), sending up MERGE event"); + up_prot.up(new Event(Event.MERGE, views)); } } return null; } else return up_prot.up(evt); - - case Event.SET_LOCAL_ADDRESS: - local_addr=(Address)evt.getArg(); - break; } return up_prot.up(evt); @@ -113,7 +101,8 @@ case Event.VIEW_CHANGE: down_prot.down(evt); - tmp=((View)evt.getArg()).getMembers(); + view=(View)evt.getArg(); + tmp=view.getMembers(); mbrs.clear(); mbrs.addAll(tmp); coord=mbrs.elementAt(0); @@ -130,6 +119,10 @@ } } break; + + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; } return down_prot.down(evt); } @@ -137,11 +130,7 @@ void startCoordAnnouncerTask() { if(announcer_task_future == null || announcer_task_future.isDone()) { - announcements.add(local_addr); - announcer_task=new CoordinatorAnnouncer(); - announcer_task_future=timer.scheduleWithDynamicInterval(announcer_task); - if(log.isDebugEnabled()) - log.debug("coordinator announcement task started, announcements=" + announcements); + announcer_task_future=timer.scheduleWithDynamicInterval(new CoordinatorAnnouncer()); } } @@ -150,10 +139,6 @@ announcer_task_future.cancel(false); announcer_task_future=null; } - announcer_task=null; - announcements.clear(); - if(log.isDebugEnabled()) - log.debug("coordinator announcement task stopped"); } @@ -167,27 +152,13 @@ - void sendCoordinatorAnnouncement(Address coord) { - Message coord_announcement=new Message(); // multicast to all - CoordAnnouncement hdr=new CoordAnnouncement(coord); - coord_announcement.putHeader(getName(), hdr); - down_prot.down(new Event(Event.MSG, coord_announcement)); + void sendView() { + Message view_announcement=new Message(); // multicast to all + CoordAnnouncement hdr=new CoordAnnouncement(view); + view_announcement.putHeader(this.id, hdr); + down_prot.down(new Event(Event.MSG, view_announcement)); } - void processAnnouncements() { - if(announcements.size() > 1) { - Vector
        coords=new Vector
        (announcements); // create a clone - if(coords.size() > 1) { - if(log.isDebugEnabled()) - log.debug("passing up MERGE event, coords=" + coords); - - Event evt=new Event(Event.MERGE, coords); - up_prot.up(evt); - } - announcements.clear(); - announcements.add(local_addr); - } - } class CoordinatorAnnouncer implements TimeScheduler.Task { @@ -197,28 +168,32 @@ public void run() { if(is_coord) - sendCoordinatorAnnouncement(local_addr); + sendView(); } } public static class CoordAnnouncement extends Header { - Address coord_addr=null; + private View view; public CoordAnnouncement() { } - public CoordAnnouncement(Address coord) { - this.coord_addr=coord; + public CoordAnnouncement(View view) { + this.view=view; + } + + public void writeTo(DataOutputStream out) throws IOException { + Util.writeView(view, out); } - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - coord_addr=(Address)in.readObject(); + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + view=Util.readView(in); } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeObject(coord_addr); + public int size() { + return Util.size(view); } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/MERGEFAST.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/MERGEFAST.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/MERGEFAST.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/MERGEFAST.java 2011-10-18 11:22:35.000000000 +0000 @@ -3,42 +3,43 @@ import org.jgroups.*; import org.jgroups.annotations.Experimental; import org.jgroups.stack.Protocol; +import org.jgroups.util.Util; -import java.io.IOException; -import java.io.ObjectInput; -import java.io.ObjectOutput; +import java.io.*; +import java.util.HashMap; +import java.util.Map; import java.util.Vector; /** - * The coordinator attaches a small header to each (or every nth) message. If another coordinator in the - * same group sees the message, it will initiate the merge protocol immediately by sending a MERGE + * The coordinator attaches a small header with its view to each (or every nth) message. If another coordinator in + * the same group sees the message, it will initiate the merge protocol immediately by sending a MERGE * event up the stack. * @author Bela Ban, Aug 25 2003 */ @Experimental public class MERGEFAST extends Protocol { Address local_addr=null; + View view; boolean is_coord=false; - static final String name="MERGEFAST"; - - public String getName() { - return name; - } - public Object down(Event evt) { - if(is_coord == true && evt.getType() == Event.MSG && local_addr != null) { - Message msg=(Message)evt.getArg(); - Address dest=msg.getDest(); - if(dest == null || dest.isMulticastAddress()) { - msg.putHeader(getName(), new MergefastHeader(local_addr)); - } - } - - if(evt.getType() == Event.VIEW_CHANGE) { - handleViewChange((View)evt.getArg()); + switch(evt.getType()) { + case Event.MSG: + if(is_coord && view != null) { + Message msg=(Message)evt.getArg(); + Address dest=msg.getDest(); + if(dest == null || dest.isMulticastAddress()) { + msg.putHeader(this.id, new MergefastHeader(view)); + } + } + break; + case Event.VIEW_CHANGE: + handleViewChange((View)evt.getArg()); + break; + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; } - return down_prot.down(evt); } @@ -46,69 +47,58 @@ public Object up(Event evt) { switch(evt.getType()) { - case Event.SET_LOCAL_ADDRESS: - local_addr=(Address)evt.getArg(); - break; case Event.MSG: if(is_coord == false) // only handle message if we are coordinator break; Message msg=(Message)evt.getArg(); - MergefastHeader hdr=(MergefastHeader)msg.getHeader(name); + MergefastHeader hdr=(MergefastHeader)msg.getHeader(this.id); up_prot.up(evt); - if(hdr != null && local_addr != null) { - Address other_coord=hdr.coord; - if(!local_addr.equals(other_coord)) { - sendUpMerge(new Address[]{local_addr, other_coord}); + if(hdr != null && view != null) { + if(!Util.sameViewId(view.getViewId(), hdr.view.getViewId())) { + Map views=new HashMap(); + views.put(local_addr, view); + views.put(msg.getSrc(), hdr.view); + if(log.isDebugEnabled()) + log.debug("detected different views (" + Util.printViews(views.values()) + "), sending up MERGE event"); + up_prot.up(new Event(Event.MERGE, views)); } } return null; // event was already passed up - case Event.VIEW_CHANGE: - handleViewChange((View)evt.getArg()); - break; } return up_prot.up(evt); } - void handleViewChange(View v) { - Vector mbrs; - if(local_addr == null) - return; - mbrs=v.getMembers(); + protected void handleViewChange(View v) { + Vector
        mbrs=v.getMembers(); + view=v; is_coord=mbrs != null && !mbrs.isEmpty() && local_addr.equals(mbrs.firstElement()); } - /** - * @todo avoid sending up too many MERGE events. - */ - void sendUpMerge(Address[] addresses) { - Vector v=new Vector(11); - for(int i=0; i < addresses.length; i++) { - Address addr=addresses[i]; - v.add(addr); - } - up_prot.up(new Event(Event.MERGE, v)); - } + public static class MergefastHeader extends Header { - Address coord=null; + private View view=null; public MergefastHeader() { } - public MergefastHeader(Address coord) { - this.coord=coord; + public MergefastHeader(View view) { + this.view=view; } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeObject(coord); + public void writeTo(DataOutputStream out) throws IOException { + Util.writeView(view, out); } - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - coord=(Address)in.readObject(); + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + view=Util.readView(in); } + public int size() { + return Util.size(view); + } } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/MFC.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/MFC.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/MFC.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/MFC.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,162 @@ +package org.jgroups.protocols; + +import org.jgroups.Address; +import org.jgroups.Event; +import org.jgroups.Message; +import org.jgroups.annotations.MBean; +import org.jgroups.annotations.ManagedAttribute; +import org.jgroups.annotations.ManagedOperation; +import org.jgroups.util.CreditMap; +import org.jgroups.util.Tuple; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.Vector; + + +/** + * Simple flow control protocol based on a credit system. Each sender has a number of credits (bytes + * to send). When the credits have been exhausted, the sender blocks. Each receiver also keeps track of + * how many credits it has received from a sender. When credits for a sender fall below a threshold, + * the receiver sends more credits to the sender. Works for both unicast and multicast messages. + *

        + * Note that this protocol must be located towards the top of the stack, or all down_threads from JChannel to this + * protocol must be set to false ! This is in order to block JChannel.send()/JChannel.down(). + *
        This is the second simplified implementation of the same model. The algorithm is sketched out in + * doc/FlowControl.txt + *
        + * Changes (Brian) April 2006: + *

          + *
        1. Receivers now send credits to a sender when more than min_credits have been received (rather than when min_credits + * are left) + *
        2. Receivers don't send the full credits (max_credits), but rather the actual number of bytes received + *
            + * @author Bela Ban + */ +@MBean(description="Simple flow control protocol based on a credit system") +public class MFC extends FlowControl { + + + + /* --------------------------------------------- Fields ------------------------------------------------------ */ + + + /** Maintains credits per member */ + protected CreditMap credits; + + + /** Last time a credit request was sent. Used to prevent credit request storms */ + protected long last_credit_request=0; + + + + /** Allows to unblock a blocked sender from an external program, e.g. JMX */ + @ManagedOperation(description="Unblock a sender") + public void unblock() { + if(log.isTraceEnabled()) + log.trace("unblocking the sender and replenishing all members"); + credits.replenishAll(); + } + + @ManagedOperation(description="Print credits") + public String printCredits() { + return super.printCredits() + "\nsenders min credits: " + credits.computeLowestCreditWithAccumulated(); + } + + @ManagedOperation(description="Print sender credits") + public String printSenderCredits() { + return credits.toString(); + } + + @ManagedAttribute(description="Number of times flow control blocks sender") + public int getNumberOfBlockings() { + return credits.getNumBlockings(); + } + + @ManagedAttribute(description="Total time (ms) spent in flow control block") + public long getTotalTimeBlocked() { + return credits.getTotalBlockTime(); + } + + protected boolean handleMulticastMessage() { + return true; + } + + + public void init() throws Exception { + super.init(); + credits=new CreditMap(max_credits); + } + + public void stop() { + super.stop(); + credits.clear(); + } + + protected Object handleDownMessage(final Event evt, final Message msg, Address dest, int length) { + if(dest != null && !dest.isMulticastAddress()) { // 2nd line of defense, not really needed + log.error(getClass().getSimpleName() + " doesn't handle unicast messages; passing message down"); + return down_prot.down(evt); + } + + long block_time=max_block_times != null? getMaxBlockTime(length) : max_block_time; + while(running) { + boolean rc=credits.decrement(length, block_time); + if(rc || max_block_times != null || !running) + break; + + if(needToSendCreditRequest()) { + List> targets=credits.getMembersWithCreditsLessThan(min_credits); + for(Tuple tuple: targets) + sendCreditRequest(tuple.getVal1(), Math.min(max_credits, max_credits - tuple.getVal2())); + } + } + + // send message - either after regular processing, or after blocking (when enough credits are available again) + return down_prot.down(evt); + } + + + + + protected synchronized boolean needToSendCreditRequest() { + long curr_time=System.currentTimeMillis(); + long wait_time=curr_time - last_credit_request; + if(wait_time >= max_block_time) { + last_credit_request=curr_time; + return true; + } + return false; + } + + + + protected void handleCredit(Address sender, long increase) { + credits.replenish(sender, increase); + if(log.isTraceEnabled()) { + StringBuilder sb=new StringBuilder(); + sb.append("received " + increase + " credits from ").append(sender).append(", new credits for " + sender + " : ") + .append(credits.get(sender) + ", min_credits=" + credits.getMinCredits()); + log.trace(sb); + } + } + + + protected void handleViewChange(Vector
            mbrs) { + super.handleViewChange(mbrs); + + Set
            keys=new HashSet
            (credits.keys()); + for(Address key: keys) { + if(!mbrs.contains(key)) + credits.remove(key); + } + + for(Address key: mbrs) + credits.putIfAbsent(key); + } + + + + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/MPING.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/MPING.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/MPING.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/MPING.java 2011-10-18 11:22:35.000000000 +0000 @@ -3,8 +3,9 @@ import org.jgroups.Event; import org.jgroups.Global; import org.jgroups.Message; -import org.jgroups.annotations.ManagedAttribute; +import org.jgroups.annotations.LocalAddress; import org.jgroups.annotations.Property; +import org.jgroups.annotations.DeprecatedProperty; import org.jgroups.conf.PropertyConverters; import org.jgroups.util.*; @@ -20,36 +21,44 @@ * back via the regular transport (e.g. TCP) to the sender (discovery request contained sender's regular address, * e.g. 192.168.0.2:7800). * @author Bela Ban - * @version $Id: MPING.java,v 1.46 2009/01/05 08:24:06 belaban Exp $ */ +@DeprecatedProperty(names="bind_to_all_interfaces") public class MPING extends PING implements Runnable { private static final boolean can_bind_to_mcast_addr; // are we running on Linux ? static { - can_bind_to_mcast_addr=Util.checkForLinux() || Util.checkForSolaris(); + can_bind_to_mcast_addr=Util.checkForLinux() || Util.checkForSolaris() || Util.checkForHp(); } /* ----------------------------------------- Properties -------------------------------------------------- */ - @ManagedAttribute(description="Bind address for multicast socket", writable=true) - @Property(converter=PropertyConverters.BindAddress.class, description="Bind address for multicast socket") + @LocalAddress + @Property(description="Bind address for multicast socket. " + + "The following special values are also recognized: GLOBAL, SITE_LOCAL, LINK_LOCAL and NON_LOOPBACK", + systemProperty={Global.BIND_ADDR, Global.BIND_ADDR_OLD}, + defaultValueIPv4=Global.NON_LOOPBACK_ADDRESS, defaultValueIPv6=Global.NON_LOOPBACK_ADDRESS) InetAddress bind_addr=null; - @Property(description="Time to live for discovery packets. Default is 8") - @ManagedAttribute(description="Time to live for discovery packets", writable=true) + @Property(name="bind_interface", converter=PropertyConverters.BindInterface.class, + description="The interface (NIC) which should be used by this transport",dependsUpon="bind_addr") + protected String bind_interface_str=null; + + + @Property(description="Time to live for discovery packets. Default is 8", systemProperty=Global.MPING_IP_TTL) int ip_ttl=8; - @ManagedAttribute(description="Multicast address for discovery packets", writable=true) + @Property(name="mcast_addr", systemProperty=Global.MPING_MCAST_ADDR, + defaultValueIPv4="230.5.6.7", defaultValueIPv6="ff0e::5:6:7") InetAddress mcast_addr=null; - @Property(description="Multicast port for discovery packets. Default is 7555") - @ManagedAttribute(description="Multicast port for discovery packets", writable=true) + + @Property(description="Multicast port for discovery packets. Default is 7555", systemProperty=Global.MPING_MCAST_PORT) int mcast_port=7555; - @Property(name="bind_to_all_interfaces", deprecatedMessage="bind_to_all_interfaces has been deprecated; use receive_on_all_interfaces instead", description="If true, the transport should use all available interfaces to receive multicast messages. Default is false") + @Property(description="If true, the transport should use all available interfaces to receive multicast messages. Default is false") boolean receive_on_all_interfaces=false; /** @@ -96,22 +105,11 @@ private volatile Thread receiver=null; - /** - * Pre-allocated byte stream. Used for serializing datagram packets. Will - * grow as needed - */ - private final ExposedByteArrayOutputStream out_stream=new ExposedByteArrayOutputStream(128); - - private final byte receive_buf[]=new byte[1024]; public MPING() { } - public String getName() { - return "MPING"; - } - public InetAddress getBindAddr() { return bind_addr; } @@ -120,22 +118,18 @@ this.bind_addr=bind_addr; } - @ManagedAttribute(description="Receive interfaces") public List getReceiveInterfaces() { return receive_interfaces; } - @ManagedAttribute(description="Send interfaces") public List getSendInterfaces() { return send_interfaces; } - @ManagedAttribute public boolean isReceiveOnAllInterfaces() { return receive_on_all_interfaces; } - @ManagedAttribute public boolean isSendOnAllInterfaces() { return send_on_all_interfaces; } @@ -156,7 +150,6 @@ this.mcast_addr=mcast_addr; } - @Property(name="mcast_addr") public void setMulticastAddress(String addr) throws UnknownHostException { mcast_addr=InetAddress.getByName(addr); } @@ -171,7 +164,7 @@ - + @SuppressWarnings("unchecked") public Object up(Event evt) { if(evt.getType() == Event.CONFIG) { if(bind_addr == null) { @@ -186,29 +179,15 @@ public void init() throws Exception { super.init(); - - String str=Util.getProperty(new String[]{Global.MPING_MCAST_ADDR}, null, "mcast_addr", false, null); - if(str != null) - mcast_addr=InetAddress.getByName(str); - - str=Util.getProperty(new String[]{Global.MPING_MCAST_PORT}, null, "mcast_port", false, null); - if(str != null) - mcast_port=Integer.parseInt(str); - - str=Util.getProperty(new String[]{Global.MPING_IP_TTL}, null, "ip_ttl", false, null); - if(str != null) - ip_ttl=Integer.parseInt(str); - - if(mcast_addr == null) { - mcast_addr=InetAddress.getByName("230.5.6.7"); - } + if(log.isDebugEnabled()) + log.debug("bind_addr=" + bind_addr + " mcast_addr=" + mcast_addr + ", mcast_port=" + mcast_port); } public void start() throws Exception { if(can_bind_to_mcast_addr) // https://jira.jboss.org/jira/browse/JGRP-836 - prevent cross talking on Linux - mcast_sock=Util.createMulticastSocket(mcast_addr, mcast_port, log); + mcast_sock=Util.createMulticastSocket(getSocketFactory(), Global.MPING_MCAST_SOCK, mcast_addr, mcast_port, log); else - mcast_sock=new MulticastSocket(mcast_port); + mcast_sock=getSocketFactory().createMulticastSocket(Global.MPING_MCAST_SOCK, mcast_port); mcast_sock.setTimeToLive(ip_ttl); @@ -279,7 +258,7 @@ } public void stop() { - mcast_sock.close(); + Util.close(mcast_sock); mcast_sock=null; receiver=null; super.stop(); @@ -293,7 +272,7 @@ try { if(msg.getSrc() == null) msg.setSrc(local_addr); - out_stream.reset(); + ExposedByteArrayOutputStream out_stream=new ExposedByteArrayOutputStream(128); out=new DataOutputStream(out_stream); msg.writeTo(out); out.flush(); // flushes contents to out_stream @@ -329,9 +308,10 @@ public void run() { + final byte[] receive_buf=new byte[65535]; DatagramPacket packet=new DatagramPacket(receive_buf, receive_buf.length); byte[] data; - ByteArrayInputStream inp_stream=null; + ByteArrayInputStream inp_stream; DataInputStream inp=null; Message msg; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/ENCRYPT1_4.java.txt libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/ENCRYPT1_4.java.txt --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/ENCRYPT1_4.java.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/ENCRYPT1_4.java.txt 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ // replacing SecretKey with SecretKey -// $Id: ENCRYPT1_4.java.txt,v 1.1 2007/01/11 11:42:28 belaban Exp $ package org.jgroups.protocols.obsolete; @@ -12,7 +11,7 @@ import org.jgroups.Event; import org.jgroups.Message; import org.jgroups.View; -import org.jgroups.protocols.PingRsp; +import org.jgroups.protocols.PingData; import org.jgroups.stack.Protocol; import javax.crypto.Cipher; @@ -200,7 +199,7 @@ keyServer=member.size() <= 0; if(member != null && member.size() > 0) - keyServerAddr=((PingRsp) member.firstElement()).coord_addr; + keyServerAddr=((PingData) member.firstElement()).coord_addr; else keyServerAddr=local_addr; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/ENCRYPT.java.txt libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/ENCRYPT.java.txt --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/ENCRYPT.java.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/ENCRYPT.java.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: ENCRYPT.java.txt,v 1.3 2005/05/30 14:31:05 belaban Exp $ package org.jgroups.protocols; @@ -149,7 +148,7 @@ keyServer = member.size() > 0 ? false : true; if (member != null && member.size() > 0) - keyServerAddr = (Address)((PingRsp)member.firstElement()).coord_addr; + keyServerAddr = (Address)((PingData)member.firstElement()).coord_addr; else keyServerAddr = local_addr; System.out.println("keyServer = " + keyServer + " keyServerAddr : "+keyServerAddr.toString()); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/FC.java.txt libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/FC.java.txt --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/FC.java.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/FC.java.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: FC.java.txt,v 1.2 2008/01/22 10:44:29 belaban Exp $ package org.jgroups.protocols; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/FD_PROB.java.txt libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/FD_PROB.java.txt --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/FD_PROB.java.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/FD_PROB.java.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: FD_PROB.java.txt,v 1.2 2008/01/22 10:44:29 belaban Exp $ package org.jgroups.protocols; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/FD_RAND.java.txt libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/FD_RAND.java.txt --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/FD_RAND.java.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/FD_RAND.java.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: FD_RAND.java.txt,v 1.3 2005/05/30 14:31:05 belaban Exp $ package org.jgroups.protocols; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/FD_SHUN.java.txt libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/FD_SHUN.java.txt --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/FD_SHUN.java.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/FD_SHUN.java.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: FD_SHUN.java.txt,v 1.3 2005/05/30 14:31:05 belaban Exp $ package org.jgroups.protocols; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/PerfHeader.java.txt libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/PerfHeader.java.txt --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/PerfHeader.java.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/PerfHeader.java.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: PerfHeader.java.txt,v 1.2 2008/01/22 10:44:29 belaban Exp $ package org.jgroups.protocols; @@ -6,8 +5,8 @@ import org.jgroups.Message; import org.jgroups.stack.Protocol; import org.jgroups.util.Util; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import java.io.*; import java.util.HashMap; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/PERF.java.txt libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/PERF.java.txt --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/PERF.java.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/PERF.java.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: PERF.java.txt,v 1.1 2006/12/27 16:44:40 belaban Exp $ package org.jgroups.protocols; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/TCP.java.txt libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/TCP.java.txt --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/TCP.java.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/TCP.java.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,493 +0,0 @@ -// $Id: TCP.java.txt,v 1.1 2005/06/27 08:48:12 belaban Exp $ - -package org.jgroups.protocols; - - -import org.jgroups.Address; -import org.jgroups.Event; -import org.jgroups.Message; -import org.jgroups.View; -import org.jgroups.blocks.ConnectionTable; -import org.jgroups.stack.IpAddress; -import org.jgroups.stack.Protocol; -import org.jgroups.util.BoundedList; -import org.jgroups.util.Util; - -import java.net.InetAddress; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.util.HashMap; -import java.util.Properties; -import java.util.Vector; - - - - -/** - * TCP based protocol. Creates a server socket, which gives us the local address of this group member. For - * each accept() on the server socket, a new thread is created that listens on the socket. - * For each outgoing message m, if m.dest is in the ougoing hashtable, the associated socket will be reused - * to send message, otherwise a new socket is created and put in the hashtable. - * When a socket connection breaks or a member is removed from the group, the corresponding items in the - * incoming and outgoing hashtables will be removed as well.
            - * This functionality is in ConnectionTable, which isT used by TCP. TCP sends messages using ct.send() and - * registers with the connection table to receive all incoming messages. - * @author Bela Ban - */ -public class TCP extends Protocol implements ConnectionTable.Receiver { - private ConnectionTable ct=null; - protected Address local_addr=null; - private String group_addr=null; - private InetAddress bind_addr=null; // local IP address to bind srv sock to (m-homed systems) - private InetAddress external_addr=null; // the IP address which is broadcast to other group members - private int start_port=7800; // find first available port starting at this port - private int end_port=0; // maximum port to bind to - private final Vector members=new Vector(11); - private long reaper_interval=0; // time in msecs between connection reaps - private long conn_expire_time=0; // max time a conn can be idle before being reaped - boolean loopback=false; // loops back msgs to self if true - - /** If set it will be added to local_addr. Used to implement - * for example transport independent addresses */ - byte[] additional_data=null; - - /** List the maintains the currently suspected members. This is used so we don't send too many SUSPECT - * events up the stack (one per message !) - */ - final BoundedList suspected_mbrs=new BoundedList(20); - - /** Should we drop unicast messages to suspected members or not */ - boolean skip_suspected_members=true; - - int recv_buf_size=150000; - int send_buf_size=150000; - int sock_conn_timeout=2000; // max time in millis for a socket creation in ConnectionTable - - static final String name="TCP"; - static final String IGNORE_BIND_ADDRESS_PROPERTY="ignore.bind.address"; - - int num_msgs_sent=0, num_msgs_received=0; - - - public TCP() { - } - - public String toString() { - return "Protocol TCP(local address: " + local_addr + ')'; - } - - public String getName() { - return "TCP"; - } - - public long getNumMessagesSent() {return num_msgs_sent;} - public long getNumMessagesReceived() {return num_msgs_received;} - public int getOpenConnections() {return ct.getNumConnections();} - public InetAddress getBindAddr() {return bind_addr;} - public void setBindAddr(InetAddress bind_addr) {this.bind_addr=bind_addr;} - public int getStartPort() {return start_port;} - public void setStartPort(int start_port) {this.start_port=start_port;} - public int getEndPort() {return end_port;} - public void setEndPort(int end_port) {this.end_port=end_port;} - public long getReaperInterval() {return reaper_interval;} - public void setReaperInterval(long reaper_interval) {this.reaper_interval=reaper_interval;} - public long getConnExpireTime() {return conn_expire_time;} - public void setConnExpireTime(long conn_expire_time) {this.conn_expire_time=conn_expire_time;} - public boolean isLoopback() {return loopback;} - public void setLoopback(boolean loopback) {this.loopback=loopback;} - public String printConnections() {return ct.toString();} - - - public void resetStats() { - super.resetStats(); - num_msgs_sent=num_msgs_received=0; - } - - protected final Vector getMembers() { - return members; - } - - /** - DON'T REMOVE ! This prevents the up-handler thread to be created, which essentially is superfluous: - messages are received from the network rather than from a layer below. - */ - public void startUpHandler() { - ; - } - - - public void start() throws Exception { - ct=getConnectionTable(reaper_interval,conn_expire_time,bind_addr,external_addr,start_port,end_port); - // ct.addConnectionListener(this); - ct.setReceiveBufferSize(recv_buf_size); - ct.setSendBufferSize(send_buf_size); - ct.setSocketConnectionTimeout(sock_conn_timeout); - local_addr=ct.getLocalAddress(); - if(additional_data != null && local_addr instanceof IpAddress) - ((IpAddress)local_addr).setAdditionalData(additional_data); - passUp(new Event(Event.SET_LOCAL_ADDRESS, local_addr)); - } - - /** - * @param reaperInterval - * @param connExpireTime - * @param bindAddress - * @param startPort - * @throws Exception - * @return ConnectionTable - * Sub classes overrides this method to initialize a different version of - * ConnectionTable. - */ - protected ConnectionTable getConnectionTable(long reaperInterval, long connExpireTime, InetAddress bindAddress, - InetAddress externalAddress, int startPort, int endPort) throws Exception { - ConnectionTable cTable=null; - if(reaperInterval == 0 && connExpireTime == 0) { - cTable=new ConnectionTable(this, bindAddress, externalAddress, startPort, endPort); - } - else { - if(reaperInterval == 0) { - reaperInterval=5000; - if(log.isWarnEnabled()) log.warn("reaper_interval was 0, set it to " + reaperInterval); - } - if(connExpireTime == 0) { - connExpireTime=1000 * 60 * 5; - if(log.isWarnEnabled()) log.warn("conn_expire_time was 0, set it to " + connExpireTime); - } - cTable=new ConnectionTable(this, bindAddress, externalAddress, startPort, endPort, - reaperInterval, connExpireTime); - } - return cTable; - } - - public void stop() { - ct.stop(); - } - - - /** - Sent to destination(s) using the ConnectionTable class. - */ - public void down(Event evt) { - Message msg; - Object dest_addr; - - if(evt.getType() != Event.MSG) { - handleDownEvent(evt); - return; - } - - msg=(Message)evt.getArg(); - num_msgs_sent++; - - if(group_addr != null) { // added patch sent by Roland Kurmann (bela March 20 2003) - /* Add header (includes channel name) */ - msg.putHeader(name, new TcpHeader(group_addr)); - } - - dest_addr=msg.getDest(); - - - /* Because we don't call Protocol.passDown(), we notify the observer directly (e.g. PerfObserver). This way, - we still have performance numbers for TCP */ - if(observer != null) - observer.passDown(evt); - - if(dest_addr == null) { // broadcast (to all members) - if(group_addr == null) { - if(log.isWarnEnabled()) log.warn("dest address of message is null, and " + - "sending to default address fails as group_addr is null, too !" + - " Discarding message."); - return; - } - else { - sendMulticastMessage(msg); // send to current membership - } - } - else { - sendUnicastMessage(msg); // send to a single member - } - } - - - /** ConnectionTable.Receiver interface */ - public void receive(Message msg) { - TcpHeader hdr=null; - Event evt=new Event(Event.MSG, msg); - - - /* Because Protocol.up() is never called by this bottommost layer, we call up() directly in the observer. - This allows e.g. PerfObserver to get the time of reception of a message */ - if(observer != null) - observer.up(evt, up_queue.size()); - - if(log.isTraceEnabled()) log.trace("received msg " + msg); - num_msgs_received++; - - hdr=(TcpHeader)msg.removeHeader(name); - - if(hdr != null) { - /* Discard all messages destined for a channel with a different name */ - String ch_name=null; - - if(hdr.group_addr != null) - ch_name=hdr.group_addr; - - // below lines were commented as patch sent by Roland Kurmann (bela March 20 2003) - -// if(group_addr == null) { -// if(log.isWarnEnabled()) log.warn("TCP.receive()", "group address in header was null, discarded"); -// return; -// } - - // Discard if message's group name is not the same as our group name unless the - // message is a diagnosis message (special group name DIAG_GROUP) - if(ch_name != null && !group_addr.equals(ch_name) && - !ch_name.equals(Util.DIAG_GROUP)) { - if(log.isWarnEnabled()) log.warn("discarded message from different group (" + - ch_name + "). Sender was " + msg.getSrc()); - return; - } - } - - passUp(evt); - } - - - // ConnectionTable.ConnectionListener interface -// public void connectionOpened(Address peer_addr) { -// if(log.isTraceEnabled()) log.trace("opened connection to " + peer_addr); -// } -// -// public void connectionClosed(Address peer_addr) { -// if(peer_addr != null) -// if(log.isTraceEnabled()) log.trace("closed connection to " + peer_addr); -// } - - - /** Setup the Protocol instance acording to the configuration string */ - public boolean setProperties(Properties props) { - String str, tmp=null; - - super.setProperties(props); - str=props.getProperty("start_port"); - if(str != null) { - start_port=Integer.parseInt(str); - props.remove("start_port"); - } - - str=props.getProperty("end_port"); - if(str != null) { - end_port=Integer.parseInt(str); - props.remove("end_port"); - } - - // PropertyPermission not granted if running in an untrusted environment with JNLP. - try { - tmp=System.getProperty("bind.address"); // set by JBoss - if(Boolean.getBoolean(IGNORE_BIND_ADDRESS_PROPERTY)) { - tmp=null; - } - } - catch (SecurityException ex){ - } - - if(tmp != null) - str=tmp; - else - str=props.getProperty("bind_addr"); - if(str != null) { - try { - bind_addr=InetAddress.getByName(str); - } - catch(UnknownHostException unknown) { - if(log.isFatalEnabled()) log.fatal("(bind_addr): host " + str + " not known"); - return false; - } - props.remove("bind_addr"); - } - - str=props.getProperty("external_addr"); - if(str != null) { - try { - external_addr=InetAddress.getByName(str); - } - catch(UnknownHostException unknown) { - if(log.isFatalEnabled()) log.fatal("(external_addr): host " + str + " not known"); - return false; - } - props.remove("external_addr"); - } - - str=props.getProperty("reaper_interval"); - if(str != null) { - reaper_interval=Long.parseLong(str); - props.remove("reaper_interval"); - } - - str=props.getProperty("conn_expire_time"); - if(str != null) { - conn_expire_time=Long.parseLong(str); - props.remove("conn_expire_time"); - } - - str=props.getProperty("sock_conn_timeout"); - if(str != null) { - sock_conn_timeout=Integer.parseInt(str); - props.remove("sock_conn_timeout"); - } - - str=props.getProperty("recv_buf_size"); - if(str != null) { - recv_buf_size=Integer.parseInt(str); - props.remove("recv_buf_size"); - } - - str=props.getProperty("send_buf_size"); - if(str != null) { - send_buf_size=Integer.parseInt(str); - props.remove("send_buf_size"); - } - - str=props.getProperty("loopback"); - if(str != null) { - loopback=Boolean.valueOf(str).booleanValue(); - props.remove("loopback"); - } - - str=props.getProperty("skip_suspected_members"); - if(str != null) { - skip_suspected_members=Boolean.valueOf(str).booleanValue(); - props.remove("skip_suspected_members"); - } - - if(props.size() > 0) { - log.error("TCP.setProperties(): the following properties are not recognized: " + props); - - return false; - } - return true; - } - - - /** - If the sender is null, set our own address. We cannot just go ahead and set the address - anyway, as we might be sending a message on behalf of someone else ! E.g. in case of - retransmission, when the original sender has crashed, or in a FLUSH protocol when we - have to return all unstable messages with the FLUSH_OK response. - */ - private void setSourceAddress(Message msg) { - if(msg.getSrc() == null) - msg.setSrc(local_addr); - } - - - /** Send a message to the address specified in msg.dest */ - private void sendUnicastMessage(Message msg) { - IpAddress dest; - Message copy; - Object hdr; - Event evt; - - dest=(IpAddress)msg.getDest(); // guaranteed not to be null - if(!(dest instanceof IpAddress)) { - if(log.isErrorEnabled()) log.error("destination address is not of type IpAddress !"); - return; - } - setSourceAddress(msg); - - /* Don't send if destination is local address. Instead, switch dst and src and put in up_queue */ - if(loopback && local_addr != null && dest != null && dest.equals(local_addr)) { - copy=msg.copy(); - hdr=copy.getHeader(name); - if(hdr != null && hdr instanceof TcpHeader) - copy.removeHeader(name); - copy.setSrc(local_addr); - copy.setDest(local_addr); - - evt=new Event(Event.MSG, copy); - - /* Because Protocol.up() is never called by this bottommost layer, we call up() directly in the observer. - This allows e.g. PerfObserver to get the time of reception of a message */ - if(observer != null) - observer.up(evt, up_queue.size()); - - passUp(evt); - return; - } - if(log.isTraceEnabled()) log.trace("dest=" + msg.getDest() + ", hdrs:\n" + msg.printObjectHeaders()); - try { - if(skip_suspected_members) { - if(suspected_mbrs.contains(dest)) { - if(log.isTraceEnabled()) log.trace("will not send unicast message to " + dest + - " as it is currently suspected"); - return; - } - } - ct.send(msg); - } - catch(SocketException e) { - if(members.contains(dest)) { - if(!suspected_mbrs.contains(dest)) { - suspected_mbrs.add(dest); - passUp(new Event(Event.SUSPECT, dest)); - } - } - } - } - - - protected void sendMulticastMessage(Message msg) { - Address dest; - Vector mbrs=(Vector)members.clone(); - for(int i=0; i < mbrs.size(); i++) { - dest=(Address)mbrs.elementAt(i); - msg.setDest(dest); - sendUnicastMessage(msg); - } - } - - - protected void handleDownEvent(Event evt) { - switch(evt.getType()) { - - case Event.TMP_VIEW: - case Event.VIEW_CHANGE: - suspected_mbrs.removeAll(); - synchronized(members) { - members.clear(); - members.addAll(((View)evt.getArg()).getMembers()); - } - break; - - case Event.GET_LOCAL_ADDRESS: // return local address -> Event(SET_LOCAL_ADDRESS, local) - passUp(new Event(Event.SET_LOCAL_ADDRESS, local_addr)); - break; - - case Event.CONNECT: - group_addr=(String)evt.getArg(); - - // removed March 18 2003 (bela), not needed (handled by GMS) - // Can't remove it; otherwise TCPGOSSIP breaks (bela May 8 2003) ! - passUp(new Event(Event.CONNECT_OK)); - break; - - case Event.DISCONNECT: - passUp(new Event(Event.DISCONNECT_OK)); - break; - - case Event.CONFIG: - if(log.isTraceEnabled()) log.trace("received CONFIG event: " + evt.getArg()); - handleConfigEvent((HashMap)evt.getArg()); - break; - } - } - - - void handleConfigEvent(HashMap map) { - if(map == null) return; - if(map.containsKey("additional_data")) - additional_data=(byte[])map.get("additional_data"); - } - - -} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/UDP.java.txt libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/UDP.java.txt --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/UDP.java.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/UDP.java.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: UDP.java.txt,v 1.2 2008/01/22 10:44:29 belaban Exp $ package org.jgroups.protocols; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/UDP_NIO.java.txt libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/UDP_NIO.java.txt --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/UDP_NIO.java.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/UDP_NIO.java.txt 2011-10-18 11:22:35.000000000 +0000 @@ -6,8 +6,8 @@ import org.jgroups.util.Queue; import org.jgroups.util.QueueClosedException; import org.jgroups.util.Util; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import java.io.*; import java.net.*; @@ -32,7 +32,6 @@ * the unicast routing caches should ensure that unicasts are only sent via 1 interface in almost all cases. * * @author Bela Ban Oct 2003 - * @version $Id: UDP_NIO.java.txt,v 1.2 2008/01/22 10:44:29 belaban Exp $ * @deprecated Use UDP instead with send_interfaces or receive_interfaces properties defined */ public class UDP_NIO extends Protocol implements Receiver { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/UNIFORM.java.txt libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/UNIFORM.java.txt --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/obsolete/UNIFORM.java.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/obsolete/UNIFORM.java.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: UNIFORM.java.txt,v 1.3 2005/05/30 14:31:05 belaban Exp $ package org.jgroups.protocols; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/PARTITION.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/PARTITION.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/PARTITION.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/PARTITION.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,71 +0,0 @@ - -package org.jgroups.protocols; - -import org.jgroups.Address; -import org.jgroups.Event; -import org.jgroups.Message; -import org.jgroups.annotations.MBean; -import org.jgroups.annotations.ManagedAttribute; -import org.jgroups.annotations.ManagedOperation; -import org.jgroups.stack.Protocol; - - -/** - * Protocol to simulate a partition. This can be done in 2 ways: send down a PARTITION event (with a boolean flag on or - * off, to start or end a partition), or by grabbing a reference to the protocol via the ProtocolStack and calling the - * methods startPartition() or stopPartition() directly. This can also be done via JMX.

            - * A partition simply discards all messages, but let's other events pass. - * @author Bela Ban - * @version $Id: PARTITION.java,v 1.4 2008/03/08 09:46:46 vlada Exp $ - */ -@MBean(description="Protocol to simulate a partition") -public class PARTITION extends Protocol { - @ManagedAttribute - protected boolean partition_on=false; - protected Address local_address=null; - - public String getName() { - return "PARTITION"; - } - - public boolean isPartitionOn() { - return partition_on; - } - - @ManagedOperation - public void startPartition() { - partition_on=true; - } - - @ManagedOperation - public void stopPartition() { - partition_on=false; - } - - - public Object down(Event evt) { - switch(evt.getType()) { - case Event.START_PARTITION: - startPartition(); - return null; - case Event.STOP_PARTITION: - stopPartition(); - return null; - default: - return down_prot.down(evt); - } - } - - public Object up(Event evt) { - if(evt.getType() == Event.SET_LOCAL_ADDRESS) - local_address=(Address)evt.getArg(); - if(partition_on == false) - return up_prot.up(evt); - if(evt.getType() != Event.MSG) - return up_prot.up(evt); - Message msg=(Message)evt.getArg(); - if(msg.getSrc().equals(local_address)) - return up_prot.up(evt); - return null; - } -} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/ClientGmsImpl.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/ClientGmsImpl.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/ClientGmsImpl.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/ClientGmsImpl.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,10 +1,8 @@ -// $Id: ClientGmsImpl.java,v 1.65 2008/09/18 14:45:34 vlada Exp $ - package org.jgroups.protocols.pbcast; import org.jgroups.*; -import org.jgroups.protocols.PingRsp; +import org.jgroups.protocols.PingData; import org.jgroups.util.Promise; import org.jgroups.util.Util; import org.jgroups.util.Digest; @@ -21,7 +19,7 @@ * ViewChange which is called by the coordinator that was contacted by this client, to * tell the client what its initial membership is. * @author Bela Ban - * @version $Revision: 1.65 $ + * @version $Revision: 1.78 $ */ public class ClientGmsImpl extends GmsImpl { private final Promise join_promise=new Promise(); @@ -36,14 +34,13 @@ join_promise.reset(); } - public void join(Address address) { - join(address, false); + public void join(Address address,boolean useFlushIfPresent) { + joinInternal(address, false,useFlushIfPresent); } - - public void joinWithStateTransfer(Address address) { - join(address, true); - } + public void joinWithStateTransfer(Address local_addr, boolean useFlushIfPresent) { + joinInternal(local_addr,true,useFlushIfPresent); + } /** * Joins this process to a group. Determines the coordinator and sends a @@ -64,7 +61,7 @@ * * @param mbr Our own address (assigned through SET_LOCAL_ADDRESS) */ - private void join(Address mbr, boolean joinWithStateTransfer) { + private void joinInternal(Address mbr, boolean joinWithStateTransfer,boolean useFlushIfPresent) { Address coord=null; JoinRsp rsp=null; View tmp_view; @@ -73,12 +70,16 @@ join_promise.reset(); while(!leaving) { if(rsp == null && !join_promise.hasResult()) { // null responses means that the discovery was cancelled - List responses=findInitialMembers(join_promise); - // Sept 2008 (bela): break if we got a belated JoinRsp (https://jira.jboss.org/jira/browse/JGRP-687) + List responses=findInitialMembers(join_promise); + if (responses == null) { + // gray: we've seen this NPE here. not sure of the cases but wanted to add more debugging info + throw new NullPointerException("responses returned by findInitialMembers for " + join_promise + " is null"); + } + /*// Sept 2008 (bela): break if we got a belated JoinRsp (https://jira.jboss.org/jira/browse/JGRP-687) if(join_promise.hasResult()) { rsp=join_promise.getResult(gms.join_timeout); // clears the result continue; - } + }*/ if(log.isDebugEnabled()) log.debug("initial_mbrs are " + responses); if(responses == null || responses.isEmpty()) { @@ -108,7 +109,7 @@ // so the member to become singleton member (and thus coord) is the first of all clients Set

            clients=new TreeSet
            (); // sorted clients.add(mbr); // add myself again (was removed by findInitialMembers()) - for(PingRsp response: responses) { + for(PingData response: responses) { Address client_addr=response.getAddress(); if(client_addr != null) clients.add(client_addr); @@ -133,7 +134,7 @@ if(log.isDebugEnabled()) log.debug("sending handleJoin(" + mbr + ") to " + coord); - sendJoinMessage(coord, mbr, joinWithStateTransfer); + sendJoinMessage(coord, mbr, joinWithStateTransfer,useFlushIfPresent); } try { @@ -151,7 +152,6 @@ throw new SecurityException(failure); // 2. Install digest - // See if the digest does not have senders, this seems to happen on high volume startup if(rsp.getDigest() == null || rsp.getDigest().getSenders() == null) { if(log.isWarnEnabled()) log.warn("digest response has no senders: digest=" + rsp.getDigest()); @@ -170,7 +170,7 @@ throw new IllegalStateException("digest returned from " + coord + " with JOIN_RSP does not contain myself (" + gms.local_addr + "): join response: " + rsp); } - tmp_digest.incrementHighestDeliveredSeqno(coord); // see DESIGN for details + tmp_digest.incrementHighestDeliveredSeqno(coord); // see doc/design/varia2.txt for details tmp_digest.seal(); gms.setDigest(tmp_digest); @@ -188,9 +188,7 @@ Message view_ack=new Message(coord, null, null); view_ack.setFlag(Message.OOB); GMS.GmsHeader tmphdr=new GMS.GmsHeader(GMS.GmsHeader.VIEW_ACK); - view_ack.putHeader(GMS.name, tmphdr); - if(!gms.members.contains(coord)) - gms.getDownProtocol().down(new Event(Event.ENABLE_UNICASTS_TO, coord)); + view_ack.putHeader(gms.getId(), tmphdr); gms.getDownProtocol().down(new Event(Event.MSG, view_ack)); return; } @@ -203,16 +201,17 @@ } catch(Throwable e) { if(log.isDebugEnabled()) - log.debug("exception=" + e + ", retrying"); + log.debug("exception=" + e + ", retrying", e); rsp=null; } } } - private List findInitialMembers(Promise promise) { - List responses=(List)gms.getDownProtocol().down(new Event(Event.FIND_INITIAL_MBRS, promise)); + @SuppressWarnings("unchecked") + private List findInitialMembers(Promise promise) { + List responses=(List)gms.getDownProtocol().down(new Event(Event.FIND_INITIAL_MBRS, promise)); if(responses != null) { - for(Iterator iter=responses.iterator(); iter.hasNext();) { + for(Iterator iter=responses.iterator(); iter.hasNext();) { Address address=iter.next().getAddress(); if(address != null && address.equals(gms.local_addr)) iter.remove(); @@ -231,28 +230,6 @@ join_promise.setResult(join_rsp); // will wake up join() method } - public void handleLeaveResponse() { - } - - - public void suspect(Address mbr) { - } - - public void unsuspect(Address mbr) { - } - - public void handleMembershipChange (Collection requests) { - } - - - /** - * Does nothing. Discards all views while still client. - */ - public synchronized void handleViewChange(View new_view, Digest digest) { - if(log.isTraceEnabled()) - log.trace("view " + new_view.getVid() + " is discarded as we are not a participant"); - } - /** * Called by join(). Installs the view returned by calling Coord.handleJoin() and @@ -274,29 +251,21 @@ } - /** Returns immediately. Clients don't handle suspect() requests */ - // public void handleSuspect(Address mbr) { - // } - /* --------------------------- Private Methods ------------------------------------ */ - - void sendJoinMessage(Address coord, Address mbr,boolean joinWithTransfer) { + void sendJoinMessage(Address coord, Address mbr,boolean joinWithTransfer, boolean useFlushIfPresent) { Message msg; GMS.GmsHeader hdr; msg=new Message(coord, null, null); msg.setFlag(Message.OOB); if(joinWithTransfer) - hdr=new GMS.GmsHeader(GMS.GmsHeader.JOIN_REQ_WITH_STATE_TRANSFER, mbr); + hdr=new GMS.GmsHeader(GMS.GmsHeader.JOIN_REQ_WITH_STATE_TRANSFER, mbr,useFlushIfPresent); else - hdr=new GMS.GmsHeader(GMS.GmsHeader.JOIN_REQ, mbr); - msg.putHeader(gms.getName(), hdr); - - // we have to enable unicasts to coord, as coord is not in our membership (the unicast message would get dropped) - gms.getDownProtocol().down(new Event(Event.ENABLE_UNICASTS_TO, coord)); + hdr=new GMS.GmsHeader(GMS.GmsHeader.JOIN_REQ, mbr,useFlushIfPresent); + msg.putHeader(gms.getId(), hdr); gms.getDownProtocol().down(new Event(Event.MSG, msg)); } @@ -304,7 +273,7 @@ The coordinator is determined by a majority vote. If there are an equal number of votes for more than 1 candidate, we determine the winner randomly. */ - private Address determineCoord(List mbrs) { + private Address determineCoord(List mbrs) { int count, most_votes; Address winner=null, tmp; @@ -314,7 +283,7 @@ Map votes=new HashMap(5); // count *all* the votes (unlike the 2000 election) - for(PingRsp mbr:mbrs) { + for(PingData mbr:mbrs) { if(mbr.hasCoord()) { if(!votes.containsKey(mbr.getCoordAddress())) votes.put(mbr.getCoordAddress(), 1); @@ -324,6 +293,13 @@ } } } + // we have seen members say someone else is coordinator but they disagree + for(PingData mbr:mbrs) { + // remove members who don't agree with the election (Florida) + if (votes.containsKey(mbr.getAddress()) && (!mbr.isCoord())) { + votes.remove(mbr.getAddress()); + } + } if(votes.size() > 1) { if(log.isWarnEnabled()) log.warn("there was more than 1 candidate for coordinator: " + votes); @@ -373,5 +349,4 @@ if(log.isDebugEnabled()) log.debug("created group (first member). My view is " + gms.view_id + ", impl is " + gms.getImpl().getClass().getName()); } - } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/CoordGmsImpl.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/CoordGmsImpl.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/CoordGmsImpl.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/CoordGmsImpl.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,18 +1,16 @@ -// $Id: CoordGmsImpl.java,v 1.93 2008/06/19 15:48:11 vlada Exp $ package org.jgroups.protocols.pbcast; -import org.jgroups.*; -import org.jgroups.annotations.GuardedBy; +import org.jgroups.Address; +import org.jgroups.Event; +import org.jgroups.Message; +import org.jgroups.View; import org.jgroups.util.Digest; +import org.jgroups.util.MergeId; import org.jgroups.util.MutableDigest; import java.util.*; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; /** @@ -20,74 +18,29 @@ * accordingly. * @author Bela Ban */ -public class CoordGmsImpl extends GmsImpl { - private volatile boolean merging=false; - private final MergeTask merge_task=new MergeTask(); - private final Vector merge_rsps=new Vector(11); - // for MERGE_REQ/MERGE_RSP correlation, contains MergeData elements - private ViewId merge_id=null; - - @GuardedBy("merge_canceller_lock") - private Future merge_canceller_future=null; - - private final Lock merge_canceller_lock=new ReentrantLock(); - - /** the max time in ms to suspend message garbage collection */ - private final Long MAX_SUSPEND_TIMEOUT=new Long(30000); - +public class CoordGmsImpl extends ServerGmsImpl { + private final Long MAX_SUSPEND_TIMEOUT=new Long(30000); public CoordGmsImpl(GMS g) { super(g); } - private void setMergeId(ViewId merge_id) { - this.merge_id=merge_id; - if(this.merge_id != null) { - stopMergeCanceller(); - startMergeCanceller(); - } - else { // merge completed - stopMergeCanceller(); - } + public MergeId getMergeId() { + return merger.getMergeId(); } - private void startMergeCanceller() { - merge_canceller_lock.lock(); - try { - if(merge_canceller_future == null || merge_canceller_future.isDone()) { - MergeCanceller task=new MergeCanceller(this.merge_id, gms.merge_timeout); - merge_canceller_future=gms.timer.schedule(task, gms.merge_timeout, TimeUnit.MILLISECONDS); - } - } - finally { - merge_canceller_lock.unlock(); - } - } - - private void stopMergeCanceller() { - merge_canceller_lock.lock(); - try { - if(merge_canceller_future != null) { - merge_canceller_future.cancel(true); - merge_canceller_future=null; - } - } - finally { - merge_canceller_lock.unlock(); - } - } public void init() throws Exception { super.init(); - cancelMerge(); + merger.cancelMerge(null); } - public void join(Address mbr) { + public void join(Address mbr,boolean useFlushIfPresent) { wrongMethod("join"); } - public void joinWithStateTransfer(Address mbr) { + public void joinWithStateTransfer(Address mbr,boolean useFlushIfPresent) { wrongMethod("join"); } @@ -99,222 +52,55 @@ } if(mbr.equals(gms.local_addr)) leaving=true; - gms.getViewHandler().add(new Request(Request.LEAVE, mbr, false, null)); + gms.getViewHandler().add(new Request(Request.LEAVE, mbr, false)); gms.getViewHandler().stop(true); // wait until all requests have been processed, then close the queue and leave gms.getViewHandler().waitUntilCompleted(gms.leave_timeout); } - public void handleJoinResponse(JoinRsp join_rsp) { - } - - public void handleLeaveResponse() { - } public void suspect(Address mbr) { if(mbr.equals(gms.local_addr)) { - if(log.isWarnEnabled()) log.warn("I am the coord and I'm being am suspected -- will probably leave shortly"); + if(log.isWarnEnabled()) log.warn("I am the coord and I'm suspected -- will probably leave shortly"); return; } Collection suspected=new LinkedHashSet(1); - suspected.add(new Request(Request.SUSPECT,mbr,true,null)); + suspected.add(new Request(Request.SUSPECT,mbr,true)); handleMembershipChange(suspected); } - public void unsuspect(Address mbr) { - - } /** * Invoked upon receiving a MERGE event from the MERGE layer. Starts the merge protocol. * See description of protocol in DESIGN. - * @param other_coords A list of coordinators (including myself) found by MERGE protocol + * @param views A List of different views detected by the merge protocol */ - public void merge(Vector
            other_coords) { - Membership tmp; - - if(merging) { - if(log.isWarnEnabled()) log.warn("merge already in progress, discarded MERGE event (I am " + gms.local_addr + ")"); - return; - } - if(other_coords == null) { - if(log.isWarnEnabled()) log.warn("list of other coordinators is null. Will not start merge."); - return; - } - - if(other_coords.size() <= 1) { - if(log.isErrorEnabled()) - log.error("number of coordinators found is " + other_coords.size() + "; will not perform merge"); - return; - } - - /* Establish deterministic order, so that coords can elect leader */ - tmp=new Membership(other_coords); - tmp.sort(); - Address merge_leader=tmp.elementAt(0); - if(log.isDebugEnabled()) log.debug("Determining merge leader from coordinators: " + tmp); - if(merge_leader.equals(gms.local_addr) || gms.merge_leader) { - if(log.isDebugEnabled()) - log.debug("I (" + gms.local_addr + ") will be the leader. Starting the merge task for " + other_coords); - startMergeTask(other_coords); - } - else { - if(log.isDebugEnabled()) log.debug("I (" + gms.local_addr + ") am not the merge leader, " + - "waiting for merge leader (" + merge_leader + ") to initiate merge"); - } + public void merge(Map views) { + merger.merge(views); } - /** - * Get the view and digest and send back both (MergeData) in the form of a MERGE_RSP to the sender. - * If a merge is already in progress, send back a MergeData with the merge_rejected field set to true. - */ - public void handleMergeRequest(Address sender, ViewId merge_id) { - Digest digest; - View view; - - if(sender == null) { - if(log.isErrorEnabled()) log.error("sender == null; cannot send back a response"); - return; - } - if(merging) { - if(log.isErrorEnabled()) log.error("For merge participant " + gms.local_addr +" merge is already in progress"); - sendMergeRejectedResponse(sender, merge_id); - return; - } - merging=true; - - if(log.isDebugEnabled()) log.debug("Suspending view handler at " + gms.local_addr); - /* Clears the view handler queue and discards all JOIN/LEAVE/MERGE requests until after the MERGE */ - gms.getViewHandler().suspend(merge_id); - - setMergeId(merge_id); - if(log.isDebugEnabled()) log.debug(gms.local_addr + " got merge request from " + sender + ", merge_id=" + merge_id); - view=new View(gms.view_id.copy(), gms.members.getMembers()); - - //[JGRP-524] - FLUSH and merge: flush doesn't wrap entire merge process - //[JGRP-770] - Concurrent startup of many channels doesn't stabilize - //[JGRP-700] - FLUSH: flushing should span merge - - /*if flush is in stack, let this coordinator flush its cluster island */ - boolean suceesfulFlush = gms.startFlush(view); - if(suceesfulFlush) { - digest=gms.getDigest(); - sendMergeResponse(sender, view, digest); - if(log.isDebugEnabled()) - log.debug(gms.local_addr + " responded to " + sender + ", merge_id=" + merge_id); - } - else { - sendMergeRejectedResponse(sender, merge_id); - gms.getViewHandler().resume(merge_id); - merging=false; - if(log.isWarnEnabled()) - log.warn("Since flush failed at " + gms.local_addr + " rejected merge to "+ sender+ ", merge_id="+ merge_id); - } + public void handleMergeResponse(MergeData data, MergeId merge_id) { + merger.handleMergeResponse(data, merge_id); } - public void handleMergeResponse(MergeData data, ViewId merge_id) { - if(merge_id == null || this.merge_id == null) { - if(log.isErrorEnabled()) - log.error("merge_id (" - + merge_id - + ") or this.merge_id (" - + this.merge_id - + ") is null (sender=" - + data.getSender() - + ")."); - return; - } - - if(!this.merge_id.equals(merge_id)) { - if(log.isErrorEnabled()) log.error("this.merge_id (" - + this.merge_id - + ") is different from merge_id (" - + merge_id - + ')'); - return; - } - synchronized(merge_rsps) { - if(!merge_rsps.contains(data)) { - merge_rsps.addElement(data); - merge_rsps.notifyAll(); - } - } + public void handleMergeCancelled(MergeId merge_id) { + merger.handleMergeCancelled(merge_id); } + /** - * If merge_id is not equal to this.merge_id then discard. - * Else cast the view/digest to all members of this group. + * Fetches the digests from all members and installs them again. Used only for diagnosis and support; don't + * use this otherwise ! */ - public void handleMergeView(final MergeData data,final ViewId merge_id) { - if(merge_id == null - || this.merge_id == null - || !this.merge_id.equals(merge_id)) { - if(log.isErrorEnabled()) log.error("merge_ids don't match (or are null); merge view discarded"); - return; - } - - // only send to our *current* members, if we have A and B being merged (we are B), then we would *not* - // receive a VIEW_ACK from A because A doesn't see us in the pre-merge view yet and discards the view - - //[JGRP-700] - FLUSH: flushing should span merge - - //we have to send new view only to current members and we should not wait - //for view acks from newly merged mebers - List
            newViewMembers=new Vector
            (data.view.getMembers()); - newViewMembers.removeAll(gms.members.getMembers()); - - - gms.castViewChangeWithDest(data.view, data.digest, null, newViewMembers); - /* - * if we have flush in stack send ack back to merge coordinator - * */ - if(gms.flushProtocolInStack) { - Message ack=new Message(data.getSender(), null, null); - ack.setFlag(Message.OOB); - GMS.GmsHeader ack_hdr=new GMS.GmsHeader(GMS.GmsHeader.INSTALL_MERGE_VIEW_OK); - ack.putHeader(gms.getName(), ack_hdr); - gms.getDownProtocol().down(new Event(Event.ENABLE_UNICASTS_TO, data.getSender())); - gms.getDownProtocol().down(new Event(Event.MSG, ack)); - } - merging=false; - gms.getViewHandler().resume(merge_id); + void fixDigests() { + merger.fixDigests(); } - public void handleMergeCancelled(ViewId merge_id) { - gms.stopFlush(); - if(merge_id != null && this.merge_id != null && this.merge_id.equals(merge_id)) { - if(log.isDebugEnabled()) - log.debug("merge was cancelled at merge participant " + gms.local_addr+ " (merge_id="+ merge_id+ ")"); - - setMergeId(null); - merging=false; - gms.getViewHandler().resume(merge_id); - } - else { - if(log.isWarnEnabled()) - log.warn("merge was supposed to be cancelled at merge participant " + gms.local_addr - + " (merge_id=" - + merge_id - + "), but it is not since merge ids do not match"); - } - } - - - private void cancelMerge() { - Object tmp=merge_id; - if(merge_id != null && log.isDebugEnabled()) log.debug("cancelling merge (merge_id=" + merge_id + ')'); - setMergeId(null); - stopMergeTask(); - merging=false; - synchronized(merge_rsps) { - merge_rsps.clear(); - } - gms.getViewHandler().resume(tmp); - } public void handleMembershipChange(Collection requests) { boolean joinAndStateTransferInitiated=false; + boolean useFlushIfPresent=gms.use_flush_if_present; Collection
            new_mbrs=new LinkedHashSet
            (requests.size()); Collection
            suspected_mbrs=new LinkedHashSet
            (requests.size()); Collection
            leaving_mbrs=new LinkedHashSet
            (requests.size()); @@ -323,10 +109,12 @@ switch(req.type) { case Request.JOIN: new_mbrs.add(req.mbr); + useFlushIfPresent=req.useFlushIfPresent; break; case Request.JOIN_WITH_STATE_TRANSFER: new_mbrs.add(req.mbr); joinAndStateTransferInitiated=true; + useFlushIfPresent=req.useFlushIfPresent; break; case Request.LEAVE: if(req.suspected) @@ -346,7 +134,7 @@ // we're probably not the coord anymore (we just left ourselves), let someone else do it // (client will retry when it doesn't get a response) if(log.isDebugEnabled()) - log.debug("gms.view_id is null, I'm not the coordinator anymore (leaving=" + leaving + + log.debug("gms.view_id is null, I'm not the coordinator anymore (leaving=" + String.valueOf(leaving) + "); the new coordinator will handle the leave request"); return; } @@ -362,15 +150,9 @@ for(Iterator
            it=new_mbrs.iterator(); it.hasNext();) { Address mbr=it.next(); if(gms.members.contains(mbr)) { // already joined: return current digest and membership - JoinRsp join_rsp; - if(gms.reject_join_from_existing_member) { - join_rsp=new JoinRsp("member " + mbr + " is already part of the group, JOIN request is rejected"); - } - else { - if(log.isWarnEnabled()) - log.warn(mbr + " already present; returning existing view " + gms.view); - join_rsp=new JoinRsp(new View(gms.view_id, gms.members.getMembers()), gms.getDigest()); - } + if(log.isWarnEnabled()) + log.warn(mbr + " already present; returning existing view " + gms.view); + JoinRsp join_rsp=new JoinRsp(new View(gms.view_id, gms.members.getMembers()), gms.getDigest()); gms.sendJoinResponse(join_rsp, mbr); it.remove(); } @@ -383,6 +165,16 @@ } View new_view=gms.getNextView(new_mbrs, leaving_mbrs, suspected_mbrs); + + if(new_view.size() == 0 && gms.local_addr != null && gms.local_addr.equals(new_view.getCreator())) { + if(log.isTraceEnabled()) + log.trace("view " + new_view + " is empty: will not multicast it (last view)"); + if(leaving) { + gms.initState(); // in case connect() is called again + } + return; + } + gms.up(new Event(Event.PREPARE_VIEW,new_view)); gms.down(new Event(Event.PREPARE_VIEW,new_view)); @@ -393,14 +185,12 @@ JoinRsp join_rsp=null; boolean hasJoiningMembers=!new_mbrs.isEmpty(); try { - boolean successfulFlush = gms.startFlush(new_view); + boolean successfulFlush =!useFlushIfPresent || gms.startFlush(new_view); if(!successfulFlush && hasJoiningMembers){ - //see http://jira.jboss.org/jira/browse/JGRP-759 - //We should NOT send back a join response if the flush fails. - //The joiner should block until the previous FLUSH completed - //we still have to send potential leave responses - sendLeaveResponses(leaving_mbrs); - //but let the joining client timeout and send another join request + // Don't send a join response if the flush fails (http://jira.jboss.org/jira/browse/JGRP-759) + // The joiner should block until the previous FLUSH completed + sendLeaveResponses(leaving_mbrs); // we still have to send potential leave responses + // but let the joining client timeout and send another join request return; } @@ -426,12 +216,12 @@ } sendLeaveResponses(leaving_mbrs); // no-op if no leaving members - gms.castViewChangeWithDest(new_view, null,join_rsp,new_mbrs); + gms.castViewChangeWithDest(new_view, join_rsp != null? join_rsp.getDigest() : null, join_rsp,new_mbrs); } finally { if(hasJoiningMembers) gms.getDownProtocol().down(new Event(Event.RESUME_STABLE)); - if(!joinAndStateTransferInitiated) + if(!joinAndStateTransferInitiated && useFlushIfPresent) gms.stopFlush(); if(leaving) { gms.initState(); // in case connect() is called again @@ -448,455 +238,28 @@ */ public void handleViewChange(View new_view, Digest digest) { Vector
            mbrs=new_view.getMembers(); - if(log.isDebugEnabled()) { - if(digest != null) - log.debug("view=" + new_view + ", digest=" + digest); - else - log.debug("view=" + new_view); - } - if(leaving && !mbrs.contains(gms.local_addr)) return; gms.installView(new_view, digest); } - public void handleExit() { - cancelMerge(); - } public void stop() { super.stop(); // sets leaving=false - stopMergeTask(); + merger.stop(); } - /* ------------------------------------------ Private methods ----------------------------------------- */ - - void startMergeTask(Vector
            coords) { - synchronized(merge_task) { - merge_task.start(coords); - } - } - - void stopMergeTask() { - synchronized(merge_task) { - merge_task.stop(); - } - } + private void sendLeaveResponses(Collection
            leaving_members) { for(Address address:leaving_members){ Message msg=new Message(address, null, null); // send an ack to the leaving member msg.setFlag(Message.OOB); GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.LEAVE_RSP); - msg.putHeader(gms.getName(), hdr); - gms.getDownProtocol().down(new Event(Event.ENABLE_UNICASTS_TO,address)); - gms.getDownProtocol().down(new Event(Event.MSG, msg)); - } - } - - - /** - * Sends a MERGE_REQ to all coords and populates a list of MergeData (in merge_rsps). Returns after coords.size() - * response have been received, or timeout msecs have elapsed (whichever is first).

            - * If a subgroup coordinator rejects the MERGE_REQ (e.g. because of participation in a different merge), - * that member will be removed from coords ! - * @param coords A list of Addresses of subgroup coordinators (inluding myself) - * @param timeout Max number of msecs to wait for the merge responses from the subgroup coords - */ - private boolean getMergeDataFromSubgroupCoordinators(final Vector

            coords, long timeout) { - - boolean gotAllResponses = false; - long start=System.currentTimeMillis(); - synchronized(merge_rsps) { - merge_rsps.removeAllElements(); - if(log.isDebugEnabled()) - log.debug("Merge leader " + gms.local_addr + " sending MERGE_REQ to " + coords); - - for(Address coord:coords) { - // this allows UNICAST to remove coord from previous_members in case of a merge - gms.getDownProtocol().down(new Event(Event.ENABLE_UNICASTS_TO, coord)); - - Message msg=new Message(coord, null, null); - msg.setFlag(Message.OOB); - GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.MERGE_REQ); - hdr.mbr=gms.local_addr; - hdr.merge_id=merge_id; - msg.putHeader(gms.getName(), hdr); - gms.getDownProtocol().down(new Event(Event.MSG, msg)); - - if(log.isDebugEnabled()) - log.debug("Merge leader " + gms.local_addr + " sent MERGE_REQ to " + coord); - } - - // wait until num_rsps_expected >= num_rsps or timeout elapsed - int num_rsps_expected=coords.size(); - long curr_time=System.currentTimeMillis(); - long end_time=curr_time + timeout; - while(end_time > curr_time && !gotAllResponses) { - long time_to_wait=end_time - curr_time; - if(log.isDebugEnabled()) log.debug("Merge leader " + gms.local_addr +" waiting " + time_to_wait + " msecs for merge responses"); - if(merge_rsps.size() < num_rsps_expected) { - try { - merge_rsps.wait(500); - } - catch(Exception ex) { - } - } - if(log.isDebugEnabled()) - log.debug("Merge leader " + gms.local_addr +" expects " + num_rsps_expected + " responses, so far got " + merge_rsps.size() + " responses"); - - gotAllResponses = merge_rsps.size() >= num_rsps_expected; - curr_time=System.currentTimeMillis(); - } - long stop=System.currentTimeMillis(); - if(log.isDebugEnabled()) - log.debug("Merge leader " + gms.local_addr + " collected " + merge_rsps.size() + " merge response(s) in " + (stop-start) + " ms"); - } - return gotAllResponses; - } - - /** - * Generates a unique merge id by taking the local address and the current time - */ - private ViewId generateMergeId() { - return new ViewId(gms.local_addr, System.currentTimeMillis()); - // we're (ab)using ViewId as a merge id - } - - /** - * Merge all MergeData. All MergeData elements should be disjunct (both views and digests). However, - * this method is prepared to resolve duplicate entries (for the same member). Resolution strategy for - * views is to merge only 1 of the duplicate members. Resolution strategy for digests is to take the higher - * seqnos for duplicate digests.

            - * After merging all members into a Membership and subsequent sorting, the first member of the sorted membership - * will be the new coordinator. This method has a lock on merge_rsps. - * @param merge_rsps A list of MergeData items. Elements with merge_rejected=true were removed before. Is guaranteed - * not to be null and to contain at least 1 member. - */ - private MergeData consolidateMergeData(Vector merge_rsps) { - MergeData ret; - long logical_time=0; // for new_vid - ViewId new_vid, tmp_vid; - MergeView new_view; - View tmp_view; - Membership new_mbrs=new Membership(); - Address new_coord; - Vector subgroups=new Vector(11); - // contains a list of Views, each View is a subgroup - - for(MergeData tmp_data:merge_rsps) { - if(log.isDebugEnabled()) log.debug("Merge leader " + gms.local_addr + " is consolidating merge data " + tmp_data); - tmp_view=tmp_data.getView(); - if(tmp_view != null) { - tmp_vid=tmp_view.getVid(); - if(tmp_vid != null) { - // compute the new view id (max of all vids +1) - logical_time=Math.max(logical_time, tmp_vid.getId()); - } - // merge all membership lists into one (prevent duplicates) - new_mbrs.add(tmp_view.getMembers()); - subgroups.addElement((View)tmp_view.clone()); - } - } - - // the new coordinator is the first member of the consolidated & sorted membership list - new_mbrs.sort(); - new_coord = new_mbrs.size() > 0 ? new_mbrs.elementAt(0) : null; - if(new_coord == null) { - if(log.isErrorEnabled()) log.error("new_coord == null"); - return null; - } - // should be the highest view ID seen up to now plus 1 - new_vid=new ViewId(new_coord, logical_time + 1); - - // determine the new view - new_view=new MergeView(new_vid, new_mbrs.getMembers(), subgroups); - if(log.isDebugEnabled()) - log.debug("Merge leader " + gms.local_addr + " computed new merged view that will be " + new_view); - - // determine the new digest - Digest new_digest=consolidateDigests(merge_rsps, new_mbrs.size()); - if(new_digest == null) { - if(log.isErrorEnabled()) log.error("Merge leader " + gms.local_addr + "could not consolidate digest for merge"); - return null; - } - if(log.isDebugEnabled()) log.debug("Merge leader " + gms.local_addr + "consolidated digest=" + new_digest); - ret=new MergeData(gms.local_addr, new_view, new_digest); - return ret; - } - - /** - * Merge all digests into one. For each sender, the new value is min(low_seqno), max(high_seqno), - * max(high_seqno_seen). This method has a lock on merge_rsps - */ - private Digest consolidateDigests(Vector merge_rsps, int num_mbrs) { - MutableDigest retval=new MutableDigest(num_mbrs); - - for(MergeData data:merge_rsps) { - Digest tmp_digest=data.getDigest(); - if(tmp_digest == null) { - if(log.isErrorEnabled()) log.error("tmp_digest == null; skipping"); - continue; - } - retval.merge(tmp_digest); - } - return retval.copy(); - } - - /** - * Sends the new view and digest to all subgroup coordinors in coords. Each coord will in turn - *

              - *
            1. cast the new view and digest to all the members of its subgroup (MergeView) - *
            2. on reception of the view, if it is a MergeView, each member will set the digest and install - * the new view - *
            - */ - private void sendMergeView(Vector
            coords, MergeData combined_merge_data) { - View v; - Digest d; - - if(coords == null || combined_merge_data == null) - return; - - v=combined_merge_data.view; - d=combined_merge_data.digest; - if(v == null || d == null) { - if(log.isErrorEnabled()) log.error("view or digest is null, cannot send consolidated merge view/digest"); - return; - } - - if(log.isDebugEnabled()) - log.debug(gms.local_addr + " is sending merge view " + v.getVid() + " to coordinators " + coords); - - gms.merge_ack_collector.reset(v.getVid(), coords); - int size=gms.merge_ack_collector.size(); - long timeout=gms.view_ack_collection_timeout; - - long start = System.currentTimeMillis(); - for(Address coord:coords) { - Message msg=new Message(coord, null, null); - GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.INSTALL_MERGE_VIEW); - hdr.view=v; - hdr.my_digest=d; - hdr.merge_id=merge_id; - msg.putHeader(gms.getName(), hdr); - gms.getDownProtocol().down(new Event(Event.MSG, msg)); - } - - //[JGRP-700] - FLUSH: flushing should span merge - //if flush is in stack wait for acks from separated island coordinators - if(gms.flushProtocolInStack) { - try { - gms.merge_ack_collector.waitForAllAcks(timeout); - long stop=System.currentTimeMillis(); - if(log.isTraceEnabled()) - log.trace("received all ACKs (" + size - + ") for merged view " - + v - + " in " - + (stop - start) - + "ms"); - } - catch(TimeoutException e) { - log.warn("Merge coordinator " + gms.local_addr + " failed to collect all ACKs for merge (" + size - + ") for view " - + v - + " after " - + timeout - + "ms, missing ACKs from " - + gms.merge_ack_collector.printMissing() - + " (received=" - + gms.merge_ack_collector.printReceived() - + "), local_addr=" - + gms.local_addr); - } - } - } - - /** - * Send back a response containing view and digest to sender - */ - private void sendMergeResponse(Address sender, View view, Digest digest) { - Message msg=new Message(sender, null, null); - msg.setFlag(Message.OOB); - GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.MERGE_RSP); - hdr.merge_id=merge_id; - hdr.view=view; - hdr.my_digest=digest; - msg.putHeader(gms.getName(), hdr); - if(log.isDebugEnabled()) log.debug("response=" + hdr); - gms.getDownProtocol().down(new Event(Event.ENABLE_UNICASTS_TO, sender)); - gms.getDownProtocol().down(new Event(Event.MSG, msg)); - } - - - private void sendMergeCancelledMessage(Vector
            coords, ViewId merge_id) { - if(coords == null || merge_id == null) { - if(log.isErrorEnabled()) log.error("coords or merge_id == null"); - return; - } - for(Address coord:coords) { - Message msg=new Message(coord, null, null); - // msg.setFlag(Message.OOB); - GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.CANCEL_MERGE); - hdr.merge_id=merge_id; - msg.putHeader(gms.getName(), hdr); + msg.putHeader(gms.getId(), hdr); gms.getDownProtocol().down(new Event(Event.MSG, msg)); - if(log.isDebugEnabled()) log.debug("Merge leader " + gms.local_addr + " send cancel merge to " + coord); - } - } - - /** Removed rejected merge requests from merge_rsps and coords. This method has a lock on merge_rsps */ - private void removeRejectedMergeRequests(Vector
            coords) { - for(Iterator it=merge_rsps.iterator(); it.hasNext();) { - MergeData data=it.next(); - if(data.merge_rejected) { - if(data.getSender() != null && coords != null) - coords.removeElement(data.getSender()); - it.remove(); - if(log.isDebugEnabled()) log.debug("removed element " + data); - } - } + } } - /* --------------------------------------- End of Private methods ------------------------------------- */ - - /** - * Starts the merge protocol (only run by the merge leader). Essentially sends a MERGE_REQ to all - * coordinators of all subgroups found. Each coord receives its digest and view and returns it. - * The leader then computes the digest and view for the new group from the return values. Finally, it - * sends this merged view/digest to all subgroup coordinators; each coordinator will install it in their - * subgroup. - */ - private class MergeTask implements Runnable { - Thread t=null; - Vector
            coords=null; // list of subgroup coordinators to be contacted - - public void start(Vector
            groupCoord) { - this.coords = groupCoord != null ? new Vector
            (groupCoord) : null; - if(!isRunning()) { - t=gms.getThreadFactory().newThread(this, "MergeTask"); - t.setDaemon(true); - t.start(); - } - } - - public void stop() { - Thread tmp=t; - if(isRunning()) { - t=null; - tmp.interrupt(); - } - t=null; - } - - public boolean isRunning() { - return t != null && t.isAlive(); - } - - /** - * Runs the merge protocol as a leader - */ - public void run() { - if(merging) { - if(log.isWarnEnabled()) - log.warn(gms.local_addr + " running merge task, but merge is is already in progress, terminating"); - return; - } - - if(coords == null || coords.size() <= 1) { - if(log.isErrorEnabled()) - log.error("coords == null or size <= 1"); - return; - } - - if(log.isDebugEnabled()) - log.debug(gms.local_addr + " running merge task, coordinators are " + this.coords); - Vector
            coordsCopy=new Vector
            (coords); - /* 1. Generate a merge_id that uniquely identifies the merge in progress */ - ViewId generatedMergeId=generateMergeId(); - - try { - setMergeId(generatedMergeId); - - /* 2. Fetch the current Views/Digests from all subgroup coordinators */ - boolean success=getMergeDataFromSubgroupCoordinators(coords, gms.merge_timeout); - - if(!success) { - throw new Exception("Merge aborted. Merge leader did not get MergeData from all subgroup coordinators " + coords); - } - - /* - * 3. Remove rejected MergeData elements from merge_rsp and - * coords (so we'll send the new view only to members who - * accepted the merge request) - */ - MergeData combined_merge_data=null; - synchronized(merge_rsps) { - removeRejectedMergeRequests(coords); - if(merge_rsps.size() <= 1) { - throw new Exception("Merge leader " + gms.local_addr - + " did not get all merge responses from subgroup coordinators (" - + merge_rsps + ")"); - } - else { - /* 4. Combine all views and digests into 1 View/1 Digest */ - combined_merge_data=consolidateMergeData(merge_rsps); - if(combined_merge_data == null) { - throw new Exception("Merge leader " + gms.local_addr - + " could not consolidate merge"); - } - } - } - /* 4. Send the new View/Digest to all coordinators (including myself). On reception, they will - install the digest and view in all of their subgroup members */ - sendMergeView(coords, combined_merge_data); - } - catch(Throwable ex) { - if(log.isWarnEnabled()) - log.warn(ex.getLocalizedMessage()); - sendMergeCancelledMessage(coordsCopy, generatedMergeId); - } - finally { - gms.getViewHandler().resume(generatedMergeId); - stopMergeCanceller(); // this is probably not necessary - - /*5. if flush is in stack stop the flush for entire cluster - [JGRP-700] - FLUSH: flushing should span merge */ - - gms.stopFlush(); - - merging=false; - if(log.isDebugEnabled()) - log.debug("Merge leader " + gms.local_addr + " completed merge task"); - t=null; - } - } - } - - - private class MergeCanceller implements Runnable { - private Object my_merge_id=null; - private long timeout; - - MergeCanceller(Object my_merge_id, long timeout) { - this.my_merge_id=my_merge_id; - this.timeout=timeout; - } - - - public void run() { - if(merge_id != null && my_merge_id.equals(merge_id)) { - if(log.isDebugEnabled()) - log.debug("At " + gms.local_addr + " cancelling merge due to timer timeout (" + timeout + " ms)"); - cancelMerge(); - } - else { - if(log.isWarnEnabled()) - log.warn("At " + gms.local_addr +" timer kicked in after " + timeout + " ms, but no (or different) merge was in progress: " + - "merge_id=" + merge_id + ", my_merge_id=" + my_merge_id); - } - } - } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/FLUSH.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/FLUSH.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/FLUSH.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/FLUSH.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,77 +1,69 @@ package org.jgroups.protocols.pbcast; import org.jgroups.*; -import org.jgroups.annotations.DeprecatedProperty; -import org.jgroups.annotations.GuardedBy; -import org.jgroups.annotations.MBean; -import org.jgroups.annotations.ManagedAttribute; -import org.jgroups.annotations.ManagedOperation; -import org.jgroups.annotations.Property; +import org.jgroups.annotations.*; import org.jgroups.stack.Protocol; import org.jgroups.util.Digest; import org.jgroups.util.Promise; -import org.jgroups.util.Streamable; import org.jgroups.util.Util; import java.io.*; import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; /** - * Flush, as it name implies, forces group members to flush their pending - * messages while blocking them to send any additional messages. The process of - * flushing acquiesces the group so that state transfer or a join can be done. - * It is also called stop-the-world model as nobody will be able to send - * messages while a flush is in process. + * Flush, as it name implies, forces group members to flush their pending messages while blocking + * them to send any additional messages. The process of flushing acquiesces the group so that state + * transfer or a join can be done. It is also called stop-the-world model as nobody will be able to + * send messages while a flush is in process. * *

            * Flush is needed for: *

            - * (1) State transfer. When a member requests state transfer, the coordinator - * tells everyone to stop sending messages and waits for everyone's ack. Then it - * asks the application for its state and ships it back to the requester. After - * the requester has received and set the state successfully, the coordinator - * tells everyone to resume sending messages. + * (1) State transfer. When a member requests state transfer, the coordinator tells everyone to stop + * sending messages and waits for everyone's ack. Then it asks the application for its state and + * ships it back to the requester. After the requester has received and set the state successfully, + * the coordinator tells everyone to resume sending messages. *

            - * (2) View changes (e.g.a join). Before installing a new view V2, flushing - * would ensure that all messages *sent* in the current view V1 are indeed - * *delivered* in V1, rather than in V2 (in all non-faulty members). This is - * essentially Virtual Synchrony. + * (2) View changes (e.g.a join). Before installing a new view V2, flushing would ensure that all + * messages *sent* in the current view V1 are indeed *delivered* in V1, rather than in V2 (in all + * non-faulty members). This is essentially Virtual Synchrony. * * * * @author Vladimir Blagojevic - * @version $Id$ * @since 2.4 */ -@MBean(description="Flushes the cluster") -@DeprecatedProperty(names={"auto_flush_conf"}) +@MBean(description = "Flushes the cluster") +@DeprecatedProperty(names = { "auto_flush_conf" }) public class FLUSH extends Protocol { - - public static final String NAME = "FLUSH"; - - - /* ------------------------------------------ Properties ------------------------------------------ */ - @Property(description="Max time to keep channel blocked in flush. Default is 8000 msec") + + /* + * ------------------------------------------ Properties------------------------------------------ + */ + @Property(description = "Max time to keep channel blocked in flush. Default is 8000 msec") private long timeout = 8000; - - @Property(description="Timeout (per atttempt) to quiet the cluster during the first flush phase. Default is 2500 msec") - private long start_flush_timeout = 1500; + + @Property(description = "Timeout (per atttempt) to quiet the cluster during the first flush phase. Default is 2000 msec") + private long start_flush_timeout = 2000; - @Property(description="Retry timeout after an unsuccessful attempt to quiet the cluster (first flush phase). Default is 3000 msec") + @Property(description = "Timeout to wait for UNBLOCK after STOP_FLUSH is issued. Default is 2000 msec") + private long end_flush_timeout = 2000; + + @Property(description = "Retry timeout after an unsuccessful attempt to quiet the cluster (first flush phase). Default is 3000 msec") private long retry_timeout = 2000; - @Property(description="Reconcilliation phase toggle. Default is true") + @Property(description = "Reconciliation phase toggle. Default is true") private boolean enable_reconciliation = true; - - @Property(description="Max number of attempts to quiet the cluster during first flush phase. Default is 4") - private int flush_retry_count = 4; - - /* --------------------------------------------- JMX ---------------------------------------------- */ - - + /* + * --------------------------------------------- JMX ---------------------------------------------- + */ + private long startFlushTime; private long totalTimeInFlush; @@ -79,74 +71,84 @@ private int numberOfFlushes; private double averageFlushDuration; - - - - /* --------------------------------------------- Fields ------------------------------------------------------ */ - - + + /* + * --------------------------------------------- Fields------------------------------------------------------ + */ + @GuardedBy("sharedLock") - private View currentView; + private View currentView=new View(new ViewId(), new Vector

            ()); private Address localAddress; /** - * Group member that requested FLUSH. For view installations flush - * coordinator is the group coordinator For state transfer flush coordinator - * is the state requesting member + * Group member that requested FLUSH. For view installations flush coordinator is the group + * coordinator For state transfer flush coordinator is the state requesting member */ @GuardedBy("sharedLock") private Address flushCoordinator; @GuardedBy("sharedLock") - private final List
            flushMembers; - - private final AtomicInteger viewCounter = new AtomicInteger(0); + private final List
            flushMembers=new ArrayList
            (); + + private final AtomicInteger viewCounter = new AtomicInteger(0); @GuardedBy("sharedLock") - private final Map flushCompletedMap; + private final Map flushCompletedMap=new HashMap(); @GuardedBy("sharedLock") - private final Set
            suspected; - + private final List
            flushNotCompletedMap=new ArrayList
            (); + + @GuardedBy("sharedLock") + private final Set
            suspected=new TreeSet
            (); + @GuardedBy("sharedLock") - private final List
            reconcileOks; + private final List
            reconcileOks=new ArrayList
            (); private final Object sharedLock = new Object(); - private final Object blockMutex = new Object(); + private final ReentrantLock blockMutex = new ReentrantLock(); + + private final Condition notBlockedDown = blockMutex.newCondition(); /** - * Indicates if FLUSH.down() is currently blocking threads Condition - * predicate associated with blockMutex + * Indicates if FLUSH.down() is currently blocking threads Condition predicate associated with + * blockMutex */ @GuardedBy("blockMutex") private volatile boolean isBlockingFlushDown = true; - + @GuardedBy("sharedLock") private boolean flushCompleted = false; - - private volatile boolean allowMessagesToPassUp = false; - private final Promise flush_promise = new Promise(); - + private final Promise flush_promise = new Promise(); + + private final Promise flush_unblock_promise = new Promise(); + private final AtomicBoolean flushInProgress = new AtomicBoolean(false); - + private final AtomicBoolean sentBlock = new AtomicBoolean(false); - + private final AtomicBoolean sentUnblock = new AtomicBoolean(false); - public FLUSH(){ - super(); - currentView = new View(new ViewId(), new Vector
            ()); - flushCompletedMap = new HashMap(); - reconcileOks = new ArrayList
            (); - flushMembers = new ArrayList
            (); - suspected = new TreeSet
            (); + public FLUSH() { + } + + + public long getStartFlushTimeout() { + return start_flush_timeout; + } + + public void setStartFlushTimeout(long start_flush_timeout) { + this.start_flush_timeout = start_flush_timeout; + } + + public long getRetryTimeout() { + return retry_timeout; } - public String getName() { - return NAME; + public void setRetryTimeout(long retry_timeout) { + this.retry_timeout = retry_timeout; } public void start() throws Exception { @@ -155,18 +157,20 @@ up_prot.up(new Event(Event.CONFIG, map)); down_prot.down(new Event(Event.CONFIG, map)); - viewCounter.set(0); - synchronized(blockMutex){ + viewCounter.set(0); + blockMutex.lock(); + try { isBlockingFlushDown = true; + } finally { + blockMutex.unlock(); } - flush_promise.setResult(Boolean.FALSE); - allowMessagesToPassUp = false; } public void stop() { - synchronized(sharedLock){ + synchronized (sharedLock) { currentView = new View(new ViewId(), new Vector
            ()); - flushCompletedMap.clear(); + flushCompletedMap.clear(); + flushNotCompletedMap.clear(); flushMembers.clear(); suspected.clear(); flushCoordinator = null; @@ -190,283 +194,310 @@ return numberOfFlushes; } - @ManagedOperation(description="Request cluster flush") - public boolean startFlush() { + @ManagedOperation(description = "Request cluster flush") + public boolean startFlush() { return startFlush(new Event(Event.SUSPEND)); } - - private boolean startFlush(Event evt){ - if(log.isDebugEnabled()) - log.debug("Received " + evt + " at " + localAddress + ". Running FLUSH..."); - - List
            flushParticipants = (List
            ) evt.getArg(); - return startFlush(flushParticipants, flush_retry_count, false); + + @SuppressWarnings("unchecked") + private boolean startFlush(Event evt) { + List
            flushParticipants = (List
            ) evt.getArg(); + return startFlush(flushParticipants); } - - private boolean startFlush(List
            flushParticipants, int numberOfAttempts, boolean force) { - boolean successfulFlush = false; - if(!flushInProgress.get() || force){ - Boolean result=flush_promise.getResult(100); - successfulFlush = result !=null && result.booleanValue(); - if(!successfulFlush){ - flush_promise.reset(); - - onSuspend(flushParticipants); - try{ - Boolean r = flush_promise.getResultWithTimeout(start_flush_timeout); - successfulFlush = r.booleanValue(); - }catch(TimeoutException e){ - if(log.isDebugEnabled()) - log.debug("At " + localAddress - + " timed out waiting for flush responses after " - + start_flush_timeout - + " msec"); - } - } - } - if(!successfulFlush && numberOfAttempts > 0) { - waitForFlushCompletion(retry_timeout); - if(log.isDebugEnabled()) { - log.debug("Retrying FLUSH at " + localAddress - + ". Attempts left " - + numberOfAttempts); + private boolean startFlush(List
            flushParticipants) { + boolean successfulFlush = false; + if (!flushInProgress.get()) { + flush_promise.reset(); + synchronized(sharedLock) { + if(flushParticipants == null) + flushParticipants=new ArrayList
            (currentView.getMembers()); + } + onSuspend(flushParticipants); + try { + Boolean r = flush_promise.getResultWithTimeout(start_flush_timeout); + successfulFlush = r.booleanValue(); + } catch (TimeoutException e) { + if (log.isDebugEnabled()) + log.debug(localAddress + + ": timed out waiting for flush responses after " + + start_flush_timeout + + " ms. Rejecting flush to participants " + + flushParticipants); + rejectFlush(flushParticipants, currentViewId()); } - successfulFlush=startFlush(flushParticipants, --numberOfAttempts, true); } return successfulFlush; } - @ManagedOperation(description="Request end of flush in a cluster") + @ManagedOperation(description = "Request end of flush in a cluster") public void stopFlush() { down(new Event(Event.RESUME)); } /* - * ------------------- end JMX attributes and operations - * --------------------- + * ------------------- end JMX attributes and operations --------------------- */ public Object down(Event evt) { - switch(evt.getType()){ - case Event.MSG: - Message msg = (Message) evt.getArg(); - Address dest = msg.getDest(); - if(dest == null || dest.isMulticastAddress()){ - //mcasts - FlushHeader fh = (FlushHeader) msg.getHeader(getName()); - if(fh != null && fh.type == FlushHeader.FLUSH_BYPASS){ + switch (evt.getType()) { + case Event.MSG: + Message msg = (Message) evt.getArg(); + Address dest = msg.getDest(); + if (dest == null || dest.isMulticastAddress()) { + // mcasts + FlushHeader fh = (FlushHeader) msg.getHeader(this.id); + if (fh != null && fh.type == FlushHeader.FLUSH_BYPASS) { + return down_prot.down(evt); + } else { + blockMessageDuringFlush(); + } + } else { + // unicasts are irrelevant in virtual synchrony, let them through return down_prot.down(evt); } - else{ - blockMessageDuringFlush(); - } - }else{ - //unicasts are irrelevant in virtual synchrony, let them through - return down_prot.down(evt); - } - break; - - case Event.CONNECT: - case Event.CONNECT_WITH_STATE_TRANSFER: - if(sentBlock.compareAndSet(false, true)){ - sendBlockUpToChannel(); - } - - Object result=down_prot.down(evt); - if(result instanceof Throwable) { - sentBlock.set(false); // set the var back to its original state if we cannot connect successfully - } - return result; - - case Event.DISCONNECT: - waitForFlushCompletion(retry_timeout); - break; - - case Event.SUSPEND: - return startFlush(evt); - - case Event.RESUME: - onResume(evt); - return null; + break; + + case Event.CONNECT: + case Event.CONNECT_USE_FLUSH: + return handleConnect(evt,true); + + case Event.CONNECT_WITH_STATE_TRANSFER: + case Event.CONNECT_WITH_STATE_TRANSFER_USE_FLUSH: + return handleConnect(evt, false); + + case Event.SUSPEND: + return startFlush(evt); + + + // only for testing, see FLUSH#testFlushWithCrashedFlushCoordinator + case Event.SUSPEND_BUT_FAIL: + if (!flushInProgress.get()) { + flush_promise.reset(); + ArrayList
            flushParticipants = null; + synchronized (sharedLock) { + flushParticipants = new ArrayList
            (currentView.getMembers()); + } + onSuspend(flushParticipants); + } + break; + + case Event.RESUME: + onResume(evt); + return null; + + case Event.SET_LOCAL_ADDRESS: + localAddress = (Address) evt.getArg(); + break; } return down_prot.down(evt); } - private void blockMessageDuringFlush() { - boolean shouldSuspendByItself = false; - long start = 0, stop = 0; - synchronized(blockMutex){ - while(isBlockingFlushDown){ - if(log.isDebugEnabled()) - log.debug("FLUSH block at " + localAddress - + " for " - + (timeout <= 0 ? "ever" : timeout + "ms")); - try{ - start = System.currentTimeMillis(); - if(timeout <= 0) - blockMutex.wait(); - else - blockMutex.wait(timeout); - stop = System.currentTimeMillis(); - }catch(InterruptedException e){ - Thread.currentThread().interrupt(); // set interrupt flag again - } - if(isBlockingFlushDown){ - isBlockingFlushDown = false; - shouldSuspendByItself = true; - blockMutex.notifyAll(); - } - } + + private Object handleConnect(Event evt, boolean waitForUnblock) { + if (sentBlock.compareAndSet(false, true)) { + sendBlockUpToChannel(); } - if(shouldSuspendByItself){ - log.warn("unblocking FLUSH.down() at " + localAddress - + " after timeout of " - + (stop - start) - + "ms"); - flush_promise.setResult(Boolean.TRUE); + + Object result = down_prot.down(evt); + if (result instanceof Throwable) { + // set the var back to its original state if we cannot + // connect successfully + sentBlock.set(false); } + if(waitForUnblock) + waitForUnblock(); + return result; + } + + private void blockMessageDuringFlush() { + boolean shouldSuspendByItself = false; + blockMutex.lock(); + try { + while (isBlockingFlushDown) { + if (log.isDebugEnabled()) + log.debug(localAddress + ": blocking for " + (timeout <= 0 ? "ever" : timeout + "ms")); + shouldSuspendByItself = !notBlockedDown.await(timeout, TimeUnit.MILLISECONDS); + } + if (shouldSuspendByItself) { + isBlockingFlushDown = false; + log.warn(localAddress + ": unblocking after " + timeout + "ms"); + flush_promise.setResult(Boolean.TRUE); + notBlockedDown.signalAll(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + blockMutex.unlock(); + } } public Object up(Event evt) { - switch(evt.getType()){ - case Event.MSG: - Message msg = (Message) evt.getArg(); - FlushHeader fh = (FlushHeader) msg.getHeader(getName()); - if(fh != null){ - switch(fh.type){ - case FlushHeader.FLUSH_BYPASS: - return up_prot.up(evt); - case FlushHeader.START_FLUSH: - Collection
            fp=fh.flushParticipants; - boolean amIParticipant = (fp != null && fp.contains(localAddress)) || msg.getSrc().equals(localAddress); - if(amIParticipant){ - handleStartFlush(msg, fh); - } - else{ - if (log.isDebugEnabled()) - log.debug("Received START_FLUSH at " + localAddress - + " but I am not flush participant, not responding"); - } - break; - case FlushHeader.FLUSH_RECONCILE: - handleFlushReconcile(msg, fh); - break; - case FlushHeader.FLUSH_RECONCILE_OK: - onFlushReconcileOK(msg); - break; - case FlushHeader.STOP_FLUSH: - onStopFlush(); - break; - case FlushHeader.ABORT_FLUSH: - synchronized(sharedLock){ - flushCompletedMap.remove(msg.getSrc()); - } - flush_promise.setResult(Boolean.FALSE); - break; - case FlushHeader.FLUSH_COMPLETED: - if(isCurrentFlushMessage(fh)) - onFlushCompleted(msg.getSrc(), fh.digest); - break; - } - return null; // do not pass FLUSH msg up - }else{ - // http://jira.jboss.com/jira/browse/JGRP-575 - // for processing of application messages after we join, - // lets wait for STOP_FLUSH to complete - // before we start allowing message up. - Address dest=msg.getDest(); - if(dest != null && !dest.isMulticastAddress()) { - return up_prot.up(evt); // allow unicasts to pass, virtual synchrony olny applies to multicasts + switch (evt.getType()) { + case Event.MSG: + Message msg = (Message) evt.getArg(); + final FlushHeader fh = (FlushHeader) msg.getHeader(this.id); + if (fh != null) { + switch (fh.type) { + case FlushHeader.FLUSH_BYPASS: + return up_prot.up(evt); + case FlushHeader.START_FLUSH: + Collection
            fp = fh.flushParticipants; + + boolean amIParticipant = (fp != null && fp.contains(localAddress)) + || msg.getSrc().equals(localAddress); + if (amIParticipant) { + handleStartFlush(msg, fh); + } else { + if (log.isDebugEnabled()) + log.debug(localAddress + + ": received START_FLUSH but I am not flush participant, not responding"); + } + break; + case FlushHeader.FLUSH_RECONCILE: + handleFlushReconcile(msg, fh); + break; + case FlushHeader.FLUSH_RECONCILE_OK: + onFlushReconcileOK(msg); + break; + case FlushHeader.STOP_FLUSH: + onStopFlush(); + break; + case FlushHeader.ABORT_FLUSH: + Collection
            flushParticipants = fh.flushParticipants; + boolean participant = flushParticipants != null && flushParticipants.contains(localAddress); + if (log.isDebugEnabled()) { + log.debug(localAddress + + ": received ABORT_FLUSH from flush coordinator " + + msg.getSrc() + + ", am I flush participant=" + + participant); + } + if (participant) { + resetForNextFlush(); + } + break; + case FlushHeader.FLUSH_NOT_COMPLETED: + if (log.isDebugEnabled()) { + log.debug(localAddress + + ": received FLUSH_NOT_COMPLETED from " + + msg.getSrc()); + } + boolean flushCollision = false; + synchronized (sharedLock) { + flushNotCompletedMap.add(msg.getSrc()); + flushCollision = !flushCompletedMap.isEmpty(); + if (flushCollision) { + flushNotCompletedMap.clear(); + flushCompletedMap.clear(); + } + } + + if (log.isDebugEnabled()) { + log.debug(localAddress + + ": received FLUSH_NOT_COMPLETED from " + + msg.getSrc() + " collision=" + flushCollision); + } + + // reject flush if we have at least one OK and at least one FAIL + if (flushCollision) { + Runnable r = new Runnable() { + public void run() { + rejectFlush(fh.flushParticipants, fh.viewID); + } + }; + new Thread(r).start(); + } + // however, flush should fail/retry as soon as one FAIL is received + flush_promise.setResult(Boolean.FALSE); + break; + + case FlushHeader.FLUSH_COMPLETED: + if (isCurrentFlushMessage(fh)) + onFlushCompleted(msg.getSrc(), fh); + break; + } + return null; // do not pass FLUSH msg up + } else { + // http://jira.jboss.com/jira/browse/JGRP-575 + // for processing of application messages after we join, + // lets wait for STOP_FLUSH to complete + // before we start allowing message up. + Address dest = msg.getDest(); + if (dest != null && !dest.isMulticastAddress()) { + return up_prot.up(evt); // allow unicasts to pass, virtual synchrony olny + // applies to multicasts + } } + break; - if(!allowMessagesToPassUp) - return null; - } - break; + case Event.VIEW_CHANGE: + /* + * [JGRP-618] - FLUSH coordinator transfer reorders block/unblock/view events in + * applications (TCP stack only) + */ + up_prot.up(evt); + View newView = (View) evt.getArg(); + boolean coordinatorLeft = onViewChange(newView); + boolean singletonMember = newView.size() == 1 + && newView.containsMember(localAddress); + boolean isThisOurFirstView = viewCounter.addAndGet(1) == 1; + // if this is channel's first view and its the only member of the group - no flush + // was run but the channel application should still receive BLOCK,VIEW,UNBLOCK + + // also if coordinator of flush left each member should run stopFlush individually. + if ((isThisOurFirstView && singletonMember) || coordinatorLeft) { + onStopFlush(); + } + return null; - case Event.VIEW_CHANGE: - /* - * [JGRP-618] - FLUSH coordinator transfer reorders - * block/unblock/view events in applications (TCP stack only) - * - */ - up_prot.up(evt); - View newView = (View) evt.getArg(); - boolean coordinatorLeft = onViewChange(newView); - boolean singletonMember = newView.size() == 1 && newView.containsMember(localAddress); - boolean isThisOurFirstView = viewCounter.addAndGet(1) == 1; - // if this is channel's first view and its the only member of the group - no flush was run - // but the channel application should still receive BLOCK,VIEW,UNBLOCK - - //also if coordinator of flush left each member should run stopFlush individually. - if((isThisOurFirstView && singletonMember) || coordinatorLeft){ - onStopFlush(); - } - return null; - - case Event.TMP_VIEW: - /* - * April 25, 2007 - * - * Accommodating current NAKACK (1.127) - * - * Updates field currentView of a leaving coordinator. Leaving - * coordinator, after it sends out the view, does not need to - * participate in second flush phase. - * - * see onStopFlush(); - * - * TODO: revisit if still needed post NAKACK 1.127 - * - */ - View tmpView = (View) evt.getArg(); - if(!tmpView.containsMember(localAddress)){ - onViewChange(tmpView); - } - break; - - case Event.SET_LOCAL_ADDRESS: - localAddress = (Address) evt.getArg(); - break; - - case Event.SUSPECT: - onSuspect((Address) evt.getArg()); - break; - - case Event.SUSPEND: - return startFlush(evt); - - case Event.RESUME: - onResume(evt); - return null; + case Event.TMP_VIEW: + View tmpView = (View) evt.getArg(); + if (!tmpView.containsMember(localAddress)) { + onViewChange(tmpView); + } + break; + case Event.SUSPECT: + onSuspect((Address) evt.getArg()); + break; + + case Event.SUSPEND: + return startFlush(evt); + + case Event.RESUME: + onResume(evt); + return null; + case Event.UNBLOCK: + flush_unblock_promise.setResult(Boolean.TRUE); + break; } return up_prot.up(evt); } - - private boolean waitForFlushCompletion(long timeout){ - long start_time = System.currentTimeMillis(), backofftime = timeout; - while (backofftime > 0 && flushInProgress.get()) { - //sleep min 100 msec and max 600 msec - Util.sleep(100 + Util.random(500)); - backofftime = timeout - (System.currentTimeMillis() - start_time); + + private void waitForUnblock() { + try { + flush_unblock_promise.getResultWithTimeout(end_flush_timeout); + } catch (TimeoutException t) { + if (log.isWarnEnabled()) + log.warn(localAddress + ": waiting for UNBLOCK timed out after " + end_flush_timeout + " ms"); + } finally { + flush_unblock_promise.reset(); } - return backofftime < 0; } private void onFlushReconcileOK(Message msg) { - if(log.isDebugEnabled()) - log.debug(localAddress + " received reconcile ok from " + msg.getSrc()); + if (log.isDebugEnabled()) + log.debug(localAddress + ": received reconcile ok from " + msg.getSrc()); - synchronized(sharedLock){ + synchronized (sharedLock) { reconcileOks.add(msg.getSrc()); - if(reconcileOks.size() >= flushMembers.size()){ + if (reconcileOks.size() >= flushMembers.size()) { flush_promise.setResult(Boolean.TRUE); - if(log.isDebugEnabled()) - log.debug("All FLUSH_RECONCILE_OK received at " + localAddress); + if (log.isDebugEnabled()) + log.debug(localAddress + ": all FLUSH_RECONCILE_OK received"); } } } @@ -475,73 +506,49 @@ Address requester = msg.getSrc(); Digest reconcileDigest = fh.digest; - if(log.isDebugEnabled()) - log.debug("Received FLUSH_RECONCILE at " + localAddress - + " passing digest to NAKACK " - + reconcileDigest); + if (log.isDebugEnabled()) + log.debug(localAddress + ": received FLUSH_RECONCILE, passing digest to NAKACK " + + reconcileDigest); // Let NAKACK reconcile missing messages down_prot.down(new Event(Event.REBROADCAST, reconcileDigest)); - if(log.isDebugEnabled()) - log.debug("Returned from FLUSH_RECONCILE at " + localAddress - + " Sending RECONCILE_OK to " - + requester - + ", thread " - + Thread.currentThread()); + if (log.isDebugEnabled()) + log.debug(localAddress + ": returned from FLUSH_RECONCILE, " + + " sending RECONCILE_OK to " + requester); Message reconcileOk = new Message(requester); reconcileOk.setFlag(Message.OOB); - reconcileOk.putHeader(getName(), new FlushHeader(FlushHeader.FLUSH_RECONCILE_OK)); + reconcileOk.putHeader(this.id, new FlushHeader(FlushHeader.FLUSH_RECONCILE_OK)); down_prot.down(new Event(Event.MSG, reconcileOk)); } - private void handleStartFlush(Message msg, FlushHeader fh) { + private void handleStartFlush(Message msg, FlushHeader fh) { Address flushRequester = msg.getSrc(); - Address abortFlushCoordinator = null; - Address proceedFlushCoordinator = null; - - boolean proceed = flushInProgress.compareAndSet(false, true); - synchronized (sharedLock) { - if(proceed){ + + boolean proceed = flushInProgress.compareAndSet(false, true); + if (proceed) { + synchronized (sharedLock) { flushCoordinator = flushRequester; - }else{ - if(flushCoordinator != null){ - if(flushCompleted){ - abortFlushCoordinator = flushRequester; - } - else if(flushRequester.compareTo(flushCoordinator) < 0){ - abortFlushCoordinator = flushCoordinator; - flushCoordinator = flushRequester; - proceedFlushCoordinator = flushRequester; - }else if(flushRequester.compareTo(flushCoordinator) > 0){ - abortFlushCoordinator = flushRequester; - proceedFlushCoordinator = flushCoordinator; - }else{ - if(log.isDebugEnabled()){ - log.debug("Rejecting flush at " + localAddress + ", previous flush has to finish first"); - } - abortFlushCoordinator = flushRequester; - } - }else{ - return; - } } - } - if(proceed){ onStartFlush(flushRequester, fh); - } else{ - if(proceedFlushCoordinator != null){ - if(log.isDebugEnabled()){ - log.debug("Rejecting flush at " + localAddress - + " to flush requester " - + abortFlushCoordinator - + " coordinator is " - + proceedFlushCoordinator); - } - rejectFlush(fh.viewID, abortFlushCoordinator); - onStartFlush(proceedFlushCoordinator, fh); - } + } else { + FlushHeader fhr = new FlushHeader(FlushHeader.FLUSH_NOT_COMPLETED, fh.viewID, + fh.flushParticipants); + Message response = new Message(flushRequester); + response.putHeader(this.id, fhr); + down_prot.down(new Event(Event.MSG, response)); + if (log.isDebugEnabled()) + log.debug(localAddress + ": received START_FLUSH, responded with FLUSH_NOT_COMPLETED to " + flushRequester); + } + } + + private void rejectFlush(Collection participants, long viewId) { + for (Address flushMember : participants) { + Message reject = new Message(flushMember, localAddress, null); + reject.setFlag(Message.OOB); + reject.putHeader(this.id, new FlushHeader(FlushHeader.ABORT_FLUSH, viewId,participants)); + down_prot.down(new Event(Event.MSG, reject)); } } @@ -552,20 +559,14 @@ return retval; } - private void rejectFlush(long viewId, Address flushRequester) { - Message reject = new Message(flushRequester, localAddress, null); - reject.putHeader(getName(), new FlushHeader(FlushHeader.ABORT_FLUSH, viewId)); - down_prot.down(new Event(Event.MSG, reject)); - } - private void sendBlockUpToChannel() { - up_prot.up(new Event(Event.BLOCK)); + this.up(new Event(Event.BLOCK)); sentUnblock.set(false); } - + private void sendUnBlockUpToChannel() { sentBlock.set(false); - up_prot.up(new Event(Event.UNBLOCK)); + this.up(new Event(Event.UNBLOCK)); } private boolean isCurrentFlushMessage(FlushHeader fh) { @@ -574,210 +575,237 @@ private long currentViewId() { long viewId = -1; - synchronized(sharedLock){ + synchronized (sharedLock) { ViewId view = currentView.getVid(); - if(view != null){ + if (view != null) { viewId = view.getId(); } } return viewId; } - private boolean onViewChange(View view) { + private boolean onViewChange(View view) { boolean coordinatorLeft = false; - synchronized(sharedLock){ + View oldView; + synchronized (sharedLock) { suspected.retainAll(view.getMembers()); + oldView = currentView; currentView = view; - coordinatorLeft = view.getMembers().size()>0 && !view.containsMember(view.getCreator()); - } - if(log.isDebugEnabled()) - log.debug("Installing view at " + localAddress + " view is " + view); + coordinatorLeft = !oldView.getMembers().isEmpty() && !view.getMembers().isEmpty() + && !view.containsMember(oldView.getCreator()); + } + if (log.isDebugEnabled()) + log.debug(localAddress + ": installing view " + view); return coordinatorLeft; } - private void onStopFlush() { - if(stats){ + private void onStopFlush() { + if (stats && startFlushTime > 0) { long stopFlushTime = System.currentTimeMillis(); totalTimeInFlush += (stopFlushTime - startFlushTime); - if(numberOfFlushes > 0){ + if (numberOfFlushes > 0) { averageFlushDuration = totalTimeInFlush / (double) numberOfFlushes; } + startFlushTime = 0; } - - synchronized(sharedLock){ - flushCompletedMap.clear(); + + if (log.isDebugEnabled()) + log.debug(localAddress + + ": received STOP_FLUSH, unblocking FLUSH.down() and sending UNBLOCK up"); + + resetForNextFlush(); + if (sentUnblock.compareAndSet(false, true)) { + // ensures that we do not repeat unblock event + sendUnBlockUpToChannel(); + } + } + + + private void resetForNextFlush() { + synchronized (sharedLock) { + flushCompletedMap.clear(); + flushNotCompletedMap.clear(); flushMembers.clear(); suspected.clear(); flushCoordinator = null; - allowMessagesToPassUp = true; flushCompleted = false; - } - - if(log.isDebugEnabled()) - log.debug("At " + localAddress - + " received STOP_FLUSH, unblocking FLUSH.down() and sending UNBLOCK up"); - - synchronized(blockMutex){ + } + blockMutex.lock(); + try { isBlockingFlushDown = false; - blockMutex.notifyAll(); - } - - if(sentUnblock.compareAndSet(false,true)){ - //ensures that we do not repeat unblock event - sendUnBlockUpToChannel(); - } + notBlockedDown.signalAll(); + } finally { + blockMutex.unlock(); + } flushInProgress.set(false); - flush_promise.setResult(Boolean.FALSE); - } + } - private void onSuspend(List
            members) { + /** + * Starts the flush protocol + * @param members List of participants in the flush protocol. Guaranteed to be non-null + */ + private void onSuspend(final List
            members) { Message msg = null; Collection
            participantsInFlush = null; - synchronized(sharedLock){ - // start FLUSH only on group members that we need to flush - if(members != null){ - participantsInFlush = members; - participantsInFlush.retainAll(currentView.getMembers()); - }else{ - participantsInFlush = new ArrayList
            (currentView.getMembers()); - } - msg = new Message(null, localAddress, null); - msg.putHeader(getName(), new FlushHeader(FlushHeader.START_FLUSH, - currentViewId(), - participantsInFlush)); - } - if(participantsInFlush.isEmpty()){ + synchronized (sharedLock) { + flushCoordinator = localAddress; + + // start FLUSH only on group members that we need to flush + participantsInFlush = members; + participantsInFlush.retainAll(currentView.getMembers()); + flushMembers.clear(); + flushMembers.addAll(participantsInFlush); + flushMembers.removeAll(suspected); + + msg = new Message(null, localAddress, null); + msg.putHeader(this.id, new FlushHeader(FlushHeader.START_FLUSH, currentViewId(), + participantsInFlush)); + } + if (participantsInFlush.isEmpty()) { flush_promise.setResult(Boolean.TRUE); - }else{ + } else { down_prot.down(new Event(Event.MSG, msg)); - if(log.isDebugEnabled()) - log.debug("Flush coordinator " + localAddress - + " is starting FLUSH with participants " - + participantsInFlush); + if (log.isDebugEnabled()) + log.debug(localAddress + ": flush coordinator " + + " is starting FLUSH with participants " + participantsInFlush); } } + @SuppressWarnings("unchecked") private void onResume(Event evt) { List
            members = (List
            ) evt.getArg(); long viewID = currentViewId(); - if(members == null || members.isEmpty()){ - Message msg = new Message(null, localAddress, null); - //Cannot be OOB since START_FLUSH is not OOB - //we have to FIFO order two subsequent flushes - msg.putHeader(getName(), new FlushHeader(FlushHeader.STOP_FLUSH, viewID)); + boolean isParticipant = false; + synchronized(sharedLock) { + isParticipant = flushMembers.contains(localAddress) || (members != null && members.contains(localAddress)); + } + if (members == null || members.isEmpty()) { + Message msg = new Message(null, localAddress, null); + // Cannot be OOB since START_FLUSH is not OOB + // we have to FIFO order two subsequent flushes + if (log.isDebugEnabled()) + log.debug(localAddress + ": received RESUME, sending STOP_FLUSH to all"); + msg.putHeader(this.id, new FlushHeader(FlushHeader.STOP_FLUSH, viewID)); down_prot.down(new Event(Event.MSG, msg)); - if(log.isDebugEnabled()) - log.debug("Received RESUME at " + localAddress + ", sent STOP_FLUSH to all"); - }else{ + } else { for (Address address : members) { - Message msg = new Message(address, localAddress, null); - //Cannot be OOB since START_FLUSH is not OOB - //we have to FIFO order two subsequent flushes - msg.putHeader(getName(), new FlushHeader(FlushHeader.STOP_FLUSH, viewID)); + Message msg = new Message(address, localAddress, null); + // Cannot be OOB since START_FLUSH is not OOB + // we have to FIFO order two subsequent flushes + if (log.isDebugEnabled()) + log.debug(localAddress + ": received RESUME, sending STOP_FLUSH to " + address); + msg.putHeader(this.id, new FlushHeader(FlushHeader.STOP_FLUSH, viewID)); down_prot.down(new Event(Event.MSG, msg)); - if(log.isDebugEnabled()) - log.debug("Received RESUME at " + localAddress + ", sent STOP_FLUSH to " + address); } } + if(isParticipant) + waitForUnblock(); } - private void onStartFlush(Address flushStarter, FlushHeader fh) { - if(stats){ + private void onStartFlush(Address flushStarter, FlushHeader fh) { + if (stats) { startFlushTime = System.currentTimeMillis(); numberOfFlushes += 1; } boolean proceed = false; - synchronized(sharedLock){ - flushCoordinator = flushStarter; - flushMembers.clear(); - if(fh.flushParticipants != null){ - flushMembers.addAll(fh.flushParticipants); + boolean amIFlushInitiator = false; + synchronized (sharedLock) { + amIFlushInitiator = flushStarter.equals(localAddress); + if(!amIFlushInitiator){ + flushCoordinator = flushStarter; + flushMembers.clear(); + if (fh.flushParticipants != null) { + flushMembers.addAll(fh.flushParticipants); + } + flushMembers.removeAll(suspected); } proceed = flushMembers.contains(localAddress); - flushMembers.removeAll(suspected); } - - if(proceed) { - if(sentBlock.compareAndSet(false, true)) { - //ensures that we do not repeat block event - //and that we do not send block event to non participants + + if (proceed) { + if (sentBlock.compareAndSet(false, true)) { + // ensures that we do not repeat block event + // and that we do not send block event to non participants sendBlockUpToChannel(); - synchronized(blockMutex) { - isBlockingFlushDown=true; - } - } - else { - if(log.isDebugEnabled()) - log.debug("Received START_FLUSH at " + localAddress - + " but not sending BLOCK up"); + blockMutex.lock(); + try { + isBlockingFlushDown = true; + } finally { + blockMutex.unlock(); + } + } else { + if (log.isDebugEnabled()) + log.debug(localAddress + ": received START_FLUSH, but not sending BLOCK up"); } - Digest digest=(Digest)down_prot.down(new Event(Event.GET_DIGEST)); - FlushHeader fhr=new FlushHeader(FlushHeader.FLUSH_COMPLETED, fh.viewID); + Digest digest = (Digest) down_prot.down(new Event(Event.GET_DIGEST)); + FlushHeader fhr = new FlushHeader(FlushHeader.FLUSH_COMPLETED, fh.viewID,fh.flushParticipants); fhr.addDigest(digest); - Message msg=new Message(flushStarter); - msg.putHeader(getName(), fhr); + Message msg = new Message(flushStarter); + msg.putHeader(this.id, fhr); down_prot.down(new Event(Event.MSG, msg)); - if(log.isDebugEnabled()) - log.debug("Received START_FLUSH at " + localAddress - + " responded with FLUSH_COMPLETED to " - + flushStarter); + if (log.isDebugEnabled()) + log.debug(localAddress + ": received START_FLUSH, responded with FLUSH_COMPLETED to " + flushStarter); } - + } - - private void onFlushCompleted(Address address, Digest digest) { + + private void onFlushCompleted(Address address, final FlushHeader header) { Message msg = null; boolean needsReconciliationPhase = false; - synchronized(sharedLock){ + boolean collision = false; + Digest digest = header.digest; + synchronized (sharedLock) { flushCompletedMap.put(address, digest); flushCompleted = flushCompletedMap.size() >= flushMembers.size() - && !flushMembers.isEmpty() - && flushCompletedMap.keySet().containsAll(flushMembers); + && !flushMembers.isEmpty() + && flushCompletedMap.keySet().containsAll(flushMembers); - if(log.isDebugEnabled()) - log.debug("At " + localAddress - + " FLUSH_COMPLETED from " - + address - + ",completed " - + flushCompleted - + ",flushMembers " - + flushMembers - + ",flushCompleted " - + flushCompletedMap.keySet()); - - needsReconciliationPhase = enable_reconciliation && flushCompleted - && hasVirtualSynchronyGaps(); - if(needsReconciliationPhase){ + collision = !flushNotCompletedMap.isEmpty(); + if (log.isDebugEnabled()) + log.debug(localAddress + ": FLUSH_COMPLETED from " + address + ", completed " + + flushCompleted + ", flushMembers " + flushMembers + + ", flushCompleted " + flushCompletedMap.keySet()); + needsReconciliationPhase = enable_reconciliation && flushCompleted && hasVirtualSynchronyGaps(); + if (needsReconciliationPhase) { Digest d = findHighestSequences(); msg = new Message(); msg.setFlag(Message.OOB); - FlushHeader fh = new FlushHeader(FlushHeader.FLUSH_RECONCILE, - currentViewId(), - flushMembers); + FlushHeader fh = new FlushHeader(FlushHeader.FLUSH_RECONCILE, currentViewId(),flushMembers); reconcileOks.clear(); fh.addDigest(d); - msg.putHeader(getName(), fh); + msg.putHeader(this.id, fh); - if(log.isDebugEnabled()) - log.debug("At "+ localAddress + " reconciling flush mebers due to virtual synchrony gap, digest is " + d - + " flush members are " - + flushMembers); + if (log.isDebugEnabled()) + log.debug(localAddress + + ": reconciling flush mebers due to virtual synchrony gap, digest is " + + d + " flush members are " + flushMembers); flushCompletedMap.clear(); - } else if (flushCompleted){ + } else if (flushCompleted) { + flushCompletedMap.clear(); + } else if (collision) { + flushNotCompletedMap.clear(); flushCompletedMap.clear(); } } - if(needsReconciliationPhase){ + if (needsReconciliationPhase) { down_prot.down(new Event(Event.MSG, msg)); - }else if(flushCompleted){ + } else if (flushCompleted) { flush_promise.setResult(Boolean.TRUE); - if(log.isDebugEnabled()) - log.debug("All FLUSH_COMPLETED received at " + localAddress); + if (log.isDebugEnabled()) + log.debug(localAddress + ": all FLUSH_COMPLETED received"); + } else if (collision) { + // reject flush if we have at least one OK and at least one FAIL + Runnable r = new Runnable() { + public void run() { + rejectFlush(header.flushParticipants, header.viewID); + } + }; + new Thread(r).start(); } } @@ -786,9 +814,9 @@ digests.addAll(flushCompletedMap.values()); Digest firstDigest = digests.get(0); List remainingDigests = digests.subList(1, digests.size()); - for(Digest digest:remainingDigests){ + for (Digest digest : remainingDigests) { Digest diff = firstDigest.difference(digest); - if(diff != Digest.EMPTY_DIGEST){ + if (diff != Digest.EMPTY_DIGEST) { return true; } } @@ -802,50 +830,71 @@ result = digests.get(0); List remainingDigests = digests.subList(1, digests.size()); - for(Digest digestG:remainingDigests){ + for (Digest digestG : remainingDigests) { result = result.highestSequence(digestG); } return result; } private void onSuspect(Address address) { + + // handles FlushTest#testFlushWithCrashedFlushCoordinator + boolean amINeighbourOfCrashedFlushCoordinator = false; + ArrayList
            flushMembersCopy = null; + synchronized (sharedLock) { + boolean flushCoordinatorSuspected = address != null && address.equals(flushCoordinator); + if (flushCoordinatorSuspected) { + int indexOfCoordinator = flushMembers.indexOf(flushCoordinator); + int myIndex = flushMembers.indexOf(localAddress); + int diff = myIndex - indexOfCoordinator; + amINeighbourOfCrashedFlushCoordinator = (diff == 1 || (myIndex == 0 && indexOfCoordinator == flushMembers.size())); + if (amINeighbourOfCrashedFlushCoordinator) { + flushMembersCopy = new ArrayList
            (flushMembers); + } + } + } + if (amINeighbourOfCrashedFlushCoordinator) { + if (log.isDebugEnabled()) + log.debug(localAddress + ": flush coordinator " + flushCoordinator + " suspected," + + " I am the neighbor, completing the flush "); + + onResume(new Event(Event.RESUME, flushMembersCopy)); + } + + // handles FlushTest#testFlushWithCrashedNonCoordinators boolean flushOkCompleted = false; Message m = null; long viewID = 0; - synchronized(sharedLock){ + synchronized (sharedLock) { suspected.add(address); flushMembers.removeAll(suspected); viewID = currentViewId(); - flushOkCompleted = !flushCompletedMap.isEmpty() && flushCompletedMap.keySet().containsAll(flushMembers); - if(flushOkCompleted){ + flushOkCompleted = !flushCompletedMap.isEmpty() + && flushCompletedMap.keySet().containsAll(flushMembers); + if (flushOkCompleted) { m = new Message(flushCoordinator, localAddress, null); } - if(log.isDebugEnabled()) - log.debug("Suspect is " + address - + ",completed " - + flushOkCompleted - + ", flushOkSet " - + flushCompletedMap - + " flushMembers " - + flushMembers); + if (log.isDebugEnabled()) + log.debug(localAddress + ": suspect is " + address + ", completed " + flushOkCompleted + + ", flushOkSet " + flushCompletedMap + ", flushMembers " + flushMembers); } - if(flushOkCompleted){ + if (flushOkCompleted) { Digest digest = (Digest) down_prot.down(new Event(Event.GET_DIGEST)); FlushHeader fh = new FlushHeader(FlushHeader.FLUSH_COMPLETED, viewID); fh.addDigest(digest); - m.putHeader(getName(), fh); + m.putHeader(this.id, fh); down_prot.down(new Event(Event.MSG, m)); - if(log.isDebugEnabled()) - log.debug(localAddress + " sent FLUSH_COMPLETED message to " + flushCoordinator); + if (log.isDebugEnabled()) + log.debug(localAddress + ": sent FLUSH_COMPLETED message to " + flushCoordinator); } } - - public static class FlushHeader extends Header implements Streamable { + + public static class FlushHeader extends Header { public static final byte START_FLUSH = 0; public static final byte STOP_FLUSH = 2; - public static final byte FLUSH_COMPLETED = 3; + public static final byte FLUSH_COMPLETED = 3; public static final byte ABORT_FLUSH = 5; @@ -855,6 +904,8 @@ public static final byte FLUSH_RECONCILE_OK = 8; + public static final byte FLUSH_NOT_COMPLETED = 9; + byte type; long viewID; @@ -862,35 +913,34 @@ Collection
            flushParticipants; Digest digest = null; - private static final long serialVersionUID=-6248843990215637687L; - public FlushHeader(){ + public FlushHeader() { this(START_FLUSH, 0); } // used for externalization - public FlushHeader(byte type){ + public FlushHeader(byte type) { this(type, 0); } - public FlushHeader(byte type,long viewID){ + public FlushHeader(byte type, long viewID) { this(type, viewID, null); } - public FlushHeader(byte type,long viewID,Collection
            flushView){ + public FlushHeader(byte type, long viewID, Collection flushView) { this.type = type; this.viewID = viewID; - if(flushView != null){ + if (flushView != null) { this.flushParticipants = new ArrayList
            (flushView); } - } + } @Override public int size() { - int retval=Global.BYTE_SIZE; // type - retval+=Global.LONG_SIZE; // viewID - retval+= Util.size(flushParticipants); - retval+=Global.BYTE_SIZE; // presence for digest - if(digest != null){ + int retval = Global.BYTE_SIZE; // type + retval += Global.LONG_SIZE; // viewID + retval += Util.size(flushParticipants); + retval += Global.BYTE_SIZE; // presence for digest + if (digest != null) { retval += digest.serializedSize(); } return retval; @@ -901,57 +951,43 @@ } public String toString() { - switch(type){ - case START_FLUSH: - return "FLUSH[type=START_FLUSH,viewId=" + viewID - + ",members=" - + flushParticipants - + "]"; - case STOP_FLUSH: - return "FLUSH[type=STOP_FLUSH,viewId=" + viewID + "]"; - case ABORT_FLUSH: - return "FLUSH[type=ABORT_FLUSH,viewId=" + viewID + "]"; - case FLUSH_COMPLETED: - return "FLUSH[type=FLUSH_COMPLETED,viewId=" + viewID + "]"; - case FLUSH_BYPASS: - return "FLUSH[type=FLUSH_BYPASS,viewId=" + viewID + "]"; - case FLUSH_RECONCILE: - return "FLUSH[type=FLUSH_RECONCILE,viewId=" + viewID + ",digest=" + digest + "]"; - case FLUSH_RECONCILE_OK: - return "FLUSH[type=FLUSH_RECONCILE_OK,viewId=" + viewID + "]"; - default: - return "[FLUSH: unknown type (" + type + ")]"; + switch (type) { + case START_FLUSH: + return "FLUSH[type=START_FLUSH,viewId=" + viewID + ",members=" + + flushParticipants + "]"; + case STOP_FLUSH: + return "FLUSH[type=STOP_FLUSH,viewId=" + viewID + "]"; + case ABORT_FLUSH: + return "FLUSH[type=ABORT_FLUSH,viewId=" + viewID + "]"; + case FLUSH_COMPLETED: + return "FLUSH[type=FLUSH_COMPLETED,viewId=" + viewID + "]"; + case FLUSH_BYPASS: + return "FLUSH[type=FLUSH_BYPASS,viewId=" + viewID + "]"; + case FLUSH_RECONCILE: + return "FLUSH[type=FLUSH_RECONCILE,viewId=" + viewID + ",digest=" + digest + + "]"; + case FLUSH_RECONCILE_OK: + return "FLUSH[type=FLUSH_RECONCILE_OK,viewId=" + viewID + "]"; + default: + return "[FLUSH: unknown type (" + type + ")]"; } } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeByte(type); - out.writeLong(viewID); - out.writeObject(flushParticipants); - out.writeObject(digest); - } - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - type = in.readByte(); - viewID = in.readLong(); - flushParticipants = (Collection
            ) in.readObject(); - digest = (Digest) in.readObject(); - } public void writeTo(DataOutputStream out) throws IOException { out.writeByte(type); out.writeLong(viewID); - Util.writeAddresses(flushParticipants, out); - Util.writeStreamable(digest, out); + Util.writeAddresses(flushParticipants, out); + Util.writeStreamable(digest, out); } - public void readFrom(DataInputStream in) throws IOException, - IllegalAccessException, - InstantiationException { + @SuppressWarnings("unchecked") + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, + InstantiationException { type = in.readByte(); viewID = in.readLong(); - flushParticipants = Util.readAddresses(in, ArrayList.class); - digest = (Digest) Util.readStreamable(Digest.class, in); + flushParticipants =(Collection
            )Util.readAddresses(in, ArrayList.class); + digest = (Digest) Util.readStreamable(Digest.class, in); } } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/GmsImpl.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/GmsImpl.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/GmsImpl.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/GmsImpl.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,57 +1,49 @@ -// $Id: GmsImpl.java,v 1.29 2008/03/27 09:02:53 vlada Exp $ package org.jgroups.protocols.pbcast; -import org.apache.commons.logging.Log; import org.jgroups.*; +import org.jgroups.logging.Log; import org.jgroups.util.Digest; +import org.jgroups.util.MergeId; import java.util.Collection; +import java.util.Map; import java.util.Vector; -import java.util.List; public abstract class GmsImpl { - protected GMS gms=null; - protected final Log log; - final boolean trace; - final boolean warn; - volatile boolean leaving=false; - - protected GmsImpl() { - log=null; - trace=warn=false; - } + protected final GMS gms; + protected final Merger merger; + protected final Log log; + volatile boolean leaving=false; + protected GmsImpl(GMS gms) { this.gms=gms; + merger=gms.merger; log=gms.getLog(); - trace=log.isTraceEnabled(); - warn=log.isWarnEnabled(); } - public abstract void join(Address mbr); - public abstract void joinWithStateTransfer(Address local_addr); + public abstract void join(Address mbr, boolean useFlushIfPresent); + public abstract void joinWithStateTransfer(Address local_addr,boolean useFlushIfPresent); public abstract void leave(Address mbr); - public abstract void handleJoinResponse(JoinRsp join_rsp); - public abstract void handleLeaveResponse(); + public void handleJoinResponse(JoinRsp join_rsp) {} + public void handleLeaveResponse() {} - public abstract void suspect(Address mbr); - public abstract void unsuspect(Address mbr); + public void suspect(Address mbr) {} + public void unsuspect(Address mbr) {} - public void merge(Vector
            other_coords) {} // only processed by coord - public void handleMergeRequest(Address sender, ViewId merge_id) {} // only processed by coords - public void handleMergeResponse(MergeData data, ViewId merge_id) {} // only processed by coords - public void handleMergeView(MergeData data, ViewId merge_id) {} // only processed by coords - public void handleMergeCancelled(ViewId merge_id) {} // only processed by coords - - public abstract void handleMembershipChange(Collection requests); - public abstract void handleViewChange(View new_view, Digest digest); - public void handleExit() {} + public void merge(Map views) {} // only processed by coord + public void handleMergeRequest(Address sender, MergeId merge_id, Collection mbrs) {} // only processed by coords + public void handleMergeResponse(MergeData data, MergeId merge_id) {} // only processed by coords + public void handleMergeView(MergeData data, MergeId merge_id) {} // only processed by coords + public void handleMergeCancelled(MergeId merge_id) {} // only processed by coords + public void handleDigestResponse(Address sender, Digest digest) {} // only processed by coords - public boolean handleUpEvent(Event evt) {return true;} + public void handleMembershipChange(Collection requests) {} + public void handleViewChange(View new_view, Digest digest) {} public void init() throws Exception {leaving=false;} public void start() throws Exception {leaving=false;} @@ -59,15 +51,14 @@ - protected void sendMergeRejectedResponse(Address sender, ViewId merge_id) { + protected void sendMergeRejectedResponse(Address sender, MergeId merge_id) { Message msg=new Message(sender, null, null); msg.setFlag(Message.OOB); GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.MERGE_RSP); hdr.merge_rejected=true; hdr.merge_id=merge_id; - msg.putHeader(gms.getName(), hdr); - if(log.isDebugEnabled()) log.debug("response=" + hdr); - gms.getDownProtocol().down(new Event(Event.ENABLE_UNICASTS_TO, sender)); + msg.putHeader(gms.getId(), hdr); + if(log.isDebugEnabled()) log.debug("merge response=" + hdr); gms.getDownProtocol().down(new Event(Event.MSG, msg)); } @@ -84,7 +75,7 @@ approach would be to keep track of the primary partition and return the first member if we are the primary partition. */ - protected boolean iWouldBeCoordinator(Vector new_mbrs) { + protected boolean iWouldBeCoordinator(Vector
            new_mbrs) { Membership tmp_mbrs=gms.members.copy(); tmp_mbrs.merge(new_mbrs, null); tmp_mbrs.sort(); @@ -100,23 +91,27 @@ static final int JOIN_WITH_STATE_TRANSFER = 6; - int type=-1; - Address mbr; - boolean suspected; - Vector
            coordinators; - View view; - Digest digest; - List
            target_members; + int type=-1; + Address mbr; + boolean suspected; + Map views; // different view on MERGE + boolean useFlushIfPresent; - Request(int type) { - this.type=type; - } - Request(int type, Address mbr, boolean suspected, Vector
            coordinators) { + Request(int type, Address mbr, boolean suspected) { this.type=type; this.mbr=mbr; this.suspected=suspected; - this.coordinators=coordinators; + } + + Request(int type, Address mbr, boolean suspected, Map views, boolean useFlushPresent) { + this(type, mbr, suspected); + this.views=views; + this.useFlushIfPresent=useFlushPresent; + } + + Request(int type, Address mbr, boolean suspected, Map views) { + this(type, mbr, suspected, views, true); } public int getType() { @@ -129,7 +124,7 @@ case JOIN_WITH_STATE_TRANSFER: return "JOIN_WITH_STATE_TRANSFER(" + mbr + ")"; case LEAVE: return "LEAVE(" + mbr + ", " + suspected + ")"; case SUSPECT: return "SUSPECT(" + mbr + ")"; - case MERGE: return "MERGE(" + coordinators + ")"; + case MERGE: return "MERGE(" + views.size() + " views)"; } return "> flushInvokerClass; + private GmsImpl impl=null; private final Object impl_mutex=new Object(); // synchronizes event entry into impl private final Hashtable impls=new Hashtable(3); + + // Handles merge related tasks + final Merger merger=new Merger(this, log); protected Address local_addr=null; protected final Membership members=new Membership(); // real membership @@ -149,18 +143,13 @@ //[JGRP-700] - FLUSH: flushing should span merge protected final AckCollector merge_ack_collector=new AckCollector(); - boolean flushProtocolInStack=false; + boolean flushProtocolInStack=false; public GMS() { initState(); } - - public String getName() { - return name; - } - @ManagedAttribute public String getView() {return view_id != null? view_id.toString() : "null";} @ManagedAttribute @@ -168,17 +157,26 @@ @ManagedAttribute public String getLocalAddress() {return local_addr != null? local_addr.toString() : "null";} @ManagedAttribute - public String getMembers() {return members != null? members.toString() : "[]";} + public String getMembers() {return members.toString();} @ManagedAttribute - public int getNumMembers() {return members != null? members.size() : 0;} + public int getNumMembers() {return members.size();} public long getJoinTimeout() {return join_timeout;} public void setJoinTimeout(long t) {join_timeout=t;} + + public long getMergeTimeout() { + return merge_timeout; + } + + public void setMergeTimeout(long timeout) {merge_timeout=timeout;} + /** @deprecated */ - public long getJoinRetryTimeout() {return -1;} + public static long getJoinRetryTimeout() {return -1;} /** @deprecated */ public void setJoinRetryTimeout(long t) {} - public boolean isShun() {return shun;} - public void setShun(boolean s) {shun=s;} + @Deprecated + public static boolean isShun() {return false;} + @Deprecated + public void setShun(boolean s) {} @ManagedOperation public String printPreviousMembers() { StringBuilder sb=new StringBuilder(); @@ -198,6 +196,8 @@ } public void setViewAckCollectionTimeout(long view_ack_collection_timeout) { + if(view_ack_collection_timeout <= 0) + throw new IllegalArgumentException("view_ack_collection_timeout has to be greater than 0"); this.view_ack_collection_timeout=view_ack_collection_timeout; } @@ -213,7 +213,6 @@ return max_bundling_time; } - public void setMaxBundlingTime(long max_bundling_time) { this.max_bundling_time=max_bundling_time; } @@ -252,11 +251,30 @@ return sb.toString(); } + + @ManagedOperation + public void suspect(String suspected_member) { + if(suspected_member == null) + return; + Map contents= UUID.getContents(); + for(Map.Entry entry: contents.entrySet()) { + String logical_name=entry.getValue(); + if(logical_name != null && logical_name.equals(suspected_member)) { + Address suspect=entry.getKey(); + if(suspect != null) + up(new Event(Event.SUSPECT, suspect)); + } + } + } + public boolean isCoordinator() { Address coord=determineCoordinator(); return coord != null && local_addr != null && local_addr.equals(coord); } + public MergeId getMergeId() { + return impl instanceof CoordGmsImpl? ((CoordGmsImpl)impl).getMergeId() : null; + } public void setLogCollectMessages(boolean flag) { log_collect_msgs=flag; @@ -283,13 +301,11 @@ public void setImpl(GmsImpl new_impl) { synchronized(impl_mutex) { - if(impl == new_impl) // superfluous + if(impl == new_impl) // unnecessary ? return; impl=new_impl; - if(log.isDebugEnabled()) { - String msg=(local_addr != null? local_addr.toString()+" " : "") + "changed role to " + new_impl.getClass().getName(); - log.debug(msg); - } + if(log.isDebugEnabled()) + log.debug(local_addr != null? local_addr + ": " : "" + "changed role to " + new_impl.getClass().getName()); } } @@ -300,12 +316,16 @@ public void init() throws Exception { + if(view_ack_collection_timeout <= 0) + throw new IllegalArgumentException("view_ack_collection_timeout has to be greater than 0"); prev_members=new BoundedList
            (num_prev_mbrs); - timer=getTransport().getTimer(); + TP transport=getTransport(); + timer=transport.getTimer(); if(timer == null) - throw new Exception("GMS.init(): timer is null"); + throw new Exception("timer is null"); if(impl != null) impl.init(); + transport.registerProbeHandler(this); } public void start() throws Exception { @@ -372,6 +392,11 @@ return impl != null && impl instanceof CoordGmsImpl; } + @ManagedOperation(description="Fetches digests from all members and installs them, unblocking blocked members") + public void fixDigests() { + if(impl instanceof CoordGmsImpl) + ((CoordGmsImpl)impl).fixDigests(); + } /** * Computes the next view. Returns a copy that has old_mbrs and @@ -395,15 +420,17 @@ tmp_mbrs.remove(old_mbrs); tmp_mbrs.add(new_mbrs); mbrs=tmp_mbrs.getMembers(); - v=new View(local_addr, vid, mbrs); + Address new_coord=local_addr; + if(!mbrs.isEmpty()) + new_coord=mbrs.firstElement(); + v=new View(new_coord, vid, mbrs); // Update membership (see DESIGN for explanation): tmp_members.set(mbrs); // Update joining list (see DESIGN for explanation) if(new_mbrs != null) { - for(Iterator
            it=new_mbrs.iterator(); it.hasNext();) { - Address tmp_mbr=it.next(); + for(Address tmp_mbr: new_mbrs) { if(!joining.contains(tmp_mbr)) joining.addElement(tmp_mbr); } @@ -411,15 +438,13 @@ // Update leaving list (see DESIGN for explanations) if(old_mbrs != null) { - for(Iterator
            it=old_mbrs.iterator(); it.hasNext();) { - Address addr=it.next(); + for(Address addr: old_mbrs) { if(!leaving.contains(addr)) leaving.add(addr); } } if(suspected_mbrs != null) { - for(Iterator
            it=suspected_mbrs.iterator(); it.hasNext();) { - Address addr=it.next(); + for(Address addr:suspected_mbrs) { if(!leaving.contains(addr)) leaving.add(addr); } @@ -436,20 +461,23 @@ * @param digest * @param newMembers */ - public void castViewChangeWithDest(View new_view, Digest digest, JoinRsp jr, Collection
            newMembers) { + public void castViewChangeWithDest(View new_view, Digest digest, JoinRsp jr, Collection
            newMembers) { if(log.isTraceEnabled()) - log.trace("mcasting view {" + new_view + "} (" + new_view.size() + " mbrs)\n"); + log.trace(local_addr + ": mcasting view {" + new_view + "} (" + new_view.size() + " mbrs)\n"); Message view_change_msg=new Message(); // bcast to all members GmsHeader hdr=new GmsHeader(GmsHeader.VIEW, new_view); hdr.my_digest=digest; - view_change_msg.putHeader(name, hdr); + view_change_msg.putHeader(this.id, hdr); List
            ackMembers = new ArrayList
            (new_view.getMembers()); if(newMembers != null && !newMembers.isEmpty()) { ackMembers.removeAll(newMembers); } - ack_collector.reset(new_view.getVid(), ackMembers); + if(!ackMembers.isEmpty()) + ack_collector.reset(ackMembers); + else + ack_collector.reset(null); // Send down a local TMP_VIEW event. This is needed by certain layers (e.g. NAKACK) to compute correct digest @@ -460,63 +488,46 @@ down_prot.down(new Event(Event.MSG, view_change_msg)); try { - ack_collector.waitForAllAcks(view_ack_collection_timeout); - if(log.isTraceEnabled()) - log.trace("received all ACKs (" + ack_collector.size() - + ") for " - + new_view.getVid()); + if(!ackMembers.isEmpty()) { + ack_collector.waitForAllAcks(view_ack_collection_timeout); + if(log.isTraceEnabled()) + log.trace(local_addr + ": received all ACKs (" + ack_collector.expectedAcks() + + ") from existing members for view " + new_view.getVid()); + } } catch(TimeoutException e) { if(log_collect_msgs && log.isWarnEnabled()) { - log.warn(local_addr + " failed to collect all ACKs (" + ack_collector.size() - + ") for view " - + new_view - + " after " - + view_ack_collection_timeout - + "ms, missing ACKs from " - + ack_collector.printMissing() - + " (received=" - + ack_collector.printReceived() - + "), local_addr=" - + local_addr); + log.warn(local_addr + ": failed to collect all ACKs (expected=" + ack_collector.expectedAcks() + + ") for view " + new_view + " after " + view_ack_collection_timeout + "ms, missing ACKs from " + + ack_collector.printMissing()); } - } - + } + if(jr != null && (newMembers != null && !newMembers.isEmpty())) { - ack_collector.reset(new_view.getVid(), new ArrayList
            (newMembers)); - for(Address joiner:newMembers) { + ack_collector.reset(new ArrayList
            (newMembers)); + for(Address joiner: newMembers) { sendJoinResponse(jr, joiner); } try { ack_collector.waitForAllAcks(view_ack_collection_timeout); if(log.isTraceEnabled()) - log.trace("received all ACKs (" + ack_collector.size() - + ") for " - + new_view.getVid()); + log.trace(local_addr + ": received all ACKs (" + ack_collector.expectedAcks() + + ") from joiners for view " + new_view.getVid()); } catch(TimeoutException e) { if(log_collect_msgs && log.isWarnEnabled()) { - log.warn(local_addr + " failed to collect all ACKs (" + ack_collector.size() - + ") for unicasted view " - + new_view - + " after " - + view_ack_collection_timeout - + "ms, missing ACKs from " - + ack_collector.printMissing() - + " (received=" - + ack_collector.printReceived() - + "), local_addr=" - + local_addr); + log.warn(local_addr + ": failed to collect all ACKs (expected=" + ack_collector.expectedAcks() + + ") for unicast view " + new_view + " after " + view_ack_collection_timeout + "ms, missing ACKs from " + + ack_collector.printMissing()); } } - } + } } - + public void sendJoinResponse(JoinRsp rsp, Address dest) { Message m=new Message(dest, null, null); GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.JOIN_RSP, rsp); - m.putHeader(getName(), hdr); - getDownProtocol().down(new Event(Event.ENABLE_UNICASTS_TO, dest)); + m.putHeader(this.id, hdr); getDownProtocol().down(new Event(Event.MSG, m)); } @@ -541,16 +552,20 @@ rc=vid.compareTo(view_id); if(rc <= 0) { if(log.isWarnEnabled() && rc < 0) // only scream if view is smaller, silently discard same views - log.warn("[" + local_addr + "] received view < current view;" + + log.warn(local_addr + ": received view < current view;" + " discarding it (current vid: " + view_id + ", new vid: " + vid + ')'); return; } } - if(digest != null) - mergeDigest(digest); + if(digest != null) { + if(new_view instanceof MergeView) + mergeDigest(digest); + else + setDigest(digest); + } - if(log.isDebugEnabled()) log.debug("[local_addr=" + local_addr + "] view is " + new_view); + if(log.isDebugEnabled()) log.debug(local_addr + ": view is " + new_view); if(stats) { num_views++; prev_views.add(new_view); @@ -562,24 +577,9 @@ ltime=Math.max(vid.getId(), ltime); // compute Lamport logical time /* Check for self-inclusion: if I'm not part of the new membership, I just discard it. - This ensures that messages sent in view V1 are only received by members of V1 */ + This ensures that messages sent in view V1 are only received by members of V1 */ if(checkSelfInclusion(mbrs) == false) { - // only shun if this member was previously part of the group. avoids problem where multiple - // members (e.g. X,Y,Z) join {A,B} concurrently, X is joined first, and Y and Z get view - // {A,B,X}, which would cause Y and Z to be shunned as they are not part of the membership - // bela Nov 20 2003 - if(shun && local_addr != null && prev_members.contains(local_addr)) { - if(log.isWarnEnabled()) - log.warn("I (" + local_addr + ") am not a member of view " + new_view + - ", shunning myself and leaving the group (prev_members are " + prev_members + - ", current view is " + view + ")"); - if(impl != null) - impl.handleExit(); - up_prot.up(new Event(Event.EXIT)); - } - else { - if(log.isWarnEnabled()) log.warn("I (" + local_addr + ") am not a member of view " + new_view + "; discarding view"); - } + if(log.isWarnEnabled()) log.warn(local_addr + ": not member of view " + new_view + "; discarding it"); return; } @@ -603,8 +603,7 @@ tmp_members.remove(leaving); // remove members that haven't yet been removed from the membership // add to prev_members - for(Iterator
            it=mbrs.iterator(); it.hasNext();) { - Address addr=it.next(); + for(Address addr: mbrs) { if(!prev_members.contains(addr)) prev_members.add(addr); } @@ -625,8 +624,10 @@ becomeCoordinator(); } else { - if(haveCoordinatorRole() && !local_addr.equals(coord)) + if(haveCoordinatorRole() && !local_addr.equals(coord)) { becomeParticipant(); + merge_ack_collector.reset(null); // we don't need this one anymore + } } } } @@ -634,7 +635,7 @@ protected Address determineCoordinator() { synchronized(members) { - return members != null && members.size() > 0? members.elementAt(0) : null; + return members.size() > 0? members.elementAt(0) : null; } } @@ -709,57 +710,84 @@ return (Digest)down_prot.down(Event.GET_DIGEST_EVT); } - boolean startFlush(View new_view) { - boolean successfulFlush=true; - boolean validView=new_view != null && new_view.size() > 0; - if(validView && flushProtocolInStack) { - successfulFlush=(Boolean)up_prot.up(new Event(Event.SUSPEND, - new ArrayList
            (new_view.getMembers()))); + boolean startFlush(View view) { + return _startFlush(view, 4, 1000L, 5000L); + } - if(successfulFlush) { - if(log.isTraceEnabled()) - log.trace("Successful GMS flush by coordinator at " + getLocalAddress()); + boolean startFlush(View view, int maxAttempts, long floor, long ceiling) { + return _startFlush(view, maxAttempts, floor, ceiling); + } + + protected boolean _startFlush(final View new_view, int maxAttempts, long randomFloor, long randomCeiling) { + if(flushInvokerClass != null) { + try { + Callable invoker = flushInvokerClass.getDeclaredConstructor(View.class).newInstance(new_view); + return invoker.call(); + } catch (Throwable e) { + return false; } - else { - if(log.isWarnEnabled()) - log.warn("GMS flush by coordinator at " + getLocalAddress() + " failed"); + } + + try { + boolean successfulFlush=true; + boolean validView=new_view != null && new_view.size() > 0; + if(validView && flushProtocolInStack) { + + int attemptCount = 0; + while(attemptCount < maxAttempts){ + successfulFlush=(Boolean)up_prot.up(new Event(Event.SUSPEND, new ArrayList
            (new_view.getMembers()))); + if(successfulFlush) + break; + Util.sleepRandom(randomFloor,randomCeiling); + attemptCount++; + } + + if(successfulFlush) { + if(log.isTraceEnabled()) + log.trace(local_addr + ": successful GMS flush by coordinator"); + } + else { + if(log.isWarnEnabled()) + log.warn(local_addr + ": GMS flush by coordinator failed"); + } } + return successfulFlush; + } catch (Exception e) { + return false; } - return successfulFlush; } void stopFlush() { if(flushProtocolInStack) { if(log.isDebugEnabled()) { - log.debug(getLocalAddress() + " sending RESUME event"); + log.debug(local_addr + ": sending RESUME event"); } up_prot.up(new Event(Event.RESUME)); } } void stopFlush(List
            members) { - if(log.isDebugEnabled()){ - log.debug(getLocalAddress() + " sending RESUME event"); + log.debug(local_addr + ": sending RESUME event"); } up_prot.up(new Event(Event.RESUME,members)); } - + @SuppressWarnings("unchecked") public Object up(Event evt) { switch(evt.getType()) { case Event.MSG: Message msg=(Message)evt.getArg(); - GmsHeader hdr=(GmsHeader)msg.getHeader(name); + GmsHeader hdr=(GmsHeader)msg.getHeader(this.id); if(hdr == null) break; switch(hdr.type) { case GmsHeader.JOIN_REQ: - view_handler.add(new Request(Request.JOIN, hdr.mbr, false, null)); + view_handler.add(new Request(Request.JOIN, hdr.mbr, false, null, hdr.useFlushIfPresent)); break; case GmsHeader.JOIN_REQ_WITH_STATE_TRANSFER: - view_handler.add(new Request(Request.JOIN_WITH_STATE_TRANSFER, hdr.mbr, false, null)); + view_handler.add(new Request(Request.JOIN_WITH_STATE_TRANSFER, hdr.mbr, false, null, hdr.useFlushIfPresent)); break; case GmsHeader.JOIN_RSP: impl.handleJoinResponse(hdr.join_rsp); @@ -768,20 +796,17 @@ if(log.isDebugEnabled()) log.debug("received LEAVE_REQ for " + hdr.mbr + " from " + msg.getSrc()); if(hdr.mbr == null) { - if(log.isErrorEnabled()) log.error("LEAVE_REQ's mbr field is null"); return null; } - view_handler.add(new Request(Request.LEAVE, hdr.mbr, false, null)); + view_handler.add(new Request(Request.LEAVE, hdr.mbr, false)); break; case GmsHeader.LEAVE_RSP: impl.handleLeaveResponse(); break; case GmsHeader.VIEW: View new_view=hdr.view; - if(new_view == null) { - if(log.isErrorEnabled()) log.error("[VIEW]: view == null"); + if(new_view == null) return null; - } Address coord=msg.getSrc(); if(!new_view.containsMember(coord)) { @@ -801,19 +826,15 @@ case GmsHeader.MERGE_REQ: down_prot.down(new Event(Event.SUSPEND_STABLE, 20000)); - - if(log.isDebugEnabled()){ - log.debug("Merge participant " + local_addr + " got merge request from " + msg.getSrc()); - } - impl.handleMergeRequest(msg.getSrc(), hdr.merge_id); + impl.handleMergeRequest(msg.getSrc(), hdr.merge_id, hdr.mbrs); break; case GmsHeader.MERGE_RSP: MergeData merge_data=new MergeData(msg.getSrc(), hdr.view, hdr.my_digest); merge_data.merge_rejected=hdr.merge_rejected; if(log.isDebugEnabled()) { - log.debug("Got merge response at " + local_addr + " from " + msg.getSrc() + - ", merge_id=" + hdr.view+ ", merge data is "+ merge_data); + log.debug(local_addr + ": got merge response from " + msg.getSrc() + + ", merge_id=" + hdr.merge_id + ", merge data is "+ merge_data); } impl.handleMergeResponse(merge_data, hdr.merge_id); break; @@ -822,6 +843,11 @@ impl.handleMergeView(new MergeData(msg.getSrc(), hdr.view, hdr.my_digest), hdr.merge_id); down_prot.down(new Event(Event.RESUME_STABLE)); break; + + case GmsHeader.INSTALL_DIGEST: + Digest tmp=hdr.my_digest; + down_prot.down(new Event(Event.MERGE_DIGEST, tmp)); + break; case GmsHeader.INSTALL_MERGE_VIEW_OK: //[JGRP-700] - FLUSH: flushing should span merge @@ -834,92 +860,100 @@ down_prot.down(new Event(Event.RESUME_STABLE)); break; + case GmsHeader.GET_DIGEST_REQ: + Digest digest=(Digest)down_prot.down(Event.GET_DIGEST_EVT); + if(digest != null) { + Digest.Entry entry=digest.get(local_addr); + if(entry != null) { + // only return my own digest information, but nobody else's ! + // https://jira.jboss.org/jira/browse/JGRP-948 + Digest retval=new Digest(local_addr, entry.getLow(), entry.getHighestDeliveredSeqno(), + entry.getHighestReceivedSeqno()); + GmsHeader rsp_hdr=new GmsHeader(GmsHeader.GET_DIGEST_RSP); + rsp_hdr.my_digest=retval; + Message get_digest_rsp=new Message(msg.getSrc(), null, null); + get_digest_rsp.setFlag(Message.OOB); + get_digest_rsp.putHeader(this.id, rsp_hdr); + down_prot.down(new Event(Event.MSG, get_digest_rsp)); + } + } + break; + + case GmsHeader.GET_DIGEST_RSP: + Digest digest_rsp=hdr.my_digest; + impl.handleDigestResponse(msg.getSrc(), digest_rsp); + break; + default: if(log.isErrorEnabled()) log.error("GmsHeader with type=" + hdr.type + " not known"); } return null; // don't pass up - case Event.SET_LOCAL_ADDRESS: - local_addr=(Address)evt.getArg(); - break; // pass up - case Event.SUSPECT: + Object retval=up_prot.up(evt); Address suspected=(Address)evt.getArg(); - view_handler.add(new Request(Request.SUSPECT, suspected, true, null)); + view_handler.add(new Request(Request.SUSPECT, suspected, true)); ack_collector.suspect(suspected); merge_ack_collector.suspect(suspected); - break; // pass up + return retval; case Event.UNSUSPECT: impl.unsuspect((Address)evt.getArg()); return null; // discard case Event.MERGE: - view_handler.add(new Request(Request.MERGE, null, false, (Vector
            )evt.getArg())); + view_handler.add(new Request(Request.MERGE, null, false, (Map)evt.getArg())); return null; // don't pass up } - - if(impl.handleUpEvent(evt)) - return up_prot.up(evt); - return null; + return up_prot.up(evt); } - - /** - This method is overridden to avoid hanging on getDigest(): when a JOIN is received, the coordinator needs - to retrieve the digest from the NAKACK layer. It therefore sends down a GET_DIGEST event, to which the NAKACK layer - responds with a GET_DIGEST_OK event.

            - However, the GET_DIGEST_OK event will not be processed because the thread handling the JOIN request won't process - the GET_DIGEST_OK event until the JOIN event returns. The receiveUpEvent() method is executed by the up-handler - thread of the lower protocol and therefore can handle the event. All we do here is unblock the mutex on which - JOIN is waiting, allowing JOIN to return with a valid digest. The GET_DIGEST_OK event is then discarded, because - it won't be processed twice. - */ -// public void receiveUpEvent(Event evt) { -// switch(evt.getType()) { -// case Event.GET_DIGEST_OK: -// digest_promise.setResult(evt.getArg()); -// return; // don't pass further up -// } -// super.receiveUpEvent(evt); -// } - - + @SuppressWarnings("unchecked") public Object down(Event evt) { - Object arg=null; - switch(evt.getType()) { + int type=evt.getType(); + + switch(type) { case Event.CONNECT: + case Event.CONNECT_USE_FLUSH: + case Event.CONNECT_WITH_STATE_TRANSFER: + case Event.CONNECT_WITH_STATE_TRANSFER_USE_FLUSH: + boolean use_flush=type == Event.CONNECT_USE_FLUSH || type == Event.CONNECT_WITH_STATE_TRANSFER_USE_FLUSH; + boolean state_transfer=type == Event.CONNECT_WITH_STATE_TRANSFER + || type == Event.CONNECT_WITH_STATE_TRANSFER_USE_FLUSH; + if(print_local_addr) { - System.out.println("\n---------------------------------------------------------\n" + - "GMS: address is " + local_addr + " (cluster=" + evt.getArg() + ")" + - "\n---------------------------------------------------------"); - } - down_prot.down(evt); - if(local_addr == null) - if(log.isFatalEnabled()) log.fatal("[CONNECT] local_addr is null"); - try { - impl.join(local_addr); + PhysicalAddress physical_addr=print_physical_addrs? + (PhysicalAddress)down(new Event(Event.GET_PHYSICAL_ADDRESS, local_addr)) : null; + System.out.println("\n-------------------------------------------------------------------\n" + + "GMS: address=" + local_addr + ", cluster=" + evt.getArg() + + (physical_addr != null? ", physical address=" + physical_addr : "") + + "\n-------------------------------------------------------------------"); } - catch(Throwable e) { - arg=e; + else { + if(log.isDebugEnabled()) { + PhysicalAddress physical_addr=print_physical_addrs? + (PhysicalAddress)down(new Event(Event.GET_PHYSICAL_ADDRESS, local_addr)) : null; + log.debug("address=" + local_addr + ", cluster=" + evt.getArg() + + (physical_addr != null? ", physical address=" + physical_addr : "")); + } } - return arg; // don't pass down: was already passed down - - case Event.CONNECT_WITH_STATE_TRANSFER: down_prot.down(evt); if(local_addr == null) if(log.isFatalEnabled()) log.fatal("[CONNECT] local_addr is null"); try { - impl.joinWithStateTransfer(local_addr); + if(state_transfer) + impl.joinWithStateTransfer(local_addr, use_flush); + else + impl.join(local_addr, use_flush); } catch(Throwable e) { - arg=e; + return e; } - return arg; // don't pass down: was already passed down - + return null; // don't pass down: event has already been passed down + case Event.DISCONNECT: impl.leave((Address)evt.getArg()); if(!(impl instanceof CoordGmsImpl)) { @@ -934,12 +968,28 @@ flushProtocolInStack=true; } break; + + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; } return down_prot.down(evt); } + public Map handleProbe(String... keys) { + for(String key: keys) { + if(key.equals("fix-digests")) { + fixDigests(); + } + } + return null; + } + + public String[] supportedKeys() { + return new String[]{"fix-digests"}; + } /* ------------------------------- Private Methods --------------------------------- */ @@ -954,9 +1004,7 @@ Message view_ack=new Message(dest, null, null); view_ack.setFlag(Message.OOB); GmsHeader tmphdr=new GmsHeader(GmsHeader.VIEW_ACK); - view_ack.putHeader(name, tmphdr); - if(log.isTraceEnabled()) - log.trace("sending VIEW_ACK to " + dest); + view_ack.putHeader(this.id, tmphdr); down_prot.down(new Event(Event.MSG, view_ack)); } @@ -964,7 +1012,7 @@ - public static class GmsHeader extends Header implements Streamable { + public static class GmsHeader extends Header { public static final byte JOIN_REQ=1; public static final byte JOIN_RSP=2; public static final byte LEAVE_REQ=3; @@ -977,15 +1025,20 @@ public static final byte VIEW_ACK=10; public static final byte JOIN_REQ_WITH_STATE_TRANSFER = 11; public static final byte INSTALL_MERGE_VIEW_OK=12; + public static final byte GET_DIGEST_REQ=13; + public static final byte GET_DIGEST_RSP=14; + public static final byte INSTALL_DIGEST=15; + byte type=0; View view=null; // used when type=VIEW or MERGE_RSP or INSTALL_MERGE_VIEW Address mbr=null; // used when type=JOIN_REQ or LEAVE_REQ + Collection mbrs=null; // used with MERGE_REQ + boolean useFlushIfPresent; // used when type=JOIN_REQ JoinRsp join_rsp=null; // used when type=JOIN_RSP Digest my_digest=null; // used when type=MERGE_RSP or INSTALL_MERGE_VIEW - ViewId merge_id=null; // used when type=MERGE_REQ or MERGE_RSP or INSTALL_MERGE_VIEW or CANCEL_MERGE + MergeId merge_id=null; // used when type=MERGE_REQ or MERGE_RSP or INSTALL_MERGE_VIEW or CANCEL_MERGE boolean merge_rejected=false; // used when type=MERGE_RSP - private static final long serialVersionUID=2369798797842183276L; public GmsHeader() { @@ -1004,9 +1057,19 @@ /** Used for JOIN_REQ or LEAVE_REQ header */ - public GmsHeader(byte type, Address mbr) { + public GmsHeader(byte type, Address mbr,boolean useFlushIfPresent) { this.type=type; this.mbr=mbr; + this.useFlushIfPresent = useFlushIfPresent; + } + + public GmsHeader(byte type, Address mbr) { + this(type,mbr,true); + } + + public GmsHeader(byte type, Collection

            mbrs) { + this(type); + this.mbrs=mbrs; } /** Used for JOIN_RSP header */ @@ -1023,11 +1086,29 @@ return mbr; } + public MergeId getMergeId() { + return merge_id; + } + + public void setMergeId(MergeId merge_id) { + this.merge_id=merge_id; + } + + public boolean isMergeRejected() { + return merge_rejected; + } + + public void setMergeRejected(boolean merge_rejected) { + this.merge_rejected=merge_rejected; + } + public String toString() { StringBuilder sb=new StringBuilder("GmsHeader"); sb.append('[' + type2String(type) + ']'); switch(type) { case JOIN_REQ: + case LEAVE_REQ: + case GET_DIGEST_REQ: sb.append(": mbr=" + mbr); break; @@ -1035,28 +1116,23 @@ sb.append(": join_rsp=" + join_rsp); break; - case LEAVE_REQ: - sb.append(": mbr=" + mbr); - break; - - case LEAVE_RSP: - break; - case VIEW: case VIEW_ACK: sb.append(": view=" + view); break; case MERGE_REQ: - sb.append(": merge_id=" + merge_id); + sb.append(": merge_id=" + merge_id).append(", mbrs=" + mbrs); break; case MERGE_RSP: - sb.append(": view=" + view + ", digest=" + my_digest + ", merge_rejected=" + merge_rejected + - ", merge_id=" + merge_id); + sb.append(": view=" + view + ", digest=" + my_digest + ", merge_id=" + merge_id); + if(merge_rejected) sb.append(", merge_rejected=" + merge_rejected); break; case INSTALL_MERGE_VIEW: + case GET_DIGEST_RSP: + case INSTALL_DIGEST: sb.append(": view=" + view + ", digest=" + my_digest); break; @@ -1081,43 +1157,27 @@ case CANCEL_MERGE: return "CANCEL_MERGE"; case VIEW_ACK: return "VIEW_ACK"; case JOIN_REQ_WITH_STATE_TRANSFER: return "JOIN_REQ_WITH_STATE_TRANSFER"; + case INSTALL_MERGE_VIEW_OK: return "INSTALL_MERGE_VIEW_OK"; + case GET_DIGEST_REQ: return "GET_DIGEST_REQ"; + case GET_DIGEST_RSP: return "GET_DIGEST_RSP"; + case INSTALL_DIGEST: return "INSTALL_DIGEST"; default: return ""; } } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeByte(type); - out.writeObject(view); - out.writeObject(mbr); - out.writeObject(join_rsp); - out.writeObject(my_digest); - out.writeObject(merge_id); - out.writeBoolean(merge_rejected); - } - - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - type=in.readByte(); - view=(View)in.readObject(); - mbr=(Address)in.readObject(); - join_rsp=(JoinRsp)in.readObject(); - my_digest=(Digest)in.readObject(); - merge_id=(ViewId)in.readObject(); - merge_rejected=in.readBoolean(); - } - - public void writeTo(DataOutputStream out) throws IOException { out.writeByte(type); boolean isMergeView=view != null && view instanceof MergeView; out.writeBoolean(isMergeView); Util.writeStreamable(view, out); Util.writeAddress(mbr, out); + Util.writeAddresses(mbrs, out); Util.writeStreamable(join_rsp, out); Util.writeStreamable(my_digest, out); - Util.writeStreamable(merge_id, out); // kludge: we know merge_id is a ViewId + Util.writeStreamable(merge_id, out); out.writeBoolean(merge_rejected); + out.writeBoolean(useFlushIfPresent); } public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { @@ -1128,10 +1188,12 @@ else view=(View)Util.readStreamable(View.class, in); mbr=Util.readAddress(in); + mbrs=Util.readAddresses(in, ArrayList.class); join_rsp=(JoinRsp)Util.readStreamable(JoinRsp.class, in); my_digest=(Digest)Util.readStreamable(Digest.class, in); - merge_id=(ViewId)Util.readStreamable(ViewId.class, in); + merge_id=(MergeId)Util.readStreamable(MergeId.class, in); merge_rejected=in.readBoolean(); + useFlushIfPresent=in.readBoolean(); } public int size() { @@ -1144,6 +1206,8 @@ retval+=Util.size(mbr); + retval+=Util.size(mbrs); + retval+=Global.BYTE_SIZE; // presence of join_rsp if(join_rsp != null) retval+=join_rsp.serializedSize(); @@ -1154,7 +1218,9 @@ retval+=Global.BYTE_SIZE; // presence for merge_id if(merge_id != null) - retval+=merge_id.serializedSize(); + retval+=merge_id.size(); + + retval+=Global.BYTE_SIZE; // boolean useFlushIfPresent return retval; } @@ -1171,37 +1237,29 @@ /** * Class which processes JOIN, LEAVE and MERGE requests. Requests are queued and processed in FIFO order * @author Bela Ban - * @version $Id: GMS.java,v 1.154 2008/12/05 09:21:13 belaban Exp $ */ class ViewHandler implements Runnable { - volatile Thread thread; - Queue q=new Queue(); // Queue - boolean suspended=false; - final static long INTERVAL=5000; - private static final long MAX_COMPLETION_TIME=10000; + volatile Thread thread; + final Queue queue=new Queue(); // Queue + volatile boolean suspended=false; + final static long INTERVAL=5000; + private static final long MAX_COMPLETION_TIME=10000; /** Maintains a list of the last 20 requests */ - private final BoundedList history=new BoundedList(20); + private final BoundedList history=new BoundedList(20); /** Map. Keeps track of Resumer tasks which have not fired yet */ - private final Map resume_tasks=new HashMap(); - private Object merge_id=null; + private final Map resume_tasks=new HashMap(); - void add(Request req) { - add(req, false, false); - } - - synchronized void add(Request req, boolean at_head, boolean unsuspend) { - if(suspended && !unsuspend) { - log.warn("queue is suspended; request " + req + " is discarded"); + synchronized void add(Request req) { + if(suspended) { + if(log.isTraceEnabled()) + log.trace("queue is suspended; request " + req + " is discarded"); return; } - start(unsuspend); + start(); try { - if(at_head) - q.addAtHead(req); - else - q.add(req); + queue.add(req); history.add(new Date() + ": " + req.toString()); } catch(QueueClosedException e) { @@ -1230,62 +1288,66 @@ resumeForce(); } + synchronized void start() { + if(queue.closed()) + queue.reset(); + if(thread == null || !thread.isAlive()) { + thread=getThreadFactory().newThread(this, "ViewHandler"); + thread.setDaemon(false); // thread cannot terminate if we have tasks left, e.g. when we as coord leave + thread.start(); + } + } + + synchronized void stop(boolean flush) { + queue.close(flush); + synchronized(resume_tasks) { + for(Future future: resume_tasks.values()) { + future.cancel(true); + } + resume_tasks.clear(); + } + } + /** - * Waits until the current request has been processes, then clears the queue and discards new + * Waits until the current request has been processed, then clears the queue and discards new * requests from now on */ - public synchronized void suspend(Object merge_id) { + public synchronized void suspend(MergeId merge_id) { if(!suspended) { suspended=true; - this.merge_id=merge_id; - q.clear(); + queue.clear(); waitUntilCompleted(MAX_COMPLETION_TIME); - q.close(true); - - if(log.isDebugEnabled()) - log.debug("suspended ViewHandler at " + local_addr); + queue.close(true); Resumer resumer=new Resumer(merge_id, resume_tasks, this); Future future=timer.schedule(resumer, resume_task_timeout, TimeUnit.MILLISECONDS); Future old_future=resume_tasks.put(merge_id, future); if(old_future != null) old_future.cancel(true); - } - else { - if(log.isWarnEnabled()) { - log.warn("attempted suspend on ViewHandler at " + local_addr+ ", however, it is already suspended"); - } + if(log.isTraceEnabled()) + log.trace(local_addr + ": view handler for merge_id " + merge_id + " was suspended"); } } - public synchronized void resume(Object merge_id) { - if(suspended) { - boolean same_merge_id=this.merge_id != null && merge_id != null && this.merge_id.equals(merge_id); - same_merge_id=same_merge_id || (this.merge_id == null && merge_id == null); + public synchronized void resume(MergeId merge_id) { + if(!suspended) + return; - if(same_merge_id) { - synchronized(resume_tasks) { - Future future=resume_tasks.get(merge_id); - if(future != null) { - future.cancel(false); - resume_tasks.remove(merge_id); - } - } - } - else{ - if(log.isWarnEnabled()) - log.warn("resume(" + merge_id+ ") does not match "+ this.merge_id); - } - resumeForce(); - } + Future future; + synchronized(resume_tasks) { + future=resume_tasks.remove(merge_id); + } + if(future != null) + future.cancel(true); + resumeForce(); } public synchronized void resumeForce() { - if(q.closed()) - q.reset(); + if(queue.closed()) + queue.reset(); suspended=false; if(log.isTraceEnabled()) - log.trace("resumed ViewHandler"); + log.trace("view handler was resumed"); } public void run() { @@ -1296,19 +1358,19 @@ boolean keepGoing=false; end_time=System.currentTimeMillis() + max_bundling_time; do { - Request firstRequest=(Request)q.remove(INTERVAL); // throws a TimeoutException if it runs into timeout + Request firstRequest=(Request)queue.remove(INTERVAL); // throws a TimeoutException if it runs into timeout requests.add(firstRequest); if(!view_bundling) break; - if(q.size() > 0) { - Request nextReq=(Request)q.peek(); + if(queue.size() > 0) { + Request nextReq=(Request)queue.peek(); keepGoing=view_bundling && firstRequest.canBeProcessedTogether(nextReq); } else { wait_time=end_time - System.currentTimeMillis(); if(wait_time > 0) - q.waitUntilClosed(wait_time); // misnomer: waits until element has been added or q closed - keepGoing=q.size() > 0 && firstRequest.canBeProcessedTogether((Request)q.peek()); + queue.waitUntilClosed(wait_time); // misnomer: waits until element has been added or q closed + keepGoing=queue.size() > 0 && firstRequest.canBeProcessedTogether((Request)queue.peek()); } } while(keepGoing && System.currentTimeMillis() < end_time); @@ -1332,11 +1394,11 @@ } } - public int size() {return q.size();} + public int size() {return queue.size();} public boolean suspended() {return suspended;} public String dumpQueue() { StringBuilder sb=new StringBuilder(); - List v=q.values(); + List v=queue.values(); for(Iterator it=v.iterator(); it.hasNext();) { sb.append(it.next() + "\n"); } @@ -1354,8 +1416,6 @@ private void process(List requests) { if(requests.isEmpty()) return; - if(log.isTraceEnabled()) - log.trace("processing " + requests); Request firstReq=requests.get(0); switch(firstReq.type) { case Request.JOIN: @@ -1365,49 +1425,13 @@ impl.handleMembershipChange(requests); break; case Request.MERGE: - if(requests.size() > 1) - log.error("more than one MERGE request to process, ignoring the others"); - impl.merge(firstReq.coordinators); + impl.merge(firstReq.views); break; default: log.error("request " + firstReq.type + " is unknown; discarded"); } } - - - - synchronized void start(boolean unsuspend) { - if(q.closed()) - q.reset(); - if(unsuspend) { - suspended=false; - Future future; - synchronized(resume_tasks) { - future=resume_tasks.remove(merge_id); - } - if(future != null) - future.cancel(true); - } - merge_id=null; - if(thread == null || !thread.isAlive()) { - thread=getThreadFactory().newThread(this, "ViewHandler"); - thread.setDaemon(false); // thread cannot terminate if we have tasks left, e.g. when we as coord leave - thread.start(); - } - } - - synchronized void stop(boolean flush) { - q.close(flush); - synchronized(resume_tasks) { - for(Future future: resume_tasks.values()) { - future.cancel(true); - } - resume_tasks.clear(); - } - merge_id=null; - // resumeForce(); - } } @@ -1420,35 +1444,35 @@ * will never be used... */ static class Resumer implements Runnable { - final Object token; - final Map tasks; + final MergeId token; + final Map tasks; final ViewHandler handler; - public Resumer(final Object token, final Map t, final ViewHandler handler) { + public Resumer(final MergeId token, final Map t, final ViewHandler handler) { this.token=token; this.tasks=t; this.handler=handler; - } +} public void run() { - boolean execute=true; + boolean executed=true; synchronized(tasks) { Future future=tasks.get(token); if(future != null) { future.cancel(false); - execute=true; + executed=true; } else { - execute=false; + executed=false; } tasks.remove(token); } - if(execute) { + if(executed) { handler.resume(token); } } } -} +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/JoinRsp.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/JoinRsp.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/JoinRsp.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/JoinRsp.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: JoinRsp.java,v 1.12 2007/04/04 05:23:33 belaban Exp $ package org.jgroups.protocols.pbcast; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/MergeData.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/MergeData.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/MergeData.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/MergeData.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: MergeData.java,v 1.7 2008/01/22 10:44:34 belaban Exp $ package org.jgroups.protocols.pbcast; @@ -23,7 +22,7 @@ Address sender=null; boolean merge_rejected=false; View view=null; - Digest digest=null; + Digest digest=null; /** * Empty constructor needed for externalization @@ -58,12 +57,6 @@ } - public boolean equals(Object other) { - return sender != null && other != null && other instanceof MergeData && - ((MergeData)other).sender != null && ((MergeData)other).sender.equals(sender); - } - - public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(sender); out.writeBoolean(merge_rejected); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/Merger.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/Merger.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/Merger.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/Merger.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,733 @@ +package org.jgroups.protocols.pbcast; + +import org.jgroups.*; +import org.jgroups.annotations.GuardedBy; +import org.jgroups.logging.Log; +import org.jgroups.util.*; + +import java.util.*; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Handles merging. Called by CoordGmsImpl and ParticipantGmsImpl + * @author Bela Ban + */ +public class Merger { + private final GMS gms; + private final Log log; + + private final MergeTask merge_task=new MergeTask(); + + /** For MERGE_REQ/MERGE_RSP correlation, contains MergeData elements */ + private final ResponseCollector merge_rsps=new ResponseCollector(); + + /** For GET_DIGEST / DIGEST_RSP correlation */ + private final ResponseCollector digest_collector=new ResponseCollector(); + + /** To serialize access to merge_id */ + private final Lock merge_lock=new ReentrantLock(); + + @GuardedBy("merge_lock") + private MergeId merge_id=null; + + @GuardedBy("merge_canceller_lock") + private Future merge_canceller_future=null; + + private final Lock merge_canceller_lock=new ReentrantLock(); + + + public Merger(GMS gms, Log log) { + this.gms=gms; + this.log=log; + } + + + /** + * Invoked upon receiving a MERGE event from the MERGE layer. Starts the merge protocol. + * See description of protocol in DESIGN. + * @param views A List of different views detected by the merge protocol + */ + public void merge(Map views) { + if(isMergeInProgress()) { + if(log.isTraceEnabled()) log.trace(gms.local_addr + ": merge is already running (merge_id=" + merge_id + ")"); + return; + } + + // we need the merge *coordinators* not merge participants because not everyone can lead a merge ! + Collection
            coords=Util.determineMergeCoords(views); + Collection
            merge_participants=Util.determineMergeParticipants(views); + Membership tmp=new Membership(coords); // establish a deterministic order, so that coords can elect leader + tmp.sort(); + Address merge_leader=tmp.elementAt(0); + if(log.isDebugEnabled()) + log.debug("determining merge leader from " + merge_participants); + if(merge_leader.equals(gms.local_addr)) { + if(log.isDebugEnabled()) + log.debug("I (" + gms.local_addr + ") will be the leader. Starting the merge task for " + merge_participants); + merge_task.start(views); + } + else { + if(log.isDebugEnabled()) log.debug("I (" + gms.local_addr + ") am not the merge leader, " + + "waiting for merge leader (" + merge_leader + ") to initiate merge"); + } + } + + + + /** + * Get the view and digest and send back both (MergeData) in the form of a MERGE_RSP to the sender. + * If a merge is already in progress, send back a MergeData with the merge_rejected field set to true. + * @param sender The address of the merge leader + * @param merge_id The merge ID + * @param mbrs The set of members from which we expect responses + */ + public void handleMergeRequest(Address sender, MergeId merge_id, Collection mbrs) { + boolean success=matchMergeId(merge_id) || setMergeId(null, merge_id); + if(!success) { + if(log.isWarnEnabled()) log.warn(gms.local_addr + ": merge is already in progress"); + sendMergeRejectedResponse(sender, merge_id); + return; + } + + /* Clears the view handler queue and discards all JOIN/LEAVE/MERGE requests until after the MERGE */ + gms.getViewHandler().suspend(merge_id); + if(log.isDebugEnabled()) + log.debug(gms.local_addr + ": got merge request from " + sender + ", merge_id=" + merge_id + ", mbrs=" + mbrs); + + // merge the membership of the current view with mbrs + List
            members=new LinkedList
            (); + if(mbrs != null) { // didn't use a set because we didn't want to change the membership order at this time (although not incorrect) + for(Address mbr: mbrs) { + if(!members.contains(mbr)) + members.add(mbr); + } + } + + ViewId tmp_vid=gms.view_id != null? gms.view_id.copy() : null; + if(tmp_vid == null) { + log.warn("view ID is null; cannot return merge response"); + sendMergeRejectedResponse(sender, merge_id); + return; + } + View view=new View(tmp_vid, new Vector
            (members)); + + //[JGRP-524] - FLUSH and merge: flush doesn't wrap entire merge process + //[JGRP-770] - Concurrent startup of many channels doesn't stabilize + //[JGRP-700] - FLUSH: flushing should span merge + + /*if flush is in stack, let this coordinator flush its cluster island */ + boolean successfulFlush=gms.startFlush(view); + if(!successfulFlush) { + sendMergeRejectedResponse(sender, merge_id); + if(log.isWarnEnabled()) + log.warn(gms.local_addr + ": flush failed; sending merge rejected message to "+ sender+ ", merge_id="+ merge_id); + cancelMerge(merge_id); + return; + } + Digest digest=fetchDigestsFromAllMembersInSubPartition(members); + if(digest.size() == 0) { + log.error("failed fetching digests from subpartition members; dropping merge response"); + return; + } + sendMergeResponse(sender, view, digest, merge_id); + } + + + public void handleMergeResponse(MergeData data, MergeId merge_id) { + if(!matchMergeId(merge_id)) { + if(log.isErrorEnabled()) + log.error(gms.local_addr + ": this.merge_id (" + this.merge_id + ") is different from merge_id (" + merge_id + ')'); + return; + } + merge_rsps.add(data.getSender(), data); + } + + + /** + * If merge_id is not equal to this.merge_id then discard. + * Else cast the view/digest to all members of this group. + */ + public void handleMergeView(final MergeData data,final MergeId merge_id) { + if(!matchMergeId(merge_id)) { + if(log.isErrorEnabled()) log.error("merge_ids don't match (or are null); merge view discarded"); + return; + } + + // only send to our *current* members, if we have A and B being merged (we are B), then we would *not* + // receive a VIEW_ACK from A because A doesn't see us in the pre-merge view yet and discards the view + + //[JGRP-700] - FLUSH: flushing should span merge + + //we have to send new view only to current members and we should not wait + //for view acks from newly merged mebers + List
            newViewMembers=new Vector
            (data.view.getMembers()); + newViewMembers.removeAll(gms.members.getMembers()); + + + gms.castViewChangeWithDest(data.view, data.digest, null, newViewMembers); + // if we have flush in stack send ack back to merge coordinator + if(gms.flushProtocolInStack) { + Message ack=new Message(data.getSender(), null, null); + ack.setFlag(Message.OOB); + GMS.GmsHeader ack_hdr=new GMS.GmsHeader(GMS.GmsHeader.INSTALL_MERGE_VIEW_OK); + ack.putHeader(gms.getId(), ack_hdr); + gms.getDownProtocol().down(new Event(Event.MSG, ack)); + } + cancelMerge(merge_id); + } + + public void handleMergeCancelled(MergeId merge_id) { + try { + gms.stopFlush(); + } + catch(Throwable t) { + log.error("stop flush failed", t); + } + if(matchMergeId(merge_id)) { + if(log.isDebugEnabled()) + log.debug(gms.local_addr + ": merge " + merge_id + " is cancelled"); + cancelMerge(merge_id); + } + } + + + public void handleDigestResponse(Address sender, Digest digest) { + digest_collector.add(sender, digest); + } + + + /** + * Removes all members from a given view which don't have us in their view + * (https://jira.jboss.org/browse/JGRP-1061). Example: + *
            +     * A: AB
            +     * B: AB
            +     * C: ABC
            +     * 
            + * becomes + *
            +     * A: AB
            +     * B: AB
            +     * C: C // A and B don't have C in their views
            +     * 
            + * @param map A map of members and their associated views + */ + public static void sanitizeViews(Map map) { + if(map == null) + return; + for(Map.Entry entry: map.entrySet()) { + Address key=entry.getKey(); + Collection
            members=new ArrayList
            (entry.getValue().getMembers()); + boolean modified=false; + for(Iterator
            it=members.iterator(); it.hasNext();) { + Address val=it.next(); + if(val.equals(key)) // we can always talk to ourself ! + continue; + View view=map.get(val); + final Collection
            tmp_mbrs=view != null? view.getMembers() : null; + if(tmp_mbrs != null && !tmp_mbrs.contains(key)) { + it.remove(); + modified=true; + } + } + if(modified) { + View old_view=entry.getValue(); + entry.setValue(new View(old_view.getVid(), members)); + } + } + } + + + /** Send back a response containing view and digest to sender */ + private void sendMergeResponse(Address sender, View view, Digest digest, MergeId merge_id) { + Message msg=new Message(sender, null, null); + msg.setFlag(Message.OOB); + GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.MERGE_RSP); + hdr.merge_id=merge_id; + hdr.view=view; + hdr.my_digest=digest; + msg.putHeader(gms.getId(), hdr); + if(log.isDebugEnabled()) log.debug(gms.local_addr + ": sending merge response=" + hdr); + gms.getDownProtocol().down(new Event(Event.MSG, msg)); + } + + /** + * Sends the new view and digest to all subgroup coordinors in coords. Each coord will in turn + *
              + *
            1. broadcast the new view and digest to all the members of its subgroup (MergeView) + *
            2. on reception of the view, if it is a MergeView, each member will set the digest and install the new view + *
            + */ + private void sendMergeView(Collection
            coords, MergeData combined_merge_data, MergeId merge_id) { + if(coords == null || combined_merge_data == null) + return; + + View view=combined_merge_data.view; + Digest digest=combined_merge_data.digest; + if(view == null || digest == null) { + if(log.isErrorEnabled()) log.error("view or digest is null, cannot send consolidated merge view/digest"); + return; + } + + if(log.isDebugEnabled()) + log.debug(gms.local_addr + ": sending merge view " + view.getVid() + " to coordinators " + coords); + + gms.merge_ack_collector.reset(coords); + int size=gms.merge_ack_collector.size(); + long timeout=gms.view_ack_collection_timeout; + + long start=System.currentTimeMillis(); + for(Address coord:coords) { + Message msg=new Message(coord, null, null); + GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.INSTALL_MERGE_VIEW); + hdr.view=view; + hdr.my_digest=digest; + hdr.merge_id=merge_id; + msg.putHeader(gms.getId(), hdr); + gms.getDownProtocol().down(new Event(Event.MSG, msg)); + } + + //[JGRP-700] - FLUSH: flushing should span merge + // if flush is in stack wait for acks from separated island coordinators + if(gms.flushProtocolInStack) { + try { + gms.merge_ack_collector.waitForAllAcks(timeout); + long stop=System.currentTimeMillis(); + if(log.isTraceEnabled()) + log.trace("received all ACKs (" + size + ") for merge view " + view + " in " + (stop - start) + "ms"); + } + catch(TimeoutException e) { + log.warn(gms.local_addr + ": failed to collect all ACKs for merge (" + size + ") for view " + view + + " after " + timeout + "ms, missing ACKs from " + gms.merge_ack_collector.printMissing()); + } + } + } + + protected void sendMergeRejectedResponse(Address sender, MergeId merge_id) { + Message msg=new Message(sender, null, null); + msg.setFlag(Message.OOB); + GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.MERGE_RSP); + hdr.merge_rejected=true; + hdr.merge_id=merge_id; + msg.putHeader(gms.getId(), hdr); + if(log.isDebugEnabled()) log.debug("merge response=" + hdr); + gms.getDownProtocol().down(new Event(Event.MSG, msg)); + } + + private void sendMergeCancelledMessage(Collection
            coords, MergeId merge_id) { + if(coords == null || merge_id == null) + return; + + for(Address coord:coords) { + Message msg=new Message(coord, null, null); + // msg.setFlag(Message.OOB); + GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.CANCEL_MERGE); + hdr.merge_id=merge_id; + msg.putHeader(gms.getId(), hdr); + if(log.isDebugEnabled()) log.debug(gms.local_addr + ": sending cancel merge to " + coord); + gms.getDownProtocol().down(new Event(Event.MSG, msg)); + } + } + + + + + /** + * Multicasts a GET_DIGEST_REQ to all current members and waits for all responses (GET_DIGEST_RSP) or N ms. + * @return + */ + private Digest fetchDigestsFromAllMembersInSubPartition(List
            current_mbrs) { + if(current_mbrs == null) + return null; + + GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.GET_DIGEST_REQ); + Message get_digest_req=new Message(); + get_digest_req.setFlag(Message.OOB); + get_digest_req.putHeader(gms.getId(), hdr); + + long max_wait_time=gms.merge_timeout > 0? gms.merge_timeout / 2 : 2000L; + digest_collector.reset(current_mbrs); + + // add my own digest first + Digest digest=(Digest)gms.getDownProtocol().down(Event.GET_DIGEST_EVT); + digest_collector.add(gms.local_addr, digest); + + gms.getDownProtocol().down(new Event(Event.MSG, get_digest_req)); + digest_collector.waitForAllResponses(max_wait_time); + if(log.isDebugEnabled()) { + if(digest_collector.hasAllResponses()) + log.debug(gms.local_addr + ": fetched all digests for " + current_mbrs); + else + log.debug(gms.local_addr + ": fetched incomplete digests (after timeout of " + max_wait_time + ") ms for " + current_mbrs); + } + Map responses=new HashMap(digest_collector.getResults()); + MutableDigest retval=new MutableDigest(responses.size()); + for(Digest dig: responses.values()) { + if(dig != null) + retval.add(dig); + } + return retval; + } + + /** + * Fetches the digests from all members and installs them again. Used only for diagnosis and support; don't + * use this otherwise ! + */ + void fixDigests() { + Digest digest=fetchDigestsFromAllMembersInSubPartition(gms.view.getMembers()); + Message msg=new Message(); + GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.INSTALL_DIGEST); + hdr.my_digest=digest; + msg.putHeader(gms.getId(), hdr); + gms.getDownProtocol().down(new Event(Event.MSG, msg)); + } + + + void stop() { + merge_task.stop(); + } + + + void cancelMerge(MergeId id) { + if(setMergeId(id, null)) { + merge_task.stop(); + merge_rsps.reset(); + gms.getViewHandler().resume(id); + } + } + + + private boolean setMergeId(MergeId expected, MergeId new_value) { + merge_lock.lock(); + try { + boolean match=Util.match(this.merge_id, expected); + if(match) { + this.merge_id=new_value; + stopMergeCanceller(); + if(this.merge_id != null) + startMergeCanceller(); + } + return match; + } + finally { + merge_lock.unlock(); + } + } + + /** Only used for testing, might get removed any time. Do not use ! */ + public MergeId getMergeId() { + merge_lock.lock(); + try { + return merge_id; + } + finally { + merge_lock.unlock(); + } + } + + private boolean isMergeInProgress() { + merge_lock.lock(); + try { + return merge_id != null; + } + finally { + merge_lock.unlock(); + } + } + + private boolean matchMergeId(MergeId id) { + merge_lock.lock(); + try { + return Util.match(this.merge_id, id); + } + finally { + merge_lock.unlock(); + } + } + + private void startMergeCanceller() { + merge_canceller_lock.lock(); + try { + if(merge_canceller_future == null || merge_canceller_future.isDone()) { + MergeCanceller task=new MergeCanceller(this.merge_id); + merge_canceller_future=gms.timer.schedule(task, (long)(gms.merge_timeout * 1.5), TimeUnit.MILLISECONDS); + } + } + finally { + merge_canceller_lock.unlock(); + } + } + + private void stopMergeCanceller() { + merge_canceller_lock.lock(); + try { + if(merge_canceller_future != null) { + merge_canceller_future.cancel(true); + merge_canceller_future=null; + } + } + finally { + merge_canceller_lock.unlock(); + } + } + + + + + + + /** + * Starts the merge protocol (only run by the merge leader). Essentially sends a MERGE_REQ to all + * coordinators of all subgroups found. Each coord receives its digest and view and returns it. + * The leader then computes the digest and view for the new group from the return values. Finally, it + * sends this merged view/digest to all subgroup coordinators; each coordinator will install it in their + * subgroup. + */ + class MergeTask implements Runnable { + private Thread thread=null; + + /** List of all subpartition coordinators and their members */ + private final ConcurrentMap> coords=Util.createConcurrentMap(8, 0.75f, 8); + + /** + * @param views Guaranteed to be non-null and to have >= 2 members, or else this thread would not be started + */ + public synchronized void start(Map views) { + if(thread == null || thread.isAlive()) { + this.coords.clear(); + + // now remove all members which don't have us in their view, so RPCs won't block (e.g. FLUSH) + // https://jira.jboss.org/browse/JGRP-1061 + sanitizeViews(views); + + // Add all different coordinators of the views into the hashmap and sets their members: + Collection
            coordinators=Util.determineMergeCoords(views); + for(Address coord: coordinators) { + View view=views.get(coord); + if(view != null) + this.coords.put(coord, new ArrayList
            (view.getMembers())); + } + + // For the merge participants which are not coordinator, we simply add them, and the associated + // membership list consists only of themselves + Collection
            merge_participants=Util.determineMergeParticipants(views); + merge_participants.removeAll(coordinators); + for(Address merge_participant: merge_participants) { + Collection
            tmp=new ArrayList
            (); + tmp.add(merge_participant); + coords.putIfAbsent(merge_participant, tmp); + } + + thread=gms.getThreadFactory().newThread(this, "MergeTask"); + thread.setDaemon(true); + thread.start(); + } + } + + + public synchronized void stop() { + Thread tmp=thread; + if(thread != null && thread.isAlive()) + tmp.interrupt(); + thread=null; + } + + + /** Runs the merge protocol as a leader */ + public void run() { + + // 1. Generate merge_id + MergeId new_merge_id=MergeId.create(gms.local_addr); + Collection
            coordsCopy=null; + + try { + boolean success=setMergeId(null, new_merge_id); + if(!success) { + log.warn("failed to set my own merge_id (" + merge_id + ") to " + new_merge_id); + return; + } + + coordsCopy=new ArrayList
            (coords.keySet()); + + /* 2. Fetch the current Views/Digests from all subgroup coordinators */ + success=getMergeDataFromSubgroupCoordinators(coords, new_merge_id, gms.merge_timeout); + if(!success) + throw new Exception("merge leader did not get data from all partition coordinators " + coords.keySet()); + + /* 3. Remove rejected MergeData elements from merge_rsp and coords (so we'll send the new view + * only to members who accepted the merge request) */ + removeRejectedMergeRequests(coords.keySet()); + if(merge_rsps.size() == 0) + throw new Exception("did not get any merge responses from partition coordinators"); + + if(!coords.keySet().contains(gms.local_addr)) // another member might have invoked a merge req on us before we got there... + throw new Exception("merge leader rejected merge request"); + + /* 4. Combine all views and digests into 1 View/1 Digest */ + Vector merge_data=new Vector(merge_rsps.getResults().values()); + MergeData combined_merge_data=consolidateMergeData(merge_data); + if(combined_merge_data == null) + throw new Exception("could not consolidate merge"); + /* 4. Send the new View/Digest to all coordinators (including myself). On reception, they will + install the digest and view in all of their subgroup members */ + sendMergeView(coords.keySet(), combined_merge_data, new_merge_id); + } + catch(Throwable ex) { + if(log.isWarnEnabled()) + log.warn(gms.local_addr + ": " + ex.getLocalizedMessage() + ", merge is cancelled"); + sendMergeCancelledMessage(coordsCopy, new_merge_id); + } + finally { + gms.getViewHandler().resume(new_merge_id); + stopMergeCanceller(); // this is probably not necessary + + /*5. if flush is in stack stop the flush for entire cluster [JGRP-700] - FLUSH: flushing should span merge */ + gms.stopFlush(); + if(log.isDebugEnabled()) + log.debug(gms.local_addr + ": merge leader completed merge task"); + thread=null; + } + } + + + /** + * Sends a MERGE_REQ to all coords and populates a list of MergeData (in merge_rsps). Returns after coords.size() + * response have been received, or timeout msecs have elapsed (whichever is first).

            + * If a subgroup coordinator rejects the MERGE_REQ (e.g. because of participation in a different merge), + * that member will be removed from coords ! + * @param coords A map of coordinatgor addresses and associated membership lists + * @param new_merge_id The new merge id + * @param timeout Max number of msecs to wait for the merge responses from the subgroup coords + */ + private boolean getMergeDataFromSubgroupCoordinators(Map> coords, MergeId new_merge_id, long timeout) { + boolean gotAllResponses; + long start=System.currentTimeMillis(); + merge_rsps.reset(coords.keySet()); + if(log.isDebugEnabled()) + log.debug(gms.local_addr + ": sending MERGE_REQ to " + coords.keySet()); + + for(Map.Entry> entry: coords.entrySet()) { + Address coord=entry.getKey(); + Collection

            mbrs=entry.getValue(); + Message msg=new Message(coord, null, null); + msg.setFlag(Message.OOB); + GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.MERGE_REQ, mbrs); + hdr.mbr=gms.local_addr; + hdr.merge_id=new_merge_id; + msg.putHeader(gms.getId(), hdr); + gms.getDownProtocol().down(new Event(Event.MSG, msg)); + } + + // wait until num_rsps_expected >= num_rsps or timeout elapsed + merge_rsps.waitForAllResponses(timeout); + gotAllResponses=merge_rsps.hasAllResponses(); + long stop=System.currentTimeMillis(); + if(log.isDebugEnabled()) + log.debug(gms.local_addr + ": collected " + merge_rsps.size() + " merge response(s) in " + (stop-start) + " ms"); + return gotAllResponses; + } + + + /** Removed rejected merge requests from merge_rsps and coords. This method has a lock on merge_rsps */ + private void removeRejectedMergeRequests(Collection
            coords) { + for(Map.Entry entry: merge_rsps.getResults().entrySet()) { + Address member=entry.getKey(); + MergeData data=entry.getValue(); + if(data.merge_rejected) { + if(data.getSender() != null) + coords.remove(data.getSender()); + merge_rsps.remove(member); + } + } + } + + /** + * Merge all MergeData. All MergeData elements should be disjunct (both views and digests). However, + * this method is prepared to resolve duplicate entries (for the same member). Resolution strategy for + * views is to merge only 1 of the duplicate members. Resolution strategy for digests is to take the higher + * seqnos for duplicate digests.

            + * After merging all members into a Membership and subsequent sorting, the first member of the sorted membership + * will be the new coordinator. This method has a lock on merge_rsps. + * @param merge_rsps A list of MergeData items. Elements with merge_rejected=true were removed before. Is guaranteed + * not to be null and to contain at least 1 member. + */ + private MergeData consolidateMergeData(Vector merge_rsps) { + long logical_time=0; // for new_vid + Membership new_mbrs=new Membership(); + Vector subgroups=new Vector(11); // contains a list of Views, each View is a subgroup + + for(MergeData tmp_data: merge_rsps) { + View tmp_view=tmp_data.getView(); + if(tmp_view != null) { + ViewId tmp_vid=tmp_view.getVid(); + if(tmp_vid != null) { + // compute the new view id (max of all vids +1) + logical_time=Math.max(logical_time, tmp_vid.getId()); + } + // merge all membership lists into one (prevent duplicates) + new_mbrs.add(tmp_view.getMembers()); + subgroups.addElement((View)tmp_view.clone()); + } + } + + // the new coordinator is the first member of the consolidated & sorted membership list + new_mbrs.sort(); + Address new_coord = new_mbrs.size() > 0 ? new_mbrs.elementAt(0) : null; + if(new_coord == null) { + if(log.isErrorEnabled()) log.error("new_coord == null"); + return null; + } + // should be the highest view ID seen up to now plus 1 + ViewId new_vid=new ViewId(new_coord, logical_time + 1); + + // determine the new view + MergeView new_view=new MergeView(new_vid, new_mbrs.getMembers(), subgroups); + + // determine the new digest + Digest new_digest=consolidateDigests(merge_rsps, new_mbrs.size()); + if(new_digest == null) { + if(log.isErrorEnabled()) log.error("Merge leader " + gms.local_addr + ": could not consolidate digest for merge"); + return null; + } + if(log.isDebugEnabled()) log.debug("Merge leader " + gms.local_addr + ": consolidated view=" + new_view + + "\nconsolidated digest=" + new_digest); + return new MergeData(gms.local_addr, new_view, new_digest); + } + + /** + * Merge all digests into one. For each sender, the new value is min(low_seqno), max(high_seqno), + * max(high_seqno_seen). This method has a lock on merge_rsps + */ + private Digest consolidateDigests(Vector merge_rsps, int num_mbrs) { + MutableDigest retval=new MutableDigest(num_mbrs); + + for(MergeData data:merge_rsps) { + Digest tmp_digest=data.getDigest(); + if(tmp_digest == null) { + if(log.isErrorEnabled()) log.error("tmp_digest == null; skipping"); + continue; + } + retval.merge(tmp_digest); + } + return retval.copy(); + } + } + + + private class MergeCanceller implements Runnable { + private final MergeId my_merge_id; + + MergeCanceller(MergeId my_merge_id) { + this.my_merge_id=my_merge_id; + } + + public void run() { + cancelMerge(my_merge_id); + } + } + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/NakAckHeader.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/NakAckHeader.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/NakAckHeader.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/NakAckHeader.java 2011-10-18 11:22:35.000000000 +0000 @@ -6,7 +6,6 @@ import org.jgroups.Global; import org.jgroups.Header; import org.jgroups.util.Range; -import org.jgroups.util.Streamable; import org.jgroups.util.Util; import java.io.*; @@ -14,103 +13,121 @@ /** * @author Bela Ban - * @version $Id: NakAckHeader.java,v 1.21 2007/08/20 10:46:53 belaban Exp $ */ -public class NakAckHeader extends Header implements Streamable { +public class NakAckHeader extends Header { public static final byte MSG=1; // regular msg public static final byte XMIT_REQ=2; // retransmit request public static final byte XMIT_RSP=3; // retransmit response (contains one or more messages) - byte type=0; long seqno=-1; // seqno of regular message (MSG) - Range range=null; // range of msgs to be retransmitted (XMIT_REQ) or retransmitted (XMIT_RSP) + Range range=null; // range of msgs to be retransmitted (XMIT_REQ) Address sender; // the original sender of the message (for XMIT_REQ) - private static final long serialVersionUID=-4305600151593420827L; public NakAckHeader() { } + public static NakAckHeader createMessageHeader(long seqno) { + return new NakAckHeader(MSG, seqno); + } + + public static NakAckHeader createXmitRequestHeader(long low, long high, Address orginal_sender) { + return new NakAckHeader(XMIT_REQ, low, high, orginal_sender); + } + + public static NakAckHeader createXmitResponseHeader() { + return new NakAckHeader(XMIT_RSP, -1); + } + + /** - * Constructor for regular messages + * Constructor for regular messages or XMIT responses */ - public NakAckHeader(byte type, long seqno) { + private NakAckHeader(byte type, long seqno) { this.type=type; this.seqno=seqno; } + /** - * Constructor for retransmit requests/responses (low and high define the range of msgs) + * Constructor for retransmit requests (XMIT_REQs) (low and high define the range of msgs) */ - public NakAckHeader(byte type, long low, long high) { + private NakAckHeader(byte type, long low, long high, Address sender) { this.type=type; range=new Range(low, high); - } - - - public NakAckHeader(byte type, long low, long high, Address sender) { - this(type, low, high); this.sender=sender; } + public byte getType() { + return type; + } - - - public void writeExternal(ObjectOutput out) throws IOException { - out.writeByte(type); - out.writeLong(seqno); - if(range != null) { - out.writeBoolean(true); // wasn't here before, bad bug ! - range.writeExternal(out); - } - else - out.writeBoolean(false); - out.writeObject(sender); + public long getSeqno() { + return seqno; } + public Range getRange() { + return range; + } - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - boolean read_range; - type=in.readByte(); - seqno=in.readLong(); - read_range=in.readBoolean(); - if(read_range) { - range=new Range(); - range.readExternal(in); - } - sender=(Address)in.readObject(); + public Address getSender() { + return sender; } + public void writeTo(DataOutputStream out) throws IOException { out.writeByte(type); - out.writeLong(seqno); - Util.writeStreamable(range, out); - Util.writeAddress(sender, out); + switch(type) { + case MSG: + case XMIT_RSP: + out.writeLong(seqno); + break; + case XMIT_REQ: + Util.writeStreamable(range, out); + Util.writeAddress(sender, out); + break; + } } public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { type=in.readByte(); - seqno=in.readLong(); - range=(Range)Util.readStreamable(Range.class, in); - sender=Util.readAddress(in); + switch(type) { + case MSG: + case XMIT_RSP: + seqno=in.readLong(); + break; + case XMIT_REQ: + range=(Range)Util.readStreamable(Range.class, in); + sender=Util.readAddress(in); + break; + } } + public int size() { - // type (1 byte) + seqno (8 bytes) - int retval=Global.BYTE_SIZE; - retval+=Global.LONG_SIZE; - retval+=Global.BYTE_SIZE; // presence for range - if(range != null) - retval+=2 * Global.LONG_SIZE; // 2 times 8 bytes for seqno - retval+=Util.size(sender); + int retval=Global.BYTE_SIZE; // type + switch(type) { + case MSG: + case XMIT_RSP: + return retval + Global.LONG_SIZE; // seqno + + case XMIT_REQ: + retval+=Global.BYTE_SIZE; // presence for range + if(range != null) + retval+=2 * Global.LONG_SIZE; // 2 times 8 bytes for seqno + retval+=Util.size(sender); + return retval; + } return retval; } public NakAckHeader copy() { - NakAckHeader ret=new NakAckHeader(type, seqno); + NakAckHeader ret=new NakAckHeader(); + ret.type=type; + ret.seqno=seqno; ret.range=range; ret.sender=sender; return ret; @@ -135,15 +152,14 @@ StringBuilder ret=new StringBuilder(); ret.append("[").append(type2Str(type)); switch(type) { - case MSG: // seqno and sender + case MSG: + case XMIT_RSP: // seqno and sender ret.append(", seqno=").append(seqno); break; case XMIT_REQ: // range and sender if(range != null) ret.append(", range=" + range); break; - case XMIT_RSP: // seqno and sender - break; } if(sender != null) ret.append(", sender=").append(sender); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/NAKACK.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/NAKACK.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/NAKACK.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/NAKACK.java 2011-10-18 11:22:35.000000000 +0000 @@ -3,18 +3,18 @@ import org.jgroups.*; import org.jgroups.annotations.*; import org.jgroups.conf.PropertyConverters; +import org.jgroups.protocols.TP; import org.jgroups.stack.*; -import org.jgroups.util.*; +import org.jgroups.util.BoundedList; +import org.jgroups.util.Digest; +import org.jgroups.util.TimeScheduler; +import org.jgroups.util.Util; -import java.io.FileWriter; -import java.io.IOException; -import java.io.Writer; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; @@ -31,48 +31,39 @@ * instead of the requester by setting use_mcast_xmit to true. * * @author Bela Ban - * @version $Id: NAKACK.java,v 1.209 2008/11/12 13:32:18 belaban Exp $ */ @MBean(description="Reliable transmission multipoint FIFO protocol") -@DeprecatedProperty(names={"max_xmit_size"}) -public class NAKACK extends Protocol implements Retransmitter.RetransmitCommand, NakReceiverWindow.Listener { +@DeprecatedProperty(names={"max_xmit_size", "eager_lock_release", "stats_list_size", "max_xmit_buf_size", + "enable_xmit_time_stats"}) +public class NAKACK extends Protocol implements Retransmitter.RetransmitCommand, NakReceiverWindow.Listener, TP.ProbeHandler { - - private static final long INITIAL_SEQNO=0; - - private static final String name="NAKACK"; - - /** - * the weight with which we take the previous smoothed average into account, - * WEIGHT should be >0 and <= 1 - */ + /** the weight with which we take the previous smoothed average into account, WEIGHT should be >0 and <= 1 */ private static final double WEIGHT=0.9; private static final double INITIAL_SMOOTHED_AVG=30.0; private static final int NUM_REBROADCAST_MSGS=3; - + /* ----------------------------------------------------- Properties --------------------- ------------------------------------ */ - + @Property(name="retransmit_timeout", converter=PropertyConverters.LongArray.class, description="Timeout before requesting retransmissions. Default is 600, 1200, 2400, 4800") private long[] retransmit_timeouts= { 600, 1200, 2400, 4800 }; // time(s) to wait before requesting retransmission - @Property(description="If true, retransmissions stats will be captured. Default is false") - boolean enable_xmit_time_stats=false; - - @ManagedAttribute(description="Garbage collection lag", writable=true) - @Property(description="Garbage collection lag. Default is 20 msec") - private int gc_lag=20; // number of msgs garbage collection lags behind - + @Deprecated + @Property(description="Garbage collection lag") + private int gc_lag=20; // number of msgs garbage collection lags behind + + @Property(description="Max number of messages to be removed from a NakReceiverWindow. This property might " + + "get removed anytime, so don't use it !") + private int max_msg_batch_size=20000; + /** - * Retransmit messages using multicast rather than unicast. This has the - * advantage that, if many receivers lost a message, the sender only - * retransmits once. + * Retransmit messages using multicast rather than unicast. This has the advantage that, if many receivers + * lost a message, the sender only retransmits once */ - @Property(description="Retransmit messages using multicast rather than unicast. Default is true") - @ManagedAttribute(description="Retransmit messages using multicast rather than unicast", writable=true) + @Property(description="Retransmit messages using multicast rather than unicast") private boolean use_mcast_xmit=true; /** @@ -87,7 +78,6 @@ * true, discard_delivered_msgs will be set to false */ @Property(description="Ask a random member for retransmission of a missing message. Default is false") - @ManagedAttribute(description="Ask a random member for retransmission of a missing message", writable=true) private boolean xmit_from_random_member=false; /** @@ -104,6 +94,12 @@ @Property(description="Use statistics gathered from actual retransmission times to compute new retransmission times. Default is false") private boolean use_stats_for_retransmission=false; + @Property(description="Whether to use the old retransmitter which retransmits individual messages or the new one " + + "which uses ranges of retransmitted messages. Default is true. Note that this property will be removed in 3.0; " + + "it is only used to switch back to the old (and proven) retransmitter mechanism if issues occur") + private boolean use_range_based_retransmitter=true; + + /** * Messages that have been received in order are sent up the stack (= * delivered to the application). Delivered messages are removed from @@ -114,33 +110,9 @@ * off, so we don't keep the message around, and don't need to wait for * garbage collection to remove them. */ - @Property(description="Should messages delivered to application be discarded. Default is false") - @ManagedAttribute(description="Discard delivered messages", writable=true) + @Property(description="Should messages delivered to application be discarded") private boolean discard_delivered_msgs=false; - /** - * By default, we release the lock on the sender in up() after the up() - * method call passed up the stack returns. However, with eager_lock_release - * enabled (default), we release the lock as soon as the application calls - * Channel.down() within the receive() callback. This leads to - * issues as the one described in - * http://jira.jboss.com/jira/browse/JGRP-656. Note that ordering is - * still correct , but messages from self might get delivered - * concurrently. This can be turned off by setting eager_lock_release to - * false. - */ - @Property(description="See http://jira.jboss.com/jira/browse/JGRP-656. Default is true") - private boolean eager_lock_release=true; - - /** - * If value is > 0, the retransmit buffer is bounded: only the - * max_xmit_buf_size latest messages are kept, older ones are discarded when - * the buffer size is exceeded. A value <= 0 means unbounded buffers - */ - @Property(description="If value is > 0, the retransmit buffer is bounded. If value <= 0 unbounded buffers are used. Default is 0") - @ManagedAttribute(description="If value is > 0, the retransmit buffer is bounded. If value <= 0 unbounded buffers are used", writable=true) - private int max_xmit_buf_size=0; - @Property(description="Size of retransmission history. Default is 50 entries") private int xmit_history_max_size=50; @@ -153,16 +125,33 @@ */ @Property(description="Should stability history be printed if we fail in retransmission. Default is false") protected boolean print_stability_history_on_failed_xmit=false; - - @Property(description="Size of send and receive history. Default is 20 entries") - private int stats_list_size=20; - + + /** If true, logs messages discarded because received from other members */ + @Property(description="discards warnings about promiscuous traffic") + private boolean log_discard_msgs=true; + + @Property(description="If true, trashes warnings about retransmission messages not found in the xmit_table (used for testing)") + private boolean log_not_found_msgs=true; + + @Property(description="Number of rows of the matrix in the retransmission table (only for experts)",writable=false) + int xmit_table_num_rows=5; + + @Property(description="Number of elements of a row of the matrix in the retransmission table (only for experts). " + + "The capacity of the matrix is xmit_table_num_rows * xmit_table_msgs_per_row",writable=false) + int xmit_table_msgs_per_row=10000; + + @Property(description="Resize factor of the matrix in the retransmission table (only for experts)",writable=false) + double xmit_table_resize_factor=1.2; + + @Property(description="Number of milliseconds after which the matrix in the retransmission table " + + "is compacted (only for experts)",writable=false) + long xmit_table_max_compaction_time=10 * 60 * 1000; /* -------------------------------------------------- JMX ---------------------------------------------------------- */ - - + + @ManagedAttribute(description="Number of retransmit requests received") private long xmit_reqs_received; @ManagedAttribute(description="Number of retransmit requests sent") @@ -174,59 +163,31 @@ @ManagedAttribute(description="Number of missing messages received") private long missing_msgs_received; - /** - * Maintains retransmission related data across a time. Only used if enable_xmit_time_stats is set to true. - * At program termination, accumulated data is dumped to a file named by the address of the member. - * Careful, don't enable this in production as the data in this hashmap are - * never reaped ! Really only meant for diagnostics ! - */ - private ConcurrentMap xmit_time_stats=null; - - private long xmit_time_stats_start; - - /** - * BoundedList. Keeps track of the last stats_list_size - * XMIT requests - */ - private BoundedList receive_history; - - /** - * BoundedList. Keeps track of the last stats_list_size - * missing messages received - */ - private BoundedList send_history; /** Captures stats on XMIT_REQS, XMIT_RSPS per sender */ - private ConcurrentMap sent=new ConcurrentHashMap(); + private ConcurrentMap sent=Util.createConcurrentMap(); /** Captures stats on XMIT_REQS, XMIT_RSPS per receiver */ - private ConcurrentMap received=new ConcurrentHashMap(); + private ConcurrentMap received=Util.createConcurrentMap(); - /** - * Per-sender map of seqnos and timestamps, to keep track of avg times for retransmission of messages - */ - private final ConcurrentMap> xmit_stats=new ConcurrentHashMap>(); + /** Per-sender map of seqnos and timestamps, to keep track of avg times for retransmission of messages */ + private final ConcurrentMap> xmit_stats=Util.createConcurrentMap(); - /** - * Maintains a list of the last N retransmission times (duration it took to - * retransmit a message) for all members - */ - private final ConcurrentMap> xmit_times_history=new ConcurrentHashMap>(); + /** Maintains a list of the last N retransmission times (duration to retransmit a message) for all members */ + private final ConcurrentMap> xmit_times_history=Util.createConcurrentMap(); /** * Maintains a smoothed average of the retransmission times per sender, * these are the actual values that are used for new retransmission requests */ private final Map smoothed_avg_xmit_times=new HashMap(); - - + + /** Keeps the last 50 retransmit requests */ + private final BoundedList xmit_history=new BoundedList(50); + /* ------------------------------------------------- Fields ------------------------------------------------------------------------- */ - - - - private Map locks; private boolean is_server=false; private Address local_addr=null; private final List

            members=new CopyOnWriteArrayList
            (); @@ -236,16 +197,11 @@ private final Lock seqno_lock=new ReentrantLock(); /** Map to store sent and received messages (keyed by sender) */ - private final ConcurrentMap xmit_table=new ConcurrentHashMap(11); + private final ConcurrentMap xmit_table=Util.createConcurrentMap(); private volatile boolean leaving=false; - private volatile boolean started=false; - private TimeScheduler timer=null; - /** - * Keeps track of OOB messages sent by myself, needed by - * {@link #handleMessage(org.jgroups.Message, NakAckHeader)} - */ - private final Set oob_loopback_msgs=Collections.synchronizedSet(new HashSet()); + private volatile boolean running=false; + private TimeScheduler timer=null; private final Lock rebroadcast_lock=new ReentrantLock(); @@ -259,14 +215,10 @@ private Digest rebroadcast_digest=null; /** BoundedList, keeps the last 10 stability messages */ - private final BoundedList stability_msgs=new BoundedList(10); + protected final BoundedList stability_msgs=new BoundedList(10); - /** If true, logs messages discarded because received from other members */ - @ManagedAttribute(description="If true, logs messages discarded because received from other members", writable=true) - private boolean log_discard_msgs=true; - - /** Regular messages which have been added, but not removed */ - private final AtomicInteger undelivered_msgs=new AtomicInteger(0); + /** Keeps a bounded list of the last N digest sets */ + protected final BoundedList digest_history=new BoundedList(10); @@ -274,22 +226,18 @@ } - public String getName() { - return name; + @Deprecated + public static int getUndeliveredMessages() { + return 0; } - @ManagedAttribute - public int getUndeliveredMessages() { - return undelivered_msgs.get(); - } - public long getXmitRequestsReceived() {return xmit_reqs_received;} public long getXmitRequestsSent() {return xmit_reqs_sent;} public long getXmitResponsesReceived() {return xmit_rsps_received;} public long getXmitResponsesSent() {return xmit_rsps_sent;} public long getMissingMessagesReceived() {return missing_msgs_received;} - @ManagedAttribute + @ManagedAttribute(description="Total number of missing messages") public int getPendingRetransmissionRequests() { int num=0; for(NakReceiverWindow win: xmit_table.values()) { @@ -307,28 +255,50 @@ return num; } + + @ManagedAttribute + public long getCurrentSeqno() {return seqno;} + + @ManagedOperation + public String printRetransmitStats() { + StringBuilder sb=new StringBuilder(); + for(Map.Entry entry: xmit_table.entrySet()) + sb.append(entry.getKey()).append(": ").append(entry.getValue().printRetransmitStats()).append("\n"); + return sb.toString(); + } + public int getReceivedTableSize() { return getPendingRetransmissionRequests(); } + /** + * Please don't use this method; it is only provided for unit testing ! + * @param mbr + * @return + */ + public NakReceiverWindow getWindow(Address mbr) { + return xmit_table.get(mbr); + } + + + /** + * Only used for unit tests, don't use ! + * @param timer + */ + public void setTimer(TimeScheduler timer) { + this.timer=timer; + } + public void resetStats() { xmit_reqs_received=xmit_reqs_sent=xmit_rsps_received=xmit_rsps_sent=missing_msgs_received=0; sent.clear(); received.clear(); - if(receive_history !=null) - receive_history.clear(); - if(send_history != null) - send_history.clear(); + stability_msgs.clear(); + digest_history.clear(); + xmit_history.clear(); } public void init() throws Exception { - if(enable_xmit_time_stats) { - if(log.isWarnEnabled()) - log.warn("enable_xmit_time_stats is experimental, and may be removed in any release"); - xmit_time_stats=new ConcurrentHashMap(); - xmit_time_stats_start=System.currentTimeMillis(); - } - if(xmit_from_random_member) { if(discard_delivered_msgs) { discard_delivered_msgs=false; @@ -336,17 +306,30 @@ } } - if(stats) { - send_history=new BoundedList(stats_list_size); - receive_history=new BoundedList(stats_list_size); + TP transport=getTransport(); + if(transport != null) { + transport.registerProbeHandler(this); + if(!transport.supportsMulticasting()) { + if(use_mcast_xmit) { + log.warn("use_mcast_xmit should not be used because the transport (" + transport.getName() + + ") does not support IP multicasting; setting use_mcast_xmit to false"); + use_mcast_xmit=false; + } + if(use_mcast_xmit_req) { + log.warn("use_mcast_xmit_req should not be used because the transport (" + transport.getName() + + ") does not support IP multicasting; setting use_mcast_xmit_req to false"); + use_mcast_xmit_req=false; + } + } } } - + @Deprecated public int getGcLag() { return gc_lag; } + @Deprecated public void setGcLag(int gc_lag) { this.gc_lag=gc_lag; } @@ -374,19 +357,16 @@ public void setDiscardDeliveredMsgs(boolean discard_delivered_msgs) { boolean old=this.discard_delivered_msgs; this.discard_delivered_msgs=discard_delivered_msgs; - if(old != this.discard_delivered_msgs) { - for(NakReceiverWindow win: xmit_table.values()) { - win.setDiscardDeliveredMessages(this.discard_delivered_msgs); - } - } } - public int getMaxXmitBufSize() { - return max_xmit_buf_size; + @Deprecated + public static int getMaxXmitBufSize() { + return 0; } - public void setMaxXmitBufSize(int max_xmit_buf_size) { - this.max_xmit_buf_size=max_xmit_buf_size; + @Deprecated + public static void setMaxXmitBufSize(int max_xmit_buf_size) { + ; } /** @@ -394,7 +374,7 @@ * @return * @deprecated removed in 2.6 */ - public long getMaxXmitSize() { + public static long getMaxXmitSize() { return -1; } @@ -419,42 +399,30 @@ } public Map dumpStats() { - Map retval=super.dumpStats(); + Map retval=super.dumpStats(); retval.put("msgs", printMessages()); return retval; } public String printStats() { - Map.Entry entry; - Object key, val; StringBuilder sb=new StringBuilder(); sb.append("sent:\n"); - for(Iterator it=sent.entrySet().iterator(); it.hasNext();) { - entry=(Map.Entry)it.next(); - key=entry.getKey(); + for(Iterator> it=sent.entrySet().iterator(); it.hasNext();) { + Map.Entry entry=it.next(); + Object key=entry.getKey(); if(key == null || key == Global.NULL) key=""; - val=entry.getValue(); + StatsEntry val=entry.getValue(); sb.append(key).append(": ").append(val).append("\n"); } sb.append("\nreceived:\n"); - for(Iterator it=received.entrySet().iterator(); it.hasNext();) { - entry=(Map.Entry)it.next(); - key=entry.getKey(); + for(Iterator> it=received.entrySet().iterator(); it.hasNext();) { + Map.Entry entry=it.next(); + Object key=entry.getKey(); if(key == null || key == Global.NULL) key=""; - val=entry.getValue(); + StatsEntry val=entry.getValue(); sb.append(key).append(": ").append(val).append("\n"); } - sb.append("\nXMIT_REQS sent:\n"); - for(XmitRequest tmp: send_history) { - sb.append(tmp).append("\n"); - } - - sb.append("\nMissing messages received\n"); - for(MissingMessage missing: receive_history) { - sb.append(missing).append("\n"); - } - sb.append("\nStability messages received\n"); sb.append(printStabilityMessages()).append("\n"); @@ -477,6 +445,14 @@ return sb.toString(); } + @ManagedOperation(description="Keeps information about the last N times a digest was set or merged") + public String printDigestHistory() { + StringBuilder sb=new StringBuilder(local_addr + ":\n"); + for(String tmp: digest_history) + sb.append(tmp).append("\n"); + return sb.toString(); + } + @ManagedOperation(description="TODO") public String printLossRates() { StringBuilder sb=new StringBuilder(); @@ -488,6 +464,28 @@ return sb.toString(); } + @ManagedOperation(description="Returns the sizes of all NakReceiverWindow.RetransmitTables") + public String printRetransmitTableSizes() { + StringBuilder sb=new StringBuilder(); + for(Map.Entry entry: xmit_table.entrySet()) { + NakReceiverWindow win=entry.getValue(); + sb.append(entry.getKey() + ": ").append(win.getRetransmiTableSize()) + .append(" (capacity=" + win.getRetransmitTableCapacity()) + .append(", fill factor=" + win.getRetransmitTableFillFactor() + "%)\n"); + } + return sb.toString(); + } + + + @ManagedOperation(description="Compacts the retransmission tables") + public void compact() { + for(Map.Entry entry: xmit_table.entrySet()) { + NakReceiverWindow win=entry.getValue(); + win.compact(); + } + } + + @ManagedAttribute public double getAverageLossRate() { double retval=0.0; @@ -503,22 +501,23 @@ @ManagedAttribute public double getAverageSmoothedLossRate() { - double retval=0.0; - int count=0; - if(xmit_table.isEmpty()) - return 0.0; - for(NakReceiverWindow win: xmit_table.values()) { - retval+=win.getSmoothedLossRate(); - count++; - } - return retval / (double)count; + double retval=0.0; + int count=0; + if(xmit_table.isEmpty()) + return 0.0; + for(NakReceiverWindow win: xmit_table.values()) { + retval+=win.getSmoothedLossRate(); + count++; } + return retval / (double)count; + } public Vector providedUpServices() { Vector retval=new Vector(5); retval.addElement(new Integer(Event.GET_DIGEST)); retval.addElement(new Integer(Event.SET_DIGEST)); + retval.addElement(new Integer(Event.OVERWRITE_DIGEST)); retval.addElement(new Integer(Event.MERGE_DIGEST)); return retval; } @@ -528,31 +527,14 @@ timer=getTransport().getTimer(); if(timer == null) throw new Exception("timer is null"); - locks=stack.getLocks(); - started=true; + running=true; leaving=false; - - if(xmit_time_stats != null) { - Runtime.getRuntime().addShutdownHook(new Thread() { - public void run() { - String filename="xmit-stats-" + local_addr + ".log"; - System.out.println("-- dumping runtime xmit stats to " + filename); - try { - dumpXmitStats(filename); - } - catch(IOException e) { - e.printStackTrace(); - } - } - }); - } } public void stop() { - started=false; + running=false; reset(); // clears sent_msgs and destroys all NakReceiverWindows - oob_loopback_msgs.clear(); } @@ -566,9 +548,9 @@ case Event.MSG: Message msg=(Message)evt.getArg(); Address dest=msg.getDest(); - if(dest != null && !dest.isMulticastAddress()) { + if(dest != null && !dest.isMulticastAddress() || msg.isFlagSet(Message.NO_RELIABILITY)) break; // unicast address: not null and not mcast, pass down unchanged - } + send(evt, msg); return null; // don't pass down the stack @@ -583,6 +565,10 @@ setDigest((Digest)evt.getArg()); return null; + case Event.OVERWRITE_DIGEST: + overwriteDigest((Digest)evt.getArg()); + return null; + case Event.MERGE_DIGEST: mergeDigest((Digest)evt.getArg()); return null; @@ -617,6 +603,10 @@ is_server=true; break; + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; + case Event.DISCONNECT: leaving=true; reset(); @@ -656,19 +646,19 @@ case Event.MSG: Message msg=(Message)evt.getArg(); - NakAckHeader hdr=(NakAckHeader)msg.getHeader(name); + if(msg.isFlagSet(Message.NO_RELIABILITY)) + break; + NakAckHeader hdr=(NakAckHeader)msg.getHeader(this.id); if(hdr == null) break; // pass up (e.g. unicast msg) - // discard messages while not yet server (i.e., until JOIN has returned) - if(!is_server) { + if(!is_server) { // discard messages while not yet server (i.e., until JOIN has returned) if(log.isTraceEnabled()) - log.trace("message was discarded (not yet server)"); + log.trace("message " + msg.getSrc() + "::" + hdr.seqno + " was discarded (not yet server)"); return null; } - // Changed by bela Jan 29 2003: we must not remove the header, otherwise - // further xmit requests will fail ! + // Changed by bela Jan 29 2003: we must not remove the header, otherwise further xmit requests will fail ! //hdr=(NakAckHeader)msg.removeHeader(getName()); switch(hdr.type) { @@ -688,9 +678,7 @@ return null; case NakAckHeader.XMIT_RSP: - if(log.isTraceEnabled()) - log.trace("received missing message " + msg.getSrc() + ":" + hdr.seqno); - handleXmitRsp(msg); + handleXmitRsp(msg, hdr); return null; default: @@ -704,10 +692,6 @@ stable((Digest)evt.getArg()); return null; // do not pass up further (Bela Aug 7 2001) - case Event.SET_LOCAL_ADDRESS: - local_addr=(Address)evt.getArg(); - break; - case Event.SUSPECT: // release the promise if rebroadcasting is in progress... otherwise we wait forever. there will be a new // flush round anyway @@ -737,21 +721,28 @@ if(msg == null) throw new NullPointerException("msg is null; event is " + evt); - if(!started) { + if(!running) { if(log.isTraceEnabled()) - log.trace("[" + local_addr + "] discarded message as start() has not been called, message: " + msg); + log.trace("[" + local_addr + "] discarded message as we're not in the 'running' state, message: " + msg); return; } long msg_id; NakReceiverWindow win=xmit_table.get(local_addr); - msg.setSrc(local_addr); // this needs to be done so we can check whether the message sender is the local_addr + if(win == null) { // discard message if there is no entry for local_addr + if(log.isWarnEnabled() && log_discard_msgs) + log.warn(local_addr + ": discarded message from " + local_addr + " with no window, my view is " + view); + return; + } + + if(msg.getSrc() == null) + msg.setSrc(local_addr); // this needs to be done so we can check whether the message sender is the local_addr seqno_lock.lock(); try { try { // incrementing seqno and adding the msg to sent_msgs needs to be atomic msg_id=seqno +1; - msg.putHeader(name, new NakAckHeader(NakAckHeader.MSG, msg_id)); + msg.putHeader(this.id, NakAckHeader.createMessageHeader(msg_id)); win.add(msg_id, msg); seqno=msg_id; } @@ -764,8 +755,6 @@ } try { // moved down_prot.down() out of synchronized clause (bela Sept 7 2006) http://jira.jboss.com/jira/browse/JGRP-300 - if(msg.isFlagSet(Message.OOB)) - oob_loopback_msgs.add(msg_id); if(log.isTraceEnabled()) log.trace("sending " + local_addr + "#" + msg_id); down_prot.down(evt); // if this fails, since msg is in sent_msgs, it can be retransmitted @@ -783,7 +772,7 @@ * Finds the corresponding NakReceiverWindow and adds the message to it (according to seqno). Then removes as many * messages as possible from the NRW and passes them up the stack. Discards messages from non-members. */ - private void handleMessage(Message msg, NakAckHeader hdr) { + private void handleMessage(Message msg, NakAckHeader hdr) { Address sender=msg.getSrc(); if(sender == null) { if(log.isErrorEnabled()) @@ -791,35 +780,34 @@ return; } - if(log.isTraceEnabled()) - log.trace(new StringBuilder().append('[').append(local_addr).append(": received ").append(sender).append('#').append(hdr.seqno)); - NakReceiverWindow win=xmit_table.get(sender); if(win == null) { // discard message if there is no entry for sender if(leaving) return; if(log.isWarnEnabled() && log_discard_msgs) - log.warn(local_addr + "] discarded message from non-member " + sender + ", my view is " + view); + log.warn(local_addr + ": dropped message from " + sender + " (not in table " + xmit_table.keySet() +"), view=" + view); return; } boolean loopback=local_addr.equals(sender); boolean added=loopback || win.add(hdr.seqno, msg); - boolean regular_msg_added=added && !msg.isFlagSet(Message.OOB); - // message is passed up if OOB. Later, when remove() is called, we discard it. This affects ordering ! - // http://jira.jboss.com/jira/browse/JGRP-379 + if(added && log.isTraceEnabled()) + log.trace(new StringBuilder().append(local_addr).append(": received ").append(sender).append('#').append(hdr.seqno)); + + + // OOB msg is passed up. When removed, we discard it. Affects ordering: http://jira.jboss.com/jira/browse/JGRP-379 if(added && msg.isFlagSet(Message.OOB)) { - if(!loopback || oob_loopback_msgs.remove(hdr.seqno)) { - up_prot.up(new Event(Event.MSG, msg)); - win.removeOOBMessage(); - if(!(win.hasMessagesToRemove() && undelivered_msgs.get() > 0)) - return; + if(loopback) + msg=win.get(hdr.seqno); // we *have* to get a message, because loopback means we didn't add it to win ! + if(msg != null && msg.isFlagSet(Message.OOB)) { + if(msg.setTransientFlagIfAbsent(Message.OOB_DELIVERED)) + up_prot.up(new Event(Event.MSG, msg)); } } // Efficient way of checking whether another thread is already processing messages from 'sender'. - // If that's the case, we return immediately and let the exiting thread process our message + // If that's the case, we return immediately and let the existing thread process our message // (https://jira.jboss.org/jira/browse/JGRP-829). Benefit: fewer threads blocked on the same lock, these threads // can be returned to the thread pool final AtomicBoolean processing=win.getProcessing(); @@ -827,58 +815,39 @@ return; } - // Prevents concurrent passing up of messages by different threads (http://jira.jboss.com/jira/browse/JGRP-198); - // this is all the more important once we have a threadless stack (http://jira.jboss.com/jira/browse/JGRP-181), - // where lots of threads can come up to this point concurrently, but only 1 is allowed to pass at a time - // We *can* deliver messages from *different* senders concurrently, e.g. reception of P1, Q1, P2, Q2 can result in - // delivery of P1, Q1, Q2, P2: FIFO (implemented by NAKACK) says messages need to be delivered in the - // order in which they were sent by the sender - short removed_regular_msgs=0; - - // 2nd line of defense: in case of an exception, remove() might not be called, therefore processing would never - // be set back to false. If we get an exception and released_processing is not true, then we set - // processing to false in the finally clause + boolean remove_msgs=discard_delivered_msgs && !loopback; boolean released_processing=false; - final ReentrantLock lock=win.getLock(); try { - if(eager_lock_release) - locks.put(Thread.currentThread(), lock); - lock.lock(); while(true) { - Message msg_to_deliver=win.remove(processing); - if(msg_to_deliver == null) { + // we're removing a msg and set processing to false (if null) *atomically* (wrt to add()) + List msgs=win.removeMany(processing, remove_msgs, max_msg_batch_size); + if(msgs == null || msgs.isEmpty()) { released_processing=true; - return; // processing will be set to false now + if(rebroadcasting) + checkForRebroadcasts(); + return; } - // discard OOB msg as it has already been delivered (http://jira.jboss.com/jira/browse/JGRP-379) - if(msg_to_deliver.isFlagSet(Message.OOB)) { - continue; - } - removed_regular_msgs++; + for(final Message msg_to_deliver: msgs) { + // discard OOB msg if it has already been delivered (http://jira.jboss.com/jira/browse/JGRP-379) + if(msg_to_deliver.isFlagSet(Message.OOB) && !msg_to_deliver.setTransientFlagIfAbsent(Message.OOB_DELIVERED)) + continue; - // Changed by bela Jan 29 2003: not needed (see above) - //msg_to_deliver.removeHeader(getName()); - up_prot.up(new Event(Event.MSG, msg_to_deliver)); + //msg_to_deliver.removeHeader(getName()); // Changed by bela Jan 29 2003: not needed (see above) + try { + up_prot.up(new Event(Event.MSG, msg_to_deliver)); + } + catch(Throwable t) { + log.error("couldn't deliver message " + msg_to_deliver, t); + } + } } } finally { + // processing is always set in win.remove(processing) above and never here ! This code is just a + // 2nd line of defense should there be an exception before win.remove(processing) sets processing if(!released_processing) processing.set(false); - if(eager_lock_release) - locks.remove(Thread.currentThread()); - if(lock.isHeldByCurrentThread()) - lock.unlock(); - // We keep track of regular messages that we added, but couldn't remove (because of ordering). - // When we have such messages pending, then even OOB threads will remove and process them - // http://jira.jboss.com/jira/browse/JGRP-781 - if(regular_msg_added && removed_regular_msgs == 0) { - undelivered_msgs.incrementAndGet(); - } - if(removed_regular_msgs > 0) { // regardless of whether a message was added or not ! - int num_msgs_added=regular_msg_added? 1 : 0; - undelivered_msgs.addAndGet(-(removed_regular_msgs -num_msgs_added)); - } } } @@ -892,8 +861,9 @@ * @param last_seqno The last sequence number to be retransmitted (>= first_seqno) * @param original_sender The member who originally sent the messsage. Guaranteed to be non-null */ - private void handleXmitReq(Address xmit_requester, long first_seqno, long last_seqno, Address original_sender) { - Message msg; + private void handleXmitReq(Address xmit_requester, long first_seqno, long last_seqno, Address original_sender) { + if(first_seqno > last_seqno) + return; if(log.isTraceEnabled()) { StringBuilder sb=new StringBuilder(); @@ -902,59 +872,55 @@ log.trace(sb.toString()); } - if(first_seqno > last_seqno) { - if(log.isErrorEnabled()) - log.error("first_seqno (" + first_seqno + ") > last_seqno (" + last_seqno + "): not able to retransmit"); - return; - } - if(stats) { xmit_reqs_received+=last_seqno - first_seqno +1; updateStats(received, xmit_requester, 1, 0, 0); } - if(xmit_time_stats != null) { - long key=(System.currentTimeMillis() - xmit_time_stats_start) / 1000; - XmitTimeStat stat=xmit_time_stats.get(key); - if(stat == null) { - stat=new XmitTimeStat(); - XmitTimeStat stat2=xmit_time_stats.putIfAbsent(key, stat); - if(stat2 != null) - stat=stat2; - } - stat.xmit_reqs_received.addAndGet((int)(last_seqno - first_seqno +1)); - stat.xmit_rsps_sent.addAndGet((int)(last_seqno - first_seqno +1)); - } - NakReceiverWindow win=xmit_table.get(original_sender); if(win == null) { if(log.isErrorEnabled()) { StringBuilder sb=new StringBuilder(); sb.append("(requester=").append(xmit_requester).append(", local_addr=").append(this.local_addr); - sb.append(") ").append(original_sender).append(" not found in retransmission table:\n").append(printMessages()); + sb.append(") ").append(original_sender).append(" not found in retransmission table"); + // don't print the table unless we are in trace mode because it can be LARGE + if (log.isTraceEnabled()) { + sb.append(":\n").append(printMessages()); + } if(print_stability_history_on_failed_xmit) { sb.append(" (stability history:\n").append(printStabilityHistory()); } - log.error(sb); + log.error(sb.toString()); } return; } - for(long i=first_seqno; i <= last_seqno; i++) { - msg=win.get(i); - if(msg == null) { - if(log.isWarnEnabled() && !local_addr.equals(xmit_requester)) { - StringBuilder sb=new StringBuilder(); - sb.append("(requester=").append(xmit_requester).append(", local_addr=").append(this.local_addr); - sb.append(") message ").append(original_sender).append("::").append(i); - sb.append(" not found in retransmission table of ").append(original_sender).append(":\n").append(win); - if(print_stability_history_on_failed_xmit) { - sb.append(" (stability history:\n").append(printStabilityHistory()); + + long diff=last_seqno - first_seqno +1; + if(diff >= 10) { + List msgs=win.get(first_seqno, last_seqno); + if(msgs != null) { + for(Message msg: msgs) + sendXmitRsp(xmit_requester, msg); + } + } + else { + for(long i=first_seqno; i <= last_seqno; i++) { + Message msg=win.get(i); + if(msg == null) { + if(log.isWarnEnabled() && log_not_found_msgs && !local_addr.equals(xmit_requester)) { + StringBuilder sb=new StringBuilder(); + sb.append("(requester=").append(xmit_requester).append(", local_addr=").append(this.local_addr); + sb.append(") message ").append(original_sender).append("::").append(i); + sb.append(" not found in retransmission table of ").append(original_sender).append(":\n").append(win); + if(print_stability_history_on_failed_xmit) { + sb.append(" (stability history:\n").append(printStabilityHistory()); + } + log.warn(sb.toString()); } - log.warn(sb); + continue; } - continue; + sendXmitRsp(xmit_requester, msg); } - sendXmitRsp(xmit_requester, msg, i); } } @@ -992,10 +958,8 @@ * to preserve the original message's properties, such as src, headers etc. * @param dest * @param msg - * @param seqno */ - private void sendXmitRsp(Address dest, Message msg, long seqno) { - Buffer buf; + private void sendXmitRsp(Address dest, Message msg) { if(msg == null) { if(log.isErrorEnabled()) log.error("message is null, cannot send retransmission"); @@ -1007,68 +971,37 @@ updateStats(sent, dest, 0, 1, 0); } - if(use_mcast_xmit) - dest=null; - if(msg.getSrc() == null) msg.setSrc(local_addr); - try { - buf=Util.messageToByteBuffer(msg); - Message xmit_msg=new Message(dest, null, buf.getBuf(), buf.getOffset(), buf.getLength()); - // changed Bela Jan 4 2007: we should not use OOB for retransmitted messages, otherwise we tax the OOB thread pool - // too much - // msg.setFlag(Message.OOB); - xmit_msg.putHeader(name, new NakAckHeader(NakAckHeader.XMIT_RSP, seqno)); - down_prot.down(new Event(Event.MSG, xmit_msg)); - } - catch(IOException ex) { - log.error("failed marshalling xmit list", ex); + + if(use_mcast_xmit) { // we simply send the original multicast message + down_prot.down(new Event(Event.MSG, msg)); + return; } + + Message xmit_msg=msg.copy(true, true); // copy payload and headers + xmit_msg.setDest(dest); + NakAckHeader hdr=(NakAckHeader)xmit_msg.getHeader(id); + hdr.type=NakAckHeader.XMIT_RSP; // change the type in the copy from MSG --> XMIT_RSP + down_prot.down(new Event(Event.MSG, xmit_msg)); } - private void handleXmitRsp(Message msg) { - if(msg == null) { - if(log.isWarnEnabled()) - log.warn("message is null"); + private void handleXmitRsp(Message msg, NakAckHeader hdr) { + if(msg == null) return; - } try { - Message wrapped_msg=Util.byteBufferToMessage(msg.getRawBuffer(), msg.getOffset(), msg.getLength()); - if(xmit_time_stats != null) { - long key=(System.currentTimeMillis() - xmit_time_stats_start) / 1000; - XmitTimeStat stat=xmit_time_stats.get(key); - if(stat == null) { - stat=new XmitTimeStat(); - XmitTimeStat stat2=xmit_time_stats.putIfAbsent(key, stat); - if(stat2 != null) - stat=stat2; - } - stat.xmit_rsps_received.incrementAndGet(); - } - if(stats) { xmit_rsps_received++; updateStats(received, msg.getSrc(), 0, 1, 0); } - up(new Event(Event.MSG, wrapped_msg)); - - if(rebroadcasting) { - Digest tmp=getDigest(); - boolean cancel_rebroadcasting; - rebroadcast_digest_lock.lock(); - try { - cancel_rebroadcasting=tmp.isGreaterThanOrEqual(rebroadcast_digest); - } - finally { - rebroadcast_digest_lock.unlock(); - } - if(cancel_rebroadcasting) { - cancelRebroadcasting(); - } - } + msg.setDest(null); + hdr.type=NakAckHeader.MSG; // change the type back from XMIT_RSP --> MSG + up(new Event(Event.MSG, msg)); + if(rebroadcasting) + checkForRebroadcasts(); } catch(Exception ex) { if(log.isErrorEnabled()) { @@ -1112,10 +1045,15 @@ if(my_entry == null) continue; their_high=their_entry.getHighest(); - my_high=my_entry.getHighest(); + + // Cannot ask for 0 to be retransmitted because the first seqno in NAKACK and UNICAST(2) is always 1 ! + // Also, we need to ask for retransmission of my_high+1, because we already *have* my_high, and don't + // need it, so the retransmission range is [my_high+1 .. their_high]: *exclude* my_high, but *include* + // their_high + my_high=Math.max(1, my_entry.getHighest() +1); if(their_high > my_high) { if(log.isTraceEnabled()) - log.trace("sending XMIT request to " + sender + " for messages " + my_high + " - " + their_high); + log.trace("[" + local_addr + "] fetching " + my_high + "-" + their_high + " from " + sender); retransmit(my_high, their_high, sender, true); // use multicast to send retransmit request xmitted=true; } @@ -1147,38 +1085,35 @@ } } + protected void checkForRebroadcasts() { + Digest tmp=getDigest(); + boolean cancel_rebroadcasting; + rebroadcast_digest_lock.lock(); + try { + cancel_rebroadcasting=tmp.isGreaterThanOrEqual(rebroadcast_digest); + } + finally { + rebroadcast_digest_lock.unlock(); + } + if(cancel_rebroadcasting) { + cancelRebroadcasting(); + } + } + /** - * Remove old members from NakReceiverWindows and add new members (starting seqno=0). Essentially removes all - * entries from xmit_table that are not in members. This method is not called concurrently - * multiple times + * Remove old members from NakReceiverWindows. Essentially removes all entries from xmit_table that are not + * in members. This method is not called concurrently multiple times */ private void adjustReceivers(List
            new_members) { - NakReceiverWindow win; - - // 1. Remove all senders in xmit_table that are not members anymore - for(Iterator
            it=xmit_table.keySet().iterator(); it.hasNext();) { - Address sender=it.next(); - if(!new_members.contains(sender)) { - if(local_addr != null && local_addr.equals(sender)) { - if(log.isErrorEnabled()) - log.error("will not remove myself (" + sender + ") from xmit_table, received incorrect new membership of " + new_members); + for(Address member: xmit_table.keySet()) { + if(!new_members.contains(member)) { + if(local_addr != null && local_addr.equals(member)) continue; - } - win=xmit_table.get(sender); - win.reset(); - if(log.isDebugEnabled()) { - log.debug("removing " + sender + " from xmit_table (not member anymore)"); - } - it.remove(); - } - } - - // 2. Add newly joined members to xmit_table (starting seqno=0) - for(Address sender: new_members) { - if(!xmit_table.containsKey(sender)) { - win=createNakReceiverWindow(sender, INITIAL_SEQNO, 0); - xmit_table.put(sender, win); + NakReceiverWindow win=xmit_table.remove(member); + win.destroy(); + if(log.isDebugEnabled()) + log.debug("removed " + member + " from xmit_table (not member anymore)"); } } } @@ -1187,19 +1122,13 @@ /** * Returns a message digest: for each member P the lowest, highest delivered and highest received seqno is added */ - private Digest getDigest() { - Digest.Entry entry; - - Map map=new HashMap(members.size()); - for(Address sender: members) { - entry=getEntry(sender); - if(entry == null) { - if(log.isErrorEnabled()) { - log.error("range is null"); - } - continue; - } - map.put(sender, entry); + public Digest getDigest() { + final Map map=new HashMap(); + for(Map.Entry entry: xmit_table.entrySet()) { + Address sender=entry.getKey(); // guaranteed to be non-null (CCHM) + NakReceiverWindow win=entry.getValue(); // guaranteed to be non-null (CCHM) + long[] digest=win.getDigest(); + map.put(sender, new Digest.Entry(digest[0], digest[1], digest[2])); } return new Digest(map); } @@ -1211,189 +1140,134 @@ * reset it. */ private void setDigest(Digest digest) { - if(digest == null) { - if(log.isErrorEnabled()) { - log.error("digest or digest.senders is null"); - } - return; - } + setDigest(digest, false); + } - if(local_addr != null && digest.contains(local_addr)) { - clear(); - } - else { - // remove all but local_addr (if not null) - for(Iterator
            it=xmit_table.keySet().iterator(); it.hasNext();) { - Address key=it.next(); - if(local_addr != null && local_addr.equals(key)) { - ; - } - else { - it.remove(); - } - } - } - Address sender; - Digest.Entry val; - long initial_seqno; - NakReceiverWindow win; + /** + * For all members of the digest, adjust the NakReceiverWindows in xmit_table. If no entry + * exists, create one with the initial seqno set to the seqno of the member in the digest. If the member already + * exists, and is not the local address, replace it with the new entry (http://jira.jboss.com/jira/browse/JGRP-699) + * if the digest's seqno is greater than the seqno in the window. + */ + private void mergeDigest(Digest digest) { + setDigest(digest, true); + } + + /** + * Overwrites existing entries, but does NOT remove entries not found in the digest + * @param digest + */ + private void overwriteDigest(Digest digest) { + if(digest == null) + return; + + StringBuilder sb=new StringBuilder("\n[overwriteDigest()]\n"); + sb.append("existing digest: " + getDigest()).append("\nnew digest: " + digest); for(Map.Entry entry: digest.getSenders().entrySet()) { - sender=entry.getKey(); - val=entry.getValue(); - if(sender == null || val == null) { - if(log.isWarnEnabled()) { - log.warn("sender or value is null"); - } + Address sender=entry.getKey(); + Digest.Entry val=entry.getValue(); + if(sender == null || val == null) continue; + + long highest_delivered_seqno=val.getHighestDeliveredSeqno(); + long low_seqno=val.getLow(); + + NakReceiverWindow win=xmit_table.get(sender); + if(win != null) { + if(local_addr.equals(sender)) { + win.setHighestDelivered(highest_delivered_seqno); + continue; // don't destroy my own window + } + xmit_table.remove(sender); + win.destroy(); // stops retransmission } - initial_seqno=val.getHighestDeliveredSeqno(); - win=createNakReceiverWindow(sender, initial_seqno, val.getLow()); + win=createNakReceiverWindow(sender, highest_delivered_seqno, low_seqno); xmit_table.put(sender, win); } - if(!xmit_table.containsKey(local_addr)) { - if(log.isWarnEnabled()) { - log.warn("digest does not contain local address (local_addr=" + local_addr + ", digest=" + digest); - } - } + sb.append("\n").append("resulting digest: " + getDigest()); + digest_history.add(sb.toString()); + if(log.isDebugEnabled()) + log.debug(sb.toString()); } /** - * For all members of the digest, adjust the NakReceiverWindows in the xmit_table hashtable. If no entry - * exists, create one with the initial seqno set to the seqno of the member in the digest. If the member already - * exists, and is not the local address, replace it with the new entry (http://jira.jboss.com/jira/browse/JGRP-699) + * Sets or merges the digest. If there is no entry for a given member in xmit_table, create a new NakReceiverWindow. + * Else skip the existing entry, unless it is a merge. In this case, skip the existing entry if its seqno is + * greater than or equal to the one in the digest, or reset the window and create a new one if not. + * @param digest The digest + * @param merge Whether to merge the new digest with our own, or not */ - private void mergeDigest(Digest digest) { - if(digest == null) { - if(log.isErrorEnabled()) { - log.error("digest or digest.senders is null"); - } + private void setDigest(Digest digest, boolean merge) { + if(digest == null) return; - } - - - StringBuilder sb=null; - if(log.isDebugEnabled()) { - sb=new StringBuilder(); - sb.append("existing digest: " + getDigest()).append("\nnew digest: " + digest); - } - Address sender; - Digest.Entry val; - NakReceiverWindow win; - long highest_delivered_seqno, low_seqno; + StringBuilder sb=new StringBuilder(merge? "\n[mergeDigest()]\n" : "\n[setDigest()]\n"); + sb.append("existing digest: " + getDigest()).append("\nnew digest: " + digest); + boolean set_own_seqno=false; for(Map.Entry entry: digest.getSenders().entrySet()) { - sender=entry.getKey(); - val=entry.getValue(); - if(sender == null || val == null) { - if(log.isWarnEnabled()) { - log.warn("sender or value is null"); - } + Address sender=entry.getKey(); + Digest.Entry val=entry.getValue(); + if(sender == null || val == null) continue; - } - highest_delivered_seqno=val.getHighestDeliveredSeqno(); - low_seqno=val.getLow(); - // changed Feb 2008 (bela): http://jira.jboss.com/jira/browse/JGRP-699: we replace all existing entries - // except for myself - win=xmit_table.get(sender); + long highest_delivered_seqno=val.getHighestDeliveredSeqno(); + long low_seqno=val.getLow(); + + NakReceiverWindow win=xmit_table.get(sender); if(win != null) { - if(local_addr != null && local_addr.equals(sender)) { + // We only reset the window if its seqno is lower than the seqno shipped with the digest. Also, we + // don't reset our own window (https://jira.jboss.org/jira/browse/JGRP-948, comment 20/Apr/09 03:39 AM) + if(!merge + || (local_addr != null && local_addr.equals(sender)) // never overwrite our own entry + || win.getHighestDelivered() >= highest_delivered_seqno) // my seqno is >= digest's seqno for sender continue; - } - else { - win.reset(); // stops retransmission - xmit_table.remove(sender); + + xmit_table.remove(sender); + win.destroy(); // stops retransmission + if(sender.equals(local_addr)) { // Adjust the seqno: https://jira.jboss.org/browse/JGRP-1251 + seqno_lock.lock(); + try { + seqno=highest_delivered_seqno; + set_own_seqno=true; + } + finally { + seqno_lock.unlock(); + } } } win=createNakReceiverWindow(sender, highest_delivered_seqno, low_seqno); xmit_table.put(sender, win); } - if(log.isDebugEnabled() && sb != null) { - sb.append("\n").append("resulting digest: " + getDigest()); - log.debug(sb); - } - - if(!xmit_table.containsKey(local_addr)) { - if(log.isWarnEnabled()) { - log.warn("digest does not contain local address (local_addr=" + local_addr + ", digest=" + digest); - } - } + sb.append("\n").append("resulting digest: " + getDigest()); + if(set_own_seqno) + sb.append("\nnew seqno for " + local_addr + ": " + seqno); + digest_history.add(sb.toString()); + if(log.isDebugEnabled()) + log.debug(sb.toString()); } private NakReceiverWindow createNakReceiverWindow(Address sender, long initial_seqno, long lowest_seqno) { - NakReceiverWindow win=new NakReceiverWindow(local_addr, sender, this, initial_seqno, lowest_seqno, timer); + NakReceiverWindow win=new NakReceiverWindow(sender, this, initial_seqno, lowest_seqno, timer, use_range_based_retransmitter, + xmit_table_num_rows, xmit_table_msgs_per_row, + xmit_table_resize_factor, xmit_table_max_compaction_time, false); - if(use_stats_for_retransmission) { + if(use_stats_for_retransmission) win.setRetransmitTimeouts(new ActualInterval(sender)); - } - else if(exponential_backoff > 0) { + else if(exponential_backoff > 0) win.setRetransmitTimeouts(new ExponentialInterval(exponential_backoff)); - } - else { + else win.setRetransmitTimeouts(new StaticInterval(retransmit_timeouts)); - } - - win.setDiscardDeliveredMessages(discard_delivered_msgs); - win.setMaxXmitBufSize(this.max_xmit_buf_size); if(stats) win.setListener(this); return win; } - private void dumpXmitStats(String filename) throws IOException { - Writer out=new FileWriter(filename); - try { - TreeMap map=new TreeMap(xmit_time_stats); - StringBuilder sb; - XmitTimeStat stat; - out.write("time (secs) gaps-detected xmit-reqs-sent xmit-reqs-received xmit-rsps-sent xmit-rsps-received missing-msgs-received\n\n"); - for(Map.Entry entry: map.entrySet()) { - sb=new StringBuilder(); - stat=entry.getValue(); - sb.append(entry.getKey()).append(" "); - sb.append(stat.gaps_detected).append(" "); - sb.append(stat.xmit_reqs_sent).append(" "); - sb.append(stat.xmit_reqs_received).append(" "); - sb.append(stat.xmit_rsps_sent).append(" "); - sb.append(stat.xmit_rsps_received).append(" "); - sb.append(stat.missing_msgs_received).append("\n"); - out.write(sb.toString()); - } - } - finally { - out.close(); - } - } - - - - private Digest.Entry getEntry(Address sender) { - if(sender == null) { - if(log.isErrorEnabled()) { - log.error("sender is null"); - } - return null; - } - NakReceiverWindow win=xmit_table.get(sender); - if(win == null) { - if(log.isErrorEnabled()) { - log.error("sender " + sender + " not found in xmit_table"); - } - return null; - } - long low=win.getLowestSeen(), - highest_delivered=win.getHighestDelivered(), - highest_received=win.getHighestReceived(); - return new Digest.Entry(low, highest_delivered, highest_received); - } - - /** * Garbage collect messages that have been seen by all members. Update sent_msgs: for the sender P in the digest @@ -1497,12 +1371,12 @@ } } - hdr=new NakAckHeader(NakAckHeader.XMIT_REQ, first_seqno, last_seqno, sender); + hdr=NakAckHeader.createXmitRequestHeader(first_seqno, last_seqno, sender); retransmit_msg=new Message(dest, null, null); retransmit_msg.setFlag(Message.OOB); if(log.isTraceEnabled()) log.trace(local_addr + ": sending XMIT_REQ ([" + first_seqno + ", " + last_seqno + "]) to " + dest); - retransmit_msg.putHeader(name, hdr); + retransmit_msg.putHeader(this.id, hdr); ConcurrentMap tmp=xmit_stats.get(sender); if(tmp == null) { @@ -1515,25 +1389,13 @@ tmp.putIfAbsent(seq, System.currentTimeMillis()); } - if(xmit_time_stats != null) { - long key=(System.currentTimeMillis() - xmit_time_stats_start) / 1000; - XmitTimeStat stat=xmit_time_stats.get(key); - if(stat == null) { - stat=new XmitTimeStat(); - XmitTimeStat stat2=xmit_time_stats.putIfAbsent(key, stat); - if(stat2 != null) - stat=stat2; - } - stat.xmit_reqs_sent.addAndGet((int)(last_seqno - first_seqno +1)); - } - down_prot.down(new Event(Event.MSG, retransmit_msg)); if(stats) { xmit_reqs_sent+=last_seqno - first_seqno +1; updateStats(sent, sender, 1, 0, 0); - XmitRequest req=new XmitRequest(sender, first_seqno, last_seqno, sender); - send_history.add(req); } + + xmit_history.add(sender + ": " + first_seqno + "-" + last_seqno); } /* ------------------- End of Interface Retransmitter.RetransmitCommand -------------------- */ @@ -1571,58 +1433,19 @@ } } - if(xmit_time_stats != null) { - long key=(System.currentTimeMillis() - xmit_time_stats_start) / 1000; - XmitTimeStat stat=xmit_time_stats.get(key); - if(stat == null) { - stat=new XmitTimeStat(); - XmitTimeStat stat2=xmit_time_stats.putIfAbsent(key, stat); - if(stat2 != null) - stat=stat2; - } - stat.missing_msgs_received.incrementAndGet(); - } - if(stats) { missing_msgs_received++; updateStats(received, original_sender, 0, 0, 1); - MissingMessage missing=new MissingMessage(original_sender, seqno); - receive_history.add(missing); } } /** Called when a message gap is detected */ public void messageGapDetected(long from, long to, Address src) { - if(xmit_time_stats != null) { - long key=(System.currentTimeMillis() - xmit_time_stats_start) / 1000; - XmitTimeStat stat=xmit_time_stats.get(key); - if(stat == null) { - stat=new XmitTimeStat(); - XmitTimeStat stat2=xmit_time_stats.putIfAbsent(key, stat); - if(stat2 != null) - stat=stat2; - } - stat.gaps_detected.addAndGet((int)(to - from +1)); - } + ; } /* ------------------- End of Interface NakReceiverWindow.Listener ------------------- */ - private void clear() { - // changed April 21 2004 (bela): SourceForge bug# 938584. We cannot delete our own messages sent between - // a join() and a getState(). Otherwise retransmission requests from members who missed those msgs might - // fail. Not to worry though: those msgs will be cleared by STABLE (message garbage collection) - - // sent_msgs.clear(); - - for(NakReceiverWindow win: xmit_table.values()) { - win.reset(); - } - xmit_table.clear(); - undelivered_msgs.set(0); - } - - private void reset() { seqno_lock.lock(); try { @@ -1636,22 +1459,16 @@ win.destroy(); } xmit_table.clear(); - undelivered_msgs.set(0); } @ManagedOperation(description="TODO") public String printMessages() { - StringBuilder ret=new StringBuilder(); - Map.Entry entry; - Address addr; - Object w; - - for(Iterator> it=xmit_table.entrySet().iterator(); it.hasNext();) { - entry=it.next(); - addr=entry.getKey(); - w=entry.getValue(); - ret.append(addr).append(": ").append(w.toString()).append('\n'); + StringBuilder ret=new StringBuilder(local_addr + ":\n"); + for(Map.Entry entry: xmit_table.entrySet()) { + Address addr=entry.getKey(); + NakReceiverWindow win=entry.getValue(); + ret.append(addr).append(": ").append(win.toString()).append('\n'); } return ret.toString(); } @@ -1697,6 +1514,14 @@ return sb.toString(); } + @ManagedOperation(description="Prints the last N retransmission requests") + public String printXmitHistory() { + StringBuilder sb=new StringBuilder(); + for(String req: xmit_history) + sb.append(req).append("\n"); + return sb.toString(); + } + @ManagedAttribute public double getTotalAverageRetransmissionTime() { long total=0; @@ -1739,79 +1564,26 @@ } -// public static final class LossRate { -// private final Set received=new HashSet(); -// private final Set missing=new HashSet(); -// private double smoothed_loss_rate=0.0; -// -// public synchronized void addReceived(long seqno) { -// received.add(seqno); -// missing.remove(seqno); -// setSmoothedLossRate(); -// } -// -// public synchronized void addReceived(Long ... seqnos) { -// for(int i=0; i < seqnos.length; i++) { -// Long seqno=seqnos[i]; -// received.add(seqno); -// missing.remove(seqno); -// } -// setSmoothedLossRate(); -// } -// -// public synchronized void addMissing(long from, long to) { -// for(long i=from; i <= to; i++) { -// if(!received.contains(i)) -// missing.add(i); -// } -// setSmoothedLossRate(); -// } -// -// public synchronized double computeLossRate() { -// int num_missing=missing.size(); -// if(num_missing == 0) -// return 0.0; -// int num_received=received.size(); -// int total=num_missing + num_received; -// return num_missing / (double)total; -// } -// -// public synchronized double getSmoothedLossRate() { -// return smoothed_loss_rate; -// } -// -// public synchronized String toString() { -// StringBuilder sb=new StringBuilder(); -// int num_missing=missing.size(); -// int num_received=received.size(); -// int total=num_missing + num_received; -// sb.append("total=").append(total).append(" (received=").append(received.size()).append(", missing=") -// .append(missing.size()).append(", loss rate=").append(computeLossRate()) -// .append(", smoothed loss rate=").append(smoothed_loss_rate).append(")"); -// return sb.toString(); -// } -// -// /** Set the new smoothed_loss_rate value to 70% of the new value and 30% of the old value */ -// private void setSmoothedLossRate() { -// double new_loss_rate=computeLossRate(); -// if(smoothed_loss_rate == 0) { -// smoothed_loss_rate=new_loss_rate; -// } -// else { -// smoothed_loss_rate=smoothed_loss_rate * .3 + new_loss_rate * .7; -// } -// } -// } - - private static class XmitTimeStat { - final AtomicInteger gaps_detected=new AtomicInteger(0); - final AtomicInteger xmit_reqs_sent=new AtomicInteger(0); - final AtomicInteger xmit_reqs_received=new AtomicInteger(0); - final AtomicInteger xmit_rsps_sent=new AtomicInteger(0); - final AtomicInteger xmit_rsps_received=new AtomicInteger(0); - final AtomicInteger missing_msgs_received=new AtomicInteger(0); + // ProbeHandler interface + public Map handleProbe(String... keys) { + Map retval=new HashMap(); + for(String key: keys) { + if(key.equals("digest-history")) + retval.put(key, printDigestHistory()); + if(key.equals("dump-digest")) + retval.put(key, "\n" + printMessages()); + } + + return retval; + } + + // ProbeHandler interface + public String[] supportedKeys() { + return new String[]{"digest-history", "dump-digest"}; } + + private class ActualInterval implements Interval { private final Address sender; @@ -1839,44 +1611,8 @@ } } - static class XmitRequest { - final Address original_sender; // original sender of message - final long low, high, timestamp=System.currentTimeMillis(); - final Address xmit_dest; // destination to which XMIT_REQ is sent, usually the original sender - - XmitRequest(Address original_sender, long low, long high, Address xmit_dest) { - this.original_sender=original_sender; - this.xmit_dest=xmit_dest; - this.low=low; - this.high=high; - } - - public String toString() { - StringBuilder sb=new StringBuilder(); - sb.append(new Date(timestamp)).append(": ").append(original_sender).append(" #[").append(low); - sb.append("-").append(high).append("]"); - sb.append(" (XMIT_REQ sent to ").append(xmit_dest).append(")"); - return sb.toString(); - } - } - - static class MissingMessage { - final Address original_sender; - final long seq, timestamp=System.currentTimeMillis(); - - MissingMessage(Address original_sender, long seqno) { - this.original_sender=original_sender; - this.seq=seqno; - } - - public String toString() { - StringBuilder sb=new StringBuilder(); - sb.append(new Date(timestamp)).append(": ").append(original_sender).append(" #").append(seq); - return sb.toString(); - } - } /* ----------------------------- End of Private Methods ------------------------------------ */ -} +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/ParticipantGmsImpl.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/ParticipantGmsImpl.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/ParticipantGmsImpl.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/ParticipantGmsImpl.java 2011-10-18 11:22:35.000000000 +0000 @@ -4,6 +4,7 @@ import org.jgroups.*; import org.jgroups.util.Promise; import org.jgroups.util.Digest; +import org.jgroups.util.MergeId; import java.util.Vector; import java.util.Collection; @@ -12,9 +13,8 @@ /** * @author Bela Ban - * @version $Id: ParticipantGmsImpl.java,v 1.30 2008/09/18 16:42:58 vlada Exp $ */ -public class ParticipantGmsImpl extends GmsImpl { +public class ParticipantGmsImpl extends ServerGmsImpl { private final Vector
            suspected_mbrs=new Vector
            (11); private final Promise leave_promise=new Promise(); @@ -30,11 +30,11 @@ leave_promise.reset(); } - public void join(Address mbr) { + public void join(Address mbr, boolean useFlushIfPresent) { wrongMethod("join"); } - public void joinWithStateTransfer(Address mbr) { + public void joinWithStateTransfer(Address mbr,boolean useFlushIfPresent) { wrongMethod("join"); } @@ -91,7 +91,7 @@ public void suspect(Address mbr) { Collection suspected=new LinkedHashSet(1); - suspected.add(new Request(Request.SUSPECT,mbr,true,null)); + suspected.add(new Request(Request.SUSPECT,mbr,true)); handleMembershipChange(suspected); } @@ -128,7 +128,7 @@ suspected_mbrs.removeAllElements(); gms.becomeCoordinator(); for(Address mbr: suspectedMembers) { - gms.getViewHandler().add(new Request(Request.SUSPECT, mbr, true, null)); + gms.getViewHandler().add(new Request(Request.SUSPECT, mbr, true)); gms.ack_collector.suspect(mbr); } } @@ -144,7 +144,6 @@ */ public void handleViewChange(View new_view, Digest digest) { Vector
            mbrs=new_view.getMembers(); - if(log.isDebugEnabled()) log.debug("view=" + new_view); suspected_mbrs.removeAllElements(); if(leaving && !mbrs.contains(gms.local_addr)) { // received a view in which I'm not member: ignore return; @@ -152,10 +151,7 @@ gms.installView(new_view, digest); } - public void handleMergeRequest(Address sender, ViewId merge_id) { - // only coords handle this method; reject it if we're not coord - sendMergeRejectedResponse(sender, merge_id); - } + /* ---------------------------------- Private Methods --------------------------------------- */ @@ -182,7 +178,7 @@ msg.setFlag(Message.OOB); GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.LEAVE_REQ, mbr); - msg.putHeader(gms.getName(), hdr); + msg.putHeader(gms.getId(), hdr); gms.getDownProtocol().down(new Event(Event.MSG, msg)); } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/ServerGmsImpl.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/ServerGmsImpl.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/ServerGmsImpl.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/ServerGmsImpl.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,42 @@ +package org.jgroups.protocols.pbcast; + +import org.jgroups.Address; +import org.jgroups.util.MergeId; +import org.jgroups.util.Digest; + +import java.util.Collection; + +/** + * Common super class for CoordGmsImpl and ParticipantGmsImpl + * @author Bela Ban + */ +public abstract class ServerGmsImpl extends GmsImpl { + + protected ServerGmsImpl(GMS gms) { + super(gms); + } + + + /** + * Get the view and digest and send back both (MergeData) in the form of a MERGE_RSP to the sender. + * If a merge is already in progress, send back a MergeData with the merge_rejected field set to true. + * @param sender The address of the merge leader + * @param merge_id The merge ID + * @param mbrs The set of members from which we expect responses + */ + public void handleMergeRequest(Address sender, MergeId merge_id, Collection mbrs) { + merger.handleMergeRequest(sender, merge_id, mbrs); + } + + /** + * If merge_id is not equal to this.merge_id then discard. + * Else cast the view/digest to all members of this group. + */ + public void handleMergeView(final MergeData data,final MergeId merge_id) { + merger.handleMergeView(data, merge_id); + } + + public void handleDigestResponse(Address sender, Digest digest) { + merger.handleDigestResponse(sender, digest); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/STABLE.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/STABLE.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/STABLE.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/STABLE.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,16 +2,16 @@ import org.jgroups.*; -import org.jgroups.annotations.DeprecatedProperty; -import org.jgroups.annotations.GuardedBy; -import org.jgroups.annotations.MBean; -import org.jgroups.annotations.ManagedAttribute; -import org.jgroups.annotations.ManagedOperation; -import org.jgroups.annotations.Property; +import org.jgroups.annotations.*; import org.jgroups.stack.Protocol; -import org.jgroups.util.*; +import org.jgroups.util.Digest; +import org.jgroups.util.MutableDigest; +import org.jgroups.util.TimeScheduler; +import org.jgroups.util.Util; import java.io.*; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; import java.util.*; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -37,31 +37,23 @@ * in docs/design/STABLE.txt * * @author Bela Ban - * @version $Id: STABLE.java,v 1.93 2008/10/21 12:56:59 vlada Exp $ */ @MBean(description="Computes the broadcast messages that are stable") @DeprecatedProperty(names={"digest_timeout","max_gossip_runs","max_suspend_time"}) public class STABLE extends Protocol { - - - private static final String name="STABLE"; - private static final long MAX_SUSPEND_TIME=200000; /* ------------------------------------------ Properties ------------------------------------------ */ /** - * Sends a STABLE gossip every 20 seconds on average. 0 disables gossiping - * of STABLE messages + * Sends a STABLE gossip every 20 seconds on average. 0 disables gossiping of STABLE messages */ - @ManagedAttribute(writable=true) @Property(description="Average time to send a STABLE message. Default is 20000 msec") private long desired_avg_gossip=20000; /** * delay before we send STABILITY msg (give others a change to send first). - * This should be set to a very small number (> 0 !) if - * max_bytes is used + * This should be set to a very small number (> 0 !) if max_bytes is used */ @Property(description="Delay before stability message is sent. Default is 6000 msec") private long stability_delay=6000; @@ -73,9 +65,15 @@ * ideally stability_delay should be set to a low number as * well */ - @ManagedAttribute(writable=true) - @Property(description="Maximum number of bytes received in all messages before sending a STABLE message is triggered. Default is 0 (disabled)") - private long max_bytes=0; + @Property(description="Maximum number of bytes received in all messages before sending a STABLE message is triggered ." + + "If ergonomics is enabled, this value is computed as max(MAX_HEAP * cap, N * max_bytes) where N = number of members") + protected long max_bytes=2000000; + + protected long original_max_bytes=max_bytes; + + @Property(description="Max percentage of the max heap (-Xmx) to be used for max_bytes. " + + "Only used if ergonomics is enabled. 0 disables setting max_bytes dynamically.") + protected double cap=0.10; // 10% of the max heap by default /* --------------------------------------------- JMX ---------------------------------------------- */ @@ -132,16 +130,14 @@ private Future resume_task_future=null; private final Object resume_task_mutex=new Object(); + + protected final MemoryMXBean memory_manager=ManagementFactory.getMemoryMXBean(); public STABLE() { } - public String getName() { - return name; - } - public long getDesiredAverageGossip() { return desired_avg_gossip; } @@ -156,9 +152,10 @@ public void setMaxBytes(long max_bytes) { this.max_bytes=max_bytes; + this.original_max_bytes=max_bytes; } - @ManagedAttribute + @ManagedAttribute(name="BytesReceived") public long getBytes() {return num_bytes_received;} @ManagedAttribute public int getStableSent() {return num_stable_msgs_sent;} @@ -169,6 +166,16 @@ @ManagedAttribute public int getStabilityReceived() {return num_stability_msgs_received;} + @ManagedAttribute + public boolean getStableTaskRunning() { + stable_task_lock.lock(); + try { + return stable_task_future != null && !stable_task_future.isDone() && !stable_task_future.isCancelled(); + } + finally { + stable_task_lock.unlock(); + } + } public void resetStats() { super.resetStats(); @@ -208,7 +215,7 @@ public void init() throws Exception { super.init(); - Util.checkBufferSize("STABLE.max_bytes", max_bytes); + original_max_bytes=max_bytes; } public void start() throws Exception { @@ -233,7 +240,7 @@ case Event.MSG: msg=(Message)evt.getArg(); - hdr=(StableHeader)msg.getHeader(name); + hdr=(StableHeader)msg.getHeader(this.id); if(hdr == null) { handleRegularMessage(msg); return up_prot.up(evt); @@ -256,10 +263,6 @@ View view=(View)evt.getArg(); handleViewChange(view); return retval; - - case Event.SET_LOCAL_ADDRESS: - local_addr=(Address)evt.getArg(); - break; } return up_prot.up(evt); } @@ -273,8 +276,8 @@ return; Address dest=msg.getDest(); if(dest == null || dest.isMulticastAddress()) { + boolean send_stable_msg=false; received.lock(); - boolean locked=true; try { num_bytes_received+=msg.getLength(); if(num_bytes_received >= max_bytes) { @@ -283,17 +286,18 @@ append(", bytes received=").append(num_bytes_received).append("): triggers stable msg")); } num_bytes_received=0; - received.unlock(); - locked=false; - Digest my_digest=getDigest(); // asks the NAKACK protocol for the current digest, - if(log.isTraceEnabled()) - log.trace("setting latest_local_digest from NAKACK: " + my_digest.printHighestDeliveredSeqnos()); - sendStableMessage(my_digest); + send_stable_msg=true; } } finally { - if(locked) - received.unlock(); + received.unlock(); + } + + if(send_stable_msg) { + Digest my_digest=getDigest(); // asks the NAKACK protocol for the current digest, + if(log.isTraceEnabled()) + log.trace("setting latest_local_digest from NAKACK: " + my_digest.printHighestDeliveredSeqnos()); + sendStableMessage(my_digest); } } } @@ -305,19 +309,23 @@ Object retval=down_prot.down(evt); View v=(View)evt.getArg(); handleViewChange(v); - return retval; + return retval; - case Event.SUSPEND_STABLE: - long timeout=0; - Object t=evt.getArg(); - if(t != null && t instanceof Long) - timeout=(Long)t; - suspend(timeout); - break; + case Event.SUSPEND_STABLE: + long timeout=0; + Object t=evt.getArg(); + if(t != null && t instanceof Long) + timeout=(Long)t; + suspend(timeout); + break; - case Event.RESUME_STABLE: - resume(); - break; + case Event.RESUME_STABLE: + resume(); + break; + + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; } return down_prot.down(evt); } @@ -345,6 +353,14 @@ resetDigest(); if(!initialized) initialized=true; + + if(ergonomics && cap > 0) { + long max_heap=(long)(memory_manager.getHeapMemoryUsage().getMax() * cap); + long new_size=tmp.size() * original_max_bytes; + max_bytes=Math.min(max_heap, new_size); + if(log.isDebugEnabled()) + log.debug("[ergonomics] setting max_bytes to " + Util.printBytes(max_bytes) + " (" + tmp.size() + " members)"); + } } finally { lock.unlock(); @@ -375,7 +391,7 @@ StringBuilder sb=null; if(log.isTraceEnabled()) { - sb=new StringBuilder("[").append(local_addr).append("] handling digest from ").append(sender).append(" ("). + sb=new StringBuilder().append(local_addr).append(": handling digest from ").append(sender).append(" ("). append(votes.size()).append(" votes):\nmine: ").append(digest.printHighestDeliveredSeqnos()) .append("\nother: ").append(d.printHighestDeliveredSeqnos()); } @@ -403,7 +419,6 @@ digest.setHighestDeliveredAndSeenSeqnos(mbr, new_low, new_highest_seqno, new_highest_seen_seqno); } if(log.isTraceEnabled()) { - assert sb != null; sb.append("\nresult: ").append(digest.printHighestDeliveredSeqnos()).append("\n"); log.trace(sb); } @@ -416,7 +431,7 @@ Digest tmp=getDigest(); digest.replace(tmp); if(log.isTraceEnabled()) - log.trace("resetting digest from NAKACK: " + digest.printHighestDeliveredSeqnos()); + log.trace(local_addr + ": resetting digest from NAKACK: " + digest.printHighestDeliveredSeqnos()); votes.clear(); } @@ -432,7 +447,9 @@ /** Votes is already locked and guaranteed to be non-null */ private boolean allVotesReceived(Set
            votes) { - return votes.equals(mbrs); // compares identity, size and element-wise (if needed) + synchronized(mbrs) { + return votes.equals(mbrs); // compares identity, size and element-wise (if needed) + } } @@ -441,7 +458,7 @@ try { if(stable_task_future == null || stable_task_future.isDone()) { StableTask stable_task=new StableTask(); - stable_task_future=timer.scheduleWithDynamicInterval(stable_task, true); + stable_task_future=timer.scheduleWithDynamicInterval(stable_task); if(log.isTraceEnabled()) log.trace("stable task started"); } @@ -548,7 +565,6 @@ } Digest copy=null; - boolean all_votes_received=false; lock.lock(); try { if(votes.contains(sender)) // already received gossip from sender; discard it @@ -558,7 +574,7 @@ if(!success) // we can only add the sender to votes if *all* elements of my digest were updated return; - all_votes_received=addVote(sender); + boolean all_votes_received=addVote(sender); if(all_votes_received) copy=digest.copy(); } @@ -594,7 +610,7 @@ } if(log.isTraceEnabled()) - log.trace(new StringBuilder("received stability msg from ").append(sender).append(": ").append(stable_digest.printHighestDeliveredSeqnos())); + log.trace(new StringBuilder(local_addr + ": received stability msg from ").append(sender).append(": ").append(stable_digest.printHighestDeliveredSeqnos())); stopStabilityTask(); lock.lock(); @@ -603,7 +619,7 @@ // this is part of the fix for the NAKACK problem (bugs #943480 and #938584) if(!this.digest.sameSenders(stable_digest)) { if(log.isDebugEnabled()) { - log.debug("received digest (digest=" + stable_digest + ") which does not match my own digest ("+ + log.debug(local_addr + ": received digest from " + sender + " (digest=" + stable_digest + ") which does not match my own digest ("+ this.digest + "): ignoring digest and re-initializing own digest"); } resetDigest(); @@ -637,12 +653,12 @@ if(d != null && d.size() > 0) { if(log.isTraceEnabled()) - log.trace("sending stable msg " + d.printHighestDeliveredSeqnos()); + log.trace(local_addr + ": sending stable msg " + d.printHighestDeliveredSeqnos()); num_stable_msgs_sent++; final Message msg=new Message(); // mcast message msg.setFlag(Message.OOB); StableHeader hdr=new StableHeader(StableHeader.STABLE_GOSSIP, d); - msg.putHeader(name, hdr); + msg.putHeader(this.id, hdr); Runnable r=new Runnable() { public void run() { @@ -681,7 +697,7 @@ // our random sleep, we will not send the STABILITY msg. this prevents that all mbrs mcast a // STABILITY msg at the same time delay=Util.random(stability_delay); - if(log.isTraceEnabled()) log.trace("sending stability msg (in " + delay + " ms) " + tmp.printHighestDeliveredSeqnos() + + if(log.isTraceEnabled()) log.trace(local_addr + ": sending stability msg (in " + delay + " ms) " + tmp.printHighestDeliveredSeqnos() + " (copy=" + tmp.hashCode() + ")"); startStabilityTask(tmp, delay); } @@ -700,7 +716,7 @@ - public static class StableHeader extends Header implements Streamable { + public static class StableHeader extends Header { public static final int STABLE_GOSSIP=1; public static final int STABILITY=2; @@ -709,7 +725,7 @@ Digest stableDigest=null; // changed by Bela April 4 2004 public StableHeader() { - } // used for externalizable + } public StableHeader(int type, Digest digest) { @@ -738,27 +754,6 @@ return sb.toString(); } - - public void writeExternal(ObjectOutput out) throws IOException { - out.writeInt(type); - if(stableDigest == null) { - out.writeBoolean(false); - return; - } - out.writeBoolean(true); - stableDigest.writeExternal(out); - } - - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - type=in.readInt(); - boolean digest_not_null=in.readBoolean(); - if(digest_not_null) { - stableDigest=new Digest(); - stableDigest.readExternal(in); - } - } - public int size() { int retval=Global.INT_SIZE + Global.BYTE_SIZE; // type + presence for digest if(stableDigest != null) @@ -775,8 +770,6 @@ type=in.readInt(); stableDigest=(Digest)Util.readStreamable(Digest.class, in); } - - } @@ -811,7 +804,7 @@ return; } if(log.isTraceEnabled()) - log.trace("setting latest_local_digest from NAKACK: " + my_digest.printHighestDeliveredSeqnos()); + log.trace(local_addr + ": setting latest_local_digest from NAKACK: " + my_digest.printHighestDeliveredSeqnos()); sendStableMessage(my_digest); } @@ -853,8 +846,8 @@ msg=new Message(); msg.setFlag(Message.OOB); hdr=new StableHeader(StableHeader.STABILITY, stability_digest); - msg.putHeader(STABLE.name, hdr); - if(log.isTraceEnabled()) log.trace("sending stability msg " + stability_digest.printHighestDeliveredSeqnos() + + msg.putHeader(id, hdr); + if(log.isTraceEnabled()) log.trace(local_addr + ": sending stability msg " + stability_digest.printHighestDeliveredSeqnos() + " (copy=" + stability_digest.hashCode() + ")"); num_stability_msgs_sent++; down_prot.down(new Event(Event.MSG, msg)); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/STATE_TRANSFER.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/STATE_TRANSFER.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/STATE_TRANSFER.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/STATE_TRANSFER.java 2011-10-18 11:22:35.000000000 +0000 @@ -7,9 +7,8 @@ import org.jgroups.annotations.ManagedAttribute; import org.jgroups.stack.Protocol; import org.jgroups.stack.StateTransferInfo; -import org.jgroups.util.Streamable; -import org.jgroups.util.Util; import org.jgroups.util.Digest; +import org.jgroups.util.Util; import java.io.*; import java.util.*; @@ -24,14 +23,11 @@ * sets its digest to D and then returns the state to the application. * * @author Bela Ban - * @version $Id: STATE_TRANSFER.java,v 1.82 2008/07/11 18:07:10 vlada Exp $ */ @MBean(description="State transfer protocol based on byte array transfer") @DeprecatedProperty(names= { "use_flush", "flush_timeout" }) public class STATE_TRANSFER extends Protocol { - private final static String name="STATE_TRANSFER"; - /* --------------------------------------------- JMX statistics --------------------------------------------- */ private long start, stop; // to measure state transfer time @@ -40,7 +36,7 @@ private final AtomicLong num_bytes_sent=new AtomicLong(0); - private volatile double avg_state_size=0; + private double avg_state_size=0; /* --------------------------------------------- Fields ------------------------------------------------------ */ @@ -56,15 +52,12 @@ private final Map> state_requesters=new HashMap>(); /** set to true while waiting for a STATE_RSP */ - private boolean waiting_for_state_response=false; + private volatile boolean waiting_for_state_response=false; private boolean flushProtocolInStack=false; public STATE_TRANSFER() {} - public String getName() { - return name; - } @ManagedAttribute public int getNumberOfStateRequests() { @@ -85,7 +78,7 @@ public Vector requiredDownServices() { Vector retval=new Vector(); retval.addElement(new Integer(Event.GET_DIGEST)); - retval.addElement(new Integer(Event.SET_DIGEST)); + retval.addElement(new Integer(Event.OVERWRITE_DIGEST)); return retval; } @@ -115,7 +108,7 @@ case Event.MSG: Message msg=(Message)evt.getArg(); - StateHeader hdr=(StateHeader)msg.getHeader(getName()); + StateHeader hdr=(StateHeader)msg.getHeader(this.id); if(hdr == null) break; @@ -124,7 +117,16 @@ handleStateReq(hdr); break; case StateHeader.STATE_RSP: - handleStateRsp(hdr, msg.getBuffer()); + // fix for https://jira.jboss.org/jira/browse/JGRP-1013 + if(isDigestNeeded()) + down_prot.down(new Event(Event.CLOSE_BARRIER)); + try { + handleStateRsp(hdr, msg.getBuffer()); + } + finally { + if(isDigestNeeded()) + down_prot.down(new Event(Event.OPEN_BARRIER)); + } break; default: if(log.isErrorEnabled()) @@ -133,10 +135,6 @@ } return null; - case Event.SET_LOCAL_ADDRESS: - local_addr=(Address)evt.getArg(); - break; - case Event.TMP_VIEW: case Event.VIEW_CHANGE: handleViewChange((View)evt.getArg()); @@ -182,7 +180,7 @@ } else { Message state_req=new Message(target, null, null); - state_req.putHeader(getName(), new StateHeader(StateHeader.STATE_REQ, + state_req.putHeader(this.id, new StateHeader(StateHeader.STATE_REQ, local_addr, System.currentTimeMillis(), null, @@ -208,6 +206,9 @@ } break; + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; } return down_prot.down(evt); // pass on to the layer below us @@ -277,7 +278,7 @@ 0, digest, id); - state_rsp.putHeader(getName(), hdr); + state_rsp.putHeader(this.id, hdr); responses.add(state_rsp); } state_requesters.remove(id); @@ -403,42 +404,24 @@ } /** Set the digest and the send the state up to the application */ - void handleStateRsp(StateHeader hdr, byte[] state) { - Address sender=hdr.sender; + private void handleStateRsp(StateHeader hdr, byte[] state) { Digest tmp_digest=hdr.my_digest; - String id=hdr.state_id; - Address state_sender=hdr.sender; + boolean digest_needed=isDigestNeeded(); waiting_for_state_response=false; - if(isDigestNeeded()) { - if(tmp_digest == null) { - if(log.isWarnEnabled()) - log.warn("digest received from " + sender - + " is null, skipping setting digest !"); - } - else - down_prot.down(new Event(Event.SET_DIGEST, tmp_digest)); // set the digest (e.g. in NAKACK) + if(digest_needed && tmp_digest != null) { + down_prot.down(new Event(Event.OVERWRITE_DIGEST, tmp_digest)); // set the digest (e.g. in NAKACK) } stop=System.currentTimeMillis(); - // resume sending and handling of message garbage collection gossip messages, - // fixes bugs #943480 and #938584). Wakes up a previously suspended message garbage - // collection protocol (e.g. STABLE) + // resume sending and handling of message garbage collection gossip messages, fixes bugs #943480 and #938584). + // Wakes up a previously suspended message garbage collection protocol (e.g. STABLE) if(log.isDebugEnabled()) log.debug("passing down a RESUME_STABLE event"); down_prot.down(new Event(Event.RESUME_STABLE)); - if(state == null) { - if(log.isWarnEnabled()) - log.warn("state received from " + sender - + " is null, will return null state to application"); - } - else - log.debug("received state, size=" + state.length - + " bytes. Time=" - + (stop - start) - + " milliseconds"); - StateTransferInfo info=new StateTransferInfo(state_sender, id, 0L, state); + log.debug("received state, size=" + (state == null? "0" : state.length) + " bytes. Time=" + (stop - start) + " milliseconds"); + StateTransferInfo info=new StateTransferInfo(hdr.sender, hdr.state_id, 0L, state); up_prot.up(new Event(Event.GET_STATE_OK, info)); } @@ -450,7 +433,7 @@ * be stored in the header itself, but in the message's buffer. * */ - public static class StateHeader extends Header implements Streamable { + public static class StateHeader extends Header { public static final byte STATE_REQ=1; public static final byte STATE_RSP=2; @@ -459,7 +442,6 @@ Address sender; // sender of state STATE_REQ or STATE_RSP Digest my_digest=null; // digest of sender (if type is STATE_RSP) String state_id=null; // for partial state transfer - private static final long serialVersionUID=4457830093491204405L; public StateHeader() { // for externalization } @@ -533,28 +515,6 @@ } } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeObject(sender); - out.writeLong(id); - out.writeByte(type); - out.writeObject(my_digest); - if(state_id == null) { - out.writeBoolean(false); - } - else { - out.writeBoolean(true); - out.writeUTF(state_id); - } - } - - public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException { - sender=(Address)in.readObject(); - id=in.readLong(); - type=in.readByte(); - my_digest=(Digest)in.readObject(); - if(in.readBoolean()) - state_id=in.readUTF(); - } public void writeTo(DataOutputStream out) throws IOException { out.writeByte(type); @@ -564,9 +524,7 @@ Util.writeString(state_id, out); } - public void readFrom(DataInputStream in) throws IOException, - IllegalAccessException, - InstantiationException { + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { type=in.readByte(); id=in.readLong(); sender=Util.readAddress(in); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/STREAMING_STATE_TRANSFER.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/STREAMING_STATE_TRANSFER.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/pbcast/STREAMING_STATE_TRANSFER.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/pbcast/STREAMING_STATE_TRANSFER.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,28 +1,24 @@ package org.jgroups.protocols.pbcast; import org.jgroups.*; -import org.jgroups.annotations.DeprecatedProperty; -import org.jgroups.annotations.GuardedBy; -import org.jgroups.annotations.MBean; -import org.jgroups.annotations.ManagedAttribute; -import org.jgroups.annotations.Property; +import org.jgroups.annotations.*; import org.jgroups.conf.PropertyConverters; import org.jgroups.stack.IpAddress; import org.jgroups.stack.Protocol; import org.jgroups.stack.StateTransferInfo; +import org.jgroups.util.Digest; import org.jgroups.util.ShutdownRejectedExecutionHandler; -import org.jgroups.util.Streamable; import org.jgroups.util.Util; -import org.jgroups.util.Digest; import java.io.*; -import java.net.*; -import java.util.*; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -30,10 +26,9 @@ /** * STREAMING_STATE_TRANSFER, as its name implies, allows a * streaming state transfer between two channel instances. - * *

            * - * Major advantage of this approach is that transfering application state to a + * Major advantage of this approach is that transferring application state to a * joining member of a group does not entail loading of the complete application * state into memory. Application state, for example, might be located entirely * on some form of disk based storage. The default STATE_TRANSFER @@ -42,12 +37,19 @@ * does not. Thus STREAMING_STATE_TRANSFER protocol is able to * transfer application state that is very large (>1Gb) without a likelihood of * such transfer resulting in OutOfMemoryException. + *

            * + * STREAMING_STATE_TRANSFER allows use of either default channel + * transport or separate tcp sockets for state transfer. If firewalls are not a + * concern then separate tcp sockets should be used as they offer faster state + * transfer. Transport for state transfer is selected using + * use_default_transport boolean property. *

            + * * * Channel instance can be configured with either - * STREAMING_STATE_TRANSFER or STATE_TRANSFER but - * not both protocols at the same time. + * STREAMING_STATE_TRANSFER or STATE_TRANSFER but not + * both protocols at the same time. * *

            * @@ -64,62 +66,83 @@ * @see org.jgroups.StreamingSetStateEvent * @see org.jgroups.protocols.pbcast.STATE_TRANSFER * @since 2.4 - * - * @version $Id$ - * */ -@MBean(description="State trasnfer protocol based on streaming state transfer") -@DeprecatedProperty(names= { "use_flush", "flush_timeout", "use_reading_thread" }) +@MBean(description = "State trasnfer protocol based on streaming state transfer") +@DeprecatedProperty(names = {"use_flush", "flush_timeout", "use_reading_thread"}) public class STREAMING_STATE_TRANSFER extends Protocol { - private final static String NAME="STREAMING_STATE_TRANSFER"; - - /* ----------------------------- Properties ------------------------------------ */ + /* + * ----------------------------------------------Properties ----------------------------------- + * + */ - @Property(converter=PropertyConverters.BindAddress.class,description="The interface (NIC) used to accept state requests") + @LocalAddress + @Property(description = "The interface (NIC) used to accept state requests. " + + "The following special values are also recognized: GLOBAL, SITE_LOCAL, LINK_LOCAL and NON_LOOPBACK", + systemProperty={Global.BIND_ADDR, Global.BIND_ADDR_OLD}, + defaultValueIPv4=Global.NON_LOOPBACK_ADDRESS, defaultValueIPv6=Global.NON_LOOPBACK_ADDRESS) private InetAddress bind_addr; + + @Property(name="bind_interface", converter=PropertyConverters.BindInterface.class, + description="The interface (NIC) which should be used by this transport", dependsUpon="bind_addr") + protected String bind_interface_str=null; + + @Property(description = "The port listening for state requests. Default value of 0 binds to any (ephemeral) port") + private int bind_port = 0; - @Property(description="The port listening for state requests. Default value of 0 binds to any (ephemeral) port") - private int bind_port=0; - - @Property(description="Maximum number of pool threads serving state requests. Default is 5") - private int max_pool=5; - - @Property(description="Keep alive for pool threads serving state requests. Default is 20000 msec") - private long pool_thread_keep_alive = 20*1000; + @Property(description = "Maximum number of pool threads serving state requests. Default is 5") + private int max_pool = 5; - @Property(description="Buffer size for state transfer sockets. Default is 8 KB") - private int socket_buffer_size=8 * 1024; + @Property(description = "Keep alive for pool threads serving state requests. Default is 20000 msec") + private long pool_thread_keep_alive = 20 * 1000; + @Property(description = "Buffer size for state transfer. Default is 8192 bytes") + private int socket_buffer_size = 8 * 1024; - /* --------------------------------------------- JMX statistics ------------------------------------------------------ */ + @Property(description = "If default transport is used the total state buffer size before state producer is blocked. Default is 81920 bytes") + private int buffer_queue_size = 81920; + + @Property(description = "If true default transport is used for state transfer rather than seperate TCP sockets. Default is false") + boolean use_default_transport = false; - private final AtomicInteger num_state_reqs=new AtomicInteger(0); + /* + * --------------------------------------------- JMX statistics ------------------------------- + */ - private final AtomicLong num_bytes_sent=new AtomicLong(0); + private final AtomicInteger num_state_reqs = new AtomicInteger(0); - private volatile double avg_state_size=0; + private final AtomicLong num_bytes_sent = new AtomicLong(0); + private volatile double avg_state_size = 0; - /* --------------------------------------------- Fields ------------------------------------------------------ */ + /* + * --------------------------------------------- Fields --------------------------------------- + */ - private Address local_addr=null; + private Address local_addr = null; @GuardedBy("members") - private final Vector

            members=new Vector
            (); + private final Vector
            members; /* - * Runnable that listens for state requests and spawns threads to serve those requests + * BlockingQueue to accept state transfer Message(s) if default transport + * is used. Only state recipient uses this queue */ - private StateProviderThreadSpawner spawner; + private BlockingQueue stateQueue; - private volatile boolean flushProtocolInStack=false; - + /* + * Runnable that listens for state requests and spawns threads to serve + * those requests if socket transport is used + */ + private StateProviderThreadSpawner spawner; - public STREAMING_STATE_TRANSFER() {} + /* + * Set to true if FLUSH protocol is detected in protocol stack + */ + private AtomicBoolean flushProtocolInStack = new AtomicBoolean(false); - public final String getName() { - return NAME; + public STREAMING_STATE_TRANSFER() { + members = new Vector
            (); } @ManagedAttribute @@ -138,9 +161,9 @@ } public Vector requiredDownServices() { - Vector retval=new Vector(); + Vector retval = new Vector(); retval.addElement(new Integer(Event.GET_DIGEST)); - retval.addElement(new Integer(Event.SET_DIGEST)); + retval.addElement(new Integer(Event.OVERWRITE_DIGEST)); return retval; } @@ -148,41 +171,71 @@ super.resetStats(); num_state_reqs.set(0); num_bytes_sent.set(0); - avg_state_size=0; + avg_state_size = 0; } - public void init() throws Exception {} + public void init() throws Exception { + } public void start() throws Exception { - Map map=new HashMap(); + Map map = new HashMap(); map.put("state_transfer", Boolean.TRUE); map.put("protocol_class", getClass().getName()); up_prot.up(new Event(Event.CONFIG, map)); + + if (use_default_transport) { + int size = buffer_queue_size / socket_buffer_size; + // idiot proof it + if (size <= 1) { + size = 10; + } + if (log.isDebugEnabled()) { + log.debug("buffer_queue_size=" + buffer_queue_size + ", socket_buffer_size=" + + socket_buffer_size + ", creating queue of size " + size); + } + stateQueue = new ArrayBlockingQueue(size); + } } public void stop() { super.stop(); - if(spawner != null) { + if (spawner != null) { spawner.stop(); } } public Object up(Event evt) { - switch(evt.getType()) { + switch (evt.getType()) { - case Event.MSG: - Message msg=(Message)evt.getArg(); - StateHeader hdr=(StateHeader)msg.getHeader(getName()); - if(hdr != null) { - switch(hdr.type) { - case StateHeader.STATE_REQ: + case Event.MSG : + Message msg = (Message) evt.getArg(); + StateHeader hdr = (StateHeader) msg.getHeader(this.id); + if (hdr != null) { + switch (hdr.type) { + case StateHeader.STATE_REQ : handleStateReq(hdr); break; - case StateHeader.STATE_RSP: - handleStateRsp(hdr); + case StateHeader.STATE_RSP : + // fix for https://jira.jboss.org/jira/browse/JGRP-1013 + if(isDigestNeeded()) + down_prot.down(new Event(Event.CLOSE_BARRIER)); + try { + handleStateRsp(hdr); + } + finally { + if(isDigestNeeded()) + down_prot.down(new Event(Event.OPEN_BARRIER)); + } break; - default: - if(log.isErrorEnabled()) + case StateHeader.STATE_PART : + try { + stateQueue.put(msg); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + break; + default : + if (log.isErrorEnabled()) log.error("type " + hdr.type + " not known in StateHeader"); break; } @@ -190,24 +243,20 @@ } break; - case Event.SET_LOCAL_ADDRESS: - local_addr=(Address)evt.getArg(); - break; - - case Event.TMP_VIEW: - case Event.VIEW_CHANGE: - handleViewChange((View)evt.getArg()); + case Event.TMP_VIEW : + case Event.VIEW_CHANGE : + handleViewChange((View) evt.getArg()); break; - case Event.CONFIG: - Map config=(Map)evt.getArg(); - if(bind_addr == null && (config != null && config.containsKey("bind_addr"))) { - bind_addr=(InetAddress)config.get("bind_addr"); - if(log.isDebugEnabled()) + case Event.CONFIG : + Map config = (Map) evt.getArg(); + if (bind_addr == null && (config != null && config.containsKey("bind_addr"))) { + bind_addr = (InetAddress) config.get("bind_addr"); + if (log.isDebugEnabled()) log.debug("using bind_addr from CONFIG event " + bind_addr); } - if(config != null && config.containsKey("state_transfer")) { - log.error("Protocol stack cannot contain two state transfer protocols. Remove either one of them"); + if (config != null && config.containsKey("state_transfer")) { + log.error("Protocol stack must have only one state transfer protocol"); } break; } @@ -216,138 +265,140 @@ public Object down(Event evt) { - switch(evt.getType()) { + switch (evt.getType()) { - case Event.TMP_VIEW: - case Event.VIEW_CHANGE: - handleViewChange((View)evt.getArg()); + case Event.TMP_VIEW : + case Event.VIEW_CHANGE : + handleViewChange((View) evt.getArg()); break; - case Event.GET_STATE: - StateTransferInfo info=(StateTransferInfo)evt.getArg(); + case Event.GET_STATE : + StateTransferInfo info = (StateTransferInfo) evt.getArg(); Address target; - if(info.target == null) { - target=determineCoordinator(); - } - else { - target=info.target; - if(target.equals(local_addr)) { - if(log.isErrorEnabled()) + if (info.target == null) { + target = determineCoordinator(); + } else { + target = info.target; + if (target.equals(local_addr)) { + if (log.isErrorEnabled()) log.error("GET_STATE: cannot fetch state from myself !"); - target=null; + target = null; } } - if(target == null) { - if(log.isDebugEnabled()) + if (target == null) { + if (log.isDebugEnabled()) log.debug("GET_STATE: first member (no state)"); up_prot.up(new Event(Event.GET_STATE_OK, new StateTransferInfo())); - } - else { - Message state_req=new Message(target, null, null); - state_req.putHeader(getName(), new StateHeader(StateHeader.STATE_REQ, - local_addr, - info.state_id)); - if(log.isDebugEnabled()) + } else { + Message state_req = new Message(target, null, null); + state_req.putHeader(this.id, new StateHeader(StateHeader.STATE_REQ, + local_addr, info.state_id)); + if (log.isDebugEnabled()) log.debug("GET_STATE: asking " + target - + " for state, passing down a SUSPEND_STABLE event, timeout=" - + info.timeout); + + " for state, passing down a SUSPEND_STABLE event, timeout=" + + info.timeout); down_prot.down(new Event(Event.SUSPEND_STABLE, new Long(info.timeout))); down_prot.down(new Event(Event.MSG, state_req)); } return null; // don't pass down any further ! - case Event.STATE_TRANSFER_INPUTSTREAM_CLOSED: - if(log.isDebugEnabled()) - log.debug("STATE_TRANSFER_INPUTSTREAM_CLOSED received,passing down a RESUME_STABLE event"); + case Event.STATE_TRANSFER_INPUTSTREAM_CLOSED : + if (log.isDebugEnabled()) + log.debug("STATE_TRANSFER_INPUTSTREAM_CLOSED received"); down_prot.down(new Event(Event.RESUME_STABLE)); return null; - case Event.CONFIG: - Map config=(Map)evt.getArg(); - if(config != null && config.containsKey("flush_supported")) { - flushProtocolInStack=true; + case Event.CONFIG : + Map config = (Map) evt.getArg(); + if (config != null && config.containsKey("flush_supported")) { + flushProtocolInStack.set(true); } break; + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; } return down_prot.down(evt); // pass on to the layer below us } /* - * --------------------------- Private Methods - * -------------------------------- + * --------------------------- Private Methods ------------------------------------------------ */ /** * When FLUSH is used we do not need to pass digests between members - * + * * see JGroups/doc/design/PArtialStateTransfer.txt see * JGroups/doc/design/FLUSH.txt - * + * * @return true if use of digests is required, false otherwise */ private boolean isDigestNeeded() { - return !flushProtocolInStack; + return !flushProtocolInStack.get(); } private void respondToStateRequester(String id, Address stateRequester, boolean open_barrier) { - // setup the plumbing if needed - if(spawner == null) { - spawner=new StateProviderThreadSpawner(setupThreadPool(), Util.createServerSocket(bind_addr, bind_port)); - Thread t=getThreadFactory().newThread(spawner, - "STREAMING_STATE_TRANSFER server socket acceptor"); + // setup socket plumbing if needed + if (spawner == null && !use_default_transport) { + spawner = new StateProviderThreadSpawner(setupThreadPool(), + Util.createServerSocket(getSocketFactory(), + Global.STREAMING_STATE_TRANSFER_SERVER_SOCK, + bind_addr, bind_port)); + Thread t = getThreadFactory().newThread(spawner, "STREAMING_STATE_TRANSFER server socket acceptor"); t.start(); } - Digest digest=isDigestNeeded()? (Digest)down_prot.down(Event.GET_DIGEST_EVT) : null; - - Message state_rsp=new Message(stateRequester); - StateHeader hdr=new StateHeader(StateHeader.STATE_RSP, - local_addr, - spawner.getServerSocketAddress(), - digest, - id); - state_rsp.putHeader(getName(), hdr); - - if(log.isDebugEnabled()) - log.debug("Responding to state requester " + state_rsp.getDest() - + " with address " - + spawner.getServerSocketAddress() - + " and digest " - + digest); + Digest digest = isDigestNeeded() ? (Digest) down_prot.down(Event.GET_DIGEST_EVT) : null; + + Message state_rsp = new Message(stateRequester); + StateHeader hdr = new StateHeader(StateHeader.STATE_RSP, local_addr, use_default_transport + ? null + : spawner.getServerSocketAddress(), digest, id); + state_rsp.putHeader(this.id, hdr); + + if (log.isDebugEnabled()) + log.debug("Responding to state requester " + state_rsp.getDest() + " with address " + + (use_default_transport ? null : spawner.getServerSocketAddress()) + + " and digest " + digest); down_prot.down(new Event(Event.MSG, state_rsp)); - if(stats) { + if (stats) { num_state_reqs.incrementAndGet(); } - if(open_barrier) + if (open_barrier) down_prot.down(new Event(Event.OPEN_BARRIER)); + + if (use_default_transport) { + openAndProvideOutputStreamToStateProvider(stateRequester, id); + } } private ThreadPoolExecutor setupThreadPool() { - ThreadPoolExecutor threadPool=new ThreadPoolExecutor(0, - max_pool, - pool_thread_keep_alive, - TimeUnit.MILLISECONDS, - new SynchronousQueue()); + ThreadPoolExecutor threadPool = new ThreadPoolExecutor(0, max_pool, pool_thread_keep_alive, + TimeUnit.MILLISECONDS, new SynchronousQueue()); - ThreadFactory factory=new ThreadFactory() { + ThreadFactory factory = new ThreadFactory() { + private final AtomicInteger threadNumber = new AtomicInteger(1); + public Thread newThread(final Runnable command) { - return getThreadFactory().newThread(command, "STREAMING_STATE_TRANSFER sender"); + return getThreadFactory().newThread(command, "STREAMING_STATE_TRANSFER-sender-" + + threadNumber.getAndIncrement()); } }; - threadPool.setRejectedExecutionHandler(new ShutdownRejectedExecutionHandler(threadPool.getRejectedExecutionHandler())); + threadPool.setRejectedExecutionHandler(new ShutdownRejectedExecutionHandler(threadPool + .getRejectedExecutionHandler())); threadPool.setThreadFactory(factory); return threadPool; } private Address determineCoordinator() { - synchronized(members) { - for(Address member:members) { - if(!local_addr.equals(member)) { + synchronized (members) { + for (Address member : members) { + if (!local_addr.equals(member)) { return member; } } @@ -356,109 +407,141 @@ } private void handleViewChange(View v) { - Vector
            new_members=v.getMembers(); - synchronized(members) { + Vector
            new_members = v.getMembers(); + synchronized (members) { members.clear(); members.addAll(new_members); } } private void handleStateReq(StateHeader hdr) { - Address sender=hdr.sender; - String id=hdr.state_id; - if(sender == null) { - if(log.isErrorEnabled()) + Address sender = hdr.sender; + String id = hdr.state_id; + if (sender == null) { + if (log.isErrorEnabled()) log.error("sender is null !"); return; } - if(isDigestNeeded()) { - down_prot.down(new Event(Event.CLOSE_BARRIER)); - // drain (and block) incoming msgs until after state has been returned + if (isDigestNeeded()) { + down_prot.down(new Event(Event.CLOSE_BARRIER)); + // drain (and block) incoming msgs until after state has been + // returned } try { respondToStateRequester(id, sender, isDigestNeeded()); - } - catch(Throwable t) { - if(log.isErrorEnabled()) + } catch (Throwable t) { + if (log.isErrorEnabled()) log.error("failed fetching state from application", t); - if(isDigestNeeded()) + if (isDigestNeeded()) down_prot.down(new Event(Event.OPEN_BARRIER)); } } - void handleStateRsp(StateHeader hdr) { - Digest tmp_digest=hdr.my_digest; - if(isDigestNeeded()) { - if(tmp_digest == null) { - if(log.isWarnEnabled()) + void handleStateRsp(final StateHeader hdr) { + Digest tmp_digest = hdr.my_digest; + if (isDigestNeeded()) { + if (tmp_digest == null) { + if (log.isWarnEnabled()) log.warn("digest received from " + hdr.sender - + " is null, skipping setting digest !"); + + " is null, skipping setting digest !"); + } else { + down_prot.down(new Event(Event.OVERWRITE_DIGEST, tmp_digest)); } - else { - down_prot.down(new Event(Event.SET_DIGEST, tmp_digest)); + } + if (use_default_transport) { + // have to use another thread to read state while state recipient + // has to accept state messages from state provider + Thread t = getThreadFactory().newThread(new Runnable() { + public void run() { + openAndProvideInputStreamToStateReceiver(hdr.sender, hdr.getStateId()); + } + }, "STREAMING_STATE_TRANSFER state reader"); + t.start(); + } else { + connectToStateProvider(hdr); + } + } + + private void openAndProvideInputStreamToStateReceiver(Address stateProvider, String state_id) { + BufferedInputStream bis = null; + try { + bis = new BufferedInputStream(new StateInputStream(), socket_buffer_size); + up_prot.up(new Event(Event.STATE_TRANSFER_INPUTSTREAM, new StateTransferInfo(stateProvider, bis, state_id))); + } catch (IOException e) { + // pass null stream up so that JChannel.getState() returns false + log.error("Could not provide state recipient with appropriate stream", e); + InputStream is = null; + up_prot.up(new Event(Event.STATE_TRANSFER_INPUTSTREAM, new StateTransferInfo(stateProvider, is, state_id))); + } finally { + Util.close(bis); + } + } + + private void openAndProvideOutputStreamToStateProvider(Address stateRequester, String state_id) { + BufferedOutputStream bos = null; + try { + bos = new BufferedOutputStream(new StateOutputStream(stateRequester, state_id),socket_buffer_size); + up_prot.up(new Event(Event.STATE_TRANSFER_OUTPUTSTREAM, new StateTransferInfo(stateRequester, bos, state_id))); + } catch (IOException e) { + if (log.isWarnEnabled()) { + log.warn("StateOutputStream could not be given to application", e); } + } finally { + Util.close(bos); } - connectToStateProvider(hdr); } private void connectToStateProvider(StateHeader hdr) { - IpAddress address=hdr.bind_addr; - String tmp_state_id=hdr.getStateId(); - StreamingInputStreamWrapper wrapper=null; - StateTransferInfo sti=null; - Socket socket=new Socket(); + IpAddress address = hdr.bind_addr; + String tmp_state_id = hdr.getStateId(); + InputStream bis = null; + StateTransferInfo sti = null; + Socket socket = new Socket(); try { socket.bind(new InetSocketAddress(bind_addr, 0)); - int bufferSize=socket.getReceiveBufferSize(); + int bufferSize = socket.getReceiveBufferSize(); socket.setReceiveBufferSize(socket_buffer_size); - if(log.isDebugEnabled()) - log.debug("Connecting to state provider " + address.getIpAddress() - + ":" - + address.getPort() - + ", original buffer size was " - + bufferSize - + " and was reset to " - + socket.getReceiveBufferSize()); - socket.connect(new InetSocketAddress(address.getIpAddress(), address.getPort())); - if(log.isDebugEnabled()) - log.debug("Connected to state provider, my end of the socket is " + socket.getLocalAddress() - + ":" - + socket.getLocalPort() - + " passing inputstream up..."); + if (log.isDebugEnabled()) + log.debug("Connecting to state provider " + address.getIpAddress() + ":" + + address.getPort() + ", original buffer size was " + bufferSize + + " and was reset to " + socket.getReceiveBufferSize()); + + Util.connect(socket, new InetSocketAddress(address.getIpAddress(), address.getPort()), 0); + if (log.isDebugEnabled()) + log.debug("Connected to state provider, my end of the socket is " + + socket.getLocalAddress() + ":" + socket.getLocalPort() + + " passing inputstream up..."); // write out our state_id and address - ObjectOutputStream out=new ObjectOutputStream(socket.getOutputStream()); + ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); out.writeObject(tmp_state_id); out.writeObject(local_addr); - wrapper=new StreamingInputStreamWrapper(socket); - sti=new StateTransferInfo(hdr.sender, wrapper, tmp_state_id); + bis = new BufferedInputStream(new StreamingInputStreamWrapper(socket),socket_buffer_size); + sti = new StateTransferInfo(hdr.sender, bis, tmp_state_id); up_prot.up(new Event(Event.STATE_TRANSFER_INPUTSTREAM, sti)); - } - catch(IOException e) { - if(log.isWarnEnabled()) { + } catch (IOException e) { + if (log.isWarnEnabled()) { log.warn("State reader socket thread spawned abnormaly", e); } // pass null stream up so that JChannel.getState() returns false - InputStream is=null; - sti=new StateTransferInfo(hdr.sender, is, tmp_state_id); + InputStream is = null; + sti = new StateTransferInfo(hdr.sender, is, tmp_state_id); up_prot.up(new Event(Event.STATE_TRANSFER_INPUTSTREAM, sti)); - } - finally { - if(!socket.isConnected()) { - if(log.isWarnEnabled()) + } finally { + if (!socket.isConnected()) { + if (log.isWarnEnabled()) log.warn("Could not connect to state provider. Closing socket..."); } - Util.close(wrapper); + Util.close(bis); Util.close(socket); } } /* - * ------------------------ End of Private Methods - * ------------------------------ + * ------------------------ End of Private Methods -------------------------------------------- */ private class StateProviderThreadSpawner implements Runnable { @@ -470,42 +553,39 @@ Thread runner; - private volatile boolean running=true; + private volatile boolean running = true; - public StateProviderThreadSpawner(ExecutorService pool,ServerSocket stateServingSocket) { + public StateProviderThreadSpawner(ExecutorService pool, ServerSocket stateServingSocket) { super(); - this.pool=pool; - this.serverSocket=stateServingSocket; - this.address=new IpAddress(STREAMING_STATE_TRANSFER.this.bind_addr, - serverSocket.getLocalPort()); + this.pool = pool; + this.serverSocket = stateServingSocket; + this.address = new IpAddress(STREAMING_STATE_TRANSFER.this.bind_addr, serverSocket.getLocalPort()); } public void run() { - runner=Thread.currentThread(); - for(;running;) { + runner = Thread.currentThread(); + for (; running;) { try { - if(log.isDebugEnabled()) - log.debug("StateProviderThreadSpawner listening at " + getServerSocketAddress() - + "..."); + if (log.isDebugEnabled()) + log.debug("StateProviderThreadSpawner listening at " + + getServerSocketAddress() + "..."); - final Socket socket=serverSocket.accept(); + final Socket socket = serverSocket.accept(); pool.execute(new Runnable() { public void run() { - if(log.isDebugEnabled()) - log.debug("Accepted request for state transfer from " + socket.getInetAddress() - + ":" - + socket.getPort() - + " handing of to PooledExecutor thread"); + if (log.isDebugEnabled()) + log.debug("Accepted request for state transfer from " + + socket.getInetAddress() + ":" + socket.getPort() + + " handing of to PooledExecutor thread"); new StateProviderHandler().process(socket); } }); - } - catch(IOException e) { - if(log.isWarnEnabled()) { + } catch (IOException e) { + if (log.isWarnEnabled()) { // we get this exception when we close server socket // exclude that case - if(serverSocket != null && !serverSocket.isClosed()) { + if (serverSocket != null && !serverSocket.isClosed()) { log.warn("Spawning socket from server socket finished abnormaly", e); } } @@ -518,242 +598,326 @@ } public void stop() { - running=false; + running = false; try { - serverSocket.close(); - } - catch(Exception ignored) {} - finally { - if(log.isDebugEnabled()) + getSocketFactory().close(serverSocket); + } catch (Exception ignored) { + } finally { + if (log.isDebugEnabled()) log.debug("Waiting for StateProviderThreadSpawner to die ... "); - if(runner != null) { + if (runner != null) { try { runner.join(Global.THREAD_SHUTDOWN_WAIT_TIME); - } - catch(InterruptedException ignored) { + } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); } } - if(log.isDebugEnabled()) + if (log.isDebugEnabled()) log.debug("Shutting the thread pool down... "); pool.shutdownNow(); try { pool.awaitTermination(Global.THREADPOOL_SHUTDOWN_WAIT_TIME, - TimeUnit.MILLISECONDS); - } - catch(InterruptedException ignored) { + TimeUnit.MILLISECONDS); + } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); } } - if(log.isDebugEnabled()) + if (log.isDebugEnabled()) log.debug("Thread pool is shutdown. All pool threads are cleaned up."); } } private class StateProviderHandler { public void process(Socket socket) { - StreamingOutputStreamWrapper wrapper=null; - ObjectInputStream ois=null; + OutputStream bos = null; + ObjectInputStream ois = null; try { - int bufferSize=socket.getSendBufferSize(); + int bufferSize = socket.getSendBufferSize(); socket.setSendBufferSize(socket_buffer_size); - if(log.isDebugEnabled()) + if (log.isDebugEnabled()) log.debug("Running on " + Thread.currentThread() - + ". Accepted request for state transfer from " - + socket.getInetAddress() - + ":" - + socket.getPort() - + ", original buffer size was " - + bufferSize - + " and was reset to " - + socket.getSendBufferSize() - + ", passing outputstream up... "); - - ois=new ObjectInputStream(socket.getInputStream()); - String state_id=(String)ois.readObject(); - Address stateRequester=(Address)ois.readObject(); - wrapper=new StreamingOutputStreamWrapper(socket); - StateTransferInfo sti=new StateTransferInfo(stateRequester, wrapper, state_id); - up_prot.up(new Event(Event.STATE_TRANSFER_OUTPUTSTREAM, sti)); - } - catch(IOException e) { - if(log.isWarnEnabled()) { + + ". Accepted request for state transfer from " + + socket.getInetAddress() + ":" + socket.getPort() + + ", original buffer size was " + bufferSize + " and was reset to " + + socket.getSendBufferSize() + ", passing outputstream up... "); + + ois = new ObjectInputStream(socket.getInputStream()); + String state_id = (String) ois.readObject(); + Address stateRequester = (Address) ois.readObject(); + bos = new BufferedOutputStream(new StreamingOutputStreamWrapper(socket),socket_buffer_size); + up_prot.up(new Event(Event.STATE_TRANSFER_OUTPUTSTREAM, new StateTransferInfo(stateRequester, bos, state_id))); + } catch (IOException e) { + if (log.isWarnEnabled()) { log.warn("State writer socket thread spawned abnormaly", e); } - } - catch(ClassNotFoundException e) { + } catch (ClassNotFoundException e) { // thrown by ois.readObject() // should never happen since String/Address are core classes - } - finally { - if(!socket.isConnected()) { - if(log.isWarnEnabled()) + } finally { + if (!socket.isConnected()) { + if (log.isWarnEnabled()) log.warn("Could not receive connection from state receiver. Closing socket..."); } - Util.close(wrapper); + Util.close(bos); Util.close(socket); } } } - private class StreamingInputStreamWrapper extends InputStream { - - private final InputStream delegate; + private class StreamingInputStreamWrapper extends FilterInputStream { private final Socket inputStreamOwner; - private final AtomicBoolean closed=new AtomicBoolean(false); + private final AtomicBoolean closed = new AtomicBoolean(false); public StreamingInputStreamWrapper(Socket inputStreamOwner) throws IOException { - super(); - this.inputStreamOwner=inputStreamOwner; - this.delegate=new BufferedInputStream(inputStreamOwner.getInputStream()); - } - - public int available() throws IOException { - return delegate.available(); + super(inputStreamOwner.getInputStream()); + this.inputStreamOwner = inputStreamOwner; } public void close() throws IOException { - if(closed.compareAndSet(false, true)) { - if(log.isDebugEnabled()) { + if (closed.compareAndSet(false, true)) { + if (log.isDebugEnabled()) { log.debug("State reader is closing the socket "); } - Util.close(delegate); Util.close(inputStreamOwner); - up_prot.up(new Event(Event.STATE_TRANSFER_INPUTSTREAM_CLOSED)); + up(new Event(Event.STATE_TRANSFER_INPUTSTREAM_CLOSED)); down(new Event(Event.STATE_TRANSFER_INPUTSTREAM_CLOSED)); } + super.close(); } + } + + private class StreamingOutputStreamWrapper extends FilterOutputStream { + private final Socket outputStreamOwner; - public synchronized void mark(int readlimit) { - delegate.mark(readlimit); + private final AtomicBoolean closed = new AtomicBoolean(false); + + private long bytesWrittenCounter = 0; + + public StreamingOutputStreamWrapper(Socket outputStreamOwner) throws IOException { + super(outputStreamOwner.getOutputStream()); + this.outputStreamOwner = outputStreamOwner; } - public boolean markSupported() { - return delegate.markSupported(); + public void close() throws IOException { + if (closed.compareAndSet(false, true)) { + if (log.isDebugEnabled()) { + log.debug("State writer is closing the socket "); + } + Util.close(outputStreamOwner); + up(new Event(Event.STATE_TRANSFER_OUTPUTSTREAM_CLOSED)); + down(new Event(Event.STATE_TRANSFER_OUTPUTSTREAM_CLOSED)); + + if (stats) { + avg_state_size = num_bytes_sent.addAndGet(bytesWrittenCounter) + / num_state_reqs.doubleValue(); + } + super.close(); + } } - public int read() throws IOException { - return delegate.read(); + public void write(byte[] b, int off, int len) throws IOException { + //Thanks Kornelius Elstner + //https://jira.jboss.org/browse/JGRP-1223 + out.write(b, off, len); + bytesWrittenCounter += len; } - public int read(byte[] b, int off, int len) throws IOException { - return delegate.read(b, off, len); + public void write(byte[] b) throws IOException { + super.write(b); + bytesWrittenCounter += b.length; } - public int read(byte[] b) throws IOException { - return delegate.read(b); + public void write(int b) throws IOException { + super.write(b); + bytesWrittenCounter += 1; } + } + + private class StateInputStream extends InputStream { - public synchronized void reset() throws IOException { - delegate.reset(); + private final AtomicBoolean closed; + + public StateInputStream() throws IOException { + super(); + this.closed = new AtomicBoolean(false); } - public long skip(long n) throws IOException { - return delegate.skip(n); + public void close() throws IOException { + if (closed.compareAndSet(false, true)) { + if (log.isDebugEnabled()) { + log.debug("State reader is closing the stream"); + } + stateQueue.clear(); + up(new Event(Event.STATE_TRANSFER_INPUTSTREAM_CLOSED)); + down(new Event(Event.STATE_TRANSFER_INPUTSTREAM_CLOSED)); + super.close(); + } } - } - private class StreamingOutputStreamWrapper extends OutputStream { - private final Socket outputStreamOwner; + public int read() throws IOException { + if (closed.get()) + return -1; + final byte[] array = new byte[1]; + return read(array) == -1 ? -1 : array[0]; + } - private final OutputStream delegate; + public int read(byte[] b, int off, int len) throws IOException { + if (closed.get()) + return -1; + Message m = null; + try { + m = stateQueue.take(); + StateHeader hdr = (StateHeader) m.getHeader(id); + if (hdr.type == StateHeader.STATE_PART) { + return readAndTransferPayload(m, b, off, len); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new InterruptedIOException(); + } + return -1; + } + + private int readAndTransferPayload(Message m, byte[] b, int off, int len) { + byte[] buffer = m.getBuffer(); + if (log.isDebugEnabled()) { + log.debug(local_addr + " reading chunk of state " + "byte[] b=" + b.length + + ", off=" + off + ", buffer.length=" + buffer.length); + } + System.arraycopy(buffer, 0, b, off, buffer.length); + return buffer.length; + } - private final AtomicBoolean closed=new AtomicBoolean(false); + public int read(byte[] b) throws IOException { + if (closed.get()) + return -1; + return read(b, 0, b.length); + } + } - private long bytesWrittenCounter=0; + private class StateOutputStream extends OutputStream { - public StreamingOutputStreamWrapper(Socket outputStreamOwner) throws IOException { + private final Address stateRequester; + private final String state_id; + private final AtomicBoolean closed; + private long bytesWrittenCounter = 0; + + public StateOutputStream(Address stateRequester, String state_id) throws IOException { super(); - this.outputStreamOwner=outputStreamOwner; - this.delegate=new BufferedOutputStream(outputStreamOwner.getOutputStream()); + this.stateRequester = stateRequester; + this.state_id = state_id; + this.closed = new AtomicBoolean(false); } public void close() throws IOException { - if(closed.compareAndSet(false, true)) { - if(log.isDebugEnabled()) { - log.debug("State writer is closing the socket "); + if (closed.compareAndSet(false, true)) { + if (log.isDebugEnabled()) { + log.debug("State writer " + local_addr + + " is closing the output stream for state_id " + state_id); } - - Util.close(delegate); - Util.close(outputStreamOwner); - up_prot.up(new Event(Event.STATE_TRANSFER_OUTPUTSTREAM_CLOSED)); + up(new Event(Event.STATE_TRANSFER_OUTPUTSTREAM_CLOSED)); down(new Event(Event.STATE_TRANSFER_OUTPUTSTREAM_CLOSED)); - - if(stats) { - avg_state_size=num_bytes_sent.addAndGet(bytesWrittenCounter) / num_state_reqs.doubleValue(); + if (stats) { + avg_state_size = num_bytes_sent.addAndGet(bytesWrittenCounter) + / num_state_reqs.doubleValue(); } + super.close(); } } - public void flush() throws IOException { - delegate.flush(); - } - public void write(byte[] b, int off, int len) throws IOException { - delegate.write(b, off, len); - bytesWrittenCounter+=len; + if (closed.get()) + throw closed(); + sendMessage(b, off, len); } public void write(byte[] b) throws IOException { - delegate.write(b); - if(b != null) { - bytesWrittenCounter+=b.length; - } + if (closed.get()) + throw closed(); + sendMessage(b, 0, b.length); } public void write(int b) throws IOException { - delegate.write(b); - bytesWrittenCounter+=1; + if (closed.get()) + throw closed(); + byte buf[] = new byte[]{(byte) b}; + write(buf); + } + + private void sendMessage(byte[] b, int off, int len) throws IOException { + Message m = new Message(stateRequester); + m.putHeader(id, new StateHeader(StateHeader.STATE_PART, local_addr, state_id)); + m.setBuffer(b, off, len); + bytesWrittenCounter += (len - off); + if (Thread.interrupted()) { + throw interrupted((int) bytesWrittenCounter); + } + down_prot.down(new Event(Event.MSG, m)); + if (log.isDebugEnabled()) { + log.debug(local_addr + " sent chunk of state to " + stateRequester + "byte[] b=" + + b.length + ", off=" + off + ", len=" + len); + } + } + + private IOException closed() { + return new IOException("The output stream is closed"); + } + + private InterruptedIOException interrupted(int cnt) { + final InterruptedIOException ex = new InterruptedIOException(); + ex.bytesTransferred = cnt; + return ex; } } - public static class StateHeader extends Header implements Streamable { - public static final byte STATE_REQ=1; + public static class StateHeader extends Header { + public static final byte STATE_REQ = 1; - public static final byte STATE_RSP=2; + public static final byte STATE_RSP = 2; - long id=0; // state transfer ID (to separate multiple state transfers at the same time) - - byte type=0; + public static final byte STATE_PART = 3; + + long id = 0; // state transfer ID (to separate multiple state transfers + // at the same time) + + byte type = 0; Address sender; // sender of state STATE_REQ or STATE_RSP - Digest my_digest=null; // digest of sender (if type is STATE_RSP) + Digest my_digest = null; // digest of sender (if type is STATE_RSP) - IpAddress bind_addr=null; + IpAddress bind_addr = null; - String state_id=null; // for partial state transfer + String state_id = null; // for partial state transfer - public StateHeader() {} // for externalization + public StateHeader() { + } // for externalization - public StateHeader(byte type,Address sender,String state_id) { - this.type=type; - this.sender=sender; - this.state_id=state_id; + public StateHeader(byte type, Address sender, String state_id) { + this.type = type; + this.sender = sender; + this.state_id = state_id; } - public StateHeader(byte type,Address sender,long id,Digest digest) { - this.type=type; - this.sender=sender; - this.id=id; - this.my_digest=digest; + public StateHeader(byte type, Address sender, long id, Digest digest) { + this.type = type; + this.sender = sender; + this.id = id; + this.my_digest = digest; } - public StateHeader(byte type, - Address sender, - IpAddress bind_addr, - Digest digest, - String state_id) { - this.type=type; - this.sender=sender; - this.my_digest=digest; - this.bind_addr=bind_addr; - this.state_id=state_id; + public StateHeader(byte type, Address sender, IpAddress bind_addr, Digest digest, + String state_id) { + this.type = type; + this.sender = sender; + this.my_digest = digest; + this.bind_addr = bind_addr; + this.state_id = state_id; } public int getType() { @@ -771,60 +935,45 @@ public boolean equals(Object o) { StateHeader other; - if(sender != null && o != null) { - if(!(o instanceof StateHeader)) + if (sender != null && o != null) { + if (!(o instanceof StateHeader)) return false; - other=(StateHeader)o; + other = (StateHeader) o; return sender.equals(other.sender) && id == other.id; } return false; } public int hashCode() { - if(sender != null) - return sender.hashCode() + (int)id; + if (sender != null) + return sender.hashCode() + (int) id; else - return (int)id; + return (int) id; } public String toString() { - StringBuilder sb=new StringBuilder(); + StringBuilder sb = new StringBuilder(); sb.append("type=").append(type2Str(type)); - if(sender != null) + if (sender != null) sb.append(", sender=").append(sender).append(" id=").append(id); - if(my_digest != null) + if (my_digest != null) sb.append(", digest=").append(my_digest); return sb.toString(); } static String type2Str(int t) { - switch(t) { - case STATE_REQ: + switch (t) { + case STATE_REQ : return "STATE_REQ"; - case STATE_RSP: + case STATE_RSP : return "STATE_RSP"; - default: + case STATE_PART : + return "STATE_PART"; + default : return ""; } } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeObject(sender); - out.writeLong(id); - out.writeByte(type); - out.writeObject(my_digest); - out.writeObject(bind_addr); - out.writeUTF(state_id); - } - - public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException { - sender=(Address)in.readObject(); - id=in.readLong(); - type=in.readByte(); - my_digest=(Digest)in.readObject(); - bind_addr=(IpAddress)in.readObject(); - state_id=in.readUTF(); - } public void writeTo(DataOutputStream out) throws IOException { out.writeByte(type); @@ -835,31 +984,30 @@ Util.writeString(state_id, out); } - public void readFrom(DataInputStream in) throws IOException, - IllegalAccessException, - InstantiationException { - type=in.readByte(); - id=in.readLong(); - sender=Util.readAddress(in); - my_digest=(Digest)Util.readStreamable(Digest.class, in); - bind_addr=(IpAddress)Util.readStreamable(IpAddress.class, in); - state_id=Util.readString(in); + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, + InstantiationException { + type = in.readByte(); + id = in.readLong(); + sender = Util.readAddress(in); + my_digest = (Digest) Util.readStreamable(Digest.class, in); + bind_addr = (IpAddress) Util.readStreamable(IpAddress.class, in); + state_id = Util.readString(in); } public int size() { - int retval=Global.LONG_SIZE + Global.BYTE_SIZE; // id and type + int retval = Global.LONG_SIZE + Global.BYTE_SIZE; // id and type - retval+=Util.size(sender); + retval += Util.size(sender); - retval+=Global.BYTE_SIZE; // presence byte for my_digest - if(my_digest != null) - retval+=my_digest.serializedSize(); + retval += Global.BYTE_SIZE; // presence byte for my_digest + if (my_digest != null) + retval += my_digest.serializedSize(); - retval+=Util.size(bind_addr); + retval += Util.size(bind_addr); - retval+=Global.BYTE_SIZE; // presence byte for state_id - if(state_id != null) - retval+=state_id.length() + 2; + retval += Global.BYTE_SIZE; // presence byte for state_id + if (state_id != null) + retval += state_id.length() + 2; return retval; } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/PEER_LOCK.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/PEER_LOCK.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/PEER_LOCK.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/PEER_LOCK.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,113 @@ +package org.jgroups.protocols; + +/** + * @author Bela Ban + */ + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.jgroups.Address; +import org.jgroups.View; +import org.jgroups.annotations.Experimental; +import org.jgroups.blocks.locking.Owner; + +/** + * Implementation of a locking protocol which acquires locks by contacting all of the nodes of a cluster.

            + * Unless a total order configuration is used (e.g. {@link org.jgroups.protocols.SEQUENCER} based), lock requests for + * the same resource from different senders may be received in different order, so deadlocks can occur. Example: + *
            + * - Nodes A and B
            + * - A and B call lock(X) at the same time
            + * - A receives L(X,A) followed by L(X,B): locks X(A), queues L(X,B)
            + * - B receives L(X,B) followed by L(X,A): locks X(B), queues L(X,A)
            + * 
            + * To acquire a lock, we need lock grants from both A and B, but this will never happen here. To fix this, either + * add SEQUENCER to the configuration, so that all lock requests are received in the same global order at both A and B, + * or use {@link java.util.concurrent.locks.Lock#tryLock(long,java.util.concurrent.TimeUnit)} with retries if a lock + * cannot be acquired.

            + * An alternative is also the {@link org.jgroups.protocols.CENTRAL_LOCK} protocol. + * @author Bela Ban + * @since 2.12 + * @see Locking + * @see CENTRAL_LOCK + */ +@Experimental +public class PEER_LOCK extends Locking { + + public PEER_LOCK() { + super(); + } + + + protected void sendGrantLockRequest(String lock_name, Owner owner, long timeout, boolean is_trylock) { + sendRequest(null, Type.GRANT_LOCK, lock_name, owner, timeout, is_trylock); + } + + protected void sendReleaseLockRequest(String lock_name, Owner owner) { + sendRequest(null, Type.RELEASE_LOCK, lock_name, owner, 0, false); + } + + + @Override + protected void sendAwaitConditionRequest(String lock_name, Owner owner) { + sendRequest(null, Type.LOCK_AWAIT, lock_name, owner, 0, false); + } + + + @Override + protected void sendSignalConditionRequest(String lock_name, boolean all) { + sendRequest(null, all ? Type.COND_SIG_ALL : Type.COND_SIG, lock_name, + null, 0, false); + } + + + @Override + protected void sendDeleteAwaitConditionRequest(String lock_name, Owner owner) { + sendRequest(null, Type.DELETE_LOCK_AWAIT, lock_name, owner, 0, false); + } + + + public void handleView(View view) { + super.handleView(view); + List

            members=view.getMembers(); + synchronized(client_locks) { + for(Map map: client_locks.values()) { + for(ClientLock lock: map.values()) + ((PeerLock)lock).retainAll(members); + } + } + } + + protected ClientLock createLock(String lock_name) { + return new PeerLock(lock_name); + } + + /** + * Lock implementation which grants a lock when all non faulty cluster members OK it. + */ + protected class PeerLock extends ClientLock { + protected final List
            grants=new ArrayList
            (view.getMembers()); + + public PeerLock(String name) { + super(name); + } + + protected synchronized void retainAll(List
            members) { + if(grants.isEmpty()) + return; + grants.retainAll(members); + if(grants.isEmpty()) + lockGranted(); + } + + protected synchronized void handleLockGrantedResponse(Owner owner, Address sender) { + if(grants.isEmpty()) + return; + grants.remove(sender); + if(grants.isEmpty()) + lockGranted(); + } + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/PERF_TP.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/PERF_TP.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/PERF_TP.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/PERF_TP.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: PERF_TP.java,v 1.21 2008/10/21 12:10:30 vlada Exp $ package org.jgroups.protocols; @@ -13,7 +12,6 @@ /** * Measures the time for a message to travel from the channel to the transport * @author Bela Ban - * @version $Id: PERF_TP.java,v 1.21 2008/10/21 12:10:30 vlada Exp $ */ @Unsupported public class PERF_TP extends Protocol { @@ -66,12 +64,6 @@ /*------------------------------ Protocol interface ------------------------------ */ - public String getName() { - return "PERF_TP"; - } - - - public void init() throws Exception { local_addr=new org.jgroups.stack.IpAddress("localhost", 10000); // fake address diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/PingData.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/PingData.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/PingData.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/PingData.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,168 @@ + +package org.jgroups.protocols; + +import org.jgroups.*; +import org.jgroups.util.Streamable; +import org.jgroups.util.Util; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; + +/** + * Encapsulates information about a cluster node, e.g. local address, coordinator's address, logical name and + * physical address(es) + * @author Bela Ban + */ +public class PingData implements Streamable { + protected Address own_addr=null; + protected View view=null; // only sent with merge-triggered discovery response (if ViewIds differ) + protected ViewId view_id=null; // only sent with GMS-triggered discovery response + protected boolean is_server=false; + protected String logical_name=null; + protected Collection physical_addrs=null; + + + public PingData() { + } + + public PingData(Address own_addr, View view, boolean is_server) { + this.own_addr=own_addr; + this.view=view; + this.is_server=is_server; + } + + + public PingData(Address own_addr, View view, boolean is_server, + String logical_name, Collection physical_addrs) { + this(own_addr, view, is_server); + this.logical_name=logical_name; + if(physical_addrs != null) + this.physical_addrs=new ArrayList(physical_addrs); + } + + + public PingData(Address own_addr, View view, ViewId view_id, boolean is_server, + String logical_name, Collection physical_addrs) { + this(own_addr, view, is_server, logical_name, physical_addrs); + this.view_id=view_id; + } + + + public boolean isCoord() { + Address coord_addr=getCoordAddress(); + return is_server && own_addr != null && coord_addr != null && own_addr.equals(coord_addr); + } + + public boolean hasCoord(){ + Address coord_addr=getCoordAddress(); + return is_server && own_addr != null && coord_addr != null; + } + + public Address getAddress() { + return own_addr; + } + + public Address getCoordAddress() { + if(view_id != null) + return view_id.getCoordAddress(); + return view != null? view.getVid().getCoordAddress() : null; + } + + public Collection
            getMembers() { + return view != null? view.getMembers() : null; + } + + public View getView() { + return view; + } + + public void setView(View view) { + this.view=view; + } + + public ViewId getViewId() {return view_id;} + + public void setViewId(ViewId view_id) {this.view_id=view_id;} + + public boolean isServer() { + return is_server; + } + + public String getLogicalName() { + return logical_name; + } + + public Collection getPhysicalAddrs() { + return physical_addrs; + } + + public boolean equals(Object obj) { + if(!(obj instanceof PingData)) + return false; + PingData other=(PingData)obj; + return own_addr != null && own_addr.equals(other.own_addr); + } + + public int hashCode() { + int retval=0; + if(own_addr != null) + retval+=own_addr.hashCode(); + if(retval == 0) + retval=super.hashCode(); + return retval; + } + + public String toString() { + StringBuilder sb=new StringBuilder(); + sb.append("own_addr=").append(own_addr); + if(view_id != null) + sb.append(", view_id=").append(view_id); + if(view != null) { + sb.append(", view="); + if(view.size() > 10) + sb.append(view.size() + " mbrs"); + else + sb.append(view); + } + sb.append(", is_server=").append(is_server).append(", is_coord=" + isCoord()); + if(logical_name != null) + sb.append(", logical_name=").append(logical_name); + if(physical_addrs != null && !physical_addrs.isEmpty()) + sb.append(", physical_addrs=").append(Util.printListWithDelimiter(physical_addrs, ", ")); + return sb.toString(); + } + + public void writeTo(DataOutputStream outstream) throws IOException { + Util.writeAddress(own_addr, outstream); + Util.writeView(view, outstream); + Util.writeViewId(view_id, outstream); + outstream.writeBoolean(is_server); + Util.writeString(logical_name, outstream); + Util.writeAddresses(physical_addrs, outstream); + } + + public void readFrom(DataInputStream instream) throws IOException, IllegalAccessException, InstantiationException { + own_addr=Util.readAddress(instream); + view=Util.readView(instream); + view_id=Util.readViewId(instream); + is_server=instream.readBoolean(); + logical_name=Util.readString(instream); + physical_addrs=(Collection)Util.readAddresses(instream, ArrayList.class); + } + + public int size() { + int retval=Global.BYTE_SIZE; // for is_server + retval+=Util.size(own_addr); + retval+=Util.size(view); + retval+=Util.size(view_id); + retval+=Global.BYTE_SIZE; // presence byte for logical_name + if(logical_name != null) + retval+=logical_name.length() +2; + retval+=Util.size(physical_addrs); + + return retval; + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/PingHeader.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/PingHeader.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/PingHeader.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/PingHeader.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,45 +1,56 @@ -// $Id: PingHeader.java,v 1.12 2007/11/29 11:15:58 belaban Exp $ package org.jgroups.protocols; -import org.jgroups.Header; import org.jgroups.Global; -import org.jgroups.util.Streamable; +import org.jgroups.Header; +import org.jgroups.ViewId; import org.jgroups.util.Util; import java.io.*; -public class PingHeader extends Header implements Streamable { +/** + * @author Bela Ban + */ +public class PingHeader extends Header { public static final byte GET_MBRS_REQ=1; // arg = null - public static final byte GET_MBRS_RSP=2; // arg = PingRsp(local_addr, coord_addr) + public static final byte GET_MBRS_RSP=2; // arg = PingData (local_addr, coord_addr) + + public byte type=0; + public PingData data=null; + public String cluster_name=null; + + // when set, we don't need the address mappings, but only the view. + // This is typically done for a merge-triggered discovery request + public ViewId view_id=null; - public byte type=0; - public PingRsp arg=null; - public String cluster_name=null; - private static final long serialVersionUID=3054979699998282428L; public PingHeader() { - } // for externalization + } public PingHeader(byte type, String cluster_name) { this.type=type; this.cluster_name=cluster_name; } - public PingHeader(byte type, PingRsp arg) { + public PingHeader(byte type, PingData data) { this.type=type; - this.arg=arg; + this.data=data; + } + + public PingHeader(byte type, PingData data, String cluster_name) { + this(type, data); + this.cluster_name=cluster_name; } public int size() { - int retval=Global.BYTE_SIZE *3; // type and presence - if(arg != null) { - retval+=arg.size(); - } - if(cluster_name != null) { + int retval=Global.BYTE_SIZE *3; // type, data presence and cluster_name presence + if(data != null) + retval+=data.size(); + if(cluster_name != null) retval += cluster_name.length() +2; - } + + retval+=Util.size(view_id); return retval; } @@ -48,46 +59,34 @@ sb.append("[PING: type=" + type2Str(type)); if(cluster_name != null) sb.append(", cluster=").append(cluster_name); - if(arg != null) - sb.append(", arg=" + arg); + if(data != null) + sb.append(", arg=" + data); + if(view_id != null) + sb.append(", view_id=").append(view_id); sb.append(']'); return sb.toString(); } static String type2Str(byte t) { switch(t) { - case GET_MBRS_REQ: - return "GET_MBRS_REQ"; - case GET_MBRS_RSP: - return "GET_MBRS_RSP"; - default: - return ""; + case GET_MBRS_REQ: return "GET_MBRS_REQ"; + case GET_MBRS_RSP: return "GET_MBRS_RSP"; + default: return ""; } } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeByte(type); - out.writeObject(arg); - out.writeObject(cluster_name); - } - - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - type=in.readByte(); - arg=(PingRsp)in.readObject(); - cluster_name=(String)in.readObject(); - } - public void writeTo(DataOutputStream outstream) throws IOException { outstream.writeByte(type); - Util.writeStreamable(arg, outstream); + Util.writeStreamable(data, outstream); Util.writeString(cluster_name, outstream); + Util.writeViewId(view_id, outstream); } public void readFrom(DataInputStream instream) throws IOException, IllegalAccessException, InstantiationException { type=instream.readByte(); - arg=(PingRsp)Util.readStreamable(PingRsp.class, instream); + data=(PingData)Util.readStreamable(PingData.class, instream); cluster_name=Util.readString(instream); + view_id=Util.readViewId(instream); } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/PING.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/PING.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/PING.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/PING.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,273 +1,75 @@ - package org.jgroups.protocols; -import org.jgroups.Address; -import org.jgroups.Event; -import org.jgroups.Message; -import org.jgroups.TimeoutException; +import org.jgroups.*; +import org.jgroups.annotations.DeprecatedProperty; import org.jgroups.annotations.Property; -import org.jgroups.stack.GossipClient; -import org.jgroups.stack.IpAddress; -import org.jgroups.util.Util; import org.jgroups.util.Promise; +import org.jgroups.util.UUID; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.Iterator; +import java.util.Arrays; import java.util.List; -import java.util.Vector; /** * The PING protocol layer retrieves the initial membership (used by the GMS when started * by sending event FIND_INITIAL_MBRS down the stack). We do this by mcasting PING - * requests to an IP MCAST address (or, if gossiping is enabled, by contacting the GossipRouter). + * requests to an IP MCAST address. * The responses should allow us to determine the coordinator whom we have to * contact, e.g. in case we want to join the group. When we are a server (after having * received the BECOME_SERVER event), we'll respond to PING requests with a PING - * response.

            The FIND_INITIAL_MBRS event will eventually be answered with a - * FIND_INITIAL_MBRS_OK event up the stack. - * The following properties are available - * property: gossip_host - if you are using GOSSIP then this defines the host of the GossipRouter, default is null - * property: gossip_port - if you are using GOSSIP then this defines the port of the GossipRouter, default is null + * response. * @author Bela Ban - * @version $Id: PING.java,v 1.51 2008/12/08 13:18:58 belaban Exp $ */ +@DeprecatedProperty(names={"gossip_host", "gossip_port", "gossip_refresh", "socket_conn_timeout", + "socket_read_timeout", "discovery_timeout"}) public class PING extends Discovery { - private static final String name="PING"; - /* ----------------------------------------- Properties -------------------------------------------------- */ - - - @Property(description="Gossip host") - private String gossip_host=null; - - @Property(description="Gossip port") - private int gossip_port=0; - @Property(description="Time in msecs after which the entry in GossipRouter will be refreshed. Default is 20000 msec") - private long gossip_refresh=20000; - - @Property(description="Number of ports to be probed for initial membership. Default is 1") - private int port_range=1; - - @Property(description="If socket is used for discovery, time in msecs to wait until socket is connected. Default is 1000 msec") - private int socket_conn_timeout=1000; - - @Property(description="Max to block on the socket on a read (in ms). 0 means block forever") - private int socket_read_timeout=3000; - @Property(description="Time (in ms) to wait for our own discovery message to be received. 0 means don't wait. If the " + "discovery message is not received within discovery_timeout ms, a warning will be logged") private long discovery_timeout=0L; - /* --------------------------------------------- Fields ------------------------------------------------------ */ - private GossipClient client; - - private List gossip_hosts=null; - - private List initial_hosts=null; // hosts to be contacted for the initial membership - protected final Promise discovery_reception=new Promise(); - - - public String getName() { - return name; - } - - public void init() throws Exception { - super.init(); - if(gossip_hosts != null) { - client=new GossipClient(gossip_hosts, gossip_refresh, 1000, timer); - client.setSocketConnectionTimeout(socket_conn_timeout); - client.setSocketReadTimeout(socket_read_timeout); - } - else if(gossip_host != null && gossip_port != 0) { - try { - client=new GossipClient(new IpAddress(InetAddress.getByName(gossip_host), - gossip_port), gossip_refresh, socket_conn_timeout, timer); - client.setSocketConnectionTimeout(socket_conn_timeout); - client.setSocketReadTimeout(socket_read_timeout); - } - catch(Exception e) { - if(log.isErrorEnabled()) - log.error("creation of GossipClient failed, exception=" + e); - throw e; - } - } - } - - - public int getGossipPort() { - return gossip_port; - } - - public void setGossipPort(int gossip_port) { - this.gossip_port=gossip_port; - } - - public long getGossipRefresh() { - return gossip_refresh; - } - - public void setGossipRefresh(long gossip_refresh) { - this.gossip_refresh=gossip_refresh; - } - - public int getSocketConnTimeout() { - return socket_conn_timeout; - } - - public void setSocketConnTimeout(int socket_conn_timeout) { - this.socket_conn_timeout=socket_conn_timeout; - } - - public int getSocketReadTimeout() { - return socket_read_timeout; - } - - public void setSocketReadTimeout(int socket_read_timeout) { - this.socket_read_timeout=socket_read_timeout; - } - - public int getSockConnTimeout() { - return socket_conn_timeout; - } - - @Property - public void setSockConnTimeout(int socket_conn_timeout) { - this.socket_conn_timeout=socket_conn_timeout; - } - - public int getSockReadTimeout() { - return socket_read_timeout; - } - - @Property - public void setSockReadTimeout(int socket_read_timeout) { - this.socket_read_timeout=socket_read_timeout; - } - - @Property - public void setInitialHosts(String hosts) throws UnknownHostException { - initial_hosts=Util.parseCommaDelimetedHosts(hosts, port_range); - } - - @Property - public void setGossipHosts(String hosts) throws UnknownHostException { - gossip_hosts=Util.parseCommaDelimetedHosts(hosts, port_range); - } - - public void stop() { super.stop(); - if(client != null) { - client.stop(); - } discovery_reception.reset(); } - - public void localAddressSet(Address addr) { - // Add own address to initial_hosts if not present: we must always be able to ping ourself ! -// if(initial_hosts != null && addr != null) { -// if(initial_hosts.contains(addr)) { -// initial_hosts.remove(addr); -// if(log.isDebugEnabled()) log.debug("[SET_LOCAL_ADDRESS]: removing my own address (" + addr + -// ") from initial_hosts; initial_hosts=" + initial_hosts); -// } -// } + public boolean isDynamic() { + return true; } + public void sendGetMembersRequest(String cluster_name, Promise promise, ViewId view_id) throws Exception { + PingData data=null; - - public void handleConnect() { - if(client != null) - client.register(group_addr, local_addr); - } - - public void handleDisconnect() { - if(client != null) { - if(group_addr != null && local_addr != null) - client.unregister(group_addr, local_addr); - client.stop(); + if(view_id == null) { + PhysicalAddress physical_addr=(PhysicalAddress)down(new Event(Event.GET_PHYSICAL_ADDRESS, local_addr)); + List physical_addrs=Arrays.asList(physical_addr); + data=new PingData(local_addr, null, false, UUID.get(local_addr), physical_addrs); } - } - - - public void sendGetMembersRequest(String cluster_name) { - Message msg; - PingHeader hdr; - List

            gossip_rsps; - - if(client != null) { - gossip_rsps=client.getMembers(group_addr); - if(gossip_rsps != null && !gossip_rsps.isEmpty()) { - // Set a temporary membership in the UDP layer, so that the following multicast - // will be sent to all of them - Event view_event=new Event(Event.TMP_VIEW, makeView(new Vector
            (gossip_rsps))); - down_prot.down(view_event); // needed e.g. by failure detector or UDP - } - else { - //do nothing - return; - } - - if(!gossip_rsps.isEmpty()) { - for(Iterator
            it=gossip_rsps.iterator(); it.hasNext();) { - Address dest=it.next(); - msg=new Message(dest, null, null); // unicast msg - msg.setFlag(Message.OOB); - msg.putHeader(getName(), new PingHeader(PingHeader.GET_MBRS_REQ, cluster_name)); - down_prot.down(new Event(Event.MSG, msg)); - } - } - - Util.sleep(500); - } - else { - if(initial_hosts != null && !initial_hosts.isEmpty()) { - for(Address addr: initial_hosts) { - if(addr.equals(local_addr)) - continue; - // if(tmpMbrs.contains(addr)) { - // ; // continue; // changed as suggested by Mark Kopec - // } - msg=new Message(addr, null, null); - msg.setFlag(Message.OOB); - msg.putHeader(name, new PingHeader(PingHeader.GET_MBRS_REQ, cluster_name)); - - if(log.isTraceEnabled()) log.trace("[FIND_INITIAL_MBRS] sending PING request to " + msg.getDest()); - down_prot.down(new Event(Event.MSG, msg)); - } - } - else { - // 1. Mcast GET_MBRS_REQ message - hdr=new PingHeader(PingHeader.GET_MBRS_REQ, cluster_name); - msg=new Message(null); // mcast msg - msg.setFlag(Message.OOB); - msg.putHeader(getName(), hdr); // needs to be getName(), so we might get "MPING" ! - sendMcastDiscoveryRequest(msg); - } - } + PingHeader hdr=new PingHeader(PingHeader.GET_MBRS_REQ, data, cluster_name); + hdr.view_id=view_id; + Message msg=new Message(null); // mcast msg + msg.setFlag(Message.OOB); + msg.putHeader(this.id, hdr); // needs to be getName(), so we might get "MPING" ! + sendMcastDiscoveryRequest(msg); } public Object up(Event evt) { if(evt.getType() == Event.MSG) { Message msg=(Message)evt.getArg(); - PingHeader hdr=(PingHeader)msg.getHeader(getName()); + PingHeader hdr=(PingHeader)msg.getHeader(this.id); if(hdr != null && hdr.type == PingHeader.GET_MBRS_REQ && msg.getSrc().equals(local_addr)) { discovery_reception.setResult(true); } } - return super.up(evt); } @@ -289,4 +91,4 @@ } } } -} +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/PingRsp.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/PingRsp.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/PingRsp.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/PingRsp.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,104 +0,0 @@ -// $Id: PingRsp.java,v 1.16 2008/09/27 15:37:15 belaban Exp $ - -package org.jgroups.protocols; - -import org.jgroups.Address; -import org.jgroups.Global; -import org.jgroups.util.Streamable; -import org.jgroups.util.Util; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.Serializable; - -public class PingRsp implements Serializable, Streamable { - - private static final long serialVersionUID=3634334590904551586L; - - private Address own_addr=null; - private Address coord_addr=null; - private boolean is_server=false; - - public PingRsp() { - // externalization - } - - public PingRsp(Address own_addr,Address coord_addr,boolean is_server) { - this.own_addr=own_addr; - this.coord_addr=coord_addr; - this.is_server=is_server; - } - - public boolean equals(Object obj) { - if(!(obj instanceof PingRsp)) - return false; - PingRsp other=(PingRsp)obj; - return own_addr != null && own_addr.equals(other.own_addr); - } - - public int hashCode() { - int retval=0; - if(own_addr != null) - retval+=own_addr.hashCode(); - if(retval == 0) - retval=super.hashCode(); - return retval; - } - - public boolean isCoord() { - return is_server && own_addr != null && coord_addr != null && own_addr.equals(coord_addr); - } - - public boolean hasCoord(){ - return is_server && own_addr != null && coord_addr != null; - } - - public int size() { - int retval=Global.BYTE_SIZE * 3; // for is_server, plus 2 presence bytes - if(own_addr != null) { - retval+=Global.BYTE_SIZE; // 1 boolean for: IpAddress or other address ? - retval+=own_addr.size(); - } - if(coord_addr != null) { - retval+=Global.BYTE_SIZE; // 1 boolean for: IpAddress or other address ? - retval+=coord_addr.size(); - } - return retval; - } - - public Address getAddress() { - return own_addr; - } - - public Address getCoordAddress() { - return coord_addr; - } - - public boolean isServer() { - return is_server; - } - - public String toString() { - return new StringBuilder("[own_addr=").append(own_addr) - .append(", coord_addr=") - .append(coord_addr) - .append(", is_server=") - .append(is_server) - .append(", is_coord=" + isCoord()) - .append(']') - .toString(); - } - - public void writeTo(DataOutputStream outstream) throws IOException { - Util.writeAddress(own_addr, outstream); - Util.writeAddress(coord_addr, outstream); - outstream.writeBoolean(is_server); - } - - public void readFrom(DataInputStream instream) throws IOException, IllegalAccessException, InstantiationException { - own_addr=Util.readAddress(instream); - coord_addr=Util.readAddress(instream); - is_server=instream.readBoolean(); - } -} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/PrioHeader.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/PrioHeader.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/PrioHeader.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/PrioHeader.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,61 @@ +package org.jgroups.protocols; + +import org.jgroups.Global; +import org.jgroups.Header; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * This Header class is used in conjunction with the PRIO protocol to prioritize message sending/receiving + * Priority values are from 0 to 255 where 0 is the highest priority + * + * Example of setting a message priority: + * + * // Create a message to send to everyone + * Message message = new Message( null, null, messagePayload ); + * // Add the priority protocol header + * PrioHeader header = new PrioHeader( 1 ); + * short protocolId = ClassConfigurator.getProtocolId(PRIO.class); + * message.putHeader( protocolId, header); + * + * @author Michael Earl + */ +public class PrioHeader extends Header { + private byte priority = 0; + + public PrioHeader() { + } + + public PrioHeader( byte priority ) { + this.priority = priority; + } + + public byte getPriority() { + return priority; + } + + public void setPriority( byte priority ) { + this.priority = priority; + } + + @Override + public int size() { + return Global.BYTE_SIZE; + } + + public void writeTo( DataOutputStream outstream ) throws IOException { + outstream.writeByte(priority); + } + + public void readFrom( DataInputStream instream ) throws IOException, IllegalAccessException, InstantiationException { + priority=instream.readByte(); + } + + @Override + public String toString() + { + return "PRIO priority=" + priority; + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/PRIO.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/PRIO.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/PRIO.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/PRIO.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,273 @@ +package org.jgroups.protocols; + +import java.util.Comparator; +import java.util.concurrent.PriorityBlockingQueue; +import org.jgroups.Event; +import org.jgroups.Message; +import org.jgroups.annotations.Experimental; +import org.jgroups.annotations.Property; +import org.jgroups.stack.Protocol; + +/** + * This protocol will provide message sending and receiving prioritization. The protocol assumes that any prioritized + * message will contain a PrioHeader header entry that will contain the byte value priority. Priority values are from + * 0 to 255 where 0 is the highest priority. + * + * When a message is received (up/down), it is added to the up/downMessageQueue. The up/downMessageThread will block + * on the queue until new message is added. Messages with the highest priority (0=highest) will bubble to the top + * of the queue and those be processed before other messages received at the same time. + * + * Example of setting a message priority: + * + * // Create a message to send to everyone + * Message message = new Message( null, null, messagePayload ); + * // Add the priority protocol header + * PrioHeader header = new PrioHeader( 1 ); + * short protocolId = ClassConfigurator.getProtocolId(PRIO.class); + * message.putHeader( protocolId, header); + * + * @author Michael Earl + */ +@Experimental +public class PRIO extends Protocol { + private PriorityBlockingQueue downMessageQueue; + private PriorityBlockingQueue upMessageQueue; + private DownMessageThread downMessageThread; + private UpMessageThread upMessageThread; + + @Property(description="The number of miliseconds to sleep before after an error occurs before sending the next message") + private int message_failure_sleep_time = 120000; // two seconds (bela: 2 minutes, is that what you wanted ?) + + @Property(description="true to prioritize outgoing messages") + private boolean prioritize_down = true; + + @Property(description="true to prioritize incoming messages") + private boolean prioritize_up = true; + + /** + * This method is called on a {@link org.jgroups.Channel#connect(String)}. Starts work. + * Protocols are connected and queues are ready to receive events. + * Will be called from bottom to top. This call will replace + * the START and START_OK events. + * @exception Exception Thrown if protocol cannot be started successfully. This will cause the ProtocolStack + * to fail, so {@link org.jgroups.Channel#connect(String)} will throw an exception + */ + public void start() throws Exception { + if (prioritize_down) { + downMessageQueue = new PriorityBlockingQueue( 100, new PriorityCompare() ); + downMessageThread = new DownMessageThread( this, downMessageQueue ); + downMessageThread.start(); + } + + if (prioritize_up) { + upMessageQueue = new PriorityBlockingQueue( 100, new PriorityCompare() ); + upMessageThread = new UpMessageThread( this, upMessageQueue ); + upMessageThread.start(); + } + } + + /** + * This method is called on a {@link org.jgroups.Channel#disconnect()}. Stops work (e.g. by closing multicast socket). + * Will be called from top to bottom. This means that at the time of the method invocation the + * neighbor protocol below is still working. This method will replace the + * STOP, STOP_OK, CLEANUP and CLEANUP_OK events. The ProtocolStack guarantees that + * when this method is called all messages in the down queue will have been flushed + */ + public void stop() { + if (downMessageThread != null) { + downMessageThread.interrupt(); + } + if (upMessageThread != null) { + upMessageThread.interrupt(); + } + } + + /** + * An event was received from the layer below. Usually the current layer will want to examine + * the event type and - depending on its type - perform some computation + * (e.g. removing headers from a MSG event type, or updating the internal membership list + * when receiving a VIEW_CHANGE event). + * Finally the event is either a) discarded, or b) an event is sent down + * the stack using down_prot.down() or c) the event (or another event) is sent up + * the stack using up_prot.up(). + */ + public Object up(Event evt) { + switch(evt.getType()) { + case Event.MSG: + Message message = (Message)evt.getArg(); + if ( message.isFlagSet( Message.OOB ) ) { + return up_prot.up(evt); + } + else { + PrioHeader hdr=(PrioHeader)message.getHeader(id); + if(hdr != null) { + log.trace( "Adding priority message " + hdr.getPriority() + " to UP queue" ); + upMessageQueue.add( new PriorityMessage( evt, hdr.getPriority() ) ); + // send with hdr.prio + return null; + } + return up_prot.up(evt); + } + default: + return up_prot.up(evt); + } + } + + + /** + * An event is to be sent down the stack. The layer may want to examine its type and perform + * some action on it, depending on the event's type. If the event is a message MSG, then + * the layer may need to add a header to it (or do nothing at all) before sending it down + * the stack using down_prot.down(). In case of a GET_ADDRESS event (which tries to + * retrieve the stack's address from one of the bottom layers), the layer may need to send + * a new response event back up the stack using up_prot.up(). + */ + public Object down(Event evt) { + switch(evt.getType()) { + case Event.MSG: + Message message = (Message)evt.getArg(); + if ( message.isFlagSet( Message.OOB ) ) { + return down_prot.down(evt); + } + else { + PrioHeader hdr=(PrioHeader)message.getHeader(id); + if(hdr != null) { + log.trace( "Adding priority message " + hdr.getPriority() + " to DOWN queue" ); + downMessageQueue.add( new PriorityMessage( evt, hdr.getPriority() ) ); + // send with hdr.prio + return null; + } + return down_prot.down(evt); + } + default: + return down_prot.down(evt); + } + } + + /** + * This class is a simple wrapper to contain the Event, timestamp and priority of the message. + * Instances of this class are added to the message queue + */ + private class PriorityMessage { + Event event; + long timestamp; + byte priority; + + private PriorityMessage( Event event, byte priority ) { + this.event = event; + this.timestamp = System.currentTimeMillis(); + this.priority = priority; + } + } + + /** + * Thread to send messages to the down protocol. + *

            + * The messageQueue contains the prioritized messages + */ + private class DownMessageThread extends MessageThread { + private DownMessageThread( PRIO prio, PriorityBlockingQueue messageQueue ) { + super( prio, messageQueue ); + } + + protected void handleMessage( PriorityMessage message ) { + log.trace( "Sending priority " + message.priority + " message" ); + down_prot.down( message.event ); + } + } + + /** + * Thread to send messages to the up protocol. + *

            + * The messageQueue contains the prioritized messages + */ + private class UpMessageThread extends MessageThread { + private UpMessageThread( PRIO prio, PriorityBlockingQueue messageQueue ) { + super( prio, messageQueue ); + } + + protected void handleMessage( PriorityMessage message ) { + log.trace( "receiving priority " + message.priority + " message" ); + up_prot.up( message.event ); + } + } + + /** + * This Thread class will process PriorityMessage's off of the queue and call the handleMessage method + * to send the message + */ + private abstract class MessageThread extends Thread + { + private PRIO prio; + private PriorityBlockingQueue messageQueue; + + private MessageThread( PRIO prio, PriorityBlockingQueue messageQueue ) { + this.prio = prio; + this.messageQueue = messageQueue; + setName( "PRIO " + (messageQueue == downMessageQueue ? "down" : "up") ); + } + + protected abstract void handleMessage( PriorityMessage message ); + + @Override + public void run() { + while (true) { + PriorityMessage priorityMessage = null; + try { + priorityMessage = messageQueue.take(); + handleMessage( priorityMessage ); + } + catch( InterruptedException e ) { + break; + } + catch (Exception e) { + log.error( "Error handling message. Sleeping " + (prio.message_failure_sleep_time/1000) + " seconds", e ); + try + { + sleep( prio.message_failure_sleep_time ); + } + catch (InterruptedException ex) + { + break; + } + /* + * Add it back to the queue to be processed again + */ + messageQueue.add( priorityMessage ); + } + } + } + } + + /** + * Comparator for PriorityMessage's + */ + private class PriorityCompare implements Comparator { + /** + * Compare two messages based on priority and time stamp in that order + * @param msg1 - first message + * @param msg2 - second message + * @return int result of comparison + */ + @Override + public int compare( PriorityMessage msg1, PriorityMessage msg2 ) { + if ( msg1.priority > msg2.priority ) { + return 1; + } + else if ( msg1.priority < msg2.priority ) { + return -1; + } + else { + if ( msg1.timestamp > msg2.timestamp ) { + return 1; + } + else if ( msg1.timestamp < msg2.timestamp ) { + return -1; + } + else { + return 0; + } + } + } + } +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/RATE_LIMITER.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/RATE_LIMITER.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/RATE_LIMITER.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/RATE_LIMITER.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,119 @@ +package org.jgroups.protocols; + +import org.jgroups.Event; +import org.jgroups.Message; +import org.jgroups.annotations.*; +import org.jgroups.stack.Protocol; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Protocol which sends at most max_bytes in time_period milliseconds. Can be used instead of a flow control protocol, + * e.g. FC or SFC (same position in the stack) + * @author Bela Ban + */ +@Experimental +@MBean(description="Limits the sending rate to max_bytes per time_period") +public class RATE_LIMITER extends Protocol { + + @Property(description="Max number of bytes to be sent in time_period ms. Blocks the sender if exceeded until a new " + + "time period has started") + protected long max_bytes=500000; + + @Property(description="Number of milliseconds during which max_bytes bytes can be sent") + protected long time_period=1000L; + + + /** Keeps track of the number of bytes sent in the current time period */ + @GuardedBy("lock") + @ManagedAttribute + protected long num_bytes_sent=0L; + + @GuardedBy("lock") + protected long end_of_current_period=0L; + + protected final Lock lock=new ReentrantLock(); + protected final Condition block=lock.newCondition(); + + @ManagedAttribute + protected int num_blockings=0; + + @ManagedAttribute + protected long total_block_time=0L; + + + + public Object down(Event evt) { + if(evt.getType() == Event.MSG) { + Message msg=(Message)evt.getArg(); + int len=msg.getLength(); + + lock.lock(); + try { + if(len > max_bytes) { + log.error("message length (" + len + " bytes) exceeded max_bytes (" + max_bytes + "); " + + "adjusting max_bytes to " + len); + max_bytes=len; + } + + while(true) { + boolean size_exceeded=num_bytes_sent + len >= max_bytes, + time_exceeded=System.currentTimeMillis() > end_of_current_period; + if(!size_exceeded && !time_exceeded) + break; + + if(time_exceeded) { + reset(); + } + else { // size exceeded + long block_time=end_of_current_period - System.currentTimeMillis(); + if(block_time > 0) { + try { + block.await(block_time, TimeUnit.MILLISECONDS); + num_blockings++; + total_block_time+=block_time; + } + catch(InterruptedException e) { + } + } + } + } + } + finally { + num_bytes_sent+=len; + lock.unlock(); + } + + return down_prot.down(evt); + } + + return down_prot.down(evt); + } + + + public void init() throws Exception { + super.init(); + if(time_period <= 0) + throw new IllegalArgumentException("time_period needs to be positive"); + } + + public void stop() { + super.stop(); + lock.lock(); + try { + reset(); + } + finally { + lock.unlock(); + } + } + + protected void reset() { + num_bytes_sent=0L; + end_of_current_period=System.currentTimeMillis() + time_period; + block.signalAll(); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/RELAY.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/RELAY.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/RELAY.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/RELAY.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,734 @@ +package org.jgroups.protocols; + +import org.jgroups.*; +import org.jgroups.annotations.*; +import org.jgroups.stack.AddressGenerator; +import org.jgroups.stack.IpAddress; +import org.jgroups.stack.Protocol; +import org.jgroups.util.*; +import org.jgroups.util.UUID; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * Simple relaying protocol: RELAY is added to the top of the stack, creates a channel to a bridge cluster, + * and - if coordinator - relays all multicast messages via the bridge cluster to the remote cluster.

            + * + * This is not a big virtual cluster, e.g. consisting of {A,B,C,X,Y,Z}, but 2 autonomous clusters + * {A,B,C} and {X,Y,Z}, bridged together by RELAY. For example, when B multicasts a message M, A (if it happens to be + * the coord) relays M to X (which happens to be the other cluster's coordinator). X then re-broadcasts M, with M.src + * being a ProxyUUID(X,B). This means that the sender of M in the {X,Y,Z} cluster will be X for all practical purposes, + * but the original sender B is also recorded, for sending back a response.

            + * + * See [1] and [2] for details.

            + * [1] https://jira.jboss.org/browse/JGRP-747

            + * [2] doc/design/RELAY.txt + * + * @author Bela Ban + * @since 2.12 + */ +@Experimental +@MBean(description="RELAY protocol") +public class RELAY extends Protocol { + + /* ------------------------------------------ Properties ---------------------------------------------- */ + @Property(description="Description of the local cluster, e.g. \"nyc\". This is added to every address, so it" + + "should be short. This is a mandatory property and must be set",writable=false) + protected String site; + + @Property(description="Properties of the bridge cluster (e.g. tcp.xml)") + protected String bridge_props=null; + + @Property(description="Name of the bridge cluster") + protected String bridge_name="bridge-cluster"; + + // @Property(description="If true, messages are relayed asynchronously, ie. via submission of a task to the timer thread pool") + // protected boolean async=false; + + @Property(description="If set to false, don't perform relaying. Used e.g. for backup clusters; " + + "unidirectional replication from one cluster to another, but not back. Can be changed at runtime") + protected boolean relay=true; + + @Property(description="Drops views received from below and instead generates global views and passes them up. " + + "A global view consists of the local view and the remote view, ordered by view ID. If true, no protocol" + + "which requires (local) views can sit on top of RELAY") + protected boolean present_global_views=true; + + + /* --------------------------------------------- Fields ------------------------------------------------ */ + protected Address local_addr; + @ManagedAttribute + protected volatile boolean is_coord=false; + protected volatile Address coord=null; + + /** The bridge between the two local clusters, usually based on a TCP config */ + protected JChannel bridge; + + /** The view of the local cluster */ + protected View local_view; + + /** The view of the bridge cluster, usually consists of max 2 nodes */ + protected View bridge_view; + + /** The view of the remote cluster */ + protected View remote_view; + + /** The combined view of local and remote cluster */ + protected View global_view; + + /** To generate new global views */ + protected long global_view_id=0; + + protected TimeScheduler timer; + + protected Future remote_view_fetcher_future; + + + + + @ManagedOperation + public void setRelay(boolean relay) { + this.relay=relay; + } + + @ManagedAttribute + public String getLocalView() { + return local_view != null? local_view.toString() : "n/a"; + } + + @ManagedAttribute + public String getBridgeView() { + return bridge_view != null? bridge_view.toString() : "n/a"; + } + + @ManagedAttribute + public String getRemoteView() { + return remote_view != null? remote_view.toString() : "n/a"; + } + + @ManagedAttribute + public String getGlobalView() { + return global_view != null? global_view.toString() : "n/a"; + } + + + + public void init() throws Exception { + super.init(); + if(site == null || site.length() == 0) + throw new IllegalArgumentException("\"site\" must be set"); + timer=getTransport().getTimer(); + JChannel channel=getProtocolStack().getChannel(); + if(channel == null) + throw new IllegalStateException("channel must be set"); + channel.setAddressGenerator(new AddressGenerator() { + public Address generateAddress() { + return PayloadUUID.randomUUID(site); + } + }); + } + + public void stop() { + stopRemoteViewFetcher(); + Util.close(bridge); + } + + public Object down(Event evt) { + switch(evt.getType()) { + + case Event.MSG: + Message msg=(Message)evt.getArg(); + Address dest=msg.getDest(); + if(dest == null || dest.isMulticastAddress()) + break; + + // forward non local destinations to the coordinator, to relay to the remote cluster + if(!isLocal(dest)) { + forwardToCoord(msg); + return null; + } + break; + + case Event.VIEW_CHANGE: + handleView((View)evt.getArg()); + break; + + case Event.DISCONNECT: + Util.close(bridge); + break; + + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; + + case Event.GET_PHYSICAL_ADDRESS: + // fix to prevent exception by JBossAS, which checks whether a physical + // address is present and throw an ex if not + // Remove this when the AS code removes that check + PhysicalAddress addr=(PhysicalAddress)down_prot.down(evt); + if(addr == null) + addr=new IpAddress(6666); + return addr; + } + return down_prot.down(evt); + } + + + public Object up(Event evt) { + switch(evt.getType()) { + case Event.MSG: + Message msg=(Message)evt.getArg(); + Address dest=msg.getDest(); + RelayHeader hdr=(RelayHeader)msg.getHeader(getId()); + if(hdr != null) { + switch(hdr.type) { + case DISSEMINATE: + Message copy=msg.copy(); + if(hdr.original_sender != null) + copy.setSrc(hdr.original_sender); + return up_prot.up(new Event(Event.MSG, copy)); + + case FORWARD: + if(is_coord) + forward(msg.getRawBuffer(), msg.getOffset(), msg.getLength()); + break; + + case VIEW: + return installView(msg.getRawBuffer(), msg.getOffset(), msg.getLength()); + + case BROADCAST_VIEW: + // sendViewOnLocalCluster(msg.getSrc(), remote_view, global_view, false); + break; + + default: + throw new IllegalArgumentException(hdr.type + " is not a valid type"); + } + return null; + } + + if(is_coord && relay && (dest == null || dest.isMulticastAddress()) && !msg.isFlagSet(Message.NO_RELAY)) { + Message tmp=msg.copy(true, Global.BLOCKS_START_ID); // we only copy headers from building blocks + try { + byte[] buf=Util.streamableToByteBuffer(tmp); + forward(buf, 0, buf.length); + } + catch(Exception e) { + log.warn("failed relaying message", e); + } + } + break; + + case Event.VIEW_CHANGE: + handleView((View)evt.getArg()); // already sends up new view if needed + if(present_global_views) + return null; + else + break; + } + return up_prot.up(evt); + } + + + + + protected void handleView(final View view) { + List

            new_mbrs=null; + if(local_view != null) + new_mbrs=Util.newMembers(local_view.getMembers(), view.getMembers()); + local_view=view; + coord=view.getMembers().firstElement(); + boolean create_bridge=false; + + boolean is_new_coord=Util.isCoordinator(view, local_addr); + + if(is_coord) { + if(!is_new_coord) { + if(log.isTraceEnabled()) + log.trace("I'm not coordinator anymore, closing the channel"); + Util.close(bridge); + is_coord=false; + bridge=null; + } + } + else if(is_new_coord) + is_coord=create_bridge=true; + + if(is_coord) { + // need to have a local view before JChannel.connect() returns; we don't want to change the viewAccepted() semantics + sendViewOnLocalCluster(remote_view, generateGlobalView(view, remote_view, view instanceof MergeView), + true, new_mbrs); + if(create_bridge) + createBridge(); + sendViewToRemote(ViewData.create(view, null), false); + } + } + + + protected Object installView(byte[] buf, int offset, int length) { + try { + ViewData data=(ViewData)Util.streamableFromByteBuffer(ViewData.class, buf, offset, length); + if(data.uuids != null) + UUID.add(data.uuids); + + remote_view=data.remote_view; + if(global_view == null || (data.global_view != null &&!global_view.equals(data.global_view))) { + global_view=data.global_view; + synchronized(this) { + if(data.global_view.getVid().getId() > global_view_id) + global_view_id=data.global_view.getViewId().getId(); + } + if(present_global_views) + return up_prot.up(new Event(Event.VIEW_CHANGE, global_view)); + } + } + catch(Exception e) { + log.error("failed installing view", e); + } + return null; + } + + + /** Forwards the message across the TCP link to the other local cluster */ + protected void forward(byte[] buffer, int offset, int length) { + Message msg=new Message(null, null, buffer, offset, length); + msg.putHeader(id, new RelayHeader(RelayHeader.Type.FORWARD)); + if(bridge != null) { + try { + bridge.send(msg); + } + catch(Throwable t) { + log.error("failed forwarding message over bridge", t); + } + } + } + + /** Wraps the message annd sends it to the current coordinator */ + protected void forwardToCoord(Message msg) { + Message tmp=msg.copy(true, Global.BLOCKS_START_ID); // // we only copy headers from building blocks + if(tmp.getSrc() == null) + tmp.setSrc(local_addr); + + try { + byte[] buf=Util.streamableToByteBuffer(tmp); + if(coord != null) { + // optimization: if I'm the coord, simply relay to the remote cluster via the bridge + if(coord.equals(local_addr)) { + forward(buf, 0, buf.length); + return; + } + + tmp=new Message(coord, null, buf, 0, buf.length); // reusing tmp is OK here ... + tmp.putHeader(id, new RelayHeader(RelayHeader.Type.FORWARD)); + down_prot.down(new Event(Event.MSG, tmp)); + } + } + catch(Exception e) { + log.error("failed forwarding unicast message to coord", e); + } + } + + + + protected void sendViewToRemote(ViewData view_data, boolean use_seperate_thread) { + try { + if(bridge != null && bridge.isConnected()) { + byte[] buf=Util.streamableToByteBuffer(view_data); + final Message msg=new Message(null, null, buf); + msg.putHeader(id, RelayHeader.create(RelayHeader.Type.VIEW)); + if(use_seperate_thread) { + timer.execute(new Runnable() { + public void run() { + try { + bridge.send(msg); + } + catch(Exception e) { + log.error("failed sending view to remote", e); + } + } + }); + } + else + bridge.send(msg); + } + } + catch(Exception e) { + log.error("failed sending view to remote", e); + } + } + + + + protected View generateGlobalView(View local_view, View remote_view) { + return generateGlobalView(local_view, remote_view, false); + } + + protected View generateGlobalView(View local_view, View remote_view, boolean merge) { + Vector views=new Vector(2); + if(local_view != null) views.add(local_view); + if(remote_view != null) views.add(remote_view); + Collections.sort(views, new Comparator() { + public int compare(View v1, View v2) { + ViewId vid1=v1.getViewId(), vid2=v2.getViewId(); + Address creator1=vid1.getCoordAddress(), creator2=vid2.getCoordAddress(); + int rc=creator1.compareTo(creator2); + if(rc != 0) + return rc; + long id1=vid1.getId(), id2=vid2.getId(); + return id1 > id2 ? 1 : id1 < id2? -1 : 0; + } + }); + + Vector
            combined_members=new Vector
            (); + for(View view: views) + combined_members.addAll(view.getMembers()); + + long new_view_id; + synchronized(this) { + new_view_id=global_view_id++; + } + Address view_creator=combined_members.isEmpty()? local_addr : combined_members.firstElement(); + if(merge) + return new MergeView(view_creator, new_view_id, combined_members, views); + else + return new View(view_creator, new_view_id, combined_members); + } + + protected void createBridge() { + try { + if(log.isTraceEnabled()) + log.trace("I'm the coordinator, creating a channel (props=" + bridge_props + ", cluster_name=" + bridge_name + ")"); + bridge=new JChannel(bridge_props); + bridge.setOpt(Channel.LOCAL, false); // don't receive my own messages + bridge.setReceiver(new Receiver()); + bridge.connect(bridge_name); + + } + catch(ChannelException e) { + log.error("failed creating bridge channel (props=" + bridge_props + ")", e); + } + } + + + protected void sendOnLocalCluster(byte[] buf, int offset, int length) { + try { + Message msg=(Message)Util.streamableFromByteBuffer(Message.class, buf, offset, length); + Address sender=msg.getSrc(); + Address dest=msg.getDest(); + + if(!isLocal(dest)) { + if(log.isWarnEnabled()) + log.warn("[" + local_addr + "] dest=" + dest + " is not local (site=" + this.site + "); discarding it"); + return; + } + + // set myself to be the sender + msg.setSrc(local_addr); + + // later, in RELAY, we'll take the original sender from the header and make it the sender + msg.putHeader(id, RelayHeader.createDisseminateHeader(sender)); + + if(log.isTraceEnabled()) + log.trace("received msg from " + sender + ", passing down the stack with dest=" + + msg.getDest() + " and src=" + msg.getSrc()); + down_prot.down(new Event(Event.MSG, msg)); + } + catch(Exception e) { + log.error("failed sending on local cluster", e); + } + } + + + protected void sendViewOnLocalCluster(View remote_view, View global_view, + boolean use_seperate_thread, List
            new_mbrs) { + sendViewOnLocalCluster(ViewData.create(remote_view, global_view), use_seperate_thread, new_mbrs); + } + + + protected void sendViewOnLocalCluster(ViewData data, boolean use_seperate_thread, final List
            new_mbrs) { + try { + final byte[] buffer=Util.streamableToByteBuffer(data); + final List
            destinations=new ArrayList
            (); + destinations.add(null); // send to all + if(new_mbrs != null) + destinations.addAll(new_mbrs); + + if(use_seperate_thread) { + timer.execute(new Runnable() { + public void run() { + sendViewOnLocalCluster(destinations, buffer); + } + }); + } + else + sendViewOnLocalCluster(destinations, buffer); + } + catch(Exception e) { + log.error("failed sending view to local cluster", e); + } + } + + + protected void sendViewOnLocalCluster(final List
            destinations, final byte[] buffer) { + for(Address dest: destinations) { + Message view_msg=new Message(dest, null, buffer); + RelayHeader hdr=RelayHeader.create(RelayHeader.Type.VIEW); + view_msg.putHeader(id, hdr); + down_prot.down(new Event(Event.MSG, view_msg)); + } + } + + /** Does the payload match the 'site' ID. Checks only unicast destinations (multicast destinations return true) */ + protected boolean isLocal(Address dest) { + if(dest instanceof PayloadUUID) { + String tmp=((PayloadUUID)dest).getPayload(); + return tmp != null && tmp.equals(this.site); + } + else if(dest instanceof TopologyUUID) { + String tmp=((TopologyUUID)dest).getSiteId(); + return tmp != null && tmp.equals(this.site); + } + return true; + } + + + protected synchronized void startRemoteViewFetcher() { + if(remote_view_fetcher_future == null || remote_view_fetcher_future.isDone()) { + remote_view_fetcher_future=timer.scheduleWithFixedDelay(new RemoteViewFetcher(), 500, 2000, TimeUnit.MILLISECONDS); + } + } + + protected synchronized void stopRemoteViewFetcher() { + if(remote_view_fetcher_future != null) { + remote_view_fetcher_future.cancel(false); + remote_view_fetcher_future=null; + } + } + + + + protected class Receiver extends ReceiverAdapter { + + public void receive(Message msg) { + Address sender=msg.getSrc(); + if(bridge.getAddress().equals(sender)) // discard my own messages + return; + + RelayHeader hdr=(RelayHeader)msg.getHeader(id); + switch(hdr.type) { + case DISSEMINATE: // should not occur here, but we'll ignore it anyway + break; + case FORWARD: + sendOnLocalCluster(msg.getRawBuffer(), msg.getOffset(), msg.getLength()); + break; + case VIEW: + try { + ViewData data=(ViewData)Util.streamableFromByteBuffer(ViewData.class, msg.getRawBuffer(), + msg.getOffset(), msg.getLength()); + // replace addrs with proxies + if(data.remote_view != null) { + List
            mbrs=new LinkedList
            (); + for(Address mbr: data.remote_view.getMembers()) { + mbrs.add(mbr); + } + data.remote_view=new View(data.remote_view.getViewId(), mbrs); + } + boolean merge=remote_view == null; + stopRemoteViewFetcher(); + data.global_view=generateGlobalView(local_view, data.remote_view, merge); + sendViewOnLocalCluster(data, false, null); + } + catch(Exception e) { + log.error("failed unmarshalling view from remote cluster", e); + } + break; + case BROADCAST_VIEW: // no-op + // our local view is seen as the remote view on the other side ! + sendViewToRemote(ViewData.create(local_view, null), true); + break; + default: + throw new IllegalArgumentException(hdr.type + " is not a valid type"); + } + } + + public void viewAccepted(View view) { + if(bridge_view != null && bridge_view.getViewId().equals(view.getViewId())) + return; + int prev_members=bridge_view != null? bridge_view.size() : 0; + bridge_view=view; + + switch(view.size()) { + case 1: + // the remote cluster disappeared, remove all of its addresses from the view + if(prev_members > 1 && view.getMembers().firstElement().equals(bridge.getAddress())) { + remote_view=null; + View new_global_view=generateGlobalView(local_view, null); + sendViewOnLocalCluster(null, new_global_view, false, null); + } + break; + case 2: + startRemoteViewFetcher(); + break; + default: + // System.err.println("View size of > 2 is invalid: view=" + view); + break; + } + } + } + + + protected class RemoteViewFetcher implements Runnable { + + public void run() { + if(bridge == null || !bridge.isConnected() || remote_view != null) + return; + Message msg=new Message(); + msg.putHeader(id, RelayHeader.create(RELAY.RelayHeader.Type.BROADCAST_VIEW)); + try { + bridge.send(msg); + } + catch(Exception e) { + } + } + } + + + public static class RelayHeader extends Header { + public static enum Type {DISSEMINATE, FORWARD, VIEW, BROADCAST_VIEW}; + protected Type type; + protected Address original_sender; // with DISSEMINATE + + + public RelayHeader() { + } + + private RelayHeader(Type type) { + this.type=type; + } + + + public static RelayHeader create(Type type) { + return new RelayHeader(type); + } + + public static RelayHeader createDisseminateHeader(Address original_sender) { + RelayHeader retval=new RelayHeader(Type.DISSEMINATE); + retval.original_sender=original_sender; + return retval; + } + + + public int size() { + int retval=Global.BYTE_SIZE; // type + switch(type) { + case DISSEMINATE: + retval+=Util.size(original_sender); + break; + case FORWARD: + case VIEW: + case BROADCAST_VIEW: + break; + } + return retval; + } + + public void writeTo(DataOutputStream out) throws IOException { + out.writeByte(type.ordinal()); + switch(type) { + case DISSEMINATE: + Util.writeAddress(original_sender, out); + break; + case FORWARD: + case VIEW: + case BROADCAST_VIEW: + break; + } + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + type=Type.values()[in.readByte()]; + switch(type) { + case DISSEMINATE: + original_sender=Util.readAddress(in); + break; + case FORWARD: + case VIEW: + case BROADCAST_VIEW: + break; + } + } + + public String toString() { + StringBuilder sb=new StringBuilder(type.toString()); + switch(type) { + case DISSEMINATE: + sb.append(" (original sender=" + original_sender + ")"); + break; + case FORWARD: + case VIEW: + case BROADCAST_VIEW: + break; + } + return sb.toString(); + } + } + + /** Contains local and remote views, and UUID information */ + protected static class ViewData implements Streamable { + protected View remote_view; + protected View global_view; + protected Map uuids; + + public ViewData() { + } + + private ViewData(View remote_view, View global_view, Map uuids) { + this.remote_view=remote_view; + this.global_view=global_view; + this.uuids=uuids; + } + + public static ViewData create(View remote_view, View global_view) { + Map tmp=UUID.getContents(); + View rv=remote_view != null? remote_view.copy() : null; + View gv=global_view != null? global_view.copy() : null; + return new ViewData(rv, gv, tmp); + } + + + public void writeTo(DataOutputStream out) throws IOException { + Util.writeView(remote_view, out); + Util.writeView(global_view, out); + out.writeInt(uuids.size()); + for(Map.Entry entry: uuids.entrySet()) { + Util.writeAddress(entry.getKey(), out); + out.writeUTF(entry.getValue()); + } + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + remote_view=Util.readView(in); + global_view=Util.readView(in); + int size=in.readInt(); + uuids=new HashMap(); + for(int i=0; i < size; i++) { + Address addr=Util.readAddress(in); + String name=in.readUTF(); + uuids.put(addr, name); + } + } + + public String toString() { + StringBuilder sb=new StringBuilder(); + sb.append("global_view: " + global_view).append(", remote_view: ").append(remote_view); + return sb.toString(); + } + } + + + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/S3_PING.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/S3_PING.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/S3_PING.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/S3_PING.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,3202 @@ +package org.jgroups.protocols; + +import org.jgroups.Address; +import org.jgroups.annotations.Experimental; +import org.jgroups.annotations.Property; +import org.jgroups.annotations.Unsupported; +import org.jgroups.util.Util; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; +import org.xml.sax.helpers.XMLReaderFactory; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +import static java.lang.String.valueOf; + + +/** + * Discovery protocol using Amazon's S3 storage. The S3 access code reuses the example shipped by Amazon. + * This protocol is unsupported and experimental ! + * @author Bela Ban + */ +@Experimental +public class S3_PING extends FILE_PING { + + @Property(description="The access key to AWS (S3)") + protected String access_key=null; + + @Property(description="The secret access key to AWS (S3)") + protected String secret_access_key=null; + + @Property(description="When non-null, we set location to prefix-UUID") + protected String prefix=null; + + @Property(description="When non-null, we use this pre-signed URL for PUTs") + protected String pre_signed_put_url=null; + + @Property(description="When non-null, we use this pre-signed URL for DELETEs") + protected String pre_signed_delete_url=null; + + protected AWSAuthConnection conn=null; + + + + public void init() throws Exception { + super.init(); + //if(access_key == null || secret_access_key == null) + // throw new IllegalArgumentException("access_key and secret_access_key must be non-null"); + + validateProperties(); + + conn=new AWSAuthConnection(access_key, secret_access_key); + + if(prefix != null && prefix.length() > 0) { + ListAllMyBucketsResponse bucket_list=conn.listAllMyBuckets(null); + List buckets=bucket_list.entries; + if(buckets != null) { + boolean found=false; + for(Object tmp: buckets) { + if(tmp instanceof Bucket) { + Bucket bucket=(Bucket)tmp; + if(bucket.name.startsWith(prefix)) { + location=bucket.name; + found=true; + } + } + } + if(!found) { + location=prefix + "-" + java.util.UUID.randomUUID().toString(); + } + } + } + + if(usingPreSignedUrls()) { + PreSignedUrlParser parsedPut = new PreSignedUrlParser(pre_signed_put_url); + location = parsedPut.getBucket(); + } + + if(!conn.checkBucketExists(location)) { + conn.createBucket(location, AWSAuthConnection.LOCATION_DEFAULT, null).connection.getResponseMessage(); + } + + Runtime.getRuntime().addShutdownHook(new Thread() { + public void run() { + remove(group_addr, local_addr); + } + }); + } + + protected void createRootDir() { + ; // do *not* create root file system (don't remove !) + } + + protected List readAll(String clustername) { + if(clustername == null) + return null; + + List retval=new ArrayList(); + try { + if (usingPreSignedUrls()) { + PreSignedUrlParser parsedPut = new PreSignedUrlParser(pre_signed_put_url); + clustername = parsedPut.getPrefix(); + } + ListBucketResponse rsp=conn.listBucket(location, sanitize(clustername), null, null, null); + if(rsp.entries != null) { + for(Iterator it=rsp.entries.iterator(); it.hasNext();) { + ListEntry key=it.next(); + GetResponse val=conn.get(location, key.key, null); + if(val.object != null) { + byte[] buf=val.object.data; + if(buf != null) { + try { + PingData data=(PingData)Util.objectFromByteBuffer(buf); + retval.add(data); + } + catch(Exception e) { + log.error("failed marshalling buffer to address", e); + } + } + } + } + } + + return retval; + } + catch(IOException ex) { + log.error("failed reading addresses", ex); + return retval; + } + } + + + protected void writeToFile(PingData data, String clustername) { + if(clustername == null || data == null) + return; + String filename=local_addr instanceof org.jgroups.util.UUID? ((org.jgroups.util.UUID)local_addr).toStringLong() : local_addr.toString(); + String key=sanitize(clustername) + "/" + sanitize(filename); + try { + byte[] buf=Util.objectToByteBuffer(data); + S3Object val=new S3Object(buf, null); + + if (usingPreSignedUrls()) { + Map headers = new TreeMap(); + headers.put("x-amz-acl", Arrays.asList("public-read")); + conn.put(pre_signed_put_url, val, headers).connection.getResponseMessage(); + } else { + Map headers=new TreeMap(); + headers.put("Content-Type", Arrays.asList("text/plain")); + conn.put(location, key, val, headers).connection.getResponseMessage(); + } + } + catch(Exception e) { + log.error("failed marshalling " + data + " to buffer", e); + } + } + + + protected void remove(String clustername, Address addr) { + if(clustername == null || addr == null) + return; + String filename=addr instanceof org.jgroups.util.UUID? ((org.jgroups.util.UUID)addr).toStringLong() : addr.toString(); + String key=sanitize(clustername) + "/" + sanitize(filename); + try { + Map headers=new TreeMap(); + headers.put("Content-Type", Arrays.asList("text/plain")); + if (usingPreSignedUrls()) { + conn.delete(pre_signed_delete_url).connection.getResponseMessage(); + } else { + conn.delete(location, key, headers).connection.getResponseMessage(); + } + if(log.isTraceEnabled()) + log.trace("removing " + location + "/" + key); + } + catch(Exception e) { + log.error("failure removing data", e); + } + } + + + protected void validateProperties() { + if (pre_signed_put_url != null && pre_signed_delete_url != null) { + PreSignedUrlParser parsedPut = new PreSignedUrlParser(pre_signed_put_url); + PreSignedUrlParser parsedDelete = new PreSignedUrlParser(pre_signed_delete_url); + if (!parsedPut.getBucket().equals(parsedDelete.getBucket()) || + !parsedPut.getPrefix().equals(parsedDelete.getPrefix())) { + throw new IllegalArgumentException("pre_signed_put_url and pre_signed_delete_url must have the same path"); + } + } else if (pre_signed_put_url != null || pre_signed_delete_url != null) { + throw new IllegalArgumentException("pre_signed_put_url and pre_signed_delete_url must both be set or both unset"); + } + } + + protected boolean usingPreSignedUrls() { + return pre_signed_put_url != null; + } + + + /** Sanitizes bucket and folder names according to AWS guidelines */ + protected static String sanitize(final String name) { + String retval=name; + retval=retval.replace('/', '-'); + retval=retval.replace('\\', '-'); + return retval; + } + + + /** + * Use this helper method to generate pre-signed S3 urls for use with S3_PING. + * You'll need to generate urls for both the put and delete http methods. + * Example: + * Your AWS Access Key is "abcd". + * Your AWS Secret Access Key is "efgh". + * You want this node to write its information to "/S3_PING/DemoCluster/node1". + * So, your bucket is "S3_PING" and your key is "DemoCluster/node1". + * You want this to expire one year from now, or + * (System.currentTimeMillis / 1000) + (60 * 60 * 24 * 365) + * Let's assume that this equals 1316286684 + * + * Here's how to generate the value for the pre_signed_put_url property: + * String putUrl = S3_PING.generatePreSignedUrl("abcd", "efgh", "put", + * "S3_Ping", "DemoCluster/node1", + * 1316286684); + * + * Here's how to generate the value for the pre_signed_delete_url property: + * String deleteUrl = S3_PING.generatePreSignedUrl("abcd", "efgh", "delete", + * "S3_Ping", "DemoCluster/node1", + * 1316286684); + * + * @param awsAccessKey Your AWS Access Key + * @param awsSecretAccessKey Your AWS Secret Access Key + * @param method The HTTP method - use "put" or "delete" for use with S3_PING + * @param bucket The S3 bucket you want to write to + * @param key The key within the bucket to write to + * @param expirationDate The date this pre-signed url should expire, in seconds since epoch + * @return The pre-signed url to be used in pre_signed_put_url or pre_signed_delete_url properties + */ + public static String generatePreSignedUrl(String awsAccessKey, String awsSecretAccessKey, String method, + String bucket, String key, long expirationDate) { + Map headers = new HashMap(); + if (method.equalsIgnoreCase("PUT")) { + headers.put("x-amz-acl", Arrays.asList("public-read")); + } + return Utils.generateQueryStringAuthentication(awsAccessKey, awsSecretAccessKey, method, + bucket, key, new HashMap(), headers, + expirationDate); + } + + + + /** + * Utility class to parse S3 pre-signed URLs + */ + static class PreSignedUrlParser { + String bucket = ""; + String prefix = ""; + + public PreSignedUrlParser(String preSignedUrl) { + try { + URL url = new URL(preSignedUrl); + String path = url.getPath(); + String[] pathParts = path.split("/"); + + if (pathParts.length < 3) { + throw new IllegalArgumentException("pre-signed url " + preSignedUrl + " must point to a file within a bucket"); + } + if (pathParts.length > 4) { + throw new IllegalArgumentException("pre-signed url " + preSignedUrl + " may only have only subdirectory under a bucket"); + } + this.bucket = pathParts[1]; + if (pathParts.length > 3) { + this.prefix = pathParts[2]; + } + } catch (MalformedURLException ex) { + throw new IllegalArgumentException("pre-signed url " + preSignedUrl + " is not a valid url"); + } + } + + public String getBucket() { + return bucket; + } + + public String getPrefix() { + return prefix; + } + } + + + + + /** + * The following classes have been copied from Amazon's sample code + */ + static class AWSAuthConnection { + public static final String LOCATION_DEFAULT=null; + public static final String LOCATION_EU="EU"; + + private String awsAccessKeyId; + private String awsSecretAccessKey; + private boolean isSecure; + private String server; + private int port; + private CallingFormat callingFormat; + + public AWSAuthConnection(String awsAccessKeyId, String awsSecretAccessKey) { + this(awsAccessKeyId, awsSecretAccessKey, true); + } + + public AWSAuthConnection(String awsAccessKeyId, String awsSecretAccessKey, boolean isSecure) { + this(awsAccessKeyId, awsSecretAccessKey, isSecure, Utils.DEFAULT_HOST); + } + + public AWSAuthConnection(String awsAccessKeyId, String awsSecretAccessKey, boolean isSecure, + String server) { + this(awsAccessKeyId, awsSecretAccessKey, isSecure, server, + isSecure? Utils.SECURE_PORT : Utils.INSECURE_PORT); + } + + public AWSAuthConnection(String awsAccessKeyId, String awsSecretAccessKey, boolean isSecure, + String server, int port) { + this(awsAccessKeyId, awsSecretAccessKey, isSecure, server, port, CallingFormat.getSubdomainCallingFormat()); + + } + + public AWSAuthConnection(String awsAccessKeyId, String awsSecretAccessKey, boolean isSecure, + String server, CallingFormat format) { + this(awsAccessKeyId, awsSecretAccessKey, isSecure, server, + isSecure? Utils.SECURE_PORT : Utils.INSECURE_PORT, + format); + } + + /** + * Create a new interface to interact with S3 with the given credential and connection + * parameters + * @param awsAccessKeyId Your user key into AWS + * @param awsSecretAccessKey The secret string used to generate signatures for authentication. + * @param isSecure use SSL encryption + * @param server Which host to connect to. Usually, this will be s3.amazonaws.com + * @param port Which port to use. + * @param format Type of request Regular/Vanity or Pure Vanity domain + */ + public AWSAuthConnection(String awsAccessKeyId, String awsSecretAccessKey, boolean isSecure, + String server, int port, CallingFormat format) { + this.awsAccessKeyId=awsAccessKeyId; + this.awsSecretAccessKey=awsSecretAccessKey; + this.isSecure=isSecure; + this.server=server; + this.port=port; + this.callingFormat=format; + } + + /** + * Creates a new bucket. + * @param bucket The name of the bucket to create. + * @param headers A Map of String to List of Strings representing the http headers to pass (can be null). + */ + public Response createBucket(String bucket, Map headers) throws IOException { + return createBucket(bucket, null, headers); + } + + /** + * Creates a new bucket. + * @param bucket The name of the bucket to create. + * @param location Desired location ("EU") (or null for default). + * @param headers A Map of String to List of Strings representing the http + * headers to pass (can be null). + * @throws IllegalArgumentException on invalid location + */ + public Response createBucket(String bucket, String location, Map headers) throws IOException { + String body; + if(location == null) { + body=null; + } + else if(LOCATION_EU.equals(location)) { + if(!callingFormat.supportsLocatedBuckets()) + throw new IllegalArgumentException("Creating location-constrained bucket with unsupported calling-format"); + body="" + location + ""; + } + else + throw new IllegalArgumentException("Invalid Location: " + location); + + // validate bucket name + if(!Utils.validateBucketName(bucket, callingFormat)) + throw new IllegalArgumentException("Invalid Bucket Name: " + bucket); + + HttpURLConnection request=makeRequest("PUT", bucket, "", null, headers); + if(body != null) { + request.setDoOutput(true); + request.getOutputStream().write(body.getBytes("UTF-8")); + } + return new Response(request); + } + + /** + * Check if the specified bucket exists (via a HEAD request) + * @param bucket The name of the bucket to check + * @return true if HEAD access returned success + */ + public boolean checkBucketExists(String bucket) throws IOException { + HttpURLConnection response=makeRequest("HEAD", bucket, "", null, null); + int httpCode=response.getResponseCode(); + + if(httpCode >= 200 && httpCode < 300) + return true; + if(httpCode == HttpURLConnection.HTTP_NOT_FOUND) // bucket doesn't exist + return false; + throw new IOException("bucket '" + bucket + "' could not be accessed (rsp=" + + httpCode + " (" + response.getResponseMessage() + "). Maybe the bucket is owned by somebody else or " + + "the authentication failed"); + + } + + /** + * Lists the contents of a bucket. + * @param bucket The name of the bucket to create. + * @param prefix All returned keys will start with this string (can be null). + * @param marker All returned keys will be lexographically greater than + * this string (can be null). + * @param maxKeys The maximum number of keys to return (can be null). + * @param headers A Map of String to List of Strings representing the http + * headers to pass (can be null). + */ + public ListBucketResponse listBucket(String bucket, String prefix, String marker, + Integer maxKeys, Map headers) throws IOException { + return listBucket(bucket, prefix, marker, maxKeys, null, headers); + } + + /** + * Lists the contents of a bucket. + * @param bucket The name of the bucket to list. + * @param prefix All returned keys will start with this string (can be null). + * @param marker All returned keys will be lexographically greater than + * this string (can be null). + * @param maxKeys The maximum number of keys to return (can be null). + * @param delimiter Keys that contain a string between the prefix and the first + * occurrence of the delimiter will be rolled up into a single element. + * @param headers A Map of String to List of Strings representing the http + * headers to pass (can be null). + */ + public ListBucketResponse listBucket(String bucket, String prefix, String marker, + Integer maxKeys, String delimiter, Map headers) throws IOException { + + Map pathArgs=Utils.paramsForListOptions(prefix, marker, maxKeys, delimiter); + return new ListBucketResponse(makeRequest("GET", bucket, "", pathArgs, headers)); + } + + /** + * Deletes a bucket. + * @param bucket The name of the bucket to delete. + * @param headers A Map of String to List of Strings representing the http headers to pass (can be null). + */ + public Response deleteBucket(String bucket, Map headers) throws IOException { + return new Response(makeRequest("DELETE", bucket, "", null, headers)); + } + + /** + * Writes an object to S3. + * @param bucket The name of the bucket to which the object will be added. + * @param key The name of the key to use. + * @param object An S3Object containing the data to write. + * @param headers A Map of String to List of Strings representing the http + * headers to pass (can be null). + */ + public Response put(String bucket, String key, S3Object object, Map headers) throws IOException { + HttpURLConnection request= + makeRequest("PUT", bucket, Utils.urlencode(key), null, headers, object); + + request.setDoOutput(true); + request.getOutputStream().write(object.data == null? new byte[]{} : object.data); + + return new Response(request); + } + + public Response put(String preSignedUrl, S3Object object, Map headers) throws IOException { + HttpURLConnection request = makePreSignedRequest("PUT", preSignedUrl, headers); + request.setDoOutput(true); + request.getOutputStream().write(object.data == null? new byte[]{} : object.data); + + return new Response(request); + } + + /** + * Creates a copy of an existing S3 Object. In this signature, we will copy the + * existing metadata. The default access control policy is private; if you want + * to override it, please use x-amz-acl in the headers. + * @param sourceBucket The name of the bucket where the source object lives. + * @param sourceKey The name of the key to copy. + * @param destinationBucket The name of the bucket to which the object will be added. + * @param destinationKey The name of the key to use. + * @param headers A Map of String to List of Strings representing the http + * headers to pass (can be null). You may wish to set the x-amz-acl header appropriately. + */ + public Response copy(String sourceBucket, String sourceKey, String destinationBucket, String destinationKey, Map headers) + throws IOException { + S3Object object=new S3Object(new byte[]{}, new HashMap()); + headers=headers == null? new HashMap() : new HashMap(headers); + headers.put("x-amz-copy-source", Arrays.asList(sourceBucket + "/" + sourceKey)); + headers.put("x-amz-metadata-directive", Arrays.asList("COPY")); + return verifyCopy(put(destinationBucket, destinationKey, object, headers)); + } + + /** + * Creates a copy of an existing S3 Object. In this signature, we will replace the + * existing metadata. The default access control policy is private; if you want + * to override it, please use x-amz-acl in the headers. + * @param sourceBucket The name of the bucket where the source object lives. + * @param sourceKey The name of the key to copy. + * @param destinationBucket The name of the bucket to which the object will be added. + * @param destinationKey The name of the key to use. + * @param metadata A Map of String to List of Strings representing the S3 metadata + * for the new object. + * @param headers A Map of String to List of Strings representing the http + * headers to pass (can be null). You may wish to set the x-amz-acl header appropriately. + */ + public Response copy(String sourceBucket, String sourceKey, String destinationBucket, String destinationKey, Map metadata, Map headers) + throws IOException { + S3Object object=new S3Object(new byte[]{}, metadata); + headers=headers == null? new HashMap() : new HashMap(headers); + headers.put("x-amz-copy-source", Arrays.asList(sourceBucket + "/" + sourceKey)); + headers.put("x-amz-metadata-directive", Arrays.asList("REPLACE")); + return verifyCopy(put(destinationBucket, destinationKey, object, headers)); + } + + /** + * Copy sometimes returns a successful response and starts to send whitespace + * characters to us. This method processes those whitespace characters and + * will throw an exception if the response is either unknown or an error. + * @param response Response object from the PUT request. + * @return The response with the input stream drained. + * @throws IOException If anything goes wrong. + */ + private static Response verifyCopy(Response response) throws IOException { + if(response.connection.getResponseCode() < 400) { + byte[] body=GetResponse.slurpInputStream(response.connection.getInputStream()); + String message=new String(body); + if(message.contains("")) { + // It worked! + } + else { + throw new IOException("Unexpected response: " + message); + } + } + return response; + } + + /** + * Reads an object from S3. + * @param bucket The name of the bucket where the object lives. + * @param key The name of the key to use. + * @param headers A Map of String to List of Strings representing the http + * headers to pass (can be null). + */ + public GetResponse get(String bucket, String key, Map headers) throws IOException { + return new GetResponse(makeRequest("GET", bucket, Utils.urlencode(key), null, headers)); + } + + /** + * Deletes an object from S3. + * @param bucket The name of the bucket where the object lives. + * @param key The name of the key to use. + * @param headers A Map of String to List of Strings representing the http + * headers to pass (can be null). + */ + public Response delete(String bucket, String key, Map headers) throws IOException { + return new Response(makeRequest("DELETE", bucket, Utils.urlencode(key), null, headers)); + } + + public Response delete(String preSignedUrl) throws IOException { + return new Response(makePreSignedRequest("DELETE", preSignedUrl, null)); + } + + /** + * Get the requestPayment xml document for a given bucket + * @param bucket The name of the bucket + * @param headers A Map of String to List of Strings representing the http + * headers to pass (can be null). + */ + public GetResponse getBucketRequestPayment(String bucket, Map headers) throws IOException { + Map pathArgs=new HashMap(); + pathArgs.put("requestPayment", null); + return new GetResponse(makeRequest("GET", bucket, "", pathArgs, headers)); + } + + /** + * Write a new requestPayment xml document for a given bucket + * @param bucket The name of the bucket + * @param requestPaymentXMLDoc + * @param headers A Map of String to List of Strings representing the http + * headers to pass (can be null). + */ + public Response putBucketRequestPayment(String bucket, String requestPaymentXMLDoc, Map headers) + throws IOException { + Map pathArgs=new HashMap(); + pathArgs.put("requestPayment", null); + S3Object object=new S3Object(requestPaymentXMLDoc.getBytes(), null); + HttpURLConnection request=makeRequest("PUT", bucket, "", pathArgs, headers, object); + + request.setDoOutput(true); + request.getOutputStream().write(object.data == null? new byte[]{} : object.data); + + return new Response(request); + } + + /** + * Get the logging xml document for a given bucket + * @param bucket The name of the bucket + * @param headers A Map of String to List of Strings representing the http headers to pass (can be null). + */ + public GetResponse getBucketLogging(String bucket, Map headers) throws IOException { + Map pathArgs=new HashMap(); + pathArgs.put("logging", null); + return new GetResponse(makeRequest("GET", bucket, "", pathArgs, headers)); + } + + /** + * Write a new logging xml document for a given bucket + * @param loggingXMLDoc The xml representation of the logging configuration as a String + * @param bucket The name of the bucket + * @param headers A Map of String to List of Strings representing the http + * headers to pass (can be null). + */ + public Response putBucketLogging(String bucket, String loggingXMLDoc, Map headers) throws IOException { + Map pathArgs=new HashMap(); + pathArgs.put("logging", null); + S3Object object=new S3Object(loggingXMLDoc.getBytes(), null); + HttpURLConnection request=makeRequest("PUT", bucket, "", pathArgs, headers, object); + + request.setDoOutput(true); + request.getOutputStream().write(object.data == null? new byte[]{} : object.data); + + return new Response(request); + } + + /** + * Get the ACL for a given bucket + * @param bucket The name of the bucket where the object lives. + * @param headers A Map of String to List of Strings representing the http + * headers to pass (can be null). + */ + public GetResponse getBucketACL(String bucket, Map headers) throws IOException { + return getACL(bucket, "", headers); + } + + /** + * Get the ACL for a given object (or bucket, if key is null). + * @param bucket The name of the bucket where the object lives. + * @param key The name of the key to use. + * @param headers A Map of String to List of Strings representing the http + * headers to pass (can be null). + */ + public GetResponse getACL(String bucket, String key, Map headers) throws IOException { + if(key == null) key=""; + + Map pathArgs=new HashMap(); + pathArgs.put("acl", null); + + return new GetResponse( + makeRequest("GET", bucket, Utils.urlencode(key), pathArgs, headers) + ); + } + + /** + * Write a new ACL for a given bucket + * @param aclXMLDoc The xml representation of the ACL as a String + * @param bucket The name of the bucket where the object lives. + * @param headers A Map of String to List of Strings representing the http headers to pass (can be null). + */ + public Response putBucketACL(String bucket, String aclXMLDoc, Map headers) throws IOException { + return putACL(bucket, "", aclXMLDoc, headers); + } + + /** + * Write a new ACL for a given object + * @param aclXMLDoc The xml representation of the ACL as a String + * @param bucket The name of the bucket where the object lives. + * @param key The name of the key to use. + * @param headers A Map of String to List of Strings representing the http + * headers to pass (can be null). + */ + public Response putACL(String bucket, String key, String aclXMLDoc, Map headers) + throws IOException { + S3Object object=new S3Object(aclXMLDoc.getBytes(), null); + + Map pathArgs=new HashMap(); + pathArgs.put("acl", null); + + HttpURLConnection request= + makeRequest("PUT", bucket, Utils.urlencode(key), pathArgs, headers, object); + + request.setDoOutput(true); + request.getOutputStream().write(object.data == null? new byte[]{} : object.data); + + return new Response(request); + } + + public LocationResponse getBucketLocation(String bucket) + throws IOException { + Map pathArgs=new HashMap(); + pathArgs.put("location", null); + return new LocationResponse(makeRequest("GET", bucket, "", pathArgs, null)); + } + + + /** + * List all the buckets created by this account. + * @param headers A Map of String to List of Strings representing the http + * headers to pass (can be null). + */ + public ListAllMyBucketsResponse listAllMyBuckets(Map headers) + throws IOException { + return new ListAllMyBucketsResponse(makeRequest("GET", "", "", null, headers)); + } + + + /** + * Make a new HttpURLConnection without passing an S3Object parameter. + * Use this method for key operations that do require arguments + * @param method The method to invoke + * @param bucketName the bucket this request is for + * @param key the key this request is for + * @param pathArgs the + * @param headers + * @return + * @throws MalformedURLException + * @throws IOException + */ + private HttpURLConnection makeRequest(String method, String bucketName, String key, Map pathArgs, Map headers) + throws IOException { + return makeRequest(method, bucketName, key, pathArgs, headers, null); + } + + + /** + * Make a new HttpURLConnection. + * @param method The HTTP method to use (GET, PUT, DELETE) + * @param bucket The bucket name this request affects + * @param key The key this request is for + * @param pathArgs parameters if any to be sent along this request + * @param headers A Map of String to List of Strings representing the http + * headers to pass (can be null). + * @param object The S3Object that is to be written (can be null). + */ + private HttpURLConnection makeRequest(String method, String bucket, String key, Map pathArgs, Map headers, + S3Object object) + throws IOException { + CallingFormat format=Utils.getCallingFormatForBucket(this.callingFormat, bucket); + if(isSecure && format != CallingFormat.getPathCallingFormat() && bucket.contains(".")) { + System.err.println("You are making an SSL connection, however, the bucket contains periods and the wildcard certificate will not match by default. Please consider using HTTP."); + } + + // build the domain based on the calling format + URL url=format.getURL(isSecure, server, this.port, bucket, key, pathArgs); + + HttpURLConnection connection=(HttpURLConnection)url.openConnection(); + connection.setRequestMethod(method); + + // subdomain-style urls may encounter http redirects. + // Ensure that redirects are supported. + if(!connection.getInstanceFollowRedirects() + && format.supportsLocatedBuckets()) + throw new RuntimeException("HTTP redirect support required."); + + addHeaders(connection, headers); + if(object != null) addMetadataHeaders(connection, object.metadata); + addAuthHeader(connection, method, bucket, key, pathArgs); + + return connection; + } + + private HttpURLConnection makePreSignedRequest(String method, String preSignedUrl, Map headers) throws IOException { + URL url = new URL(preSignedUrl); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod(method); + + addHeaders(connection, headers); + + return connection; + } + + /** + * Add the given headers to the HttpURLConnection. + * @param connection The HttpURLConnection to which the headers will be added. + * @param headers A Map of String to List of Strings representing the http + * headers to pass (can be null). + */ + private static void addHeaders(HttpURLConnection connection, Map headers) { + addHeaders(connection, headers, ""); + } + + /** + * Add the given metadata fields to the HttpURLConnection. + * @param connection The HttpURLConnection to which the headers will be added. + * @param metadata A Map of String to List of Strings representing the s3 + * metadata for this resource. + */ + private static void addMetadataHeaders(HttpURLConnection connection, Map metadata) { + addHeaders(connection, metadata, Utils.METADATA_PREFIX); + } + + /** + * Add the given headers to the HttpURLConnection with a prefix before the keys. + * @param connection The HttpURLConnection to which the headers will be added. + * @param headers A Map of String to List of Strings representing the http + * headers to pass (can be null). + * @param prefix The string to prepend to each key before adding it to the connection. + */ + private static void addHeaders(HttpURLConnection connection, Map headers, String prefix) { + if(headers != null) { + for(Iterator i=headers.keySet().iterator(); i.hasNext();) { + String key=(String)i.next(); + for(Iterator j=((List)headers.get(key)).iterator(); j.hasNext();) { + String value=(String)j.next(); + connection.addRequestProperty(prefix + key, value); + } + } + } + } + + /** + * Add the appropriate Authorization header to the HttpURLConnection. + * @param connection The HttpURLConnection to which the header will be added. + * @param method The HTTP method to use (GET, PUT, DELETE) + * @param bucket the bucket name this request is for + * @param key the key this request is for + * @param pathArgs path arguments which are part of this request + */ + private void addAuthHeader(HttpURLConnection connection, String method, String bucket, String key, Map pathArgs) { + if(connection.getRequestProperty("Date") == null) { + connection.setRequestProperty("Date", httpDate()); + } + if(connection.getRequestProperty("Content-Type") == null) { + connection.setRequestProperty("Content-Type", ""); + } + + if(this.awsAccessKeyId != null && this.awsSecretAccessKey != null) { + String canonicalString= + Utils.makeCanonicalString(method, bucket, key, pathArgs, connection.getRequestProperties()); + String encodedCanonical=Utils.encode(this.awsSecretAccessKey, canonicalString, false); + connection.setRequestProperty("Authorization", + "AWS " + this.awsAccessKeyId + ":" + encodedCanonical); + } + } + + + /** + * Generate an rfc822 date for use in the Date HTTP header. + */ + public static String httpDate() { + final String DateFormat="EEE, dd MMM yyyy HH:mm:ss "; + SimpleDateFormat format=new SimpleDateFormat(DateFormat, Locale.US); + format.setTimeZone(TimeZone.getTimeZone("GMT")); + return format.format(new Date()) + "GMT"; + } + } + + static class ListEntry { + /** + * The name of the object + */ + public String key; + + /** + * The date at which the object was last modified. + */ + public Date lastModified; + + /** + * The object's ETag, which can be used for conditional GETs. + */ + public String eTag; + + /** + * The size of the object in bytes. + */ + public long size; + + /** + * The object's storage class + */ + public String storageClass; + + /** + * The object's owner + */ + public Owner owner; + + public String toString() { + return key; + } + } + + static class Owner { + public String id; + public String displayName; + } + + + static class Response { + public HttpURLConnection connection; + + public Response(HttpURLConnection connection) throws IOException { + this.connection=connection; + } + } + + + static class GetResponse extends Response { + public S3Object object; + + /** + * Pulls a representation of an S3Object out of the HttpURLConnection response. + */ + public GetResponse(HttpURLConnection connection) throws IOException { + super(connection); + if(connection.getResponseCode() < 400) { + Map metadata=extractMetadata(connection); + byte[] body=slurpInputStream(connection.getInputStream()); + this.object=new S3Object(body, metadata); + } + } + + /** + * Examines the response's header fields and returns a Map from String to List of Strings + * representing the object's metadata. + */ + private static Map extractMetadata(HttpURLConnection connection) { + TreeMap metadata=new TreeMap(); + Map headers=connection.getHeaderFields(); + for(Iterator i=headers.keySet().iterator(); i.hasNext();) { + String key=(String)i.next(); + if(key == null) continue; + if(key.startsWith(Utils.METADATA_PREFIX)) { + metadata.put(key.substring(Utils.METADATA_PREFIX.length()), headers.get(key)); + } + } + + return metadata; + } + + /** + * Read the input stream and dump it all into a big byte array + */ + static byte[] slurpInputStream(InputStream stream) throws IOException { + final int chunkSize=2048; + byte[] buf=new byte[chunkSize]; + ByteArrayOutputStream byteStream=new ByteArrayOutputStream(chunkSize); + int count; + + while((count=stream.read(buf)) != -1) byteStream.write(buf, 0, count); + + return byteStream.toByteArray(); + } + } + + static class LocationResponse extends Response { + String location; + + /** + * Parse the response to a ?location query. + */ + public LocationResponse(HttpURLConnection connection) throws IOException { + super(connection); + if(connection.getResponseCode() < 400) { + try { + XMLReader xr=Utils.createXMLReader(); + ; + LocationResponseHandler handler=new LocationResponseHandler(); + xr.setContentHandler(handler); + xr.setErrorHandler(handler); + + xr.parse(new InputSource(connection.getInputStream())); + this.location=handler.loc; + } + catch(SAXException e) { + throw new RuntimeException("Unexpected error parsing ListAllMyBuckets xml", e); + } + } + else { + this.location=""; + } + } + + /** + * Report the location-constraint for a bucket. + * A value of null indicates an error; + * the empty string indicates no constraint; + * and any other value is an actual location constraint value. + */ + public String getLocation() { + return location; + } + + /** + * Helper class to parse LocationConstraint response XML + */ + static class LocationResponseHandler extends DefaultHandler { + String loc=null; + private StringBuffer currText=null; + + public void startDocument() { + } + + public void startElement(String uri, String name, String qName, Attributes attrs) { + if(name.equals("LocationConstraint")) { + this.currText=new StringBuffer(); + } + } + + public void endElement(String uri, String name, String qName) { + if(name.equals("LocationConstraint")) { + loc=this.currText.toString(); + this.currText=null; + } + } + + public void characters(char ch[], int start, int length) { + if(currText != null) + this.currText.append(ch, start, length); + } + } + } + + + static class Bucket { + /** + * The name of the bucket. + */ + public String name; + + /** + * The bucket's creation date. + */ + public Date creationDate; + + public Bucket() { + this.name=null; + this.creationDate=null; + } + + public Bucket(String name, Date creationDate) { + this.name=name; + this.creationDate=creationDate; + } + + public String toString() { + return this.name; + } + } + + static class ListBucketResponse extends Response { + + /** + * The name of the bucket being listed. Null if request fails. + */ + public String name=null; + + /** + * The prefix echoed back from the request. Null if request fails. + */ + public String prefix=null; + + /** + * The marker echoed back from the request. Null if request fails. + */ + public String marker=null; + + /** + * The delimiter echoed back from the request. Null if not specified in + * the request, or if it fails. + */ + public String delimiter=null; + + /** + * The maxKeys echoed back from the request if specified. 0 if request fails. + */ + public int maxKeys=0; + + /** + * Indicates if there are more results to the list. True if the current + * list results have been truncated. false if request fails. + */ + public boolean isTruncated=false; + + /** + * Indicates what to use as a marker for subsequent list requests in the event + * that the results are truncated. Present only when a delimiter is specified. + * Null if request fails. + */ + public String nextMarker=null; + + /** + * A List of ListEntry objects representing the objects in the given bucket. + * Null if the request fails. + */ + public List entries=null; + + /** + * A List of CommonPrefixEntry objects representing the common prefixes of the + * keys that matched up to the delimiter. Null if the request fails. + */ + public List commonPrefixEntries=null; + + public ListBucketResponse(HttpURLConnection connection) throws IOException { + super(connection); + if(connection.getResponseCode() < 400) { + try { + XMLReader xr=Utils.createXMLReader(); + ListBucketHandler handler=new ListBucketHandler(); + xr.setContentHandler(handler); + xr.setErrorHandler(handler); + + xr.parse(new InputSource(connection.getInputStream())); + + this.name=handler.getName(); + this.prefix=handler.getPrefix(); + this.marker=handler.getMarker(); + this.delimiter=handler.getDelimiter(); + this.maxKeys=handler.getMaxKeys(); + this.isTruncated=handler.getIsTruncated(); + this.nextMarker=handler.getNextMarker(); + this.entries=handler.getKeyEntries(); + this.commonPrefixEntries=handler.getCommonPrefixEntries(); + + } + catch(SAXException e) { + throw new RuntimeException("Unexpected error parsing ListBucket xml", e); + } + } + } + + static class ListBucketHandler extends DefaultHandler { + + private String name=null; + private String prefix=null; + private String marker=null; + private String delimiter=null; + private int maxKeys=0; + private boolean isTruncated=false; + private String nextMarker=null; + private boolean isEchoedPrefix=false; + private List keyEntries=null; + private ListEntry keyEntry=null; + private List commonPrefixEntries=null; + private CommonPrefixEntry commonPrefixEntry=null; + private StringBuffer currText=null; + private SimpleDateFormat iso8601Parser=null; + + public ListBucketHandler() { + super(); + keyEntries=new ArrayList(); + commonPrefixEntries=new ArrayList(); + this.iso8601Parser=new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + this.iso8601Parser.setTimeZone(new SimpleTimeZone(0, "GMT")); + this.currText=new StringBuffer(); + } + + public void startDocument() { + this.isEchoedPrefix=true; + } + + public void endDocument() { + // ignore + } + + public void startElement(String uri, String name, String qName, Attributes attrs) { + if(name.equals("Contents")) { + this.keyEntry=new ListEntry(); + } + else if(name.equals("Owner")) { + this.keyEntry.owner=new Owner(); + } + else if(name.equals("CommonPrefixes")) { + this.commonPrefixEntry=new CommonPrefixEntry(); + } + } + + public void endElement(String uri, String name, String qName) { + if(name.equals("Name")) { + this.name=this.currText.toString(); + } + // this prefix is the one we echo back from the request + else if(name.equals("Prefix") && this.isEchoedPrefix) { + this.prefix=this.currText.toString(); + this.isEchoedPrefix=false; + } + else if(name.equals("Marker")) { + this.marker=this.currText.toString(); + } + else if(name.equals("MaxKeys")) { + this.maxKeys=Integer.parseInt(this.currText.toString()); + } + else if(name.equals("Delimiter")) { + this.delimiter=this.currText.toString(); + } + else if(name.equals("IsTruncated")) { + this.isTruncated=Boolean.valueOf(this.currText.toString()); + } + else if(name.equals("NextMarker")) { + this.nextMarker=this.currText.toString(); + } + else if(name.equals("Contents")) { + this.keyEntries.add(this.keyEntry); + } + else if(name.equals("Key")) { + this.keyEntry.key=this.currText.toString(); + } + else if(name.equals("LastModified")) { + try { + this.keyEntry.lastModified=this.iso8601Parser.parse(this.currText.toString()); + } + catch(ParseException e) { + throw new RuntimeException("Unexpected date format in list bucket output", e); + } + } + else if(name.equals("ETag")) { + this.keyEntry.eTag=this.currText.toString(); + } + else if(name.equals("Size")) { + this.keyEntry.size=Long.parseLong(this.currText.toString()); + } + else if(name.equals("StorageClass")) { + this.keyEntry.storageClass=this.currText.toString(); + } + else if(name.equals("ID")) { + this.keyEntry.owner.id=this.currText.toString(); + } + else if(name.equals("DisplayName")) { + this.keyEntry.owner.displayName=this.currText.toString(); + } + else if(name.equals("CommonPrefixes")) { + this.commonPrefixEntries.add(this.commonPrefixEntry); + } + // this is the common prefix for keys that match up to the delimiter + else if(name.equals("Prefix")) { + this.commonPrefixEntry.prefix=this.currText.toString(); + } + if(this.currText.length() != 0) + this.currText=new StringBuffer(); + } + + public void characters(char ch[], int start, int length) { + this.currText.append(ch, start, length); + } + + public String getName() { + return this.name; + } + + public String getPrefix() { + return this.prefix; + } + + public String getMarker() { + return this.marker; + } + + public String getDelimiter() { + return this.delimiter; + } + + public int getMaxKeys() { + return this.maxKeys; + } + + public boolean getIsTruncated() { + return this.isTruncated; + } + + public String getNextMarker() { + return this.nextMarker; + } + + public List getKeyEntries() { + return this.keyEntries; + } + + public List getCommonPrefixEntries() { + return this.commonPrefixEntries; + } + } + } + + + static class CommonPrefixEntry { + /** + * The prefix common to the delimited keys it represents + */ + public String prefix; + } + + + static class ListAllMyBucketsResponse extends Response { + /** + * A list of Bucket objects, one for each of this account's buckets. Will be null if + * the request fails. + */ + public List entries; + + public ListAllMyBucketsResponse(HttpURLConnection connection) throws IOException { + super(connection); + if(connection.getResponseCode() < 400) { + try { + XMLReader xr=Utils.createXMLReader(); + ; + ListAllMyBucketsHandler handler=new ListAllMyBucketsHandler(); + xr.setContentHandler(handler); + xr.setErrorHandler(handler); + + xr.parse(new InputSource(connection.getInputStream())); + this.entries=handler.getEntries(); + } + catch(SAXException e) { + throw new RuntimeException("Unexpected error parsing ListAllMyBuckets xml", e); + } + } + } + + static class ListAllMyBucketsHandler extends DefaultHandler { + + private List entries=null; + private Bucket currBucket=null; + private StringBuffer currText=null; + private SimpleDateFormat iso8601Parser=null; + + public ListAllMyBucketsHandler() { + super(); + entries=new ArrayList(); + this.iso8601Parser=new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + this.iso8601Parser.setTimeZone(new SimpleTimeZone(0, "GMT")); + this.currText=new StringBuffer(); + } + + public void startDocument() { + // ignore + } + + public void endDocument() { + // ignore + } + + public void startElement(String uri, String name, String qName, Attributes attrs) { + if(name.equals("Bucket")) { + this.currBucket=new Bucket(); + } + } + + public void endElement(String uri, String name, String qName) { + if(name.equals("Bucket")) { + this.entries.add(this.currBucket); + } + else if(name.equals("Name")) { + this.currBucket.name=this.currText.toString(); + } + else if(name.equals("CreationDate")) { + try { + this.currBucket.creationDate=this.iso8601Parser.parse(this.currText.toString()); + } + catch(ParseException e) { + throw new RuntimeException("Unexpected date format in list bucket output", e); + } + } + this.currText=new StringBuffer(); + } + + public void characters(char ch[], int start, int length) { + this.currText.append(ch, start, length); + } + + public List getEntries() { + return this.entries; + } + } + } + + + static class S3Object { + + public byte[] data; + + /** + * A Map from String to List of Strings representing the object's metadata + */ + public Map metadata; + + public S3Object(byte[] data, Map metadata) { + this.data=data; + this.metadata=metadata; + } + } + + + abstract static class CallingFormat { + + protected static CallingFormat pathCallingFormat=new PathCallingFormat(); + protected static CallingFormat subdomainCallingFormat=new SubdomainCallingFormat(); + protected static CallingFormat vanityCallingFormat=new VanityCallingFormat(); + + public abstract boolean supportsLocatedBuckets(); + + public abstract String getEndpoint(String server, int port, String bucket); + + public abstract String getPathBase(String bucket, String key); + + public abstract URL getURL(boolean isSecure, String server, int port, String bucket, String key, Map pathArgs) + throws MalformedURLException; + + public static CallingFormat getPathCallingFormat() { + return pathCallingFormat; + } + + public static CallingFormat getSubdomainCallingFormat() { + return subdomainCallingFormat; + } + + public static CallingFormat getVanityCallingFormat() { + return vanityCallingFormat; + } + + private static class PathCallingFormat extends CallingFormat { + public boolean supportsLocatedBuckets() { + return false; + } + + public String getPathBase(String bucket, String key) { + return isBucketSpecified(bucket)? "/" + bucket + "/" + key : "/"; + } + + public String getEndpoint(String server, int port, String bucket) { + return server + ":" + port; + } + + public URL getURL(boolean isSecure, String server, int port, String bucket, String key, Map pathArgs) + throws MalformedURLException { + String pathBase=isBucketSpecified(bucket)? "/" + bucket + "/" + key : "/"; + String pathArguments=Utils.convertPathArgsHashToString(pathArgs); + return new URL(isSecure? "https" : "http", server, port, pathBase + pathArguments); + } + + private static boolean isBucketSpecified(String bucket) { + return bucket != null && bucket.length() != 0; + } + } + + private static class SubdomainCallingFormat extends CallingFormat { + public boolean supportsLocatedBuckets() { + return true; + } + + public String getServer(String server, String bucket) { + return bucket + "." + server; + } + + public String getEndpoint(String server, int port, String bucket) { + return getServer(server, bucket) + ":" + port; + } + + public String getPathBase(String bucket, String key) { + return "/" + key; + } + + public URL getURL(boolean isSecure, String server, int port, String bucket, String key, Map pathArgs) + throws MalformedURLException { + if(bucket == null || bucket.length() == 0) { + //The bucket is null, this is listAllBuckets request + String pathArguments=Utils.convertPathArgsHashToString(pathArgs); + return new URL(isSecure? "https" : "http", server, port, "/" + pathArguments); + } + else { + String serverToUse=getServer(server, bucket); + String pathBase=getPathBase(bucket, key); + String pathArguments=Utils.convertPathArgsHashToString(pathArgs); + return new URL(isSecure? "https" : "http", serverToUse, port, pathBase + pathArguments); + } + } + } + + private static class VanityCallingFormat extends SubdomainCallingFormat { + public String getServer(String server, String bucket) { + return bucket; + } + } + } + + static class Utils { + static final String METADATA_PREFIX="x-amz-meta-"; + static final String AMAZON_HEADER_PREFIX="x-amz-"; + static final String ALTERNATIVE_DATE_HEADER="x-amz-date"; + public static final String DEFAULT_HOST="s3.amazonaws.com"; + + public static final int SECURE_PORT=443; + public static final int INSECURE_PORT=80; + + + /** + * HMAC/SHA1 Algorithm per RFC 2104. + */ + private static final String HMAC_SHA1_ALGORITHM="HmacSHA1"; + + static String makeCanonicalString(String method, String bucket, String key, Map pathArgs, Map headers) { + return makeCanonicalString(method, bucket, key, pathArgs, headers, null); + } + + /** + * Calculate the canonical string. When expires is non-null, it will be + * used instead of the Date header. + */ + static String makeCanonicalString(String method, String bucketName, String key, Map pathArgs, + Map headers, String expires) { + StringBuilder buf=new StringBuilder(); + buf.append(method + "\n"); + + // Add all interesting headers to a list, then sort them. "Interesting" + // is defined as Content-MD5, Content-Type, Date, and x-amz- + SortedMap interestingHeaders=new TreeMap(); + if(headers != null) { + for(Iterator i=headers.keySet().iterator(); i.hasNext();) { + String hashKey=(String)i.next(); + if(hashKey == null) continue; + String lk=hashKey.toLowerCase(); + + // Ignore any headers that are not particularly interesting. + if(lk.equals("content-type") || lk.equals("content-md5") || lk.equals("date") || + lk.startsWith(AMAZON_HEADER_PREFIX)) { + List s=(List)headers.get(hashKey); + interestingHeaders.put(lk, concatenateList(s)); + } + } + } + + if(interestingHeaders.containsKey(ALTERNATIVE_DATE_HEADER)) { + interestingHeaders.put("date", ""); + } + + // if the expires is non-null, use that for the date field. this + // trumps the x-amz-date behavior. + if(expires != null) { + interestingHeaders.put("date", expires); + } + + // these headers require that we still put a new line in after them, + // even if they don't exist. + if(!interestingHeaders.containsKey("content-type")) { + interestingHeaders.put("content-type", ""); + } + if(!interestingHeaders.containsKey("content-md5")) { + interestingHeaders.put("content-md5", ""); + } + + // Finally, add all the interesting headers (i.e.: all that startwith x-amz- ;-)) + for(Iterator i=interestingHeaders.keySet().iterator(); i.hasNext();) { + String headerKey=(String)i.next(); + if(headerKey.startsWith(AMAZON_HEADER_PREFIX)) { + buf.append(headerKey).append(':').append(interestingHeaders.get(headerKey)); + } + else { + buf.append(interestingHeaders.get(headerKey)); + } + buf.append("\n"); + } + + // build the path using the bucket and key + if(bucketName != null && bucketName.length() != 0) { + buf.append("/" + bucketName); + } + + // append the key (it might be an empty string) + // append a slash regardless + buf.append("/"); + if(key != null) { + buf.append(key); + } + + // if there is an acl, logging or torrent parameter + // add them to the string + if(pathArgs != null) { + if(pathArgs.containsKey("acl")) { + buf.append("?acl"); + } + else if(pathArgs.containsKey("torrent")) { + buf.append("?torrent"); + } + else if(pathArgs.containsKey("logging")) { + buf.append("?logging"); + } + else if(pathArgs.containsKey("location")) { + buf.append("?location"); + } + } + + return buf.toString(); + + } + + /** + * Calculate the HMAC/SHA1 on a string. + * @return Signature + * @throws java.security.NoSuchAlgorithmException + * If the algorithm does not exist. Unlikely + * @throws java.security.InvalidKeyException + * If the key is invalid. + */ + static String encode(String awsSecretAccessKey, String canonicalString, + boolean urlencode) { + // The following HMAC/SHA1 code for the signature is taken from the + // AWS Platform's implementation of RFC2104 (amazon.webservices.common.Signature) + // + // Acquire an HMAC/SHA1 from the raw key bytes. + SecretKeySpec signingKey= + new SecretKeySpec(awsSecretAccessKey.getBytes(), HMAC_SHA1_ALGORITHM); + + // Acquire the MAC instance and initialize with the signing key. + Mac mac=null; + try { + mac=Mac.getInstance(HMAC_SHA1_ALGORITHM); + } + catch(NoSuchAlgorithmException e) { + // should not happen + throw new RuntimeException("Could not find sha1 algorithm", e); + } + try { + mac.init(signingKey); + } + catch(InvalidKeyException e) { + // also should not happen + throw new RuntimeException("Could not initialize the MAC algorithm", e); + } + + // Compute the HMAC on the digest, and set it. + String b64=Base64.encodeBytes(mac.doFinal(canonicalString.getBytes())); + + if(urlencode) { + return urlencode(b64); + } + else { + return b64; + } + } + + static Map paramsForListOptions(String prefix, String marker, Integer maxKeys) { + return paramsForListOptions(prefix, marker, maxKeys, null); + } + + static Map paramsForListOptions(String prefix, String marker, Integer maxKeys, String delimiter) { + + Map argParams=new HashMap(); + // these three params must be url encoded + if(prefix != null) + argParams.put("prefix", urlencode(prefix)); + if(marker != null) + argParams.put("marker", urlencode(marker)); + if(delimiter != null) + argParams.put("delimiter", urlencode(delimiter)); + + if(maxKeys != null) + argParams.put("max-keys", Integer.toString(maxKeys.intValue())); + + return argParams; + + } + + /** + * Converts the Path Arguments from a map to String which can be used in url construction + * @param pathArgs a map of arguments + * @return a string representation of pathArgs + */ + public static String convertPathArgsHashToString(Map pathArgs) { + StringBuilder pathArgsString=new StringBuilder(); + String argumentValue; + boolean firstRun=true; + if(pathArgs != null) { + for(Iterator argumentIterator=pathArgs.keySet().iterator(); argumentIterator.hasNext();) { + String argument=(String)argumentIterator.next(); + if(firstRun) { + firstRun=false; + pathArgsString.append("?"); + } + else { + pathArgsString.append("&"); + } + + argumentValue=(String)pathArgs.get(argument); + pathArgsString.append(argument); + if(argumentValue != null) { + pathArgsString.append("="); + pathArgsString.append(argumentValue); + } + } + } + + return pathArgsString.toString(); + } + + + static String urlencode(String unencoded) { + try { + return URLEncoder.encode(unencoded, "UTF-8"); + } + catch(UnsupportedEncodingException e) { + // should never happen + throw new RuntimeException("Could not url encode to UTF-8", e); + } + } + + static XMLReader createXMLReader() { + try { + return XMLReaderFactory.createXMLReader(); + } + catch(SAXException e) { + // oops, lets try doing this (needed in 1.4) + System.setProperty("org.xml.sax.driver", "org.apache.crimson.parser.XMLReaderImpl"); + } + try { + // try once more + return XMLReaderFactory.createXMLReader(); + } + catch(SAXException e) { + throw new RuntimeException("Couldn't initialize a sax driver for the XMLReader"); + } + } + + /** + * Concatenates a bunch of header values, seperating them with a comma. + * @param values List of header values. + * @return String of all headers, with commas. + */ + private static String concatenateList(List values) { + StringBuilder buf=new StringBuilder(); + for(int i=0, size=values.size(); i < size; ++i) { + buf.append(((String)values.get(i)).replaceAll("\n", "").trim()); + if(i != (size - 1)) { + buf.append(","); + } + } + return buf.toString(); + } + + /** + * Validate bucket-name + */ + static boolean validateBucketName(String bucketName, CallingFormat callingFormat) { + if(callingFormat == CallingFormat.getPathCallingFormat()) { + final int MIN_BUCKET_LENGTH=3; + final int MAX_BUCKET_LENGTH=255; + final String BUCKET_NAME_REGEX="^[0-9A-Za-z\\.\\-_]*$"; + + return null != bucketName && + bucketName.length() >= MIN_BUCKET_LENGTH && + bucketName.length() <= MAX_BUCKET_LENGTH && + bucketName.matches(BUCKET_NAME_REGEX); + } + else { + return isValidSubdomainBucketName(bucketName); + } + } + + static boolean isValidSubdomainBucketName(String bucketName) { + final int MIN_BUCKET_LENGTH=3; + final int MAX_BUCKET_LENGTH=63; + // don't allow names that look like 127.0.0.1 + final String IPv4_REGEX="^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$"; + // dns sub-name restrictions + final String BUCKET_NAME_REGEX="^[a-z0-9]([a-z0-9\\-\\_]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9\\-\\_]*[a-z0-9])?)*$"; + + // If there wasn't a location-constraint, then the current actual + // restriction is just that no 'part' of the name (i.e. sequence + // of characters between any 2 '.'s has to be 63) but the recommendation + // is to keep the entire bucket name under 63. + return null != bucketName && + bucketName.length() >= MIN_BUCKET_LENGTH && + bucketName.length() <= MAX_BUCKET_LENGTH && + !bucketName.matches(IPv4_REGEX) && + bucketName.matches(BUCKET_NAME_REGEX); + } + + static CallingFormat getCallingFormatForBucket(CallingFormat desiredFormat, String bucketName) { + CallingFormat callingFormat=desiredFormat; + if(callingFormat == CallingFormat.getSubdomainCallingFormat() && !Utils.isValidSubdomainBucketName(bucketName)) { + callingFormat=CallingFormat.getPathCallingFormat(); + } + return callingFormat; + } + + public static String generateQueryStringAuthentication(String awsAccessKey, String awsSecretAccessKey, + String method, String bucket, String key, + Map pathArgs, Map headers) { + int defaultExpiresIn = 300; // 5 minutes + long expirationDate = (System.currentTimeMillis() / 1000) + defaultExpiresIn; + return generateQueryStringAuthentication(awsAccessKey, awsSecretAccessKey, + method, bucket, key, + pathArgs, headers, expirationDate); + } + + public static String generateQueryStringAuthentication(String awsAccessKey, String awsSecretAccessKey, + String method, String bucket, String key, + Map pathArgs, Map headers, long expirationDate) { + method = method.toUpperCase(); // Method should always be uppercase + String canonicalString = + makeCanonicalString(method, bucket, key, pathArgs, headers, "" + expirationDate); + String encodedCanonical = encode(awsSecretAccessKey, canonicalString, true); + return "http://" + DEFAULT_HOST + "/" + bucket + "/" + key + "?" + + "AWSAccessKeyId=" + awsAccessKey + "&Expires=" + expirationDate + + "&Signature=" + encodedCanonical; + } + } + + +// +// NOTE: The following source code is the iHarder.net public domain +// Base64 library and is provided here as a convenience. For updates, +// problems, questions, etc. regarding this code, please visit: +// http://iharder.sourceforge.net/current/java/base64/ +// + + + /** + * Encodes and decodes to and from Base64 notation. + *

            + *

            + * Change Log: + *

            + *
              + *
            • v2.1 - Cleaned up javadoc comments and unused variables and methods. Added + * some convenience methods for reading and writing to and from files.
            • + *
            • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems + * with other encodings (like EBCDIC).
            • + *
            • v2.0.1 - Fixed an error when decoding a single byte, that is, when the + * encoded data was a single byte.
            • + *
            • v2.0 - I got rid of methods that used booleans to set options. + * Now everything is more consolidated and cleaner. The code now detects + * when data that's being decoded is gzip-compressed and will decompress it + * automatically. Generally things are cleaner. You'll probably have to + * change some method calls that you were making to support the new + * options format (ints that you "OR" together).
            • + *
            • v1.5.1 - Fixed bug when decompressing and decoding to a + * byte[] using decode( String s, boolean gzipCompressed ). + * Added the ability to "suspend" encoding in the Output Stream so + * you can turn on and off the encoding if you need to embed base64 + * data in an otherwise "normal" stream (like an XML file).
            • + *
            • v1.5 - Output stream pases on flush() command but doesn't do anything itself. + * This helps when using GZIP streams. + * Added the ability to GZip-compress objects before encoding them.
            • + *
            • v1.4 - Added helper methods to read/write files.
            • + *
            • v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
            • + *
            • v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream + * where last buffer being read, if not completely full, was not returned.
            • + *
            • v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
            • + *
            • v1.3.3 - Fixed I/O streams which were totally messed up.
            • + *
            + *

            + *

            + * I am placing this code in the Public Domain. Do with it as you will. + * This software comes with no guarantees or warranties but with + * plenty of well-wishing instead! + * Please visit http://iharder.net/base64 + * periodically to check for updates or to contribute improvements. + *

            + * @author Robert Harder + * @author rob@iharder.net + * @version 2.1 + */ + static class Base64 { + +/* ******** P U B L I C F I E L D S ******** */ + + + /** + * No options specified. Value is zero. + */ + public final static int NO_OPTIONS=0; + + /** + * Specify encoding. + */ + public final static int ENCODE=1; + + + /** + * Specify decoding. + */ + public final static int DECODE=0; + + + /** + * Specify that data should be gzip-compressed. + */ + public final static int GZIP=2; + + + /** + * Don't break lines when encoding (violates strict Base64 specification) + */ + public final static int DONT_BREAK_LINES=8; + + +/* ******** P R I V A T E F I E L D S ******** */ + + + /** + * Maximum line length (76) of Base64 output. + */ + private final static int MAX_LINE_LENGTH=76; + + + /** + * The equals sign (=) as a byte. + */ + private final static byte EQUALS_SIGN=(byte)'='; + + + /** + * The new line character (\n) as a byte. + */ + private final static byte NEW_LINE=(byte)'\n'; + + + /** + * Preferred encoding. + */ + private final static String PREFERRED_ENCODING="UTF-8"; + + + /** + * The 64 valid Base64 values. + */ + private static final byte[] ALPHABET; + private static final byte[] _NATIVE_ALPHABET= /* May be something funny like EBCDIC */ + { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', + (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' + }; + + /** Determine which ALPHABET to use. */ + static { + byte[] __bytes; + try { + __bytes="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes(PREFERRED_ENCODING); + } // end try + catch(java.io.UnsupportedEncodingException use) { + __bytes=_NATIVE_ALPHABET; // Fall back to native encoding + } // end catch + ALPHABET=__bytes; + } // end static + + + /** + * Translates a Base64 value to either its 6-bit reconstruction value + * or a negative number indicating some other meaning. + */ + private final static byte[] DECODABET= + { + -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 + -5, -5, // Whitespace: Tab and Linefeed + -9, -9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 + -9, -9, -9, -9, -9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 + 62, // Plus sign at decimal 43 + -9, -9, -9, // Decimal 44 - 46 + 63, // Slash at decimal 47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine + -9, -9, -9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9, -9, -9, // Decimal 62 - 64 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' + -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' + -9, -9, -9, -9 // Decimal 123 - 126 + /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + }; + + // I think I end up not using the BAD_ENCODING indicator. + //private final static byte BAD_ENCODING = -9; // Indicates error in encoding + private final static byte WHITE_SPACE_ENC=-5; // Indicates white space in encoding + private final static byte EQUALS_SIGN_ENC=-1; // Indicates equals sign in encoding + + + /** + * Defeats instantiation. + */ + private Base64() { + } + + +/* ******** E N C O D I N G M E T H O D S ******** */ + + + /** + * Encodes up to the first three bytes of array threeBytes + * and returns a four-byte array in Base64 notation. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * The array threeBytes needs only be as big as + * numSigBytes. + * Code can reuse a byte array by passing a four-byte array as b4. + * @param b4 A reusable byte array to reduce array instantiation + * @param threeBytes the array to convert + * @param numSigBytes the number of significant bytes in your array + * @return four byte array in Base64 notation. + * @since 1.5.1 + */ + private static byte[] encode3to4(byte[] b4, byte[] threeBytes, int numSigBytes) { + encode3to4(threeBytes, 0, numSigBytes, b4, 0); + return b4; + } // end encode3to4 + + + /** + * Encodes up to three bytes of the array source + * and writes the resulting four Base64 bytes to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accomodate srcOffset + 3 for + * the source array or destOffset + 4 for + * the destination array. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param numSigBytes the number of significant bytes in your array + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @return the destination array + * @since 1.3 + */ + private static byte[] encode3to4( + byte[] source, int srcOffset, int numSigBytes, + byte[] destination, int destOffset) { + // 1 2 3 + // 01234567890123456789012345678901 Bit position + // --------000000001111111122222222 Array position from threeBytes + // --------| || || || | Six bit groups to index ALPHABET + // >>18 >>12 >> 6 >> 0 Right shift necessary + // 0x3f 0x3f 0x3f Additional AND + + // Create buffer with zero-padding if there are only one or two + // significant bytes passed in the array. + // We have to shift left 24 in order to flush out the 1's that appear + // when Java treats a value as negative that is cast from a byte to an int. + int inBuff=(numSigBytes > 0? ((source[srcOffset] << 24) >>> 8) : 0) + | (numSigBytes > 1? ((source[srcOffset + 1] << 24) >>> 16) : 0) + | (numSigBytes > 2? ((source[srcOffset + 2] << 24) >>> 24) : 0); + + switch(numSigBytes) { + case 3: + destination[destOffset]=ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1]=ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2]=ALPHABET[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3]=ALPHABET[(inBuff) & 0x3f]; + return destination; + + case 2: + destination[destOffset]=ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1]=ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2]=ALPHABET[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3]=EQUALS_SIGN; + return destination; + + case 1: + destination[destOffset]=ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1]=ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2]=EQUALS_SIGN; + destination[destOffset + 3]=EQUALS_SIGN; + return destination; + + default: + return destination; + } // end switch + } // end encode3to4 + + + /** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. If the object + * cannot be serialized or there is another error, + * the method will return null. + * The object is not GZip-compressed before being encoded. + * @param serializableObject The object to encode + * @return The Base64-encoded object + * @since 1.4 + */ + public static String encodeObject(java.io.Serializable serializableObject) { + return encodeObject(serializableObject, NO_OPTIONS); + } // end encodeObject + + + /** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. If the object + * cannot be serialized or there is another error, + * the method will return null. + *

            + * Valid options:

            +         *   GZIP: gzip-compresses object before encoding it.
            +         *   DONT_BREAK_LINES: don't break lines at 76 characters
            +         *     Note: Technically, this makes your encoding non-compliant.
            +         * 
            + *

            + * Example: encodeObject( myObj, Base64.GZIP ) or + *

            + * Example: encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES ) + * @param serializableObject The object to encode + * @param options Specified options + * @return The Base64-encoded object + * @see Base64#GZIP + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public static String encodeObject(java.io.Serializable serializableObject, int options) { + // Streams + java.io.ByteArrayOutputStream baos=null; + java.io.OutputStream b64os=null; + java.io.ObjectOutputStream oos=null; + java.util.zip.GZIPOutputStream gzos=null; + + // Isolate options + int gzip=(options & GZIP); + int dontBreakLines=(options & DONT_BREAK_LINES); + + try { + // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream + baos=new java.io.ByteArrayOutputStream(); + b64os=new Base64.OutputStream(baos, ENCODE | dontBreakLines); + + // GZip? + if(gzip == GZIP) { + gzos=new java.util.zip.GZIPOutputStream(b64os); + oos=new java.io.ObjectOutputStream(gzos); + } // end if: gzip + else + oos=new java.io.ObjectOutputStream(b64os); + + oos.writeObject(serializableObject); + } // end try + catch(java.io.IOException e) { + e.printStackTrace(); + return null; + } // end catch + finally { + try { + oos.close(); + } + catch(Exception e) { + } + try { + gzos.close(); + } + catch(Exception e) { + } + try { + b64os.close(); + } + catch(Exception e) { + } + try { + baos.close(); + } + catch(Exception e) { + } + } // end finally + + // Return value according to relevant encoding. + try { + return new String(baos.toByteArray(), PREFERRED_ENCODING); + } // end try + catch(java.io.UnsupportedEncodingException uue) { + return new String(baos.toByteArray()); + } // end catch + + } // end encode + + + /** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * @param source The data to convert + * @since 1.4 + */ + public static String encodeBytes(byte[] source) { + return encodeBytes(source, 0, source.length, NO_OPTIONS); + } // end encodeBytes + + + /** + * Encodes a byte array into Base64 notation. + *

            + * Valid options:

            +         *   GZIP: gzip-compresses object before encoding it.
            +         *   DONT_BREAK_LINES: don't break lines at 76 characters
            +         *     Note: Technically, this makes your encoding non-compliant.
            +         * 
            + *

            + * Example: encodeBytes( myData, Base64.GZIP ) or + *

            + * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) + * @param source The data to convert + * @param options Specified options + * @see Base64#GZIP + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public static String encodeBytes(byte[] source, int options) { + return encodeBytes(source, 0, source.length, options); + } // end encodeBytes + + + /** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @since 1.4 + */ + public static String encodeBytes(byte[] source, int off, int len) { + return encodeBytes(source, off, len, NO_OPTIONS); + } // end encodeBytes + + + /** + * Encodes a byte array into Base64 notation. + *

            + * Valid options:

            +         *   GZIP: gzip-compresses object before encoding it.
            +         *   DONT_BREAK_LINES: don't break lines at 76 characters
            +         *     Note: Technically, this makes your encoding non-compliant.
            +         * 
            + *

            + * Example: encodeBytes( myData, Base64.GZIP ) or + *

            + * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @param options Specified options + * @see Base64#GZIP + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public static String encodeBytes(byte[] source, int off, int len, int options) { + // Isolate options + int dontBreakLines=(options & DONT_BREAK_LINES); + int gzip=(options & GZIP); + + // Compress? + if(gzip == GZIP) { + java.io.ByteArrayOutputStream baos=null; + java.util.zip.GZIPOutputStream gzos=null; + Base64.OutputStream b64os=null; + + + try { + // GZip -> Base64 -> ByteArray + baos=new java.io.ByteArrayOutputStream(); + b64os=new Base64.OutputStream(baos, ENCODE | dontBreakLines); + gzos=new java.util.zip.GZIPOutputStream(b64os); + + gzos.write(source, off, len); + gzos.close(); + } // end try + catch(java.io.IOException e) { + e.printStackTrace(); + return null; + } // end catch + finally { + try { + gzos.close(); + } + catch(Exception e) { + } + try { + b64os.close(); + } + catch(Exception e) { + } + try { + baos.close(); + } + catch(Exception e) { + } + } // end finally + + // Return value according to relevant encoding. + try { + return new String(baos.toByteArray(), PREFERRED_ENCODING); + } // end try + catch(java.io.UnsupportedEncodingException uue) { + return new String(baos.toByteArray()); + } // end catch + } // end if: compress + + // Else, don't compress. Better not to use streams at all then. + else { + // Convert option to boolean in way that code likes it. + boolean breakLines=dontBreakLines == 0; + + int len43=len * 4 / 3; + byte[] outBuff=new byte[(len43) // Main 4:3 + + ((len % 3) > 0? 4 : 0) // Account for padding + + (breakLines? (len43 / MAX_LINE_LENGTH) : 0)]; // New lines + int d=0; + int e=0; + int len2=len - 2; + int lineLength=0; + for(; d < len2; d+=3, e+=4) { + encode3to4(source, d + off, 3, outBuff, e); + + lineLength+=4; + if(breakLines && lineLength == MAX_LINE_LENGTH) { + outBuff[e + 4]=NEW_LINE; + e++; + lineLength=0; + } // end if: end of line + } // en dfor: each piece of array + + if(d < len) { + encode3to4(source, d + off, len - d, outBuff, e); + e+=4; + } // end if: some padding needed + + + // Return value according to relevant encoding. + try { + return new String(outBuff, 0, e, PREFERRED_ENCODING); + } // end try + catch(java.io.UnsupportedEncodingException uue) { + return new String(outBuff, 0, e); + } // end catch + + } // end else: don't compress + + } // end encodeBytes + + +/* ******** D E C O D I N G M E T H O D S ******** */ + + + /** + * Decodes four bytes from array source + * and writes the resulting bytes (up to three of them) + * to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accomodate srcOffset + 4 for + * the source array or destOffset + 3 for + * the destination array. + * This method returns the actual number of bytes that + * were converted from the Base64 encoding. + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @return the number of decoded bytes converted + * @since 1.3 + */ + private static int decode4to3(byte[] source, int srcOffset, byte[] destination, int destOffset) { + // Example: Dk== + if(source[srcOffset + 2] == EQUALS_SIGN) { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); + int outBuff=((DECODABET[source[srcOffset]] & 0xFF) << 18) + | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12); + + destination[destOffset]=(byte)(outBuff >>> 16); + return 1; + } + + // Example: DkL= + else if(source[srcOffset + 3] == EQUALS_SIGN) { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); + int outBuff=((DECODABET[source[srcOffset]] & 0xFF) << 18) + | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) + | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6); + + destination[destOffset]=(byte)(outBuff >>> 16); + destination[destOffset + 1]=(byte)(outBuff >>> 8); + return 2; + } + + // Example: DkLE + else { + try { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) + // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); + int outBuff=((DECODABET[source[srcOffset]] & 0xFF) << 18) + | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) + | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6) + | ((DECODABET[source[srcOffset + 3]] & 0xFF)); + + + destination[destOffset]=(byte)(outBuff >> 16); + destination[destOffset + 1]=(byte)(outBuff >> 8); + destination[destOffset + 2]=(byte)(outBuff); + + return 3; + } + catch(Exception e) { + System.out.println(valueOf(source[srcOffset]) + ": " + (DECODABET[source[srcOffset]])); + System.out.println(valueOf(source[srcOffset + 1]) + ": " + (DECODABET[source[srcOffset + 1]])); + System.out.println(valueOf(source[srcOffset + 2]) + ": " + (DECODABET[source[srcOffset + 2]])); + System.out.println(String.valueOf(source[srcOffset + 3]) + ": " + (DECODABET[source[srcOffset + 3]])); + return -1; + } //e nd catch + } + } // end decodeToBytes + + + /** + * Very low-level access to decoding ASCII characters in + * the form of a byte array. Does not support automatically + * gunzipping or any other "fancy" features. + * @param source The Base64 encoded data + * @param off The offset of where to begin decoding + * @param len The length of characters to decode + * @return decoded data + * @since 1.3 + */ + public static byte[] decode(byte[] source, int off, int len) { + int len34=len * 3 / 4; + byte[] outBuff=new byte[len34]; // Upper limit on size of output + int outBuffPosn=0; + + byte[] b4=new byte[4]; + int b4Posn=0; + int i=0; + byte sbiCrop=0; + byte sbiDecode=0; + for(i=off; i < off + len; i++) { + sbiCrop=(byte)(source[i] & 0x7f); // Only the low seven bits + sbiDecode=DECODABET[sbiCrop]; + + if(sbiDecode >= WHITE_SPACE_ENC) // White space, Equals sign or better + { + if(sbiDecode >= EQUALS_SIGN_ENC) { + b4[b4Posn++]=sbiCrop; + if(b4Posn > 3) { + outBuffPosn+=decode4to3(b4, 0, outBuff, outBuffPosn); + b4Posn=0; + + // If that was the equals sign, break out of 'for' loop + if(sbiCrop == EQUALS_SIGN) + break; + } // end if: quartet built + + } // end if: equals sign or better + + } // end if: white space, equals sign or better + else { + System.err.println("Bad Base64 input character at " + i + ": " + source[i] + "(decimal)"); + return null; + } // end else: + } // each input character + + byte[] out=new byte[outBuffPosn]; + System.arraycopy(outBuff, 0, out, 0, outBuffPosn); + return out; + } // end decode + + + /** + * Decodes data from Base64 notation, automatically + * detecting gzip-compressed data and decompressing it. + * @param s the string to decode + * @return the decoded data + * @since 1.4 + */ + public static byte[] decode(String s) { + byte[] bytes; + try { + bytes=s.getBytes(PREFERRED_ENCODING); + } // end try + catch(java.io.UnsupportedEncodingException uee) { + bytes=s.getBytes(); + } // end catch + // + + // Decode + bytes=decode(bytes, 0, bytes.length); + + + // Check to see if it's gzip-compressed + // GZIP Magic Two-Byte Number: 0x8b1f (35615) + if(bytes != null && bytes.length >= 4) { + + int head=((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); + if(java.util.zip.GZIPInputStream.GZIP_MAGIC == head) { + java.io.ByteArrayInputStream bais=null; + java.util.zip.GZIPInputStream gzis=null; + java.io.ByteArrayOutputStream baos=null; + byte[] buffer=new byte[2048]; + int length=0; + + try { + baos=new java.io.ByteArrayOutputStream(); + bais=new java.io.ByteArrayInputStream(bytes); + gzis=new java.util.zip.GZIPInputStream(bais); + + while((length=gzis.read(buffer)) >= 0) { + baos.write(buffer, 0, length); + } // end while: reading input + + // No error? Get new bytes. + bytes=baos.toByteArray(); + + } // end try + catch(java.io.IOException e) { + // Just return originally-decoded bytes + } // end catch + finally { + try { + baos.close(); + } + catch(Exception e) { + } + try { + gzis.close(); + } + catch(Exception e) { + } + try { + bais.close(); + } + catch(Exception e) { + } + } // end finally + + } // end if: gzipped + } // end if: bytes.length >= 2 + + return bytes; + } // end decode + + + /** + * Attempts to decode Base64 data and deserialize a Java + * Object within. Returns null if there was an error. + * @param encodedObject The Base64 data to decode + * @return The decoded and deserialized object + * @since 1.5 + */ + public static Object decodeToObject(String encodedObject) { + // Decode and gunzip if necessary + byte[] objBytes=decode(encodedObject); + + java.io.ByteArrayInputStream bais=null; + java.io.ObjectInputStream ois=null; + Object obj=null; + + try { + bais=new java.io.ByteArrayInputStream(objBytes); + ois=new java.io.ObjectInputStream(bais); + + obj=ois.readObject(); + } // end try + catch(java.io.IOException e) { + e.printStackTrace(); + obj=null; + } // end catch + catch(java.lang.ClassNotFoundException e) { + e.printStackTrace(); + obj=null; + } // end catch + finally { + try { + if(bais != null) + bais.close(); + } + catch(Exception e) { + } + try { + if(ois != null) + ois.close(); + } + catch(Exception e) { + } + } // end finally + + return obj; + } // end decodeObject + + + /** + * Convenience method for encoding data to a file. + * @param dataToEncode byte array of data to encode in base64 form + * @param filename Filename for saving encoded data + * @return true if successful, false otherwise + * @since 2.1 + */ + public static boolean encodeToFile(byte[] dataToEncode, String filename) { + boolean success=false; + Base64.OutputStream bos=null; + try { + bos=new Base64.OutputStream( + new java.io.FileOutputStream(filename), Base64.ENCODE); + bos.write(dataToEncode); + success=true; + } // end try + catch(java.io.IOException e) { + + success=false; + } // end catch: IOException + finally { + try { + if(bos != null) + bos.close(); + } + catch(Exception e) { + } + } // end finally + + return success; + } // end encodeToFile + + + /** + * Convenience method for decoding data to a file. + * @param dataToDecode Base64-encoded data as a string + * @param filename Filename for saving decoded data + * @return true if successful, false otherwise + * @since 2.1 + */ + public static boolean decodeToFile(String dataToDecode, String filename) { + boolean success=false; + Base64.OutputStream bos=null; + try { + bos=new Base64.OutputStream( + new java.io.FileOutputStream(filename), Base64.DECODE); + bos.write(dataToDecode.getBytes(PREFERRED_ENCODING)); + success=true; + } // end try + catch(java.io.IOException e) { + success=false; + } // end catch: IOException + finally { + try { + if(bos != null) + bos.close(); + } + catch(Exception e) { + } + } // end finally + + return success; + } // end decodeToFile + + + /** + * Convenience method for reading a base64-encoded + * file and decoding it. + * @param filename Filename for reading encoded data + * @return decoded byte array or null if unsuccessful + * @since 2.1 + */ + public static byte[] decodeFromFile(String filename) { + byte[] decodedData=null; + Base64.InputStream bis=null; + try { + // Set up some useful variables + java.io.File file=new java.io.File(filename); + byte[] buffer=null; + int length=0; + int numBytes=0; + + // Check for size of file + if(file.length() > Integer.MAX_VALUE) { + System.err.println("File is too big for this convenience method (" + file.length() + " bytes)."); + return null; + } // end if: file too big for int index + buffer=new byte[(int)file.length()]; + + // Open a stream + bis=new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream(file)), Base64.DECODE); + + // Read until done + while((numBytes=bis.read(buffer, length, 4096)) >= 0) + length+=numBytes; + + // Save in a variable to return + decodedData=new byte[length]; + System.arraycopy(buffer, 0, decodedData, 0, length); + + } // end try + catch(java.io.IOException e) { + System.err.println("Error decoding from file " + filename); + } // end catch: IOException + finally { + try { + if(bis != null) + bis.close(); + } + catch(Exception e) { + } + } // end finally + + return decodedData; + } // end decodeFromFile + + + /** + * Convenience method for reading a binary file + * and base64-encoding it. + * @param filename Filename for reading binary data + * @return base64-encoded string or null if unsuccessful + * @since 2.1 + */ + public static String encodeFromFile(String filename) { + String encodedData=null; + Base64.InputStream bis=null; + try { + // Set up some useful variables + java.io.File file=new java.io.File(filename); + byte[] buffer=new byte[(int)(file.length() * 1.4)]; + int length=0; + int numBytes=0; + + // Open a stream + bis=new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream(file)), Base64.ENCODE); + + // Read until done + while((numBytes=bis.read(buffer, length, 4096)) >= 0) + length+=numBytes; + + // Save in a variable to return + encodedData=new String(buffer, 0, length, Base64.PREFERRED_ENCODING); + + } // end try + catch(java.io.IOException e) { + System.err.println("Error encoding from file " + filename); + } // end catch: IOException + finally { + try { + if(bis != null) + bis.close(); + } + catch(Exception e) { + } + } // end finally + + return encodedData; + } // end encodeFromFile + + + /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ + + + /** + * A {@link Base64.InputStream} will read data from another + * java.io.InputStream, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * @see Base64 + * @since 1.3 + */ + public static class InputStream extends java.io.FilterInputStream { + private boolean encode; // Encoding or decoding + private int position; // Current position in the buffer + private byte[] buffer; // Small buffer holding converted data + private int bufferLength; // Length of buffer (3 or 4) + private int numSigBytes; // Number of meaningful bytes in the buffer + private int lineLength; + private boolean breakLines; // Break lines at less than 80 characters + + + /** + * Constructs a {@link Base64.InputStream} in DECODE mode. + * @param in the java.io.InputStream from which to read data. + * @since 1.3 + */ + public InputStream(java.io.InputStream in) { + this(in, DECODE); + } // end constructor + + + /** + * Constructs a {@link Base64.InputStream} in + * either ENCODE or DECODE mode. + *

            + * Valid options:

            +             *   ENCODE or DECODE: Encode or Decode as data is read.
            +             *   DONT_BREAK_LINES: don't break lines at 76 characters
            +             *     (only meaningful when encoding)
            +             *     Note: Technically, this makes your encoding non-compliant.
            +             * 
            + *

            + * Example: new Base64.InputStream( in, Base64.DECODE ) + * @param in the java.io.InputStream from which to read data. + * @param options Specified options + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public InputStream(java.io.InputStream in, int options) { + super(in); + this.breakLines=(options & DONT_BREAK_LINES) != DONT_BREAK_LINES; + this.encode=(options & ENCODE) == ENCODE; + this.bufferLength=encode? 4 : 3; + this.buffer=new byte[bufferLength]; + this.position=-1; + this.lineLength=0; + } // end constructor + + /** + * Reads enough of the input stream to convert + * to/from Base64 and returns the next byte. + * @return next byte + * @since 1.3 + */ + public int read() throws java.io.IOException { + // Do we need to get data? + if(position < 0) { + if(encode) { + byte[] b3=new byte[3]; + int numBinaryBytes=0; + for(int i=0; i < 3; i++) { + try { + int b=in.read(); + + // If end of stream, b is -1. + if(b >= 0) { + b3[i]=(byte)b; + numBinaryBytes++; + } // end if: not end of stream + + } // end try: read + catch(java.io.IOException e) { + // Only a problem if we got no data at all. + if(i == 0) + throw e; + + } // end catch + } // end for: each needed input byte + + if(numBinaryBytes > 0) { + encode3to4(b3, 0, numBinaryBytes, buffer, 0); + position=0; + numSigBytes=4; + } // end if: got data + else { + return -1; + } // end else + } // end if: encoding + + // Else decoding + else { + byte[] b4=new byte[4]; + int i=0; + for(i=0; i < 4; i++) { + // Read four "meaningful" bytes: + int b=0; + do { + b=in.read(); + } + while(b >= 0 && DECODABET[b & 0x7f] <= WHITE_SPACE_ENC); + + if(b < 0) + break; // Reads a -1 if end of stream + + b4[i]=(byte)b; + } // end for: each needed input byte + + if(i == 4) { + numSigBytes=decode4to3(b4, 0, buffer, 0); + position=0; + } // end if: got four characters + else if(i == 0) { + return -1; + } // end else if: also padded correctly + else { + // Must have broken out from above. + throw new java.io.IOException("Improperly padded Base64 input."); + } // end + + } // end else: decode + } // end else: get data + + // Got data? + if(position >= 0) { + // End of relevant data? + if( /*!encode &&*/ position >= numSigBytes) + return -1; + + if(encode && breakLines && lineLength >= MAX_LINE_LENGTH) { + lineLength=0; + return '\n'; + } // end if + else { + lineLength++; // This isn't important when decoding + // but throwing an extra "if" seems + // just as wasteful. + + int b=buffer[position++]; + + if(position >= bufferLength) + position=-1; + + return b & 0xFF; // This is how you "cast" a byte that's + // intended to be unsigned. + } // end else + } // end if: position >= 0 + + // Else error + else { + // When JDK1.4 is more accepted, use an assertion here. + throw new java.io.IOException("Error in Base64 code reading stream."); + } // end else + } // end read + + + /** + * Calls {@link #read()} repeatedly until the end of stream + * is reached or len bytes are read. + * Returns number of bytes read into array or -1 if + * end of stream is encountered. + * @param dest array to hold values + * @param off offset for array + * @param len max number of bytes to read into array + * @return bytes read into array or -1 if end of stream is encountered. + * @since 1.3 + */ + public int read(byte[] dest, int off, int len) throws java.io.IOException { + int i; + int b; + for(i=0; i < len; i++) { + b=read(); + + //if( b < 0 && i == 0 ) + // return -1; + + if(b >= 0) + dest[off + i]=(byte)b; + else if(i == 0) + return -1; + else + break; // Out of 'for' loop + } // end for: each byte read + return i; + } // end read + + } // end inner class InputStream + + + /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ + + + /** + * A {@link Base64.OutputStream} will write data to another + * java.io.OutputStream, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * @see Base64 + * @since 1.3 + */ + public static class OutputStream extends java.io.FilterOutputStream { + private boolean encode; + private int position; + private byte[] buffer; + private int bufferLength; + private int lineLength; + private boolean breakLines; + private byte[] b4; // Scratch used in a few places + private boolean suspendEncoding; + + /** + * Constructs a {@link Base64.OutputStream} in ENCODE mode. + * @param out the java.io.OutputStream to which data will be written. + * @since 1.3 + */ + public OutputStream(java.io.OutputStream out) { + this(out, ENCODE); + } // end constructor + + + /** + * Constructs a {@link Base64.OutputStream} in + * either ENCODE or DECODE mode. + *

            + * Valid options:

            +             *   ENCODE or DECODE: Encode or Decode as data is read.
            +             *   DONT_BREAK_LINES: don't break lines at 76 characters
            +             *     (only meaningful when encoding)
            +             *     Note: Technically, this makes your encoding non-compliant.
            +             * 
            + *

            + * Example: new Base64.OutputStream( out, Base64.ENCODE ) + * @param out the java.io.OutputStream to which data will be written. + * @param options Specified options. + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DONT_BREAK_LINES + * @since 1.3 + */ + public OutputStream(java.io.OutputStream out, int options) { + super(out); + this.breakLines=(options & DONT_BREAK_LINES) != DONT_BREAK_LINES; + this.encode=(options & ENCODE) == ENCODE; + this.bufferLength=encode? 3 : 4; + this.buffer=new byte[bufferLength]; + this.position=0; + this.lineLength=0; + this.suspendEncoding=false; + this.b4=new byte[4]; + } // end constructor + + + /** + * Writes the byte to the output stream after + * converting to/from Base64 notation. + * When encoding, bytes are buffered three + * at a time before the output stream actually + * gets a write() call. + * When decoding, bytes are buffered four + * at a time. + * @param theByte the byte to write + * @since 1.3 + */ + public void write(int theByte) throws java.io.IOException { + // Encoding suspended? + if(suspendEncoding) { + super.out.write(theByte); + return; + } // end if: supsended + + // Encode? + if(encode) { + buffer[position++]=(byte)theByte; + if(position >= bufferLength) // Enough to encode. + { + out.write(encode3to4(b4, buffer, bufferLength)); + + lineLength+=4; + if(breakLines && lineLength >= MAX_LINE_LENGTH) { + out.write(NEW_LINE); + lineLength=0; + } // end if: end of line + + position=0; + } // end if: enough to output + } // end if: encoding + + // Else, Decoding + else { + // Meaningful Base64 character? + if(DECODABET[theByte & 0x7f] > WHITE_SPACE_ENC) { + buffer[position++]=(byte)theByte; + if(position >= bufferLength) // Enough to output. + { + int len=Base64.decode4to3(buffer, 0, b4, 0); + out.write(b4, 0, len); + //out.write( Base64.decode4to3( buffer ) ); + position=0; + } // end if: enough to output + } // end if: meaningful base64 character + else if(DECODABET[theByte & 0x7f] != WHITE_SPACE_ENC) { + throw new java.io.IOException("Invalid character in Base64 data."); + } // end else: not white space either + } // end else: decoding + } // end write + + + /** + * Calls {@link #write(int)} repeatedly until len + * bytes are written. + * @param theBytes array from which to read bytes + * @param off offset for array + * @param len max number of bytes to read into array + * @since 1.3 + */ + public void write(byte[] theBytes, int off, int len) throws java.io.IOException { + // Encoding suspended? + if(suspendEncoding) { + super.out.write(theBytes, off, len); + return; + } // end if: supsended + + for(int i=0; i < len; i++) { + write(theBytes[off + i]); + } // end for: each byte written + + } // end write + + + /** + * Method added by PHIL. [Thanks, PHIL. -Rob] + * This pads the buffer without closing the stream. + */ + public void flushBase64() throws java.io.IOException { + if(position > 0) { + if(encode) { + out.write(encode3to4(b4, buffer, position)); + position=0; + } // end if: encoding + else { + throw new java.io.IOException("Base64 input not properly padded."); + } // end else: decoding + } // end if: buffer partially full + + } // end flush + + + /** + * Flushes and closes (I think, in the superclass) the stream. + * @since 1.3 + */ + public void close() throws java.io.IOException { + // 1. Ensure that pending characters are written + flushBase64(); + + // 2. Actually close the stream + // Base class both flushes and closes. + super.close(); + + buffer=null; + out=null; + } // end close + + + /** + * Suspends encoding of the stream. + * May be helpful if you need to embed a piece of + * base640-encoded data in a stream. + * @since 1.5.1 + */ + public void suspendEncoding() throws java.io.IOException { + flushBase64(); + this.suspendEncoding=true; + } // end suspendEncoding + + + /** + * Resumes encoding of the stream. + * May be helpful if you need to embed a piece of + * base640-encoded data in a stream. + * @since 1.5.1 + */ + public void resumeEncoding() { + this.suspendEncoding=false; + } // end resumeEncoding + + + } // end inner class OutputStream + + + } // end class Base64 + +} + + + + + diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/SCOPE.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/SCOPE.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/SCOPE.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/SCOPE.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,550 @@ +package org.jgroups.protocols; + +import org.jgroups.*; +import org.jgroups.util.*; +import org.jgroups.util.ThreadFactory; +import org.jgroups.annotations.*; +import org.jgroups.stack.Protocol; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.Queue; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Implements https://jira.jboss.org/jira/browse/JGRP-822, which allows for concurrent delivery of messages from the + * same sender based on scopes. Similar to using OOB messages, but messages within the same scope are ordered. + * @author Bela Ban + * @since 2.10 + */ +@Experimental +@MBean(description="Implementation of scopes (concurrent delivery of messages from the same sender)") +public class SCOPE extends Protocol { + + protected int thread_pool_min_threads=2; + + protected int thread_pool_max_threads=10; + + protected long thread_pool_keep_alive_time=30000; + + @Property(description="Thread naming pattern for threads in this channel. Default is cl") + protected String thread_naming_pattern="cl"; + + @Property(description="Time in milliseconds after which an expired scope will get removed. An expired scope is one " + + "to which no messages have been added in max_expiration_time milliseconds. 0 never expires scopes") + protected long expiration_time=30000; + + @Property(description="Interval in milliseconds at which the expiry task tries to remove expired scopes") + protected long expiration_interval=60000; + + protected Future expiry_task=null; + + + /** + * Used to find the correct AckReceiverWindow on message reception and deliver it in the right order + */ + protected final ConcurrentMap> queues=Util.createConcurrentMap(); + + + protected String cluster_name; + + protected Address local_addr; + + protected Executor thread_pool; + + protected ThreadGroup thread_group; + + protected ThreadFactory thread_factory; + + protected TimeScheduler timer; + + + public SCOPE() { + } + + @ManagedAttribute(description="Number of scopes in queues") + public int getNumberOfReceiverScopes() { + int retval=0; + for(ConcurrentMap map: queues.values()) + retval+=map.keySet().size(); + return retval; + } + + @ManagedAttribute(description="Total number of messages in all queues") + public int getNumberOfMessages() { + int retval=0; + for(ConcurrentMap map: queues.values()) { + for(MessageQueue queue: map.values()) + retval+=queue.size(); + } + + return retval; + } + + + @Property(name="thread_pool.min_threads",description="Minimum thread pool size for the regular thread pool") + public void setThreadPoolMinThreads(int size) { + thread_pool_min_threads=size; + if(thread_pool instanceof ThreadPoolExecutor) + ((ThreadPoolExecutor)thread_pool).setCorePoolSize(size); + } + + public int getThreadPoolMinThreads() {return thread_pool_min_threads;} + + + @Property(name="thread_pool.max_threads",description="Maximum thread pool size for the regular thread pool") + public void setThreadPoolMaxThreads(int size) { + thread_pool_max_threads=size; + if(thread_pool instanceof ThreadPoolExecutor) + ((ThreadPoolExecutor)thread_pool).setMaximumPoolSize(size); + } + + public int getThreadPoolMaxThreads() {return thread_pool_max_threads;} + + + @Property(name="thread_pool.keep_alive_time",description="Timeout in milliseconds to remove idle thread from regular pool") + public void setThreadPoolKeepAliveTime(long time) { + thread_pool_keep_alive_time=time; + if(thread_pool instanceof ThreadPoolExecutor) + ((ThreadPoolExecutor)thread_pool).setKeepAliveTime(time, TimeUnit.MILLISECONDS); + } + + public long getThreadPoolKeepAliveTime() {return thread_pool_keep_alive_time;} + + @Experimental + @ManagedOperation(description="Removes all queues and scopes - only used for testing, might get removed any time !") + public void removeAllQueues() { + queues.clear(); + } + + /** + * Multicasts an EXPIRE message to all members, and - on reception - each member removes the scope locally + * @param scope + */ + @ManagedOperation(description="Expires the given scope around the cluster") + public void expire(short scope) { + ScopeHeader hdr=ScopeHeader.createExpireHeader(scope); + Message expiry_msg=new Message(); + expiry_msg.putHeader(Global.SCOPE_ID, hdr); + expiry_msg.setFlag(Message.SCOPED); + down_prot.down(new Event(Event.MSG, expiry_msg)); + } + + public void removeScope(Address member, short scope) { + if(member == null) return; + ConcurrentMap val=queues.get(member); + if(val != null) { + MessageQueue queue=val.remove(scope); + if(queue != null) + queue.clear(); + } + } + + @ManagedOperation(description="Dumps all scopes associated with members") + public String dumpScopes() { + StringBuilder sb=new StringBuilder(); + for(Map.Entry> entry: queues.entrySet()) { + sb.append(entry.getKey()).append(": ").append(new TreeSet(entry.getValue().keySet())).append("\n"); + } + return sb.toString(); + } + + @ManagedAttribute(description="Number of active thread in the pool") + public int getNumActiveThreads() { + if(thread_pool instanceof ThreadPoolExecutor) + return ((ThreadPoolExecutor)thread_pool).getActiveCount(); + return 0; + } + + + public void init() throws Exception { + super.init(); + timer=getTransport().getTimer(); + thread_group=new ThreadGroup(getTransport().getPoolThreadGroup(), "SCOPE Threads"); + thread_factory=new DefaultThreadFactory(thread_group, "SCOPE", false, true); + setInAllThreadFactories(cluster_name, local_addr, thread_naming_pattern); + + // sanity check for expiration + if((expiration_interval > 0 && expiration_time <= 0) || (expiration_interval <= 0 && expiration_time > 0)) + throw new IllegalArgumentException("expiration_interval (" + expiration_interval + ") and expiration_time (" + + expiration_time + ") don't match"); + } + + public void start() throws Exception { + super.start(); + thread_pool=createThreadPool(thread_pool_min_threads, thread_pool_max_threads, + thread_pool_keep_alive_time, thread_factory); + if(expiration_interval > 0 && expiration_time > 0) + startExpiryTask(); + } + + public void stop() { + super.stop(); + stopExpiryTask(); + shutdownThreadPool(thread_pool); + for(ConcurrentMap map: queues.values()) { + for(MessageQueue queue: map.values()) + queue.release(); // to prevent a thread killed on shutdown() from holding on to the lock + } + } + + public Object down(Event evt) { + switch(evt.getType()) { + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; + + case Event.VIEW_CHANGE: + handleView((View)evt.getArg()); + break; + + case Event.CONNECT: + case Event.CONNECT_WITH_STATE_TRANSFER: + case Event.CONNECT_USE_FLUSH: + case Event.CONNECT_WITH_STATE_TRANSFER_USE_FLUSH: + cluster_name=(String)evt.getArg(); + setInAllThreadFactories(cluster_name, local_addr, thread_naming_pattern); + break; + } + return down_prot.down(evt); + } + + + public Object up(Event evt) { + + switch(evt.getType()) { + case Event.MSG: + Message msg=(Message)evt.getArg(); + + // we don't handle unscoped or OOB messages + if(!msg.isFlagSet(Message.SCOPED) || msg.isFlagSet(Message.OOB)) + break; + + ScopeHeader hdr=(ScopeHeader)msg.getHeader(id); + if(hdr == null) + throw new IllegalStateException("message doesn't have a ScopeHeader attached"); + + if(hdr.type == ScopeHeader.EXPIRE) { + removeScope(msg.getSrc(), hdr.scope); + return null; + } + + MessageQueue queue=getOrCreateQueue(msg.getSrc(), hdr.scope); + queue.add(msg); + + if(!queue.acquire()) + return null; + + QueueThread thread=new QueueThread(queue); + thread_pool.execute(thread); + return null; + + case Event.VIEW_CHANGE: + handleView((View)evt.getArg()); + break; + } + + return up_prot.up(evt); + } + + + protected MessageQueue getOrCreateQueue(Address sender, short scope) { + ConcurrentMap val=queues.get(sender); + if(val == null) { + val=Util.createConcurrentMap(); + ConcurrentMap tmp=queues.putIfAbsent(sender, val); + if(tmp != null) + val=tmp; + } + MessageQueue queue=val.get(scope); + if(queue == null) { + queue=new MessageQueue(); + MessageQueue old=val.putIfAbsent(scope, queue); + if(old != null) + queue=old; + } + + return queue; + } + + + protected synchronized void startExpiryTask() { + if(expiry_task == null || expiry_task.isDone()) + expiry_task=timer.scheduleWithFixedDelay(new ExpiryTask(), expiration_interval, expiration_interval, TimeUnit.MILLISECONDS); + } + + protected synchronized void stopExpiryTask() { + if(expiry_task != null) { + expiry_task.cancel(true); + expiry_task=null; + } + } + + protected static ExecutorService createThreadPool(int min_threads, int max_threads, long keep_alive_time, + final org.jgroups.util.ThreadFactory factory) { + + ThreadPoolExecutor pool=new ThreadManagerThreadPoolExecutor(min_threads, max_threads, keep_alive_time, + TimeUnit.MILLISECONDS, new SynchronousQueue()); + pool.setThreadFactory(factory); + pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + return pool; + } + + protected static void shutdownThreadPool(Executor thread_pool) { + if(thread_pool instanceof ExecutorService) { + ExecutorService service=(ExecutorService)thread_pool; + service.shutdownNow(); + try { + service.awaitTermination(Global.THREADPOOL_SHUTDOWN_WAIT_TIME, TimeUnit.MILLISECONDS); + } + catch(InterruptedException e) { + } + } + } + + private void setInAllThreadFactories(String cluster_name, Address local_address, String pattern) { + ThreadFactory[] factories= {thread_factory}; + + for(ThreadFactory factory:factories) { + if(pattern != null) + factory.setPattern(pattern); + if(cluster_name != null) + factory.setClusterName(cluster_name); + if(local_address != null) + factory.setAddress(local_address.toString()); + } + } + + private void handleView(View view) { + Vector

            members=view.getMembers(); + + // Remove all non members from receiver_table + Set
            keys=new HashSet
            (queues.keySet()); + keys.removeAll(members); + for(Address key: keys) + clearQueue(key); + } + + public void clearQueue(Address member) { + ConcurrentMap val=queues.remove(member); + if(val != null) { + Collection values=val.values(); + for(MessageQueue queue: values) + queue.clear(); + } + if(log.isTraceEnabled()) + log.trace("removed " + member + " from receiver_table"); + } + + + + protected static class MessageQueue { + private final Queue queue=new ConcurrentLinkedQueue(); + private final AtomicBoolean processing=new AtomicBoolean(false); + private long last_update=System.currentTimeMillis(); + + public boolean acquire() { + return processing.compareAndSet(false, true); + } + + public boolean release() { + boolean result=processing.compareAndSet(true, false); + if(result) + last_update=System.currentTimeMillis(); + return result; + } + + public void add(Message msg) { + queue.add(msg); + } + + public Message remove() { + return queue.poll(); + } + + public void clear() { + queue.clear(); + } + + public int size() { + return queue.size(); + } + + public long getLastUpdate() { + return last_update; + } + } + + + protected class QueueThread implements Runnable { + protected final MessageQueue queue; + protected boolean first=true; + + public QueueThread(MessageQueue queue) { + this.queue=queue; + } + + + /** Try to remove as many messages as possible from the queue and pass them up. The outer and inner loop and + * the size() check at the end prevent the following scenario: + *
            +         * - Threads T1 and T2
            +         * - T1 has the CAS
            +         * - T1: remove() == null
            +         * - T2: add()
            +         * - T2: attempt to set the CAS: false, return
            +         * - T1: set the CAS to false, return
            +         * ==> Result: we have a message in the queue that nobody takes care of !
            +         * 
            + *

            + */ + public void run() { + while(true) { // outer loop + if(first) // we already have the queue CAS acquired + first=false; + else { + if(!queue.acquire()) + return; + } + + try { + Message msg_to_deliver; + while((msg_to_deliver=queue.remove()) != null) { // inner loop + try { + up_prot.up(new Event(Event.MSG, msg_to_deliver)); + } + catch(Throwable t) { + log.error("couldn't deliver message " + msg_to_deliver, t); + } + } + } + finally { + queue.release(); + } + + // although ConcurrentLinkedQueue.size() iterates through the list, this is not costly, + // as at this point, the queue is almost always empty, or has only a few elements + if(queue.size() == 0) // prevents a concurrent add() (which returned) to leave a dangling message in the queue + break; + } + } + } + + + protected class ExpiryTask implements Runnable { + + public void run() { + try { + _run(); + } + catch(Throwable t) { + log.error("failed expiring old scopes", t); + } + } + + protected void _run() { + long current_time=System.currentTimeMillis(); + for(Map.Entry> entry: queues.entrySet()) { + ConcurrentMap map=entry.getValue(); + for(Iterator> it=map.entrySet().iterator(); it.hasNext();) { + Map.Entry entry2=it.next(); + Short scope=entry2.getKey(); + MessageQueue queue=entry2.getValue(); + long diff=current_time - queue.getLastUpdate(); + if(diff >= expiration_time && queue.size() == 0) { + it.remove(); + if(log.isTraceEnabled()) + log.trace("expired scope " + entry.getKey() + "::" + scope + " (" + diff + " ms old)"); + } + } + } + } + } + + + public static class ScopeHeader extends Header { + public static final byte MSG = 1; + public static final byte EXPIRE = 2; + + byte type; + short scope=0; // starts with 1 + + public static ScopeHeader createMessageHeader(short scope) { + return new ScopeHeader(MSG, scope); + } + + public static ScopeHeader createExpireHeader(short scope) { + return new ScopeHeader(EXPIRE, scope); + } + + public ScopeHeader() { + } + + private ScopeHeader(byte type, short scope) { + this.type=type; + this.scope=scope; + } + + public short getScope() { + return scope; + } + + public int size() { + switch(type) { + case MSG: + case EXPIRE: + return Global.BYTE_SIZE + Global.SHORT_SIZE; + default: + throw new IllegalStateException("type has to be MSG or EXPIRE"); + } + } + + public void writeTo(DataOutputStream out) throws IOException { + out.writeByte(type); + switch(type) { + case MSG: + case EXPIRE: + out.writeShort(scope); + break; + default: + throw new IllegalStateException("type has to be MSG or EXPIRE"); + } + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + type=in.readByte(); + switch(type) { + case MSG: + case EXPIRE: + scope=in.readShort(); + break; + default: + throw new IllegalStateException("type has to be MSG or EXPIRE"); + } + } + + public String toString() { + StringBuilder sb=new StringBuilder(typeToString(type)); + switch(type) { + case MSG: + case EXPIRE: + sb.append(": scope=").append(scope); + break; + default: + sb.append("n/a"); + } + return sb.toString(); + } + + public static String typeToString(byte type) { + switch(type) { + case MSG: return "MSG"; + case EXPIRE: return "EXPIRE"; + default: return "n/a"; + } + } + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/SEQUENCER.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/SEQUENCER.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/SEQUENCER.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/SEQUENCER.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,34 +2,35 @@ package org.jgroups.protocols; import org.jgroups.*; +import org.jgroups.annotations.Experimental; import org.jgroups.annotations.MBean; import org.jgroups.annotations.ManagedAttribute; import org.jgroups.annotations.ManagedOperation; -import org.jgroups.annotations.Experimental; import org.jgroups.stack.Protocol; import org.jgroups.util.SeqnoTable; -import org.jgroups.util.Streamable; import org.jgroups.util.Util; import java.io.*; import java.util.*; +import java.util.concurrent.atomic.AtomicLong; /** * Implementation of total order protocol using a sequencer. Consult doc/design/SEQUENCER.txt for details * @author Bela Ban - * @version $Id: SEQUENCER.java,v 1.24 2008/10/10 13:46:12 vlada Exp $ */ @Experimental @MBean(description="Implementation of total order protocol using a sequencer") public class SEQUENCER extends Protocol { - private Address local_addr=null, coord=null; - static final String name="SEQUENCER"; - private boolean is_coord=false; - private long seqno=0; + private Address local_addr=null, coord=null; + private final Collection

            members=new ArrayList
            (); + private volatile boolean is_coord=false; + private AtomicLong seqno=new AtomicLong(0); - /** Map: maintains messages forwarded to the coord which which no ack has been received yet */ - private final Map forward_table=new TreeMap(); + /** Maintains messages forwarded to the coord which which no ack has been received yet. + * Needs to be sorted so we resend them in the right order + */ + private final Map forward_table=new TreeMap(); /** Map: maintains the highest seqnos seen for a given member */ private final SeqnoTable received_table=new SeqnoTable(0); @@ -43,7 +44,6 @@ public boolean isCoordinator() {return is_coord;} public Address getCoordinator() {return coord;} public Address getLocalAddress() {return local_addr;} - public String getName() {return name;} @ManagedAttribute public long getForwarded() {return forwarded_msgs;} @ManagedAttribute @@ -73,27 +73,26 @@ return dumpStats().toString(); } - private final long nextSeqno() { - synchronized(this) { - return seqno++; - } - } public Object down(Event evt) { switch(evt.getType()) { case Event.MSG: Message msg=(Message)evt.getArg(); + if(msg.isFlagSet(Message.NO_TOTAL_ORDER)) + break; Address dest=msg.getDest(); if(dest == null || dest.isMulticastAddress()) { // only handle multicasts - long next_seqno=nextSeqno(); - SequencerHeader hdr=new SequencerHeader(SequencerHeader.FORWARD, local_addr, next_seqno); - msg.putHeader(name, hdr); - if(!is_coord) { - forwardToCoord(msg, next_seqno); + long next_seqno=seqno.getAndIncrement(); + if(is_coord) { + SequencerHeader hdr=new SequencerHeader(SequencerHeader.BCAST, local_addr, next_seqno); + msg.putHeader(this.id, hdr); + broadcast(msg, false); // don't copy, just use the message passed as argument } else { - broadcast(msg); + // SequencerHeader hdr=new SequencerHeader(SequencerHeader.FORWARD, local_addr, next_seqno); + // msg.putHeader(this.id, hdr); + forwardToCoord(msg, next_seqno); } return null; // don't pass down } @@ -102,6 +101,10 @@ case Event.VIEW_CHANGE: handleViewChange((View)evt.getArg()); break; + + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; } return down_prot.down(evt); } @@ -114,14 +117,11 @@ SequencerHeader hdr; switch(evt.getType()) { - - case Event.SET_LOCAL_ADDRESS: - local_addr=(Address)evt.getArg(); - break; - case Event.MSG: msg=(Message)evt.getArg(); - hdr=(SequencerHeader)msg.getHeader(name); + if(msg.isFlagSet(Message.NO_TOTAL_ORDER)) + break; + hdr=(SequencerHeader)msg.getHeader(this.id); if(hdr == null) break; // pass up @@ -129,22 +129,32 @@ case SequencerHeader.FORWARD: if(!is_coord) { if(log.isErrorEnabled()) - log.warn("I (" + local_addr + ") am not the coord and don't handle " + - "FORWARD requests, ignoring request"); + log.error(local_addr + ": non-coord; dropping FORWARD request from " + msg.getSrc()); return null; } - broadcast(msg); + broadcast(msg, true); // do copy the message received_forwards++; return null; + case SequencerHeader.BCAST: - deliver(msg, hdr); // deliver a copy and return (discard the original msg) + deliver(msg, evt, hdr); + received_bcasts++; + return null; + + case SequencerHeader.WRAPPED_BCAST: + unwrapAndDeliver(msg); // unwrap the original message (in the payload) and deliver it received_bcasts++; return null; } break; case Event.VIEW_CHANGE: + Object retval=up_prot.up(evt); handleViewChange((View)evt.getArg()); + return retval; + + case Event.SUSPECT: + handleSuspect((Address)evt.getArg()); break; } @@ -156,19 +166,45 @@ /* --------------------------------- Private Methods ----------------------------------- */ private void handleViewChange(View v) { - Vector
            members=v.getMembers(); - if(members.isEmpty()) return; + Vector
            mbrs=v.getMembers(); + if(mbrs.isEmpty()) return; + boolean coord_changed=false; - Address prev_coord=coord; - coord=(Address)members.firstElement(); - is_coord=local_addr != null && local_addr.equals(coord); + synchronized(this) { + members.clear(); + members.addAll(mbrs); + Address prev_coord=coord; + coord=mbrs.firstElement(); + is_coord=local_addr != null && local_addr.equals(coord); + coord_changed=prev_coord != null && !prev_coord.equals(coord); + } - boolean coord_changed=prev_coord != null && !prev_coord.equals(coord); if(coord_changed) { resendMessagesInForwardTable(); // maybe optimize in the future: broadcast directly if coord } // remove left members from received_table - received_table.retainAll(members); + received_table.retainAll(mbrs); + } + + private void handleSuspect(Address suspected_mbr) { + boolean coord_changed=false; + + if(suspected_mbr == null) + return; + + synchronized(this) { + List
            non_suspected_mbrs=new ArrayList
            (members); + non_suspected_mbrs.remove(suspected_mbr); + if(!non_suspected_mbrs.isEmpty()) { + Address prev_coord=coord; + coord=non_suspected_mbrs.get(0); + is_coord=local_addr != null && local_addr.equals(coord); + coord_changed=prev_coord != null && !prev_coord.equals(coord); + } + } + if(coord_changed) { + resendMessagesInForwardTable(); // maybe optimize in the future: broadcast directly if coord + } } /** @@ -179,82 +215,137 @@ * from being inserted until we're done, that's why there's synchronization. */ private void resendMessagesInForwardTable() { - Map copy; + Map copy; synchronized(forward_table) { - copy=new TreeMap(forward_table); + copy=new TreeMap(forward_table); } - for(Message msg: copy.values()) { - msg.setDest(coord); - down_prot.down(new Event(Event.MSG, msg)); + for(Map.Entry entry: copy.entrySet()) { + Long key=entry.getKey(); + byte[] val=entry.getValue(); + + Message forward_msg=new Message(coord, null, val); + SequencerHeader hdr=new SequencerHeader(SequencerHeader.FORWARD, local_addr, key); + forward_msg.putHeader(this.id, hdr); + + if (log.isTraceEnabled()) { + log.trace("resending msg " + local_addr + "::" + key + " to coord (" + coord + ")"); + } + down_prot.down(new Event(Event.MSG, forward_msg)); } } - private void forwardToCoord(Message msg, long seqno) { - msg.setDest(coord); // we change the message dest from multicast to unicast (to coord) - synchronized(forward_table) { - forward_table.put(new Long(seqno), msg); + private void forwardToCoord(final Message msg, long seqno) { + if(msg.getSrc() == null) + msg.setSrc(local_addr); + if(log.isTraceEnabled()) + log.trace("forwarding msg " + msg + " (seqno " + seqno + ") to coord (" + coord + ")"); + + byte[] marshalled_msg; + try { + marshalled_msg=Util.objectToByteBuffer(msg); + synchronized(forward_table) { + forward_table.put(seqno, marshalled_msg); + } + Message forward_msg=new Message(coord, null, marshalled_msg); + SequencerHeader hdr=new SequencerHeader(SequencerHeader.FORWARD, local_addr, seqno); + forward_msg.putHeader(this.id, hdr); + down_prot.down(new Event(Event.MSG, forward_msg)); + forwarded_msgs++; + } + catch(Exception e) { + log.error("failed marshalling message", e); } - down_prot.down(new Event(Event.MSG, msg)); - forwarded_msgs++; } - private void broadcast(Message msg) { - SequencerHeader hdr=(SequencerHeader)msg.getHeader(name); - hdr.type=SequencerHeader.BCAST; // we change the type of header, but leave the tag intact - msg.setDest(null); // mcast - msg.setSrc(local_addr); // the coord is sending it - this will be replaced with sender in deliver() - down_prot.down(new Event(Event.MSG, msg)); + private void broadcast(final Message msg, boolean copy) { + Message bcast_msg=null; + final SequencerHeader hdr=(SequencerHeader)msg.getHeader(this.id); + + if(!copy) { + bcast_msg=msg; // no need to add a header, message already has one + } + else { + bcast_msg=new Message(null, local_addr, msg.getRawBuffer(), msg.getOffset(), msg.getLength()); + SequencerHeader new_hdr=new SequencerHeader(SequencerHeader.WRAPPED_BCAST, hdr.getOriginalSender(), hdr.getSeqno()); + bcast_msg.putHeader(this.id, new_hdr); + } + + if(log.isTraceEnabled()) + log.trace("broadcasting msg " + bcast_msg + " (seqno " + hdr.getSeqno() + ")"); + + down_prot.down(new Event(Event.MSG, bcast_msg)); bcast_msgs++; } /** - * We copy the message in order to change the sender's address. If we did this on the original message, - * retransmission would likely run into problems, and possibly also stability (STABLE) of messages + * Unmarshal the original message (in the payload) and then pass it up (unless already delivered) * @param msg - * @param hdr */ - private void deliver(Message msg, SequencerHeader hdr) { - Address original_sender=hdr.getOriginalSender(); - if(original_sender == null) { + private void unwrapAndDeliver(final Message msg) { + try { + SequencerHeader hdr=(SequencerHeader)msg.getHeader(this.id); + Message msg_to_deliver=(Message)Util.objectFromByteBuffer(msg.getRawBuffer(), msg.getOffset(), msg.getLength()); + long msg_seqno=hdr.getSeqno(); + if(!canDeliver(msg_to_deliver.getSrc(), msg_seqno)) + return; + if(log.isTraceEnabled()) + log.trace("delivering msg " + msg_to_deliver + " (seqno " + msg_seqno + + "), original sender " + msg_to_deliver.getSrc()); + + up_prot.up(new Event(Event.MSG, msg_to_deliver)); + } + catch(Exception e) { + log.error("failure unmarshalling buffer", e); + } + } + + + private void deliver(Message msg, Event evt, SequencerHeader hdr) { + Address sender=msg.getSrc(); + if(sender == null) { if(log.isErrorEnabled()) - log.error("original sender is null, cannot swap sender address back to original sender"); + log.error("sender is null, cannot deliver msg " + msg); return; } long msg_seqno=hdr.getSeqno(); + if(!canDeliver(sender, msg_seqno)) + return; + if(log.isTraceEnabled()) + log.trace("delivering msg " + msg + " (seqno " + msg_seqno + "), sender " + sender); + up_prot.up(evt); + } + + private boolean canDeliver(Address sender, long seqno) { // this is the ack for the message sent by myself - if(original_sender.equals(local_addr)) { + if(sender.equals(local_addr)) { synchronized(forward_table) { - forward_table.remove(new Long(msg_seqno)); + forward_table.remove(seqno); } } // if msg was already delivered, discard it - boolean added=received_table.add(original_sender, msg_seqno); + boolean added=received_table.add(sender, seqno); if(!added) { if(log.isWarnEnabled()) - log.warn("seqno (" + original_sender + "::" + msg_seqno + " has already been received " + - "(highest received=" + received_table.getHighestReceived(original_sender) + + log.warn("seqno (" + sender + "::" + seqno + " has already been received " + + "(highest received=" + received_table.getHighestReceived(sender) + "); discarding duplicate message"); - return; } - - // pass a copy of the message up the stack - Message tmp=msg.copy(true); - tmp.setSrc(original_sender); - up_prot.up(new Event(Event.MSG, tmp)); + return added; } - /* ----------------------------- End of Private Methods -------------------------------- */ +/* ----------------------------- End of Private Methods -------------------------------- */ - public static class SequencerHeader extends Header implements Streamable { - static final byte FORWARD = 1; - static final byte BCAST = 2; + public static class SequencerHeader extends Header { + private static final byte FORWARD = 1; + private static final byte BCAST = 2; + private static final byte WRAPPED_BCAST = 3; byte type=-1; /** the original sender's address and a seqno */ @@ -287,22 +378,14 @@ private final String printType() { switch(type) { - case FORWARD: return "FORWARD"; - case BCAST: return "BCAST"; - default: return "n/a"; + case FORWARD: return "FORWARD"; + case BCAST: return "BCAST"; + case WRAPPED_BCAST: return "WRAPPED_BCAST"; + default: return "n/a"; } } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeByte(type); - out.writeObject(tag); - } - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - type=in.readByte(); - tag=(ViewId)in.readObject(); - } - + public void writeTo(DataOutputStream out) throws IOException { out.writeByte(type); Util.writeStreamable(tag, out); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/SFC.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/SFC.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/SFC.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/SFC.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,22 +1,16 @@ package org.jgroups.protocols; import org.jgroups.*; -import org.jgroups.annotations.GuardedBy; -import org.jgroups.annotations.MBean; -import org.jgroups.annotations.ManagedAttribute; -import org.jgroups.annotations.ManagedOperation; -import org.jgroups.annotations.Property; +import org.jgroups.annotations.*; import org.jgroups.stack.Protocol; -import org.jgroups.util.Util; -import org.jgroups.util.Streamable; import org.jgroups.util.BoundedList; +import java.io.*; import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import java.util.concurrent.TimeUnit; -import java.io.*; /** * Simple flow control protocol. After max_credits bytes sent to the group (or an individual member), the sender blocks @@ -25,12 +19,11 @@ * Note that SFC supports only flow control for multicast messages; unicast flow control is not supported ! Use FC if * unicast flow control is required. * @author Bela Ban - * @version $Id: SFC.java,v 1.24 2008/10/21 12:41:47 vlada Exp $ */ +@Experimental +@Deprecated @MBean(description="Simple flow control protocol") public class SFC extends Protocol { - private static final String name="SFC"; - /* ----------------------------------------- Properties -------------------------------------------------- */ @Property(description="Max number of bytes to send per receiver until an ack must be received to proceed. Default is 2000000 bytes") @@ -100,7 +93,6 @@ blockings.clear(); } - @ManagedAttribute public long getMaxCredits() {return max_credits;} @ManagedAttribute public long getCredits() {return curr_credits_available;} @@ -171,11 +163,6 @@ // ------------------- End of management information ---------------------- - - public final String getName() { - return name; - } - public Object down(Event evt) { switch(evt.getType()) { case Event.MSG: @@ -277,7 +264,7 @@ case Event.MSG: Message msg=(Message)evt.getArg(); - Header hdr=(Header)msg.getHeader(name); + Header hdr=(Header)msg.getHeader(this.id); Address sender=msg.getSrc(); if(hdr != null) { switch(hdr.type) { @@ -322,7 +309,6 @@ public void init() throws Exception{ - Util.checkBufferSize("SFC.max_credits", max_credits); MAX_CREDITS=new Long(max_credits); curr_credits_available=max_credits; } @@ -521,7 +507,7 @@ Message credit_req=new Message(); // credit_req.setFlag(Message.OOB); // we need to receive the credit request after regular messages byte type=urgent? Header.URGENT_CREDIT_REQUEST : Header.CREDIT_REQUEST; - credit_req.putHeader(name, new Header(type)); + credit_req.putHeader(this.id, new Header(type)); num_credit_requests_sent++; down_prot.down(new Event(Event.MSG, credit_req)); } @@ -530,7 +516,7 @@ Message credit_rsp=new Message(dest); credit_rsp.setFlag(Message.OOB); Header hdr=new Header(Header.REPLENISH); - credit_rsp.putHeader(name, hdr); + credit_rsp.putHeader(this.id, hdr); if(log.isTraceEnabled()) log.trace("sending credit response to " + dest); num_replenishments_sent++; @@ -539,7 +525,7 @@ - public static class Header extends org.jgroups.Header implements Streamable { + public static class Header extends org.jgroups.Header { public static final byte CREDIT_REQUEST = 1; // the sender of the message is the requester public static final byte REPLENISH = 2; // the sender of the message is the creditor public static final byte URGENT_CREDIT_REQUEST = 3; @@ -547,7 +533,6 @@ byte type=CREDIT_REQUEST; public Header() { - } public Header(byte type) { @@ -558,14 +543,6 @@ return Global.BYTE_SIZE; } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeByte(type); - } - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - type=in.readByte(); - } - public void writeTo(DataOutputStream out) throws IOException { out.writeByte(type); } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/SHARED_LOOPBACK.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/SHARED_LOOPBACK.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/SHARED_LOOPBACK.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/SHARED_LOOPBACK.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,7 +2,7 @@ import org.jgroups.Address; import org.jgroups.Event; -import org.jgroups.Message; +import org.jgroups.PhysicalAddress; import org.jgroups.stack.IpAddress; import java.util.Map; @@ -13,11 +13,13 @@ * Loopback transport shared by all channels within the same VM. Property for testing is that no messages are lost. Allows * us to test various protocols (with ProtocolTester) at maximum speed. * @author Bela Ban - * @version $Id: SHARED_LOOPBACK.java,v 1.5 2008/04/04 10:23:36 belaban Exp $ */ public class SHARED_LOOPBACK extends TP { private static int next_port=10000; + private PhysicalAddress physical_addr=null; + private Address local_addr=null; + /** Map of cluster names and address-protocol mappings. Used for routing messages to all or single members */ private static final Map> routing_table=new ConcurrentHashMap>(); @@ -25,13 +27,15 @@ public SHARED_LOOPBACK() { } - + public boolean supportsMulticasting() { + return false; + } public String toString() { return "SHARED_LOOPBACK(local address: " + local_addr + ')'; } - public void sendToAllMembers(byte[] data, int offset, int length) throws Exception { + public void sendMulticast(byte[] data, int offset, int length) throws Exception { Map dests=routing_table.get(channel_name); if(dests == null) { if(log.isWarnEnabled()) @@ -42,7 +46,7 @@ Address dest=entry.getKey(); SHARED_LOOPBACK target=entry.getValue(); try { - target.receive(dest, local_addr, data, offset, length); + target.receive(local_addr, data, offset, length); } catch(Throwable t) { log.error("failed sending message to " + dest, t); @@ -50,7 +54,7 @@ } } - public void sendToSingleMember(Address dest, byte[] data, int offset, int length) throws Exception { + public void sendUnicast(PhysicalAddress dest, byte[] data, int offset, int length) throws Exception { Map dests=routing_table.get(channel_name); if(dests == null) { if(log.isWarnEnabled()) @@ -63,40 +67,40 @@ log.warn("destination address " + dest + " not found"); return; } - target.receive(dest, local_addr, data, offset, length); + target.receive(local_addr, data, offset, length); + } + + protected void sendToSingleMember(Address dest, byte[] buf, int offset, int length) throws Exception { + Map dests=routing_table.get(channel_name); + if(dests == null) { + if(log.isWarnEnabled()) + log.warn("no destination found for " + channel_name); + return; + } + SHARED_LOOPBACK target=dests.get(dest); + if(target == null) { + if(log.isWarnEnabled()) + log.warn("destination address " + dest + " not found"); + return; + } + target.receive(local_addr, buf, offset, length); } public String getInfo() { return toString(); } - public void postUnmarshalling(Message msg, Address dest, Address src, boolean multicast) { - msg.setDest(dest); - } - public void postUnmarshallingList(Message msg, Address dest, boolean multicast) { - msg.setDest(dest); + protected PhysicalAddress getPhysicalAddress() { + return physical_addr; } /*------------------------------ Protocol interface ------------------------------ */ - public String getName() { - return "SHARED_LOOPBACK"; - } - -// public boolean setProperties(Properties props) { -// super.setProperties(props); -// if(!props.isEmpty()) { -// log.error("the following properties are not recognized: " + props); -// return false; -// } -// return true; -// } - - public void init() throws Exception { local_addr=new IpAddress("127.0.0.1", next_port++); super.init(); + } public void start() throws Exception { @@ -113,10 +117,16 @@ switch(evt.getType()) { case Event.CONNECT: - case Event.CONNECT_WITH_STATE_TRANSFER: + case Event.CONNECT_WITH_STATE_TRANSFER: + case Event.CONNECT_USE_FLUSH: + case Event.CONNECT_WITH_STATE_TRANSFER_USE_FLUSH: register(channel_name, local_addr, this); break; + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; + case Event.DISCONNECT: unregister(channel_name, local_addr); break; @@ -125,7 +135,7 @@ return retval; } - private void register(String channel_name, Address local_addr, SHARED_LOOPBACK shared_loopback) { + private static void register(String channel_name, Address local_addr, SHARED_LOOPBACK shared_loopback) { Map map=routing_table.get(channel_name); if(map == null) { map=new ConcurrentHashMap(); @@ -134,7 +144,7 @@ map.put(local_addr, shared_loopback); } - private void unregister(String channel_name, Address local_addr) { + private static void unregister(String channel_name, Address local_addr) { Map map=routing_table.get(channel_name); if(map != null) { map.remove(local_addr); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/SHUFFLE.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/SHUFFLE.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/SHUFFLE.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/SHUFFLE.java 2011-10-18 11:22:35.000000000 +0000 @@ -5,110 +5,138 @@ import org.jgroups.annotations.Property; import org.jgroups.annotations.Unsupported; import org.jgroups.stack.Protocol; +import org.jgroups.util.TimeScheduler; -import java.util.*; - +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; /** - * This layer shuffles upcoming messages, put it just above your bottom layer. - * If you system sends less than 2 messages per sec you can notice a latency due - * to this layer. + * Reorders messages by buffering them and shuffling the result after TIMEOUT ms. * - * @author Gianluca Collot + * @author Bela Ban * */ @Unsupported -public class SHUFFLE extends Protocol implements Runnable { +public class SHUFFLE extends Protocol { + protected TimeScheduler timer=null; + protected final List up_msgs=new LinkedList(); + protected final List down_msgs=new LinkedList(); + protected Future task=null; - @Property - String name="SHUFFLE"; - final List messages; - Thread messagesHandler; - public SHUFFLE() { - messages = Collections.synchronizedList(new ArrayList()); - } + @Property protected boolean up=true; + @Property protected boolean down=false; + @Property(description="max number of messages before we bundle") + protected int max_size=10; + @Property(description="max time (ms) before we pass the bundled messages up or down") + protected long max_time=1500L; - public String getName() { - return name; + + public boolean isUp() { + return up; } - /** - * Adds upcoming messages to the messages List<\code> where the messagesHandler<\code> - * retrieves them. - */ + public void setUp(boolean up) { + this.up=up; + } - public Object up(Event evt) { - Message msg; + public boolean isDown() { + return down; + } - switch (evt.getType()) { + public void setDown(boolean down) { + this.down=down; + } - case Event.MSG: - msg=(Message)evt.getArg(); - // Do something with the event, e.g. extract the message and remove a header. - // Optionally pass up - messages.add(msg); - return null; - } + public int getMaxSize() { + return max_size; + } - return up_prot.up(evt); // Pass up to the layer above us + public void setMaxSize(int max_size) { + this.max_size=max_size; } + public long getMaxTime() { + return max_time; + } + public void setMaxTime(long max_time) { + this.max_time=max_time; + } + public void init() throws Exception { + super.init(); + timer=getTransport().getTimer(); + } - /** - * Starts the messagesHandler<\code> - */ - public void start() throws Exception { - messagesHandler = new Thread(this,"MessagesHandler"); - messagesHandler.setDaemon(true); - messagesHandler.start(); + public Object up(Event evt) { + if(!up) + return up_prot.up(evt); + if(evt.getType() != Event.MSG) + return up_prot.up(evt); + Message msg=(Message)evt.getArg(); + synchronized(up_msgs) { + up_msgs.add(msg); + } + if(up_msgs.size() >= max_size) { + shuffleAndSendMessages(); + } + else + startTask(); + return null; } - /** - * Stops the messagesHandler - */ - public void stop() { - Thread tmp = messagesHandler; - messagesHandler = null; - try { - tmp.join(); - } catch (Exception ex) {ex.printStackTrace();} + public Object down(Event evt) { + if(!down) + return down_prot.down(evt); + if(evt.getType() != Event.MSG) + return down_prot.down(evt); + Message msg=(Message)evt.getArg(); + synchronized(down_msgs) { + down_msgs.add(msg); + } + if(down_msgs.size() >= max_size) { + shuffleAndSendMessages(); + } + else + startTask(); + return null; } - /** - * Removes a random chosen message from the messages List<\code> if there - * are less than 10 messages in the List it waits some time to ensure to chose from - * a set of messages > 1. - */ + private synchronized void startTask() { + if(task == null || task.isDone() || task.isCancelled()) { + task=timer.schedule(new Runnable() { - public void run() { - Message msg; - while (messagesHandler != null) { - if (!messages.isEmpty()) { - msg = (Message) messages.remove(rnd(messages.size())); - up_prot.up(new Event(Event.MSG,msg)); - } - if (messages.size() < 5) { - try { - Thread.sleep(300); /** @todo make this time user configurable */ - } - catch (Exception ex) { - ex.printStackTrace(); + public void run() { + shuffleAndSendMessages(); } + }, max_time, TimeUnit.MILLISECONDS); + } + } + + private void shuffleAndSendMessages() { + synchronized(up_msgs) { + if(!up_msgs.isEmpty()) { + Collections.shuffle(up_msgs); + for(Message msg: up_msgs) + up_prot.up(new Event(Event.MSG, msg)); + up_msgs.clear(); + } + } + + synchronized(down_msgs) { + if(!down_msgs.isEmpty()) { + Collections.shuffle(down_msgs); + for(Message msg: down_msgs) + down_prot.down(new Event(Event.MSG, msg)); + down_msgs.clear(); } - }// while - // PassUp remaining messages - Iterator iter = messages.iterator(); - while (iter.hasNext()) { - msg = (Message) iter.next(); - up_prot.up(new Event(Event.MSG,msg)); } } - // random integer between 0 and n-1 - int rnd(int n) { return (int)(Math.random()*n); } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/SIZE.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/SIZE.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/SIZE.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/SIZE.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,18 +1,15 @@ -// $Id: SIZE.java,v 1.24 2008/10/28 08:50:17 belaban Exp $ package org.jgroups.protocols; import org.jgroups.Event; import org.jgroups.Message; -import org.jgroups.util.Util; -import org.jgroups.util.ExposedByteArrayOutputStream; -import org.jgroups.util.ExposedDataOutputStream; import org.jgroups.annotations.Property; import org.jgroups.annotations.Unsupported; import org.jgroups.stack.Protocol; +import org.jgroups.util.ExposedByteArrayOutputStream; +import org.jgroups.util.ExposedDataOutputStream; +import org.jgroups.util.Util; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; import java.util.Vector; @@ -39,34 +36,24 @@ final ExposedDataOutputStream out=new ExposedDataOutputStream(out_stream); - /** - * All protocol names have to be unique ! - */ - public String getName() { - return "SIZE"; - } - - public void init() { } public Object up(Event evt) { - Message msg; - int payload_size=0, serialized_size; - + switch(evt.getType()) { case Event.MSG: - msg=(Message)evt.getArg(); - payload_size=msg.getLength(); + Message msg=(Message)evt.getArg(); + int payload_size=msg.getLength(); if(raw_buffer) { if(log.isTraceEnabled()) log.trace("size of message buffer is " + payload_size + ", " + numHeaders(msg) + " headers"); } else { - serialized_size=sizeOf(msg); + int serialized_size=sizeOf(msg); if(serialized_size > min_size) { if(log.isTraceEnabled()) log.trace("size of serialized message is " + serialized_size + diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/SMACK.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/SMACK.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/SMACK.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/SMACK.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,9 +1,9 @@ - package org.jgroups.protocols; import org.jgroups.*; import org.jgroups.annotations.Experimental; +import org.jgroups.annotations.GuardedBy; import org.jgroups.annotations.Property; import org.jgroups.annotations.Unsupported; import org.jgroups.conf.PropertyConverters; @@ -11,11 +11,17 @@ import org.jgroups.stack.AckReceiverWindow; import org.jgroups.stack.Protocol; import org.jgroups.stack.StaticInterval; -import org.jgroups.util.Streamable; import org.jgroups.util.Util; -import java.io.*; -import java.util.*; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.Vector; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; /** @@ -44,7 +50,6 @@ *
      * Advantage of this protocol: no group membership necessary, fast. * @author Bela Ban Aug 2002 - * @version $Id: SMACK.java,v 1.30 2008/05/30 20:39:30 vlada Exp $ *
      Fix membershop bug: start a, b, kill b, restart b: b will be suspected by a. */ @Experimental @Unsupported @@ -55,15 +60,16 @@ int max_xmits=10; // max retransmissions (if still no ack, member will be removed) final Set
      members=new LinkedHashSet
      (); // contains Addresses AckMcastSenderWindow sender_win=null; - final Map receivers=new HashMap(); // keys=sender (Address), values=AckReceiverWindow - final Map xmit_table=new HashMap(); // keeps track of num xmits / member (keys: mbr, val:num) + final Map receivers=Util.createConcurrentMap(); // keys=sender (Address), values=AckReceiverWindow + final Map xmit_table=Util.createConcurrentMap(); // keeps track of num xmits / member (keys: mbr, val:num) Address local_addr=null; // my own address + @GuardedBy("lock") long seqno=1; // seqno for msgs sent by this sender + final Lock lock=new ReentrantLock(); // for access to seqno long vid=1; // for the fake view changes @Property boolean print_local_addr=true; - static final String name="SMACK"; - + @@ -71,18 +77,12 @@ public SMACK() { } - public String getName() { - return name; - } - public void stop() { - AckReceiverWindow win; if(sender_win != null) { sender_win.stop(); sender_win=null; } - for(Iterator it=receivers.values().iterator(); it.hasNext();) { - win=(AckReceiverWindow)it.next(); + for(AckReceiverWindow win: receivers.values()) { win.reset(); } receivers.clear(); @@ -94,16 +94,6 @@ switch(evt.getType()) { - case Event.SET_LOCAL_ADDRESS: - local_addr=(Address)evt.getArg(); - addMember(local_addr); - if(print_local_addr) { - System.out.println("\n-------------------------------------------------------\n" + - "GMS: address is " + local_addr + - "\n-------------------------------------------------------"); - } - break; - case Event.SUSPECT: if(log.isInfoEnabled()) log.info("removing suspected member " + evt.getArg()); removeMember((Address)evt.getArg()); @@ -111,9 +101,9 @@ case Event.MSG: Message msg=(Message)evt.getArg(), tmp_msg; - if(msg == null) break; + if(msg == null || msg.isFlagSet(Message.NO_RELIABILITY)) break; sender=msg.getSrc(); - SmackHeader hdr=(SmackHeader)msg.getHeader(name); + SmackHeader hdr=(SmackHeader)msg.getHeader(this.id); if(hdr == null) // is probably a unicast message break; switch(hdr.type) { @@ -131,7 +121,7 @@ boolean added=win.add(hdr.seqno, msg); Message ack_msg=new Message(sender); - ack_msg.putHeader(name, new SmackHeader(SmackHeader.ACK, hdr.seqno)); + ack_msg.putHeader(this.id, new SmackHeader(SmackHeader.ACK, hdr.seqno)); down_prot.down(new Event(Event.MSG, ack_msg)); // message is passed up if OOB. Later, when remove() is called, we discard it. This affects ordering ! @@ -162,8 +152,7 @@ if(log.isInfoEnabled()) log.info("received join announcement by " + msg.getSrc()); if(!containsMember(sender)) { Message join_rsp=new Message(sender); - join_rsp.putHeader(name, new SmackHeader(SmackHeader.JOIN_ANNOUNCEMENT, -1)); - down_prot.down(new Event(Event.ENABLE_UNICASTS_TO, sender)); + join_rsp.putHeader(this.id, new SmackHeader(SmackHeader.JOIN_ANNOUNCEMENT, -1)); down_prot.down(new Event(Event.MSG, join_rsp)); } addMember(sender); @@ -192,8 +181,9 @@ case Event.DISCONNECT: leave_msg=new Message(); - leave_msg.putHeader(name, new SmackHeader(SmackHeader.LEAVE_ANNOUNCEMENT, -1)); + leave_msg.putHeader(this.id, new SmackHeader(SmackHeader.LEAVE_ANNOUNCEMENT, -1)); down_prot.down(new Event(Event.MSG, leave_msg)); + Util.sleep(100); sender_win.stop(); break; @@ -203,7 +193,7 @@ // send join announcement Message join_msg=new Message(); - join_msg.putHeader(name, new SmackHeader(SmackHeader.JOIN_ANNOUNCEMENT, -1)); + join_msg.putHeader(this.id, new SmackHeader(SmackHeader.JOIN_ANNOUNCEMENT, -1)); down_prot.down(new Event(Event.MSG, join_msg)); return ret; @@ -211,12 +201,30 @@ // add a header with the current sequence number and increment seqno case Event.MSG: Message msg=(Message)evt.getArg(); - if(msg == null) break; + if(msg == null || msg.isFlagSet(Message.NO_RELIABILITY)) break; if(msg.getDest() == null || msg.getDest().isMulticastAddress()) { - msg.putHeader(name, new SmackHeader(SmackHeader.MCAST, seqno)); - sender_win.add(seqno, msg, new Vector
      (members)); - if(log.isTraceEnabled()) log.trace("sending mcast #" + seqno); - seqno++; + lock.lock(); + try { + long msg_id=seqno; + msg.putHeader(this.id, new SmackHeader(SmackHeader.MCAST, msg_id)); + sender_win.add(msg_id, msg, new Vector
      (members)); + if(log.isTraceEnabled()) log.trace("sending mcast #" + msg_id); + seqno++; + } + finally { + lock.unlock(); + } + + } + break; + + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + addMember(local_addr); + if(print_local_addr) { + System.out.println("\n-------------------------------------------------------\n" + + "GMS: address is " + local_addr + + "\n-------------------------------------------------------"); } break; } @@ -240,7 +248,7 @@ - public static class SmackHeader extends Header implements Streamable { + public static class SmackHeader extends Header { public static final byte MCAST=1; public static final byte ACK=2; public static final byte JOIN_ANNOUNCEMENT=3; @@ -248,7 +256,6 @@ byte type=0; long seqno=-1; - private static final long serialVersionUID=7605481696520929774L; public SmackHeader() { } @@ -258,18 +265,6 @@ this.seqno=seqno; } - - public void writeExternal(ObjectOutput out) throws IOException { - out.writeByte(type); - out.writeLong(seqno); - } - - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - type=in.readByte(); - seqno=in.readLong(); - } - public int size() { return Global.LONG_SIZE + Global.BYTE_SIZE; } @@ -333,6 +328,14 @@ down_prot.down(new Event(Event.VIEW_CHANGE, new_view)); if(sender_win != null) sender_win.remove(mbr); // causes retransmissions to mbr to stop + for(Map.Entry entry: receivers.entrySet()) { + Address member=entry.getKey(); + if(!members.contains(member)) { + AckReceiverWindow win=entry.getValue(); + win.reset(); + } + } + receivers.keySet().retainAll(members); } } @@ -345,4 +348,4 @@ /* --------------------------------- End of Private methods ------------------------------------ */ -} +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/STATS.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/STATS.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/STATS.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/STATS.java 2011-10-18 11:22:35.000000000 +0000 @@ -12,7 +12,6 @@ /** * Provides various stats * @author Bela Ban - * @version $Id: STATS.java,v 1.10 2008/08/12 08:43:56 belaban Exp $ */ public class STATS extends Protocol { long sent_msgs, sent_bytes, sent_ucasts, sent_mcasts, received_ucasts, received_mcasts; @@ -28,10 +27,6 @@ static final short DOWN=2; - public String getName() { - return "STATS"; - } - public void resetStats() { sent_msgs=sent_bytes=sent_ucasts=sent_mcasts=received_ucasts=received_mcasts=0; received_msgs=received_bytes=sent_ucast_bytes=sent_mcast_bytes=received_ucast_bytes=received_mcast_bytes=0; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/STOMP.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/STOMP.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/STOMP.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/STOMP.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,746 @@ +package org.jgroups.protocols; + +import org.jgroups.*; +import org.jgroups.annotations.*; +import org.jgroups.stack.Protocol; +import org.jgroups.util.UUID; +import org.jgroups.util.Util; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.net.*; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.concurrent.ConcurrentMap; + +/** + * Protocol which provides STOMP (http://stomp.codehaus.org/) support. Very simple implementation, with a + * one-thread-per-connection model. Use for a few hundred clients max.

      + * The intended use for this protocol is pub-sub with clients which handle text messages, e.g. stock updates, + * SMS messages to mobile clients, SNMP traps etc.

      + * Note that the full STOMP protocol has not yet been implemented, e.g. transactions are not supported. + * todo: use a thread pool to handle incoming frames and to send messages to clients + *

      + * todo: add PING to test health of client connections + *

      + * @author Bela Ban + * @since 2.11 + */ +@MBean(description="Server side STOPM protocol, STOMP clients can connect to it") +@Experimental +public class STOMP extends Protocol implements Runnable { + + /* ----------------------------------------- Properties ----------------------------------------------- */ + + @LocalAddress + @Property(name="bind_addr", + description="The bind address which should be used by the server socket. The following special values " + + "are also recognized: GLOBAL, SITE_LOCAL, LINK_LOCAL and NON_LOOPBACK", + defaultValueIPv4="0.0.0.0", defaultValueIPv6="::", + systemProperty={Global.STOMP_BIND_ADDR},writable=false) + protected InetAddress bind_addr=null; + + @Property(description="If set, then endpoint will be set to this address",systemProperty=Global.STOMP_ENDPOINT_ADDR) + protected String endpoint_addr; + + @Property(description="Port on which the STOMP protocol listens for requests",writable=false) + protected int port=8787; + + @Property(description="If set to false, then a destination of /a/b match /a/b/c, a/b/d, a/b/c/d etc") + protected boolean exact_destination_match=true; + + @Property(description="If true, information such as a list of endpoints, or views, will be sent to all clients " + + "(via the INFO command). This allows for example intelligent clients to connect to " + + "a different server should a connection be closed.") + protected boolean send_info=true; + + @Property(description="Forward received messages which don't have a StompHeader to clients") + protected boolean forward_non_client_generated_msgs=false; + + /* --------------------------------------------- JMX ---------------------------------------------------*/ + @ManagedAttribute(description="Number of client connections",writable=false) + public int getNumConnections() {return connections.size();} + + @ManagedAttribute(description="Number of subscriptions",writable=false) + public int getNumSubscriptions() {return subscriptions.size();} + + @ManagedAttribute(description="Print subscriptions",writable=false) + public String getSubscriptions() {return subscriptions.keySet().toString();} + + @ManagedAttribute + public String getEndpoints() {return endpoints.toString();} + + /* --------------------------------------------- Fields ------------------------------------------------------ */ + protected Address local_addr; + protected ServerSocket srv_sock; + @ManagedAttribute(writable=false) + protected String endpoint; + protected Thread acceptor; + protected final List connections=new LinkedList(); + protected final Map endpoints=new HashMap(); + + protected View view; + + // Subscriptions and connections which are subscribed + protected final ConcurrentMap> subscriptions=Util.createConcurrentMap(20); + + public static enum ClientVerb {CONNECT, SEND, SUBSCRIBE, UNSUBSCRIBE, BEGIN, COMMIT, ABORT, ACK, DISCONNECT} + public static enum ServerVerb {MESSAGE, RECEIPT, ERROR, CONNECTED, INFO} + + public static final byte NULL_BYTE=0; + + + + public STOMP() { + } + + + + public void start() throws Exception { + super.start(); + srv_sock=Util.createServerSocket(getSocketFactory(), Global.STOMP_SRV_SOCK, bind_addr, port); + if(log.isDebugEnabled()) + log.debug("server socket listening on " + srv_sock.getLocalSocketAddress()); + + if(acceptor == null) { + acceptor=getThreadFactory().newThread(this, "STOMP acceptor"); + acceptor.setDaemon(true); + acceptor.start(); + } + + endpoint=endpoint_addr != null? endpoint_addr : getAddress(); + } + + + public void stop() { + if(log.isDebugEnabled()) + log.debug("closing server socket " + srv_sock.getLocalSocketAddress()); + + if(acceptor != null && acceptor.isAlive()) { + try { + // this will terminate thread, peer will receive SocketException (socket close) + getSocketFactory().close(srv_sock); + } + catch(Exception ex) { + } + } + synchronized(connections) { + for(Connection conn: connections) + conn.stop(); + connections.clear(); + } + acceptor=null; + super.stop(); + } + + // Acceptor loop + public void run() { + Socket client_sock; + while(acceptor != null && srv_sock != null) { + try { + client_sock=srv_sock.accept(); + if(log.isTraceEnabled()) + log.trace("accepted connection from " + client_sock.getInetAddress() + ':' + client_sock.getPort()); + Connection conn=new Connection(client_sock); + Thread thread=getThreadFactory().newThread(conn, "STOMP client connection"); + thread.setDaemon(true); + + synchronized(connections) { + connections.add(conn); + } + thread.start(); + conn.sendInfo(); + } + catch(IOException io_ex) { + break; + } + } + acceptor=null; + } + + + public Object down(Event evt) { + switch(evt.getType()) { + case Event.VIEW_CHANGE: + handleView((View)evt.getArg()); + break; + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; + } + return down_prot.down(evt); + } + + public Object up(Event evt) { + switch(evt.getType()) { + case Event.MSG: + Message msg=(Message)evt.getArg(); + StompHeader hdr=(StompHeader)msg.getHeader(id); + if(hdr == null) { + if(forward_non_client_generated_msgs) { + HashMap hdrs=new HashMap(); + hdrs.put("sender", msg.getSrc().toString()); + sendToClients(hdrs, msg.getRawBuffer(), msg.getOffset(), msg.getLength()); + } + break; + } + + switch(hdr.type) { + case MESSAGE: + sendToClients(hdr.headers, msg.getRawBuffer(), msg.getOffset(), msg.getLength()); + break; + case ENDPOINT: + String tmp_endpoint=hdr.headers.get("endpoint"); + if(tmp_endpoint != null) { + boolean update_clients; + String old_endpoint=null; + synchronized(endpoints) { + endpoints.put(msg.getSrc(), tmp_endpoint); + } + update_clients=old_endpoint == null || !old_endpoint.equals(tmp_endpoint); + if(update_clients && this.send_info) { + synchronized(connections) { + for(Connection conn: connections) { + conn.writeResponse(ServerVerb.INFO, "endpoints", getAllEndpoints()); + } + } + } + } + return null; + default: + throw new IllegalArgumentException("type " + hdr.type + " is not known"); + } + break; + + case Event.VIEW_CHANGE: + handleView((View)evt.getArg()); + break; + } + + + return up_prot.up(evt); + } + + + public static Frame readFrame(DataInputStream in) throws IOException { + String verb=Util.readLine(in); + if(verb == null) + throw new EOFException("reading verb"); + if(verb.length() == 0) + return null; + verb=verb.trim(); + + Map headers=new HashMap(); + byte[] body=null; + + for(;;) { + String header=Util.readLine(in); + if(header == null) + throw new EOFException("reading header"); + if(header.length() == 0) + break; + int index=header.indexOf(":"); + if(index != -1) + headers.put(header.substring(0, index).trim(), header.substring(index+1).trim()); + } + + if(headers.containsKey("content-length")) { + int length=Integer.parseInt(headers.get("content-length")); + body=new byte[length]; + in.read(body, 0, body.length); + } + else { + ByteBuffer buf=ByteBuffer.allocate(500); + boolean terminate=false; + for(;;) { + int c=in.read(); + if(c == -1 || c == 0) + terminate=true; + + if(buf.remaining() == 0 || terminate) { + if(body == null) { + body=new byte[buf.position()]; + System.arraycopy(buf.array(), buf.arrayOffset(), body, 0, buf.position()); + } + else { + byte[] tmp=new byte[body.length + buf.position()]; + System.arraycopy(body, 0, tmp, 0, body.length); + try { + System.arraycopy(buf.array(), buf.arrayOffset(), tmp, body.length, buf.position()); + } + catch(Throwable t) { + } + body=tmp; + } + buf.rewind(); + } + + if(terminate) + break; + + buf.put((byte)c); + } + } + return new Frame(verb, headers, body); + } + + + protected void handleView(View view) { + broadcastEndpoint(); + List

      mbrs=view.getMembers(); + this.view=view; + + synchronized(endpoints) { + endpoints.keySet().retainAll(mbrs); + } + + synchronized(connections) { + for(Connection conn: connections) + conn.sendInfo(); + } + } + + private String getAddress() { + InetSocketAddress saddr=(InetSocketAddress)srv_sock.getLocalSocketAddress(); + InetAddress tmp=saddr.getAddress(); + if(!tmp.isAnyLocalAddress()) + return tmp.getHostAddress() + ":" + srv_sock.getLocalPort(); + + for(Util.AddressScope scope: Util.AddressScope.values()) { + try { + InetAddress addr=Util.getAddress(scope); + if(addr != null) return addr.getHostAddress() + ":" + srv_sock.getLocalPort(); + } + catch(SocketException e) { + } + } + return null; + } + + protected String getAllEndpoints() { + synchronized(endpoints) { + return Util.printListWithDelimiter(endpoints.values(), ","); + } + } + +// protected String getAllClients() { +// StringBuilder sb=new StringBuilder(); +// boolean first=true; +// +// synchronized(connections) { +// for(Connection conn: connections) { +// UUID session_id=conn.session_id; +// if(session_id != null) { +// if(first) +// first=false; +// else +// sb.append(","); +// sb.append(session_id); +// } +// } +// } +// +// return sb.toString(); +// } + + protected void broadcastEndpoint() { + if(endpoint != null) { + Message msg=new Message(); + msg.putHeader(id, StompHeader.createHeader(StompHeader.Type.ENDPOINT, "endpoint", endpoint)); + down_prot.down(new Event(Event.MSG, msg)); + } + } + +// private void sendToClients(String destination, String sender, byte[] buffer, int offset, int length) { +// int len=50 + length + (ServerVerb.MESSAGE.name().length() + 2) +// + (destination != null? destination.length()+ 2 : 0) +// + (sender != null? sender.length() +2 : 0) +// + (buffer != null? 20 : 0); +// +// ByteBuffer buf=ByteBuffer.allocate(len); +// +// StringBuilder sb=new StringBuilder(ServerVerb.MESSAGE.name()).append("\n"); +// if(destination != null) +// sb.append("destination: ").append(destination).append("\n"); +// if(sender != null) +// sb.append("sender: ").append(sender).append("\n"); +// if(buffer != null) +// sb.append("content-length: ").append(String.valueOf(length)).append("\n"); +// sb.append("\n"); +// +// byte[] tmp=sb.toString().getBytes(); +// +// if(buffer != null) { +// buf.put(tmp, 0, tmp.length); +// buf.put(buffer, offset, length); +// } +// buf.put(NULL_BYTE); +// +// final Set target_connections=new HashSet(); +// if(destination == null) { +// synchronized(connections) { +// target_connections.addAll(connections); +// } +// } +// else { +// if(!exact_destination_match) { +// for(Map.Entry> entry: subscriptions.entrySet()) { +// if(entry.getKey().startsWith(destination)) +// target_connections.addAll(entry.getValue()); +// } +// } +// else { +// Set conns=subscriptions.get(destination); +// if(conns != null) +// target_connections.addAll(conns); +// } +// } +// +// for(Connection conn: target_connections) +// conn.writeResponse(buf.array(), buf.arrayOffset(), buf.position()); +// } + + + private void sendToClients(Map headers, byte[] buffer, int offset, int length) { + int len=50 + length + (ServerVerb.MESSAGE.name().length() + 2); + if(headers != null) { + for(Map.Entry entry: headers.entrySet()) { + len+=entry.getKey().length() +2; + len+=entry.getValue().length() +2; + len+=5; // fill chars, such as ": " or "\n" + } + } + len+=(buffer != null? 20 : 0); + ByteBuffer buf=ByteBuffer.allocate(len); + + StringBuilder sb=new StringBuilder(ServerVerb.MESSAGE.name()).append("\n"); + if(headers != null) { + for(Map.Entry entry: headers.entrySet()) + sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); + } + + if(buffer != null) + sb.append("content-length: ").append(String.valueOf(length)).append("\n"); + sb.append("\n"); + + byte[] tmp=sb.toString().getBytes(); + + if(buffer != null) { + buf.put(tmp, 0, tmp.length); + buf.put(buffer, offset, length); + } + buf.put(NULL_BYTE); + + final Set target_connections=new HashSet(); + String destination=headers != null? headers.get("destination") : null; + if(destination == null) { + synchronized(connections) { + target_connections.addAll(connections); + } + } + else { + if(!exact_destination_match) { + for(Map.Entry> entry: subscriptions.entrySet()) { + if(entry.getKey().startsWith(destination)) + target_connections.addAll(entry.getValue()); + } + } + else { + Set conns=subscriptions.get(destination); + if(conns != null) + target_connections.addAll(conns); + } + } + + for(Connection conn: target_connections) + conn.writeResponse(buf.array(), buf.arrayOffset(), buf.position()); + } + + + /** + * Class which handles a connection to a client + */ + public class Connection implements Runnable { + protected final Socket sock; + protected final DataInputStream in; + protected final DataOutputStream out; + protected final UUID session_id=UUID.randomUUID(); + + public Connection(Socket sock) throws IOException { + this.sock=sock; + this.in=new DataInputStream(sock.getInputStream()); + this.out=new DataOutputStream(sock.getOutputStream()); + } + + public void stop() { + if(log.isTraceEnabled()) + log.trace("closing connection to " + sock.getRemoteSocketAddress()); + Util.close(in); + Util.close(out); + Util.close(sock); + } + + protected void remove() { + synchronized(connections) { + connections.remove(this); + } + for(Set conns: subscriptions.values()) { + conns.remove(this); + } + for(Iterator>> it=subscriptions.entrySet().iterator(); it.hasNext();) { + Map.Entry> entry=it.next(); + if(entry.getValue().isEmpty()) + it.remove(); + } + } + + public void run() { + while(!sock.isClosed()) { + try { + Frame frame=readFrame(in); + if(frame != null) { + if(log.isTraceEnabled()) + log.trace(frame); + handleFrame(frame); + } + } + catch(IOException ex) { + stop(); + remove(); + } + catch(Throwable t) { + log.error("failure reading frame", t); + } + } + } + + + protected void handleFrame(Frame frame) { + Map headers=frame.getHeaders(); + ClientVerb verb=ClientVerb.valueOf(frame.getVerb()); + + switch(verb) { + case CONNECT: + writeResponse(ServerVerb.CONNECTED, + "session-id", session_id.toString(), + "password-check", "none"); + break; + case SEND: + if(!headers.containsKey("sender")) { + headers.put("sender", session_id.toString()); + } + + Message msg=new Message(null, null, frame.getBody()); + Header hdr=StompHeader.createHeader(StompHeader.Type.MESSAGE, headers); + msg.putHeader(id, hdr); + down_prot.down(new Event(Event.MSG, msg)); + String receipt=headers.get("receipt"); + if(receipt != null) + writeResponse(ServerVerb.RECEIPT, "receipt-id", receipt); + break; + case SUBSCRIBE: + String destination=headers.get("destination"); + if(destination != null) { + Set conns=subscriptions.get(destination); + if(conns == null) { + conns=new HashSet(); + Set tmp=subscriptions.putIfAbsent(destination, conns); + if(tmp != null) + conns=tmp; + } + conns.add(this); + } + break; + case UNSUBSCRIBE: + destination=headers.get("destination"); + if(destination != null) { + Set conns=subscriptions.get(destination); + if(conns != null) { + if(conns.remove(this) && conns.isEmpty()) + subscriptions.remove(destination); + } + } + break; + case BEGIN: + break; + case COMMIT: + break; + case ABORT: + break; + case ACK: + break; + case DISCONNECT: + break; + default: + log.error("Verb " + frame.getVerb() + " is not handled"); + break; + } + } + + public void sendInfo() { + if(send_info) { + writeResponse(ServerVerb.INFO, + "local_addr", local_addr != null? local_addr.toString() : "n/a", + "view", view.toString(), + "endpoints", getAllEndpoints()); + // "clients", getAllClients()); + } + } + + /** + * Sends back a response. The keys_and_values vararg array needs to have an even number of elements + * @param response + * @param keys_and_values + */ + private void writeResponse(ServerVerb response, String ... keys_and_values) { + String tmp=response.name(); + try { + out.write(tmp.getBytes()); + out.write('\n'); + + for(int i=0; i < keys_and_values.length; i++) { + String key=keys_and_values[i]; + String val=keys_and_values[++i]; + out.write((key + ": " + val + "\n").getBytes()); + } + out.write("\n".getBytes()); + out.write(NULL_BYTE); + out.flush(); + } + catch(IOException ex) { + log.error("failed writing response " + response + ": " + ex); + } + } + + private void writeResponse(byte[] response, int offset, int length) { + try { + out.write(response, offset, length); + out.flush(); + } + catch(IOException ex) { + log.error("failed writing response: " + ex); + } + } + } + + + public static class Frame { + final String verb; + final Map headers; + final byte[] body; + + public Frame(String verb, Map headers, byte[] body) { + this.verb=verb; + this.headers=headers; + this.body=body; + } + + public byte[] getBody() { + return body; + } + + public Map getHeaders() { + return headers; + } + + public String getVerb() { + return verb; + } + + public String toString() { + StringBuilder sb=new StringBuilder(); + sb.append(verb).append("\n"); + if(headers != null && !headers.isEmpty()) { + for(Map.Entry entry: headers.entrySet()) + sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); + } + if(body != null && body.length > 0) { + sb.append("body: "); + if(body.length < 50) + sb.append(new String(body)).append(" (").append(body.length).append(" bytes)"); + else + sb.append(body.length).append(" bytes"); + } + return sb.toString(); + } + } + + + public static class StompHeader extends org.jgroups.Header { + public static enum Type {MESSAGE, ENDPOINT} + + protected Type type; + protected final Map headers=new HashMap(); + + + public StompHeader() { + } + + private StompHeader(Type type) { + this.type=type; + } + + /** + * Creates a new header + * @param type + * @param headers Keys and values to be added to the header hashmap. Needs to be an even number + * @return + */ + public static StompHeader createHeader(Type type, String ... headers) { + StompHeader retval=new StompHeader(type); + if(headers != null) { + for(int i=0; i < headers.length; i++) { + String key=headers[i]; + String value=headers[++i]; + retval.headers.put(key, value); + } + } + return retval; + } + + public static StompHeader createHeader(Type type, Map headers) { + StompHeader retval=new StompHeader(type); + if(headers != null) + retval.headers.putAll(headers); + return retval; + } + + + + public int size() { + int retval=Global.INT_SIZE *2; // type + size of hashmap + for(Map.Entry entry: headers.entrySet()) { + retval+=entry.getKey().length() +2; + retval+=entry.getValue().length() +2; + } + return retval; + } + + public void writeTo(DataOutputStream out) throws IOException { + out.writeInt(type.ordinal()); + out.writeInt(headers.size()); + for(Map.Entry entry: headers.entrySet()) { + out.writeUTF(entry.getKey()); + out.writeUTF(entry.getValue()); + } + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + type=Type.values()[in.readInt()]; + int size=in.readInt(); + for(int i=0; i < size; i++) { + String key=in.readUTF(); + String value=in.readUTF(); + headers.put(key, value); + } + } + + public String toString() { + StringBuilder sb=new StringBuilder(type.toString()); + sb.append("headers: ").append(headers); + return sb.toString(); + } + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/TCPGOSSIP.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/TCPGOSSIP.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/TCPGOSSIP.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/TCPGOSSIP.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,16 +1,20 @@ package org.jgroups.protocols; -import org.jgroups.Address; -import org.jgroups.Event; -import org.jgroups.Message; -import org.jgroups.annotations.Property; -import org.jgroups.stack.GossipClient; -import org.jgroups.stack.IpAddress; -import org.jgroups.util.Util; - +import java.net.InetSocketAddress; import java.util.*; -import java.net.UnknownHostException; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.jgroups.*; +import org.jgroups.annotations.DeprecatedProperty; +import org.jgroups.annotations.ManagedOperation; +import org.jgroups.annotations.Property; +import org.jgroups.conf.PropertyConverters; +import org.jgroups.stack.RouterStubManager; +import org.jgroups.stack.RouterStub; +import org.jgroups.util.Promise; +import org.jgroups.util.Tuple; +import org.jgroups.util.UUID; /** @@ -26,124 +30,222 @@ * FIND_INITIAL_MBRS_OK event up the stack. * * @author Bela Ban - * @version $Id: TCPGOSSIP.java,v 1.34 2008/12/08 13:18:58 belaban Exp $ */ +@DeprecatedProperty(names={"gossip_refresh_rate"}) public class TCPGOSSIP extends Discovery { - private final static String name="TCPGOSSIP"; - /* ----------------------------------------- Properties -------------------------------------------------- */ - - @Property(description="Rate of continious refresh registering of underlying gossip client with gossip server. Default is 20000 msec") - long gossip_refresh_rate=20000; @Property(description="Max time for socket creation. Default is 1000 msec") int sock_conn_timeout=1000; + @Property(description="Max time in milliseconds to block on a read. 0 blocks forever") int sock_read_timeout=3000; - - /* --------------------------------------------- Fields ------------------------------------------------------ */ + @Property(description="Interval (ms) by which a disconnected stub attempts to reconnect to the GossipRouter") + long reconnect_interval=10000L; - List initial_hosts=null; // (list of IpAddresses) hosts to be contacted for the initial membership - GossipClient gossip_client=null; // accesses the GossipRouter(s) to find initial mbrship + @Property(name="initial_hosts", description="Comma delimited list of hosts to be contacted for initial membership", + converter=PropertyConverters.InitialHosts2.class) + public void setInitialHosts(List hosts) { + if(hosts == null || hosts.isEmpty()) + throw new IllegalArgumentException("initial_hosts must contain the address of at least one GossipRouter"); - public String getName() { - return name; + initial_hosts.addAll(hosts) ; } - - public void init() throws Exception { - super.init(); - if(initial_hosts == null || initial_hosts.isEmpty()) { - throw new IllegalArgumentException("initial_hosts must contain the address of at least one GossipRouter"); - } - if(timeout <= sock_conn_timeout) { - throw new IllegalArgumentException("timeout (" + timeout - + ") must be greater than sock_conn_timeout (" - + sock_conn_timeout - + ")"); - } + public List getInitialHosts() { + return initial_hosts; + } + + public boolean isDynamic() { + return true; } + + /* --------------------------------------------- Fields ------------------------------------------------------ */ + - @Property - public void setInitialHosts(String hosts) throws UnknownHostException { - initial_hosts=Util.parseCommaDelimetedHosts(hosts,1); + // (list of IpAddresses) hosts to be contacted for the initial membership + private final List initial_hosts = new CopyOnWriteArrayList(); + + private volatile RouterStubManager stubManager; + + public void init() throws Exception { + super.init(); + stubManager = RouterStubManager.emptyGossipClientStubManager(this); + if(timeout <= sock_conn_timeout) + throw new IllegalArgumentException("timeout (" + timeout + ") must be greater than sock_conn_timeout (" + + sock_conn_timeout + ")"); + + // we cannot use TCPGOSSIP together with TUNNEL (https://jira.jboss.org/jira/browse/JGRP-1101) + TP transport=getTransport(); + if(transport instanceof TUNNEL) + throw new IllegalStateException("TCPGOSSIP cannot be used with TUNNEL; use either TUNNEL:PING or " + + "TCP:TCPGOSSIP as valid configurations"); } public void start() throws Exception { super.start(); - if(gossip_client == null) { - gossip_client=new GossipClient(initial_hosts, gossip_refresh_rate, sock_conn_timeout, timer); - gossip_client.setSocketReadTimeout(sock_read_timeout); - } } public void stop() { - super.stop(); - if(gossip_client != null) { - gossip_client.stop(); - gossip_client=null; - } + super.stop(); + stubManager.disconnectStubs(); } public void destroy() { - if(gossip_client != null) { - gossip_client.destroy(); - gossip_client=null; - } + stubManager.destroyStubs(); + super.destroy(); } - public void handleConnect() { - if(group_addr == null || local_addr == null) { - if(log.isErrorEnabled()) + if (group_addr == null || local_addr == null) { + if (log.isErrorEnabled()) log.error("group_addr or local_addr is null, cannot register with GossipRouter(s)"); - } - else { - if(log.isTraceEnabled()) + } else { + if (log.isTraceEnabled()) log.trace("registering " + local_addr + " under " + group_addr + " with GossipRouter"); - gossip_client.register(group_addr, local_addr); + + stubManager.destroyStubs(); + stubManager = new RouterStubManager(this, group_addr, local_addr, reconnect_interval); + for (InetSocketAddress host : initial_hosts) { + stubManager.createAndRegisterStub(host.getHostName(), host.getPort(), null); + } + connectAllStubs(group_addr, local_addr); } } + public void handleDisconnect() { - if(group_addr != null && local_addr != null) { - gossip_client.unregister(group_addr, local_addr); - } + stubManager.disconnectStubs(); } - public void sendGetMembersRequest(String cluster_name) { - Message msg, copy; - PingHeader hdr; - List
      tmp_mbrs; - Address mbr_addr; - - if(group_addr == null) { - if(log.isErrorEnabled()) log.error("[FIND_INITIAL_MBRS]: group_addr is null, cannot get mbrship"); + @SuppressWarnings("unchecked") + public void sendGetMembersRequest(String cluster_name, Promise promise, ViewId view_id) throws Exception { + if (group_addr == null) { + if (log.isErrorEnabled()) + log.error("cluster_name is null, cannot get membership"); return; } - if(log.isTraceEnabled()) log.trace("fetching members from GossipRouter(s)"); - tmp_mbrs=gossip_client.getMembers(group_addr, - (long)(timeout * .50)); // needs to be below timeout - if(tmp_mbrs == null || tmp_mbrs.isEmpty()) { - if(log.isErrorEnabled()) log.error("[FIND_INITIAL_MBRS]: gossip client found no members"); + + if (log.isTraceEnabled()) + log.trace("fetching members from GossipRouter(s)"); + + final List responses = new LinkedList(); + for (RouterStub stub : stubManager.getStubs()) { + try { + List rsps = stub.getMembers(group_addr); + responses.addAll(rsps); + } + catch(Throwable t) { + log.warn("failed fetching members from " + stub.getGossipRouterAddress() + ": " + t); + } + } + + final Set
      initial_mbrs = new HashSet
      (); + for (PingData rsp : responses) { + Address logical_addr = rsp.getAddress(); + initial_mbrs.add(logical_addr); + + // 1. Set physical addresses + Collection physical_addrs = rsp.getPhysicalAddrs(); + if (physical_addrs != null) { + for (PhysicalAddress physical_addr : physical_addrs) + down(new Event(Event.SET_PHYSICAL_ADDRESS, new Tuple( + logical_addr, physical_addr))); + } + + // 2. Set logical name + String logical_name = rsp.getLogicalName(); + if (logical_name != null && logical_addr instanceof org.jgroups.util.UUID) + org.jgroups.util.UUID.add(logical_addr, logical_name); + } + + if (initial_mbrs.isEmpty()) { + if (log.isTraceEnabled()) + log.trace("[FIND_INITIAL_MBRS]: found no members"); return; } - if(log.isTraceEnabled()) log.trace("consolidated mbrs from GossipRouter(s) are " + tmp_mbrs); + if (log.isTraceEnabled()) + log.trace("consolidated mbrs from GossipRouter(s) are " + initial_mbrs); + + PhysicalAddress physical_addr=(PhysicalAddress)down_prot.down(new Event(Event.GET_PHYSICAL_ADDRESS, local_addr)); + PingData data=new PingData(local_addr, null, false, UUID.get(local_addr), Arrays.asList(physical_addr)); - // 1. 'Mcast' GET_MBRS_REQ message - hdr=new PingHeader(PingHeader.GET_MBRS_REQ, cluster_name); - msg=new Message(null); - msg.setFlag(Message.OOB); - msg.putHeader(name, hdr); - - for(Iterator
      it=tmp_mbrs.iterator(); it.hasNext();) { - mbr_addr=it.next(); - copy=msg.copy(); - copy.setDest(mbr_addr); - if(log.isTraceEnabled()) log.trace("[FIND_INITIAL_MBRS] sending PING request to " + copy.getDest()); - down_prot.down(new Event(Event.MSG, copy)); + for (Address mbr_addr : initial_mbrs) { + Message msg = new Message(mbr_addr); + msg.setFlag(Message.OOB); + PingHeader hdr = new PingHeader(PingHeader.GET_MBRS_REQ, data, cluster_name); + hdr.view_id=view_id; + msg.putHeader(this.id, hdr); + if (log.isTraceEnabled()) + log.trace("[FIND_INITIAL_MBRS] sending GET_MBRS_REQ request to " + mbr_addr); + down_prot.down(new Event(Event.MSG, msg)); + } + } + + @ManagedOperation + public void addInitialHost(String hostname, int port) { + //if there is such a stub already, remove and destroy it + removeInitialHost(hostname, port); + + //now re-add it + InetSocketAddress isa = new InetSocketAddress(hostname, port); + initial_hosts.add(isa); + RouterStub s = new RouterStub(isa.getHostName(), isa.getPort(),null,stubManager); + connect(s, group_addr, local_addr); + stubManager.registerStub(s); + } + + @ManagedOperation + public boolean removeInitialHost(String hostname, int port) { + InetSocketAddress isa = new InetSocketAddress(hostname, port); + RouterStub unregisterStub = stubManager.unregisterStub(isa); + if(unregisterStub != null) { + stubManager.stopReconnecting(unregisterStub); + unregisterStub.destroy(); + } + return initial_hosts.remove(isa); + } + + + protected void connectAllStubs(String group, Address logical_addr) { + String logical_name=org.jgroups.util.UUID.get(logical_addr); + PhysicalAddress physical_addr=(PhysicalAddress)down_prot.down(new Event(Event.GET_PHYSICAL_ADDRESS, local_addr)); + List physical_addrs=physical_addr != null? new ArrayList() : null; + if(physical_addr != null) + physical_addrs.add(physical_addr); + + + for (RouterStub stub : stubManager.getStubs()) { + try { + if(log.isTraceEnabled()) + log.trace("trying to connect to " + stub.getGossipRouterAddress()); + stub.connect(group, logical_addr, logical_name, physical_addrs); + } + catch(Exception e) { + if(log.isErrorEnabled()) + log.error("failed connecting to " + stub.getGossipRouterAddress() + ": " + e); + stubManager.startReconnecting(stub); + } + } + } + + protected void connect(RouterStub stub, String group, Address logical_addr) { + String logical_name = org.jgroups.util.UUID.get(logical_addr); + PhysicalAddress physical_addr = (PhysicalAddress) down_prot.down(new Event(Event.GET_PHYSICAL_ADDRESS, local_addr)); + List physical_addrs = physical_addr != null ? new ArrayList(): null; + if (physical_addr != null) + physical_addrs.add(physical_addr); + + try { + if (log.isTraceEnabled()) + log.trace("trying to connect to " + stub.getGossipRouterAddress()); + stub.connect(group, logical_addr, logical_name, physical_addrs); + } catch (Exception e) { + if (log.isErrorEnabled()) + log.error("failed connecting to " + stub.getGossipRouterAddress() + ": " + e); + stubManager.startReconnecting(stub); } } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/TCP.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/TCP.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/TCP.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/TCP.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,16 +1,16 @@ -// $Id: TCP.java,v 1.57 2008/10/29 13:07:38 vlada Exp $ package org.jgroups.protocols; -import java.net.InetAddress; -import java.util.Collection; - import org.jgroups.Address; +import org.jgroups.PhysicalAddress; +import org.jgroups.Global; import org.jgroups.annotations.ManagedAttribute; import org.jgroups.annotations.ManagedOperation; import org.jgroups.blocks.TCPConnectionMap; -import org.jgroups.stack.IpAddress; -import org.jgroups.util.PortsManager; +import org.jgroups.util.SocketFactory; + +import java.net.InetAddress; +import java.util.Collection; /** * TCP based protocol. Creates a server socket, which gives us the local address @@ -23,7 +23,7 @@ * as well. *

      * - * This functionality is in ConnectionTable, which is used by TCP. TCP sends + * This functionality is in TCPConnectionMap, which is used by TCP. TCP sends * messages using ct.send() and registers with the connection table to receive * all incoming messages. * @@ -35,9 +35,6 @@ public TCP() {} - public String getName() { - return "TCP"; - } @ManagedAttribute public int getOpenConnections() { @@ -46,7 +43,13 @@ @ManagedOperation public String printConnections() { - return ct.toString(); + return ct.printConnections(); + } + + public void setSocketFactory(SocketFactory factory) { + super.setSocketFactory(factory); + if(ct != null) + ct.setSocketFactory(factory); } public void send(Address dest, byte[] data, int offset, int length) throws Exception { @@ -58,13 +61,13 @@ } public void start() throws Exception { - ct=getConnectionTable(reaper_interval, + ct=createConnectionMap(reaper_interval, conn_expire_time, bind_addr, external_addr, bind_port, - bind_port+port_range, - pm); + bind_port+port_range + ); ct.setReceiveBufferSize(recv_buf_size); ct.setSendQueueSize(send_queue_size); ct.setUseSendQueues(use_send_queues); @@ -72,12 +75,9 @@ ct.setSocketConnectionTimeout(sock_conn_timeout); ct.setTcpNodelay(tcp_nodelay); ct.setLinger(linger); - local_addr=ct.getLocalAddress(); - if(additional_data != null && local_addr instanceof IpAddress) - ((IpAddress)local_addr).setAdditionalData(additional_data); + ct.setSocketFactory(getSocketFactory()); - //http://jira.jboss.com/jira/browse/JGRP-626 - //we first start threads in TP + // we first start threads in TP (http://jira.jboss.com/jira/browse/JGRP-626) super.start(); } @@ -116,25 +116,26 @@ * @param bindAddress * @param startPort * @throws Exception - * @return ConnectionTable Sub classes overrides this method to initialize a - * different version of ConnectionTable. + * @return TCPConnectionMap Subclasses override this method to initialize a different version of ConnectionMap */ - protected TCPConnectionMap getConnectionTable(long reaperInterval, - long connExpireTime, - InetAddress bindAddress, - InetAddress externalAddress, - int startPort, - int endPort, - PortsManager pm) throws Exception { + protected TCPConnectionMap createConnectionMap(long reaperInterval, + long connExpireTime, + InetAddress bindAddress, + InetAddress externalAddress, + int startPort, + int endPort + ) throws Exception { TCPConnectionMap cTable; if(reaperInterval == 0 && connExpireTime == 0) { - cTable=new TCPConnectionMap(getThreadFactory(), - this, - bindAddress, - externalAddress, - startPort, - endPort, - pm); + cTable=new TCPConnectionMap(Global.TCP_SRV_SOCK, + getThreadFactory(), + getSocketFactory(), + this, + bindAddress, + externalAddress, + startPort, + endPort + ); } else { if(reaperInterval == 0) { @@ -147,17 +148,23 @@ if(log.isWarnEnabled()) log.warn("conn_expire_time was 0, set it to " + connExpireTime); } - cTable=new TCPConnectionMap(getThreadFactory(), - this, - bindAddress, - externalAddress, - startPort, - endPort, - reaperInterval, - connExpireTime, - pm); + cTable=new TCPConnectionMap(Global.TCP_SRV_SOCK, + getThreadFactory(), + getSocketFactory(), + this, + bindAddress, + externalAddress, + startPort, + endPort, + reaperInterval, + connExpireTime + ); } return cTable; } + + protected PhysicalAddress getPhysicalAddress() { + return ct != null? (PhysicalAddress)ct.getLocalAddress() : null; + } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/TCP_NIO.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/TCP_NIO.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/TCP_NIO.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/TCP_NIO.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,13 +1,13 @@ package org.jgroups.protocols; import org.jgroups.Address; +import org.jgroups.PhysicalAddress; +import org.jgroups.annotations.Experimental; import org.jgroups.annotations.ManagedAttribute; import org.jgroups.annotations.Property; -import org.jgroups.annotations.Experimental; +import org.jgroups.annotations.Unsupported; import org.jgroups.blocks.BasicConnectionTable; import org.jgroups.blocks.ConnectionTableNIO; -import org.jgroups.stack.IpAddress; -import org.jgroups.util.PortsManager; import java.net.InetAddress; import java.util.Collection; @@ -17,23 +17,22 @@ * @author Scott Marlow * @author Alex Fu * @author Bela Ban - * @version $Id: TCP_NIO.java,v 1.25 2008/10/21 15:58:04 belaban Exp $ */ -@Experimental +@Experimental @Unsupported public class TCP_NIO extends BasicTCP implements BasicConnectionTable.Receiver { - /* - * (non-Javadoc) - * - * @see org.jgroups.protocols.TCP#getConnectionTable(long, long) - */ + /* + * (non-Javadoc) + * + * @see org.jgroups.protocols.TCP#getConnectionTable(long, long) + */ protected ConnectionTableNIO getConnectionTable(long ri, long cet, InetAddress b_addr, InetAddress bc_addr, - int s_port, int e_port, PortsManager pm) throws Exception { + int s_port, int e_port) throws Exception { ConnectionTableNIO retval=null; if (ri == 0 && cet == 0) { - retval = new ConnectionTableNIO(this, b_addr, bc_addr, s_port, e_port, pm, false ); + retval = new ConnectionTableNIO(this, b_addr, bc_addr, s_port, e_port, false ); } else { if (ri == 0) { @@ -44,7 +43,7 @@ cet = 1000 * 60 * 5; if(log.isWarnEnabled()) log.warn("conn_expire_time was 0, set it to " + cet); } - retval = new ConnectionTableNIO(this, b_addr, bc_addr, s_port, e_port, pm, ri, cet, false); + retval = new ConnectionTableNIO(this, b_addr, bc_addr, s_port, e_port, ri, cet, false); } retval.setThreadFactory(getThreadFactory()); retval.setProcessorMaxThreads(getProcessorMaxThreads()); @@ -58,12 +57,16 @@ public String printConnections() {return ct.toString();} + protected PhysicalAddress getPhysicalAddress() { + return ct != null? (PhysicalAddress)ct.getLocalAddress() : null; + } + public void send(Address dest, byte[] data, int offset, int length) throws Exception { ct.send(dest, data, offset, length); } public void start() throws Exception { - ct=getConnectionTable(reaper_interval,conn_expire_time,bind_addr,external_addr,bind_port,bind_port+port_range,pm); + ct=getConnectionTable(reaper_interval,conn_expire_time,bind_addr,external_addr,bind_port,bind_port+port_range); ct.setUseSendQueues(use_send_queues); // ct.addConnectionListener(this); ct.setReceiveBufferSize(recv_buf_size); @@ -72,9 +75,6 @@ ct.setPeerAddressReadTimeout(peer_addr_read_timeout); ct.setTcpNodelay(tcp_nodelay); ct.setLinger(linger); - local_addr=ct.getLocalAddress(); - if(additional_data != null && local_addr instanceof IpAddress) - ((IpAddress)local_addr).setAdditionalData(additional_data); super.start(); } @@ -87,45 +87,34 @@ super.stop(); } - public String getName() { - return "TCP_NIO"; - } - - @ManagedAttribute - public int getReaderThreads() { return m_reader_threads; } - @ManagedAttribute - public int getWriterThreads() { return m_writer_threads; } - @ManagedAttribute - public int getProcessorThreads() { return m_processor_threads; } - @ManagedAttribute - public int getProcessorMinThreads() { return m_processor_minThreads;} - @ManagedAttribute - public int getProcessorMaxThreads() { return m_processor_maxThreads;} - @ManagedAttribute - public int getProcessorQueueSize() { return m_processor_queueSize; } - @ManagedAttribute - public long getProcessorKeepAliveTime() { return m_processor_keepAliveTime; } + public int getReaderThreads() { return reader_threads; } + public int getWriterThreads() { return writer_threads; } + public int getProcessorThreads() { return processor_threads; } + public int getProcessorMinThreads() { return processor_minThreads;} + public int getProcessorMaxThreads() { return processor_maxThreads;} + public int getProcessorQueueSize() { return processor_queueSize; } + public long getProcessorKeepAliveTime() { return processor_keepAliveTime; } @ManagedAttribute public int getOpenConnections() {return ct.getNumConnections();} @Property - private int m_reader_threads = 3; + private int reader_threads= 3; @Property - private int m_writer_threads = 3; + private int writer_threads= 3; @Property - private int m_processor_threads = 5; // PooledExecutor.createThreads() + private int processor_threads= 5; // PooledExecutor.createThreads() @Property - private int m_processor_minThreads = 5; // PooledExecutor.setMinimumPoolSize() + private int processor_minThreads= 5; // PooledExecutor.setMinimumPoolSize() @Property - private int m_processor_maxThreads = 5; // PooledExecutor.setMaxThreads() + private int processor_maxThreads= 5; // PooledExecutor.setMaxThreads() @Property - private int m_processor_queueSize=100; // Number of queued requests that can be pending waiting + private int processor_queueSize=100; // Number of queued requests that can be pending waiting // for a background thread to run the request. @Property - private long m_processor_keepAliveTime = Long.MAX_VALUE; // PooledExecutor.setKeepAliveTime( milliseconds); + private long processor_keepAliveTime= Long.MAX_VALUE; // PooledExecutor.setKeepAliveTime( milliseconds); // negative value used to mean (before 2.5 release) to wait forever, // instead set to Long.MAX_VALUE to keep alive forever private ConnectionTableNIO ct; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/TCPPING.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/TCPPING.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/TCPPING.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/TCPPING.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,20 +2,23 @@ package org.jgroups.protocols; -import org.jgroups.Address; -import org.jgroups.Event; -import org.jgroups.Message; +import org.jgroups.*; +import org.jgroups.annotations.ManagedAttribute; +import org.jgroups.annotations.ManagedOperation; import org.jgroups.annotations.Property; +import org.jgroups.conf.PropertyConverters; import org.jgroups.stack.IpAddress; -import org.jgroups.util.Util; +import org.jgroups.util.BoundedList; +import org.jgroups.util.Promise; +import org.jgroups.util.UUID; -import java.util.List; +import java.util.*; /** * The TCPPING protocol layer retrieves the initial membership in answer to the * GMS's FIND_INITIAL_MBRS event. The initial membership is retrieved by - * directly contacting other group members, sending point-to-point mebership + * directly contacting other group members, sending point-to-point membership * requests. The responses should allow us to determine the coordinator whom we * have to contact in case we want to join the group. When we are a server * (after having received the BECOME_SERVER event), we'll respond to TCPPING @@ -24,39 +27,45 @@ * The FIND_INITIAL_MBRS event will eventually be answered with a * FIND_INITIAL_MBRS_OK event up the stack. *

      - * The TCPPING protocol requires a static conifiguration, which assumes that you + * The TCPPING protocol requires a static configuration, which assumes that you * to know in advance where to find other members of your group. For dynamic * discovery, use the PING protocol, which uses multicast discovery, or the * TCPGOSSIP protocol, which contacts a Gossip Router to acquire the initial * membership. * * @author Bela Ban - * @version $Id: TCPPING.java,v 1.41 2008/10/21 12:47:04 vlada Exp $ */ public class TCPPING extends Discovery { - private final static String NAME="TCPPING"; - /* ----------------------------------------- Properties --------------------------------------- */ @Property(description="Number of ports to be probed for initial membership. Default is 1") private int port_range=1; - @Property(name="initial_hosts", description="Comma delimeted list of hosts to be contacted for initial membership") - private String hosts; - - + @Property(name="initial_hosts", description="Comma delimited list of hosts to be contacted for initial membership", + converter=PropertyConverters.InitialHosts.class, dependsUpon="port_range", + systemProperty=Global.TCPPING_INITIAL_HOSTS) + private List initial_hosts=null; + + @Property(description="max number of hosts to keep beyond the ones in initial_hosts") + protected int max_dynamic_hosts=100; /* --------------------------------------------- Fields ------------------------------------------------------ */ - - private List initial_hosts; + /** + * List of PhysicalAddresses + */ + + /** https://jira.jboss.org/jira/browse/JGRP-989 */ + protected final BoundedList dynamic_hosts=new BoundedList(max_dynamic_hosts); - public TCPPING() { + + public TCPPING() { + return_entire_cache=true; } - public String getName() { - return NAME; + public boolean isDynamic() { + return false; } /** @@ -69,8 +78,8 @@ return initial_hosts; } - public void setInitialHosts(String hosts) { - this.hosts = hosts; + public void setInitialHosts(List initial_hosts) { + this.initial_hosts=initial_hosts; } public int getPortRange() { @@ -80,34 +89,86 @@ public void setPortRange(int port_range) { this.port_range=port_range; } + + @ManagedAttribute + public String getDynamicHostList() { + return dynamic_hosts.toString(); + } + + @ManagedOperation + public void clearDynamicHostList() { + dynamic_hosts.clear(); + } + + @ManagedAttribute + public String getInitialHostsList() { + return initial_hosts.toString(); + } + public void init() throws Exception { + super.init(); + } + public void start() throws Exception { super.start(); - initial_hosts = Util.parseCommaDelimetedHosts(hosts, port_range); } - public void sendGetMembersRequest(String cluster_name) { - for(final Address addr: initial_hosts) { - if(addr.equals(local_addr)) + + public void sendGetMembersRequest(String cluster_name, Promise promise, ViewId view_id) throws Exception{ + PhysicalAddress physical_addr=(PhysicalAddress)down_prot.down(new Event(Event.GET_PHYSICAL_ADDRESS, local_addr)); + PingData data=new PingData(local_addr, null, false, UUID.get(local_addr), Arrays.asList(physical_addr)); + PingHeader hdr=new PingHeader(PingHeader.GET_MBRS_REQ, data, cluster_name); + hdr.view_id=view_id; + + Set combined_target_members=new HashSet(initial_hosts); + combined_target_members.addAll(dynamic_hosts); + + for(final Address addr: combined_target_members) { + if(addr.equals(physical_addr)) continue; - final Message msg = new Message(addr, null, null); + final Message msg=new Message(addr, null, null); msg.setFlag(Message.OOB); - msg.putHeader(NAME, new PingHeader(PingHeader.GET_MBRS_REQ, cluster_name)); - + msg.putHeader(this.id, hdr); if(log.isTraceEnabled()) log.trace("[FIND_INITIAL_MBRS] sending PING request to " + msg.getDest()); - timer.submit(new Runnable() { + timer.execute(new Runnable() { public void run() { try { down_prot.down(new Event(Event.MSG, msg)); } catch(Exception ex){ if(log.isErrorEnabled()) - log.error("failed sending discovery request to " + addr, ex); + log.error("failed sending discovery request to " + addr + ": " + ex); } } }); } } + + public Object down(Event evt) { + Object retval=super.down(evt); + switch(evt.getType()) { + case Event.VIEW_CHANGE: + for(Address logical_addr: members) { + PhysicalAddress physical_addr=(PhysicalAddress)down_prot.down(new Event(Event.GET_PHYSICAL_ADDRESS, logical_addr)); + if(physical_addr != null && !initial_hosts.contains(physical_addr)) { + dynamic_hosts.addIfAbsent(physical_addr); + } + } + return_entire_cache=true; + break; + } + return retval; + } + + public void discoveryRequestReceived(Address sender, String logical_name, Collection physical_addrs) { + super.discoveryRequestReceived(sender, logical_name, physical_addrs); + if(physical_addrs != null) { + for(PhysicalAddress addr: physical_addrs) { + if(!initial_hosts.contains(addr)) + dynamic_hosts.addIfAbsent(addr); + } + } + } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/TpHeader.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/TpHeader.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/TpHeader.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/TpHeader.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,7 +2,6 @@ import org.jgroups.Header; -import org.jgroups.util.Streamable; import java.io.*; @@ -11,9 +10,8 @@ /** * Generic transport header, used by TP. * @author Bela Ban - * @version $Id: TpHeader.java,v 1.4 2007/05/01 10:55:10 belaban Exp $ */ -public class TpHeader extends Header implements Streamable { +public class TpHeader extends Header { public String channel_name=null; int size=0; @@ -30,21 +28,10 @@ return "[channel_name=" + channel_name + ']'; } - public int size() { return size; } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeUTF(channel_name); - } - - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - channel_name=in.readUTF(); - } - - public void writeTo(DataOutputStream out) throws IOException { out.writeUTF(channel_name); } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/TP.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/TP.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/TP.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/TP.java 2011-10-18 11:22:35.000000000 +0000 @@ -3,20 +3,23 @@ import org.jgroups.*; import org.jgroups.annotations.*; +import org.jgroups.blocks.LazyRemovalCache; import org.jgroups.conf.PropertyConverters; -import org.jgroups.stack.IpAddress; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.stack.Protocol; import org.jgroups.util.*; import org.jgroups.util.ThreadFactory; +import org.jgroups.util.UUID; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.io.InterruptedIOException; import java.net.*; import java.text.NumberFormat; import java.util.*; import java.util.concurrent.*; -import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -32,8 +35,8 @@ * * A subclass has to override *

        - *
      • {@link #sendToAllMembers(byte[], int, int)} - *
      • {@link #sendToSingleMember(org.jgroups.Address, byte[], int, int)} + *
      • {@link #sendMulticast(byte[], int, int)} + *
      • {@link #sendUnicast(org.jgroups.PhysicalAddress, byte[], int, int)} *
      • {@link #init()} *
      • {@link #start()}: subclasses must call super.start() after they initialize themselves * (e.g., created their sockets). @@ -41,49 +44,48 @@ *
      • {@link #destroy()} *
      * The create() or start() method has to create a local address.
      - * The {@link #receive(Address, Address, byte[], int, int)} method must + * The {@link #receive(Address, byte[], int, int)} method must * be called by subclasses when a unicast or multicast message has been received. * @author Bela Ban - * @version $Id: TP.java,v 1.239 2008/12/12 08:10:22 belaban Exp $ */ @MBean(description="Transport protocol") @DeprecatedProperty(names={"bind_to_all_interfaces", "use_incoming_packet_handler", "use_outgoing_packet_handler", - "use_concurrent_stack"}) + "use_concurrent_stack", "prevent_port_reuse", "persistent_ports", "pm_expiry_time", "persistent_ports_file", + "start_port", "end_port", "use_local_host", "marshaller_pool_size", "num_timer_threads", "timer.num_threads"}) public abstract class TP extends Protocol { - - private static final byte LIST=1; // we have a list of messages rather than a single message when set - private static final byte MULTICAST=2; // message is a multicast (versus a unicast) message when set - private static final byte OOB=4; // message has OOB flag set (Message.OOB) - private static NumberFormat f; - private static final int INITIAL_BUFSIZE=4095; + protected static final byte LIST=1; // we have a list of messages rather than a single message when set + protected static final byte MULTICAST=2; // message is a multicast (versus a unicast) message when set + protected static final byte OOB=4; // message has OOB flag set (Message.OOB) + + protected static final boolean can_bind_to_mcast_addr; // are we running on Linux ? + + protected static NumberFormat f; static { + can_bind_to_mcast_addr=Util.checkForLinux() || Util.checkForSolaris() || Util.checkForHp(); f=NumberFormat.getNumberInstance(); f.setGroupingUsed(false); f.setMaximumFractionDigits(2); } - private ExposedByteArrayOutputStream out_stream=null; - private ExposedDataOutputStream dos=null; - private final Lock out_stream_lock=new ReentrantLock(); - - - /* ------------------------------------------ JMX and Properties ------------------------------------------ */ - - - - @ManagedAttribute - @Property(converter=PropertyConverters.BindAddress.class, - description="The interface (NIC) which should be used by this transport ") - protected InetAddress bind_addr=null; - @Property(description="Ignores all bind address parameters and let's the OS return the local host address. Default is false") - protected boolean use_local_host=false; - @ManagedAttribute - @Property(description=" If true, the transport should use all available interfaces to receive multicast messages. Default is false") + @LocalAddress + @Property(name="bind_addr", + description="The bind address which should be used by this transport. The following special values " + + "are also recognized: GLOBAL, SITE_LOCAL, LINK_LOCAL and NON_LOOPBACK", + defaultValueIPv4=Global.NON_LOOPBACK_ADDRESS, defaultValueIPv6=Global.NON_LOOPBACK_ADDRESS, + systemProperty={Global.BIND_ADDR, Global.BIND_ADDR_OLD},writable=false) + protected InetAddress bind_addr=null; + + @Property(name="bind_interface", converter=PropertyConverters.BindInterface.class, + description="The interface (NIC) which should be used by this transport", dependsUpon="bind_addr", + exposeAsManagedAttribute=false) + protected String bind_interface_str=null; + + @Property(description="If true, the transport should use all available interfaces to receive multicast messages") protected boolean receive_on_all_interfaces=false; /** @@ -92,38 +94,37 @@ * "192.168.5.1,eth1,127.0.0.1". Duplicates are discarded; we only bind to * an interface once. If this property is set, it overrides receive_on_all_interfaces. */ - @ManagedAttribute - @Property(converter=PropertyConverters.NetworkInterfaceList.class, - description="Comma delimited list of interfaces (IP addresses or interface names) to receive multicasts on") + @Property(converter=PropertyConverters.NetworkInterfaceList.class, + description="Comma delimited list of interfaces (IP addresses or interface names) to receive multicasts on") protected List receive_interfaces=null; + @Property(description="Max number of elements in the logical address cache before eviction starts") + protected int logical_addr_cache_max_size=200; + + @Property(description="Time (in ms) after which entries in the logical address cache marked as removable are removed") + protected long logical_addr_cache_expiration=120000; /** The port to which the transport binds. 0 means to bind to any (ephemeral) port */ - @Property(name="start_port", deprecatedMessage="start_port is deprecated; use bind_port instead", - description="The port to which the transport binds. Default of 0 binds to any (ephemeral) port") + @Property(description="The port to which the transport binds. Default of 0 binds to any (ephemeral) port",writable=false) protected int bind_port=0; - - @Property(name="end_port", deprecatedMessage="end_port is deprecated; use port_range instead") - protected int port_range=1; // 27-6-2003 bgooren, Only try one port by default - @Property(description="TODO") - protected boolean prevent_port_reuse=false; + @Property(description="The range of valid ports, from bind_port to end_port. Infinite if 0") + protected int port_range=50; // 27-6-2003 bgooren, Only try one port by default + /** * If true, messages sent to self are treated specially: unicast messages are looped back immediately, * multicast messages get a local copy first and - when the real copy arrives - it will be discarded. Useful for * Window media (non)sense */ - @ManagedAttribute(description="", writable=true) - @Property(description="Messages to self are looped back immediately if true. Default is false") - protected boolean loopback=false; + @Property(description="Messages to self are looped back immediately if true") + protected boolean loopback=true; /** * Discard packets with a different version. Usually minor version differences are okay. Setting this property * to true means that we expect the exact same version on all incoming packets */ - @ManagedAttribute(description="Discard packets with a different version", writable=true) @Property(description="Discard packets with a different version if true. Default is false") protected boolean discard_incompatible_packets=false; @@ -131,74 +132,69 @@ @Property(description="Thread naming pattern for threads in this channel. Default is cl") protected String thread_naming_pattern="cl"; - @Property(name="oob_thread_pool.enabled",description="Switch for enabling thread pool for OOB messages. Default true") + @Property(name="oob_thread_pool.enabled",description="Switch for enabling thread pool for OOB messages. " + + "Default=true",writable=false) protected boolean oob_thread_pool_enabled=true; - @ManagedAttribute(description="Minimum thread pool size for OOB messages. Default is 2") - @Property(name="oob_thread_pool.min_threads",description="Minimum thread pool size for OOB messages. Default is 2") protected int oob_thread_pool_min_threads=2; - - @ManagedAttribute(description="Maximum thread pool size for OOB messages. Default is 10") - @Property(name="oob_thread_pool.max_threads",description="Maximum thread pool size for OOB messages. Default is 10") + protected int oob_thread_pool_max_threads=10; - - @ManagedAttribute(description="Timeout in milliseconds to remove idle thread from OOB pool. Default is 30000") - @Property(name="oob_thread_pool.keep_alive_time",description="Timeout in milliseconds to remove idle thread from OOB pool. Default is 30000") + protected long oob_thread_pool_keep_alive_time=30000; - @ManagedAttribute(description="Use queue to enqueue incoming OOB messages. Default is true") - @Property(name="oob_thread_pool.queue_enabled", - description="Use queue to enqueue incoming OOB messages. Default is true") + @Property(name="oob_thread_pool.queue_enabled", description="Use queue to enqueue incoming OOB messages") protected boolean oob_thread_pool_queue_enabled=true; - - - @ManagedAttribute(description="Maximum queue size for incoming OOB messages. Default is 500") + @Property(name="oob_thread_pool.queue_max_size",description="Maximum queue size for incoming OOB messages. Default is 500") protected int oob_thread_pool_queue_max_size=500; - - @ManagedAttribute + @Property(name="oob_thread_pool.rejection_policy", - description="Thread rejection policy. Possible values are Abort, Discard, DiscardOldest and Run. Default is Run") - String oob_thread_pool_rejection_policy="Run"; + description="Thread rejection policy. Possible values are Abort, Discard, DiscardOldest and Run. Default is Discard") + String oob_thread_pool_rejection_policy="discard"; - @ManagedAttribute(description="Minimum thread pool size for regular messages. Default is 2") - @Property(name="thread_pool.min_threads",description="Minimum thread pool size for regular messages. Default is 2") protected int thread_pool_min_threads=2; - @ManagedAttribute(description="Maximum thread pool size for regular messages. Default is 10") - @Property(name="thread_pool.max_threads",description="Maximum thread pool size for regular messages. Default is 10") protected int thread_pool_max_threads=10; - - - @ManagedAttribute(description="Timeout in milliseconds to remove idle thread from regular pool. Default is 30000") - @Property(name="thread_pool.keep_alive_time",description="Timeout in milliseconds to remove idle thread from regular pool. Default is 30000") + protected long thread_pool_keep_alive_time=30000; - @ManagedAttribute(description="Switch for enabling thread pool for regular messages. Default true") @Property(name="thread_pool.enabled",description="Switch for enabling thread pool for regular messages. Default true") protected boolean thread_pool_enabled=true; - - @ManagedAttribute(description="Use queue to enqueue incoming regular messages") - @Property(name="thread_pool.queue_enabled", - description="Use queue to enqueue incoming regular messages. Default is true") + + @Property(name="thread_pool.queue_enabled", description="Use queue to enqueue incoming regular messages. Default is true") protected boolean thread_pool_queue_enabled=true; - - @ManagedAttribute(description="Maximum queue size for incoming OOB messages") - @Property(name="thread_pool.queue_max_size", - description="Maximum queue size for incoming OOB messages. Default is 500") + + @Property(name="thread_pool.queue_max_size", description="Maximum queue size for incoming OOB messages. Default is 500") protected int thread_pool_queue_max_size=500; - @ManagedAttribute @Property(name="thread_pool.rejection_policy", - description="Thread rejection policy. Possible values are Abort, Discard, DiscardOldest and Run Default is Run") - protected String thread_pool_rejection_policy="Run"; + description="Thread rejection policy. Possible values are Abort, Discard, DiscardOldest and Run. Default is Discard") + protected String thread_pool_rejection_policy="Discard"; + + @Property(description="Type of timer to be used. Valid values are \"old\" (DefaultTimeScheduler, used up to 2.10), " + + "\"new\" (TimeScheduler2) and \"wheel\". Note that this property might disappear " + + "in future releases, if one of the 3 timers is chosen as default timer") + protected String timer_type="new"; + + protected int timer_min_threads=4; + + protected int timer_max_threads=10; + + protected long timer_keep_alive_time=5000; + + @Property(name="timer.queue_max_size", description="Max number of elements on a timer queue") + protected int timer_queue_max_size=500; + + // hashed timing wheel specific props + @Property(name="timer.wheel_size", + description="Number of ticks in the HashedTimingWheel timer. Only applicable if timer_type is \"wheel\"") + protected int wheel_size=200; + + @Property(name="timer.tick_time", + description="Tick duration in the HashedTimingWheel timer. Only applicable if timer_type is \"wheel\"") + protected long tick_time=50L; - @ManagedAttribute(description="Number of threads to be used by the timer thread pool") - @Property(name="timer.num_threads",description="Number of threads to be used by the timer thread pool. Default is 4") - protected int num_timer_threads=4; - - @ManagedAttribute(description="Enable bundling of smaller messages into bigger ones", writable=true) @Property(description="Enable bundling of smaller messages into bigger ones. Default is true") protected boolean enable_bundling=true; @@ -208,26 +204,24 @@ @Property(description="Switch to enable diagnostic probing. Default is true") protected boolean enable_diagnostics=true; - - @Property(description="Address for diagnostic probing. Default is 224.0.75.75") - protected String diagnostics_addr="224.0.75.75"; - + + @Property(description="Address for diagnostic probing. Default is 224.0.75.75", + defaultValueIPv4="224.0.75.75",defaultValueIPv6="ff0e::0:75:75") + protected InetAddress diagnostics_addr=null; + @Property(description="Port for diagnostic probing. Default is 7500") protected int diagnostics_port=7500; - + @Property(description="If assigned enable this transport to be a singleton (shared) transport") protected String singleton_name=null; - @Property(description="Path to a file to store currently used ports on this machine.") - protected String persistent_ports_file=null; - - @Property(name="ports_expiry_time",description="Timeout to expire ports used with PortManager. Default is 30000 msec") - protected long pm_expiry_time=30000L; - - @Property(description="Switch to enable tracking of currently used ports on this machine. Default is false") - protected boolean persistent_ports=false; - - + /** Whether or not warnings about messages from different groups are logged - private flag, not for common use */ + @Property(description="whether or not warnings about messages from different groups are logged") + protected boolean log_discard_msgs=true; + + + + /** * Maximum number of bytes for messages to be queued until they are sent. * This value needs to be smaller than the largest datagram packet size in case of UDP @@ -239,129 +233,268 @@ * or max_bundle_timeout has been exceeded (whichever occurs faster) */ protected long max_bundle_timeout=20; - - - - /* --------------------------------------------- JMX ---------------------------------------------- */ + @Property(description="The type of bundler used. Has to be \"old\" (default) or \"new\"") + protected String bundler_type="new"; + @Experimental + @Property(description="The max number of elements in a bundler if the bundler supports size limitations") + protected int bundler_capacity=20000; + + + @Property(name="max_bundle_size", description="Maximum number of bytes for messages to be queued until they are sent") + public void setMaxBundleSize(int size) { + if(size <= 0) + throw new IllegalArgumentException("max_bundle_size (" + size + ") is <= 0"); + max_bundle_size=size; + } + + public long getMaxBundleTimeout() {return max_bundle_timeout;} - - + + @Property(name="max_bundle_timeout", description="Max number of milliseconds until queued messages are sent") + public void setMaxBundleTimeout(long timeout) { + if(timeout <= 0) { + throw new IllegalArgumentException("max_bundle_timeout of " + timeout + " is invalid"); + } + max_bundle_timeout=timeout; + } + + public int getMaxBundleSize() {return max_bundle_size;} + + @ManagedAttribute public int getBundlerBufferSize() { + if(bundler instanceof TransferQueueBundler) + return ((TransferQueueBundler)bundler).getBufferSize(); + return 0; + } + + @Property(name="oob_thread_pool.keep_alive_time", description="Timeout in ms to remove idle threads from the OOB pool") + public void setOOBThreadPoolKeepAliveTime(long time) { + oob_thread_pool_keep_alive_time=time; + if(oob_thread_pool instanceof ThreadPoolExecutor) + ((ThreadPoolExecutor)oob_thread_pool).setKeepAliveTime(time, TimeUnit.MILLISECONDS); + } + + public long getOOBThreadPoolKeepAliveTime() {return oob_thread_pool_keep_alive_time;} + + + @Property(name="oob_thread_pool.min_threads",description="Minimum thread pool size for the OOB thread pool") + public void setOOBThreadPoolMinThreads(int size) { + oob_thread_pool_min_threads=size; + if(oob_thread_pool instanceof ThreadPoolExecutor) + ((ThreadPoolExecutor)oob_thread_pool).setCorePoolSize(size); + } + + public int getOOBThreadPoolMinThreads() {return oob_thread_pool_min_threads;} + + @Property(name="oob_thread_pool.max_threads",description="Max thread pool size for the OOB thread pool") + public void setOOBThreadPoolMaxThreads(int size) { + oob_thread_pool_max_threads=size; + if(oob_thread_pool instanceof ThreadPoolExecutor) + ((ThreadPoolExecutor)oob_thread_pool).setMaximumPoolSize(size); + } + + public int getOOBThreadPoolMaxThreads() {return oob_thread_pool_max_threads;} + + + @Property(name="thread_pool.min_threads",description="Minimum thread pool size for the regular thread pool") + public void setThreadPoolMinThreads(int size) { + thread_pool_min_threads=size; + if(thread_pool instanceof ThreadPoolExecutor) + ((ThreadPoolExecutor)thread_pool).setCorePoolSize(size); + } + + public int getThreadPoolMinThreads() {return thread_pool_min_threads;} + + + @Property(name="thread_pool.max_threads",description="Maximum thread pool size for the regular thread pool") + public void setThreadPoolMaxThreads(int size) { + thread_pool_max_threads=size; + if(thread_pool instanceof ThreadPoolExecutor) + ((ThreadPoolExecutor)thread_pool).setMaximumPoolSize(size); + } + + public int getThreadPoolMaxThreads() {return thread_pool_max_threads;} + + + @Property(name="thread_pool.keep_alive_time",description="Timeout in milliseconds to remove idle thread from regular pool") + public void setThreadPoolKeepAliveTime(long time) { + thread_pool_keep_alive_time=time; + if(thread_pool instanceof ThreadPoolExecutor) + ((ThreadPoolExecutor)thread_pool).setKeepAliveTime(time, TimeUnit.MILLISECONDS); + } + + public long getThreadPoolKeepAliveTime() {return thread_pool_keep_alive_time;} + + + + @Property(name="timer.min_threads",description="Minimum thread pool size for the timer thread pool") + public void setTimerMinThreads(int size) { + timer_min_threads=size; + if(timer != null) + timer.setMinThreads(size); + } + + public int getTimerMinThreads() {return timer_min_threads;} + + + @Property(name="timer.max_threads",description="Max thread pool size for the timer thread pool") + public void setTimerMaxThreads(int size) { + timer_max_threads=size; + if(timer != null) + timer.setMaxThreads(size); + } + + public int getTimerMaxThreads() {return timer_max_threads;} + + + @Property(name="timer.keep_alive_time", description="Timeout in ms to remove idle threads from the timer pool") + public void setTimerKeepAliveTime(long time) { + timer_keep_alive_time=time; + if(timer != null) + timer.setKeepAliveTime(time); + } + + public long getTimerKeepAliveTime() {return timer_keep_alive_time;} + @ManagedAttribute + public int getTimerQueueSize() { + if(timer instanceof TimeScheduler2) + return ((TimeScheduler2)timer).getQueueSize(); + return 0; + } + + /* --------------------------------------------- JMX ---------------------------------------------- */ + + + @ManagedAttribute(description="Number of messages sent") protected long num_msgs_sent=0; - @ManagedAttribute + @ManagedAttribute(description="Number of messages received") protected long num_msgs_received=0; - @ManagedAttribute + @ManagedAttribute(description="Number of bytes sent") protected long num_bytes_sent=0; - @ManagedAttribute + @ManagedAttribute(description="Number of bytes received") protected long num_bytes_received=0; - /** The name of the group to which this member is connected */ - @ManagedAttribute + /** The name of the group to which this member is connected. With a shared transport, the channel name is + * in TP.ProtocolAdapter (cluster_name), and this field is not used */ + @ManagedAttribute(description="Channel (cluster) name") protected String channel_name=null; - /** whether or not warnings about messages from different groups are logged - private flag, not for common use */ - @ManagedAttribute(writable=true, description="whether or not warnings about messages from different groups are logged") - private boolean log_discard_msgs=true; - - @ManagedAttribute + @ManagedAttribute(description="Number of OOB messages received") protected long num_oob_msgs_received=0; - @ManagedAttribute + @ManagedAttribute(description="Number of regular messages received") protected long num_incoming_msgs_received=0; - - - /* --------------------------------------------- Fields ------------------------------------------------------ */ + @ManagedAttribute(description="Class of the timer implementation") + public String getTimerClass() { + return timer != null? timer.getClass().getSimpleName() : "null"; + } - - - /** The address (host and port) of this member */ - protected Address local_addr=null; + /* --------------------------------------------- Fields ------------------------------------------------------ */ - /** The members of this group (updated when a member joins or leaves) */ - protected final HashSet
      members=new HashSet
      (11); - protected View view=null; - protected final ExposedByteArrayInputStream in_stream=new ExposedByteArrayInputStream(new byte[] { '0' }); - protected final DataInputStream dis=new DataInputStream(in_stream); + /** The address (host and port) of this member. Null by default when a shared transport is used */ + protected Address local_addr=null; + /** The members of this group (updated when a member joins or leaves). With a shared transport, + * members contains *all* members from all channels sitting on the shared transport */ + protected final Set
      members=new CopyOnWriteArraySet
      (); protected ThreadGroup pool_thread_group=new ThreadGroup(Util.getGlobalThreadGroup(), "Thread Pools"); /** Keeps track of connects and disconnects, in order to start and stop threads */ protected int connect_count=0; - + //http://jira.jboss.org/jira/browse/JGRP-849 protected final ReentrantLock connectLock = new ReentrantLock(); + - /** - * ================================== OOB thread pool ======================== - */ + // ================================== OOB thread pool ======================== protected Executor oob_thread_pool; - + /** Factory which is used by oob_thread_pool */ protected ThreadFactory oob_thread_factory=null; /** Used if oob_thread_pool is a ThreadPoolExecutor and oob_thread_pool_queue_enabled is true */ protected BlockingQueue oob_thread_pool_queue=null; - - /** - * ================================== Regular thread pool ======================= - */ + + // ================================== Regular thread pool ====================== /** The thread pool which handles unmarshalling, version checks and dispatching of regular messages */ protected Executor thread_pool; - + /** Factory which is used by oob_thread_pool */ protected ThreadFactory default_thread_factory=null; /** Used if thread_pool is a ThreadPoolExecutor and thread_pool_queue_enabled is true */ protected BlockingQueue thread_pool_queue=null; - /** - * ================================== Timer thread pool ========================= - */ + // ================================== Timer thread pool ========================= protected TimeScheduler timer=null; protected ThreadFactory timer_thread_factory; - /** - * =================================Default thread factory ======================== - */ + // ================================ Default thread factory ======================== /** Used by all threads created by JGroups outside of the thread pools */ protected ThreadFactory global_thread_factory=null; - - /** - * If set it will be added to local_addr. Used to implement for example transport independent addresses - */ - protected byte[] additional_data=null; + // ================================= Default SocketFactory ======================== + protected SocketFactory socket_factory=new DefaultSocketFactory(); - private Bundler bundler=null; + protected Bundler bundler=null; - private DiagnosticsHandler diag_handler=null; - private final List preregistered_probe_handlers=new LinkedList(); + protected DiagnosticsHandler diag_handler=null; + protected final List preregistered_probe_handlers=new LinkedList(); /** * If singleton_name is enabled, this map is used to de-multiplex incoming messages according to their cluster * names (attached to the message by the transport anyway). The values are the next protocols above the * transports. */ - private final ConcurrentMap up_prots=new ConcurrentHashMap(); + protected final ConcurrentMap up_prots=Util.createConcurrentMap(16, 0.75f, 16); + /** The header including the cluster name, sent with each message. Not used with a shared transport (instead + * TP.ProtocolAdapter attaches the header to the message */ protected TpHeader header; - protected final String name=getName(); - protected PortsManager pm=null; - + /** + * Cache which maintains mappings between logical and physical addresses. When sending a message to a logical + * address, we look up the physical address from logical_addr_cache and send the message to the physical address + *
      + * The keys are logical addresses, the values physical addresses + */ + protected LazyRemovalCache logical_addr_cache=null; + + // last time we sent a discovery request + protected long last_discovery_request=0; + + Future logical_addr_cache_reaper=null; + protected static final LazyRemovalCache.Printable print_function=new LazyRemovalCache.Printable() { + public java.lang.String print(final Address logical_addr, final PhysicalAddress physical_addr) { + StringBuilder sb=new StringBuilder(); + String tmp_logical_name=UUID.get(logical_addr); + if(tmp_logical_name != null) + sb.append(tmp_logical_name).append(": "); + if(logical_addr instanceof UUID) + sb.append(((UUID)logical_addr).toStringLong()); + else + sb.append(logical_addr); + sb.append(": ").append(physical_addr).append("\n"); + return sb.toString(); + } + }; + + /** Cache keeping track of WHO_HAS requests for physical addresses (given a logical address) and expiring + * them after 5000ms */ + protected AgeOutCache
      who_has_cache=null; /** @@ -371,13 +504,18 @@ protected TP() { } - /** - * debug only - */ + /** Whether or not hardware multicasting is supported */ + public abstract boolean supportsMulticasting(); + + public boolean isMulticastCapable() {return supportsMulticasting();} + public String toString() { - return local_addr != null? name + "(local address: " + local_addr + ')' : name; + if(!isSingleton()) + return local_addr != null? name + "(local address: " + local_addr + ')' : name; + else + return name + " (singleton=" + singleton_name + ")"; } - + public void resetStats() { num_msgs_sent=num_msgs_received=num_bytes_sent=num_bytes_received=0; num_oob_msgs_received=num_incoming_msgs_received=0; @@ -394,7 +532,11 @@ if(diag_handler != null) diag_handler.unregisterProbeHandler(handler); } - + + public ThreadGroup getPoolThreadGroup() { + return pool_thread_group; + } + public void setThreadPoolQueueEnabled(boolean flag) {thread_pool_queue_enabled=flag;} @@ -417,7 +559,7 @@ if(thread_pool instanceof ThreadPoolExecutor) ((ThreadPoolExecutor)thread_pool).setThreadFactory(factory); } - + public Executor getOOBThreadPool() { return oob_thread_pool; } @@ -438,7 +580,7 @@ if(oob_thread_pool instanceof ThreadPoolExecutor) ((ThreadPoolExecutor)oob_thread_pool).setThreadFactory(factory); } - + public ThreadFactory getTimerThreadFactory() { return timer_thread_factory; } @@ -449,7 +591,7 @@ } public TimeScheduler getTimer() {return timer;} - + public ThreadFactory getThreadFactory() { return global_thread_factory; } @@ -458,13 +600,21 @@ global_thread_factory=factory; } - + public SocketFactory getSocketFactory() { + return socket_factory; + } + + public void setSocketFactory(SocketFactory factory) { + if(factory != null) + socket_factory=factory; + } + /** * Names the current thread. Valid values are "pcl": * p: include the previous (original) name, e.g. "Incoming thread-1", "UDP ucast receiver" * c: include the cluster name, e.g. "MyCluster" * l: include the local address of the current member, e.g. "192.168.5.1:5678" - */ + */ public String getThreadNamingPattern() {return thread_naming_pattern;} @@ -496,157 +646,88 @@ public boolean isEnableUnicastBundling() {return enable_unicast_bundling;} public void setEnableUnicastBundling(boolean enable_unicast_bundling) {this.enable_unicast_bundling=enable_unicast_bundling;} public void setPortRange(int range) {this.port_range=range;} + public int getPortRange() {return port_range ;} /** @deprecated the concurrent stack is used by default */ @Deprecated public void setUseConcurrentStack(boolean flag) {} - - @ManagedAttribute - public String getLocalAddressAsString() {return local_addr != null? local_addr.toString() : "n/a";} - - @ManagedAttribute public boolean isOOBThreadPoolEnabled() { return oob_thread_pool_enabled; } - @ManagedAttribute - public boolean isDefaulThreadPoolEnabled() { return thread_pool_enabled; } - - @ManagedAttribute(description="Maximum number of bytes for messages to be queued until they are sent") - public int getMaxBundleSize() {return max_bundle_size;} + public boolean isDefaulThreadPoolEnabled() { return thread_pool_enabled; } - @ManagedAttribute(description="Maximum number of bytes for messages to be queued until they are sent",writable=true) - @Property(name="max_bundle_size") - public void setMaxBundleSize(int size) { - if(size <= 0) { - throw new IllegalArgumentException("max_bundle_size (" + size + ") is <= 0"); - } - max_bundle_size=size; - } - - @ManagedAttribute(description="Max number of milliseconds until queued messages are sent") - public long getMaxBundleTimeout() {return max_bundle_timeout;} - - @ManagedAttribute(description="Max number of milliseconds until queued messages are sent", writable=true) - @Property(name="max_bundle_timeout") - public void setMaxBundleTimeout(long timeout) { - if(timeout <= 0) { - throw new IllegalArgumentException("max_bundle_timeout of " + timeout + " is invalid"); - } - max_bundle_timeout=timeout; - } - - public Address getLocalAddress() {return local_addr;} - public String getChannelName() {return channel_name;} public boolean isLoopback() {return loopback;} public void setLoopback(boolean b) {loopback=b;} /** @deprecated With the concurrent stack being the default, this property is ignored */ public static boolean isUseIncomingPacketHandler() {return false;} - public ConcurrentMap getUpProtocols() { - return up_prots; - } - - @ManagedAttribute - public int getOOBMinPoolSize() { - return oob_thread_pool instanceof ThreadPoolExecutor? ((ThreadPoolExecutor)oob_thread_pool).getCorePoolSize() : 0; - } + public ConcurrentMap getUpProtocols() {return up_prots;} - @ManagedAttribute - public void setOOBMinPoolSize(int size) { - if(oob_thread_pool instanceof ThreadPoolExecutor) - ((ThreadPoolExecutor)oob_thread_pool).setCorePoolSize(size); - } - - @ManagedAttribute - public int getOOBMaxPoolSize() { - return oob_thread_pool instanceof ThreadPoolExecutor? ((ThreadPoolExecutor)oob_thread_pool).getMaximumPoolSize() : 0; - } - - @ManagedAttribute - public void setOOBMaxPoolSize(int size) { - if(oob_thread_pool instanceof ThreadPoolExecutor) - ((ThreadPoolExecutor)oob_thread_pool).setMaximumPoolSize(size); - } - - @ManagedAttribute + + @ManagedAttribute(description="Current number of threads in the OOB thread pool") public int getOOBPoolSize() { return oob_thread_pool instanceof ThreadPoolExecutor? ((ThreadPoolExecutor)oob_thread_pool).getPoolSize() : 0; } - @ManagedAttribute - public long getOOBKeepAliveTime() { - return oob_thread_pool instanceof ThreadPoolExecutor? ((ThreadPoolExecutor)oob_thread_pool).getKeepAliveTime(TimeUnit.MILLISECONDS) : 0; - } - - @ManagedAttribute - public void setOOBKeepAliveTime(long time) { - if(oob_thread_pool instanceof ThreadPoolExecutor) - ((ThreadPoolExecutor)oob_thread_pool).setKeepAliveTime(time, TimeUnit.MILLISECONDS); - } - public long getOOBMessages() { return num_oob_msgs_received; } - @ManagedAttribute + @ManagedAttribute(description="Number of messages in the OOB thread pool's queue") public int getOOBQueueSize() { - return oob_thread_pool_queue.size(); + return oob_thread_pool_queue != null? oob_thread_pool_queue.size() : 0; } public int getOOBMaxQueueSize() { return oob_thread_pool_queue_max_size; } - @ManagedAttribute - public int getIncomingMinPoolSize() { - return thread_pool instanceof ThreadPoolExecutor? ((ThreadPoolExecutor)thread_pool).getCorePoolSize() : 0; - } - @ManagedAttribute - public void setIncomingMinPoolSize(int size) { - if(thread_pool instanceof ThreadPoolExecutor) - ((ThreadPoolExecutor)thread_pool).setCorePoolSize(size); + public void setOOBRejectionPolicy(String rejection_policy) { + RejectedExecutionHandler handler=parseRejectionPolicy(rejection_policy); + if(oob_thread_pool instanceof ThreadPoolExecutor) + ((ThreadPoolExecutor)oob_thread_pool).setRejectedExecutionHandler(new ShutdownRejectedExecutionHandler(handler)); } - @ManagedAttribute - public int getIncomingMaxPoolSize() { - return thread_pool instanceof ThreadPoolExecutor? ((ThreadPoolExecutor)thread_pool).getMaximumPoolSize() : 0; + + @ManagedAttribute(description="Current number of threads in the default thread pool") + public int getRegularPoolSize() { + return thread_pool instanceof ThreadPoolExecutor? ((ThreadPoolExecutor)thread_pool).getPoolSize() : 0; } - @ManagedAttribute - public void setIncomingMaxPoolSize(int size) { - if(thread_pool instanceof ThreadPoolExecutor) - ((ThreadPoolExecutor)thread_pool).setMaximumPoolSize(size); + public long getRegularMessages() { + return num_incoming_msgs_received; } - @ManagedAttribute - public int getIncomingPoolSize() { - return thread_pool instanceof ThreadPoolExecutor? ((ThreadPoolExecutor)thread_pool).getPoolSize() : 0; + @ManagedAttribute(description="Number of messages in the default thread pool's queue") + public int getRegularQueueSize() { + return thread_pool_queue != null? thread_pool_queue.size() : 0; } - @ManagedAttribute - public long getIncomingKeepAliveTime() { - return thread_pool instanceof ThreadPoolExecutor? ((ThreadPoolExecutor)thread_pool).getKeepAliveTime(TimeUnit.MILLISECONDS) : 0; + public int getRegularMaxQueueSize() { + return thread_pool_queue_max_size; } - @ManagedAttribute - public void setIncomingKeepAliveTime(long time) { - if(thread_pool instanceof ThreadPoolExecutor) - ((ThreadPoolExecutor)thread_pool).setKeepAliveTime(time, TimeUnit.MILLISECONDS); + @ManagedAttribute(name="TimerTasks",description="Number of timer tasks queued up for execution") + public int getNumTimerTasks() { + return timer != null? timer.size() : -1; } - public long getIncomingMessages() { - return num_incoming_msgs_received; + @ManagedOperation + public String dumpTimerTasks() { + return timer.dumpTimerTasks(); } - @ManagedAttribute - public int getIncomingQueueSize() { - return thread_pool_queue.size(); + @ManagedAttribute(description="Number of threads currently in the pool") + public int getTimerThreads() { + return timer.getCurrentThreads(); } - public int getIncomingMaxQueueSize() { - return thread_pool_queue_max_size; + public void setRegularRejectionPolicy(String rejection_policy) { + RejectedExecutionHandler handler=parseRejectionPolicy(rejection_policy); + if(thread_pool instanceof ThreadPoolExecutor) + ((ThreadPoolExecutor)thread_pool).setRejectedExecutionHandler(new ShutdownRejectedExecutionHandler(handler)); } public void setLogDiscardMessages(boolean flag) { @@ -657,6 +738,16 @@ return log_discard_msgs; } + @ManagedOperation(description="Dumps the contents of the logical address cache") + public String printLogicalAddressCache() { + return logical_addr_cache.printCache(print_function); + } + + @ManagedOperation(description="Evicts elements in the logical address cache which have expired") + public void evictLogicalAddressCache() { + logical_addr_cache.removeMarkedElements(); + fetchLocalAddresses(); + } /** * Send to all members in the group. UDP would use an IP multicast message, whereas TCP would send N @@ -666,25 +757,20 @@ * @param length * @throws Exception */ - public abstract void sendToAllMembers(byte[] data, int offset, int length) throws Exception; + public abstract void sendMulticast(byte[] data, int offset, int length) throws Exception; /** - * Send to all members in the group. UDP would use an IP multicast message, whereas TCP would send N - * messages, one for each member + * Send a unicast to 1 member. Note that the destination address is a *physical*, not a logical address * @param dest Must be a non-null unicast address * @param data The data to be sent. This is not a copy, so don't modify it * @param offset * @param length * @throws Exception */ - public abstract void sendToSingleMember(Address dest, byte[] data, int offset, int length) throws Exception; + public abstract void sendUnicast(PhysicalAddress dest, byte[] data, int offset, int length) throws Exception; public abstract String getInfo(); - public abstract void postUnmarshalling(Message msg, Address dest, Address src, boolean multicast); - - public abstract void postUnmarshallingList(Message msg, Address dest, boolean multicast); - /* ------------------------------------------------------------------------------- */ @@ -696,88 +782,125 @@ super.init(); // Create the default thread factory - global_thread_factory=new DefaultThreadFactory(Util.getGlobalThreadGroup(), "", false); + if(global_thread_factory == null) + global_thread_factory=new DefaultThreadFactory(Util.getGlobalThreadGroup(), "", false); // Create the timer and the associated thread factory - depends on singleton_name - // timer_thread_factory=new DefaultThreadFactory(Util.getGlobalThreadGroup(), "Timer", true, true); - timer_thread_factory=new LazyThreadFactory(Util.getGlobalThreadGroup(), "Timer", true, true); - if(isSingleton()) { + if(timer_thread_factory == null) + timer_thread_factory=new LazyThreadFactory(Util.getGlobalThreadGroup(), "Timer", true, true); + if(isSingleton()) timer_thread_factory.setIncludeClusterName(false); - } - default_thread_factory=new DefaultThreadFactory(pool_thread_group, "Incoming", false, true); + if(default_thread_factory == null) + default_thread_factory=new DefaultThreadFactory(pool_thread_group, "Incoming", false, true); - oob_thread_factory=new DefaultThreadFactory(pool_thread_group, "OOB", false, true); + if(oob_thread_factory == null) + oob_thread_factory=new DefaultThreadFactory(pool_thread_group, "OOB", false, true); + // local_addr is null when shared transport, channel_name is not used setInAllThreadFactories(channel_name, local_addr, thread_naming_pattern); - timer=new TimeScheduler(timer_thread_factory, num_timer_threads); + if(timer_type.equalsIgnoreCase("old")) { + if(timer_min_threads < 2) { + log.warn("timer.min_threads should not be less than 2 for timer_type=\"old\"; setting value to 2 (from " + + timer_min_threads + ")"); + timer_min_threads=2; + } + timer=new DefaultTimeScheduler(timer_thread_factory, timer_min_threads); + } + else if(timer_type.equalsIgnoreCase("new")) { + timer=new TimeScheduler2(timer_thread_factory, timer_min_threads, timer_max_threads, timer_keep_alive_time, + timer_queue_max_size); + } + else if(timer_type.equalsIgnoreCase("wheel")) { + timer=new HashedTimingWheel(timer_thread_factory, timer_min_threads, timer_max_threads, timer_keep_alive_time, + timer_queue_max_size, wheel_size, tick_time); + } + else { + throw new Exception("timer_type has to be either \"old\", \"new\" or \"wheel\""); + } + + who_has_cache=new AgeOutCache
      (timer, 5000L); verifyRejectionPolicy(oob_thread_pool_rejection_policy); verifyRejectionPolicy(thread_pool_rejection_policy); - out_stream=new ExposedByteArrayOutputStream(INITIAL_BUFSIZE); - dos=new ExposedDataOutputStream(out_stream); - // ========================================== OOB thread pool ============================== - if(oob_thread_pool_enabled) { - if(oob_thread_pool_queue_enabled) - oob_thread_pool_queue=new LinkedBlockingQueue(oob_thread_pool_queue_max_size); - else - oob_thread_pool_queue=new SynchronousQueue(); - oob_thread_pool=createThreadPool(oob_thread_pool_min_threads, oob_thread_pool_max_threads, oob_thread_pool_keep_alive_time, - oob_thread_pool_rejection_policy, oob_thread_pool_queue, oob_thread_factory); - } - else { // otherwise use the caller's thread to unmarshal the byte buffer into a message - oob_thread_pool=new DirectExecutor(); + if(oob_thread_pool == null) { + if(oob_thread_pool_enabled) { + if(oob_thread_pool_queue_enabled) + oob_thread_pool_queue=new LinkedBlockingQueue(oob_thread_pool_queue_max_size); + else + oob_thread_pool_queue=new SynchronousQueue(); + oob_thread_pool=createThreadPool(oob_thread_pool_min_threads, oob_thread_pool_max_threads, oob_thread_pool_keep_alive_time, + oob_thread_pool_rejection_policy, oob_thread_pool_queue, oob_thread_factory); + } + else { // otherwise use the caller's thread to unmarshal the byte buffer into a message + oob_thread_pool=new DirectExecutor(); + } } // ====================================== Regular thread pool =========================== - if(thread_pool_enabled) { - if(thread_pool_queue_enabled) - thread_pool_queue=new LinkedBlockingQueue(thread_pool_queue_max_size); - else - thread_pool_queue=new SynchronousQueue(); - thread_pool=createThreadPool(thread_pool_min_threads, thread_pool_max_threads, thread_pool_keep_alive_time, - thread_pool_rejection_policy, thread_pool_queue, default_thread_factory); - } - else { // otherwise use the caller's thread to unmarshal the byte buffer into a message - thread_pool=new DirectExecutor(); + if(thread_pool == null) { + if(thread_pool_enabled) { + if(thread_pool_queue_enabled) + thread_pool_queue=new LinkedBlockingQueue(thread_pool_queue_max_size); + else + thread_pool_queue=new SynchronousQueue(); + thread_pool=createThreadPool(thread_pool_min_threads, thread_pool_max_threads, thread_pool_keep_alive_time, + thread_pool_rejection_policy, thread_pool_queue, default_thread_factory); + } + else { // otherwise use the caller's thread to unmarshal the byte buffer into a message + thread_pool=new DirectExecutor(); + } } - if(persistent_ports){ - pm = new PortsManager(pm_expiry_time,persistent_ports_file); - } if(bind_addr != null) { Map m=new HashMap(1); m.put("bind_addr", bind_addr); up(new Event(Event.CONFIG, m)); } + + logical_addr_cache=new LazyRemovalCache(logical_addr_cache_max_size, logical_addr_cache_expiration); + + if(logical_addr_cache_reaper == null || logical_addr_cache_reaper.isDone()) { + if(logical_addr_cache_expiration <= 0) + throw new IllegalArgumentException("logical_addr_cache_expiration has to be > 0"); + logical_addr_cache_reaper=timer.scheduleWithFixedDelay(new Runnable() { + public void run() { + logical_addr_cache.removeMarkedElements(); + fetchLocalAddresses(); + } + + public String toString() { + return "TP.LogicalAddressCacheReaper (interval=" + logical_addr_cache_expiration + " ms)"; + } + }, logical_addr_cache_expiration, logical_addr_cache_expiration, TimeUnit.MILLISECONDS); + } } public void destroy() { super.destroy(); + + if(logical_addr_cache_reaper != null) { + logical_addr_cache_reaper.cancel(false); + logical_addr_cache_reaper=null; + } + if(timer != null) { - try { - timer.stop(); - } - catch(InterruptedException e) { - log.error("failed stopping the timer", e); - } + timer.stop(); } // 3. Stop the thread pools if(oob_thread_pool instanceof ThreadPoolExecutor) { shutdownThreadPool(oob_thread_pool); - oob_thread_pool=null; } if(thread_pool instanceof ThreadPoolExecutor) { shutdownThreadPool(thread_pool); - thread_pool=null; } } @@ -785,6 +908,8 @@ * Creates the unicast and multicast sockets and starts the unicast and multicast receiver threads */ public void start() throws Exception { + fetchLocalAddresses(); + if(timer == null) throw new Exception("timer is null"); @@ -797,11 +922,20 @@ } if(enable_bundling) { - bundler=new Bundler(); + if(bundler_type.equals("new")) + bundler=new TransferQueueBundler(bundler_capacity); + else if(bundler_type.equals("old")) + bundler=new DefaultBundler(); + else + log.warn("bundler_type \"" + bundler_type + "\" not known; using default bundler"); + if(bundler == null) + bundler=new DefaultBundler(); + + bundler.start(); } + // local_addr is null when shared transport setInAllThreadFactories(channel_name, local_addr, thread_naming_pattern); - sendUpLocalAddressEvent(); } @@ -811,6 +945,8 @@ diag_handler=null; } preregistered_probe_handlers.clear(); + if(bundler != null) + bundler.stop(); } @@ -822,13 +958,13 @@ protected void handleDisconnect() { connect_count=Math.max(0, connect_count -1); } - + public String getSingletonName() { return singleton_name; } public boolean isSingleton(){ - return singleton_name != null && singleton_name.length() >0; + return singleton_name != null; } @@ -837,16 +973,6 @@ * @param evt - the event being send from the stack */ public Object up(Event evt) { - switch(evt.getType()) { - case Event.CONFIG: - if(isSingleton()) - passToAllUpProtocols(evt); - else - up_prot.up(evt); - if(log.isDebugEnabled()) log.debug("received CONFIG event: " + evt.getArg()); - handleConfigEvent((Map)evt.getArg()); - return null; - } if(isSingleton()) { passToAllUpProtocols(evt); return null; @@ -869,21 +995,29 @@ Message msg=(Message)evt.getArg(); if(header != null) { // added patch by Roland Kurmann (March 20 2003) - // msg.putHeader(name, new TpHeader(channel_name)); - msg.putHeaderIfAbsent(name, header); + // msg.putHeader(this.id, new TpHeader(channel_name)); + msg.putHeaderIfAbsent(this.id, header); } - setSourceAddress(msg); // very important !! listToBuffer() will fail with a null src address !! + if(!isSingleton()) + setSourceAddress(msg); // very important !! listToBuffer() will fail with a null src address !! if(log.isTraceEnabled()) { log.trace("sending msg to " + msg.getDest() + ", src=" + msg.getSrc() + ", headers are " + msg.printHeaders()); } - // Don't send if destination is local address. Instead, switch dst and src and put in up_queue. + // Don't send if destination is local address. Instead, switch dst and src and send it up the stack. // If multicast message, loopback a copy directly to us (but still multicast). Once we receive this, // we will discard our own multicast message Address dest=msg.getDest(); - boolean multicast=dest == null || dest.isMulticastAddress(); - if(loopback && (multicast || dest.equals(local_addr))) { + if(dest instanceof PhysicalAddress) { + // We can modify the message because it won't get retransmitted. The only time we have a physical address + // as dest is when TCPPING sends the initial discovery requests to initial_hosts: this is below UNICAST, + // so no retransmission + msg.setDest(null); + } + + final boolean multicast=dest == null || dest.isMulticastAddress(); + if(loopback && (multicast || (dest.equals(msg.getSrc()) && dest.equals(local_addr)))) { // we *have* to make a copy, or else up_prot.up() might remove headers from msg which will then *not* // be available for marshalling further down (when sending the message) @@ -895,7 +1029,7 @@ Executor pool=msg.isFlagSet(Message.OOB)? oob_thread_pool : thread_pool; pool.execute(new Runnable() { public void run() { - passMessageUp(copy, false); + passMessageUp(copy, false, multicast, false); } }); @@ -906,13 +1040,16 @@ try { send(msg, dest, multicast); } + catch(InterruptedIOException iex) { + ; + } catch(InterruptedException interruptedEx) { Thread.currentThread().interrupt(); // let someone else handle the interrupt } catch(Throwable e) { if(log.isErrorEnabled()) { - String dst=msg.getDest() == null? "null" : msg.getDest().toString(); - log.error("failed sending message to " + dst + " (" + msg.size() + " bytes)", e); + log.error("failed sending message to " + (dest == null? "cluster" : dest) + + " (" + msg.size() + " bytes): " + e + ", cause: " + e.getCause()); } } return null; @@ -929,177 +1066,133 @@ /** * If the sender is null, set our own address. We cannot just go ahead and set the address - * anyway, as we might be sending a message on behalf of someone else ! E.gin case of + * anyway, as we might be sending a message on behalf of someone else ! E.g. in case of * retransmission, when the original sender has crashed, or in a FLUSH protocol when we * have to return all unstable messages with the FLUSH_OK response. */ - private void setSourceAddress(Message msg) { - if(msg.getSrc() == null) + protected void setSourceAddress(Message msg) { + if(msg.getSrc() == null && local_addr != null) // should already be set by TP.ProtocolAdapter in shared transport case ! msg.setSrc(local_addr); } - private void passMessageUp(Message msg, boolean perform_cluster_name_matching) { - TpHeader hdr=(TpHeader)msg.getHeader(name); // replaced removeHeader() with getHeader() + protected void passMessageUp(Message msg, boolean perform_cluster_name_matching, boolean multicast, boolean discard_own_mcast) { + TpHeader hdr=(TpHeader)msg.getHeader(this.id); if(hdr == null) { - if(channel_name == null) { - Event evt=new Event(Event.MSG, msg); - if(isSingleton()) { - passMessageToAll(evt); - } - else { - up_prot.up(evt); - } - } - else { - if(log.isErrorEnabled()) - log.error(new StringBuilder("message does not have a transport header, msg is ").append(msg). - append(", headers are ").append(msg.printHeaders()).append(", will be discarded")); - } + if(log.isErrorEnabled()) + log.error(new StringBuilder("message does not have a transport header, msg is ").append(msg). + append(", headers are ").append(msg.printHeaders()).append(", will be discarded").toString()); return; } + if(log.isTraceEnabled()) + log.trace(new StringBuilder("received ").append(msg).append(", headers are ").append(msg.printHeaders())); + String ch_name=hdr.channel_name; - if(isSingleton()) { - Protocol tmp_prot=up_prots.get(ch_name); - if(tmp_prot != null) { - Event evt=new Event(Event.MSG, msg); - if(log.isTraceEnabled()) { - StringBuilder sb=new StringBuilder("message is ").append(msg).append(", headers are ").append(msg.printHeaders()); - log.trace(sb); - } - tmp_prot.up(evt); - } - else { - // we discard messages for a group we don't have. If we had a scenario with channel C1 and A,B on it, - // and channel C2 and only A on it (asymmetric setup), then C2 would always log warnings that B was - // not found (Jan 25 2008 (bela)) - // if(log.isWarnEnabled()) - // log.warn(new StringBuilder("discarded message from group \"").append(ch_name). - // append("\" (our groups are ").append(up_prots.keySet()).append("). Sender was ").append(msg.getSrc())); - } - } - else { - // Discard if message's group name is not the same as our group name - if(perform_cluster_name_matching && channel_name != null && !channel_name.equals(ch_name)) { + + final Protocol tmp_prot=isSingleton()? up_prots.get(ch_name) : up_prot; + if(tmp_prot != null) { + boolean is_protocol_adapter=tmp_prot instanceof ProtocolAdapter; + // Discard if message's cluster name is not the same as our cluster name + if(!is_protocol_adapter && perform_cluster_name_matching && channel_name != null && !channel_name.equals(ch_name)) { if(log.isWarnEnabled() && log_discard_msgs) - log.warn(new StringBuilder("discarded message from different group \"").append(ch_name). - append("\" (our group is \"").append(channel_name).append("\"). Sender was ").append(msg.getSrc())); - } - else { - Event evt=new Event(Event.MSG, msg); - if(log.isTraceEnabled()) { - StringBuilder sb=new StringBuilder("message is ").append(msg).append(", headers are ").append(msg.printHeaders()); - log.trace(sb); - } - up_prot.up(evt); + log.warn(new StringBuilder("discarded message from different cluster \"").append(ch_name). + append("\" (our cluster is \"").append(channel_name).append("\"). Sender was ").append(msg.getSrc()).toString()); + return; } - } - } - private void passMessageToAll(Event evt) { - for(Protocol tmp_prot: up_prots.values()) { - try { - tmp_prot.up(evt); - } - catch(Exception ex) { - if(log.isErrorEnabled()) - log.error("failure passing message up: message is " + evt.getArg(), ex); + if(loopback && multicast && discard_own_mcast) { + Address local=is_protocol_adapter? ((ProtocolAdapter)tmp_prot).getAddress() : local_addr; + if(local != null && local.equals(msg.getSrc())) + return; } + tmp_prot.up(new Event(Event.MSG, msg)); } } + + /** * Subclasses must call this method when a unicast or multicast message has been received. * Declared final so subclasses cannot override this method. * - * @param dest * @param sender * @param data * @param offset * @param length */ - protected final void receive(Address dest, Address sender, byte[] data, int offset, int length) { + protected void receive(Address sender, byte[] data, int offset, int length) { if(data == null) return; - if(log.isTraceEnabled()){ - boolean mcast=dest == null || dest.isMulticastAddress(); - StringBuilder sb=new StringBuilder("received ("); - sb.append(mcast? "mcast) " : "ucast) ").append(length).append(" bytes from ").append(sender); - log.trace(sb); - } - try { // determine whether OOB or not by looking at first byte of 'data' byte oob_flag=data[Global.SHORT_SIZE]; // we need to skip the first 2 bytes (version) + if((oob_flag & OOB) == OOB) { num_oob_msgs_received++; - dispatchToThreadPool(oob_thread_pool, dest, sender, data, offset, length); + dispatchToThreadPool(oob_thread_pool, sender, data, offset, length); } else { num_incoming_msgs_received++; - dispatchToThreadPool(thread_pool, dest, sender, data, offset, length); + dispatchToThreadPool(thread_pool, sender, data, offset, length); } } catch(Throwable t) { if(log.isErrorEnabled()) - log.error(new StringBuilder("failed handling data from ").append(sender), t); + log.error(new StringBuilder("failed handling data from ").append(sender).toString(), t); } } - private void dispatchToThreadPool(Executor pool, Address dest, Address sender, byte[] data, int offset, int length) { + protected void dispatchToThreadPool(Executor pool, Address sender, byte[] data, int offset, int length) { if(pool instanceof DirectExecutor) { // we don't make a copy of the buffer if we execute on this thread - pool.execute(new IncomingPacket(dest, sender, data, offset, length)); + pool.execute(new IncomingPacket(sender, data, offset, length)); } else { byte[] tmp=new byte[length]; System.arraycopy(data, offset, tmp, 0, length); - pool.execute(new IncomingPacket(dest, sender, tmp, 0, length)); + pool.execute(new IncomingPacket(sender, tmp, 0, length)); } } - /** Internal method to serialize and send a message. This method is not reentrant */ - private void send(Message msg, Address dest, boolean multicast) throws Exception { + /** Serializes and sends a message. This method is not reentrant */ + protected void send(Message msg, Address dest, boolean multicast) throws Exception { // bundle only regular messages; send OOB messages directly - if(enable_bundling && !msg.isFlagSet(Message.OOB)) { + if(enable_bundling && !(msg.isFlagSet(Message.OOB) || msg.isFlagSet(Message.DONT_BUNDLE))) { if(!enable_unicast_bundling && !multicast) { ; // don't bundle unicast msgs if enable_unicast_bundling is off (http://jira.jboss.com/jira/browse/JGRP-429) } else { - bundler.send(msg, dest); + bundler.send(msg); return; } } - out_stream_lock.lock(); - try { - out_stream.reset(); - dos.reset(); - writeMessage(msg, dos, multicast); - Buffer buf=new Buffer(out_stream.getRawBuffer(), 0, out_stream.size()); - doSend(buf, dest, multicast); - } - finally { - out_stream_lock.unlock(); - } + // we can create between 300'000 - 400'000 output streams and do the marshalling per second, + // so this is not a bottleneck ! + ExposedByteArrayOutputStream out_stream=new ExposedByteArrayOutputStream((int)(msg.size() + 50)); + ExposedDataOutputStream dos=new ExposedDataOutputStream(out_stream); + writeMessage(msg, dos, multicast); + Buffer buf=new Buffer(out_stream.getRawBuffer(), 0, out_stream.size()); + doSend(buf, dest, multicast); + // we don't need to close() or flush() any of the 2 streams above, as these ops are no-ops } - private void doSend(Buffer buf, Address dest, boolean multicast) throws Exception { + protected void doSend(Buffer buf, Address dest, boolean multicast) throws Exception { if(stats) { num_msgs_sent++; num_bytes_sent+=buf.getLength(); } if(multicast) { - sendToAllMembers(buf.getBuf(), buf.getOffset(), buf.getLength()); + sendMulticast(buf.getBuf(), buf.getOffset(), buf.getLength()); } else { sendToSingleMember(dest, buf.getBuf(), buf.getOffset(), buf.getLength()); @@ -1107,6 +1200,45 @@ } + protected void sendToSingleMember(Address dest, byte[] buf, int offset, int length) throws Exception { + PhysicalAddress physical_dest=dest instanceof PhysicalAddress? (PhysicalAddress)dest : getPhysicalAddressFromCache(dest); + if(physical_dest == null) { + if(!who_has_cache.contains(dest)) { + who_has_cache.add(dest); + if(log.isWarnEnabled()) + log.warn(local_addr+ ": no physical address for " + dest + ", dropping message"); + up(new Event(Event.GET_PHYSICAL_ADDRESS, dest)); + } + return; + } + sendUnicast(physical_dest, buf, offset, length); + } + + + protected void sendToAllPhysicalAddresses(byte[] buf, int offset, int length) throws Exception { + Set dests=new HashSet(logical_addr_cache.nonRemovedValues()); + if(!logical_addr_cache.containsKeys(members)) { + long current_time=0; + synchronized(this) { + if(last_discovery_request == 0 || (current_time=System.currentTimeMillis()) - last_discovery_request >= 10000) { + last_discovery_request=current_time == 0? System.currentTimeMillis() : current_time; + if(log.isWarnEnabled()) + log.warn("logical address cache didn't contain all physical address, sending up a discovery request"); + up_prot.up(new Event(Event.FIND_INITIAL_MBRS)); + } + } + } + + for(PhysicalAddress dest: dests) { + try { + sendUnicast(dest, buf, offset, length); + } + catch(Throwable t) { + if(log.isErrorEnabled()) + log.error("failure sending message to " + dest + ": " + t); + } + } + } /** * This method needs to be synchronized on out_stream when it is called @@ -1114,7 +1246,7 @@ * @return * @throws java.io.IOException */ - private static void writeMessage(Message msg, DataOutputStream dos, boolean multicast) throws Exception { + protected static void writeMessage(Message msg, DataOutputStream dos, boolean multicast) throws Exception { byte flags=0; dos.writeShort(Version.version); // write the version if(multicast) @@ -1125,127 +1257,205 @@ msg.writeTo(dos); } - private Message readMessage(DataInputStream instream, Address dest, Address sender, boolean multicast) throws Exception { + protected static Message readMessage(DataInputStream instream) throws Exception { Message msg=new Message(false); // don't create headers, readFrom() will do this msg.readFrom(instream); - postUnmarshalling(msg, dest, sender, multicast); // allows for optimization by subclass return msg; } - private static void writeMessageList(List msgs, DataOutputStream dos, boolean multicast) throws Exception { - Address src; - byte flags=0; - int len=msgs != null? msgs.size() : 0; - boolean src_written=false; + /** + * Write a lits of messages with the same destination and *mostly* the same src addresses. The message list is + * marshalled as follows: + *
      +     * List: * | version | flags | dest | src | [Message*] |
      +     *
      +     * Message:  | presence | leading | flags | [src] | length | [buffer] | size | [Headers*] |
      +     *
      +     * 
      + * @param dest + * @param src + * @param msgs + * @param dos + * @param multicast + * @throws Exception + */ + protected static void writeMessageList(Address dest, Address src, + List msgs, DataOutputStream dos, boolean multicast) throws Exception { dos.writeShort(Version.version); - flags+=LIST; + + byte flags=LIST; if(multicast) flags+=MULTICAST; + dos.writeByte(flags); - dos.writeInt(len); + + Util.writeAddress(dest, dos); + + Util.writeAddress(src, dos); + if(msgs != null) { for(Message msg: msgs) { - src=msg.getSrc(); - if(!src_written) { - Util.writeAddress(src, dos); - src_written=true; - } - msg.writeTo(dos); + dos.writeBoolean(true); + msg.writeToNoAddrs(src, dos); } } + + dos.writeBoolean(false); // terminating presence - no more messages will follow } - private List readMessageList(DataInputStream instream, Address dest, boolean multicast) throws Exception { - int len; - Message msg; - Address src; - len=instream.readInt(); - List list=new ArrayList(len); - src=Util.readAddress(instream); - for(int i=0; i < len; i++) { - msg=new Message(false); // don't create headers, readFrom() will do this - msg.readFrom(instream); - postUnmarshallingList(msg, dest, multicast); - msg.setSrc(src); + + protected static List readMessageList(DataInputStream in) throws Exception { + List list=new LinkedList(); + Address dest=Util.readAddress(in); + Address src=Util.readAddress(in); + + while(in.readBoolean()) { + Message msg=new Message(false); + msg.readFrom(in); + msg.setDest(dest); + if(msg.getSrc() == null) + msg.setSrc(src); list.add(msg); } return list; } - + @SuppressWarnings("unchecked") protected Object handleDownEvent(Event evt) { switch(evt.getType()) { - case Event.TMP_VIEW: - case Event.VIEW_CHANGE: - synchronized(members) { - view=(View)evt.getArg(); - members.clear(); - - if(!isSingleton()) { - Vector
      tmpvec=view.getMembers(); - members.addAll(tmpvec); - } - else { - for(Protocol prot: up_prots.values()) { - if(prot instanceof ProtocolAdapter) { - ProtocolAdapter ad=(ProtocolAdapter)prot; - List
      tmp=ad.getMembers(); - members.addAll(tmp); + case Event.TMP_VIEW: + case Event.VIEW_CHANGE: + synchronized(members) { + View view=(View)evt.getArg(); + members.clear(); + + if(!isSingleton()) { + Vector
      tmpvec=view.getMembers(); + members.addAll(tmpvec); + } + else { + // add all members from all clusters + for(Protocol prot: up_prots.values()) { + if(prot instanceof ProtocolAdapter) { + ProtocolAdapter ad=(ProtocolAdapter)prot; + Set
      tmp=ad.getMembers(); + members.addAll(tmp); + } } } + + // fix for https://jira.jboss.org/jira/browse/JGRP-918 + logical_addr_cache.retainAll(members); + fetchLocalAddresses(); + UUID.retainAll(members); + } + + break; + + case Event.CONNECT: + case Event.CONNECT_WITH_STATE_TRANSFER: + case Event.CONNECT_USE_FLUSH: + case Event.CONNECT_WITH_STATE_TRANSFER_USE_FLUSH: + channel_name=(String)evt.getArg(); + header=new TpHeader(channel_name); + + // local_addr is null when shared transport + setInAllThreadFactories(channel_name, local_addr, thread_naming_pattern); + setThreadNames(); + connectLock.lock(); + try { + handleConnect(); } - } - break; + catch(Exception e) { + throw new RuntimeException(e); + } + finally { + connectLock.unlock(); + } + return null; - case Event.CONNECT: - case Event.CONNECT_WITH_STATE_TRANSFER: - channel_name=(String)evt.getArg(); - header=new TpHeader(channel_name); - setInAllThreadFactories(channel_name, local_addr, thread_naming_pattern); - setThreadNames(); - connectLock.lock(); - try { - handleConnect(); - } - catch(Exception e) { - throw new RuntimeException(e); - } - finally { - connectLock.unlock(); - } - return null; + case Event.DISCONNECT: + unsetThreadNames(); + connectLock.lock(); + try { + handleDisconnect(); + } + finally { + connectLock.unlock(); + } + break; - case Event.DISCONNECT: - unsetThreadNames(); - connectLock.lock(); - try { - handleDisconnect(); - } - finally { - connectLock.unlock(); - } - break; + case Event.GET_PHYSICAL_ADDRESS: + return getPhysicalAddressFromCache((Address)evt.getArg()); - case Event.CONFIG: - if(log.isDebugEnabled()) log.debug("received CONFIG event: " + evt.getArg()); - handleConfigEvent((Map)evt.getArg()); - break; + case Event.GET_LOGICAL_PHYSICAL_MAPPINGS: + return logical_addr_cache.contents(); + + case Event.SET_PHYSICAL_ADDRESS: + Tuple tuple=(Tuple)evt.getArg(); + addPhysicalAddressToCache(tuple.getVal1(), tuple.getVal2()); + break; + + case Event.REMOVE_ADDRESS: + removeLogicalAddressFromCache((Address)evt.getArg()); + break; + + case Event.SET_LOCAL_ADDRESS: + if(!isSingleton()) + local_addr=(Address)evt.getArg(); + registerLocalAddress((Address)evt.getArg()); + break; } return null; } + /** + * Associates the address with the physical address fetched from the cache + * @param addr + * @return true if registered successfully, otherwise false (e.g. physical addr could not be fetched) + */ + protected void registerLocalAddress(Address addr) { + PhysicalAddress physical_addr=getPhysicalAddress(); + if(physical_addr != null && addr != null) + addPhysicalAddressToCache(addr, physical_addr); + } + /** + * Grabs the local address (or addresses in the shared transport case) and registers them with the physical address + * in the transport's cache + */ + protected void fetchLocalAddresses() { + if(!isSingleton()) { + if(local_addr != null) { + registerLocalAddress(local_addr); + } + else { + Address addr=(Address)up_prot.up(new Event(Event.GET_LOCAL_ADDRESS)); + local_addr=addr; + registerLocalAddress(addr); + } + } + else { + for(Protocol prot: up_prots.values()) { + Address addr=(Address)prot.up(new Event(Event.GET_LOCAL_ADDRESS)); + registerLocalAddress(addr); + } + } + } - protected void setThreadNames() { - if(diag_handler != null) { + protected void setThreadNames() { + if(diag_handler != null) global_thread_factory.renameThread(DiagnosticsHandler.THREAD_NAME, diag_handler.getThread()); + if(bundler instanceof TransferQueueBundler) { + global_thread_factory.renameThread(TransferQueueBundler.THREAD_NAME, + ((TransferQueueBundler)bundler).getThread()); } } @@ -1253,14 +1463,19 @@ protected void unsetThreadNames() { if(diag_handler != null && diag_handler.getThread() != null) diag_handler.getThread().setName(DiagnosticsHandler.THREAD_NAME); + if(bundler instanceof TransferQueueBundler) { + Thread thread=((TransferQueueBundler)bundler).getThread(); + if(thread != null) + global_thread_factory.renameThread(TransferQueueBundler.THREAD_NAME, thread); + } } - private void setInAllThreadFactories(String cluster_name, Address local_address, String pattern) { + protected void setInAllThreadFactories(String cluster_name, Address local_address, String pattern) { ThreadFactory[] factories= {timer_thread_factory, default_thread_factory, oob_thread_factory, global_thread_factory }; - + boolean is_shared_transport=isSingleton(); for(ThreadFactory factory:factories) { @@ -1276,15 +1491,6 @@ } } - - protected void handleConfigEvent(Map map) { - if(map == null) return; - if(map.containsKey("additional_data")) { - additional_data=(byte[])map.get("additional_data"); - if(local_addr instanceof IpAddress) - ((IpAddress)local_addr).setAdditionalData(additional_data); - } - } protected static ExecutorService createThreadPool(int min_threads, int max_threads, long keep_alive_time, String rejection_policy, @@ -1292,24 +1498,13 @@ ThreadPoolExecutor pool=new ThreadManagerThreadPoolExecutor(min_threads, max_threads, keep_alive_time, TimeUnit.MILLISECONDS, queue); pool.setThreadFactory(factory); - - //default - RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy(); - if(rejection_policy != null) { - if(rejection_policy.equals("abort")) - handler = new ThreadPoolExecutor.AbortPolicy(); - else if(rejection_policy.equals("discard")) - handler = new ThreadPoolExecutor.DiscardPolicy(); - else if(rejection_policy.equals("discardoldest")) - handler = new ThreadPoolExecutor.DiscardOldestPolicy(); - } + RejectedExecutionHandler handler=parseRejectionPolicy(rejection_policy); pool.setRejectedExecutionHandler(new ShutdownRejectedExecutionHandler(handler)); - return pool; } - private static void shutdownThreadPool(Executor thread_pool) { + protected static void shutdownThreadPool(Executor thread_pool) { if(thread_pool instanceof ExecutorService) { ExecutorService service=(ExecutorService)thread_pool; service.shutdownNow(); @@ -1320,14 +1515,28 @@ } } } - - private void verifyRejectionPolicy(String str) throws Exception{ + + protected void verifyRejectionPolicy(String str) throws Exception{ if(!(str.equalsIgnoreCase("run") || str.equalsIgnoreCase("abort")|| str.equalsIgnoreCase("discard")|| str.equalsIgnoreCase("discardoldest"))) { log.error("rejection policy of " + str + " is unknown"); throw new Exception("Unknown rejection policy " + str); } } + protected static RejectedExecutionHandler parseRejectionPolicy(String rejection_policy) { + if(rejection_policy == null) + throw new IllegalArgumentException("rejection policy is null"); + if(rejection_policy.equalsIgnoreCase("abort")) + return new ThreadPoolExecutor.AbortPolicy(); + if(rejection_policy.equalsIgnoreCase("discard")) + return new ThreadPoolExecutor.DiscardPolicy(); + if(rejection_policy.equalsIgnoreCase("discardoldest")) + return new ThreadPoolExecutor.DiscardOldestPolicy(); + if(rejection_policy.equalsIgnoreCase("run")) + return new ThreadPoolExecutor.CallerRunsPolicy(); + throw new IllegalArgumentException("rejection policy \"" + rejection_policy + "\" not known"); + } + protected void passToAllUpProtocols(Event evt) { for(Protocol prot: up_prots.values()) { @@ -1341,20 +1550,33 @@ } } - public void sendUpLocalAddressEvent() { - if(up_prot != null) - up(new Event(Event.SET_LOCAL_ADDRESS, local_addr)); - else { - for(Map.Entry entry: up_prots.entrySet()) { - String tmp=entry.getKey(); - if(tmp.startsWith(Global.DUMMY)) - continue; - Protocol prot=entry.getValue(); - prot.up(new Event(Event.SET_LOCAL_ADDRESS, local_addr)); - } + + + protected void addPhysicalAddressToCache(Address logical_addr, PhysicalAddress physical_addr) { + if(logical_addr != null && physical_addr != null) + logical_addr_cache.add(logical_addr, physical_addr); + } + + protected PhysicalAddress getPhysicalAddressFromCache(Address logical_addr) { + return logical_addr != null? logical_addr_cache.get(logical_addr) : null; + } + + protected void removeLogicalAddressFromCache(Address logical_addr) { + if(logical_addr != null) { + logical_addr_cache.remove(logical_addr); + fetchLocalAddresses(); } } + /** Clears the cache. Do not use, this is only for unit testing ! */ + public void clearLogicalAddressCache() { + logical_addr_cache.clear(true); + fetchLocalAddresses(); + } + + + protected abstract PhysicalAddress getPhysicalAddress(); + /* ----------------------------- End of Private Methods ---------------------------------------- */ @@ -1362,25 +1584,23 @@ /* ----------------------------- Inner Classes ---------------------------------------- */ class IncomingPacket implements Runnable { - final Address dest, sender; + final Address sender; final byte[] buf; final int offset, length; - IncomingPacket(Address dest, Address sender, byte[] buf, int offset, int length) { - this.dest=dest; + IncomingPacket(Address sender, byte[] buf, int offset, int length) { this.sender=sender; this.buf=buf; this.offset=offset; this.length=length; } - + /** Code copied from handleIncomingPacket */ public void run() { - short version=0; - boolean is_message_list, multicast; + short version; byte flags; - ExposedByteArrayInputStream in_stream=null; + ExposedByteArrayInputStream in_stream; DataInputStream dis=null; try { @@ -1403,18 +1623,18 @@ sb.append("Packet is discarded"); else sb.append("This may cause problems"); - log.warn(sb); + log.warn(sb.toString()); } if(discard_incompatible_packets) return; } flags=dis.readByte(); - is_message_list=(flags & LIST) == LIST; - multicast=(flags & MULTICAST) == MULTICAST; + boolean is_message_list=(flags & LIST) == LIST; + boolean multicast=(flags & MULTICAST) == MULTICAST; if(is_message_list) { // used if message bundling is enabled - List msgs=readMessageList(dis, dest, multicast); + List msgs=readMessageList(dis); for(Message msg: msgs) { if(msg.isFlagSet(Message.OOB)) { log.warn("bundled message should not be marked as OOB"); @@ -1423,7 +1643,7 @@ } } else { - Message msg=readMessage(dis, dest, sender, multicast); + Message msg=readMessage(dis); handleMyMessage(msg, multicast); } } @@ -1442,62 +1662,78 @@ num_msgs_received++; num_bytes_received+=msg.getLength(); } - - Address src=msg.getSrc(); - if(loopback && multicast && src != null && src.equals(local_addr)) { - return; // drop message that was already looped back and delivered - } - - passMessageUp(msg, true); + passMessageUp(msg, true, multicast, true); } } + protected interface Bundler { + void start(); + void stop(); + void send(Message msg) throws Exception; + } - - private class Bundler { + private class DefaultBundler implements Bundler { static final int MIN_NUMBER_OF_BUNDLING_TASKS=2; - /** HashMap>. Keys are destinations, values are lists of Messages */ - final Map> msgs=new HashMap>(36); + /** Keys are destinations, values are lists of Messages */ + final Map> msgs=new HashMap>(36); @GuardedBy("lock") long count=0; // current number of bytes accumulated int num_msgs=0; @GuardedBy("lock") int num_bundling_tasks=0; - long last_bundle_time; + long last_bundle_time; final ReentrantLock lock=new ReentrantLock(); - final ExposedByteArrayOutputStream bundler_out_stream=new ExposedByteArrayOutputStream(INITIAL_BUFSIZE); - final ExposedDataOutputStream bundler_dos=new ExposedDataOutputStream(bundler_out_stream); + final Log log=LogFactory.getLog(getClass()); + + public void start() { + } + public void stop() { + } - private void send(Message msg, Address dest) throws Exception { + public void send(Message msg) throws Exception { long length=msg.size(); + boolean do_schedule=false; checkLength(length); lock.lock(); try { if(count + length >= max_bundle_size) { - if(!msgs.isEmpty()) { - sendBundledMessages(msgs); - } + sendBundledMessages(msgs); } - addMessage(msg, dest); + addMessage(msg); count+=length; if(num_bundling_tasks < MIN_NUMBER_OF_BUNDLING_TASKS) { num_bundling_tasks++; - timer.schedule(new BundlingTimer(), max_bundle_timeout, TimeUnit.MILLISECONDS); + do_schedule=true; } } finally { lock.unlock(); } + + if(do_schedule) + timer.schedule(new BundlingTimer(), max_bundle_timeout, TimeUnit.MILLISECONDS); } /** Run with lock acquired */ - private void addMessage(Message msg, Address dest) { // no sync needed, always called with lock held + private void addMessage(Message msg) { + Address dst=msg.getDest(); + String cluster_name; + + if(!isSingleton()) + cluster_name=TP.this.channel_name; + else { + TpHeader hdr=(TpHeader)msg.getHeader(id); + cluster_name=hdr.channel_name; + } + + SingletonAddress dest=new SingletonAddress(cluster_name, dst); + if(msgs.isEmpty()) last_bundle_time=System.currentTimeMillis(); List tmp=msgs.get(dest); @@ -1515,11 +1751,7 @@ * This method may be called by timer and bundler concurrently * @param msgs */ - private void sendBundledMessages(final Map> msgs) { - boolean multicast; - Buffer buffer; - Address dst; - + private void sendBundledMessages(final Map> msgs) { if(log.isTraceEnabled()) { long stop=System.currentTimeMillis(); double percentage=100.0 / max_bundle_size * count; @@ -1534,21 +1766,27 @@ log.trace(sb); } - for(Map.Entry> entry: msgs.entrySet()) { + ExposedByteArrayOutputStream bundler_out_stream=new ExposedByteArrayOutputStream((int)(count + 50)); + ExposedDataOutputStream bundler_dos=new ExposedDataOutputStream(bundler_out_stream); + + for(Map.Entry> entry: msgs.entrySet()) { List list=entry.getValue(); if(list.isEmpty()) continue; - dst=entry.getKey(); - multicast=dst == null || dst.isMulticastAddress(); + SingletonAddress dst=entry.getKey(); + Address dest=dst.getAddress(); + Address src_addr=list.get(0).getSrc(); + + boolean multicast=dest == null || dest.isMulticastAddress(); try { bundler_out_stream.reset(); bundler_dos.reset(); - writeMessageList(list, bundler_dos, multicast); // flushes output stream when done - buffer=new Buffer(bundler_out_stream.getRawBuffer(), 0, bundler_out_stream.size()); - doSend(buffer, dst, multicast); + writeMessageList(dest, src_addr, list, bundler_dos, multicast); // flushes output stream when done + Buffer buffer=new Buffer(bundler_out_stream.getRawBuffer(), 0, bundler_out_stream.size()); + doSend(buffer, dest, multicast); } catch(Throwable e) { - if(log.isErrorEnabled()) log.error("exception sending msg: " + e.toString(), e.getCause()); + if(log.isErrorEnabled()) log.error("exception sending bundled msgs", e); } } msgs.clear(); @@ -1570,7 +1808,12 @@ lock.lock(); try { if(!msgs.isEmpty()) { - sendBundledMessages(msgs); + try { + sendBundledMessages(msgs); + } + catch(Exception e) { + log.error("failed sending bundled messages", e); + } } } finally { @@ -1578,9 +1821,188 @@ lock.unlock(); } } + + public String toString() { + return getClass().getSimpleName(); + } + } + } + + + + private class TransferQueueBundler implements Bundler, Runnable { + final int threshold; + final BlockingQueue buffer; + volatile Thread bundler_thread; + final Log log=LogFactory.getLog(getClass()); + + /** Keys are destinations, values are lists of Messages */ + final Map> msgs=new HashMap>(36); + long count=0; // current number of bytes accumulated + int num_msgs=0; + long next_bundle_time; + volatile boolean running=true; + public static final String THREAD_NAME="TransferQueueBundler"; + + + + public TransferQueueBundler(int capacity) { + if(capacity <=0) throw new IllegalArgumentException("Bundler capacity cannot be " + capacity); + buffer=new LinkedBlockingQueue(capacity); + threshold=(int)(capacity * .9); // 90% of capacity + } + + public void start() { + if(bundler_thread == null || !bundler_thread.isAlive()) { + bundler_thread=getThreadFactory().newThread(this, THREAD_NAME); + running=true; + bundler_thread.start(); + } + } + + public Thread getThread() {return bundler_thread;} + + public void stop() { + running=false; + if(bundler_thread != null) + bundler_thread.interrupt(); + } + + public void send(Message msg) throws Exception { + long length=msg.size(); + checkLength(length); + buffer.put(msg); + } + + public int getBufferSize() { + return buffer.size(); + } + + + + public void run() { + next_bundle_time=System.currentTimeMillis() + max_bundle_timeout; + while(running) { + Message msg=null; + long sleep_time=next_bundle_time - System.currentTimeMillis(); + + try { + if(count == 0) + msg=buffer.take(); + else + msg=buffer.poll(sleep_time, TimeUnit.MILLISECONDS); + + long size=msg != null? msg.size() : 0; + boolean send_msgs=(msg != null && count + size >= max_bundle_size) || + buffer.size() >= threshold || + System.currentTimeMillis() >= next_bundle_time; + + if(send_msgs) { + next_bundle_time=System.currentTimeMillis() + max_bundle_timeout; + try { + if(!msgs.isEmpty()) { + sendBundledMessages(msgs); + msgs.clear(); + } + count=0; + } + catch(Exception e) { + log.error("failed sending bundled messages: " + e.getMessage()); + } + } + + if(msg != null) { + count+=size; + addMessage(msg); + } + } + catch(Throwable t) { + } + } + } + + + private void checkLength(long len) throws Exception { + if(len > max_bundle_size) + throw new Exception("message size (" + len + ") is greater than max bundling size (" + max_bundle_size + + "). Set the fragmentation/bundle size in FRAG and TP correctly"); + } + + + private void addMessage(Message msg) { + Address dst=msg.getDest(); + String cluster_name; + + if(!isSingleton()) + cluster_name=TP.this.channel_name; + else { + TpHeader hdr=(TpHeader)msg.getHeader(id); + cluster_name=hdr.channel_name; + } + + SingletonAddress dest=new SingletonAddress(cluster_name, dst); + + List tmp=msgs.get(dest); + if(tmp == null) { + tmp=new LinkedList(); + msgs.put(dest, tmp); + } + tmp.add(msg); + num_msgs++; + } + + + + /** + * Sends all messages from the map, all messages for the same destination are bundled into 1 message. + * This method may be called by timer and bundler concurrently + * @param msgs + */ + private void sendBundledMessages(final Map> msgs) { + boolean multicast; + + if(log.isTraceEnabled()) { + double percentage=100.0 / max_bundle_size * count; + StringBuilder sb=new StringBuilder("sending ").append(num_msgs).append(" msgs ("); + sb.append(count).append(" bytes (" + f.format(percentage) + "% of max_bundle_size)"); + sb.append(" to ").append(msgs.size()).append(" destination(s)"); + if(msgs.size() > 1) sb.append(" (dests=").append(msgs.keySet()).append(")"); + log.trace(sb); + num_msgs=0; + } + + ExposedByteArrayOutputStream bundler_out_stream=new ExposedByteArrayOutputStream((int)(count + 50)); + ExposedDataOutputStream bundler_dos=new ExposedDataOutputStream(bundler_out_stream); + + for(Map.Entry> entry: msgs.entrySet()) { + List list=entry.getValue(); + if(list.isEmpty()) + continue; + + SingletonAddress dst=entry.getKey(); + Address dest=dst.getAddress(); + Address src_addr=list.get(0).getSrc(); + + multicast=dest == null || dest.isMulticastAddress(); + try { + bundler_out_stream.reset(); + bundler_dos.reset(); + writeMessageList(dest, src_addr, list, bundler_dos, multicast); // flushes output stream when done + Buffer buf=new Buffer(bundler_out_stream.getRawBuffer(), 0, bundler_out_stream.size()); + doSend(buf, dest, multicast); + } + catch(Throwable e) { + if(log.isErrorEnabled()) log.error("exception sending bundled msgs: " + e + ":, cause: " + e.getCause()); + } + } } + + } + + + public interface ProbeHandler { /** * Handles a probe. For each key that is handled, the key and its result should be in the returned map. @@ -1593,12 +2015,26 @@ String[] supportedKeys(); } +// /** +// * Maps UUIDs to physical addresses +// */ +// public interface AddressMapper { +// /** +// * Given a UUID, pick one physical address from a list. If the UUID is null, the message needs to be sent to +// * the entire cluster. In UDP, for example, we would pick a multicast address +// * @param uuid The UUID. Null for a cluster wide destination +// * @param physical_addrs A list of physical addresses +// * @return an address from the list +// */ +// Address pick(UUID uuid, List
      physical_addrs); +// } + - private class DiagnosticsHandler implements Runnable { - public static final String THREAD_NAME = "DiagnosticsHandler"; + protected class DiagnosticsHandler implements Runnable { + public static final String THREAD_NAME = "DiagnosticsHandler"; private Thread thread=null; private MulticastSocket diag_sock=null; - private final Set handlers=new HashSet(); + private final Set handlers=new CopyOnWriteArraySet(); DiagnosticsHandler() { } @@ -1628,6 +2064,12 @@ retval.put("dump", Util.dumpThreads()); continue; } + if(key.equals("uuids")) { + retval.put("uuids", printLogicalAddressCache()); + if(!isSingleton() && !retval.containsKey("local_addr")) + retval.put("local_addr", local_addr != null? local_addr.toString() : null); + continue; + } if(key.equals("keys")) { StringBuilder sb=new StringBuilder(); for(ProbeHandler handler: handlers) { @@ -1639,22 +2081,33 @@ } retval.put("keys", sb.toString()); } + if(key.equals("info")) { + if(singleton_name != null && singleton_name.length() > 0) + retval.put("singleton_name", singleton_name); + + } } return retval; } public String[] supportedKeys() { - return new String[]{"dump", "keys"}; + return new String[]{"dump", "keys", "uuids", "info"}; } }); - diag_sock=new MulticastSocket(diagnostics_port); - // diag_sock=Util.createMulticastSocket(null, diagnostics_port, log); + // https://jira.jboss.org/jira/browse/JGRP-777 - this doesn't work on MacOS, and we don't have + // cross talking on Windows anyway, so we just do it for Linux. (How about Solaris ?) + if(can_bind_to_mcast_addr) + diag_sock=Util.createMulticastSocket(getSocketFactory(), + Global.TP_DIAG_MCAST_SOCK, diagnostics_addr, diagnostics_port, log); + else + diag_sock=getSocketFactory().createMulticastSocket(Global.TP_DIAG_MCAST_SOCK, diagnostics_port); + List interfaces=Util.getAllAvailableInterfaces(); bindToInterfaces(interfaces, diag_sock); if(thread == null || !thread.isAlive()) { - thread=global_thread_factory.newThread(this, THREAD_NAME); + thread=global_thread_factory.newThread(this, THREAD_NAME); thread.setDaemon(true); thread.start(); } @@ -1662,7 +2115,7 @@ void stop() { if(diag_sock != null) - diag_sock.close(); + getSocketFactory().close(diag_sock); handlers.clear(); if(thread != null){ try{ @@ -1684,7 +2137,11 @@ handleDiagnosticProbe(packet.getSocketAddress(), diag_sock, new String(packet.getData(), packet.getOffset(), packet.getLength())); } - catch(IOException e) { + catch(IOException socket_ex) { + } + catch(Throwable e) { + if(log.isErrorEnabled()) + log.error("failure handling diagnostics request", e); } } } @@ -1708,8 +2165,6 @@ Map map=handler.handleProbe(tokens); if(map == null || map.isEmpty()) continue; - if(!map.containsKey("local_addr")) - map.put("local_addr", local_addr != null? local_addr.toString() : "n/a"); if(!map.containsKey("cluster")) map.put("cluster", channel_name != null? channel_name : "n/a"); StringBuilder info=new StringBuilder(); @@ -1753,48 +2208,98 @@ } } - public static class ProtocolAdapter extends Protocol { - final String cluster_name; - final String transport_name; - final TpHeader header; - final List
      members=new CopyOnWriteArrayList
      (); - final ThreadFactory factory; + /** + * Used when the transport is shared (singleton_name is not null). Maintains the cluster name, local address and + * view + */ + public static class ProtocolAdapter extends Protocol implements ProbeHandler { + String cluster_name; + final short transport_id; + TpHeader header; + final Set
      members=new CopyOnWriteArraySet
      (); + final ThreadFactory factory; + protected SocketFactory socket_factory=new DefaultSocketFactory(); + Address local_addr; + + // TODO [JGRP-1194] - Revisit implementation of TUNNEL and shared transport + static final ThreadLocal thread_local=new ThreadLocal(); - public ProtocolAdapter(String cluster_name, String transport_name, Protocol up, Protocol down, String pattern, Address addr) { + public ProtocolAdapter(String cluster_name, Address local_addr, short transport_id, Protocol up, Protocol down, String pattern) { this.cluster_name=cluster_name; - this.transport_name=transport_name; + this.local_addr=local_addr; + this.transport_id=transport_id; this.up_prot=up; this.down_prot=down; this.header=new TpHeader(cluster_name); this.factory=new DefaultThreadFactory(Util.getGlobalThreadGroup(), "", false); factory.setPattern(pattern); - if(addr != null) - factory.setAddress(addr.toString()); + if(local_addr != null) + factory.setAddress(local_addr.toString()); + if(cluster_name != null) + factory.setClusterName(cluster_name); } @ManagedAttribute(description="Name of the cluster to which this adapter proxies") - public String getCluster_name() { + public String getClusterName() { return cluster_name; } - @ManagedAttribute(description="Name of the transport") - public String getTransport_name() { - return transport_name; + + public Address getAddress() { + return local_addr; + } + + @ManagedAttribute(name="Address", description="local address") + public String getAddressAsString() { + return local_addr != null? local_addr.toString() : null; + } + + @ManagedAttribute(name="AddressUUID", description="local address") + public String getAddressAsUUID() { + return (local_addr instanceof UUID)? ((UUID)local_addr).toStringLong() : null; } - public List
      getMembers() { - return Collections.unmodifiableList(members); + @ManagedAttribute(description="ID of the transport") + public short getTransportName() { + return transport_id; + } + + public Set
      getMembers() { + return Collections.unmodifiableSet(members); } public ThreadFactory getThreadFactory() { return factory; } + public SocketFactory getSocketFactory() { + return socket_factory; + } + + public void setSocketFactory(SocketFactory factory) { + if(factory != null) + socket_factory=factory; + } + + public void start() throws Exception { + TP tp=getTransport(); + if(tp != null) + tp.registerProbeHandler(this); + } + + public void stop() { + TP tp=getTransport(); + if(tp != null) + tp.unregisterProbeHandler(this); + } + public Object down(Event evt) { switch(evt.getType()) { case Event.MSG: Message msg=(Message)evt.getArg(); - msg.putHeader(transport_name, header); + msg.putHeader(transport_id, header); + if(msg.getSrc() == null) + msg.setSrc(local_addr); break; case Event.VIEW_CHANGE: View view=(View)evt.getArg(); @@ -1802,23 +2307,29 @@ members.clear(); members.addAll(tmp); break; + case Event.DISCONNECT: + // TODO [JGRP-1194] - Revisit implementation of TUNNEL and shared transport + thread_local.set(this); + break; case Event.CONNECT: case Event.CONNECT_WITH_STATE_TRANSFER: - factory.setClusterName((String)evt.getArg()); + case Event.CONNECT_USE_FLUSH: + case Event.CONNECT_WITH_STATE_TRANSFER_USE_FLUSH: + // TODO [JGRP-1194] - Revisit implementation of TUNNEL and shared transport + thread_local.set(this); + cluster_name=(String)evt.getArg(); + factory.setClusterName(cluster_name); + this.header=new TpHeader(cluster_name); break; - } - return down_prot.down(evt); - } - - public Object up(Event evt) { - switch(evt.getType()) { case Event.SET_LOCAL_ADDRESS: Address addr=(Address)evt.getArg(); - if(addr != null) - factory.setAddress(addr.toString()); + if(addr != null) { + local_addr=addr; + factory.setAddress(addr.toString()); // used for thread naming + } break; } - return up_prot.up(evt); + return down_prot.down(evt); } public String getName() { @@ -1826,7 +2337,20 @@ } public String toString() { - return cluster_name + " (" + transport_name + ")"; + return cluster_name + " (" + transport_id + ")"; + } + + public Map handleProbe(String... keys) { + HashMap retval=new HashMap(); + retval.put("cluster", cluster_name); + retval.put("local_addr", local_addr != null? local_addr.toString() : null); + retval.put("local_addr (UUID)", local_addr instanceof UUID? ((UUID)local_addr).toStringLong() : null); + retval.put("transport_id", Short.toString(transport_id)); + return retval; + } + + public String[] supportedKeys() { + return null; } } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/TRACE.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/TRACE.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/TRACE.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/TRACE.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: TRACE.java,v 1.7 2008/10/21 12:10:30 vlada Exp $ package org.jgroups.protocols; import org.jgroups.Event; @@ -11,10 +10,6 @@ public TRACE() {} - public String getName() {return "TRACE";} - - - public Object up(Event evt) { System.out.println("---------------- TRACE (received) ----------------------"); System.out.println(evt); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/TransportedVectorTime.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/TransportedVectorTime.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/TransportedVectorTime.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/TransportedVectorTime.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: TransportedVectorTime.java,v 1.8 2008/01/22 10:44:30 belaban Exp $ package org.jgroups.protocols; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/TUNNEL.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/TUNNEL.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/TUNNEL.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/TUNNEL.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,285 +1,433 @@ -// $Id: TUNNEL.java,v 1.51 2008/10/31 08:38:44 belaban Exp $ package org.jgroups.protocols; +import java.io.DataInputStream; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + import org.jgroups.Address; import org.jgroups.Event; +import org.jgroups.Global; import org.jgroups.Message; -import org.jgroups.annotations.GuardedBy; +import org.jgroups.PhysicalAddress; +import org.jgroups.annotations.Experimental; import org.jgroups.annotations.Property; -import org.jgroups.stack.RouterStub; +import org.jgroups.stack.GossipData; +import org.jgroups.stack.GossipRouter; +import org.jgroups.stack.RouterStubManager; import org.jgroups.stack.IpAddress; +import org.jgroups.stack.Protocol; +import org.jgroups.stack.RouterStub; +import org.jgroups.util.Buffer; +import org.jgroups.util.ExposedByteArrayOutputStream; +import org.jgroups.util.ExposedDataOutputStream; import org.jgroups.util.Util; -import java.io.DataInputStream; -import java.io.IOException; -import java.net.SocketException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - /** - * Replacement for UDP. Instead of sending packets via UDP, a TCP connection is - * opened to a Router (using the RouterStub client-side stub), the IP - * address/port of which was given using channel properties - * router_host and router_port. All outgoing - * traffic is sent via this TCP socket to the Router which distributes it to all - * connected TUNNELs in this group. Incoming traffic received from Router will - * simply be passed up the stack. + * Replacement for UDP. Instead of sending packets via UDP, a TCP connection is opened to a Router + * (using the RouterStub client-side stub), the IP address/port of which was given using channel + * properties router_host and router_port. All outgoing traffic is sent + * via this TCP socket to the Router which distributes it to all connected TUNNELs in this group. + * Incoming traffic received from Router will simply be passed up the stack. * *

      - * A TUNNEL layer can be used to penetrate a firewall, most firewalls allow - * creating TCP connections to the outside world, however, they do not permit - * outside hosts to initiate a TCP connection to a host inside the firewall. - * Therefore, the connection created by the inside host is reused by Router to - * send traffic from an outside host to a host inside the firewall. + * A TUNNEL layer can be used to penetrate a firewall, most firewalls allow creating TCP connections + * to the outside world, however, they do not permit outside hosts to initiate a TCP connection to a + * host inside the firewall. Therefore, the connection created by the inside host is reused by + * Router to send traffic from an outside host to a host inside the firewall. * * @author Bela Ban + * @author Vladimir Blagojevic */ +@Experimental public class TUNNEL extends TP { - - /* ----------------------------------------- Properties -------------------------------------------------- */ - - @Property(description="Router host address") - private String router_host = null; - - @Property(description="Router port") - private int router_port = 0; - @Property(description="Interval in msec to attempt connecting back to router in case of torn connection. Default is 5000 msec") - private long reconnect_interval = 5000; - - - - /* --------------------------------------------- Fields ------------------------------------------------------ */ - - - private RouterStub stub; - /* - * flag indicating if tunnel was destroyed intentionally (disconnect, channel destroy etc) - */ - private volatile boolean intentionallyTornDown = false; - - /** time to wait in ms between reconnect attempts */ - - @GuardedBy("reconnectorLock") - private Future reconnectorFuture = null; - - private final Lock reconnectorLock = new ReentrantLock(); + * ----------------------------------------- Properties + * -------------------------------------------------- + */ - public TUNNEL(){} + @Deprecated + @Property(name = "router_host", deprecatedMessage = "router_host is deprecated. Specify target GRs using gossip_router_hosts", description = "Router host address") + private String router_host = null; - public String toString() { - return "Protocol TUNNEL(local_addr=" + local_addr + ')'; - } + @Deprecated + @Property(name = "router_port", deprecatedMessage = "router_port is deprecated. Specify target GRs using gossip_router_hosts", description = "Router port") + private int router_port = 0; - public String getRouterHost() { - return router_host; - } - - public void setRouterHost(String router_host) { - this.router_host=router_host; - } + @Property(description = "Interval in msec to attempt connecting back to router in case of torn connection. Default is 5000 msec") + private long reconnect_interval = 5000; - public int getRouterPort() { - return router_port; - } + @Property(description="Should TCP no delay flag be turned on") + boolean tcp_nodelay=false; - public void setRouterPort(int router_port) { - this.router_port=router_port; - } + /* + * --------------------------------------------- Fields + * ------------------------------------------------------ + */ + + private final List gossip_router_hosts = new ArrayList(); + + private TUNNELPolicy tunnel_policy = new DefaultTUNNELPolicy(); + + private DatagramSocket sock; + + private volatile RouterStubManager stubManager; + + public TUNNEL() { + } + + public boolean supportsMulticasting() { + return false; + } + + @Property + public void setGossipRouterHosts(String hosts) throws UnknownHostException { + gossip_router_hosts.clear(); + // if we get passed value of List#toString() we have to strip [] + if (hosts.startsWith("[") && hosts.endsWith("]")) { + hosts = hosts.substring(1, hosts.length() - 1); + } + gossip_router_hosts.addAll(Util.parseCommaDelimitedHosts2(hosts, 1)); + } + + public String toString() { + return "TUNNEL"; + } + + @Deprecated + public String getRouterHost() { + return router_host; + } + + @Deprecated + public void setRouterHost(String router_host) { + this.router_host = router_host; + } + + @Deprecated + public int getRouterPort() { + return router_port; + } + + @Deprecated + public void setRouterPort(int router_port) { + this.router_port = router_port; + } + + public long getReconnectInterval() { + return reconnect_interval; + } + + public void setReconnectInterval(long reconnect_interval) { + this.reconnect_interval = reconnect_interval; + } + + /*------------------------------ Protocol interface ------------------------------ */ + + public synchronized void setTUNNELPolicy(TUNNELPolicy policy) { + if (policy == null) + throw new IllegalArgumentException("Tunnel policy has to be non null"); + tunnel_policy = policy; + } - public long getReconnectInterval() { - return reconnect_interval; - } + public void init() throws Exception { + super.init(); - public void setReconnectInterval(long reconnect_interval) { - this.reconnect_interval=reconnect_interval; - } + if(enable_bundling) { + log.warn("bundling is currently not supported by TUNNEL; bundling is disabled"); + enable_bundling=false; + } - /*------------------------------ Protocol interface ------------------------------ */ + if (timer == null) + throw new Exception("timer cannot be retrieved from protocol stack"); + + // Postpone TUNNEL and shared transport until 3.0 timeframe + // TODO [JGRP-1194] - Revisit implementation of TUNNEL and shared transport + if (isSingleton()) { + throw new Exception("TUNNEL and shared transport mode are not supported!"); + } - public String getName() { - return "TUNNEL"; - } + if ((router_host == null || router_port == 0) && gossip_router_hosts.isEmpty()) { + throw new Exception("either router_host and router_port have to be set or a list of gossip routers"); + } - public void init() throws Exception { - super.init(); - if(timer == null) - throw new Exception("TUNNEL.init(): timer cannot be retrieved from protocol stack"); - - if(log.isDebugEnabled()){ - log.debug("router_host=" + router_host + ";router_port=" + router_port); + if (router_host != null && router_port != 0 && !gossip_router_hosts.isEmpty()) { + throw new Exception("cannot specify both router host and port along with gossip_router_hosts"); } - if(router_host == null || router_port == 0){ - throw new Exception("both router_host and router_port have to be set !"); + if (router_host != null && router_port != 0 && gossip_router_hosts.isEmpty()) { + gossip_router_hosts.add(new InetSocketAddress(router_host, router_port)); } - } - public void start() throws Exception { + if (log.isDebugEnabled()) { + log.debug("GossipRouters are:" + gossip_router_hosts.toString()); + } + + stubManager = RouterStubManager.emptyGossipClientStubManager(this); + sock = getSocketFactory().createDatagramSocket(Global.TUNNEL_UCAST_SOCK, bind_port, bind_addr); + // loopback turned on is mandatory loopback = true; - intentionallyTornDown = false; - - stub = new RouterStub(router_host, router_port, bind_addr); - stub.setConnectionListener(new StubConnectionListener()); - local_addr = stub.getLocalAddress(); - if(additional_data != null && local_addr instanceof IpAddress) - ((IpAddress) local_addr).setAdditionalData(additional_data); - super.start(); - } - - public void stop() { - teardownTunnel(); - super.stop(); - local_addr = null; } - - /** Tears the TCP connection to the router down */ - void teardownTunnel() { - intentionallyTornDown = true; - stopReconnecting(); - stub.disconnect(); - } - - public Object handleDownEvent(Event evt) { - Object retEvent = super.handleDownEvent(evt); - switch(evt.getType()){ - case Event.CONNECT: - case Event.CONNECT_WITH_STATE_TRANSFER: - try{ - stub.connect(channel_name); - }catch(Exception e){ - if(log.isErrorEnabled()) - log.error("failed connecting to GossipRouter at " + router_host - + ":" - + router_port); - startReconnecting(); - } + + public void destroy() { + stubManager.destroyStubs(); + super.destroy(); + } + + private void disconnectStub(String group, Address addr) { + stubManager.disconnectStubs(); + } + + public Object handleDownEvent(Event evt) { + Object retEvent = super.handleDownEvent(evt); + switch (evt.getType()) { + case Event.CONNECT: + case Event.CONNECT_WITH_STATE_TRANSFER: + case Event.CONNECT_USE_FLUSH: + case Event.CONNECT_WITH_STATE_TRANSFER_USE_FLUSH: + String group=(String)evt.getArg(); + Address local= null; + if(!isSingleton()) { + local = local_addr; + } else { + // TODO [JGRP-1194] - Revisit implementation of TUNNEL and shared transport + ProtocolAdapter adapter = ProtocolAdapter.thread_local.get(); + local = adapter.local_addr; + } + + if(stubManager != null){ + stubManager.destroyStubs(); + } + stubManager = new TUNNELStubManager(this,group,local,getReconnectInterval()); + for (InetSocketAddress gr : gossip_router_hosts) { + RouterStub stub = stubManager.createAndRegisterStub(gr.getHostName(), gr.getPort(), bind_addr); + stub.setTcpNoDelay(tcp_nodelay); + } + PhysicalAddress physical_addr=(PhysicalAddress)down(new Event(Event.GET_PHYSICAL_ADDRESS, local)); + List physical_addrs=Arrays.asList(physical_addr); + String logical_name=org.jgroups.util.UUID.get(local); + List stubs = stubManager.getStubs(); + tunnel_policy.connect(stubs, group, local, logical_name, physical_addrs); break; - case Event.DISCONNECT: - teardownTunnel(); + case Event.DISCONNECT: + if(!isSingleton()) { + local = local_addr; + group = channel_name; + } else { + // TODO [JGRP-1194] - Revisit implementation of TUNNEL and shared transport + ProtocolAdapter adapter = ProtocolAdapter.thread_local.get(); + local = adapter.local_addr; + group = adapter.cluster_name; + } + disconnectStub(group,local); break; - } - return retEvent; - } + } + return retEvent; + } + private class TUNNELStubManager extends RouterStubManager { - private void startReconnecting() { - reconnectorLock.lock(); - try{ - if(reconnectorFuture == null || reconnectorFuture.isDone()){ - final Runnable reconnector = new Runnable() { - public void run() { - try{ - if(!intentionallyTornDown){ - if(log.isDebugEnabled()){ - log.debug("Reconnecting " + getLocalAddress() - + " to router at " - + router_host - + ":" - + router_port); - } - stub.connect(channel_name); - } - }catch(Exception ex){ - if(log.isTraceEnabled()) - log.trace("failed reconnecting", ex); - } - } - }; - reconnectorFuture = timer.scheduleWithFixedDelay(reconnector, - 0, - reconnect_interval, - TimeUnit.MILLISECONDS); - } - }finally{ - reconnectorLock.unlock(); + TUNNELStubManager(Protocol owner, String channelName, Address logicalAddress, long interval) { + super(owner, channelName, logicalAddress, interval); } - } - private void stopReconnecting() { - reconnectorLock.lock(); - try{ - if(reconnectorFuture != null){ - reconnectorFuture.cancel(true); - reconnectorFuture = null; - } - }finally{ - reconnectorLock.unlock(); + public void connectionStatusChange(RouterStub stub, RouterStub.ConnectionStatus newState) { + super.connectionStatusChange(stub, newState); + if (newState == RouterStub.ConnectionStatus.CONNECTED) { + StubReceiver stubReceiver = new StubReceiver(stub); + stub.setReceiver(stubReceiver); + Thread t = global_thread_factory.newThread(stubReceiver, "TUNNEL receiver for " + stub.toString()); + stubReceiver.setThread(t); + t.setDaemon(true); + t.start(); + } } } - private class StubConnectionListener implements RouterStub.ConnectionListener { + public class StubReceiver implements Runnable { + + private Thread runner; + private final RouterStub stub; + + public StubReceiver(RouterStub stub) { + this.stub = stub; + } - private volatile int currentState = RouterStub.STATUS_DISCONNECTED; + public synchronized void setThread(Thread t) { + runner = t; + } - public void connectionStatusChange(int newState) { - if(newState == RouterStub.STATUS_DISCONNECTED){ - startReconnecting(); - }else if(currentState != RouterStub.STATUS_CONNECTED && newState == RouterStub.STATUS_CONNECTED){ - stopReconnecting(); - Thread t = global_thread_factory.newThread(new TunnelReceiver(), "TUNNEL receiver"); - t.setDaemon(true); - t.start(); - } - currentState = newState; + public synchronized Thread getThread() { + return runner; } - } - private class TunnelReceiver implements Runnable { public void run() { - while(stub.isConnected()){ - Address dest = null; - int len; - byte[] data = null; - DataInputStream input = null; - try{ - input = stub.getInputStream(); - dest = Util.readAddress(input); - len = input.readInt(); - if(len > 0){ - data = new byte[len]; - input.readFully(data, 0, len); - receive(dest, null/*src will be read from data*/, data, 0, len); + final DataInputStream input = stub.getInputStream(); + mainloop: + while (!Thread.currentThread().isInterrupted()) { + try { + GossipData msg = new GossipData(); + msg.readFrom(input); + switch (msg.getType()) { + case GossipRouter.DISCONNECT_OK: + break mainloop; + case GossipRouter.MESSAGE: + byte[] data = msg.getBuffer(); + receive(null/* src will be read from data */, data, 0, data.length); + break; + case GossipRouter.SUSPECT: + final Address suspect = Util.readAddress(input); + log.debug("Firing suspect event " + suspect + " at " + local_addr); + if(suspect != null) { + // https://jira.jboss.org/jira/browse/JGRP-902 + Thread thread = getThreadFactory().newThread(new Runnable() { + public void run() { + fireSuspectEvent(suspect); + } + }, "StubReceiver-suspect"); + thread.start(); + } + break; } - }catch(SocketException se){ - // if(log.isWarnEnabled()) log.warn("failure in TUNNEL - // receiver thread", se); - }catch(IOException ioe){ - // if(log.isWarnEnabled()) log.warn("failure in TUNNEL - // receiver thread", ioe); - }catch(Exception e){ - if(log.isWarnEnabled()) - log.warn("failure in TUNNEL receiver thread", e); - } + }catch (Exception ioe) { + if(stub.isConnected()) + continue mainloop; + else + break; + } } } - } - public void sendToAllMembers(byte[] data, int offset, int length) throws Exception { - stub.sendToAllMembers(data, offset, length); + private void fireSuspectEvent(Address suspect) { + up(new Event(Event.SUSPECT, suspect)); + } } - public void sendToSingleMember(Address dest, byte[] data, int offset, int length) throws Exception { - stub.sendToSingleMember(dest, data, offset, length); - } - public String getInfo() { - if(stub != null) - return stub.toString(); - else - return "RouterStub not yet initialized"; - } + protected void send(Message msg, Address dest, boolean multicast) throws Exception { - public void postUnmarshalling(Message msg, Address dest, Address src, boolean multicast) { - msg.setDest(dest); + // we don't currently support message bundling in TUNNEL + TpHeader hdr=(TpHeader)msg.getHeader(this.id); + if(hdr == null) + throw new Exception("message " + msg + " doesn't have a transport header, cannot route it"); + String group=hdr.channel_name; + + ExposedByteArrayOutputStream out_stream=new ExposedByteArrayOutputStream((int)(msg.size() + 50)); + ExposedDataOutputStream dos=new ExposedDataOutputStream(out_stream); + + writeMessage(msg, dos, multicast); + Buffer buf=new Buffer(out_stream.getRawBuffer(), 0, out_stream.size()); + + if(stats) { + num_msgs_sent++; + num_bytes_sent+=buf.getLength(); + } + List stubs = stubManager.getStubs(); + if(multicast) { + tunnel_policy.sendToAllMembers(stubs, group, buf.getBuf(), buf.getOffset(), buf.getLength()); + } + else { + tunnel_policy.sendToSingleMember(stubs, group, dest, buf.getBuf(), buf.getOffset(), buf.getLength()); + } } - public void postUnmarshallingList(Message msg, Address dest, boolean multicast) { - msg.setDest(dest); + + public void sendMulticast(byte[] data, int offset, int length) throws Exception { + throw new UnsupportedOperationException("sendMulticast() should not get called on TUNNEL"); } + + public void sendUnicast(PhysicalAddress dest, byte[] data, int offset, int length) throws Exception { + throw new UnsupportedOperationException("sendUnicast() should not get called on TUNNEL"); + } + + public String getInfo() { + List stubs = stubManager.getStubs(); + if (stubs.isEmpty()) + return stubs.toString(); + else + return "RouterStubs not yet initialized"; + } + + protected PhysicalAddress getPhysicalAddress() { + return sock != null ? new IpAddress(bind_addr, sock.getLocalPort()) : null; + } + + public interface TUNNELPolicy { + public void connect(List stubs, String group, Address addr, String logical_name, List phys_addrs); + + public void sendToAllMembers(List stubs, String group, byte[] data, int offset, int length) throws Exception; + + public void sendToSingleMember(List stubs, String group, Address dest, byte[] data, int offset, + int length) throws Exception; + } + + private class DefaultTUNNELPolicy implements TUNNELPolicy { + + public void sendToAllMembers(List stubs, String group, byte[] data, int offset, int length) + throws Exception { + boolean sent = false; + if(stubs.size() > 1) + Collections.shuffle(stubs); // todo: why is this needed ? + for (RouterStub stub : stubs) { + try { + if(!stub.isConnected()) + continue; + stub.sendToAllMembers(group, data, offset, length); + if (log.isTraceEnabled()) + log.trace("sent a message to all members, GR used " + stub.getGossipRouterAddress()); + sent = true; + break; + } catch (Exception e) { + if (log.isWarnEnabled()) + log.warn("failed sending a message to all members, GR used " + stub.getGossipRouterAddress()); + } + } + if (!sent) + throw new Exception("None of the available stubs " + stubs + " accepted a multicast message"); + } + + public void sendToSingleMember(List stubs, String group, Address dest, byte[] data, int offset, int length) throws Exception { + boolean sent = false; + if(stubs.size() > 1) + Collections.shuffle(stubs); + for (RouterStub stub : stubs) { + try { + if(!stub.isConnected()) + continue; + stub.sendToMember(group, dest, data, offset, length); + if (log.isDebugEnabled()) + log.debug("sent a message to " + dest + ", GR used " + stub.getGossipRouterAddress()); + sent = true; + break; + } catch (Exception e) { + if (log.isWarnEnabled()) { + log.warn("failed sending a message to " + dest + ", GR used " + stub.getGossipRouterAddress()); + } + } + } + if (!sent) + throw new Exception("None of the available stubs " + stubs + + " accepted a message for dest " + dest); + } + + public void connect(List stubs, String group, Address addr, String logical_name, List phys_addrs) { + for (RouterStub stub : stubs) { + try { + stub.connect(group, addr, logical_name, phys_addrs); + } + catch (Exception e) { + if (log.isWarnEnabled()) + log.warn("Failed connecting to GossipRouter at " + stub.getGossipRouterAddress()); + stubManager.startReconnecting(stub); + } + } + } + } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/UDP.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/UDP.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/UDP.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/UDP.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,18 +1,15 @@ package org.jgroups.protocols; -import org.jgroups.Address; import org.jgroups.Global; -import org.jgroups.Message; +import org.jgroups.PhysicalAddress; import org.jgroups.annotations.DeprecatedProperty; import org.jgroups.annotations.Property; import org.jgroups.stack.IpAddress; -import org.jgroups.util.BoundedList; import org.jgroups.util.Util; import java.io.IOException; import java.net.*; -import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -42,25 +39,10 @@ * * * @author Bela Ban - * @version $Id: UDP.java,v 1.195 2009/01/05 08:24:06 belaban Exp $ */ @DeprecatedProperty(names={"num_last_ports","null_src_addresses", "send_on_all_interfaces", "send_interfaces"}) public class UDP extends TP { - - /** - * BoundedList of the last 100 ports used. This is to avoid - * reusing a port for DatagramSocket - */ - private static final BoundedList last_ports_used=new BoundedList(100); - - private static final boolean can_bind_to_mcast_addr; // are we running on Linux ? - - - static { - can_bind_to_mcast_addr=Util.checkForLinux() || Util.checkForSolaris(); - } - /* ------------------------------------------ Properties ------------------------------------------ */ /** @@ -74,41 +56,46 @@ * */ @Property(description="Traffic class for sending unicast and multicast datagrams. Default is 8") - private int tos=8; // valid values: 2, 4, 8 (default), 16 - - @Property(name="mcast_addr", description="The multicast address used for sending and receiving packets. Default is 228.8.8.8") - private String mcast_addr_name="228.8.8.8"; - - @Property(description="The multicast port used for sending and receiving packets. Default is 7600") - private int mcast_port=7600; + protected int tos=8; // valid values: 2, 4, 8 (default), 16 - @Property(description="Multicast toggle. If false multiple unicast datagrams are sent instead of one multicast. Default is true") - private boolean ip_mcast=true; + @Property(name="mcast_addr", description="The multicast address used for sending and receiving packets. Default is 228.8.8.8", + defaultValueIPv4="228.8.8.8", defaultValueIPv6="ff0e::8:8:8", + systemProperty=Global.UDP_MCAST_ADDR,writable=false) + protected InetAddress mcast_group_addr=null; + + @Property(description="The multicast port used for sending and receiving packets. Default is 7600", + systemProperty=Global.UDP_MCAST_PORT, writable=false) + protected int mcast_port=7600; + + @Property(description="Multicast toggle. If false multiple unicast datagrams are sent instead of one multicast. " + + "Default is true", writable=false) + protected boolean ip_mcast=true; - @Property(description="The time-to-live (TTL) for multicast datagram packets. Default is 8") - private int ip_ttl=8; + @Property(description="The time-to-live (TTL) for multicast datagram packets. Default is 8",systemProperty=Global.UDP_IP_TTL) + protected int ip_ttl=8; @Property(description="Send buffer size of the multicast datagram socket. Default is 100'000 bytes") - private int mcast_send_buf_size=100000; + protected int mcast_send_buf_size=100000; @Property(description="Receive buffer size of the multicast datagram socket. Default is 500'000 bytes") - private int mcast_recv_buf_size=500000; + protected int mcast_recv_buf_size=500000; @Property(description="Send buffer size of the unicast datagram socket. Default is 100'000 bytes") - private int ucast_send_buf_size=100000; + protected int ucast_send_buf_size=100000; @Property(description="Receive buffer size of the unicast datagram socket. Default is 64'000 bytes") - private int ucast_recv_buf_size=64000; + protected int ucast_recv_buf_size=64000; + + @Property + protected boolean disable_loopback=false; + - - - /* --------------------------------------------- Fields ------------------------------------------------ */ - - + + /** The multicast address (mcast address and port) this member uses */ - private IpAddress mcast_addr=null; + protected IpAddress mcast_addr=null; /** * Socket used for @@ -118,16 +105,16 @@ *

    * The address of this socket will be our local address (local_addr) */ - private DatagramSocket sock=null; + protected DatagramSocket sock=null; /** IP multicast socket for receiving multicast packets */ - private MulticastSocket mcast_sock=null; + protected MulticastSocket mcast_sock=null; /** Runnable to receive multicast packets */ - private PacketReceiver mcast_receiver=null; + protected PacketReceiver mcast_receiver=null; /** Runnable to receive unicast packets */ - private PacketReceiver ucast_receiver=null; + protected PacketReceiver ucast_receiver=null; /** @@ -148,63 +135,79 @@ public UDP() { } + public boolean supportsMulticasting() { + return ip_mcast; + } - public void setMulticastAddress(String addr) {this.mcast_addr_name=addr;} - public String getMulticastAddress() {return mcast_addr_name;} + public void setMulticastAddress(InetAddress addr) {this.mcast_group_addr=addr;} + public InetAddress getMulticastAddress() {return mcast_group_addr;} public int getMulticastPort() {return mcast_port;} public void setMulticastPort(int mcast_port) {this.mcast_port=mcast_port;} public void setMcastPort(int mcast_port) {this.mcast_port=mcast_port;} + /** + * Set the ttl for multicast socket + * @param ttl the time to live for the socket. + * @throws IOException + */ + public void setMulticastTTL(int ttl) throws IOException { + this.ip_ttl=ttl; + mcast_sock.setTimeToLive((byte)ttl); + } + + /** + * Getter for current multicast TTL + * @return + */ + public int getMulticastTTL() { + return this.ip_ttl; + } + + @Property(name="max_bundle_size", description="Maximum number of bytes for messages to be queued until they are sent") + public void setMaxBundleSize(int size) { + super.setMaxBundleSize(size); + if(size > Global.MAX_DATAGRAM_PACKET_SIZE) + throw new IllegalArgumentException("max_bundle_size (" + size + ") cannot exceed the max datagram " + + "packet size of " + Global.MAX_DATAGRAM_PACKET_SIZE); + } public String getInfo() { StringBuilder sb=new StringBuilder(); - sb.append("group_addr=").append(mcast_addr_name).append(':').append(mcast_port).append("\n"); + sb.append("group_addr=").append(mcast_group_addr.getHostName()).append(':').append(mcast_port).append("\n"); return sb.toString(); } - public void sendToAllMembers(byte[] data, int offset, int length) throws Exception { + public void sendMulticast(byte[] data, int offset, int length) throws Exception { if(ip_mcast && mcast_addr != null) { _send(mcast_addr.getIpAddress(), mcast_addr.getPort(), true, data, offset, length); } else { - List
    mbrs; - synchronized(members) { - mbrs=new ArrayList
    (members); - } - for(Address mbr: mbrs) { - _send(((IpAddress)mbr).getIpAddress(), ((IpAddress)mbr).getPort(), false, data, offset, length); - } + sendToAllPhysicalAddresses(data, offset, length); } } - public void sendToSingleMember(Address dest, byte[] data, int offset, int length) throws Exception { + public void sendUnicast(PhysicalAddress dest, byte[] data, int offset, int length) throws Exception { _send(((IpAddress)dest).getIpAddress(), ((IpAddress)dest).getPort(), false, data, offset, length); } - public void postUnmarshalling(Message msg, Address dest, Address src, boolean multicast) { - if(multicast) - msg.setDest(null); - else - msg.setDest(dest); - } - - public void postUnmarshallingList(Message msg, Address dest, boolean multicast) { - if(multicast) - msg.setDest(null); - else - msg.setDest(dest); - } - - private void _send(InetAddress dest, int port, boolean mcast, byte[] data, int offset, int length) throws Exception { + protected void _send(InetAddress dest, int port, boolean mcast, byte[] data, int offset, int length) throws Exception { DatagramPacket packet=new DatagramPacket(data, offset, length, dest, port); try { if(mcast) { - if(mcast_sock != null) - mcast_sock.send(packet); + if(mcast_sock != null && !mcast_sock.isClosed()) { + try { + mcast_sock.send(packet); + } + // solve reconnection issue with Windows (https://jira.jboss.org/browse/JGRP-1254) + catch(NoRouteToHostException e) { + log.warn(e.getMessage() +", reset interface"); + mcast_sock.setInterface(mcast_sock.getInterface()); + } + } } else { - if(sock != null) + if(sock != null && !sock.isClosed()) sock.send(packet); } } @@ -220,42 +223,13 @@ /*------------------------------ Protocol interface ------------------------------ */ - public String getName() { - return "UDP"; - } - - public void init() throws Exception { - super.init(); - - String str=Util.getProperty(new String[]{Global.UDP_MCAST_ADDR}, - null, "mcast_addr", false, null); - if(str != null) - mcast_addr_name=str; - - str=Util.getProperty(new String[]{Global.UDP_MCAST_PORT}, - null, "mcast_port", false, null); - - if(str != null) - mcast_port=Integer.parseInt(str); - - str=Util.getProperty(new String[]{Global.UDP_IP_TTL}, null, "ip_ttl", false, null); - if(str != null) - ip_ttl=Integer.parseInt(str); - - Util.checkBufferSize("UDP.mcast_send_buf_size", mcast_send_buf_size); - Util.checkBufferSize("UDP.mcast_recv_buf_size", mcast_recv_buf_size); - Util.checkBufferSize("UDP.ucast_send_buf_size", ucast_send_buf_size); - Util.checkBufferSize("UDP.ucast_recv_buf_size", ucast_recv_buf_size); - } - - /** * Creates the unicast and multicast sockets and starts the unicast and multicast receiver threads */ public void start() throws Exception { - if(log.isDebugEnabled()) log.debug("creating sockets and starting threads"); + if(log.isDebugEnabled()) log.debug("creating sockets"); try { createSockets(); } @@ -263,10 +237,10 @@ String tmp="problem creating sockets (bind_addr=" + bind_addr + ", mcast_addr=" + mcast_addr + ")"; throw new Exception(tmp, ex); } + super.start(); ucast_receiver=new PacketReceiver(sock, - local_addr, "unicast receiver", new Runnable() { public void run() { @@ -276,7 +250,6 @@ if(ip_mcast) mcast_receiver=new PacketReceiver(mcast_sock, - mcast_addr, "multicast receiver", new Runnable() { public void run() { @@ -292,6 +265,10 @@ super.stop(); } + public void destroy() { + super.destroy(); + destroySockets(); + } protected void handleConnect() throws Exception { if(isSingleton()) { @@ -320,16 +297,12 @@ /* ------------------------------ Private Methods -------------------------------- */ - - - - /** * Create UDP sender and receiver sockets. Currently there are 2 sockets * (sending and receiving). This is due to Linux's non-BSD compatibility * in the JDK port (see DESIGN). */ - private void createSockets() throws Exception { + protected void createSockets() throws Exception { // bind_addr not set, try to assign one by default. This is needed on Windows // changed by bela Feb 12 2003: by default multicast sockets will be bound to all network interfaces @@ -342,15 +315,22 @@ // if(interfaces != null && interfaces.length > 0) // bind_addr=interfaces[0]; // } + // RA 14 Sep 09: These lines were never called as bind_addr always returned a non-null value +// if(bind_addr == null && !use_local_host) { +// bind_addr=Util.getFirstNonLoopbackAddress(); +// } +// if(bind_addr == null) +// bind_addr=InetAddress.getLocalHost(); + + if(bind_addr == null) + throw new IllegalArgumentException("bind_addr cannot be null") ; + +// RA: 16 Sep 09: need to resolve the use of use_local_host +// if(bind_addr != null && !bind_addr.isLoopbackAddress() && use_local_host) { +// throw new IllegalArgumentException("must use use localhost as a bind address") ; +// } - if(bind_addr == null && !use_local_host) { - bind_addr=Util.getFirstNonLoopbackAddress(); - } - if(bind_addr == null) - bind_addr=InetAddress.getLocalHost(); - - if(bind_addr != null) - if(log.isDebugEnabled()) log.debug("sockets will use interface " + bind_addr.getHostAddress()); + if(log.isDebugEnabled()) log.debug("sockets will use interface " + bind_addr.getHostAddress()); // 2. Create socket for receiving unicast UDP packets. The address and port @@ -359,57 +339,50 @@ sock=createDatagramSocketWithBindPort(); } else { - DatagramSocket tmp_sock=null; - if(prevent_port_reuse) { - tmp_sock=new DatagramSocket(0, bind_addr); - } - try { - sock=createEphemeralDatagramSocket(); - } - finally { - Util.close(tmp_sock); - } + sock=createEphemeralDatagramSocket(); } if(tos > 0) { try { sock.setTrafficClass(tos); } catch(SocketException e) { - log.warn("traffic class of " + tos + " could not be set, will be ignored"); - if(log.isDebugEnabled()) - log.debug("Cause of failure to set traffic class:", e); + log.warn("traffic class of " + tos + " could not be set, will be ignored: " + e); } } if(sock == null) - throw new Exception("UDP.createSocket(): sock is null"); - - local_addr=createLocalAddress(); - if(additional_data != null) - ((IpAddress)local_addr).setAdditionalData(additional_data); - + throw new Exception("socket is null"); // 3. Create socket for receiving IP multicast packets if(ip_mcast) { - // 3a. Create mcast receiver socket - InetAddress group_addr=InetAddress.getByName(mcast_addr_name); - // https://jira.jboss.org/jira/browse/JGRP-777 - this doesn't work on MacOS, and we don't have // cross talking on Windows anyway, so we just do it for Linux. (How about Solaris ?) if(can_bind_to_mcast_addr) - mcast_sock=Util.createMulticastSocket(group_addr, mcast_port, log); + mcast_sock=Util.createMulticastSocket(getSocketFactory(), Global.UDP_MCAST_SOCK, mcast_group_addr, mcast_port, log); else - mcast_sock=new MulticastSocket(mcast_port); + mcast_sock=getSocketFactory().createMulticastSocket(Global.UDP_MCAST_SOCK, mcast_port); + + if(disable_loopback) + mcast_sock.setLoopbackMode(disable_loopback); mcast_sock.setTimeToLive(ip_ttl); - mcast_addr=new IpAddress(group_addr, mcast_port); + mcast_addr=new IpAddress(mcast_group_addr, mcast_port); + + // check that we're not using the same mcast address and port as the diagnostics socket + if(enable_diagnostics) { + if(diagnostics_addr != null && diagnostics_addr.equals(mcast_group_addr) || + diagnostics_port == mcast_port) + throw new IllegalArgumentException("diagnostics_addr / diagnostics_port and mcast_addr / mcast_port " + + "have to be different"); + } + if(tos > 0) { try { mcast_sock.setTrafficClass(tos); } catch(SocketException e) { - log.warn("traffic class of " + tos + " could not be set, will be ignored", e); + log.warn("traffic class of " + tos + " could not be set, will be ignored: " + e); } } @@ -424,7 +397,7 @@ else { if(bind_addr != null) mcast_sock.setInterface(bind_addr); - mcast_sock.joinGroup(group_addr); + mcast_sock.joinGroup(mcast_group_addr); } } @@ -433,12 +406,20 @@ } - protected Address createLocalAddress() { - return new IpAddress(sock.getLocalAddress(), sock.getLocalPort()); + protected void destroySockets() { + closeMulticastSocket(); + closeUnicastSocket(); } + protected IpAddress createLocalAddress() { + return sock != null && !sock.isClosed()? new IpAddress(sock.getLocalAddress(), sock.getLocalPort()) : null; + } + protected PhysicalAddress getPhysicalAddress() { + return createLocalAddress(); + } + /** * * @param interfaces List. Guaranteed to have no duplicates @@ -446,7 +427,7 @@ * @param mcastAddr * @throws IOException */ - private void bindToInterfaces(List interfaces, + protected void bindToInterfaces(List interfaces, MulticastSocket s, InetAddress mcastAddr) { SocketAddress tmp_mcast_addr=new InetSocketAddress(mcastAddr, mcast_port); @@ -474,7 +455,7 @@ int localPort=0; while(true) { try { - tmp=new DatagramSocket(localPort, bind_addr); // first time localPort is 0 + tmp=getSocketFactory().createDatagramSocket(Global.UDP_UCAST_SOCK, localPort, bind_addr); } catch(SocketException socket_ex) { // Vladimir May 30th 2007 @@ -483,16 +464,7 @@ continue; } localPort=tmp.getLocalPort(); - if(last_ports_used.contains(localPort)) { - if(log.isDebugEnabled()) - log.debug("local port " + localPort + " already seen in this session; will try to get other port"); - try {tmp.close();} catch(Throwable e) {} - localPort++; - } - else { - last_ports_used.add(localPort); - break; - } + break; } return tmp; } @@ -510,12 +482,9 @@ DatagramSocket tmp=null; // 27-6-2003 bgooren, find available port in range (start_port, start_port+port_range) int rcv_port=bind_port, max_port=bind_port + port_range; - if(pm != null && bind_port > 0) { - rcv_port=pm.getNextAvailablePort(rcv_port); - } while(rcv_port <= max_port) { try { - tmp=new DatagramSocket(rcv_port, bind_addr); + tmp=getSocketFactory().createDatagramSocket(Global.UDP_UCAST_SOCK, rcv_port, bind_addr); return tmp; } catch(SocketException bind_ex) { // Cannot listen on this port @@ -534,9 +503,8 @@ } - private String dumpSocketInfo() throws Exception { + protected String dumpSocketInfo() throws Exception { StringBuilder sb=new StringBuilder(128); - sb.append("local_addr=").append(local_addr); sb.append(", mcast_addr=").append(mcast_addr); sb.append(", bind_addr=").append(bind_addr); sb.append(", ttl=").append(ip_ttl); @@ -566,7 +534,7 @@ setBufferSize(mcast_sock, mcast_send_buf_size, mcast_recv_buf_size); } - private void setBufferSize(DatagramSocket sock, int send_buf_size, int recv_buf_size) { + protected void setBufferSize(DatagramSocket sock, int send_buf_size, int recv_buf_size) { try { sock.setSendBufferSize(send_buf_size); int actual_size=sock.getSendBufferSize(); @@ -584,7 +552,7 @@ try { sock.setReceiveBufferSize(recv_buf_size); int actual_size=sock.getReceiveBufferSize(); - if(actual_size < send_buf_size && log.isWarnEnabled()) { + if(actual_size < recv_buf_size && log.isWarnEnabled()) { log.warn("receive buffer of socket " + sock + " was set to " + Util.printBytes(recv_buf_size) + ", but the OS only allocated " + Util.printBytes(actual_size) + ". This might lead to performance problems. Please set your " + @@ -604,7 +572,7 @@ if(mcast_addr != null) { mcast_sock.leaveGroup(mcast_addr.getIpAddress()); } - mcast_sock.close(); // this will cause the mcast receiver thread to break out of its loop + getSocketFactory().close(mcast_sock); // this will cause the mcast receiver thread to break out of its loop mcast_sock=null; if(log.isDebugEnabled()) log.debug("multicast socket closed"); } @@ -615,21 +583,12 @@ } - private void closeUnicastSocket() { - if(sock != null) { - if(pm != null && bind_port > 0) { - int port=local_addr != null? ((IpAddress)local_addr).getPort() : sock.getLocalPort(); - pm.updatePort(port); - } - sock.close(); - sock=null; - if(log.isDebugEnabled()) log.debug("socket closed"); - } + protected void closeUnicastSocket() { + getSocketFactory().close(sock); } - /** * Starts the unicast and multicast receiver threads */ @@ -652,7 +611,6 @@ protected void handleConfigEvent(Map map) { boolean set_buffers=false; - super.handleConfigEvent(map); if(map == null) return; if(map.containsKey("send_buf_size")) { @@ -679,13 +637,11 @@ public class PacketReceiver implements Runnable { private Thread thread=null; private final DatagramSocket receiver_socket; - private final Address dest; private final String name; private final Runnable close_strategy; - public PacketReceiver(DatagramSocket socket, Address dest, String name, Runnable close_strategy) { + public PacketReceiver(DatagramSocket socket, String name, Runnable close_strategy) { this.receiver_socket=socket; - this.dest=dest; this.name=name; this.close_strategy=close_strategy; } @@ -725,7 +681,7 @@ public void run() { - final byte receive_buf[]=new byte[65535]; + final byte receive_buf[]=new byte[66000]; // to be on the safe side (IPv6 == 65575 bytes, IPv4 = 65535) final DatagramPacket packet=new DatagramPacket(receive_buf, receive_buf.length); while(thread != null && Thread.currentThread().equals(thread)) { @@ -739,19 +695,18 @@ "Use the FRAG2 protocol and make its frag_size lower than " + receive_buf.length); } - receive(dest, - new IpAddress(packet.getAddress(), packet.getPort()), + receive(new IpAddress(packet.getAddress(), packet.getPort()), receive_buf, packet.getOffset(), len); } catch(SocketException sock_ex) { - if(log.isDebugEnabled()) log.debug("unicast receiver socket is closed, exception=" + sock_ex); + if(log.isDebugEnabled()) log.debug("receiver socket is closed, exception=" + sock_ex); break; } catch(Throwable ex) { if(log.isErrorEnabled()) - log.error("[" + local_addr + "] failed receiving unicast packet", ex); + log.error("failed receiving packet", ex); } } if(log.isDebugEnabled()) log.debug(name + " thread terminated"); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/UFC.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/UFC.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/UFC.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/UFC.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,162 @@ +package org.jgroups.protocols; + +import org.jgroups.Address; +import org.jgroups.Event; +import org.jgroups.Message; +import org.jgroups.annotations.MBean; +import org.jgroups.annotations.ManagedAttribute; +import org.jgroups.annotations.ManagedOperation; +import org.jgroups.util.Util; + +import java.util.Iterator; +import java.util.Map; +import java.util.Vector; + + +/** + * Simple flow control protocol based on a credit system. Each sender has a number of credits (bytes + * to send). When the credits have been exhausted, the sender blocks. Each receiver also keeps track of + * how many credits it has received from a sender. When credits for a sender fall below a threshold, + * the receiver sends more credits to the sender. Works for both unicast and multicast messages. + *

    + * Note that this protocol must be located towards the top of the stack, or all down_threads from JChannel to this + * protocol must be set to false ! This is in order to block JChannel.send()/JChannel.down(). + *
    This is the second simplified implementation of the same model. The algorithm is sketched out in + * doc/FlowControl.txt + *
    + * Changes (Brian) April 2006: + *

      + *
    1. Receivers now send credits to a sender when more than min_credits have been received (rather than when min_credits + * are left) + *
    2. Receivers don't send the full credits (max_credits), but rather the actual number of bytes received + *
        + * @author Bela Ban + */ +@MBean(description="Simple flow control protocol based on a credit system") +public class UFC extends FlowControl { + + /** + * Map: keys are members, values are credits left. For each send, + * the number of credits is decremented by the message size + */ + protected final Map sent=Util.createConcurrentMap(); + + + + @ManagedOperation(description="Print sender credits") + public String printSenderCredits() { + return printMap(sent); + } + + + @ManagedOperation(description="Print credits") + public String printCredits() { + StringBuilder sb=new StringBuilder(super.printCredits()); + sb.append("\nsenders:\n").append(printMap(sent)); + return sb.toString(); + } + + public Map dumpStats() { + Map retval=super.dumpStats(); + retval.put("senders", printMap(sent)); + return retval; + } + + + protected boolean handleMulticastMessage() { + return false; + } + + + + public void unblock() { + super.unblock(); + } + + @ManagedAttribute(description="Number of times flow control blocks sender") + public int getNumberOfBlockings() { + int retval=0; + for(Credit cred: sent.values()) + retval+=cred.getNumBlockings(); + return retval; + } + + @ManagedAttribute(description="Total time (ms) spent in flow control block") + public long getTotalTimeBlocked() { + long retval=0; + for(Credit cred: sent.values()) + retval+=cred.getTotalBlockingTime(); + return retval; + } + + public void stop() { + super.stop(); + for(Credit cred: sent.values()) + cred.set(max_credits); + } + + + + + protected Object handleDownMessage(final Event evt, final Message msg, Address dest, int length) { + if(dest == null || dest.isMulticastAddress()) { // 2nd line of defense, not really needed + log.error(getClass().getSimpleName() + " doesn't handle multicast messages; passing message down"); + return down_prot.down(evt); + } + + Credit cred=sent.get(dest); + if(cred == null) + return down_prot.down(evt); + + long block_time=max_block_times != null? getMaxBlockTime(length) : max_block_time; + + while(running && sent.containsKey(dest)) { + boolean rc=cred.decrementIfEnoughCredits(length, block_time); + if(rc || !running || max_block_times != null) + break; + + if(cred.needToSendCreditRequest()) + sendCreditRequest(dest, Math.max(0, max_credits - cred.get())); + } + + // send message - either after regular processing, or after blocking (when enough credits available again) + return down_prot.down(evt); + } + + + protected void handleViewChange(Vector
        mbrs) { + super.handleViewChange(mbrs); + if(mbrs == null) return; + + // add members not in membership to received and sent hashmap (with full credits) + for(Address addr: mbrs) { + if(!sent.containsKey(addr)) + sent.put(addr, new Credit(max_credits)); + } + + // remove members that left + for(Iterator
        it=sent.keySet().iterator(); it.hasNext();) { + Address addr=it.next(); + if(!mbrs.contains(addr)) + it.remove(); // modified the underlying map + } + } + + + protected void handleCredit(Address sender, long increase) { + Credit cred; + if(sender == null || (cred=sent.get(sender)) == null || increase <= 0) + return; + + long new_credit=Math.min(max_credits, cred.get() + increase); + if(log.isTraceEnabled()) { + StringBuilder sb=new StringBuilder(); + sb.append("received " + increase + " credits from ").append(sender).append(", old credits: ").append(cred) + .append(", new credits: ").append(new_credit); + log.trace(sb); + } + cred.increment(increase); + } + + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/UNICAST2.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/UNICAST2.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/UNICAST2.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/UNICAST2.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,1062 @@ +package org.jgroups.protocols; + +import org.jgroups.*; +import org.jgroups.annotations.*; +import org.jgroups.conf.PropertyConverters; +import org.jgroups.stack.AckSenderWindow; +import org.jgroups.stack.NakReceiverWindow; +import org.jgroups.stack.Protocol; +import org.jgroups.stack.Retransmitter; +import org.jgroups.util.AgeOutCache; +import org.jgroups.util.TimeScheduler; +import org.jgroups.util.Util; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + + +/** + * Reliable unicast layer. Implemented with negative acks. Every sender keeps its messages in an AckSenderWindow. A + * receiver stores incoming messages in a NakReceiverWindow, and asks the sender for retransmission if a gap is + * detected. Every now and then (stable_interval), a timer task sends a STABLE message to all senders, including the + * highest received and delivered seqnos. A sender purges messages lower than highest delivered and asks the STABLE + * sender for messages it might have missed (smaller than highest received). A STABLE message can also be sent when + * a receiver has received more than max_bytes from a given sender.

        + * The advantage of this protocol over {@link org.jgroups.protocols.UNICAST} is that it doesn't send acks for every + * message. Instead, it sends 'acks' after receiving max_bytes and/ or periodically (stable_interval). + * @author Bela Ban + */ +@Experimental +@MBean(description="Reliable unicast layer") +public class UNICAST2 extends Protocol implements Retransmitter.RetransmitCommand, AgeOutCache.Handler

        { + public static final long DEFAULT_FIRST_SEQNO=Global.DEFAULT_FIRST_UNICAST_SEQNO; + + + /* ------------------------------------------ Properties ------------------------------------------ */ + + private long[] timeout= { 400, 800, 1600, 3200 }; // for NakSenderWindow: max time to wait for missing acks + + @Property(description="Max number of messages to be removed from a NakReceiverWindow. This property might " + + "get removed anytime, so don't use it !") + private int max_msg_batch_size=50000; + + @Property(description="Max number of bytes before a stability message is sent to the sender") + protected long max_bytes=10000000; + + @Property(description="Max number of milliseconds before a stability message is sent to the sender(s)") + protected long stable_interval=60000L; + + @Property(description="Max number of STABLE messages sent for the same highest_received seqno. A value < 1 is invalid") + protected int max_stable_msgs=5; + + @Property(description="Number of rows of the matrix in the retransmission table (only for experts)",writable=false) + int xmit_table_num_rows=5; + + @Property(description="Number of elements of a row of the matrix in the retransmission table (only for experts). " + + "The capacity of the matrix is xmit_table_num_rows * xmit_table_msgs_per_row",writable=false) + int xmit_table_msgs_per_row=10000; + + @Property(description="Resize factor of the matrix in the retransmission table (only for experts)",writable=false) + double xmit_table_resize_factor=1.2; + + @Property(description="Number of milliseconds after which the matrix in the retransmission table " + + "is compacted (only for experts)",writable=false) + long xmit_table_max_compaction_time=10 * 60 * 1000; + + @Property(description="If enabled, the removal of a message from the retransmission table causes an " + + "automatic purge (only for experts)",writable=false) + boolean xmit_table_automatic_purging=true; + + @Property(description="Whether to use the old retransmitter which retransmits individual messages or the new one " + + "which uses ranges of retransmitted messages. Default is true. Note that this property will be removed in 3.0; " + + "it is only used to switch back to the old (and proven) retransmitter mechanism if issues occur") + private boolean use_range_based_retransmitter=true; + /* --------------------------------------------- JMX ---------------------------------------------- */ + + + private long num_msgs_sent=0, num_msgs_received=0, num_bytes_sent=0, num_bytes_received=0, num_xmits=0; + + + /* --------------------------------------------- Fields ------------------------------------------------ */ + + private final ConcurrentMap send_table=Util.createConcurrentMap(); + private final ConcurrentMap recv_table=Util.createConcurrentMap(); + + protected final ReentrantLock recv_table_lock=new ReentrantLock(); + + private final Vector
        members=new Vector
        (11); + + private Address local_addr=null; + + private TimeScheduler timer=null; // used for retransmissions (passed to AckSenderWindow) + + private boolean started=false; + + private short last_conn_id=0; + + protected long max_retransmit_time=60 * 1000L; + + private AgeOutCache
        cache=null; + + private Future stable_task_future=null; // bcasts periodic STABLE message (added to timer below) + + + public long[] getTimeout() {return timeout;} + + @Property(name="timeout",converter=PropertyConverters.LongArray.class) + public void setTimeout(long[] val) { + if(val != null) + timeout=val; + } + + public void setMaxMessageBatchSize(int size) { + if(size >= 1) + max_msg_batch_size=size; + } + + @ManagedAttribute + public String getLocalAddress() {return local_addr != null? local_addr.toString() : "null";} + + @ManagedAttribute + public String getMembers() {return members != null? members.toString() : "[]";} + + @ManagedOperation + public String printConnections() { + StringBuilder sb=new StringBuilder(); + if(!send_table.isEmpty()) { + sb.append("send connections:\n"); + for(Map.Entry entry: send_table.entrySet()) { + sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); + } + } + + if(!recv_table.isEmpty()) { + sb.append("\nreceive connections:\n"); + for(Map.Entry entry: recv_table.entrySet()) { + sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); + } + } + return sb.toString(); + } + + @ManagedAttribute + public long getNumMessagesSent() { + return num_msgs_sent; + } + + @ManagedAttribute + public long getNumMessagesReceived() { + return num_msgs_received; + } + + @ManagedAttribute + public long getNumBytesSent() { + return num_bytes_sent; + } + + @ManagedAttribute + public long getNumBytesReceived() { + return num_bytes_received; + } + + @ManagedAttribute + public long getNumberOfRetransmissions() { + return num_xmits; + } + + public long getMaxRetransmitTime() { + return max_retransmit_time; + } + + @Property(description="Max number of milliseconds we try to retransmit a message to any given member. After that, " + + "the connection is removed. Any new connection to that member will start with seqno #1 again. 0 disables this") + public void setMaxRetransmitTime(long max_retransmit_time) { + this.max_retransmit_time=max_retransmit_time; + if(cache != null && max_retransmit_time > 0) + cache.setTimeout(max_retransmit_time); + } + + @ManagedAttribute + public int getAgeOutCacheSize() { + return cache != null? cache.size() : 0; + } + + @ManagedOperation + public String printAgeOutCache() { + return cache != null? cache.toString() : "n/a"; + } + + public AgeOutCache
        getAgeOutCache() { + return cache; + } + + @ManagedAttribute + public int getNumberOfMessagesInReceiveWindows() { + int num=0; + for(ReceiverEntry entry: recv_table.values()) { + if(entry.received_msgs != null) + num+=entry.received_msgs.size(); + } + return num; + } + + + @ManagedOperation(description="Returns the sizes of all NakReceiverWindow.RetransmitTables") + public String printRetransmitTableSizes() { + StringBuilder sb=new StringBuilder(); + for(Map.Entry entry: recv_table.entrySet()) { + NakReceiverWindow win=entry.getValue().received_msgs; + sb.append(entry.getKey() + ": ").append(win.getRetransmiTableSize()) + .append(" (capacity=" + win.getRetransmitTableCapacity()) + .append(", fill factor=" + win.getRetransmitTableFillFactor() + "%)\n"); + } + return sb.toString(); + } + + + @ManagedOperation(description="Compacts the retransmission tables") + public void compact() { + for(Map.Entry entry: recv_table.entrySet()) { + NakReceiverWindow win=entry.getValue().received_msgs; + win.compact(); + } + } + + @ManagedOperation(description="Purges highes delivered messages and compacts the retransmission tables") + public void purgeAndCompact() { + for(Map.Entry entry: recv_table.entrySet()) { + NakReceiverWindow win=entry.getValue().received_msgs; + win.stable(win.getHighestDelivered()); + win.compact(); + } + } + + + public void resetStats() { + num_msgs_sent=num_msgs_received=num_bytes_sent=num_bytes_received=num_xmits=0; + } + + + public Map dumpStats() { + Map m=super.dumpStats(); + m.put("num_msgs_sent", num_msgs_sent); + m.put("num_msgs_received", num_msgs_received); + m.put("num_bytes_sent", num_bytes_sent); + m.put("num_bytes_received", num_bytes_received); + m.put("num_xmits", num_xmits); + m.put("num_msgs_in_recv_windows", getNumberOfMessagesInReceiveWindows()); + return m; + } + + + public TimeScheduler getTimer() { + return timer; + } + + /** + * Only used for unit tests, don't use ! + * @param timer + */ + public void setTimer(TimeScheduler timer) { + this.timer=timer; + } + + public void init() throws Exception { + super.init(); + if(max_stable_msgs < 1) + throw new IllegalArgumentException("max_stable_msgs ( " + max_stable_msgs + ") must be > 0"); + if(max_bytes <= 0) + throw new IllegalArgumentException("max_bytes has to be > 0"); + } + + public void start() throws Exception { + timer=getTransport().getTimer(); + if(timer == null) + throw new Exception("timer is null"); + if(max_retransmit_time > 0) + cache=new AgeOutCache
        (timer, max_retransmit_time, this); + started=true; + if(stable_interval > 0) { + startStableTask(); + } + } + + public void stop() { + started=false; + stopStableTask(); + removeAllConnections(); + } + + + public Object up(Event evt) { + Message msg; + Address dst, src; + Unicast2Header hdr; + + switch(evt.getType()) { + + case Event.MSG: + msg=(Message)evt.getArg(); + dst=msg.getDest(); + + if(dst == null || dst.isMulticastAddress() || msg.isFlagSet(Message.NO_RELIABILITY)) // only handle unicast messages + break; // pass up + + // changed from removeHeader(): we cannot remove the header because if we do loopback=true at the + // transport level, we will not have the header on retransmit ! (bela Aug 22 2006) + hdr=(Unicast2Header)msg.getHeader(this.id); + if(hdr == null) + break; + src=msg.getSrc(); + switch(hdr.type) { + case Unicast2Header.DATA: // received regular message + handleDataReceived(src, hdr.seqno, hdr.conn_id, hdr.first, msg, evt); + return null; // we pass the deliverable message up in handleDataReceived() + case Unicast2Header.XMIT_REQ: // received ACK for previously sent message + handleXmitRequest(src, hdr.seqno, hdr.high_seqno); + break; + case Unicast2Header.SEND_FIRST_SEQNO: + handleResendingOfFirstMessage(src, hdr.seqno); + break; + case Unicast2Header.STABLE: + stable(msg.getSrc(), hdr.seqno, hdr.high_seqno); + break; + default: + log.error("UnicastHeader type " + hdr.type + " not known !"); + break; + } + return null; + } + + return up_prot.up(evt); // Pass up to the layer above us + } + + + + public Object down(Event evt) { + switch (evt.getType()) { + + case Event.MSG: // Add UnicastHeader, add to AckSenderWindow and pass down + Message msg=(Message)evt.getArg(); + Address dst=msg.getDest(); + + /* only handle unicast messages */ + if (dst == null || dst.isMulticastAddress() || msg.isFlagSet(Message.NO_RELIABILITY)) + break; + + if(!started) { + if(log.isTraceEnabled()) + log.trace("discarded message as start() has not yet been called, message: " + msg); + return null; + } + + SenderEntry entry=send_table.get(dst); + if(entry == null) { + entry=new SenderEntry(getNewConnectionId()); + SenderEntry existing=send_table.putIfAbsent(dst, entry); + if(existing != null) + entry=existing; + else { + if(log.isTraceEnabled()) + log.trace(local_addr + ": created connection to " + dst); + if(cache != null && !members.contains(dst)) + cache.add(dst); + } + } + + long seqno=-2; + short send_conn_id=-1; + Unicast2Header hdr; + + entry.lock(); // threads will only sync if they access the same entry + try { + seqno=entry.sent_msgs_seqno; + send_conn_id=entry.send_conn_id; + hdr=Unicast2Header.createDataHeader(seqno, send_conn_id, seqno == DEFAULT_FIRST_SEQNO); + msg.putHeader(this.id, hdr); + entry.sent_msgs.addToMessages(seqno, msg); // add *including* UnicastHeader, adds to retransmitter + entry.sent_msgs_seqno++; + } + finally { + entry.unlock(); + } + + if(log.isTraceEnabled()) { + StringBuilder sb=new StringBuilder(); + sb.append(local_addr).append(" --> DATA(").append(dst).append(": #").append(seqno). + append(", conn_id=").append(send_conn_id); + if(hdr.first) sb.append(", first"); + sb.append(')'); + log.trace(sb); + } + + try { + down_prot.down(evt); + num_msgs_sent++; + num_bytes_sent+=msg.getLength(); + } + catch(Throwable t) { + log.warn("failed sending the message", t); + } + return null; // we already passed the msg down + + case Event.VIEW_CHANGE: // remove connections to peers that are not members anymore ! + View view=(View)evt.getArg(); + Vector
        new_members=view.getMembers(); + Set
        non_members=new HashSet
        (send_table.keySet()); + non_members.addAll(recv_table.keySet()); + + synchronized(members) { + members.clear(); + if(new_members != null) + members.addAll(new_members); + non_members.removeAll(members); + if(cache != null) { + cache.removeAll(members); + } + } + + if(!non_members.isEmpty()) { + if(log.isTraceEnabled()) + log.trace("removing non members " + non_members); + for(Address non_mbr: non_members) + removeConnection(non_mbr); + } + break; + + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; + } + + return down_prot.down(evt); // Pass on to the layer below us + } + + /** + * Purge all messages in window for local_addr, which are <= low. Check if the window's highest received message is + * > high: if true, retransmit all messages from high - win.high to sender + * @param sender + * @param highest_delivered + * @param highest_seen + */ + protected void stable(Address sender, long highest_delivered, long highest_seen) { + SenderEntry entry=send_table.get(sender); + AckSenderWindow win=entry != null? entry.sent_msgs : null; + if(win == null) + return; + + if(log.isTraceEnabled()) + log.trace(new StringBuilder().append(local_addr).append(" <-- STABLE(").append(sender). + append(": ").append(highest_delivered).append("-").append(highest_seen).append(')')); + + win.ack(highest_delivered); + long win_high=win.getHighest(); + if(win_high > highest_seen) { + for(long seqno=highest_seen; seqno <= win_high; seqno++) { + Message msg=win.get(seqno); // destination is still the same (the member which sent the STABLE message) + if(msg != null) + down_prot.down(new Event(Event.MSG, msg)); + } + } + } + + @ManagedOperation(description="Sends a STABLE message to all senders. This causes message purging and potential" + + " retransmissions from senders") + public void sendStableMessages() { + for(Map.Entry entry: recv_table.entrySet()) { + Address dest=entry.getKey(); + ReceiverEntry val=entry.getValue(); + NakReceiverWindow win=val != null? val.received_msgs : null; + if(win != null) { + long low=win.getHighestDelivered(); + long high=win.getHighestReceived(); + + if(val.last_highest == high) { + if(val.num_stable_msgs >= val.max_stable_msgs) { + continue; + } + else + val.num_stable_msgs++; + } + else { + val.last_highest=high; + val.num_stable_msgs=1; + } + sendStableMessage(dest, low, high); + } + } + } + + protected void sendStableMessage(Address dest, long low, long high) { + Message stable_msg=new Message(dest, null, null); + Unicast2Header hdr=Unicast2Header.createStableHeader(low, high); + stable_msg.putHeader(this.id, hdr); + if(log.isTraceEnabled()) { + StringBuilder sb=new StringBuilder(); + sb.append(local_addr).append(" --> STABLE(").append(dest).append(": ").append(low).append("-").append(high).append(")"); + log.trace(sb.toString()); + } + down_prot.down(new Event(Event.MSG, stable_msg)); + + ReceiverEntry entry=recv_table.get(dest); + NakReceiverWindow win=entry != null? entry.received_msgs : null; + if(win != null) { + //System.out.println("[" + local_addr + "] stable(" + dest + ", hd=" + win.getHighestDelivered() + "): " + + // "win: " + win); + win.stable(win.getHighestDelivered()); + } + } + + + private void startStableTask() { + if(stable_task_future == null || stable_task_future.isDone()) { + final Runnable stable_task=new Runnable() { + public void run() { + try { + sendStableMessages(); + } + catch(Throwable t) { + log.error("sending of STABLE messages failed", t); + } + } + }; + stable_task_future=timer.scheduleWithFixedDelay(stable_task, stable_interval, stable_interval, TimeUnit.MILLISECONDS); + if(log.isTraceEnabled()) + log.trace("stable task started"); + } + } + + + private void stopStableTask() { + if(stable_task_future != null) { + stable_task_future.cancel(false); + stable_task_future=null; + } + } + + + /** + * Removes and resets from connection table (which is already locked). Returns true if member was found, otherwise + * false. This method is public only so it can be invoked by unit testing, but should not otherwise be used ! + */ + public void removeConnection(Address mbr) { + SenderEntry entry=send_table.remove(mbr); + if(entry != null) + entry.reset(); + + ReceiverEntry entry2=recv_table.remove(mbr); + if(entry2 != null) { + NakReceiverWindow win=entry2.received_msgs; + if(win != null) + sendStableMessage(mbr, win.getHighestDelivered(), win.getHighestReceived()); + entry2.reset(); + } + } + + /** + * This method is public only so it can be invoked by unit testing, but should not otherwise be used ! + */ + @ManagedOperation(description="Trashes all connections to other nodes. This is only used for testing") + public void removeAllConnections() { + for(SenderEntry entry: send_table.values()) + entry.reset(); + send_table.clear(); + + sendStableMessages(); + for(ReceiverEntry entry2: recv_table.values()) + entry2.reset(); + recv_table.clear(); + } + + + public void retransmit(long first_seqno, long last_seqno, Address sender) { + Unicast2Header hdr=Unicast2Header.createXmitReqHeader(first_seqno, last_seqno); + Message xmit_req=new Message(sender, null, null); + xmit_req.putHeader(this.id, hdr); + down_prot.down(new Event(Event.MSG, xmit_req)); + } + + + /** + * Called by AgeOutCache, to removed expired connections + * @param key + */ + public void expired(Address key) { + if(key != null) { + if(log.isDebugEnabled()) + log.debug("removing connection to " + key + " because it expired"); + removeConnection(key); + } + } + + + + /** + * Check whether the hashtable contains an entry e for sender (create if not). If + * e.received_msgs is null and first is true: create a new AckReceiverWindow(seqno) and + * add message. Set e.received_msgs to the new window. Else just add the message. + */ + protected void handleDataReceived(Address sender, long seqno, long conn_id, boolean first, Message msg, Event evt) { + if(log.isTraceEnabled()) { + StringBuilder sb=new StringBuilder(); + sb.append(local_addr).append(" <-- DATA(").append(sender).append(": #").append(seqno); + if(conn_id != 0) sb.append(", conn_id=").append(conn_id); + if(first) sb.append(", first"); + sb.append(')'); + log.trace(sb); + } + + ReceiverEntry entry; + NakReceiverWindow win; + + recv_table_lock.lock(); + try { + entry=recv_table.get(sender); + win=entry != null? entry.received_msgs : null; + if(first) { + if(entry == null) { + entry=getOrCreateReceiverEntry(sender, seqno, conn_id); + win=entry.received_msgs; + } + else { // entry != null && win != null + if(conn_id != entry.recv_conn_id) { + if(log.isTraceEnabled()) + log.trace(local_addr + ": conn_id=" + conn_id + " != " + entry.recv_conn_id + "; resetting receiver window"); + + ReceiverEntry entry2=recv_table.remove(sender); + if(entry2 != null) + entry2.received_msgs.destroy(); + + entry=getOrCreateReceiverEntry(sender, seqno, conn_id); + win=entry.received_msgs; + } + else { + ; + } + } + } + else { // entry == null && win == null OR entry != null && win == null OR entry != null && win != null + if(win == null || entry.recv_conn_id != conn_id) { + recv_table_lock.unlock(); + sendRequestForFirstSeqno(sender, seqno); // drops the message and returns (see below) + return; + } + } + } + finally { + if(recv_table_lock.isHeldByCurrentThread()) + recv_table_lock.unlock(); + } + + boolean added=win.add(seqno, msg); // win is guaranteed to be non-null if we get here + num_msgs_received++; + num_bytes_received+=msg.getLength(); + + if(added) { + int bytes=entry.received_bytes.addAndGet(msg.getLength()); + if(bytes >= max_bytes) { + entry.received_bytes_lock.lock(); + try { + entry.received_bytes.set(0); + } + finally { + entry.received_bytes_lock.unlock(); + } + + sendStableMessage(sender, win.getHighestDelivered(), win.getHighestReceived()); + } + } + + // An OOB message is passed up immediately. Later, when remove() is called, we discard it. This affects ordering ! + // http://jira.jboss.com/jira/browse/JGRP-377 + if(msg.isFlagSet(Message.OOB) && added) { + try { + up_prot.up(evt); + } + catch(Throwable t) { + log.error("couldn't deliver OOB message " + msg, t); + } + } + + final AtomicBoolean processing=win.getProcessing(); + if(!processing.compareAndSet(false, true)) { + return; + } + + // try to remove (from the AckReceiverWindow) as many messages as possible and pass them up + + // Prevents concurrent passing up of messages by different threads (http://jira.jboss.com/jira/browse/JGRP-198); + // this is all the more important once we have a concurrent stack (http://jira.jboss.com/jira/browse/JGRP-181), + // where lots of threads can come up to this point concurrently, but only 1 is allowed to pass at a time + // We *can* deliver messages from *different* senders concurrently, e.g. reception of P1, Q1, P2, Q2 can result in + // delivery of P1, Q1, Q2, P2: FIFO (implemented by UNICAST) says messages need to be delivered only in the + // order in which they were sent by their senders + boolean released_processing=false; + try { + while(true) { + List msgs=win.removeMany(processing, true, max_msg_batch_size); // remove my own messages + if(msgs == null || msgs.isEmpty()) { + released_processing=true; + return; + } + + for(Message m: msgs) { + // discard OOB msg: it has already been delivered (http://jira.jboss.com/jira/browse/JGRP-377) + if(m.isFlagSet(Message.OOB)) + continue; + try { + up_prot.up(new Event(Event.MSG, m)); + } + catch(Throwable t) { + log.error("couldn't deliver message " + m, t); + } + } + } + } + finally { + // processing is always set in win.remove(processing) above and never here ! This code is just a + // 2nd line of defense should there be an exception before win.remove(processing) sets processing + if(!released_processing) + processing.set(false); + } + } + + + private ReceiverEntry getOrCreateReceiverEntry(Address sender, long seqno, long conn_id) { + NakReceiverWindow win=new NakReceiverWindow(sender, this, seqno-1, seqno-1, timer, use_range_based_retransmitter, + xmit_table_num_rows, xmit_table_msgs_per_row, + xmit_table_resize_factor, xmit_table_max_compaction_time, + xmit_table_automatic_purging); + ReceiverEntry entry=new ReceiverEntry(win, conn_id, max_stable_msgs); + ReceiverEntry entry2=recv_table.putIfAbsent(sender, entry); + if(entry2 != null) + return entry2; + if(log.isTraceEnabled()) + log.trace(local_addr + ": created receiver window for " + sender + " at seqno=#" + seqno + " for conn-id=" + conn_id); + return entry; + } + + + private void handleXmitRequest(Address sender, long low, long high) { + if(log.isTraceEnabled()) + log.trace(new StringBuilder().append(local_addr).append(" <-- XMIT(").append(sender). + append(": #").append(low).append( "-").append(high).append(')')); + + SenderEntry entry=send_table.get(sender); + AckSenderWindow win=entry != null? entry.sent_msgs : null; + if(win != null) { + for(long i=low; i <= high; i++) { + Message msg=win.get(i); + if(msg == null) { + if(log.isWarnEnabled() && !local_addr.equals(sender)) { + StringBuilder sb=new StringBuilder(); + sb.append("(requester=").append(sender).append(", local_addr=").append(this.local_addr); + sb.append(") message ").append(sender).append("::").append(i); + sb.append(" not found in retransmission table of ").append(sender).append(":\n").append(win); + log.warn(sb.toString()); + } + continue; + } + + down_prot.down(new Event(Event.MSG, msg)); + num_xmits++; + } + } + } + + + /** + * We need to resend our first message with our conn_id + * @param sender + * @param seqno Resend messages in the range [lowest .. seqno] + */ + private void handleResendingOfFirstMessage(Address sender, long seqno) { + if(log.isTraceEnabled()) + log.trace(local_addr + " <-- SEND_FIRST_SEQNO(" + sender + ")"); + SenderEntry entry=send_table.get(sender); + AckSenderWindow win=entry != null? entry.sent_msgs : null; + if(win == null) { + if(log.isErrorEnabled()) + log.error(local_addr + ": sender window for " + sender + " not found"); + return; + } + long lowest=win.getLowest(); + Message rsp=win.get(lowest); + if(rsp == null) + return; + + // We need to copy the UnicastHeader and put it back into the message because Message.copy() doesn't copy + // the headers and therefore we'd modify the original message in the sender retransmission window + // (https://jira.jboss.org/jira/browse/JGRP-965) + Message copy=rsp.copy(); + Unicast2Header hdr=(Unicast2Header)copy.getHeader(this.id); + Unicast2Header newhdr=hdr.copy(); + newhdr.first=true; + copy.putHeader(this.id, newhdr); + + if(log.isTraceEnabled()) { + StringBuilder sb=new StringBuilder(); + sb.append(local_addr).append(" --> DATA(").append(copy.getDest()).append(": #").append(newhdr.seqno). + append(", conn_id=").append(newhdr.conn_id); + if(newhdr.first) sb.append(", first"); + sb.append(')'); + log.trace(sb); + } + down_prot.down(new Event(Event.MSG, copy)); + + if(++lowest > seqno) + return; + for(long i=lowest; i <= seqno; i++) { + rsp=win.get(i); + if(rsp != null) + down_prot.down(new Event(Event.MSG, rsp)); + } + } + + + + + + private short getNewConnectionId() { + synchronized(this) { + short retval=last_conn_id; + if(last_conn_id >= Short.MAX_VALUE || last_conn_id < 0) + last_conn_id=0; + else + last_conn_id++; + return retval; + } + } + + private void sendRequestForFirstSeqno(Address dest, long seqno_received) { + Message msg=new Message(dest); + msg.setFlag(Message.OOB); + Unicast2Header hdr=Unicast2Header.createSendFirstSeqnoHeader(seqno_received); + msg.putHeader(this.id, hdr); + if(log.isTraceEnabled()) + log.trace(local_addr + " --> SEND_FIRST_SEQNO(" + dest + ")"); + down_prot.down(new Event(Event.MSG, msg)); + } + + + /** + * The following types and fields are serialized: + *
        +     * | DATA | seqno | conn_id | first |
        +     * | ACK  | seqno |
        +     * | SEND_FIRST_SEQNO | seqno |
        +     * 
        + */ + public static class Unicast2Header extends Header { + public static final byte DATA = 0; + public static final byte XMIT_REQ = 1; + public static final byte SEND_FIRST_SEQNO = 2; + public static final byte STABLE = 3; + + byte type; + long seqno; // DATA, XMIT_REQ and STABLE + long high_seqno; // XMIT_REQ and STABLE + short conn_id; // DATA + boolean first; // DATA + + + public Unicast2Header() {} // used for externalization + + public static Unicast2Header createDataHeader(long seqno, short conn_id, boolean first) { + return new Unicast2Header(DATA, seqno, 0L, conn_id, first); + } + + public static Unicast2Header createXmitReqHeader(long low, long high) { + Unicast2Header retval=new Unicast2Header(XMIT_REQ, low); + retval.high_seqno=high; + return retval; + } + + public static Unicast2Header createStableHeader(long low, long high) { + Unicast2Header retval=new Unicast2Header(STABLE, low); + retval.high_seqno=high; + return retval; + } + + public static Unicast2Header createSendFirstSeqnoHeader(long seqno_received) { + return new Unicast2Header(SEND_FIRST_SEQNO, seqno_received); + } + + private Unicast2Header(byte type, long seqno) { + this.type=type; + this.seqno=seqno; + } + + private Unicast2Header(byte type, long seqno, long high, short conn_id, boolean first) { + this.type=type; + this.seqno=seqno; + this.high_seqno=high; + this.conn_id=conn_id; + this.first=first; + } + + public long getSeqno() { + return seqno; + } + + public String toString() { + StringBuilder sb=new StringBuilder(); + sb.append(type2Str(type)).append(", seqno=").append(seqno); + if(conn_id != 0) sb.append(", conn_id=").append(conn_id); + if(first) sb.append(", first"); + return sb.toString(); + } + + public static String type2Str(byte t) { + switch(t) { + case DATA: return "DATA"; + case XMIT_REQ: return "XMIT_REQ"; + case SEND_FIRST_SEQNO: return "SEND_FIRST_SEQNO"; + case STABLE: return "STABLE"; + default: return ""; + } + } + + public final int size() { + switch(type) { + case DATA: + return Global.BYTE_SIZE *2 + Global.LONG_SIZE + Global.SHORT_SIZE; + case XMIT_REQ: + case STABLE: + return Global.BYTE_SIZE + Global.LONG_SIZE *2; + case SEND_FIRST_SEQNO: + return Global.BYTE_SIZE; + } + return 0; + } + + public Unicast2Header copy() { + return new Unicast2Header(type, seqno, high_seqno, conn_id, first); + } + + + + public void writeTo(DataOutputStream out) throws IOException { + out.writeByte(type); + switch(type) { + case DATA: + out.writeLong(seqno); + out.writeShort(conn_id); + out.writeBoolean(first); + break; + case XMIT_REQ: + case STABLE: + out.writeLong(seqno); + out.writeLong(high_seqno); + break; + case SEND_FIRST_SEQNO: + out.writeLong(seqno); + break; + } + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + type=in.readByte(); + switch(type) { + case DATA: + seqno=in.readLong(); + conn_id=in.readShort(); + first=in.readBoolean(); + break; + case XMIT_REQ: + case STABLE: + seqno=in.readLong(); + high_seqno=in.readLong(); + break; + case SEND_FIRST_SEQNO: + seqno=in.readLong(); + break; + } + } + } + + + + private static final class SenderEntry { + // stores (and retransmits) msgs sent by us to a certain peer + final AckSenderWindow sent_msgs; + long sent_msgs_seqno=DEFAULT_FIRST_SEQNO; // seqno for msgs sent by us + final short send_conn_id; + final Lock lock=new ReentrantLock(); + + public SenderEntry(short send_conn_id) { + this.send_conn_id=send_conn_id; + sent_msgs=new AckSenderWindow(); + } + + public void lock() { + lock.lock(); + } + + public void unlock() { + lock.unlock(); + } + + void reset() { + if(sent_msgs != null) + sent_msgs.reset(); + sent_msgs_seqno=DEFAULT_FIRST_SEQNO; + } + + public String toString() { + StringBuilder sb=new StringBuilder(); + if(sent_msgs != null) + sb.append(sent_msgs).append(", "); + sb.append("send_conn_id=" + send_conn_id); + return sb.toString(); + } + } + + private static final class ReceiverEntry { + private final NakReceiverWindow received_msgs; // stores all msgs rcvd by a certain peer in seqno-order + private final long recv_conn_id; + final AtomicInteger received_bytes=new AtomicInteger(0); + final Lock received_bytes_lock=new ReentrantLock(); + + private long last_highest=-1; + private int num_stable_msgs=0; + public final int max_stable_msgs; + + + + public ReceiverEntry(NakReceiverWindow received_msgs, long recv_conn_id, final int max_stable_msgs) { + this.received_msgs=received_msgs; + this.recv_conn_id=recv_conn_id; + this.max_stable_msgs=max_stable_msgs; + } + + void reset() { + if(received_msgs != null) + received_msgs.destroy(); + received_bytes.set(0); + last_highest=-1; + num_stable_msgs=0; + } + + + public String toString() { + StringBuilder sb=new StringBuilder(); + if(received_msgs != null) + sb.append(received_msgs).append(", "); + sb.append("recv_conn_id=" + recv_conn_id); + return sb.toString(); + } + } + + + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/UNICAST.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/UNICAST.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/UNICAST.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/UNICAST.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,27 +1,24 @@ package org.jgroups.protocols; import org.jgroups.*; -import org.jgroups.annotations.MBean; -import org.jgroups.annotations.ManagedAttribute; -import org.jgroups.annotations.ManagedOperation; -import org.jgroups.annotations.Property; +import org.jgroups.annotations.*; import org.jgroups.conf.PropertyConverters; import org.jgroups.stack.AckReceiverWindow; import org.jgroups.stack.AckSenderWindow; import org.jgroups.stack.Protocol; import org.jgroups.stack.StaticInterval; -import org.jgroups.util.BoundedList; -import org.jgroups.util.Streamable; +import org.jgroups.util.AgeOutCache; import org.jgroups.util.TimeScheduler; +import org.jgroups.util.Tuple; import org.jgroups.util.Util; -import java.io.*; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Vector; -import java.util.concurrent.atomic.AtomicInteger; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -42,98 +39,66 @@ * whenever a message is received: the new message is added and then we try to remove as many messages as * possible (until we stop at a gap, or there are no more messages). * @author Bela Ban - * @version $Id: UNICAST.java,v 1.118 2008/10/21 08:19:08 vlada Exp $ */ @MBean(description="Reliable unicast layer") -public class UNICAST extends Protocol implements AckSenderWindow.RetransmitCommand { +@DeprecatedProperty(names={"immediate_ack", "use_gms", "enabled_mbrs_timeout", "eager_lock_release"}) +public class UNICAST extends Protocol implements AckSenderWindow.RetransmitCommand, AgeOutCache.Handler
        { + public static final long DEFAULT_FIRST_SEQNO=Global.DEFAULT_FIRST_UNICAST_SEQNO; - - private static final String name="UNICAST"; - - private static final long DEFAULT_FIRST_SEQNO=1; - - - /* ------------------------------------------ Properties ------------------------------------------ */ - - - @Property(description="If GMS should be consulted for the membership on retransmit. Default is true") - private boolean use_gms=true; - @Property(description="Acks a message before it is processed by the application to limit unnecessary retransmits. Default is false") - private boolean immediate_ack=false; + /* ------------------------------------------ Properties ------------------------------------------ */ - @Property(description="Whether to loop back messages sent to self. Default is false") + + @Property(description="Whether to loop back messages sent to self. Default is false", + deprecatedMessage="might get removed soon as it can destroy ordering. " + + "See https://jira.jboss.org/jira/browse/JGRP-1092 for details") + @Deprecated private boolean loopback=false; - - + + private long[] timeout= { 400, 800, 1600, 3200 }; // for AckSenderWindow: max time to wait for missing acks - /** - * By default, we release the lock on the sender in up() after the up() - * method call passed up the stack returns. However, with eager_lock_release - * enabled (default), we release the lock as soon as the application calls - * Channel.down() within the receive() callback. This leads to - * issues as the one described in - * http://jira.jboss.com/jira/browse/JGRP-656. Note that ordering is - * still correct , but messages from self might get delivered - * concurrently. This can be turned off by setting eager_lock_release to - * false. - */ - @Property(description="See http://jira.jboss.com/jira/browse/JGRP-656. Default is true") - private boolean eager_lock_release=true; - - - - - /* --------------------------------------------- JMX ---------------------------------------------- */ - - + @Property(description="Max number of messages to be removed from the AckReceiverWindow. This property might " + + "get removed anytime, so don't use it !") + private int max_msg_batch_size=50000; + + /* --------------------------------------------- JMX ---------------------------------------------- */ + + private long num_msgs_sent=0, num_msgs_received=0, num_bytes_sent=0, num_bytes_received=0; - private long num_acks_sent=0, num_acks_received=0, num_xmit_requests_received=0; + private long num_acks_sent=0, num_acks_received=0, num_xmits=0; + - - /* --------------------------------------------- Fields ------------------------------------------------ */ - - + + private final ConcurrentMap send_table=Util.createConcurrentMap(); + private final ConcurrentMap recv_table=Util.createConcurrentMap(); + + protected final ReentrantLock recv_table_lock=new ReentrantLock(); + private final Vector
        members=new Vector
        (11); - - private final HashMap connections=new HashMap(11); - + private Address local_addr=null; - + private TimeScheduler timer=null; // used for retransmissions (passed to AckSenderWindow) - - private Map locks; private boolean started=false; - /** - * A list of members who left, used to determine when to prevent sending - * messages to left mbrs - */ - private final BoundedList
        previous_members=new BoundedList
        (50); - /** - * Contains all members that were enabled for unicasts by - * Event.ENABLE_UNICAST_TO - */ - private final BoundedList
        enabled_members=new BoundedList
        (100); - + // didn't make this 'connected' in case we need to send early acks which may race to the socket + private volatile boolean disconnected=false; - /** Regular messages which have been added, but not removed */ - private final AtomicInteger undelivered_msgs=new AtomicInteger(0); + private short last_conn_id=0; - - - - @ManagedAttribute - public int getUndeliveredMessages() { - return undelivered_msgs.get(); - } + protected long max_retransmit_time=60 * 1000L; + + private AgeOutCache
        cache=null; - /** All protocol names have to be unique ! */ - public String getName() {return name;} + + @Deprecated @ManagedAttribute + public static int getUndeliveredMessages() { + return 0; + } public long[] getTimeout() {return timeout;} @@ -143,15 +108,32 @@ timeout=val; } + public void setMaxMessageBatchSize(int size) { + if(size >= 1) + max_msg_batch_size=size; + } + @ManagedAttribute public String getLocalAddress() {return local_addr != null? local_addr.toString() : "null";} @ManagedAttribute public String getMembers() {return members != null? members.toString() : "[]";} + + @ManagedOperation public String printConnections() { StringBuilder sb=new StringBuilder(); - for(Map.Entry entry: connections.entrySet()) { - sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); + if(!send_table.isEmpty()) { + sb.append("\nsend connections:\n"); + for(Map.Entry entry: send_table.entrySet()) { + sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); + } + } + + if(!recv_table.isEmpty()) { + sb.append("\nreceive connections:\n"); + for(Map.Entry entry: recv_table.entrySet()) { + sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); + } } return sb.toString(); } @@ -175,7 +157,7 @@ public long getNumBytesReceived() { return num_bytes_received; } - + @ManagedAttribute public long getNumAcksSent() { return num_acks_sent; @@ -187,36 +169,55 @@ } @ManagedAttribute - public long getNumberOfRetransmitRequestsReceived() { - return num_xmit_requests_received; + public long getNumberOfRetransmissions() { + return num_xmits; + } + + public long getMaxRetransmitTime() { + return max_retransmit_time; + } + + @Property(description="Max number of milliseconds we try to retransmit a message to any given member. After that, " + + "the connection is removed. Any new connection to that member will start with seqno #1 again. 0 disables this") + public void setMaxRetransmitTime(long max_retransmit_time) { + this.max_retransmit_time=max_retransmit_time; + if(cache != null && max_retransmit_time > 0) + cache.setTimeout(max_retransmit_time); + } + + @ManagedAttribute + public int getAgeOutCacheSize() { + return cache != null? cache.size() : 0; + } + + @ManagedOperation + public String printAgeOutCache() { + return cache != null? cache.toString() : "n/a"; + } + + public AgeOutCache
        getAgeOutCache() { + return cache; } /** The number of messages in all Entry.sent_msgs tables (haven't received an ACK yet) */ @ManagedAttribute public int getNumberOfUnackedMessages() { int num=0; - synchronized(connections) { - for(Entry entry: connections.values()) { + for(SenderEntry entry: send_table.values()) { if(entry.sent_msgs != null) num+=entry.sent_msgs.size(); } - } return num; } @ManagedOperation public String printUnackedMessages() { StringBuilder sb=new StringBuilder(); - Entry e; - Object member; - synchronized(connections) { - for(Map.Entry entry: connections.entrySet()) { - member=entry.getKey(); - e=entry.getValue(); - sb.append(member).append(": "); - if(e.sent_msgs != null) - sb.append(e.sent_msgs.toString()).append("\n"); - } + for(Map.Entry entry: send_table.entrySet()) { + sb.append(entry.getKey()).append(": "); + SenderEntry val=entry.getValue(); + if(val.sent_msgs != null) + sb.append(val.sent_msgs).append("\n"); } return sb.toString(); } @@ -225,24 +226,31 @@ @ManagedAttribute public int getNumberOfMessagesInReceiveWindows() { int num=0; - synchronized(connections) { - for(Entry entry: connections.values()) { - if(entry.received_msgs != null) - num+=entry.received_msgs.size(); - } + for(ReceiverEntry entry: recv_table.values()) { + if(entry.received_msgs != null) + num+=entry.received_msgs.size(); } return num; } public void resetStats() { num_msgs_sent=num_msgs_received=num_bytes_sent=num_bytes_received=num_acks_sent=num_acks_received=0; - num_xmit_requests_received=0; + num_xmits=0; } public Map dumpStats() { - Map m=super.dumpStats(); - m.put("unacked_msgs", printUnackedMessages()); + Map m=super.dumpStats(); + m.put("unacked_msgs", printUnackedMessages()); + m.put("num_msgs_sent", num_msgs_sent); + m.put("num_msgs_received", num_msgs_received); + m.put("num_bytes_sent", num_bytes_sent); + m.put("num_bytes_received", num_bytes_received); + m.put("num_acks_sent", num_acks_sent); + m.put("num_acks_received", num_acks_received); + m.put("num_xmits", num_xmits); + m.put("num_unacked_msgs", getNumberOfUnackedMessages()); + m.put("num_msgs_in_recv_windows", getNumberOfMessagesInReceiveWindows()); return m; } @@ -252,14 +260,14 @@ timer=getTransport().getTimer(); if(timer == null) throw new Exception("timer is null"); - locks=stack.getLocks(); + if(max_retransmit_time > 0) + cache=new AgeOutCache
        (timer, max_retransmit_time, this); started=true; } public void stop() { started=false; removeAllConnections(); - undelivered_msgs.set(0); } @@ -270,38 +278,34 @@ switch(evt.getType()) { - case Event.MSG: - msg=(Message)evt.getArg(); - dst=msg.getDest(); - - if(dst == null || dst.isMulticastAddress()) // only handle unicast messages - break; // pass up - - // changed from removeHeader(): we cannot remove the header because if we do loopback=true at the - // transport level, we will not have the header on retransmit ! (bela Aug 22 2006) - hdr=(UnicastHeader)msg.getHeader(name); - if(hdr == null) - break; - src=msg.getSrc(); - switch(hdr.type) { - case UnicastHeader.DATA: // received regular message - // only send an ACK if added to the received_msgs table (bela Aug 2006) - // if in immediate_ack mode, send ack inside handleDataReceived - if(handleDataReceived(src, hdr.seqno, msg) && !immediate_ack) - sendAck(src, hdr.seqno); - return null; // we pass the deliverable message up in handleDataReceived() - case UnicastHeader.ACK: // received ACK for previously sent message - handleAckReceived(src, hdr.seqno); - break; - default: - log.error("UnicastHeader type " + hdr.type + " not known !"); - break; - } - return null; - - case Event.SET_LOCAL_ADDRESS: - local_addr=(Address)evt.getArg(); - break; + case Event.MSG: + msg=(Message)evt.getArg(); + dst=msg.getDest(); + + if(dst == null || dst.isMulticastAddress() || msg.isFlagSet(Message.NO_RELIABILITY)) // only handle unicast messages + break; // pass up + + // changed from removeHeader(): we cannot remove the header because if we do loopback=true at the + // transport level, we will not have the header on retransmit ! (bela Aug 22 2006) + hdr=(UnicastHeader)msg.getHeader(this.id); + if(hdr == null) + break; + src=msg.getSrc(); + switch(hdr.type) { + case UnicastHeader.DATA: // received regular message + handleDataReceived(src, hdr.seqno, hdr.conn_id, hdr.first, msg, evt); + return null; // we pass the deliverable message up in handleDataReceived() + case UnicastHeader.ACK: // received ACK for previously sent message + handleAckReceived(src, hdr.seqno); + break; + case UnicastHeader.SEND_FIRST_SEQNO: + handleResendingOfFirstMessage(src); + break; + default: + log.error("UnicastHeader type " + hdr.type + " not known !"); + break; + } + return null; } return up_prot.up(evt); // Pass up to the layer above us @@ -317,9 +321,8 @@ Address dst=msg.getDest(); /* only handle unicast messages */ - if (dst == null || dst.isMulticastAddress()) { + if (dst == null || dst.isMulticastAddress() || msg.isFlagSet(Message.NO_RELIABILITY)) break; - } if(!started) { if(log.isTraceEnabled()) @@ -327,58 +330,69 @@ return null; } - // if the dest is self --> pass the message back up - if(loopback && local_addr != null && local_addr.equals(dst)) { - msg.setSrc(local_addr); - up_prot.up(evt); - num_msgs_sent++; - num_bytes_sent+=msg.getLength(); - return null; - } - - if(!members.contains(dst) && !enabled_members.contains(dst)) { - throw new IllegalArgumentException(dst + " is not a member of the group " + members + " (enabled_members=" + enabled_members + ")"); - } - - Entry entry; - synchronized(connections) { - entry=connections.get(dst); - if(entry == null) { - entry=new Entry(); - connections.put(dst, entry); + SenderEntry entry=send_table.get(dst); + if(entry == null) { + entry=new SenderEntry(getNewConnectionId(), this, timeout, timer, local_addr); + SenderEntry existing=send_table.putIfAbsent(dst, entry); + if(existing != null) + entry=existing; + else { if(log.isTraceEnabled()) - log.trace(local_addr + ": created new connection for dst " + dst); + log.trace(local_addr + ": created connection to " + dst); + if(cache != null && !members.contains(dst)) + cache.add(dst); } } long seqno=-2; - synchronized(entry) { // threads will only sync if they access the same entry + short send_conn_id=-1; + UnicastHeader hdr; + + entry.lock(); // threads will only sync if they access the same entry + try { + seqno=entry.sent_msgs_seqno; + send_conn_id=entry.send_conn_id; + hdr=UnicastHeader.createDataHeader(seqno, send_conn_id, seqno == DEFAULT_FIRST_SEQNO); + msg.putHeader(this.id, hdr); + + // AckSenderWindow.add() is costly as it calls Retransmitter.add() which calls TimeScheduler.schedule(), + // which adds the scheduled task to a DelayQueue, which does costly tree rebalancing. + // In 2.9 (or 3.0), we'll replace use of ScheduledThreadPoolExecutor in TimeScheduler with + // a ConcurrentSkipListMap, which is faster (log(n) access cost for most ops). CSLM requires JDK 1.6 + // Note that moving the next statement out of the lock scope made for some really ugly code, that's + // why this was reverted ! + + // entry.sent_msgs.add(seqno, msg); // add *including* UnicastHeader, adds to retransmitter + entry.sent_msgs.addToMessages(seqno, msg); // add *including* UnicastHeader, adds to retransmitter + entry.sent_msgs_seqno++; + } + finally { + entry.unlock(); + } + + long sleep_time=100; + for(int i=1; i <= 10; i++) { try { - seqno=entry.sent_msgs_seqno; - UnicastHeader hdr=new UnicastHeader(UnicastHeader.DATA, seqno); - if(entry.sent_msgs == null) { // first msg to peer 'dst' - entry.sent_msgs=new AckSenderWindow(this, new StaticInterval(timeout), timer, this.local_addr); // use the global timer - } - msg.putHeader(name, hdr); - if(log.isTraceEnabled()) - log.trace(new StringBuilder().append(local_addr).append(" --> DATA(").append(dst).append(": #"). - append(seqno)); - if(entry.sent_msgs != null) - entry.sent_msgs.add(seqno, msg); // add *including* UnicastHeader, adds to retransmitter - entry.sent_msgs_seqno++; + entry.sent_msgs.addToRetransmitter(seqno, msg); // adds to retransmitter + break; } catch(Throwable t) { - if(entry.sent_msgs != null) - entry.sent_msgs.ack(seqno); // remove seqno again, so it is not transmitted - if(t instanceof Error) - throw (Error)t; - if(t instanceof RuntimeException) - throw (RuntimeException)t; - else { - throw new RuntimeException("failure adding msg " + msg + " to the retransmit table", t); - } + log.error("failed adding message " + seqno + " to " + dst + + " to the retransmitter, retrying... (attempt #" + i + ")"); + Util.sleep(sleep_time); + sleep_time*=2; } } + + if(log.isTraceEnabled()) { + StringBuilder sb=new StringBuilder(); + sb.append(local_addr).append(" --> DATA(").append(dst).append(": #").append(seqno). + append(", conn_id=").append(send_conn_id); + if(hdr.first) sb.append(", first"); + sb.append(')'); + log.trace(sb); + } + // moved passing down of message out of the synchronized block: similar to NAKACK, we do *not* need // to send unicast messages in order of sequence numbers because they will be sorted into the correct // order at the receiver anyway. Of course, most of the time, the order will be correct (FIFO), so @@ -394,74 +408,37 @@ case Event.VIEW_CHANGE: // remove connections to peers that are not members anymore ! View view=(View)evt.getArg(); Vector
        new_members=view.getMembers(); - Vector
        left_members; + Set
        non_members=new HashSet
        (send_table.keySet()); + non_members.addAll(recv_table.keySet()); + synchronized(members) { - left_members=Util.determineLeftMembers(members, new_members); members.clear(); if(new_members != null) members.addAll(new_members); - } - - // Remove all connections for members that left between the current view and the new view - // See DESIGN for details - boolean rc; - if(use_gms && !left_members.isEmpty()) { - for(Address mbr: left_members) { - rc=removeConnection(mbr); // adds to previous_members - if(rc && log.isTraceEnabled()) - log.trace("removed " + mbr + " from connection table, member(s) " + left_members + " left"); - } - } - // code by Matthias Weber May 23 2006 - for(Address mbr: previous_members) { - if(members.contains(mbr)) { - if(previous_members.remove(mbr)) { - if(log.isTraceEnabled()) - log.trace("removed " + mbr + " from previous_members as result of VIEW_CHANGE event, " + - "previous_members=" + previous_members); - } - } - } - // remove all members from enabled_members - synchronized(members) { - for(Address mbr: members) { - enabled_members.remove(mbr); + non_members.removeAll(members); + if(cache != null) { + cache.removeAll(members); } } - synchronized(previous_members) { - for(Address mbr: previous_members) { - enabled_members.remove(mbr); - } + if(!non_members.isEmpty()) { + if(log.isTraceEnabled()) + log.trace("removing non members " + non_members); + for(Address non_mbr: non_members) + removeConnection(non_mbr); } + break; - // trash connections to/from members who are in the merge view, fix for: http://jira.jboss.com/jira/browse/JGRP-348 - // update (bela, April 25 2008): reverted because of http://jira.jboss.com/jira/browse/JGRP-659, we fix this - // in 2.7 only as we don't want to change the serialization format. The JIRA issue is - // http://jira.jboss.com/jira/browse/JGRP-742 -// if(view instanceof MergeView) { -// if(log.isTraceEnabled()) -// log.trace("removing all connections for the current members due to a merge"); -// removeConnections(members); -// } - + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); break; - case Event.ENABLE_UNICASTS_TO: - Address member=(Address)evt.getArg(); - if(!enabled_members.contains(member)) - enabled_members.add(member); - boolean removed=previous_members.remove(member); - if(removed && log.isTraceEnabled()) - log.trace("removing " + member + " from previous_members as result of ENABLE_UNICAST_TO event, " + - "previous_members=" + previous_members); + case Event.CONNECT: + disconnected=false; break; - case Event.DISABLE_UNICASTS_TO: - member=(Address)evt.getArg(); - removeConnection(member); - enabled_members.remove(member); - previous_members.remove(member); + case Event.DISCONNECT: + disconnected=true; break; } @@ -475,73 +452,56 @@ num_bytes_sent+=msg.getLength(); } - /** Removes and resets from connection table (which is already locked). Returns true if member was found, otherwise false */ - private boolean removeConnection(Address mbr) { - Entry entry; - - synchronized(connections) { - entry=connections.remove(mbr); - if(!previous_members.contains(mbr)) - previous_members.add(mbr); - } - if(entry != null) { + /** + * Removes and resets from connection table (which is already locked). Returns true if member was found, + * otherwise false. This method is public only so it can be invoked by unit testing, but should not otherwise be + * used ! + */ + public void removeConnection(Address mbr) { + SenderEntry entry=send_table.remove(mbr); + if(entry != null) entry.reset(); - return true; - } - else - return false; - } - - private void removeAllConnections() { - synchronized(connections) { - for(Entry entry: connections.values()) { - entry.reset(); - } - connections.clear(); - } + ReceiverEntry entry2=recv_table.remove(mbr); + if(entry2 != null) + entry2.reset(); } + /** + * This method is public only so it can be invoked by unit testing, but should not otherwise be used ! + */ + @ManagedOperation(description="Trashes all connections to other nodes. This is only used for testing") + public void removeAllConnections() { + for(SenderEntry entry: send_table.values()) + entry.reset(); + send_table.clear(); - private void removeConnections(List
        mbrs) { - Entry entry; - synchronized(connections) { - for(Address mbr: mbrs) { - entry=connections.remove(mbr); - if(entry != null) - entry.reset(); - } - } + for(ReceiverEntry entry2: recv_table.values()) + entry2.reset(); + recv_table.clear(); } /** Called by AckSenderWindow to resend messages for which no ACK has been received yet */ public void retransmit(long seqno, Message msg) { - Object dst=msg.getDest(); - - // bela Dec 23 2002: - // this will remove a member on a MERGE request, e.g. A and B merge: when A sends the unicast - // request to B and there's a retransmit(), B will be removed ! - - // if(use_gms && !members.contains(dst) && !prev_members.contains(dst)) { - // - // if(log.isWarnEnabled()) log.warn("UNICAST.retransmit()", "seqno=" + seqno + ": dest " + dst + - // " is not member any longer; removing entry !"); - - // synchronized(connections) { - // removeConnection(dst); - // } - // return; - // } - if(log.isTraceEnabled()) - log.trace("[" + local_addr + "] --> XMIT(" + dst + ": #" + seqno + ')'); - + log.trace(local_addr + " --> XMIT(" + msg.getDest() + ": #" + seqno + ')'); down_prot.down(new Event(Event.MSG, msg)); - num_xmit_requests_received++; + num_xmits++; } + /** + * Called by AgeOutCache, to removed expired connections + * @param key + */ + public void expired(Address key) { + if(key != null) { + if(log.isDebugEnabled()) + log.debug("removing connection to " + key + " because it expired"); + removeConnection(key); + } + } @@ -550,241 +510,422 @@ * Check whether the hashtable contains an entry e for sender (create if not). If * e.received_msgs is null and first is true: create a new AckReceiverWindow(seqno) and * add message. Set e.received_msgs to the new window. Else just add the message. - * @return boolean True if we can send an ack, false otherwise */ - private boolean handleDataReceived(Address sender, long seqno, Message msg) { - if(log.isTraceEnabled()) - log.trace(new StringBuilder().append(local_addr).append(" <-- DATA(").append(sender).append(": #").append(seqno)); - - if(previous_members.contains(sender)) { - // we don't want to see messages from departed members - if(seqno > DEFAULT_FIRST_SEQNO) { - if(log.isTraceEnabled()) - log.trace("discarding message " + seqno + " from previous member " + sender); - return false; // don't ack this message so the sender keeps resending it ! - } - if(log.isTraceEnabled()) - log.trace("removed " + sender + " from previous_members as we received a message from it"); - previous_members.remove(sender); + protected void handleDataReceived(Address sender, long seqno, long conn_id, boolean first, Message msg, Event evt) { + if(log.isTraceEnabled()) { + StringBuilder sb=new StringBuilder(); + sb.append(local_addr).append(" <-- DATA(").append(sender).append(": #").append(seqno); + if(conn_id != 0) sb.append(", conn_id=").append(conn_id); + if(first) sb.append(", first"); + sb.append(')'); + log.trace(sb); } - Entry entry; AckReceiverWindow win; - synchronized(connections) { - entry=connections.get(sender); - if(entry == null) { - entry=new Entry(); - connections.put(sender, entry); - if(log.isTraceEnabled()) - log.trace(local_addr + ": created new connection for dst " + sender); - } - win=entry.received_msgs; - if(win == null) { - win=new AckReceiverWindow(DEFAULT_FIRST_SEQNO); - entry.received_msgs=win; + + recv_table_lock.lock(); + try { + ReceiverEntry entry=recv_table.get(sender); + win=entry != null? entry.received_msgs : null; + if(first) { + if(entry == null) { + entry=getOrCreateReceiverEntry(sender, seqno, conn_id); + win=entry.received_msgs; + } + else { // entry != null && win != null + if(conn_id != entry.recv_conn_id) { + if(log.isTraceEnabled()) + log.trace(local_addr + ": conn_id=" + conn_id + " != " + entry.recv_conn_id + "; resetting receiver window"); + + ReceiverEntry entry2=recv_table.remove(sender); + if(entry2 != null) + entry2.received_msgs.reset(); + + entry=getOrCreateReceiverEntry(sender, seqno, conn_id); + win=entry.received_msgs; + } + else { + ; + } + } } + else { // entry == null && win == null OR entry != null && win == null OR entry != null && win != null + if(win == null || entry.recv_conn_id != conn_id) { + recv_table_lock.unlock(); + sendRequestForFirstSeqno(sender); // drops the message and returns (see below) + return; + } + } + } + finally { + if(recv_table_lock.isHeldByCurrentThread()) + recv_table_lock.unlock(); } - boolean added=win.add(seqno, msg); // entry.received_msgs is guaranteed to be non-null if we get here - boolean regular_msg_added=added && !msg.isFlagSet(Message.OOB); + byte result=win.add2(seqno, msg); // win is guaranteed to be non-null if we get here + boolean added=result > 0; num_msgs_received++; num_bytes_received+=msg.getLength(); - // http://jira.jboss.com/jira/browse/JGRP-713: // send the ack back *before* we process the message - // to limit unnecessary retransmits - if(immediate_ack) - sendAck(sender, seqno); // send an ack regardless of whether the message was added (stops retransmission) + // Cannot be replaced with if(!added), see https://jira.jboss.org/jira/browse/JGRP-1043 comment 15/Sep/09 06:57 AM - // message is passed up if OOB. Later, when remove() is called, we discard it. This affects ordering ! - // http://jira.jboss.com/jira/browse/JGRP-377 - if(msg.isFlagSet(Message.OOB)) { - if(added) - up_prot.up(new Event(Event.MSG, msg)); - win.removeOOBMessage(); // if we only have OOB messages, we'd never remove them ! - if(!(win.hasMessagesToRemove() && undelivered_msgs.get() > 0)) - return true; + // We *have* to do send the ACK, to cover the following scenario: + // - A sends #3 to B + // - B removes #3 and sends ACK(3) to A. B's next_to_remove is now 4 + // - B's ACK(3) to A is dropped by the network + // - A keeps retransmitting #3 to B, until it gets an ACK(3) + // -B will never ACK #3 if the 2 lines below are commented ==> endless retransmission of A's #3 ! + if(result == -1) { // only ack if seqno was < next_to_remove ! + sendAck(sender, seqno); } - if(!added && !win.hasMessagesToRemove()) { // no ack if we didn't add the msg (e.g. duplicate) - return true; // ack the message, because this will stop retransmissions (which are unreliable) ! + // An OOB message is passed up immediately. Later, when remove() is called, we discard it. This affects ordering ! + // http://jira.jboss.com/jira/browse/JGRP-377 + if(msg.isFlagSet(Message.OOB) && added) { + try { + up_prot.up(evt); + } + catch(Throwable t) { + log.error("couldn't deliver OOB message " + msg, t); + } } final AtomicBoolean processing=win.getProcessing(); if(!processing.compareAndSet(false, true)) { - return true; + return; } - // Try to remove (from the AckReceiverWindow) as many messages as possible as pass them up - Message m; - boolean released_processing=false; - short removed_regular_msgs=0; + // try to remove (from the AckReceiverWindow) as many messages as possible as pass them up // Prevents concurrent passing up of messages by different threads (http://jira.jboss.com/jira/browse/JGRP-198); - // this is all the more important once we have a threadless stack (http://jira.jboss.com/jira/browse/JGRP-181), + // this is all the more important once we have a concurrent stack (http://jira.jboss.com/jira/browse/JGRP-181), // where lots of threads can come up to this point concurrently, but only 1 is allowed to pass at a time // We *can* deliver messages from *different* senders concurrently, e.g. reception of P1, Q1, P2, Q2 can result in // delivery of P1, Q1, Q2, P2: FIFO (implemented by UNICAST) says messages need to be delivered only in the // order in which they were sent by their senders - ReentrantLock lock=win.getLock(); try { - if(eager_lock_release) - locks.put(Thread.currentThread(), lock); - lock.lock(); // we don't block on entry any more (http://jira.jboss.com/jira/browse/JGRP-485) while(true) { - m=win.remove(processing); - if(m == null) { - released_processing=true; - return true; - } - - // discard OOB msg as it has already been delivered (http://jira.jboss.com/jira/browse/JGRP-377) - if(m.isFlagSet(Message.OOB)) { - continue; + Tuple,Long> tuple=win.removeMany(max_msg_batch_size); + if(tuple == null) + return; + List msgs=tuple.getVal1(); + if(msgs.isEmpty()) + return; + + long highest_removed=tuple.getVal2(); + if(highest_removed > 0) + sendAck(sender, highest_removed); // guaranteed not to throw an exception ! + + for(Message m: msgs) { + // discard OOB msg: it has already been delivered (http://jira.jboss.com/jira/browse/JGRP-377) + if(m.isFlagSet(Message.OOB)) + continue; + try { + up_prot.up(new Event(Event.MSG, m)); + } + catch(Throwable t) { + log.error("couldn't deliver message " + m, t); + } } - removed_regular_msgs++; - up_prot.up(new Event(Event.MSG, m)); } } finally { - if(!released_processing) - processing.set(false); - if(eager_lock_release) - locks.remove(Thread.currentThread()); - if(lock.isHeldByCurrentThread()) - lock.unlock(); - // We keep track of regular messages that we added, but couldn't remove (because of ordering). - // When we have such messages pending, then even OOB threads will remove and process them. - // http://jira.jboss.com/jira/browse/JGRP-780 - if(regular_msg_added && removed_regular_msgs == 0) { - undelivered_msgs.incrementAndGet(); - } - - if(removed_regular_msgs > 0) { // regardless of whether a message was added or not ! - int num_msgs_added=regular_msg_added? 1 : 0; - undelivered_msgs.addAndGet(-(removed_regular_msgs -num_msgs_added)); - } + processing.set(false); } } + private ReceiverEntry getOrCreateReceiverEntry(Address sender, long seqno, long conn_id) { + ReceiverEntry entry=new ReceiverEntry(new AckReceiverWindow(seqno), conn_id); + ReceiverEntry entry2=recv_table.putIfAbsent(sender, entry); + if(entry2 != null) + return entry2; + if(log.isTraceEnabled()) + log.trace(local_addr + ": created receiver window for " + sender + " at seqno=#" + seqno + " for conn-id=" + conn_id); + return entry; + } + + /** Add the ACK to hashtable.sender.sent_msgs */ private void handleAckReceived(Address sender, long seqno) { - Entry entry; - AckSenderWindow win; - if(log.isTraceEnabled()) log.trace(new StringBuilder().append(local_addr).append(" <-- ACK(").append(sender). - append(": #").append(seqno).append(')')); - synchronized(connections) { - entry=connections.get(sender); + append(": #").append(seqno).append(')')); + SenderEntry entry=send_table.get(sender); + AckSenderWindow win=entry != null? entry.sent_msgs : null; + if(win != null) { + win.ack(seqno); // removes message from retransmission + num_acks_received++; } - if(entry == null || entry.sent_msgs == null) - return; - win=entry.sent_msgs; - win.ack(seqno); // removes message from retransmission - num_acks_received++; } + /** + * We need to resend our first message with our conn_id + * @param sender + */ + private void handleResendingOfFirstMessage(Address sender) { + if(log.isTraceEnabled()) + log.trace(local_addr + " <-- SEND_FIRST_SEQNO(" + sender + ")"); + SenderEntry entry=send_table.get(sender); + AckSenderWindow win=entry != null? entry.sent_msgs : null; + if(win == null) { + if(log.isErrorEnabled()) + log.error(local_addr + ": sender window for " + sender + " not found"); + return; + } + Message rsp=win.getLowestMessage(); + if(rsp == null) + return; + + // We need to copy the UnicastHeader and put it back into the message because Message.copy() doesn't copy + // the headers and therefore we'd modify the original message in the sender retransmission window + // (https://jira.jboss.org/jira/browse/JGRP-965) + Message copy=rsp.copy(); + UnicastHeader hdr=(UnicastHeader)copy.getHeader(this.id); + UnicastHeader newhdr=hdr.copy(); + newhdr.first=true; + copy.putHeader(this.id, newhdr); + + if(log.isTraceEnabled()) { + StringBuilder sb=new StringBuilder(); + sb.append(local_addr).append(" --> DATA(").append(copy.getDest()).append(": #").append(newhdr.seqno). + append(", conn_id=").append(newhdr.conn_id); + if(newhdr.first) sb.append(", first"); + sb.append(')'); + log.trace(sb); + } + + down_prot.down(new Event(Event.MSG, copy)); + } + private void sendAck(Address dst, long seqno) { + if(disconnected) // if we are disconnected, then don't send any acks which throw exceptions on shutdown + return; Message ack=new Message(dst); // commented Jan 23 2008 (bela): TP.enable_unicast_bundling should decide whether we bundle or not, and *not* // the OOB flag ! Bundling UNICAST ACKs should be really fast + + // https://jira.jboss.org/jira/browse/JGRP-1125; will be reverted in 2.10 // ack.setFlag(Message.OOB); - ack.putHeader(name, new UnicastHeader(UnicastHeader.ACK, seqno)); + + ack.putHeader(this.id, UnicastHeader.createAckHeader(seqno)); if(log.isTraceEnabled()) log.trace(new StringBuilder().append(local_addr).append(" --> ACK(").append(dst). - append(": #").append(seqno).append(')')); - down_prot.down(new Event(Event.MSG, ack)); - num_acks_sent++; + append(": #").append(seqno).append(')')); + try { + down_prot.down(new Event(Event.MSG, ack)); + num_acks_sent++; + } + catch(Throwable t) { + log.error("failed sending ACK(" + seqno + ") to " + dst, t); + } } + + private short getNewConnectionId() { + synchronized(this) { + short retval=last_conn_id; + if(last_conn_id >= Short.MAX_VALUE || last_conn_id < 0) + last_conn_id=0; + else + last_conn_id++; + return retval; + } + } + private void sendRequestForFirstSeqno(Address dest) { + Message msg=new Message(dest); + msg.setFlag(Message.OOB); + UnicastHeader hdr=UnicastHeader.createSendFirstSeqnoHeader(); + msg.putHeader(this.id, hdr); + if(log.isTraceEnabled()) + log.trace(local_addr + " --> SEND_FIRST_SEQNO(" + dest + ")"); + down_prot.down(new Event(Event.MSG, msg)); + } + /** + * The following types and fields are serialized: + *
        +     * | DATA | seqno | conn_id | first |
        +     * | ACK  | seqno |
        +     * | SEND_FIRST_SEQNO |
        +     * 
        + */ + public static class UnicastHeader extends Header { + public static final byte DATA = 0; + public static final byte ACK = 1; + public static final byte SEND_FIRST_SEQNO = 2; + + byte type; + long seqno; // DATA and ACK + short conn_id; // DATA + boolean first; // DATA - public static class UnicastHeader extends Header implements Streamable { - public static final byte DATA=0; - public static final byte ACK=1; - byte type=DATA; - long seqno=0; + public UnicastHeader() {} // used for externalization - static final int serialized_size=Global.BYTE_SIZE + Global.LONG_SIZE; - private static final long serialVersionUID=-5590873777959784299L; + public static UnicastHeader createDataHeader(long seqno, short conn_id, boolean first) { + return new UnicastHeader(DATA, seqno, conn_id, first); + } + public static UnicastHeader createAckHeader(long seqno) { + return new UnicastHeader(ACK, seqno); + } - public UnicastHeader() {} // used for externalization + public static UnicastHeader createSendFirstSeqnoHeader() { + return new UnicastHeader(SEND_FIRST_SEQNO, 0L); + } - public UnicastHeader(byte type, long seqno) { + private UnicastHeader(byte type, long seqno) { this.type=type; this.seqno=seqno; } + private UnicastHeader(byte type, long seqno, short conn_id, boolean first) { + this.type=type; + this.seqno=seqno; + this.conn_id=conn_id; + this.first=first; + } + + public long getSeqno() { + return seqno; + } + public String toString() { - return "[UNICAST: " + type2Str(type) + ", seqno=" + seqno + ']'; + StringBuilder sb=new StringBuilder(); + sb.append(type2Str(type)).append(", seqno=").append(seqno); + if(conn_id != 0) sb.append(", conn_id=").append(conn_id); + if(first) sb.append(", first"); + return sb.toString(); } public static String type2Str(byte t) { switch(t) { case DATA: return "DATA"; case ACK: return "ACK"; + case SEND_FIRST_SEQNO: return "SEND_FIRST_SEQNO"; default: return ""; } } public final int size() { - return serialized_size; + switch(type) { + case DATA: + return Global.BYTE_SIZE *2 + Global.LONG_SIZE + Global.SHORT_SIZE; + case ACK: + return Global.BYTE_SIZE + Global.LONG_SIZE; + case SEND_FIRST_SEQNO: + return Global.BYTE_SIZE; + } + return 0; } - - public void writeExternal(ObjectOutput out) throws IOException { - out.writeByte(type); - out.writeLong(seqno); + public UnicastHeader copy() { + return new UnicastHeader(type, seqno, conn_id, first); } - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - type=in.readByte(); - seqno=in.readLong(); - } - public void writeTo(DataOutputStream out) throws IOException { out.writeByte(type); - out.writeLong(seqno); + switch(type) { + case DATA: + out.writeLong(seqno); + out.writeShort(conn_id); + out.writeBoolean(first); + break; + case ACK: + out.writeLong(seqno); + break; + case SEND_FIRST_SEQNO: + break; + } } public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { type=in.readByte(); - seqno=in.readLong(); + switch(type) { + case DATA: + seqno=in.readLong(); + conn_id=in.readShort(); + first=in.readBoolean(); + break; + case ACK: + seqno=in.readLong(); + break; + case SEND_FIRST_SEQNO: + break; + } } } - private static final class Entry { - AckReceiverWindow received_msgs=null; // stores all msgs rcvd by a certain peer in seqno-order - AckSenderWindow sent_msgs=null; // stores (and retransmits) msgs sent by us to a certain peer - long sent_msgs_seqno=DEFAULT_FIRST_SEQNO; // seqno for msgs sent by us + + + private static final class SenderEntry { + // stores (and retransmits) msgs sent by us to a certain peer + final AckSenderWindow sent_msgs; + long sent_msgs_seqno=DEFAULT_FIRST_SEQNO; // seqno for msgs sent by us + final short send_conn_id; + final Lock lock=new ReentrantLock(); + + public SenderEntry(short send_conn_id, AckSenderWindow.RetransmitCommand cmd, long[] timeout, + TimeScheduler timer, Address local_addr) { + this.send_conn_id=send_conn_id; + sent_msgs=new AckSenderWindow(cmd, new StaticInterval(timeout), timer, local_addr); + } + + public void lock() { + lock.lock(); + } + + public void unlock() { + lock.unlock(); + } void reset() { if(sent_msgs != null) sent_msgs.reset(); + sent_msgs_seqno=DEFAULT_FIRST_SEQNO; + } + + public String toString() { + StringBuilder sb=new StringBuilder(); + if(sent_msgs != null) + sb.append(sent_msgs).append(", "); + sb.append("send_conn_id=" + send_conn_id); + return sb.toString(); + } + } + + private static final class ReceiverEntry { + private final AckReceiverWindow received_msgs; // stores all msgs rcvd by a certain peer in seqno-order + private final long recv_conn_id; + + + public ReceiverEntry(AckReceiverWindow received_msgs, long recv_conn_id) { + this.received_msgs=received_msgs; + this.recv_conn_id=recv_conn_id; + } + + void reset() { if(received_msgs != null) received_msgs.reset(); - sent_msgs_seqno=DEFAULT_FIRST_SEQNO; } public String toString() { StringBuilder sb=new StringBuilder(); - if(sent_msgs != null) - sb.append("sent_msgs=").append(sent_msgs).append('\n'); if(received_msgs != null) - sb.append("received_msgs=").append(received_msgs).append('\n'); + sb.append(received_msgs).append(", "); + sb.append("recv_conn_id=" + recv_conn_id); return sb.toString(); } } -} + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/VERIFY_SUSPECT.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/VERIFY_SUSPECT.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/VERIFY_SUSPECT.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/VERIFY_SUSPECT.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,11 +2,12 @@ package org.jgroups.protocols; import org.jgroups.*; +import org.jgroups.annotations.LocalAddress; +import org.jgroups.annotations.MBean; import org.jgroups.annotations.Property; import org.jgroups.conf.PropertyConverters; import org.jgroups.stack.IpAddress; import org.jgroups.stack.Protocol; -import org.jgroups.util.Streamable; import org.jgroups.util.Util; import java.io.*; @@ -20,12 +21,10 @@ * passes SUSPECT event up the stack, otherwise discards it. Has to be placed somewhere above the FD layer and * below the GMS layer (receiver of the SUSPECT event). Note that SUSPECT events may be reordered by this protocol. * @author Bela Ban - * @version $Id: VERIFY_SUSPECT.java,v 1.38 2008/07/18 15:18:04 vlada Exp $ */ +@MBean(description="Double-checks suspicions reports") public class VERIFY_SUSPECT extends Protocol implements Runnable { - - private static final String name="VERIFY_SUSPECT"; - + /* ------------------------------------------ Properties ------------------------------------------ */ @Property(description="Number of millisecs to wait for a response from a suspected member") @@ -36,19 +35,24 @@ @Property(description="Use InetAddress.isReachable() to verify suspected member instead of regular messages") private boolean use_icmp=false; - - @Property(converter=PropertyConverters.BindAddress.class,description="Interface for ICMP pings. Used if use_icmp is true") + + @LocalAddress + @Property(description="Interface for ICMP pings. Used if use_icmp is true " + + "The following special values are also recognized: GLOBAL, SITE_LOCAL, LINK_LOCAL and NON_LOOPBACK", + systemProperty={Global.BIND_ADDR, Global.BIND_ADDR_OLD}, + defaultValueIPv4=Global.NON_LOOPBACK_ADDRESS, defaultValueIPv6=Global.NON_LOOPBACK_ADDRESS) private InetAddress bind_addr; // interface for ICMP pings - + @Property(name="bind_interface", converter=PropertyConverters.BindInterface.class, + description="The interface (NIC) which should be used by this transport", dependsUpon="bind_addr") + protected String bind_interface_str=null; + /* --------------------------------------------- Fields ------------------------------------------------ */ /** network interface to be used to send the ICMP packets */ private NetworkInterface intf=null; - protected boolean shutting_down=false; - private Address local_addr=null; /**keys=Addresses, vals=time in mcses since added **/ @@ -61,13 +65,9 @@ public VERIFY_SUSPECT() { } - public String getName() { - return name; - } - public Object down(Event evt) { - if(evt.getType() == Event.SHUTDOWN) { - shutting_down=true; + if(evt.getType() == Event.SET_LOCAL_ADDRESS) { + local_addr=(Address)evt.getArg(); } return down_prot.down(evt); } @@ -75,14 +75,7 @@ public Object up(Event evt) { switch(evt.getType()) { - case Event.SET_LOCAL_ADDRESS: - local_addr=(Address)evt.getArg(); - shutting_down=false; - break; - case Event.SUSPECT: // it all starts here ... - if(shutting_down) - return null; Address suspected_mbr=(Address)evt.getArg(); if(suspected_mbr == null) { if(log.isErrorEnabled()) log.error("suspected member is null"); @@ -104,11 +97,9 @@ case Event.MSG: Message msg=(Message)evt.getArg(); - VerifyHeader hdr=(VerifyHeader)msg.getHeader(name); + VerifyHeader hdr=(VerifyHeader)msg.getHeader(this.id); if(hdr == null) break; - if(shutting_down) - return null; switch(hdr.type) { case VerifyHeader.ARE_YOU_DEAD: if(hdr.from == null) { @@ -119,7 +110,7 @@ for(int i=0; i < num_msgs; i++) { rsp=new Message(hdr.from, null, null); rsp.setFlag(Message.OOB); - rsp.putHeader(name, new VerifyHeader(VerifyHeader.I_AM_NOT_DEAD, local_addr)); + rsp.putHeader(this.id, new VerifyHeader(VerifyHeader.I_AM_NOT_DEAD, local_addr)); down_prot.down(new Event(Event.MSG, rsp)); } } @@ -211,7 +202,7 @@ for(int i=0; i < num_msgs; i++) { msg=new Message(mbr, null, null); msg.setFlag(Message.OOB); - msg.putHeader(name, new VerifyHeader(VerifyHeader.ARE_YOU_DEAD, local_addr)); + msg.putHeader(this.id, new VerifyHeader(VerifyHeader.ARE_YOU_DEAD, local_addr)); down_prot.down(new Event(Event.MSG, msg)); } } @@ -276,10 +267,7 @@ intf=NetworkInterface.getByInetAddress(bind_addr); } - public void start() throws Exception { - super.start(); - shutting_down=false; - } + public synchronized void stop() { Thread tmp; @@ -297,7 +285,7 @@ - public static class VerifyHeader extends Header implements Streamable { + public static class VerifyHeader extends Header { static final short ARE_YOU_DEAD=1; // 'from' is sender of verify msg static final short I_AM_NOT_DEAD=2; // 'from' is suspected member @@ -329,16 +317,6 @@ } } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeShort(type); - out.writeObject(from); - } - - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - type=in.readShort(); - from=(Address)in.readObject(); - } public void writeTo(DataOutputStream out) throws IOException { out.writeShort(type); @@ -350,6 +328,9 @@ from=Util.readAddress(in); } + public int size() { + return Global.SHORT_SIZE + Util.size(from); + } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/VIEW_SYNC.java libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/VIEW_SYNC.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/protocols/VIEW_SYNC.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/protocols/VIEW_SYNC.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,14 +2,10 @@ import org.jgroups.*; +import org.jgroups.annotations.*; +import org.jgroups.conf.ClassConfigurator; import org.jgroups.protocols.pbcast.GMS; -import org.jgroups.annotations.GuardedBy; -import org.jgroups.annotations.MBean; -import org.jgroups.annotations.ManagedAttribute; -import org.jgroups.annotations.ManagedOperation; -import org.jgroups.annotations.Property; import org.jgroups.stack.Protocol; -import org.jgroups.util.Streamable; import org.jgroups.util.TimeScheduler; import org.jgroups.util.Util; @@ -28,17 +24,13 @@ * just below GMS. * * @author Bela Ban - * @version $Id: VIEW_SYNC.java,v 1.32 2008/10/21 08:16:17 vlada Exp $ */ @MBean(description="Periodically sends the view to the group") public class VIEW_SYNC extends Protocol { - private static final String name="VIEW_SYNC"; - /* ----------------------------------------- Properties --------------------------------------- */ @Property(description="View synchronization interval. Default is 60 sec") - @ManagedAttribute(description="View synchronization interval. Default is 60 sec", writable=true) private long avg_send_interval=60000; /* --------------------------------------------- JMX ---------------------------------------------- */ @@ -82,14 +74,12 @@ private final Lock view_task_lock=new ReentrantLock(); - private TimeScheduler timer=null; - + private TimeScheduler timer=null; + private static final short gms_id=ClassConfigurator.getProtocolId(GMS.class); - public String getName() { - return name; - } + public long getAverageSendInterval() { return avg_send_interval; } @@ -129,7 +119,7 @@ Message msg=new Message(null); msg.setFlag(Message.OOB); ViewSyncHeader hdr=new ViewSyncHeader(ViewSyncHeader.VIEW_SYNC_REQ, null); - msg.putHeader(name, hdr); + msg.putHeader(this.id, hdr); down_prot.down(new Event(Event.MSG, msg)); num_view_requests_sent++; last_view_request_sent=System.currentTimeMillis(); @@ -154,7 +144,7 @@ case Event.MSG: msg=(Message)evt.getArg(); - hdr=(ViewSyncHeader)msg.getHeader(name); + hdr=(ViewSyncHeader)msg.getHeader(this.id); if(hdr == null) break; Address sender=msg.getSrc(); @@ -175,10 +165,6 @@ View view=(View)evt.getArg(); handleViewChange(view); break; - - case Event.SET_LOCAL_ADDRESS: - local_addr=(Address)evt.getArg(); - break; } return up_prot.up(evt); @@ -192,6 +178,10 @@ View v=(View)evt.getArg(); handleViewChange(v); break; + + case Event.SET_LOCAL_ADDRESS: + local_addr=(Address)evt.getArg(); + break; } return down_prot.down(evt); } @@ -220,7 +210,7 @@ Message view_change=new Message(local_addr, local_addr, null); org.jgroups.protocols.pbcast.GMS.GmsHeader hdr; hdr=new org.jgroups.protocols.pbcast.GMS.GmsHeader(org.jgroups.protocols.pbcast.GMS.GmsHeader.VIEW, v); - view_change.putHeader(GMS.name, hdr); + view_change.putHeader(gms_id, hdr); up_prot.up(new Event(Event.MSG, view_change)); num_views_adjusted++; } else if (rc == 0) { @@ -256,7 +246,7 @@ Message msg=new Message(null); // send to the group msg.setFlag(Message.OOB); ViewSyncHeader hdr=new ViewSyncHeader(ViewSyncHeader.VIEW_SYNC, tmp); - msg.putHeader(name, hdr); + msg.putHeader(this.id, hdr); down_prot.down(new Event(Event.MSG, msg)); num_views_sent++; } @@ -267,7 +257,7 @@ view_task_lock.lock(); if(view_send_task_future == null || view_send_task_future.isDone()) { ViewSendTask view_send_task=new ViewSendTask(); - view_send_task_future=timer.scheduleWithDynamicInterval(view_send_task, true); // fixed-rate scheduling + view_send_task_future=timer.scheduleWithDynamicInterval(view_send_task); if(log.isTraceEnabled()) log.trace("view send task started"); } @@ -306,7 +296,7 @@ - public static class ViewSyncHeader extends Header implements Streamable { + public static class ViewSyncHeader extends Header { public static final int VIEW_SYNC = 1; // contains a view public static final int VIEW_SYNC_REQ = 2; // request to all members to send their views @@ -348,27 +338,6 @@ return sb.toString(); } - - public void writeExternal(ObjectOutput out) throws IOException { - out.writeInt(type); - if(view == null) { - out.writeBoolean(false); - return; - } - out.writeBoolean(true); - view.writeExternal(out); - } - - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - type=in.readInt(); - boolean available=in.readBoolean(); - if(available) { - view=new View(); - view.readExternal(in); - } - } - public int size() { int retval=Global.INT_SIZE + Global.BYTE_SIZE + Global.BYTE_SIZE; // type + view type + presence for digest if(view != null) diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/ReceiverAdapter.java libjgroups-java-2.12.2.Final/src/org/jgroups/ReceiverAdapter.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/ReceiverAdapter.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/ReceiverAdapter.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,7 +2,6 @@ /** * @author Bela Ban - * @version $Id: ReceiverAdapter.java,v 1.1 2005/11/08 10:43:38 belaban Exp $ */ public class ReceiverAdapter implements Receiver { @@ -16,10 +15,10 @@ public void setState(byte[] state) { } - public void viewAccepted(View new_view) { + public void viewAccepted(View view) { } - public void suspect(Address suspected_mbr) { + public void suspect(Address mbr) { } public void block() { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/Receiver.java libjgroups-java-2.12.2.Final/src/org/jgroups/Receiver.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/Receiver.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/Receiver.java 2011-10-18 11:22:35.000000000 +0000 @@ -3,7 +3,6 @@ /** * Defines the callbacks that are invoked when messages, views etc are received on a channel * @author Bela Ban - * @version $Id: Receiver.java,v 1.1 2005/11/08 10:40:16 belaban Exp $ */ public interface Receiver extends MessageListener, MembershipListener { } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/SetStateEvent.java libjgroups-java-2.12.2.Final/src/org/jgroups/SetStateEvent.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/SetStateEvent.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/SetStateEvent.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: SetStateEvent.java,v 1.6 2006/03/17 09:04:45 belaban Exp $ package org.jgroups; @@ -11,7 +10,6 @@ * Encapsulates a state returned by Channel.receive(), as requested by * Channel.getState(s) previously. * @author Bela Ban - * @version $Id: SetStateEvent.java,v 1.6 2006/03/17 09:04:45 belaban Exp $ */ public class SetStateEvent { byte[] state=null; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/AckMcastReceiverWindow.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/AckMcastReceiverWindow.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/AckMcastReceiverWindow.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/AckMcastReceiverWindow.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,10 +1,9 @@ -// $Id: AckMcastReceiverWindow.java,v 1.9 2008/01/22 10:44:36 belaban Exp $ package org.jgroups.stack; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.Address; import java.util.Enumeration; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/AckMcastSenderWindow.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/AckMcastSenderWindow.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/AckMcastSenderWindow.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/AckMcastSenderWindow.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,10 +1,11 @@ package org.jgroups.stack; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.Address; import org.jgroups.Message; +import org.jgroups.util.DefaultTimeScheduler; import org.jgroups.util.TimeScheduler; import java.io.PrintWriter; @@ -29,7 +30,6 @@ * retransmissions because of timeouts. * @author Bela Ban June 9 1999, 2007 * @author John Georgiadis May 8 2001 - * @version $Id: AckMcastSenderWindow.java,v 1.15 2008/01/22 10:44:36 belaban Exp $ */ public class AckMcastSenderWindow { /** @@ -210,7 +210,7 @@ * @throws IllegalArgumentException if cmd is null */ public AckMcastSenderWindow(RetransmitCommand cmd, Interval retransmit_intervals) { - init(cmd, retransmit_intervals, new TimeScheduler(), true); + init(cmd, retransmit_intervals, new DefaultTimeScheduler(), true); } /** @@ -465,12 +465,7 @@ // ii. Clear all pending msgs and notify anyone waiting synchronized(msgs) { if(retransmitter_owned) { - try { - timer.stop(); - } - catch(InterruptedException ex) { - if(log.isErrorEnabled()) log.error(_toString(ex)); - } + timer.stop(); } else { for(Entry entry: msgs.values()) { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/AckReceiverWindow.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/AckReceiverWindow.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/AckReceiverWindow.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/AckReceiverWindow.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,15 +1,18 @@ package org.jgroups.stack; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.Message; +import org.jgroups.util.Tuple; +import org.jgroups.util.Util; -import java.util.HashMap; -import java.util.Map; -import java.util.TreeSet; -import java.util.concurrent.locks.ReentrantLock; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReferenceArray; /** @@ -22,148 +25,303 @@ * a sorted set incurs overhead. * * @author Bela Ban - * @version $Id: AckReceiverWindow.java,v 1.28 2008/10/09 15:10:49 belaban Exp $ */ public class AckReceiverWindow { - long next_to_remove=0; - final Map msgs=new HashMap(); // keys: seqnos (Long), values: Messages - static final Log log=LogFactory.getLog(AckReceiverWindow.class); - final ReentrantLock lock=new ReentrantLock(); - final AtomicBoolean processing=new AtomicBoolean(false); + private final AtomicLong next_to_remove; + private final AtomicBoolean processing=new AtomicBoolean(false); + private final ConcurrentMap segments=Util.createConcurrentMap(); + private volatile Segment current_segment=null; + private volatile Segment current_remove_segment=null; + private final int segment_capacity; + private long highest_segment_created=0; + + public static final Message TOMBSTONE=new Message(false) { + public String toString() { + return "tombstone"; + } + }; public AckReceiverWindow(long initial_seqno) { - this.next_to_remove=initial_seqno; + this(initial_seqno, 20000); } + - public ReentrantLock getLock() { - return lock; + public AckReceiverWindow(long initial_seqno, int segment_capacity) { + next_to_remove=new AtomicLong(initial_seqno); + this.segment_capacity=segment_capacity; + long index=next_to_remove.get() / segment_capacity; + long first_seqno=(next_to_remove.get() / segment_capacity) * segment_capacity; + this.segments.put(index, new Segment(first_seqno, segment_capacity)); + Segment initial_segment=findOrCreateSegment(next_to_remove.get()); + current_segment=initial_segment; + current_remove_segment=initial_segment; + for(long i=0; i < next_to_remove.get(); i++) { + initial_segment.add(i, TOMBSTONE); + initial_segment.remove(i); + } } public AtomicBoolean getProcessing() { return processing; } + + /** Adds a new message. Message cannot be null * @return True if the message was added, false if not (e.g. duplicate, message was already present) */ public boolean add(long seqno, Message msg) { - if(msg == null) - throw new IllegalArgumentException("msg must be non-null"); - synchronized(msgs) { - if(seqno < next_to_remove) { - if(log.isTraceEnabled()) - log.trace("discarded msg with seqno=" + seqno + " (next msg to receive is " + next_to_remove + ')'); - return false; - } - if(!msgs.containsKey(seqno)) { - msgs.put(seqno, msg); - return true; - } - else { - if(log.isTraceEnabled()) - log.trace("seqno " + seqno + " already received - dropping it"); - return false; - } - } + return add2(seqno, msg) == 1; } /** - * Removes a message whose seqno is equal to next_to_remove, increments the latter. - * Returns message that was removed, or null, if no message can be removed. Messages are thus - * removed in order. + * Adds a message if not yet received + * @param seqno + * @param msg + * @return -1 if not added because seqno < next_to_remove, 0 if not added because already present, + * 1 if added successfully */ - public Message remove() { - Message retval; - - synchronized(msgs) { - retval=msgs.remove(next_to_remove); - if(retval != null) { - if(log.isTraceEnabled()) - log.trace("removed seqno=" + next_to_remove); - next_to_remove++; - } + public byte add2(long seqno, Message msg) { + Segment segment=current_segment; + if(segment == null || !segment.contains(seqno)) { + segment=findOrCreateSegment(seqno); + if(segment != null) + current_segment=segment; } - return retval; + if(segment == null) + return -1; + return segment.add(seqno, msg); } - public Message remove(AtomicBoolean processing) { - Message retval; - boolean found=false; - - synchronized(msgs) { - try { - retval=msgs.remove(next_to_remove); - found=retval != null; - if(retval != null) { - if(log.isTraceEnabled()) - log.trace("removed seqno=" + next_to_remove); - next_to_remove++; - } - } - finally { - if(!found) - processing.set(false); - } + + /** + * Removes a message whose seqno is equal to next_to_remove, increments the latter. Returns message + * that was removed, or null, if no message can be removed. Messages are thus removed in order. + */ + public Message remove() { + long next=next_to_remove.get(); + Segment segment=current_remove_segment; + if(segment == null || !segment.contains(next)) { + segment=findSegment(next); + if(segment != null) + current_remove_segment=segment; + } + if(segment == null) + return null; + Message retval=segment.remove(next); + if(retval != null) { + next_to_remove.compareAndSet(next, next +1); + if(segment.allRemoved()) + segments.remove(next / segment_capacity); } return retval; } - public Message removeOOBMessage() { - Message retval; - synchronized(msgs) { - retval=msgs.get(next_to_remove); - if(retval != null) { - if(!retval.isFlagSet(Message.OOB)) { - return null; + + /** + * Removes as many messages as possible (in sequence, without gaps) + * @param max Max number of messages to be removed + * @return Tuple,Long>: a tuple of the message list and the highest seqno removed + */ + public Tuple,Long> removeMany(final int max) { + List list=null; // we remove msgs.size() messages *max* + Tuple,Long> retval=null; + + int count=0; + boolean looping=true; + while(count < max && looping) { + long next=next_to_remove.get(); + Segment segment=current_remove_segment; + if(segment == null || !segment.contains(next)) { + segment=findSegment(next); + if(segment != null) + current_remove_segment=segment; + } + if(segment == null) + return retval; + + long segment_id=next; + long end=segment.getEndIndex(); + while(next < end && count < max) { + Message msg=segment.remove(next); + if(msg == null) { + looping=false; + break; } - retval=msgs.remove(next_to_remove); - if(log.isTraceEnabled()) - log.trace("removed OOB message with seqno=" + next_to_remove); - next_to_remove++; + if(list == null) { + list=new LinkedList(); // we remove msgs.size() messages *max* + retval=new Tuple,Long>(list, 0L); + } + list.add(msg); + count++; + retval.setVal2(next); + next_to_remove.compareAndSet(next, ++next); + if(segment.allRemoved()) + segments.remove(segment_id / segment_capacity); } } + return retval; } - - public boolean hasMessagesToRemove() { - synchronized(msgs) { - return msgs.containsKey(next_to_remove); - } + public List removeManyAsList(int max) { + Tuple, Long> tuple=removeMany(max); + return tuple != null? tuple.getVal1() : null; } - + public void reset() { - synchronized(msgs) { - msgs.clear(); - } + segments.clear(); } public int size() { - return msgs.size(); + int retval=0; + for(Segment segment: segments.values()) + retval+=segment.size(); + return retval; } public String toString() { StringBuilder sb=new StringBuilder(); - sb.append(msgs.size()).append(" msgs (").append("next=").append(next_to_remove).append(")"); - TreeSet s=new TreeSet(msgs.keySet()); - if(!s.isEmpty()) { - sb.append(" [").append(s.first()).append(" - ").append(s.last()).append("]"); - sb.append(": ").append(s); - } + int size=size(); + sb.append(size + " messages"); + if(size <= 100) + sb.append(" in " + segments.size() + " segments"); return sb.toString(); } - - public String printDetails() { + public String printMessages() { StringBuilder sb=new StringBuilder(); - sb.append(msgs.size()).append(" msgs (").append("next=").append(next_to_remove).append(")"). - append(", msgs=" ).append(new TreeSet(msgs.keySet())); + List keys=new LinkedList(segments.keySet()); + Collections.sort(keys); + for(long key: keys) { + Segment segment=segments.get(key); + if(segment == null) + continue; + for(long i=segment.getStartIndex(); i < segment.getEndIndex(); i++) { + Message msg=segment.get(i); + if(msg == null) + continue; + if(msg == TOMBSTONE) + sb.append("T "); + else + sb.append(i + " "); + } + } + return sb.toString(); } -} + private Segment findOrCreateSegment(long seqno) { + long index=seqno / segment_capacity; + if(index > highest_segment_created) { + long start_seqno=seqno / segment_capacity * segment_capacity; + Segment segment=new Segment(start_seqno, segment_capacity); + Segment tmp=segments.putIfAbsent(index, segment); + if(tmp != null) // segment already exists + segment=tmp; + else + highest_segment_created=index; + return segment; + } + + return segments.get(index); + } + + private Segment findSegment(long seqno) { + long index=seqno / segment_capacity; + return segments.get(index); + } + + + + private static class Segment { + final long start_index; // e.g. 5000. Then seqno 5100 would be at index 100 + final int capacity; + final AtomicReferenceArray array; + final AtomicInteger num_tombstones=new AtomicInteger(0); + + public Segment(long start_index, int capacity) { + this.start_index=start_index; + this.capacity=capacity; + this.array=new AtomicReferenceArray(capacity); + } + + public long getStartIndex() { + return start_index; + } + + public long getEndIndex() { + return start_index + capacity; + } + + public boolean contains(long seqno) { + return seqno >= start_index && seqno < getEndIndex(); + } + + public Message get(long seqno) { + int index=index(seqno); + if(index < 0 || index >= array.length()) + return null; + return array.get(index); + } + + public byte add(long seqno, Message msg) { + int index=index(seqno); + if(index < 0) + return -1; + boolean success=array.compareAndSet(index, null, msg); + if(success) { + return 1; + } + else + return 0; + } + + public Message remove(long seqno) { + int index=index(seqno); + if(index < 0) + return null; + Message retval=array.get(index); + if(retval != null && retval != TOMBSTONE && array.compareAndSet(index, retval, TOMBSTONE)) { + num_tombstones.incrementAndGet(); + return retval; + } + return null; + } + + public boolean allRemoved() { + return num_tombstones.get() >= capacity; + } + + public String toString() { + return start_index + " - " + (start_index + capacity -1) + " (" + size() + " elements)"; + } + + public int size() { + int retval=0; + for(int i=0; i < capacity; i++) { + Message tmp=array.get(i); + if(tmp != null && tmp != TOMBSTONE) + retval++; + } + return retval; + } + + private int index(long seqno) { + if(seqno < start_index) + return -1; + + int index=(int)(seqno - start_index); + if(index < 0 || index >= capacity) + throw new IndexOutOfBoundsException("index=" + index + ", start_index=" + start_index + ", seqno=" + seqno); + return index; + } + + } + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/AckSenderWindow.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/AckSenderWindow.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/AckSenderWindow.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/AckSenderWindow.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,11 +1,9 @@ -// $Id: AckSenderWindow.java,v 1.30 2008/05/15 10:49:10 belaban Exp $ package org.jgroups.stack; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.Address; +import org.jgroups.Global; import org.jgroups.Message; import org.jgroups.util.TimeScheduler; @@ -24,11 +22,12 @@ * @author Bela Ban */ public class AckSenderWindow implements Retransmitter.RetransmitCommand { - RetransmitCommand retransmit_command = null; // called to request XMIT of msg - final ConcurrentMap msgs=new ConcurrentHashMap(); - Interval interval=new StaticInterval(400,800,1200,1600); - final Retransmitter retransmitter; - static final Log log=LogFactory.getLog(AckSenderWindow.class); + private RetransmitCommand retransmit_command = null; // called to request XMIT of msgs + private final ConcurrentMap msgs=new ConcurrentHashMap(); + private Interval interval=new StaticInterval(400,800,1200,1600); + private final Retransmitter retransmitter; + private long lowest=Global.DEFAULT_FIRST_UNICAST_SEQNO; // lowest seqno, used by ack() + private long highest=0; public interface RetransmitCommand { @@ -36,76 +35,126 @@ } + public AckSenderWindow(RetransmitCommand com) { + retransmit_command = com; + retransmitter = new DefaultRetransmitter(null, this, null); + retransmitter.setRetransmitTimeouts(interval); + } public AckSenderWindow(RetransmitCommand com, Interval interval, TimeScheduler sched) { retransmit_command = com; this.interval = interval; - retransmitter = new Retransmitter(null, this, sched); + retransmitter = new DefaultRetransmitter(null, this, sched); retransmitter.setRetransmitTimeouts(interval); } public AckSenderWindow(RetransmitCommand com, Interval interval, TimeScheduler sched, Address sender) { retransmit_command = com; this.interval = interval; - retransmitter = new Retransmitter(sender, this, sched); + retransmitter = new DefaultRetransmitter(sender, this, sched); retransmitter.setRetransmitTimeouts(interval); } + /** + * Creates an instance without retransmitter + */ + public AckSenderWindow() { + retransmitter=null; + } + /** Only to be used for testing purposes */ + public synchronized long getLowest() { + return lowest; + } + + public long getHighest() { + return highest; + } + public void reset() { msgs.clear(); - - // moved out of sync scope: Retransmitter.reset()/add()/remove() are sync'ed anyway - // Bela Jan 15 2003 - retransmitter.reset(); + if(retransmitter != null) + retransmitter.reset(); + lowest=Global.DEFAULT_FIRST_UNICAST_SEQNO; } + public Message get(long seqno) { + return msgs.get(seqno); + } + /** - * Adds a new message to the retransmission table. If the message won't have received an ack within - * a certain time frame, the retransmission thread will retransmit the message to the receiver. If - * a sliding window protocol is used, we only add up to window_size messages. If the table is - * full, we add all new messages to a queue. Those will only be added once the table drains below a certain - * threshold (min_threshold) + * Adds a new message to the retransmission table. The message will be retransmitted (based on timeouts passed into + * AckSenderWindow until (1) an ACK is received or (2) the AckSenderWindow is stopped (@link{#reset}) */ public void add(long seqno, Message msg) { msgs.putIfAbsent(seqno, msg); - retransmitter.add(seqno, seqno); + if(retransmitter != null) + retransmitter.add(seqno, seqno); + highest=Math.max(highest, seqno); + } + + public void addToMessages(long seqno, Message msg) { + msgs.putIfAbsent(seqno, msg); + highest=Math.max(highest, seqno); + } + + public void addToRetransmitter(long seqno, Message msg) { + if(retransmitter != null) + retransmitter.add(seqno, seqno); } /** - * Removes the message from msgs, removing them also from retransmission. If - * sliding window protocol is used, and was queueing, check whether we can resume adding elements. - * Add all elements. If this goes above window_size, stop adding and back to queueing. Else - * set queueing to false. + * Removes all messages less than or equal to seqno from msgs, and cancels their retransmission */ public void ack(long seqno) { - msgs.remove(new Long(seqno)); - retransmitter.remove(seqno); + long prev_lowest; + synchronized(this) { + if(seqno < lowest) return; // not really needed, but we can avoid the max() call altogether... + prev_lowest=lowest; + lowest=Math.max(lowest, seqno +1); + } + removeRange(prev_lowest, seqno); + } + + + /** Returns the message with the lowest seqno */ + public synchronized Message getLowestMessage() { + return msgs.get(lowest); } + public int size() { return msgs.size(); } public String toString() { StringBuilder sb=new StringBuilder(); - sb.append(msgs.size()).append(" msgs (").append(retransmitter.size()).append(" to retransmit): "); - TreeSet keys=new TreeSet(msgs.keySet()); - if(!keys.isEmpty()) - sb.append(keys.first()).append(" - ").append(keys.last()); - else - sb.append("[]"); + int num_msgs=msgs.size(); + sb.append(num_msgs).append(" msgs"); + if(retransmitter != null) + sb.append(" (").append(retransmitter.size()).append(" to retransmit)"); + if(num_msgs > 0) { + sb.append(": "); + TreeSet keys=new TreeSet(msgs.keySet()); + if(!keys.isEmpty()) + sb.append(keys.first()).append(" - ").append(keys.last()); + else + sb.append("[]"); + } return sb.toString(); } public String printDetails() { StringBuilder sb=new StringBuilder(); - sb.append(msgs.size()).append(" msgs (").append(retransmitter.size()).append(" to retransmit): "). - append(new TreeSet(msgs.keySet())); + sb.append(msgs.size()).append(" msgs"); + if(retransmitter != null) + sb.append(" (").append(retransmitter.size()).append(" to retransmit)"); + sb.append(":\n"); + sb.append(new TreeSet(msgs.keySet())); return sb.toString(); } @@ -114,9 +163,6 @@ Message msg; if(retransmit_command != null) { - if(log.isTraceEnabled()) - log.trace(new StringBuilder("retransmitting messages ").append(first_seqno). - append(" - ").append(last_seqno).append(" from ").append(sender)); for(long i = first_seqno; i <= last_seqno; i++) { if((msg=msgs.get(i)) != null) { // find the message to retransmit retransmit_command.retransmit(i, msg); @@ -126,41 +172,12 @@ } /* ----------------------------- End of Retransmitter.RetransmitCommand interface ---------------- */ - - - - - /* ---------------------------------- Private methods --------------------------------------- */ - - /* ------------------------------ End of Private methods ------------------------------------ */ - - - - - /** Struct used to store message alongside with its seqno in the message queue */ - static class Entry { - final long seqno; - final Message msg; - - Entry(long seqno, Message msg) { - this.seqno = seqno; - this.msg = msg; - } - } - - - static class Dummy implements RetransmitCommand { - static final long last_xmit_req = 0; - long curr_time; - - - public void retransmit(long seqno, Message msg) { - if(log.isDebugEnabled()) log.debug("seqno=" + seqno); - curr_time = System.currentTimeMillis(); + private void removeRange(long from, long to) { + for(long i=from; i <= to; i++) { + msgs.remove(i); + if(retransmitter != null) + retransmitter.remove(i); } } - - - } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/AddressGenerator.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/AddressGenerator.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/AddressGenerator.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/AddressGenerator.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,12 @@ +package org.jgroups.stack; + +import org.jgroups.Address; + +/** + * Callback to provide custom addresses. Will be called by {@link org.jgroups.JChannel#connect(String)}. + * @author Bela Ban + * @since 2.12 + */ +public interface AddressGenerator { + Address generateAddress(); +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/Configurator.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/Configurator.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/Configurator.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/Configurator.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,16 +1,17 @@ package org.jgroups.stack; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.Event; import org.jgroups.Global; import org.jgroups.annotations.DeprecatedProperty; +import org.jgroups.annotations.LocalAddress; import org.jgroups.annotations.Property; -import org.jgroups.conf.PropertyConverter; -import org.jgroups.conf.PropertyConverters; +import org.jgroups.conf.PropertyHelper; +import org.jgroups.conf.ProtocolConfiguration; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.protocols.TP; -import org.jgroups.stack.ProtocolStack.ProtocolStackFactory; +import org.jgroups.util.StackType; import org.jgroups.util.Tuple; import org.jgroups.util.Util; @@ -18,14 +19,13 @@ import java.io.PushbackReader; import java.io.Reader; import java.io.StringReader; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; +import java.lang.reflect.*; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; import java.util.*; import java.util.concurrent.ConcurrentMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - /** @@ -36,27 +36,27 @@ * Future functionality will include the capability to dynamically modify the layering * of the protocol stack and the properties of each layer. * @author Bela Ban - * @version $Id: Configurator.java,v 1.59 2008/12/05 09:22:00 belaban Exp $ + * @author Richard Achmatowicz */ -public class Configurator implements ProtocolStackFactory { - +public class Configurator { protected static final Log log=LogFactory.getLog(Configurator.class); private final ProtocolStack stack; + public Configurator() { - stack = null; + stack=null; } public Configurator(ProtocolStack protocolStack) { stack=protocolStack; } - public Protocol setupProtocolStack() throws Exception{ - return setupProtocolStack(stack.getSetupString(), stack); + public Protocol setupProtocolStack(List config) throws Exception{ + return setupProtocolStack(config, stack); } public Protocol setupProtocolStack(ProtocolStack copySource)throws Exception{ - Vector protocols=copySource.copyProtocols(stack); + List protocols=copySource.copyProtocols(stack); Collections.reverse(protocols); return connectProtocols(protocols); } @@ -84,14 +84,67 @@ * ----------------------- * */ - private Protocol setupProtocolStack(String configuration, ProtocolStack st) throws Exception { - Vector protocol_configs=parseConfigurations(configuration); - Vector protocols=createProtocols(protocol_configs, st); + private static Protocol setupProtocolStack(List protocol_configs, ProtocolStack st) throws Exception { + List protocols=createProtocols(protocol_configs, st); if(protocols == null) return null; - return connectProtocols(protocols); + + + // check InetAddress related features of stack + Map> inetAddressMap = createInetAddressMap(protocol_configs, protocols) ; + Collection addrs=getAddresses(inetAddressMap); + + StackType ip_version=Util.getIpStackType(); // 0 = n/a, 4 = IPv4, 6 = IPv6 + + if(!addrs.isEmpty()) { + // check that all user-supplied InetAddresses have a consistent version: + // 1. If an addr is IPv6 and we have an IPv4 stack --> FAIL + // 2. If an address is an IPv4 class D (multicast) address and the stack is IPv6: FAIL + // Else pass + + for(InetAddress addr: addrs) { + if(addr instanceof Inet6Address && ip_version == StackType.IPv4) + throw new IllegalArgumentException("found IPv6 address " + addr + " in an IPv4 stack"); + if(addr instanceof Inet4Address && addr.isMulticastAddress() && ip_version == StackType.IPv6) + throw new Exception("found IPv4 multicast address " + addr + " in an IPv6 stack"); + } + } + + + // process default values + setDefaultValues(protocol_configs, protocols, ip_version); + ensureValidBindAddresses(protocols); + return connectProtocols(protocols); + } + + + public static void setDefaultValues(List protocols) throws Exception { + if(protocols == null) + return; + + // check InetAddress related features of stack + Collection addrs=getInetAddresses(protocols); + StackType ip_version=Util.getIpStackType(); // 0 = n/a, 4 = IPv4, 6 = IPv6 + + if(!addrs.isEmpty()) { + // check that all user-supplied InetAddresses have a consistent version: + // 1. If an addr is IPv6 and we have an IPv4 stack --> FAIL + // 2. If an address is an IPv4 class D (multicast) address and the stack is IPv6: FAIL + // Else pass + + for(InetAddress addr : addrs) { + if(addr instanceof Inet6Address && ip_version == StackType.IPv4) + throw new IllegalArgumentException("found IPv6 address " + addr + " in an IPv4 stack"); + if(addr instanceof Inet4Address && addr.isMulticastAddress() && ip_version == StackType.IPv6) + throw new Exception("found IPv4 multicast address " + addr + " in an IPv6 stack"); + } + } + + // process default values + setDefaultValues(protocols, ip_version); } + /** * Creates a new protocol given the protocol specification. Initializes the properties and starts the * up and down handler threads. @@ -113,7 +166,7 @@ config=new ProtocolConfiguration(prot_spec); // create an instance of the protocol class and configure it - prot=config.createLayer(stack); + prot=createLayer(stack, config); prot.init(); return prot; } @@ -131,14 +184,14 @@ * @param protocol_list List of Protocol elements (from top to bottom) * @return Protocol stack */ - private Protocol connectProtocols(Vector protocol_list) { + public static Protocol connectProtocols(List protocol_list) throws Exception { Protocol current_layer=null, next_layer=null; for(int i=0; i < protocol_list.size(); i++) { - current_layer=protocol_list.elementAt(i); + current_layer=protocol_list.get(i); if(i + 1 >= protocol_list.size()) break; - next_layer=protocol_list.elementAt(i + 1); + next_layer=protocol_list.get(i + 1); next_layer.setDownProtocol(current_layer); current_layer.setUpProtocol(next_layer); @@ -160,6 +213,10 @@ } } } + + // basic protocol sanity check + sanityCheck(protocol_list); + return current_layer; } @@ -171,8 +228,8 @@ * @param config_str Configuration string * @return Vector of strings */ - private Vector parseProtocols(String config_str) throws IOException { - Vector retval=new Vector(); + private static List parseProtocols(String config_str) throws IOException { + List retval=new LinkedList(); PushbackReader reader=new PushbackReader(new StringReader(config_str)); int ch; StringBuilder sb; @@ -233,22 +290,38 @@ /** * Return a number of ProtocolConfigurations in a vector * @param configuration protocol-stack configuration string - * @return Vector of ProtocolConfigurations + * @return List of ProtocolConfigurations */ - public Vector parseConfigurations(String configuration) throws Exception { - Vector retval=new Vector(); - Vector protocol_string=parseProtocols(configuration); + public static List parseConfigurations(String configuration) throws Exception { + List retval=new ArrayList(); + List protocol_string=parseProtocols(configuration); if(protocol_string == null) return null; - for(String component_string:protocol_string) { - retval.addElement(new ProtocolConfiguration(component_string)); + for(String component_string: protocol_string) { + retval.add(new ProtocolConfiguration(component_string)); } return retval; } + public static String printConfigurations(Collection configs) { + StringBuilder sb=new StringBuilder(); + boolean first=true; + for(ProtocolConfiguration config: configs) { + if(first) + first=false; + else + sb.append(":"); + sb.append(config.getProtocolName()); + if(!config.getProperties().isEmpty()) { + sb.append('(').append(config.propertiesToString()).append(')'); + } + } + + return sb.toString(); + } private static String readUntil(Reader reader, char c) throws IOException { StringBuilder sb=new StringBuilder(); @@ -284,497 +357,1108 @@ * each ProtocolConfiguration and returns all Protocols in a vector. * @param protocol_configs Vector of ProtocolConfigurations * @param stack The protocol stack - * @return Vector of Protocols + * @return List of Protocols */ - private Vector createProtocols(Vector protocol_configs, final ProtocolStack stack) throws Exception { - Vector retval=new Vector(); + private static List createProtocols(List protocol_configs, final ProtocolStack stack) throws Exception { + List retval=new LinkedList(); ProtocolConfiguration protocol_config; Protocol layer; String singleton_name; for(int i=0; i < protocol_configs.size(); i++) { - protocol_config=protocol_configs.elementAt(i); - singleton_name=protocol_config.getProperties().getProperty(Global.SINGLETON_NAME); + protocol_config=protocol_configs.get(i); + singleton_name=protocol_config.getProperties().get(Global.SINGLETON_NAME); if(singleton_name != null && singleton_name.trim().length() > 0) { - synchronized(stack) { + Map> singleton_transports=ProtocolStack.getSingletonTransports(); + synchronized(singleton_transports) { if(i > 0) { // crude way to check whether protocol is a transport throw new IllegalArgumentException("Property 'singleton_name' can only be used in a transport" + " protocol (was used in " + protocol_config.getProtocolName() + ")"); } - Map> singleton_transports=ProtocolStack.getSingletonTransports(); Tuple val=singleton_transports.get(singleton_name); layer=val != null? val.getVal1() : null; if(layer != null) { retval.add(layer); } else { - layer=protocol_config.createLayer(stack); + layer=createLayer(stack, protocol_config); if(layer == null) return null; singleton_transports.put(singleton_name, new Tuple((TP)layer,new ProtocolStack.RefCounter((short)0,(short)0))); - retval.addElement(layer); + retval.add(layer); } } continue; } - layer=protocol_config.createLayer(stack); + layer=createLayer(stack, protocol_config); if(layer == null) return null; - retval.addElement(layer); + retval.add(layer); } - sanityCheck(retval); return retval; } - /** - Throws an exception if sanity check fails. Possible sanity check is uniqueness of all protocol names - */ - public static void sanityCheck(Vector protocols) throws Exception { - Vector names=new Vector(); - Protocol prot; - String name; - Vector req_list=new Vector(); + protected static Protocol createLayer(ProtocolStack stack, ProtocolConfiguration config) throws Exception { + String protocol_name=config.getProtocolName(); + Map properties=config.getProperties(); + Protocol retval=null; - // Checks for unique names - for(int i=0; i < protocols.size(); i++) { - prot=protocols.elementAt(i); - name=prot.getName(); - for(int j=0; j < names.size(); j++) { - if(name.equals(names.elementAt(j))) { - throw new Exception("Configurator.sanityCheck(): protocol name " + name + - " has been used more than once; protocol names have to be unique !"); - } - } - names.addElement(name); + if(protocol_name == null || properties == null) + return null; + + String defaultProtocolName=ProtocolConfiguration.protocol_prefix + '.' + protocol_name; + Class clazz=null; + + try { + clazz=Util.loadClass(defaultProtocolName, stack.getClass()); + } + catch(ClassNotFoundException e) { } + if(clazz == null) { + try { + clazz=Util.loadClass(protocol_name, stack.getClass()); + } + catch(ClassNotFoundException e) { + } + if(clazz == null) { + throw new Exception("unable to load class for protocol " + protocol_name + + " (either as an absolute - " + protocol_name + " - or relative - " + + defaultProtocolName + " - package name)!"); + } + } - // Checks whether all requirements of all layers are met - for(Protocol p:protocols){ - req_list.add(new ProtocolReq(p)); - } - - for(ProtocolReq pr:req_list){ - for(Integer evt_type:pr.up_reqs) { - if(!providesDownServices(req_list, evt_type)) { - throw new Exception("Configurator.sanityCheck(): event " + - Event.type2String(evt_type) + " is required by " + - pr.name + ", but not provided by any of the layers above"); + try { + retval=(Protocol)clazz.newInstance(); + if(retval == null) + throw new Exception("creation of instance for protocol " + protocol_name + "failed !"); + retval.setProtocolStack(stack); + + removeDeprecatedProperties(retval, properties); + // before processing Field and Method based properties, take dependencies specified + // with @Property.dependsUpon into account + AccessibleObject[] dependencyOrderedFieldsAndMethods = computePropertyDependencies(retval, properties) ; + for(AccessibleObject ordered: dependencyOrderedFieldsAndMethods) { + if (ordered instanceof Field) { + resolveAndAssignField(retval, (Field)ordered, properties) ; } - } - - for(Integer evt_type:pr.down_reqs) { - if(!providesUpServices(req_list, evt_type)) { - throw new Exception("Configurator.sanityCheck(): event " + - Event.type2String(evt_type) + " is required by " + - pr.name + ", but not provided by any of the layers above"); + else if (ordered instanceof Method) { + resolveAndInvokePropertyMethod(retval, (Method)ordered, properties) ; } - } - } - } + } + List additional_objects=retval.getConfigurableObjects(); + if(additional_objects != null && !additional_objects.isEmpty()) { + for(Object obj: additional_objects) { + resolveAndAssignFields(obj, properties); + resolveAndInvokePropertyMethods(obj, properties); + } + } - /** Check whether any of the protocols 'below' provide evt_type */ - static boolean providesUpServices(Vector req_list, int evt_type) { - for (ProtocolReq pr:req_list){ - if(pr.providesUpService(evt_type)) - return true; + if(!properties.isEmpty()) { + throw new IllegalArgumentException("the following properties in " + protocol_name + + " are not recognized: " + properties); + } } - return false; + catch(InstantiationException inst_ex) { + log.error("an instance of " + protocol_name + " could not be created. Please check that it implements" + + " interface Protocol and that is has a public empty constructor !"); + throw inst_ex; + } + return retval; } - /** Checks whether any of the protocols 'above' provide evt_type */ - static boolean providesDownServices(Vector req_list, int evt_type) { - for (ProtocolReq pr:req_list){ - if(pr.providesDownService(evt_type)) - return true; + /** + Throws an exception if sanity check fails. Possible sanity check is uniqueness of all protocol names + */ + public static void sanityCheck(List protocols) throws Exception { + + // check for unique IDs + Set ids=new HashSet(); + for(Protocol protocol: protocols) { + short id=protocol.getId(); + if(id > 0 && ids.add(id) == false) + throw new Exception("Protocol ID " + id + " (name=" + protocol.getName() + + ") is duplicate; protocol IDs have to be unique"); } - return false; - } - public static void resolveAndInvokePropertyMethods(Object obj, Properties props) throws Exception { - Method[] methods=obj.getClass().getMethods(); - for(Method method: methods) { - String methodName=method.getName(); - if(method.isAnnotationPresent(Property.class) && isSetPropertyMethod(method)) { - Property annotation=method.getAnnotation(Property.class); - String propertyName=annotation.name().length() > 0? annotation.name() : methodName.substring(3); - propertyName=renameFromJavaCodingConvention(propertyName); - String prop=props.getProperty(propertyName); - if(prop != null) { - PropertyConverter propertyConverter=(PropertyConverter)annotation.converter().newInstance(); - if(propertyConverter == null) { - String name=obj instanceof Protocol? ((Protocol)obj).getName() : obj.getClass().getName(); - throw new Exception("Could not find property converter for field " + propertyName - + " in " + name); - } - Object converted=null; - try { - converted=propertyConverter.convert(method.getParameterTypes()[0], props, prop); - method.invoke(obj, converted); - } - catch(Exception e) { - String name=obj instanceof Protocol? ((Protocol)obj).getName() : obj.getClass().getName(); - throw new Exception("Could not assign property " + propertyName + " in " - + name + ", method is " + methodName + ", converted value is " + converted, e); - } - finally { - props.remove(propertyName); - } - } + // For each protocol, get its required up and down services and check (if available) if they're satisfied + for(Protocol protocol: protocols) { + List required_down_services=protocol.requiredDownServices(); + List required_up_services=protocol.requiredUpServices(); + + if(required_down_services != null && !required_down_services.isEmpty()) { + + // the protocols below 'protocol' have to provide the services listed in required_down_services + List tmp=new ArrayList(required_down_services); + removeProvidedUpServices(protocol, tmp); + if(!tmp.isEmpty()) + throw new Exception("events " + printEvents(tmp) + " are required by " + protocol.getName() + + ", but not provided by any of the protocols below it"); + } + + if(required_up_services != null && !required_up_services.isEmpty()) { + + // the protocols above 'protocol' have to provide the services listed in required_up_services + List tmp=new ArrayList(required_up_services); + removeProvidedDownServices(protocol, tmp); + if(!tmp.isEmpty()) + throw new Exception("events " + printEvents(tmp) + " are required by " + protocol.getName() + + ", but not provided by any of the protocols above it"); } } } - public static boolean isSetPropertyMethod(Method method) { - return (method.getName().startsWith("set") && - method.getReturnType() == java.lang.Void.TYPE && - method.getParameterTypes().length == 1); + protected static String printEvents(List events) { + StringBuilder sb=new StringBuilder("["); + for(int evt: events) + sb.append(Event.type2String(evt)).append(" "); + sb.append("]"); + return sb.toString(); } - public static void resolveAndAssignFields(Object obj, Properties props) throws Exception { - //traverse class hierarchy and find all annotated fields - for(Class clazz=obj.getClass(); clazz != null; clazz=clazz.getSuperclass()) { - Field[] fields=clazz.getDeclaredFields(); - for(Field field: fields) { - if(field.isAnnotationPresent(Property.class)) { - Property annotation=field.getAnnotation(Property.class); - String propertyName=field.getName(); - if(props.containsKey(annotation.name())) { - propertyName=annotation.name(); - boolean isDeprecated=annotation.deprecatedMessage().length() > 0; - if(isDeprecated && log.isWarnEnabled()) { - log.warn(annotation.deprecatedMessage()); - } - } - String propertyValue=props.getProperty(propertyName); - if(propertyValue != null || !annotation.converter().equals(PropertyConverters.Default.class)){ - PropertyConverter propertyConverter=(PropertyConverter)annotation.converter().newInstance(); - if(propertyConverter == null) { - String name=obj instanceof Protocol? ((Protocol)obj).getName() : obj.getClass().getName(); - throw new Exception("Could not find property converter for field " + propertyName - + " in " + name); - } - Object converted=null; - try { - converted=propertyConverter.convert(field.getType(), props, propertyValue); - setField(field, obj, converted); - } - catch(Exception e) { - String name=obj instanceof Protocol? ((Protocol)obj).getName() : obj.getClass().getName(); - throw new Exception("Property assignment of " + propertyName + " in " - + name + " with original property value " + propertyValue + " and converted to " + converted - + " could not be assigned. Exception is " +e, e); - } - finally { - props.remove(propertyName); - } - } - } - } + /** + * Removes all events provided by the protocol below protocol from events + * @param protocol + * @param events + */ + protected static void removeProvidedUpServices(Protocol protocol, List events) { + if(protocol == null || events == null) + return; + for(Protocol prot=protocol.getDownProtocol(); prot != null && !events.isEmpty(); prot=prot.getDownProtocol()) { + List provided_up_services=prot.providedUpServices(); + if(provided_up_services != null && !provided_up_services.isEmpty()) + events.removeAll(provided_up_services); } } - public static void removeDeprecatedProperties(Object obj, Properties props) throws Exception { - //traverse class hierarchy and find all deprecated properties - for(Class clazz=obj.getClass(); clazz != null; clazz=clazz.getSuperclass()) { - if(clazz.isAnnotationPresent(DeprecatedProperty.class)) { - DeprecatedProperty declaredAnnotation=clazz.getAnnotation(DeprecatedProperty.class); - String[] deprecatedProperties=declaredAnnotation.names(); - for(String propertyName : deprecatedProperties) { - String propertyValue=props.getProperty(propertyName); - if(propertyValue != null) { - if(log.isWarnEnabled()) { - String name=obj instanceof Protocol? ((Protocol)obj).getName() : obj.getClass().getName(); - log.warn(name + " property " + propertyName + " was deprecated and is ignored"); - } - props.remove(propertyName); - } - } - } + /** + * Removes all events provided by the protocol above protocol from events + * @param protocol + * @param events + */ + protected static void removeProvidedDownServices(Protocol protocol, List events) { + if(protocol == null || events == null) + return; + for(Protocol prot=protocol.getUpProtocol(); prot != null && !events.isEmpty(); prot=prot.getUpProtocol()) { + List provided_down_services=prot.providedDownServices(); + if(provided_down_services != null && !provided_down_services.isEmpty()) + events.removeAll(provided_down_services); } } - - public static void setField(Field field, Object target, Object value) { - if(!Modifier.isPublic(field.getModifiers())) { - field.setAccessible(true); - } - try { - field.set(target, value); - } - catch(IllegalAccessException iae) { - throw new IllegalArgumentException("Could not set field " + field, iae); + /** + * Returns all inet addresses found + */ + public static Collection getAddresses(Map> inetAddressMap) throws Exception { + Set addrs=new HashSet(); + + for(Map.Entry> inetAddressMapEntry : inetAddressMap.entrySet()) { + Map protocolInetAddressMap=inetAddressMapEntry.getValue(); + for(Map.Entry protocolInetAddressMapEntry : protocolInetAddressMap.entrySet()) { + InetAddressInfo inetAddressInfo=protocolInetAddressMapEntry.getValue(); + // add InetAddressInfo to sets based on IP version + List addresses=inetAddressInfo.getInetAddresses(); + for(InetAddress address : addresses) { + if(address == null) + throw new RuntimeException("failed converting address info to IP address: " + inetAddressInfo); + addrs.add(address); + } + } } + return addrs; } - public static Object getField(Field field, Object target) { - if(!Modifier.isPublic(field.getModifiers())) { - field.setAccessible(true); - } - try { - return field.get(target); - } - catch(IllegalAccessException iae) { - throw new IllegalArgumentException("Could not get field " + field, iae); + + /** + * This method takes a set of InetAddresses, represented by an inetAddressmap, and: + * - if the resulting set is non-empty, goes through to see if all InetAddress-related + * user settings have a consistent IP version: v4 or v6, and throws an exception if not + * - if the resulting set is empty, sets the default IP version based on available stacks + * and if a dual stack, stack preferences + * - sets the IP version to be used in the JGroups session + * @return StackType.IPv4 for IPv4, StackType.IPv6 for IPv6, StackType.Unknown if the version cannot be determined + */ + public static StackType determineIpVersionFromAddresses(Collection addrs) throws Exception { + Set ipv4_addrs= new HashSet() ; + Set ipv6_addrs= new HashSet() ; + + for(InetAddress address: addrs) { + if (address instanceof Inet4Address) + ipv4_addrs.add(address) ; + else + ipv6_addrs.add(address) ; } - } - public static String renameFromJavaCodingConvention(String fieldName) { - Pattern p=Pattern.compile("[A-Z]"); - Matcher m=p.matcher(fieldName.substring(1)); - StringBuffer sb=new StringBuffer(); - while(m.find()) { - m.appendReplacement(sb, "_" + fieldName.substring(m.end(), m.end() + 1).toLowerCase()); + if(log.isTraceEnabled()) + log.trace("all addrs=" + addrs + ", IPv4 addrs=" + ipv4_addrs + ", IPv6 addrs=" + ipv6_addrs); + + // the user supplied 1 or more IP address inputs. Check if we have a consistent set + if (!addrs.isEmpty()) { + if (!ipv4_addrs.isEmpty() && !ipv6_addrs.isEmpty()) { + throw new RuntimeException("all addresses have to be either IPv4 or IPv6: IPv4 addresses=" + + ipv4_addrs + ", IPv6 addresses=" + ipv6_addrs); + } + return !ipv6_addrs.isEmpty()? StackType.IPv6 : StackType.IPv4; } - m.appendTail(sb); - sb.insert(0, fieldName.substring(0, 1).toLowerCase()); - return sb.toString(); + return StackType.Unknown; } - /* --------------------------- End of Private Methods ---------------------------------- */ + /* + * A method which does the following: + * - discovers all Fields or Methods within the protocol stack which set + * InetAddress, IpAddress, InetSocketAddress (and Lists of such) for which the user *has* + * specified a default value. + * - stores the resulting set of Fields and Methods in a map of the form: + * Protocol -> Property -> InetAddressInfo + * where InetAddressInfo instances encapsulate the InetAddress related information + * of the Fields and Methods. + */ + public static Map> createInetAddressMap(List protocol_configs, + List protocols) throws Exception { + // Map protocol -> Map, where the latter is protocol specific + Map> inetAddressMap = new HashMap>() ; + + // collect InetAddressInfo + for (int i = 0; i < protocol_configs.size(); i++) { + ProtocolConfiguration protocol_config = protocol_configs.get(i) ; + Protocol protocol = protocols.get(i) ; + String protocolName = protocol.getName(); + + // regenerate the Properties which were destroyed during basic property processing + Map properties = protocol_config.getOriginalProperties(); + + // check which InetAddress-related properties are ***non-null ***, and + // create an InetAddressInfo structure for them + // Method[] methods=protocol.getClass().getMethods(); + Method[] methods=Util.getAllDeclaredMethodsWithAnnotations(protocol.getClass(), Property.class); + for(int j = 0; j < methods.length; j++) { + if (methods[j].isAnnotationPresent(Property.class) && isSetPropertyMethod(methods[j])) { + String propertyName = PropertyHelper.getPropertyName(methods[j]) ; + String propertyValue = properties.get(propertyName); + + // if there is a systemProperty attribute defined in the annotation, set the property value from the system property + String tmp=grabSystemProp(methods[j].getAnnotation(Property.class)); + if(tmp != null) + propertyValue=tmp; + + if (propertyValue != null && InetAddressInfo.isInetAddressRelated(methods[j])) { + Object converted = null ; + try { + converted=PropertyHelper.getConvertedValue(protocol, methods[j], properties, propertyValue, false); + } + catch(Exception e) { + throw new Exception("String value could not be converted for method " + propertyName + " in " + + protocolName + " with default value " + propertyValue + ".Exception is " +e, e); + } + InetAddressInfo inetinfo = new InetAddressInfo(protocol, methods[j], properties, propertyValue, converted) ; + + Map protocolInetAddressMap=inetAddressMap.get(protocolName); + if(protocolInetAddressMap == null) { + protocolInetAddressMap = new HashMap() ; + inetAddressMap.put(protocolName, protocolInetAddressMap) ; + } + protocolInetAddressMap.put(propertyName, inetinfo) ; + } + } + } + + //traverse class hierarchy and find all annotated fields and add them to the list if annotated + for(Class clazz=protocol.getClass(); clazz != null; clazz=clazz.getSuperclass()) { + Field[] fields=clazz.getDeclaredFields(); + for(int j = 0; j < fields.length; j++ ) { + if (fields[j].isAnnotationPresent(Property.class)) { + String propertyName = PropertyHelper.getPropertyName(fields[j], properties) ; + String propertyValue = properties.get(propertyName) ; + + // if there is a systemProperty attribute defined in the annotation, set the property value from the system property + String tmp=grabSystemProp(fields[j].getAnnotation(Property.class)); + if(tmp != null) + propertyValue=tmp; + + if ((propertyValue != null || !PropertyHelper.usesDefaultConverter(fields[j])) + && InetAddressInfo.isInetAddressRelated(protocol, fields[j])) { + Object converted = null ; + try { + converted=PropertyHelper.getConvertedValue(protocol, fields[j], properties, propertyValue, false); + } + catch(Exception e) { + throw new Exception("String value could not be converted for method " + propertyName + " in " + + protocolName + " with default value " + propertyValue + ".Exception is " +e, e); + } + InetAddressInfo inetinfo = new InetAddressInfo(protocol, fields[j], properties, propertyValue, converted) ; + + Map protocolInetAddressMap=inetAddressMap.get(protocolName); + if(protocolInetAddressMap == null) { + protocolInetAddressMap = new HashMap() ; + inetAddressMap.put(protocolName, protocolInetAddressMap) ; + } + protocolInetAddressMap.put(propertyName, inetinfo) ; + }// recompute + } + } + } + } + return inetAddressMap ; + } + + + public static List getInetAddresses(List protocols) throws Exception { + List retval=new LinkedList(); + + // collect InetAddressInfo + for(Protocol protocol : protocols) { + String protocolName=protocol.getName(); + + //traverse class hierarchy and find all annotated fields and add them to the list if annotated + for(Class clazz=protocol.getClass(); clazz != null; clazz=clazz.getSuperclass()) { + Field[] fields=clazz.getDeclaredFields(); + for(int j=0; j < fields.length; j++) { + if(fields[j].isAnnotationPresent(Property.class)) { + if(InetAddressInfo.isInetAddressRelated(protocol, fields[j])) { + Object value=getValueFromProtocol(protocol, fields[j]); + if(value instanceof InetAddress) + retval.add((InetAddress)value); + else if(value instanceof IpAddress) + retval.add(((IpAddress)value).getIpAddress()); + else if(value instanceof InetSocketAddress) + retval.add(((InetSocketAddress)value).getAddress()); + } + } + } + } + } + return retval; + } + /* + * Method which processes @Property.default() values, associated with the annotation + * using the defaultValue= attribute. This method does the following: + * - locate all properties which have no user value assigned + * - if the defaultValue attribute is not "", generate a value for the field using the + * property converter for that property and assign it to the field + */ + public static void setDefaultValues(List protocol_configs, List protocols, + StackType ip_version) throws Exception { + InetAddress default_ip_address=Util.getNonLoopbackAddress(); + if(default_ip_address == null) { + log.warn("unable to find an address other than loopback for IP version " + ip_version); + default_ip_address=Util.getLocalhost(ip_version); + } + for(int i=0; i < protocol_configs.size(); i++) { + ProtocolConfiguration protocol_config=protocol_configs.get(i); + Protocol protocol=protocols.get(i); + String protocolName=protocol.getName(); + + // regenerate the Properties which were destroyed during basic property processing + Map properties=protocol_config.getOriginalProperties(); + + Method[] methods=Util.getAllDeclaredMethodsWithAnnotations(protocol.getClass(), Property.class); + for(int j=0; j < methods.length; j++) { + if(isSetPropertyMethod(methods[j])) { + String propertyName=PropertyHelper.getPropertyName(methods[j]); + + Object propertyValue=getValueFromProtocol(protocol, propertyName); + if(propertyValue == null) { // if propertyValue is null, check if there is a we can use + Property annotation=methods[j].getAnnotation(Property.class); + + // get the default value for the method- check for InetAddress types + String defaultValue=null; + if(InetAddressInfo.isInetAddressRelated(methods[j])) { + defaultValue=ip_version == StackType.IPv4? annotation.defaultValueIPv4() : annotation.defaultValueIPv6(); + if(defaultValue != null && defaultValue.length() > 0) { + Object converted=null; + try { + if(defaultValue.equalsIgnoreCase(Global.NON_LOOPBACK_ADDRESS)) + converted=default_ip_address; + else + converted=PropertyHelper.getConvertedValue(protocol, methods[j], properties, defaultValue, true); + methods[j].invoke(protocol, converted); + } + catch(Exception e) { + throw new Exception("default could not be assigned for method " + propertyName + " in " + + protocolName + " with default " + defaultValue, e); + } + if(log.isDebugEnabled()) + log.debug("set property " + protocolName + "." + propertyName + " to default value " + converted); + } + } + } + } + } + //traverse class hierarchy and find all annotated fields and add them to the list if annotated + Field[] fields=Util.getAllDeclaredFieldsWithAnnotations(protocol.getClass(), Property.class); + for(int j=0; j < fields.length; j++) { + String propertyName=PropertyHelper.getPropertyName(fields[j], properties); + Object propertyValue=getValueFromProtocol(protocol, fields[j]); + if(propertyValue == null) { + // add to collection of @Properties with no user specified value + Property annotation=fields[j].getAnnotation(Property.class); + + // get the default value for the field - check for InetAddress types + String defaultValue=null; + if(InetAddressInfo.isInetAddressRelated(protocol, fields[j])) { + defaultValue=ip_version == StackType.IPv4? annotation.defaultValueIPv4() : annotation.defaultValueIPv6(); + if(defaultValue != null && defaultValue.length() > 0) { + // condition for invoking converter + if(defaultValue != null || !PropertyHelper.usesDefaultConverter(fields[j])) { + Object converted=null; + try { + if(defaultValue.equalsIgnoreCase(Global.NON_LOOPBACK_ADDRESS)) + converted=default_ip_address; + else + converted=PropertyHelper.getConvertedValue(protocol, fields[j], properties, defaultValue, true); + if(converted != null) + setField(fields[j], protocol, converted); + } + catch(Exception e) { + throw new Exception("default could not be assigned for field " + propertyName + " in " + + protocolName + " with default value " + defaultValue, e); + } + + if(log.isDebugEnabled()) + log.debug("set property " + protocolName + "." + propertyName + " to default value " + converted); + } + } + } + } + } + } + } - private static class ProtocolReq { - final Vector up_reqs=new Vector(); - final Vector down_reqs=new Vector(); - final Vector up_provides=new Vector(); - final Vector down_provides=new Vector(); - final String name; + public static void setDefaultValues(List protocols, StackType ip_version) throws Exception { + InetAddress default_ip_address=Util.getNonLoopbackAddress(); + if(default_ip_address == null) { + log.warn("unable to find an address other than loopback for IP version " + ip_version); + default_ip_address=Util.getLocalhost(ip_version); + } + + for(Protocol protocol : protocols) { + String protocolName=protocol.getName(); + + //traverse class hierarchy and find all annotated fields and add them to the list if annotated + Field[] fields=Util.getAllDeclaredFieldsWithAnnotations(protocol.getClass(), Property.class); + for(int j=0; j < fields.length; j++) { + // get the default value for the field - check for InetAddress types + if(InetAddressInfo.isInetAddressRelated(protocol, fields[j])) { + Object propertyValue=getValueFromProtocol(protocol, fields[j]); + if(propertyValue == null) { + // add to collection of @Properties with no user specified value + Property annotation=fields[j].getAnnotation(Property.class); + + String defaultValue=ip_version == StackType.IPv4? annotation.defaultValueIPv4() : annotation.defaultValueIPv6(); + if(defaultValue != null && defaultValue.length() > 0) { + // condition for invoking converter + Object converted=null; + try { + if(defaultValue.equalsIgnoreCase(Global.NON_LOOPBACK_ADDRESS)) + converted=default_ip_address; + else + converted=PropertyHelper.getConvertedValue(protocol, fields[j], defaultValue, true); + if(converted != null) + setField(fields[j], protocol, converted); + } + catch(Exception e) { + throw new Exception("default could not be assigned for field " + fields[j].getName() + " in " + + protocolName + " with default value " + defaultValue, e); + } - ProtocolReq(Protocol p) { - this.name=p.getName(); - if(p.requiredUpServices() != null) { - up_reqs.addAll(p.requiredUpServices()); - } - if(p.requiredDownServices() != null) { - down_reqs.addAll(p.requiredDownServices()); + if(log.isDebugEnabled()) + log.debug("set property " + protocolName + "." + fields[j].getName() + " to default value " + converted); + } + } + } } + } + } - if(p.providedUpServices() != null) { - up_provides.addAll(p.providedUpServices()); - } - if(p.providedDownServices() != null) { - down_provides.addAll(p.providedDownServices()); - } - } + /** + * Makes sure that all fields annotated with @LocalAddress is (1) an InetAddress and (2) a valid address on any + * local network interface + * @param protocols + * @throws Exception + */ + public static void ensureValidBindAddresses(List protocols) throws Exception { + for(Protocol protocol : protocols) { + String protocolName=protocol.getName(); - boolean providesUpService(int evt_type) { - for(Integer type:up_provides) { - if(type == evt_type) - return true; + //traverse class hierarchy and find all annotated fields and add them to the list if annotated + Field[] fields=Util.getAllDeclaredFieldsWithAnnotations(protocol.getClass(), LocalAddress.class); + for(int i=0; i < fields.length; i++) { + Object val=getValueFromProtocol(protocol, fields[i]); + if(!(val instanceof InetAddress)) + throw new Exception("field " + protocolName + "." + fields[i].getName() + " is not an InetAddress"); + Util.checkIfValidAddress((InetAddress)val, protocolName); } - return false; } + } - boolean providesDownService(int evt_type) { - for(Integer type:down_provides) { - if(type == evt_type) - return true; - } - return false; - } - public String toString() { - StringBuilder ret=new StringBuilder(); - ret.append('\n' + name + ':'); - if(!up_reqs.isEmpty()) - ret.append("\nRequires from above: " + printUpReqs()); + public static Object getValueFromProtocol(Protocol protocol, Field field) throws IllegalAccessException { + if(protocol == null || field == null) return null; + if(!Modifier.isPublic(field.getModifiers())) + field.setAccessible(true); + return field.get(protocol); + } - if(!down_reqs.isEmpty()) - ret.append("\nRequires from below: " + printDownReqs()); - if(!up_provides.isEmpty()) - ret.append("\nProvides to above: " + printUpProvides()); + public static Object getValueFromProtocol(Protocol protocol, String field_name) throws IllegalAccessException { + if(protocol == null || field_name == null) return null; + Field field=Util.getField(protocol.getClass(), field_name); + return field != null? getValueFromProtocol(protocol, field) : null; + } - if(!down_provides.isEmpty()) - ret.append("\nProvides to below: ").append(printDownProvides()); - return ret.toString(); - } - String printUpReqs() { - StringBuilder ret; - ret=new StringBuilder("["); - for(Integer type:up_reqs) { - ret.append(Event.type2String(type) + ' '); - } - return ret.toString() + ']'; - } - String printDownReqs() { - StringBuilder ret=new StringBuilder("["); - for(Integer type:down_reqs) { - ret.append(Event.type2String(type) + ' '); - } - return ret.toString() + ']'; + /** + * This method creates a list of all properties (Field or Method) in dependency order, + * where dependencies are specified using the dependsUpon specifier of the Property annotation. + * In particular, it does the following: + * (i) creates a master list of properties + * (ii) checks that all dependency references are present + * (iii) creates a copy of the master list in dependency order + */ + static AccessibleObject[] computePropertyDependencies(Object obj, Map properties) { + + // List of Fields and Methods of the protocol annotated with @Property + List unorderedFieldsAndMethods = new LinkedList() ; + List orderedFieldsAndMethods = new LinkedList() ; + // Maps property name to property object + Map propertiesInventory = new HashMap() ; + + // get the methods for this class and add them to the list if annotated with @Property + Method[] methods=obj.getClass().getMethods(); + for(int i = 0; i < methods.length; i++) { + + if (methods[i].isAnnotationPresent(Property.class) && isSetPropertyMethod(methods[i])) { + String propertyName = PropertyHelper.getPropertyName(methods[i]) ; + unorderedFieldsAndMethods.add(methods[i]) ; + propertiesInventory.put(propertyName, methods[i]) ; + } + } + //traverse class hierarchy and find all annotated fields and add them to the list if annotated + for(Class clazz=obj.getClass(); clazz != null; clazz=clazz.getSuperclass()) { + Field[] fields=clazz.getDeclaredFields(); + for(int i = 0; i < fields.length; i++ ) { + if (fields[i].isAnnotationPresent(Property.class)) { + String propertyName = PropertyHelper.getPropertyName(fields[i], properties) ; + unorderedFieldsAndMethods.add(fields[i]) ; + // may need to change this based on name parameter of Property + propertiesInventory.put(propertyName, fields[i]) ; + } + } + } + + // at this stage, we have all Fields and Methods annotated with @Property + checkDependencyReferencesPresent(unorderedFieldsAndMethods, propertiesInventory) ; + + // order the fields and methods by dependency + orderedFieldsAndMethods = orderFieldsAndMethodsByDependency(unorderedFieldsAndMethods, propertiesInventory) ; + + // convert to array of Objects + AccessibleObject[] result = new AccessibleObject[orderedFieldsAndMethods.size()] ; + for(int i = 0; i < orderedFieldsAndMethods.size(); i++) { + result[i] = orderedFieldsAndMethods.get(i) ; + } + + return result ; + } + + static List orderFieldsAndMethodsByDependency(List unorderedList, + Map propertiesMap) { + // Stack to detect cycle in depends relation + Stack stack = new Stack() ; + // the result list + List orderedList = new LinkedList() ; + + // add the elements from the unordered list to the ordered list + // any dependencies will be checked and added first, in recursive manner + for(int i = 0; i < unorderedList.size(); i++) { + AccessibleObject obj = unorderedList.get(i) ; + addPropertyToDependencyList(orderedList, propertiesMap, stack, obj) ; + } + + return orderedList ; + } + + /** + * DFS of dependency graph formed by Property annotations and dependsUpon parameter + * This is used to create a list of Properties in dependency order + */ + static void addPropertyToDependencyList(List orderedList, Map props, Stack stack, AccessibleObject obj) { + + if (orderedList.contains(obj)) + return ; + + if (stack.search(obj) > 0) { + throw new RuntimeException("Deadlock in @Property dependency processing") ; + } + // record the fact that we are processing obj + stack.push(obj) ; + // process dependencies for this object before adding it to the list + Property annotation = obj.getAnnotation(Property.class) ; + String dependsClause = annotation.dependsUpon() ; + StringTokenizer st = new StringTokenizer(dependsClause, ",") ; + while (st.hasMoreTokens()) { + String token = st.nextToken().trim(); + AccessibleObject dep = props.get(token) ; + // if null, throw exception + addPropertyToDependencyList(orderedList, props, stack, dep) ; + } + // indicate we're done with processing dependencies + stack.pop() ; + // we can now add in dependency order + orderedList.add(obj) ; + } + + /* + * Checks that for every dependency referred, there is a matching property + */ + static void checkDependencyReferencesPresent(List objects, Map props) { + + // iterate overall properties marked by @Property + for(int i = 0; i < objects.size(); i++) { + + // get the Property annotation + AccessibleObject ao = objects.get(i) ; + Property annotation = ao.getAnnotation(Property.class) ; + if (annotation == null) { + throw new IllegalArgumentException("@Property annotation is required for checking dependencies;" + + " annotation is missing for Field/Method " + ao.toString()) ; + } + + String dependsClause = annotation.dependsUpon() ; + if (dependsClause.trim().length() == 0) + continue ; + + // split dependsUpon specifier into tokens; trim each token; search for token in list + StringTokenizer st = new StringTokenizer(dependsClause, ",") ; + while (st.hasMoreTokens()) { + String token = st.nextToken().trim() ; + + // check that the string representing a property name is in the list + boolean found = false ; + Set keyset = props.keySet(); + for (Iterator iter = keyset.iterator(); iter.hasNext();) { + if (iter.next().equals(token)) { + found = true ; + break ; + } + } + if (!found) { + throw new IllegalArgumentException("@Property annotation " + annotation.name() + + " has an unresolved dependsUpon property: " + token) ; + } + } + } + + } + + public static void resolveAndInvokePropertyMethods(Object obj, Map props) throws Exception { + Method[] methods=obj.getClass().getMethods(); + for(Method method: methods) { + resolveAndInvokePropertyMethod(obj, method, props) ; } + } - String printUpProvides() { - StringBuilder ret=new StringBuilder("["); - for(Integer type:up_provides) { - ret.append(Event.type2String(type) + ' '); + public static void resolveAndInvokePropertyMethod(Object obj, Method method, Map props) throws Exception { + String methodName=method.getName(); + Property annotation=method.getAnnotation(Property.class); + if(annotation != null && isSetPropertyMethod(method)) { + String propertyName=PropertyHelper.getPropertyName(method) ; + String propertyValue=props.get(propertyName); + + // if there is a systemProperty attribute defined in the annotation, set the property value from the system property + String tmp=grabSystemProp(method.getAnnotation(Property.class)); + if(tmp != null) + propertyValue=tmp; + + if(propertyName != null && propertyValue != null) { + String deprecated_msg=annotation.deprecatedMessage(); + if(deprecated_msg != null && deprecated_msg.length() > 0) { + log.warn(method.getDeclaringClass().getSimpleName() + "." + methodName + " has been deprecated : " + + deprecated_msg); + } } - return ret.toString() + ']'; - } - String printDownProvides() { - StringBuilder ret=new StringBuilder("["); - for(Integer type:down_provides) { - ret.append(Event.type2String(type) + ' '); + if(propertyValue != null) { + Object converted=null; + try { + converted=PropertyHelper.getConvertedValue(obj, method, props, propertyValue, true); + method.invoke(obj, converted); + } + catch(Exception e) { + String name=obj instanceof Protocol? ((Protocol)obj).getName() : obj.getClass().getName(); + throw new Exception("Could not assign property " + propertyName + " in " + + name + ", method is " + methodName + ", converted value is " + converted, e); + } + } + + props.remove(propertyName); + } + } + + public static void resolveAndAssignFields(Object obj, Map props) throws Exception { + //traverse class hierarchy and find all annotated fields + for(Class clazz=obj.getClass(); clazz != null; clazz=clazz.getSuperclass()) { + Field[] fields=clazz.getDeclaredFields(); + for(Field field: fields) { + resolveAndAssignField(obj, field, props) ; } - return ret.toString() + ']'; } } + public static void resolveAndAssignField(Object obj, Field field, Map props) throws Exception { + Property annotation=field.getAnnotation(Property.class); + if(annotation != null) { + String propertyName = PropertyHelper.getPropertyName(field, props) ; + String propertyValue=props.get(propertyName); - /** - * Parses and encapsulates the specification for 1 protocol of the protocol stack, e.g. - * UNICAST(timeout=5000) - */ - public static class ProtocolConfiguration { - private final String protocol_name; - private final String properties_str; - private final Properties properties=new Properties(); - private static final String protocol_prefix="org.jgroups.protocols"; + // if there is a systemProperty attribute defined in the annotation, set the property value from the system property + String tmp=grabSystemProp(field.getAnnotation(Property.class)); + if(tmp != null) + propertyValue=tmp; - - /** - * Creates a new ProtocolConfiguration. - * @param config_str The configuration specification for the protocol, e.g. - *
        VERIFY_SUSPECT(timeout=1500)
        - */ - public ProtocolConfiguration(String config_str) throws Exception { - int index=config_str.indexOf('('); // e.g. "UDP(in_port=3333)" - int end_index=config_str.lastIndexOf(')'); - - if(index == -1) { - protocol_name=config_str; - properties_str=""; - } - else { - if(end_index == -1) { - throw new Exception("Configurator.ProtocolConfiguration(): closing ')' " + - "not found in " + config_str + ": properties cannot be set !"); - } - else { - properties_str=config_str.substring(index + 1, end_index); - protocol_name=config_str.substring(0, index); + if(propertyName != null && propertyValue != null) { + String deprecated_msg=annotation.deprecatedMessage(); + if(deprecated_msg != null && deprecated_msg.length() > 0) { + log.warn(field.getDeclaringClass().getSimpleName() + "." + field.getName() + " has been deprecated: " + + deprecated_msg); } } - - /* "in_port=5555;out_port=6666" */ - if(properties_str.length() > 0) { - String[] components=properties_str.split(";"); - for(String property : components) { - String name, value; - index=property.indexOf('='); - if(index == -1) { - throw new Exception("Configurator.ProtocolConfiguration(): '=' not found in " + property - + " of " - + protocol_name); + + if(propertyValue != null || !PropertyHelper.usesDefaultConverter(field)){ + Object converted=null; + try { + converted=PropertyHelper.getConvertedValue(obj, field, props, propertyValue, true); + if(converted != null) + setField(field, obj, converted); + } + catch(Exception e) { + String name=obj instanceof Protocol? ((Protocol)obj).getName() : obj.getClass().getName(); + throw new Exception("Property assignment of " + propertyName + " in " + + name + " with original property value " + propertyValue + " and converted to " + converted + + " could not be assigned", e); + } + } + + props.remove(propertyName); + } + } + + + public static void removeDeprecatedProperties(Object obj, Map props) throws Exception { + //traverse class hierarchy and find all deprecated properties + for(Class clazz=obj.getClass(); clazz != null; clazz=clazz.getSuperclass()) { + if(clazz.isAnnotationPresent(DeprecatedProperty.class)) { + DeprecatedProperty declaredAnnotation=clazz.getAnnotation(DeprecatedProperty.class); + String[] deprecatedProperties=declaredAnnotation.names(); + for(String propertyName : deprecatedProperties) { + String propertyValue=props.get(propertyName); + if(propertyValue != null) { + if(log.isWarnEnabled()) { + String name=obj instanceof Protocol? ((Protocol)obj).getName() : obj.getClass().getName(); + log.warn(name + " property " + propertyName + " was deprecated and is ignored"); + } + props.remove(propertyName); } - name=property.substring(0, index); - value=property.substring(index + 1, property.length()); - properties.put(name, value); } } } + } - public String getProtocolName() { - return protocol_name; - } + public static boolean isSetPropertyMethod(Method method) { + return (method.getName().startsWith("set") && + method.getReturnType() == java.lang.Void.TYPE && + method.getParameterTypes().length == 1); + } - public Properties getProperties() { - return properties; - } - private Protocol createLayer(ProtocolStack prot_stack) throws Exception { - Protocol retval=null; - if(protocol_name == null) - return null; + public static void setField(Field field, Object target, Object value) { + if(!Modifier.isPublic(field.getModifiers())) { + field.setAccessible(true); + } + try { + field.set(target, value); + } + catch(IllegalAccessException iae) { + throw new IllegalArgumentException("Could not set field " + field, iae); + } + } - String defaultProtocolName=protocol_prefix + '.' + protocol_name; - Class clazz=null; + public static Object getField(Field field, Object target) { + if(!Modifier.isPublic(field.getModifiers())) { + field.setAccessible(true); + } + try { + return field.get(target); + } + catch(IllegalAccessException iae) { + throw new IllegalArgumentException("Could not get field " + field, iae); + } + } - try { - clazz=Util.loadClass(defaultProtocolName, this.getClass()); - } - catch(ClassNotFoundException e) { - } - if(clazz == null) { + private static String grabSystemProp(Property annotation) { + String[] system_property_names=annotation.systemProperty(); + String retval=null; + + for(String system_property_name: system_property_names) { + if(system_property_name != null && system_property_name.length() > 0) { + if(system_property_name.equals(Global.BIND_ADDR) || system_property_name.equals(Global.BIND_ADDR_OLD)) + if(Util.isBindAddressPropertyIgnored()) + continue; + try { - clazz=Util.loadClass(protocol_name, this.getClass()); - } - catch(ClassNotFoundException e) { + retval=System.getProperty(system_property_name); + if(retval != null) + return retval; } - if(clazz == null) { - throw new Exception("unable to load class for protocol " + protocol_name + - " (either as an absolute - " + protocol_name + " - or relative - " + - defaultProtocolName + " - package name)!"); + catch(SecurityException ex) { + log.error("failed getting system property for " + system_property_name, ex); } } + } + return retval; + } - try { - retval=(Protocol)clazz.newInstance(); - if(retval == null) - throw new Exception("creation of instance for protocol " + protocol_name + "failed !"); - retval.setProtocolStack(prot_stack); - removeDeprecatedProperties(retval, properties); - resolveAndAssignFields(retval, properties); - resolveAndInvokePropertyMethods(retval, properties); - - List additional_objects=retval.getConfigurableObjects(); - if(additional_objects != null && !additional_objects.isEmpty()) { - for(Object obj: additional_objects) { - resolveAndAssignFields(obj, properties); - resolveAndInvokePropertyMethods(obj, properties); - } - } + /* --------------------------- End of Private Methods ---------------------------------- */ - if(!properties.isEmpty()) { - throw new IllegalArgumentException("the following properties in " + protocol_name - + " are not recognized: " + properties); - } - } - catch(InstantiationException inst_ex) { - log.error("an instance of " + protocol_name + " could not be created. Please check that it implements" + - " interface Protocol and that is has a public empty constructor !"); - throw inst_ex; - } - return retval; - } - public String toString() { - StringBuilder retval=new StringBuilder(); - retval.append("Protocol: "); - if(protocol_name == null) - retval.append(""); - else - retval.append(protocol_name); - if(properties != null) - retval.append("(" + properties + ')'); - return retval.toString(); - } + public static class InetAddressInfo { + Protocol protocol ; + AccessibleObject fieldOrMethod ; + Map properties ; + String propertyName ; + String stringValue ; + Object convertedValue ; + boolean isField ; + boolean isParameterized ; // is the associated type parametrized? (e.g. Collection) + Object baseType ; // what is the base type (e.g. Collection) + + InetAddressInfo(Protocol protocol, AccessibleObject fieldOrMethod, Map properties, String stringValue, + Object convertedValue) { + // check input values + if (protocol == null) { + throw new IllegalArgumentException("Protocol for Field/Method must be non-null") ; + } + if (fieldOrMethod instanceof Field) { + isField = true ; + } else if (fieldOrMethod instanceof Method) { + isField = false ; + } else + throw new IllegalArgumentException("AccesibleObject is neither Field nor Method") ; + if (properties == null) { + throw new IllegalArgumentException("Properties for Field/Method must be non-null") ; + } + + // set the values passed by the user - need to check for null + this.protocol = protocol ; + this.fieldOrMethod = fieldOrMethod ; + this.properties = properties ; + this.stringValue = stringValue ; + this.convertedValue = convertedValue ; + + // set the property name + Property annotation=fieldOrMethod.getAnnotation(Property.class); + if (isField()) + propertyName=PropertyHelper.getPropertyName((Field)fieldOrMethod, properties) ; + else + propertyName=PropertyHelper.getPropertyName((Method)fieldOrMethod) ; + + // is variable type parameterized + this.isParameterized = false ; + if (isField()) + this.isParameterized = hasParameterizedType((Field)fieldOrMethod) ; + else + this.isParameterized = hasParameterizedType((Method)fieldOrMethod) ; + + // if parameterized, what is the base type? + this.baseType = null ; + if (isField() && isParameterized) { + // the Field has a single type + ParameterizedType fpt = (ParameterizedType)((Field)fieldOrMethod).getGenericType() ; + this.baseType = fpt.getActualTypeArguments()[0] ; + } + else if (!isField() && isParameterized) { + // the Method has several parameters (and so types) + Type[] types = (Type[])((Method)fieldOrMethod).getGenericParameterTypes(); + ParameterizedType mpt = (ParameterizedType) types[0] ; + this.baseType = mpt.getActualTypeArguments()[0] ; + } + } + + // Protocol getProtocol() {return protocol ;} + Object getProtocol() {return protocol ;} + AccessibleObject getFieldOrMethod() {return fieldOrMethod ;} + boolean isField() { return isField ; } + String getStringValue() {return stringValue ;} + String getPropertyName() {return propertyName ;} + Map getProperties() {return properties ;} + Object getConvertedValue() {return convertedValue ;} + boolean isParameterized() {return isParameterized ;} + Object getBaseType() { return baseType ;} + + static boolean hasParameterizedType(Field f) { + if (f == null) { + throw new IllegalArgumentException("Field argument is null") ; + } + Type type = f.getGenericType(); + return (type instanceof ParameterizedType) ; + } + static boolean hasParameterizedType(Method m) throws IllegalArgumentException { + if (m == null) { + throw new IllegalArgumentException("Method argument is null") ; + } + Type[] types = m.getGenericParameterTypes() ; + return (types[0] instanceof ParameterizedType) ; + } + + static boolean isInetAddressRelated(Protocol prot, Field f) { + if (hasParameterizedType(f)) { + // check for List, List, List + ParameterizedType fieldtype = (ParameterizedType) f.getGenericType() ; + // check that this parameterized type satisfies our constraints + try { + parameterizedTypeSanityCheck(fieldtype) ; + } + catch(IllegalArgumentException e) { + // because this Method's parameter fails the sanity check, its probably not an InetAddress related structure + return false ; + } + + Class listType = (Class) fieldtype.getActualTypeArguments()[0] ; + return isInetAddressOrCompatibleType(listType) ; + } + else { + // check if the non-parameterized type is InetAddress, InetSocketAddress or IpAddress + Class fieldtype = f.getType() ; + return isInetAddressOrCompatibleType(fieldtype) ; + } + } + /* + * Checks if this method's single parameter represents of of the following: + * an InetAddress, IpAddress or InetSocketAddress or one of + * List, List or List + */ + static boolean isInetAddressRelated(Method m) { + if (hasParameterizedType(m)) { + Type[] types = m.getGenericParameterTypes(); + ParameterizedType methodParamType = (ParameterizedType)types[0] ; + // check that this parameterized type satisfies our constraints + try { + parameterizedTypeSanityCheck(methodParamType) ; + } + catch(IllegalArgumentException e) { + if(log.isErrorEnabled()) { + log.error("Method " + m.getName() + " failed paramaterizedTypeSanityCheck()") ; + } + // because this Method's parameter fails the sanity check, its probably not + // an InetAddress related structure + return false ; + } + + Class listType = (Class) methodParamType.getActualTypeArguments()[0] ; + return isInetAddressOrCompatibleType(listType) ; + } + else { + Class methodParamType = m.getParameterTypes()[0] ; + return isInetAddressOrCompatibleType(methodParamType) ; + } + } + static boolean isInetAddressOrCompatibleType(Class c) { + return c.equals(InetAddress.class) || c.equals(InetSocketAddress.class) || c.equals(IpAddress.class) ; + } + /* + * Check if the parameterized type represents one of: + * List, List, List + */ + static void parameterizedTypeSanityCheck(ParameterizedType pt) throws IllegalArgumentException { + + Type rawType = pt.getRawType() ; + Type[] actualTypes = pt.getActualTypeArguments() ; + + // constraints on use of parameterized types with @Property + if (!(rawType instanceof Class && rawType.equals(List.class))) { + throw new IllegalArgumentException("Invalid parameterized type definition - parameterized type must be a List") ; + } + // check for non-parameterized type in List + if (!(actualTypes[0] instanceof Class)) { + throw new IllegalArgumentException("Invalid parameterized type - List must not contain a parameterized type") ; + } + } + + /* + * Converts the computedValue to a list of InetAddresses. + * Because computedValues may be null, we need to return + * a zero length list in some cases. + */ + public List getInetAddresses() { + List addresses = new ArrayList() ; + if (getConvertedValue() == null) + return addresses ; + // if we take only an InetAddress argument + if (!isParameterized()) { + addresses.add(getInetAddress(getConvertedValue())) ; + return addresses ; + } + // if we take a List or similar + else { + List values = (List) getConvertedValue() ; + if (values.isEmpty()) + return addresses ; + for (int i = 0; i < values.size(); i++) { + addresses.add(getInetAddress(values.get(i))) ; + } + return addresses ; + } + } + + private static InetAddress getInetAddress(Object obj) throws IllegalArgumentException { + if (obj == null) + throw new IllegalArgumentException("Input argument must represent a non-null IP address") ; + if (obj instanceof InetAddress) + return (InetAddress) obj ; + else if (obj instanceof IpAddress) + return ((IpAddress) obj).getIpAddress() ; + else if (obj instanceof InetSocketAddress) + return ((InetSocketAddress) obj).getAddress() ; + else { + if (log.isWarnEnabled()) + log.warn("Input argument does not represent one of InetAddress...: class=" + obj.getClass().getName()) ; + throw new IllegalArgumentException("Input argument does not represent one of InetAddress. IpAddress not InetSocketAddress") ; + } + } + + public String toString() { + StringBuilder sb = new StringBuilder() ; + sb.append("InetAddressInfo(") ; + sb.append("protocol=" + protocol.getName()) ; + sb.append(", propertyName=" + getPropertyName()) ; + sb.append(", string value=" + getStringValue()) ; + sb.append(", parameterized=" + isParameterized()) ; + if (isParameterized()) + sb.append(", baseType=" + getBaseType()) ; + sb.append(")") ; + return sb.toString(); + } } } - - diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/DefaultRetransmitter.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/DefaultRetransmitter.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/DefaultRetransmitter.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/DefaultRetransmitter.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,133 @@ + +package org.jgroups.stack; + +import org.jgroups.Address; +import org.jgroups.util.TimeScheduler; +import org.jgroups.util.Util; + +import java.util.Set; +import java.util.concurrent.ConcurrentMap; + + +/** + * Maintains a pool of sequence numbers of messages that need to be retransmitted. Messages + * are aged and retransmission requests sent according to age (configurable backoff). If a + * TimeScheduler instance is given to the constructor, it will be used, otherwise Reransmitter + * will create its own. The retransmit timeouts have to be set first thing after creating an instance. + * The add() method adds the sequence numbers of messages to be retransmitted. The + * remove() method removes a sequence number again, cancelling retransmission requests for it. + * Whenever a message needs to be retransmitted, the RetransmitCommand.retransmit() method is called. + * It can be used e.g. by an ack-based scheme (e.g. AckSenderWindow) to retransmit a message to the receiver, or + * by a nak-based scheme to send a retransmission request to the sender of the missing message.
        + * Changes Aug 2007 (bela): the retransmitter was completely rewritten. Entry was removed, instead all tasks + * are directly placed into a hashmap, keyed by seqnos. When a message has been received, we simply remove + * the task from the hashmap and cancel it. This simplifies the code and avoids having to iterate through + * the (previous) message list linearly on removal. Performance is about the same, or slightly better in + * informal tests. + * @author Bela Ban + * @version $Revision: 1.4 $ + */ +public class DefaultRetransmitter extends Retransmitter { + private final ConcurrentMap msgs=Util.createConcurrentMap(); + + + /** + * Create a new Retransmitter associated with the given sender address + * @param sender the address from which retransmissions are expected or to which retransmissions are sent + * @param cmd the retransmission callback reference + * @param sched retransmissions scheduler + */ + public DefaultRetransmitter(Address sender, RetransmitCommand cmd, TimeScheduler sched) { + super(sender, cmd, sched); + } + + + + /** + * Add the given range [first_seqno, last_seqno] in the list of + * entries eligible for retransmission. If first_seqno > last_seqno, + * then the range [last_seqno, first_seqno] is added instead + */ + public void add(long first_seqno, long last_seqno) { + if(first_seqno > last_seqno) { + long tmp=first_seqno; + first_seqno=last_seqno; + last_seqno=tmp; + } + + Task new_task; + for(long seqno=first_seqno; seqno <= last_seqno; seqno++) { + // each task needs its own retransmission interval, as they are stateful *and* mutable, so we *need* to copy ! + new_task=new SeqnoTask(seqno, RETRANSMIT_TIMEOUTS.copy(), cmd, sender); + Task old_task=msgs.putIfAbsent(seqno, new_task); + if(old_task == null) // only schedule if we actually *added* the new task ! + new_task.doSchedule(); // Entry adds itself to the timer + } + + } + + /** + * Remove the given sequence number from the list of seqnos eligible + * for retransmission. If there are no more seqno intervals in the + * respective entry, cancel the entry from the retransmission + * scheduler and remove it from the pending entries + */ + public int remove(long seqno) { + Task task=msgs.remove(seqno); + if(task != null) { + task.cancel(); + return task.getNumRetransmits(); + } + return -1; + } + + /** + * Reset the retransmitter: clear all msgs and cancel all the + * respective tasks + */ + public void reset() { + for(Task task: msgs.values()) + task.cancel(); + msgs.clear(); + } + + + public String toString() { + Set keys=msgs.keySet(); + int size=keys.size(); + StringBuilder sb=new StringBuilder(); + sb.append(size).append(" messages to retransmit"); + if(size < 50) + sb.append(": ").append(keys); + return sb.toString(); + } + + + public int size() { + return msgs.size(); + } + + + protected class SeqnoTask extends Task { + private long seqno=-1; + + protected SeqnoTask(long seqno, Interval intervals, RetransmitCommand cmd, Address msg_sender) { + super(intervals, cmd, msg_sender); + this.seqno=seqno; + } + + + public String toString() { + return String.valueOf(seqno); + } + + protected void callRetransmissionCommand() { + command.retransmit(seqno, seqno, msg_sender); + } + } + + + + +} + diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/ExponentialInterval.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/ExponentialInterval.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/ExponentialInterval.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/ExponentialInterval.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,7 +2,6 @@ /** * @author Bela Ban - * @version $Id: ExponentialInterval.java,v 1.1 2007/08/14 07:51:12 belaban Exp $ */ public class ExponentialInterval implements Interval { private long value=30; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/GossipClient.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/GossipClient.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/GossipClient.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/GossipClient.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,600 +0,0 @@ -package org.jgroups.stack; - - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jgroups.Address; -import org.jgroups.util.TimeScheduler; -import org.jgroups.util.Util; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.util.*; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - - -/** - * Local stub for clients to access one (or more) GossipRouters. Will use proprietary protocol - * (using GossipData PDUs) based on TCP to connect to GossipRouter.

        - * - * @author Bela Ban Oct 4 2001 - * @version $Id: GossipClient.java,v 1.27 2008/12/10 15:15:56 vlada Exp $ - */ -public class GossipClient { - TimeScheduler timer=null; - - /** Hashtable> */ - final Map> groups=new Hashtable>(); // groups - List of Addresses - private Future refresher_task=null; - final Vector gossip_servers=new Vector(); // a list of GossipRouters (IpAddress) - boolean refresher_enabled=true; - long refresh_interval=20000; // must be less than in GossipRouter - int sock_conn_timeout=2000; // max number of ms to wait for socket establishment to GossipRouter - int sock_read_timeout=0; // max number of ms to wait for socket reads (0 means block forever, or until the sock is closed) - - protected final Log log=LogFactory.getLog(this.getClass()); - - - public GossipClient(IpAddress gossip_host, long expiry, int sock_conn_timeout, TimeScheduler timer) { - this(new Vector(Arrays.asList(gossip_host)), expiry, sock_conn_timeout, timer); - } - - /** - Creates the GossipClient - @param gossip_hosts List of IpAddresses - @param expiry Interval (in msecs) for the refresher task - */ - public GossipClient(List gossip_hosts, long expiry, int sock_conn_timeout, TimeScheduler timer) { - this.sock_conn_timeout=sock_conn_timeout; - setTimer(timer); - if(gossip_hosts == null) { - if(log.isErrorEnabled()) log.error("empty set of GossipRouters given"); - return; - } - for(IpAddress host: gossip_hosts) - init(host, expiry); - } - - - public boolean isRefresherEnabled() { - return refresher_enabled; - } - - public void setRefresherEnabled(boolean refresher_enabled) { - this.refresher_enabled=refresher_enabled; - } - - public int getSocketConnectionTimeout() { - return sock_conn_timeout; - } - - public void setSocketConnectionTimeout(int sock_conn_timeout) { - this.sock_conn_timeout=sock_conn_timeout; - } - - public int getSocketReadTimeout() { - return sock_read_timeout; - } - - public void setSocketReadTimeout(int sock_read_timeout) { - this.sock_read_timeout=sock_read_timeout; - } - - public long getRefreshInterval() { - return refresh_interval; - } - - public void setRefreshInterval(long refresh_interval) { - this.refresh_interval=refresh_interval; - } - - public void setTimer(TimeScheduler timer) { - if(this.timer != null) { - try { - this.timer.stop(); - } - catch(InterruptedException e) { - } - } - this.timer=timer; - if(this.timer == null) - this.timer=new TimeScheduler(); - } - - public void stop() { - synchronized(this) { - if(refresher_task != null) { - refresher_task.cancel(true); - refresher_task=null; - } - } - groups.clear(); - } - - public void stopTimer() { - if(timer != null) { - try {timer.stop();} catch(InterruptedException e) {} - } - } - - - public void destroy() { - stop(); // needed ? - } - - - /** - * Adds a GossipRouter to be accessed. - */ - public void addGossipRouter(IpAddress gossip_host) { - if(!gossip_servers.contains(gossip_host)) - gossip_servers.addElement(gossip_host); - } - - - /** - Adds the member to the given group. If the group already has an entry for the member, - its timestamp will be updated, preventing the cache cleaner from removing the entry.

        - The entry will be registered with all GossipRouters that GossipClient is configured to access - */ - public void register(String group, Address mbr, boolean synchronous) { - if(group == null || mbr == null) { - if(log.isErrorEnabled()) log.error("group or mbr is null"); - return; - } - - List

        mbrs=groups.get(group); - if(mbrs == null) { - mbrs=new LinkedList
        (); - mbrs.add(mbr); - groups.put(group, mbrs); - } - else { - if(!mbrs.contains(mbr)) - mbrs.add(mbr); - } - - _register(group, mbr, synchronous); // update entry in GossipRouter - - if(refresher_enabled) { - synchronized(this) { - if(refresher_task == null || refresher_task.isDone()) { - Refresher tmp=new Refresher(); - refresher_task=timer.scheduleWithFixedDelay(tmp, refresh_interval, refresh_interval, TimeUnit.MILLISECONDS); - } - } - } - } - - public void register(String group, Address mbr) { - register(group, mbr, false); - } - - - public void unregister(String group, Address mbr) { - if(group == null || mbr == null) { - if(log.isErrorEnabled()) log.error("group or mbr is null"); - return; - } - - _unregister(group, mbr); // remove entry from GossipRouter - } - - - /** - Returns all members of a given group - @param group The group name - @return List A list of Addresses - */ - public List
        getMembers(String group, long timeout) { - if(group == null) { - if(log.isErrorEnabled()) log.error("group is null"); - return null; - } - List
        result=_getMembers(group, timeout); - if(log.isTraceEnabled()) - log.trace("GET(" + group + ") --> " + result); - return result; - } - - public List
        getMembers(String group) { - return getMembers(group, 0); - } - - - - /* ------------------------------------- Private methods ----------------------------------- */ - - - final void init(IpAddress gossip_host, long expiry) { - refresh_interval=expiry; - addGossipRouter(gossip_host); - } - - - /** - * Registers the group|mbr with *all* GossipRouters. - */ - void _register(final String group, final Address mbr, boolean synchronous) { - List> futures=null; - if(synchronous) - futures=new ArrayList>(); - - for(final IpAddress entry: gossip_servers) { - if(entry.getIpAddress() == null || entry.getPort() == 0) { - if(log.isErrorEnabled()) log.error("entry.host or entry.port is null"); - continue; - } - Future future=timer.submit(new Runnable() { - public void run() { - Socket sock=null; - DataOutputStream out=null; - try { - if(log.isTraceEnabled()) - log.trace("REGISTER(" + group + ", " + mbr + ") with GossipRouter at " + entry.getIpAddress() + ':' + entry.getPort()); - sock=new Socket(); - if(sock_read_timeout > 0) - sock.setSoTimeout(sock_read_timeout); - sock.connect(new InetSocketAddress(entry.getIpAddress(), entry.getPort()), sock_conn_timeout); - out=new DataOutputStream(sock.getOutputStream()); - GossipData gossip_req=new GossipData(GossipRouter.REGISTER, group, mbr, null); - // must send GossipData as fast as possible, otherwise the request might be rejected - gossip_req.writeTo(out); - out.flush(); - } - catch(Exception ex) { - if(log.isErrorEnabled()) - log.error("register(" + group + ", " + mbr + "): exception connecting to host " + entry); - } - finally { - Util.close(out); - if(sock != null) { - try { - sock.close(); - } - catch(IOException e) { - } - } - } - } - }); - - if(futures != null) - futures.add(future); - } - if(futures != null) { - for(Future f: futures) { - try { - f.get(); - } - catch(Throwable t) { - } - } - } - } - - - void _unregister(final String group, final Address mbr) { - for(final IpAddress entry: gossip_servers) { - if(entry.getIpAddress() == null || entry.getPort() == 0) { - if(log.isErrorEnabled()) log.error("entry.host or entry.port is null"); - continue; - } - timer.submit(new Runnable() { - public void run() { - Socket sock=null; - DataOutputStream out=null; - try { - if(log.isTraceEnabled()) - log.trace("UNREGISTER(" + group + ", " + mbr + ") with GossipRouter at " + entry.getIpAddress() + ':' + entry.getPort()); - sock=new Socket(); - if(sock_read_timeout > 0) - sock.setSoTimeout(sock_read_timeout); - sock.connect(new InetSocketAddress(entry.getIpAddress(), entry.getPort()), sock_conn_timeout); - out=new DataOutputStream(sock.getOutputStream()); - GossipData gossip_req=new GossipData(GossipRouter.UNREGISTER, group, mbr, null); - // must send GossipData as fast as possible, otherwise the request might be rejected - gossip_req.writeTo(out); - out.flush(); - } - catch(Exception ex) { - if(log.isErrorEnabled()) log.error("exception connecting to host " + entry); - } - finally { - Util.close(out); - if(sock != null) { - try {sock.close();} catch(IOException e) {} - } - } - } - }); - } - } - - - /** - * Sends a GET_MBR_REQ to *all* GossipRouters, merges responses. - */ -/* private List
        _getMembers(final String group) { - final List
        ret=new LinkedList
        (); - - List>> tasks=new ArrayList>>(gossip_servers.size()); - for(int i=0; i < gossip_servers.size(); i++) { - final IpAddress entry=(IpAddress) gossip_servers.elementAt(i); - if(entry.getIpAddress() == null || entry.getPort() == 0) { - if(log.isErrorEnabled()) log.error("entry.host or entry.port is null"); - continue; - } - - tasks.add(new Callable>() { - public List
        call() throws Exception { - Socket sock=null; - DataOutputStream out=null; - DataInputStream in=null; - try { - sock=new Socket(); - System.out.println("CONNECTING to " + entry); - sock.connect(new InetSocketAddress(entry.getIpAddress(), entry.getPort()), sock_conn_timeout); - out=new DataOutputStream(sock.getOutputStream()); - GossipData gossip_req=new GossipData(GossipRouter.GOSSIP_GET, group, null, null); - // must send GossipData as fast as possible, otherwise the request might be rejected - gossip_req.writeTo(out); - out.flush(); - in=new DataInputStream(sock.getInputStream()); - GossipData gossip_rsp=new GossipData(); - gossip_rsp.readFrom(in); - System.out.println("gossip_rsp = " + gossip_rsp); - return gossip_rsp.mbrs; - } - catch(Exception ex) { - if(log.isErrorEnabled()) log.error("exception connecting to host " + entry); - return null; - } - finally { - Util.close(out); - Util.close(in); - if(sock != null) { - try {sock.close();} catch(IOException e) {} - } - } - } - }); - } - - try { - List>> responses=timer.invokeAll(tasks); - for(Future> future: responses) { - if(!future.isCancelled()) { - try { - List
        addrs=future.get(); - if(addrs != null) { - for(Address addr: addrs) { - synchronized(ret) { - if(!ret.contains(addr)) { - System.out.println("adding " + addr); - ret.add(addr); - } - } - } - } - } - catch(Throwable t) { - } - } - } - } - catch(InterruptedException e) { - e.printStackTrace(); - } - - return ret; - }*/ - - private List
        _getMembers(final String group, long timeout) { - final List
        ret=new LinkedList
        (); - final AtomicInteger num_rsps=new AtomicInteger(0); - final long stop_time=System.currentTimeMillis() + timeout; - - for(final IpAddress entry: gossip_servers) { - if(entry.getIpAddress() == null || entry.getPort() == 0) { - if(log.isErrorEnabled()) log.error("entry.host or entry.port is null"); - continue; - } - - timer.execute(new Runnable() { - public void run() { - Socket sock=null; - DataOutputStream out=null; - DataInputStream in=null; - try { - sock=new Socket(); - if(sock_read_timeout > 0) - sock.setSoTimeout(sock_read_timeout); - sock.connect(new InetSocketAddress(entry.getIpAddress(), entry.getPort()), sock_conn_timeout); - out=new DataOutputStream(sock.getOutputStream()); - GossipData gossip_req=new GossipData(GossipRouter.GOSSIP_GET, group, null, null); - // must send GossipData as fast as possible, otherwise the request might be rejected - gossip_req.writeTo(out); - out.flush(); - in=new DataInputStream(sock.getInputStream()); - GossipData gossip_rsp=new GossipData(); - gossip_rsp.readFrom(in); - if(gossip_rsp.mbrs != null) { - for(Address addr: gossip_rsp.mbrs) { - synchronized(ret) { - if(!ret.contains(addr)) { - ret.add(addr); - } - } - } - } - synchronized(ret) { - num_rsps.incrementAndGet(); - ret.notifyAll(); - } - } - catch(Exception ex) { - if(log.isErrorEnabled()) log.error("getMembers(" + group + "): exception connecting to host " + entry); - } - finally { - Util.close(out); - Util.close(in); - if(sock != null) { - try {sock.close();} catch(IOException e) {} - } - } - } - }); - } - - synchronized(ret) { - if(timeout <= 0) { - if(num_rsps.get() == 0) - try {ret.wait();} catch(InterruptedException e) {} - } - else { - long curr_time; - while(num_rsps.get() == 0 && (curr_time=System.currentTimeMillis()) < stop_time) { - long wait_time=stop_time - curr_time; - if(wait_time <= 0) - break; - try {ret.wait(wait_time);} catch(InterruptedException e) {} - } - } - } - - return ret; - } - - /* ---------------------------------- End of Private methods ------------------------------- */ - - - - /** - * Periodically iterates through groups and refreshes all registrations with GossipRouter - */ - private class Refresher implements Runnable { - - public void run() { - int num_items=0; - String group; - List
        mbrs; - - if(log.isTraceEnabled()) log.trace("refresher task is run"); - for(Map.Entry> entry: groups.entrySet()) { - group=entry.getKey(); - mbrs=entry.getValue(); - if(mbrs != null) { - for(Address mbr: mbrs) { - if(log.isTraceEnabled()) log.trace("registering " + group + " : " + mbr); - register(group, mbr); - num_items++; - } - } - } - if(log.isTraceEnabled()) log.trace("refresher task done. Registered " + num_items + " items"); - } - } - - - public static void main(String[] args) { - Vector gossip_hosts=new Vector(); - String host; - InetAddress ip_addr; - int port; - boolean get=false, register=false, keep_running=false; - String register_host=null; - int register_port=0; - String get_group=null, register_group=null; - GossipClient gossip_client=null; - List mbrs; - long expiry=20000; - - - for(int i=0; i < args.length; i++) { - if("-help".equals(args[i])) { - usage(); - return; - } - if("-expiry".equals(args[i])) { - expiry=Long.parseLong(args[++i]); - continue; - } - if("-host".equals(args[i])) { - host=args[++i]; - port=Integer.parseInt(args[++i]); - try { - ip_addr=InetAddress.getByName(host); - gossip_hosts.add(new IpAddress(ip_addr, port)); - } - catch(Exception ex) { - System.err.println(ex); - } - continue; - } - if("-keep_running".equals(args[i])) { - keep_running=true; - continue; - } - if("-get".equals(args[i])) { - get=true; - get_group=args[++i]; - continue; - } - if("-register".equals(args[i])) { - register_group=args[++i]; - register_host=args[++i]; - register_port=Integer.parseInt(args[++i]); - register=true; - continue; - } - usage(); - return; - } - - if(gossip_hosts.isEmpty()) { - System.err.println("At least 1 GossipRouter has to be given"); - return; - } - - if(!register && !get) { - System.err.println("Neither get nor register command given, will not do anything"); - return; - } - - try { - gossip_client=new GossipClient(gossip_hosts, expiry, 1000, null); - if(register) { - System.out.println("Registering " + register_group + " --> " + register_host + ':' + register_port); - gossip_client.register(register_group, new IpAddress(register_host, register_port)); - } - - if(get) { - System.out.println("Getting members for group " + get_group); - mbrs=gossip_client.getMembers(get_group); - System.out.println("Members for group " + get_group + " are " + mbrs); - } - } - catch(Exception ex) { - System.err.println(ex); - } - if(!keep_running) { - gossip_client.stop(); - gossip_client.stopTimer(); - } - } - - - static void usage() { - System.out.println("GossipClient [-help] [-host ]+ " + - " [-get ] [-register ] [-expiry ] " + - "[-keep_running]]"); - } - -} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/GossipData.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/GossipData.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/GossipData.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/GossipData.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,9 +1,10 @@ -// $Id: GossipData.java,v 1.5 2008/02/29 12:19:52 belaban Exp $ package org.jgroups.stack; import org.jgroups.Address; +import org.jgroups.PhysicalAddress; +import org.jgroups.Global; import org.jgroups.util.Streamable; import org.jgroups.util.Util; @@ -12,6 +13,8 @@ import java.io.IOException; import java.util.LinkedList; import java.util.List; +import java.util.Collection; +import java.util.ArrayList; /** @@ -22,7 +25,12 @@ byte type=0; // One of GossipRouter type, e.g. CONNECT, REGISTER etc String group=null; // CONNECT, GET_REQ and GET_RSP Address addr=null; // CONNECT + String logical_name=null; List
        mbrs=null; // GET_RSP + Collection physical_addrs=null; // GET_RSP, GET_REQ + byte[] buffer=null; // MESSAGE + int offset=0; + int length=0; public GossipData() { // for streamable } @@ -31,18 +39,50 @@ this.type=type; } - public GossipData(byte type, String group, Address addr, List
        mbrs) { - this.type=type; + public GossipData(byte type, String group, Address addr) { + this(type); this.group=group; this.addr=addr; + } + + public GossipData(byte type, String group, Address addr, List
        mbrs) { + this(type, group, addr); this.mbrs=mbrs; } + public GossipData(byte type, String group, Address addr, List
        mbrs, List physical_addrs) { + this(type, group, addr, mbrs); + this.physical_addrs=physical_addrs; + } + + public GossipData(byte type, String group, Address addr, String logical_name, List phys_addrs) { + this(type, group, addr); + this.logical_name=logical_name; + this.physical_addrs=phys_addrs; + } + + public GossipData(byte type, String group, Address addr, byte[] buffer) { + this(type, group, addr, buffer, 0, buffer.length); + } + + public GossipData(byte type, String group, Address addr, byte[] buffer, int offset, int length) { + this(type, group, addr); + this.buffer=buffer; + this.offset=offset; + this.length=length; + } + public byte getType() {return type;} public String getGroup() {return group;} public Address getAddress() {return addr;} + public String getLogicalName() {return logical_name;} public List
        getMembers() {return mbrs;} + public byte[] getBuffer() {return buffer;} + + public Collection getPhysicalAddresses() { + return physical_addrs; + } public void setMembers(List
        mbrs) { this.mbrs=mbrs; @@ -52,7 +92,15 @@ public String toString() { StringBuilder sb=new StringBuilder(); sb.append(GossipRouter.type2String(type)).append( "(").append("group=").append(group).append(", addr=").append(addr); - sb.append(", mbrs=").append(mbrs); + if(logical_name != null) + sb.append(", logical_name=" + logical_name); + if(mbrs != null && !mbrs.isEmpty()) + sb.append(", mbrs=").append(mbrs); + if(physical_addrs != null && !physical_addrs.isEmpty()) + sb.append(", physical_addrs=").append(Util.printListWithDelimiter(physical_addrs, ", ")); + if(buffer != null) + sb.append(", buffer: " + length + " bytes"); + sb.append(")"); return sb.toString(); } @@ -61,14 +109,40 @@ out.writeByte(type); Util.writeString(group, out); Util.writeAddress(addr, out); + Util.writeString(logical_name, out); Util.writeAddresses(mbrs, out); + Util.writeAddresses(physical_addrs, out); + Util.writeByteBuffer(buffer, offset, length, out); } public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { type=in.readByte(); group=Util.readString(in); addr=Util.readAddress(in); + logical_name=Util.readString(in); mbrs=(List
        )Util.readAddresses(in, LinkedList.class); + physical_addrs=(Collection)Util.readAddresses(in, ArrayList.class); + buffer=Util.readByteBuffer(in); + if(buffer != null) { + offset=0; + length=buffer.length; + } + } + + + public int size() { + int retval=Global.BYTE_SIZE; // type + retval+=Global.BYTE_SIZE * 3; // presence byte for group and logical_name, buffer + if(group != null) + retval+=group.length() +2; + retval+=Util.size(addr); + if(logical_name != null) + retval+=logical_name.length() +2; + retval+=Util.size(mbrs); + retval+=Util.size(physical_addrs); + if(buffer != null) + retval+=Global.INT_SIZE + length; + return retval; } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/GossipRouter.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/GossipRouter.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/GossipRouter.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/GossipRouter.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,159 +1,117 @@ - package org.jgroups.stack; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.Address; +import org.jgroups.PhysicalAddress; +import org.jgroups.protocols.PingData; import org.jgroups.annotations.ManagedAttribute; import org.jgroups.annotations.ManagedOperation; import org.jgroups.annotations.Property; import org.jgroups.jmx.JmxConfigurator; -import org.jgroups.util.DefaultThreadFactory; -import org.jgroups.util.ShutdownRejectedExecutionHandler; -import org.jgroups.util.ThreadFactory; -import org.jgroups.util.ThreadManagerThreadPoolExecutor; -import org.jgroups.util.Util; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.util.*; +import org.jgroups.util.UUID; import javax.management.MBeanServer; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.io.EOFException; import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; +import java.net.SocketTimeoutException; import java.util.*; import java.util.Map.Entry; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.RejectedExecutionHandler; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; /** - * Router for TCP based group comunication (using layer TCP instead of UDP). - * Instead of the TCP layer sending packets point-to-point to each other - * member, it sends the packet to the router which - depending on the target - * address - multicasts or unicasts it to the group / or single member.

        - * This class is especially interesting for applets which cannot directly make - * connections (neither UDP nor TCP) to a host different from the one they were - * loaded from. Therefore, an applet would create a normal channel plus - * protocol stack, but the bottom layer would have to be the TCP layer which - * sends all packets point-to-point (over a TCP connection) to the router, - * which in turn forwards them to their end location(s) (also over TCP). A - * centralized router would therefore have to be running on the host the applet - * was loaded from.

        - * An alternative for running JGroups in an applet (IP multicast is not allows - * in applets as of 1.2), is to use point-to-point UDP communication via the - * gossip server. However, then the appplet has to be signed which involves - * additional administrative effort on the part of the user.

        + * Router for TCP based group comunication (using layer TCP instead of UDP). Instead of the TCP + * layer sending packets point-to-point to each other member, it sends the packet to the router + * which - depending on the target address - multicasts or unicasts it to the group / or single member. + *

        + * This class is especially interesting for applets which cannot directly make connections (neither + * UDP nor TCP) to a host different from the one they were loaded from. Therefore, an applet would + * create a normal channel plus protocol stack, but the bottom layer would have to be the TCP layer + * which sends all packets point-to-point (over a TCP connection) to the router, which in turn + * forwards them to their end location(s) (also over TCP). A centralized router would therefore have + * to be running on the host the applet was loaded from. + *

        + * An alternative for running JGroups in an applet (IP multicast is not allows in applets as of + * 1.2), is to use point-to-point UDP communication via the gossip server. However, then the appplet + * has to be signed which involves additional administrative effort on the part of the user. + *

        + * Note that a GossipRouter is also a good way of running JGroups in Amazon's EC2 environment which (as of summer 09) + * doesn't support IP multicasting. * @author Bela Ban + * @author Vladimir Blagojevic * @author Ovidiu Feodorov - * @version $Id: GossipRouter.java,v 1.40 2008/12/18 12:23:37 vlada Exp $ * @since 2.1.1 */ public class GossipRouter { - public static final byte CONNECT=1; // CONNECT(group, addr) --> local address + public static final byte CONNECT=1; // CONNECT(group, addr) --> local address public static final byte DISCONNECT=2; // DISCONNECT(group, addr) - public static final byte REGISTER=3; // REGISTER(group, addr) public static final byte GOSSIP_GET=4; // GET(group) --> List (members) - public static final byte ROUTER_GET=5; // GET(group) --> List (members) - public static final byte GET_RSP=6; // GET_RSP(List) - public static final byte UNREGISTER=7; // UNREGISTER(group, addr) - public static final byte DUMP=8; // DUMP - public static final byte SHUTDOWN=9; + public static final byte MESSAGE=10; + public static final byte SUSPECT=11; + public static final byte PING=12; + public static final byte CLOSE=13; + public static final byte CONNECT_OK=14; + public static final byte OP_FAIL=15; + public static final byte DISCONNECT_OK=16; + + public static final int PORT=12001; - public static final long EXPIRY_TIME=30000; - public static final long GOSSIP_REQUEST_TIMEOUT=1000; - public static final long ROUTING_CLIENT_REPLY_TIMEOUT=120000; @ManagedAttribute(description="server port on which the GossipRouter accepts client connections", writable=true) private int port; @ManagedAttribute(description="address to which the GossipRouter should bind", writable=true, name="bindAddress") private String bindAddressString; - - @ManagedAttribute(description="time (in msecs) until a cached 'gossip' member entry expires", writable=true) - private long expiryTime=30000; - - @ManagedAttribute(description="number of millisecs the main thread waits to receive a gossip request " + - "after connection was established; upon expiration, the router initiates " + - "the routing protocol on the connection. Don't set the interval too big, " + - "otherwise the router will appear slow in answering routing requests.", writable=true) - private long gossipRequestTimeout; - - @ManagedAttribute(description="time (in ms) main thread waits for a router client to send the routing " + - "request type and the group afiliation before it declares the request " + - "failed.", writable=true) - private long routingClientReplyTimeout; + + @ManagedAttribute(description="time (in msecs) until gossip entry expires", writable=true) + private long expiryTime=0; // Maintains associations between groups and their members - private final ConcurrentMap> routingTable=new ConcurrentHashMap>(); + private final ConcurrentMap> routingTable=new ConcurrentHashMap>(); + + /** + * Store physical address(es) associated with a logical address. Used mainly by TCPGOSSIP + */ + private final Map> address_mappings=new ConcurrentHashMap>(); private ServerSocket srvSock=null; private InetAddress bindAddress=null; - @Property(description="Time (in millis) for setting SO_LINGER on sockets returned from accept(). " + - "0 means do not set SO_LINGER") + @Property(description="Time (in ms) for setting SO_LINGER on sockets returned from accept(). 0 means do not set SO_LINGER") private long linger_timeout=2000L; - @Property(description="Time (in millis) for SO_TIMEOUT on sockets returned from accept(). " + - "0 means don't set SO_TIMEOUT") - private long sock_read_timeout=3000L; + @Property(description="Time (in ms) for SO_TIMEOUT on sockets returned from accept(). 0 means don't set SO_TIMEOUT") + private long sock_read_timeout=0L; @Property(description="The max queue size of backlogged connections") private int backlog=1000; - @ManagedAttribute(description="operational status", name="running") - private boolean up=true; + private final AtomicBoolean running = new AtomicBoolean(false); @ManagedAttribute(description="whether to discard message sent to self", writable=true) private boolean discard_loopbacks=false; - - @ManagedAttribute(description="Minimum thread pool size for incoming connections. Default is 2") - protected int thread_pool_min_threads=2; - @ManagedAttribute(description="Maximum thread pool size for incoming connections. Default is 10") - protected int thread_pool_max_threads=10; - - - @ManagedAttribute(description="Timeout in milliseconds to remove idle thread from regular pool. Default is 30000") - protected long thread_pool_keep_alive_time=30000; - - @ManagedAttribute(description="Switch for enabling thread pool for incoming connections. Default true") - protected boolean thread_pool_enabled=false; - - @ManagedAttribute(description="Use queue to enqueue incoming connections") - protected boolean thread_pool_queue_enabled=true; - - - @ManagedAttribute(description="Maximum queue size for incoming connections") - protected int thread_pool_queue_max_size=50; + protected List connectionTearListeners=new CopyOnWriteArrayList(); - @ManagedAttribute(description="Thread rejection policy. Possible values are Abort, Discard, DiscardOldest and Run Default is Run") - protected String thread_pool_rejection_policy="Run"; + protected ThreadFactory default_thread_factory=new DefaultThreadFactory(Util.getGlobalThreadGroup(), "gossip-handlers", true, true); - protected ExecutorService thread_pool; - - protected BlockingQueue thread_pool_queue=null; - - protected ThreadFactory default_thread_factory = new DefaultThreadFactory(Util.getGlobalThreadGroup(), "gossip-handlers", true, true); - - - // the cache sweeper protected Timer timer=null; protected final Log log=LogFactory.getLog(this.getClass()); private boolean jmx=false; - private boolean registered= false; - + private boolean registered=false; public GossipRouter() { this(PORT); @@ -164,31 +122,21 @@ } public GossipRouter(int port, String bindAddressString) { - this(port, bindAddressString, EXPIRY_TIME); + this(port,bindAddressString,false,0); } - public GossipRouter(int port, String bindAddressString, long expiryTime) { - this(port, bindAddressString, expiryTime, GOSSIP_REQUEST_TIMEOUT, ROUTING_CLIENT_REPLY_TIMEOUT); + public GossipRouter(int port, String bindAddressString, boolean jmx) { + this(port, bindAddressString,jmx,0); } - - public GossipRouter(int port, String bindAddressString, - long expiryTime, long gossipRequestTimeout, - long routingClientReplyTimeout) { - this.port=port; - this.bindAddressString=bindAddressString; - this.expiryTime=expiryTime; - this.gossipRequestTimeout=gossipRequestTimeout; - this.routingClientReplyTimeout=routingClientReplyTimeout; - } - - public GossipRouter(int port, String bindAddressString, - long expiryTime, long gossipRequestTimeout, - long routingClientReplyTimeout, boolean jmx) { - this(port, bindAddressString, expiryTime, gossipRequestTimeout, routingClientReplyTimeout); - this.jmx=jmx; + + public GossipRouter(int port, String bindAddressString, boolean jmx, long expiryTime) { + this.port = port; + this.bindAddressString = bindAddressString; + this.jmx = jmx; + this.expiryTime = expiryTime; + this.connectionTearListeners.add(new FailureDetectionListener()); } - public void setPort(int port) { this.port=port; } @@ -214,32 +162,34 @@ } public void setExpiryTime(long expiryTime) { - this.expiryTime=expiryTime; + this.expiryTime = expiryTime; } public long getExpiryTime() { return expiryTime; } + @Deprecated public void setGossipRequestTimeout(long gossipRequestTimeout) { - this.gossipRequestTimeout=gossipRequestTimeout; } - public long getGossipRequestTimeout() { - return gossipRequestTimeout; + @Deprecated + public static long getGossipRequestTimeout() { + return 0; } + @Deprecated public void setRoutingClientReplyTimeout(long routingClientReplyTimeout) { - this.routingClientReplyTimeout=routingClientReplyTimeout; } - public long getRoutingClientReplyTimeout() { - return routingClientReplyTimeout; + @Deprecated + public static long getRoutingClientReplyTimeout() { + return 0; } @ManagedAttribute(description="status") public boolean isStarted() { - return srvSock != null; + return isRunning(); } public boolean isDiscardLoopbacks() { @@ -265,170 +215,87 @@ public void setSocketReadTimeout(long sock_read_timeout) { this.sock_read_timeout=sock_read_timeout; } - + public ThreadFactory getDefaultThreadPoolThreadFactory() { return default_thread_factory; } - public void setDefaultThreadPoolThreadFactory(ThreadFactory factory) { - default_thread_factory=factory; - if(thread_pool instanceof ThreadPoolExecutor) - ((ThreadPoolExecutor)thread_pool).setThreadFactory(factory); - } - - public int getThreadPoolMinThreads() { - return thread_pool_min_threads; - } - - public void setThreadPoolMinThreads(int thread_pool_min_threads) { - this.thread_pool_min_threads = thread_pool_min_threads; - } - - public int getThreadPoolMaxThreads() { - return thread_pool_max_threads; - } - - public void setThreadPoolMaxThreads(int thread_pool_max_threads) { - this.thread_pool_max_threads = thread_pool_max_threads; - } - - public long getThreadPoolKeepAliveTime() { - return thread_pool_keep_alive_time; - } - - public void setThreadPoolKeepAliveTime(long thread_pool_keep_alive_time) { - this.thread_pool_keep_alive_time = thread_pool_keep_alive_time; - } - - public boolean isThreadPoolEnabled() { - return thread_pool_enabled; - } - - public void setThreadPoolEnabled(boolean thread_pool_enabled) { - this.thread_pool_enabled = thread_pool_enabled; - } - - public boolean isThreadPoolQueueEnabled() { - return thread_pool_queue_enabled; - } - - public void setThreadPoolQueueEnabled(boolean thread_pool_queue_enabled) { - this.thread_pool_queue_enabled = thread_pool_queue_enabled; - } - - public int getThreadPoolQueueMaxSize() { - return thread_pool_queue_max_size; - } - - public void setThreadPoolQueueMaxSize(int thread_pool_queue_max_size) { - this.thread_pool_queue_max_size = thread_pool_queue_max_size; - } - - public String getThreadPoolRejectionPolicy() { - return thread_pool_rejection_policy; - } - - public void setThreadPoolRejectionPolicy(String thread_pool_rejection_policy) { - this.thread_pool_rejection_policy = thread_pool_rejection_policy; - } - - public static String type2String(int type) { - switch(type) { + switch (type) { case CONNECT: return "CONNECT"; case DISCONNECT: return "DISCONNECT"; - case REGISTER: - return "REGISTER"; case GOSSIP_GET: return "GOSSIP_GET"; - case ROUTER_GET: - return "ROUTER_GET"; - case GET_RSP: - return "GET_RSP"; - case UNREGISTER: - return "UNREGISTER"; - case DUMP: - return "DUMP"; - case SHUTDOWN: - return "SHUTDOWN"; + case MESSAGE: + return "MESSAGE"; + case SUSPECT: + return "SUSPECT"; + case PING: + return "PING"; + case CLOSE: + return "CLOSE"; + case CONNECT_OK: + return "CONNECT_OK"; + case DISCONNECT_OK: + return "DISCONNECT_OK"; + case OP_FAIL: + return "OP_FAIL"; default: - return "unknown"; + return "unknown (" + type + ")"; } } - public void create() throws Exception { - } - /** - * Lifecycle operation. Called after create(). When this method is called, the managed attributes have already - * been set.
        Brings the Router into a fully functional state. + * Lifecycle operation. Called after create(). When this method is called, the managed attributes + * have already been set.
        + * Brings the Router into a fully functional state. */ - @ManagedOperation(description="Lifecycle operation. Called after create(). When this method is called, " + - "the managed attributes have already been set. Brings the Router into a fully functional state.") + @ManagedOperation(description="Lifecycle operation. Called after create(). When this method is called, " + + "the managed attributes have already been set. Brings the Router into a fully functional state.") public void start() throws Exception { - if(srvSock != null) { - throw new Exception("Router already started."); - } - - if(jmx && !registered) { - MBeanServer server=Util.getMBeanServer(); - JmxConfigurator.register(this, server, "jgroups:name=GossipRouter"); - registered = true; - } - - if(bindAddressString != null) { - bindAddress=InetAddress.getByName(bindAddressString); - srvSock=new ServerSocket(port, backlog, bindAddress); - } - else { - srvSock=new ServerSocket(port, backlog); - } - - up=true; - - if (thread_pool_enabled) { - if (thread_pool_queue_enabled) { - thread_pool_queue = new LinkedBlockingQueue( - thread_pool_queue_max_size); - } - else { - thread_pool_queue = new SynchronousQueue(); - } - thread_pool = createThreadPool(thread_pool_min_threads, - thread_pool_max_threads, thread_pool_keep_alive_time, - thread_pool_rejection_policy, thread_pool_queue, - default_thread_factory); - } - else { - thread_pool = Executors.newSingleThreadExecutor(default_thread_factory); - } - - Runtime.getRuntime().addShutdownHook(new Thread() { - public void run() { - cleanup(); - GossipRouter.this.stop(); - } - }); - - // start the main server thread - new Thread(new Runnable() { - public void run() { + if(running.compareAndSet(false, true)) { + if(jmx && !registered) { + MBeanServer server=Util.getMBeanServer(); + JmxConfigurator.register(this, server, "jgroups:name=GossipRouter"); + registered=true; + } + + if(bindAddressString != null) { + bindAddress=InetAddress.getByName(bindAddressString); + srvSock=new ServerSocket(port, backlog, bindAddress); + } + else { + srvSock=new ServerSocket(port, backlog); + } + + Runtime.getRuntime().addShutdownHook(new Thread() { + public void run() { + GossipRouter.this.stop(); + } + }); + + // start the main server thread + new Thread(new Runnable() { + public void run() { mainLoop(); - cleanup(); } - }, "GossipRouter").start(); - - // starts the cache sweeper as daemon thread, so we won't block on it - // upon termination - timer=new Timer(true); - timer.schedule(new TimerTask() { - public void run() { - sweep(); - } - }, expiryTime, expiryTime); + }, "GossipRouter").start(); + + long expiryTime = getExpiryTime(); + if (expiryTime > 0) { + timer = new Timer(true); + timer.schedule(new TimerTask() { + public void run() { + sweep(); + } + }, expiryTime, expiryTime); + } + } else { + throw new Exception("Router already started."); + } } /** @@ -436,23 +303,32 @@ */ @ManagedOperation(description="Always called before destroy(). Closes connections and frees resources") public void stop() { - up=false; - thread_pool.shutdownNow(); + clear(); + if(running.compareAndSet(true, false)){ + Util.close(srvSock); + if(log.isInfoEnabled()) + log.info("router stopped"); + } + } - timer.cancel(); - if(srvSock != null) { - shutdown(); - Util.close(srvSock); - // exiting the mainLoop will clean the tables - srvSock=null; + @ManagedOperation(description="Closes all connections and clears routing table (leave the server socket open)") + public void clear() { + if(running.get()) { + for(ConcurrentMap map: routingTable.values()) { + for(ConnectionHandler ce: map.values()) + ce.close(); + } + routingTable.clear(); } - if(log.isInfoEnabled()) log.info("router stopped"); } public void destroy() { } - - + + @ManagedAttribute(description="operational status", name="running") + public boolean isRunning() { + return running.get(); + } @ManagedOperation(description="dumps the contents of the routing table") public String dumpRoutingTable() { @@ -463,625 +339,569 @@ sb.append("empty ").append(label).append(" table"); } else { - for(Iterator i=routingTable.keySet().iterator(); i.hasNext();) { - String gname=i.next(); - sb.append("GROUP: '" + gname + "'\n"); - Map map=routingTable.get(gname); - if(map == null) { - sb.append("\tnull list of addresses\n"); - } - else if(map.isEmpty()) { - sb.append("\tempty list of addresses\n"); + boolean first=true; + for(Map.Entry> entry : routingTable.entrySet()) { + String gname=entry.getKey(); + if(!first) + sb.append("\n"); + else + first=false; + sb.append(gname + ": "); + Map map=entry.getValue(); + if(map == null || map.isEmpty()) { + sb.append("null"); } else { - for(Iterator j=map.values().iterator(); j.hasNext();) { - sb.append('\t').append(i.next()).append('\n'); - } + sb.append(Util.printListWithDelimiter(map.keySet(), ", ")); } } } return sb.toString(); } + @ManagedOperation(description="dumps the contents of the routing table") + public String dumpRoutingTableDetailed() { + String label="routing"; + StringBuilder sb=new StringBuilder(); - private void mainLoop() { - - if (bindAddress == null) { - bindAddress = srvSock.getInetAddress(); - } - System.out.println("GossipRouter started at " + new Date() - + "\nListening on port " + port + " bound on address " - + bindAddress + '\n'); - - while (up && srvSock != null) { - try { - final Socket sock = srvSock.accept(); - if (linger_timeout > 0) { - int linger = Math.min(1, (int) (linger_timeout / 1000)); - sock.setSoLinger(true, linger); - } - if (sock_read_timeout > 0) { - sock.setSoTimeout((int) sock_read_timeout); - } - - final DataInputStream input = new DataInputStream(sock.getInputStream()); - Runnable task = new Runnable(){ - - public void run() { - DataOutputStream output =null; - Address peer_addr = null, mbr, logical_addr; - String group; - GossipData req = new GossipData(); - try { - req.readFrom(input); - - switch (req.getType()) { - case GossipRouter.REGISTER: - mbr = req.getAddress(); - group = req.getGroup(); - if (log.isTraceEnabled()) - log.trace("REGISTER(" + group + ", " + mbr + ")"); - if (group == null || mbr == null) { - if (log.isErrorEnabled()) - log.error("group or member is null, cannot register member"); - } else - addGossipEntry(group, mbr, new AddressEntry(mbr)); - Util.close(input); - Util.close(sock); - break; - - case GossipRouter.UNREGISTER: - mbr = req.getAddress(); - group = req.getGroup(); - if (log.isTraceEnabled()) - log.trace("UNREGISTER(" + group + ", " + mbr + ")"); - if (group == null || mbr == null) { - if (log.isErrorEnabled()) - log.error("group or member is null, cannot unregister member"); - } else - removeGossipEntry(group, mbr); - Util.close(input); - Util.close(output); - Util.close(sock); - break; - - case GossipRouter.GOSSIP_GET: - group = req.getGroup(); - List

        mbrs = null; - Map map; - map = routingTable.get(group); - if (map != null) { - mbrs = new LinkedList
        (map.keySet()); - } - - if (log.isTraceEnabled()) - log.trace("GOSSIP_GET(" + group + ") --> " + mbrs); - output = new DataOutputStream(sock.getOutputStream()); - GossipData rsp = new GossipData(GossipRouter.GET_RSP,group, null, mbrs); - rsp.writeTo(output); - Util.close(input); - Util.close(output); - Util.close(sock); - break; - - case GossipRouter.ROUTER_GET: - group = req.getGroup(); - output = new DataOutputStream(sock.getOutputStream()); - - List
        ret = null; - map = routingTable.get(group); - if (map != null) { - ret = new LinkedList
        (map.keySet()); - } else - ret = new LinkedList
        (); - if (log.isTraceEnabled()) - log.trace("ROUTER_GET(" + group + ") --> " + ret); - rsp = new GossipData(GossipRouter.GET_RSP, group, null, - ret); - rsp.writeTo(output); - Util.close(input); - Util.close(output); - Util.close(sock); - break; - - case GossipRouter.DUMP: - output = new DataOutputStream(sock.getOutputStream()); - output.writeUTF(dumpRoutingTable()); - Util.close(input); - Util.close(output); - Util.close(sock); - break; - - case GossipRouter.CONNECT: - sock.setSoTimeout(0); // we have to disable this here - output = new DataOutputStream(sock.getOutputStream()); - peer_addr = new IpAddress(sock.getInetAddress(), sock.getPort()); - logical_addr = req.getAddress(); - String group_name = req.getGroup(); - - if (log.isTraceEnabled()) - log.trace("CONNECT(" + group_name + ", "+ logical_addr + ")"); - SocketThread st = new SocketThread(sock, input,group_name, logical_addr); - addEntry(group_name, logical_addr, new AddressEntry(logical_addr, peer_addr, sock, st, output)); - st.start(); - break; - - case GossipRouter.DISCONNECT: - Address addr = req.getAddress(); - group_name = req.getGroup(); - removeEntry(group_name, addr); - if (log.isTraceEnabled()) - log.trace("DISCONNECT(" + group_name + ", " + addr+ ")"); - Util.close(input); - Util.close(output); - Util.close(sock); - break; - - case GossipRouter.SHUTDOWN: - if (log.isInfoEnabled()) - log.info("router shutting down"); - Util.close(input); - Util.close(output); - Util.close(sock); - up = false; - break; - default: - if (log.isWarnEnabled()) - log.warn("received unkown gossip request (gossip="+ req + ')'); - break; - } - } catch (Exception e) { - if (up) - if (log.isErrorEnabled()) - log.error("failure handling a client request", e); - Util.close(input); - Util.close(output); - Util.close(sock); - } - }}; - if(!thread_pool.isShutdown()) - thread_pool.submit(task); - else - task.run(); - } catch (Exception exc) { - if (log.isErrorEnabled()) - log.error("failure receiving and setting up a client request", exc); - } - } - } - - protected ExecutorService createThreadPool(int min_threads, - int max_threads, long keep_alive_time, String rejection_policy, - BlockingQueue queue, final ThreadFactory factory) { - - ThreadPoolExecutor pool = new ThreadManagerThreadPoolExecutor( - min_threads, max_threads, keep_alive_time, - TimeUnit.MILLISECONDS, queue); - pool.setThreadFactory(factory); - - // default - RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy(); - if (rejection_policy != null) { - if (rejection_policy.equals("abort")) - handler = new ThreadPoolExecutor.AbortPolicy(); - else if (rejection_policy.equals("discard")) - handler = new ThreadPoolExecutor.DiscardPolicy(); - else if (rejection_policy.equals("discardoldest")) - handler = new ThreadPoolExecutor.DiscardOldestPolicy(); - } - pool.setRejectedExecutionHandler(new ShutdownRejectedExecutionHandler( - handler)); - - return pool; - } - - - - /** - * Cleans the routing tables while the Router is going down. - */ - private void cleanup() { - // shutdown the routing threads and cleanup the tables - for(Map map: routingTable.values()) { - if(map != null) { - for(AddressEntry entry: map.values()) { - entry.destroy(); + if(routingTable.isEmpty()) { + sb.append("empty ").append(label).append(" table"); + } + else { + boolean first=true; + for(Map.Entry> entry : routingTable.entrySet()) { + String gname=entry.getKey(); + if(!first) + sb.append("\n"); + else + first=false; + sb.append(gname + ":\n"); + Map map=entry.getValue(); + if(map == null || map.isEmpty()) { + sb.append("null"); + } + else { + for(Map.Entry en: map.entrySet()) { + sb.append(en.getKey() + ": "); + ConnectionHandler handler=en.getValue(); + sb.append("sock=" +handler.sock).append("\n"); + } } + sb.append("\n"); } } - routingTable.clear(); + return sb.toString(); } - /** - * Connects to the ServerSocket and sends the shutdown header. - */ - private void shutdown() { - Socket s=null; - DataOutputStream dos=null; - try { - s=new Socket(srvSock.getInetAddress(),srvSock.getLocalPort()); - dos=new DataOutputStream(s.getOutputStream()); - dos.writeInt(SHUTDOWN); - dos.writeUTF(""); - } - catch(Exception e) { - if(log.isErrorEnabled()) log.error("shutdown failed: " + e); - } - finally { - Util.close(s); - Util.close(dos); + @ManagedOperation(description="dumps the mappings between logical and physical addresses") + public String dumpAddresssMappings() { + StringBuilder sb=new StringBuilder(); + for(Map.Entry> entry: address_mappings.entrySet()) { + sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); } + return sb.toString(); } + private void mainLoop() { + if(bindAddress == null) + bindAddress=srvSock.getInetAddress(); + printStartupInfo(); - + while(isRunning()) { + Socket sock=null; + try { + sock=srvSock.accept(); + if(linger_timeout > 0) { + int linger=Math.max(1, (int)(linger_timeout / 1000)); + sock.setSoLinger(true, linger); + } + if(sock_read_timeout > 0) + sock.setSoTimeout((int)sock_read_timeout); + + if(log.isDebugEnabled()) + log.debug("Accepted connection, socket is " + sock); + + ConnectionHandler ch=new ConnectionHandler(sock); + getDefaultThreadPoolThreadFactory().newThread(ch).start(); + } + catch(IOException e) { + //only consider this exception if GR is not shutdown + if(isRunning()) { + log.error("failure handling connection from " + sock, e); + Util.close(sock); + } + } + } + } + /** * Removes expired gossip entries (entries older than EXPIRY_TIME msec). * @since 2.2.1 */ private void sweep() { - long diff, currentTime=System.currentTimeMillis(); - int num_entries_removed=0; - - for(Iterator>> it=routingTable.entrySet().iterator(); it.hasNext();) { - Entry > entry=it.next(); - Map map=entry.getValue(); - if(map == null || map.isEmpty()) { + long diff, currentTime = System.currentTimeMillis(); + List victims = new ArrayList(); + for (Iterator>> it = routingTable.entrySet().iterator(); it.hasNext();) { + Map map = it.next().getValue(); + if (map == null || map.isEmpty()) { it.remove(); continue; } - for(Iterator> it2=map.entrySet().iterator(); it2.hasNext();) { - Entryentry2=it2.next(); - AddressEntry ae=entry2.getValue(); - diff=currentTime - ae.timestamp; - if(diff > expiryTime) { - it2.remove(); - if(log.isTraceEnabled()) - log.trace("removed " + ae.logical_addr + " (" + diff + " msecs old)"); - num_entries_removed++; + for (Iterator> it2 = map.entrySet().iterator(); it2.hasNext();) { + ConnectionHandler ch = it2.next().getValue(); + diff = currentTime - ch.timestamp; + if (diff > expiryTime) { + victims.add(ch); } } } - if(num_entries_removed > 0) { - if(log.isTraceEnabled()) log.trace("done (removed " + num_entries_removed + " entries)"); - } + for (ConnectionHandler v : victims) { + v.close(); + } } - - - - - private void route(Address dest, String dest_group, byte[] msg, Address sender) { - //if(log.isTraceEnabled()) { - // int len=msg != null? msg.length : 0; - //log.trace("routing request from " + sender + " for " + dest_group + " to " + - // (dest == null? "ALL" : dest.toString()) + ", " + len + " bytes"); - //} - - if(dest == null) { // send to all members in group dest.getChannelName() - if(dest_group == null) { - if(log.isErrorEnabled()) log.error("both dest address and group are null"); + + private void route(Address dest, String group, byte[] msg) { + if(dest == null) { // send to all members in group + if(group == null) { + if(log.isErrorEnabled()) + log.error("group is null"); } else { - sendToAllMembersInGroup(dest_group, msg, sender); + sendToAllMembersInGroup(group, msg); } } - else { - // send to destination address - AddressEntry ae=findAddressEntry(dest_group, dest); - if(ae == null) { + else { // send unicast + + ConnectionHandler handler=findAddressEntry(group, dest); + if(handler == null) { if(log.isTraceEnabled()) log.trace("cannot find " + dest + " in the routing table, \nrouting table=\n" + dumpRoutingTable()); return; } - if(ae.output == null) { - if(log.isErrorEnabled()) log.error(dest + " is associated with a null output stream"); + if(handler.output == null) { + if(log.isErrorEnabled()) + log.error(dest + " is associated with a null output stream"); return; } try { - sendToMember(dest, ae.output, msg, sender); + sendToMember(dest, handler.output, msg); } catch(Exception e) { - if(log.isErrorEnabled()) log.error("failed sending message to " + dest + ": " + e.getMessage()); - removeEntry(dest_group, dest); // will close socket + if(log.isErrorEnabled()) + log.error("failed sending message to " + dest + ": " + e.getMessage()); + removeEntry(group, dest); // will close socket } } } - private void addEntry(String groupname, Address logical_addr, AddressEntry entry) { - addEntry(groupname, logical_addr, entry, false); - } - - - /** - * Adds a new member to the routing group. - */ - private void addEntry(String groupname, Address logical_addr, AddressEntry entry, boolean update_only) { - if(groupname == null || logical_addr == null) { - if(log.isErrorEnabled()) log.error("groupname or logical_addr was null, entry was not added"); - return; - } - - ConcurrentMap mbrs=routingTable.get(groupname); - if(mbrs == null) { - mbrs=new ConcurrentHashMap(); - mbrs.put(logical_addr, entry); - routingTable.putIfAbsent(groupname, mbrs); + private void removeEntry(String group, Address addr) { + // Remove from routing table + ConcurrentMap map; + if(group != null) { + map=routingTable.get(group); + if(map != null && map.remove(addr) != null) { + if(log.isTraceEnabled()) + log.trace("Removed " +addr + " from group " + group); + + if(map.isEmpty()) { + routingTable.remove(group); + if(log.isTraceEnabled()) + log.trace("Removed group " + group); + } + } } else { - AddressEntry tmp=mbrs.get(logical_addr); - if(tmp != null) { // already present - if(update_only) { - tmp.update(); - return; + for(Map.Entry> entry: routingTable.entrySet()) { + map=entry.getValue(); + if(map != null && map.remove(addr) != null && map.isEmpty()) { + routingTable.remove(entry.getKey()); + if(log.isTraceEnabled()) + log.trace("Removed " + entry.getKey() + " from group " + group); } - tmp.destroy(); } - mbrs.put(logical_addr, entry); } - } - - - private void removeEntry(String groupname, Address logical_addr) { - Mapval=routingTable.get(groupname); - if(val == null) - return; - synchronized(val) { - AddressEntry entry=val.get(logical_addr); - if(entry != null) { - entry.destroy(); - val.remove(logical_addr); - } - } + address_mappings.remove(addr); + UUID.remove(addr); } - /** * @return null if not found */ - private AddressEntry findAddressEntry(String group_name, Address logical_addr) { - if(group_name == null || logical_addr == null) + private ConnectionHandler findAddressEntry(String group, Address addr) { + if(group == null || addr == null) return null; - Map val=routingTable.get(group_name); - if(val == null) + ConcurrentMap map=routingTable.get(group); + if(map == null) return null; - return (AddressEntry)val.get(logical_addr); + return map.get(addr); } - - - /** - * Adds a new member to the group in the gossip table or renews the - * membership where is the case. - * @since 2.2.1 - */ - private void addGossipEntry(String groupname, Address logical_addr, AddressEntry e) { - addEntry(groupname, logical_addr, e, true); - } - - - private void removeGossipEntry(String groupname, Address mbr) { - removeEntry(groupname, mbr); - } - - - - private void sendToAllMembersInGroup(String groupname, byte[] msg, Address sender) { - Map val = routingTable.get(groupname); - if(val == null || val.isEmpty()) + private void sendToAllMembersInGroup(String group, byte[] msg) { + final ConcurrentMap map=routingTable.get(group); + if(map == null || map.isEmpty()) { + if(log.isWarnEnabled()) + log.warn("didn't find any members for group " + group); return; - - synchronized(val) { - for(Iterator> i=val.entrySet().iterator(); i.hasNext();) { - Entry tmp=i.next(); - AddressEntry entry=tmp.getValue(); - DataOutputStream dos=entry.output; + } + + synchronized(map) { + for(Map.Entry entry: map.entrySet()) { + ConnectionHandler handler=entry.getValue(); + DataOutputStream dos=handler.output; if(dos != null) { - // send only to 'connected' members try { - sendToMember(null, dos, msg, sender); + sendToMember(null, dos, msg); } catch(Exception e) { - if(log.isWarnEnabled()) log.warn("cannot send to " + entry.logical_addr + ": " + e.getMessage()); - entry.destroy(); // this closes the socket - i.remove(); + if(log.isWarnEnabled()) + log.warn("cannot send to " + entry.getKey() + ": " + e.getMessage()); } } } } } - - /** - * @throws IOException - */ - private void sendToMember(Address dest, DataOutputStream out, byte[] msg, Address sender) throws IOException { + private static void sendToMember(Address dest, final DataOutputStream out, byte[] msg) throws IOException { if(out == null) return; - - if(discard_loopbacks && dest != null && dest.equals(sender)) { - return; - } - synchronized(out) { - Util.writeAddress(dest, out); - out.writeInt(msg.length); - out.write(msg, 0, msg.length); + GossipData request=new GossipData(GossipRouter.MESSAGE, null, dest, msg); + request.writeTo(out); + out.flush(); } } - - /** - * Class used to store Addresses in both routing and gossip tables. - * If it is used for routing, sock and output have valid values, otherwise - * they're null and only the timestamp counts. - */ - class AddressEntry { - Address logical_addr=null, physical_addr=null; - Socket sock=null; - DataOutputStream output=null; - long timestamp=0; - final SocketThread thread; - - /** - * AddressEntry for a 'gossip' membership. - */ - public AddressEntry(Address addr) { - this(addr, null, null, null, null); - } - - public AddressEntry(Address logical_addr, Address physical_addr, Socket sock, SocketThread thread, DataOutputStream output) { - this.logical_addr=logical_addr; - this.physical_addr=physical_addr; - this.sock=sock; - this.thread=thread; - this.output=output; - this.timestamp=System.currentTimeMillis(); + private void notifyAbnormalConnectionTear(final ConnectionHandler ch, final Exception e) { + for (ConnectionTearListener l : connectionTearListeners) { + l.connectionTorn(ch, e); } + } - void destroy() { - if(thread != null) { - thread.finish(); + public interface ConnectionTearListener { + public void connectionTorn(ConnectionHandler ch, Exception e); + } + + /* + * https://jira.jboss.org/jira/browse/JGRP-902 + */ + class FailureDetectionListener implements ConnectionTearListener { + + public void connectionTorn(ConnectionHandler ch, Exception e) { + Set groups = ch.known_groups; + for (String group : groups) { + if(group == null) + continue; + Map map = routingTable.get(group); + if (map != null && !map.isEmpty()) { + for (Iterator> i = map.entrySet().iterator(); i.hasNext();) { + ConnectionHandler entry = i.next().getValue(); + DataOutputStream stream = entry.output; + try { + for (Address a : ch.logical_addrs) { + GossipData suspect = new GossipData(GossipRouter.SUSPECT); + suspect.writeTo(stream); + Util.writeAddress(a, stream); + stream.flush(); + } + } catch (Exception ioe) { + // intentionally ignored + } + } + } } - Util.close(output); - output=null; - Util.close(sock); - sock=null; - timestamp=0; } + } - public void update() { - timestamp=System.currentTimeMillis(); - } + /** + * Prints startup information. + */ + private void printStartupInfo() { + System.out.println("GossipRouter started at " + new Date()); - public boolean equals(Object other) { - return other instanceof AddressEntry && logical_addr.equals(((AddressEntry)other).logical_addr); - } + System.out.print("Listening on port " + port); + System.out.println(" bound on address " + bindAddress); - public String toString() { - StringBuilder sb=new StringBuilder("logical addr="); - sb.append(logical_addr).append(" (").append(physical_addr).append(")"); - //if(sock != null) { - // sb.append(", sock="); - //sb.append(sock); - //} - if(timestamp > 0) { - long diff=System.currentTimeMillis() - timestamp; - sb.append(", ").append(diff).append(" ms old"); - } - return sb.toString(); - } + System.out.print("Backlog is " + backlog); + System.out.print(", linger timeout is " + linger_timeout); + System.out.println(", and read timeout is " + sock_read_timeout); } - private static int threadCounter=0; - - /** - * A SocketThread manages one connection to a client. Its main task is message routing. + * Handles the requests from a client (RouterStub) */ - class SocketThread extends Thread { - private volatile boolean active=true; - Socket sock=null; - DataInputStream input=null; - Address logical_addr=null; - String group_name=null; + class ConnectionHandler implements Runnable { + private final AtomicBoolean active = new AtomicBoolean(false); + private final Socket sock; + private final DataOutputStream output; + private final DataInputStream input; + private final List
        logical_addrs=new ArrayList
        (); + Set known_groups = new HashSet(); + private long timestamp; - - public SocketThread(Socket sock, DataInputStream ois, String group_name, Address logical_addr) { - super(Util.getGlobalThreadGroup(), "SocketThread " + (threadCounter++)); + public ConnectionHandler(Socket sock) throws IOException { this.sock=sock; - input=ois; - this.group_name=group_name; - this.logical_addr=logical_addr; - } - - void closeSocket() { - Util.close(input); - Util.close(sock); + this.input=new DataInputStream(sock.getInputStream()); + this.output=new DataOutputStream(sock.getOutputStream()); } - void finish() { - active=false; + void close() { + if(active.compareAndSet(true, false)) { + if(log.isDebugEnabled()) + log.debug(this + " is being closed"); + + Util.close(input); + Util.close(output); + Util.close(sock); + for(Address addr: logical_addrs) { + removeEntry(null, addr); + } + } } - public void run() { - byte[] buf; - int len; - Address dst_addr=null; - String gname; - - DataOutputStream output = null; - try { - output=new DataOutputStream(sock.getOutputStream()); - output.writeBoolean(true); - } - catch(IOException e1) { + if(active.compareAndSet(false, true)) { try { - output.writeBoolean(false); + if(log.isDebugEnabled()) + log.debug(this + " entering receive loop"); + readLoop(); + } + finally { + close(); } - catch(IOException e) {} } - - while(active) { - try { - // 1. Group name is first - gname=input.readUTF(); + } + + public boolean isRunning() { + return active.get(); + } - // 2. Second is the destination address - dst_addr=Util.readAddress(input); + private void readLoop() { + while(isRunning()) { + GossipData request; + Address addr; + String group; + try { + request=new GossipData(); + request.readFrom(input); + byte command=request.getType(); + addr=request.getAddress(); + group=request.getGroup(); + known_groups.add(group); - // 3. Then the length of the byte buffer representing the message - len=input.readInt(); - if(len == 0) { - if(log.isWarnEnabled()) log.warn("received null message"); - continue; + timestamp = System.currentTimeMillis(); + if(log.isTraceEnabled()) + log.trace(this + " received " + request); + + switch(command) { + + case GossipRouter.CONNECT: + handleConnect(request, addr, group); + break; + + case GossipRouter.PING: + // do nothing here - client doesn't expect response data + break; + + case GossipRouter.MESSAGE: + if(request.buffer == null || request.buffer.length == 0) { + if(log.isWarnEnabled()) + log.warn(this +" received null message"); + break; + } + + try { + route(addr, request.getGroup(), request.getBuffer()); + } + catch(Exception e) { + if(log.isErrorEnabled()) + log.error(this +" failed in routing request to " + addr, e); + } + break; + + case GossipRouter.GOSSIP_GET: + Set physical_addrs; + List mbrs=new ArrayList(); + ConcurrentMap map=routingTable.get(group); + if(map != null) { + for(Address logical_addr: map.keySet()) { + physical_addrs=address_mappings.get(logical_addr); + PingData rsp=new PingData(logical_addr, null, true, UUID.get(logical_addr), + physical_addrs != null? new ArrayList(physical_addrs) : null); + mbrs.add(rsp); + } + } + output.writeShort(mbrs.size()); + for(PingData data: mbrs) + data.writeTo(output); + output.flush(); + if(log.isDebugEnabled()) + log.debug(this + " responded to GOSSIP_GET with " + mbrs); + break; + + case GossipRouter.DISCONNECT: + try { + removeEntry(group, addr); + sendData(new GossipData(DISCONNECT_OK)); + if(log.isDebugEnabled()) + log.debug(this + " disconnect completed"); + } + catch(Exception e) { + sendData(new GossipData(OP_FAIL)); + } + break; + + case GossipRouter.CLOSE: + close(); + break; + + case -1: // EOF + notifyAbnormalConnectionTear(this, new EOFException("Connection broken")); + break; } - - // 4. Finally the message itself, as a byte buffer - buf=new byte[len]; - input.readFully(buf, 0, buf.length); // message - } - catch(Exception io_ex) { if(log.isTraceEnabled()) - log.trace(sock.getInetAddress().getHostName() + ':' + sock.getPort() + - " closed connection; removing it from routing table"); - removeEntry(group_name, logical_addr); // will close socket - return; + log.trace(this + " processed " + request); } + catch(SocketTimeoutException ste) { + } + catch(IOException ioex) { + notifyAbnormalConnectionTear(this, ioex); + break; + } + catch(Exception ex) { + if (active.get()) { + if (log.isWarnEnabled()) + log.warn("Exception in ConnectionHandler thread", ex); + } + break; + } + } + } - try { - route(dst_addr, gname, buf, logical_addr); + private void handleConnect(GossipData request, Address addr, String group) throws Exception { + ConcurrentMap map = null; + try { + + checkExistingConnection(addr,group); + + String logical_name = request.getLogicalName(); + if (logical_name != null && addr instanceof org.jgroups.util.UUID) + org.jgroups.util.UUID.add(addr, logical_name); + + // group name, logical address, logical name, physical addresses (could be null) + logical_addrs.add(addr); // allows us to remove the entries for this connection on + // socket close + + map = routingTable.get(group); + if (map == null) { + map = new ConcurrentHashMap(); + routingTable.put(group, map); // no concurrent requests on the same connection + } + map.put(addr, this); + + Set physical_addrs; + if (request.getPhysicalAddresses() != null) { + physical_addrs = address_mappings.get(addr); + if (physical_addrs == null) { + physical_addrs = new HashSet(); + address_mappings.put(addr, physical_addrs); + } + physical_addrs.addAll(request.getPhysicalAddresses()); } - catch(Exception e) { - if(log.isErrorEnabled()) log.error("failed routing request to " + dst_addr, e); - break; + sendStatus(CONNECT_OK); + + if(log.isDebugEnabled()) + log.debug(this + " connection handshake completed, added " +addr + " to group "+ group); + + } catch (Exception e) { + removeEntry(group, addr); + sendStatus(OP_FAIL); + throw new Exception("Unsuccessful connection setup handshake for " + this); + } + } + + private boolean checkExistingConnection(Address addr, String group) throws Exception { + boolean isOldExists = false; + if (address_mappings.containsKey(addr)) { + ConcurrentMap map = null; + ConnectionHandler oldConnectionH = null; + if (group != null) { + map = routingTable.get(group); + if (map != null) { + oldConnectionH = map.get(addr); + } + } else { + for (Map.Entry> entry : routingTable + .entrySet()) { + map = entry.getValue(); + if (map != null) { + oldConnectionH = map.get(addr); + } + } } + if (oldConnectionH != null) { + isOldExists = true; + if (log.isDebugEnabled()) { + log.debug("Found old connection[" + oldConnectionH + "] for addr[" + addr + + "]. Closing old connection ..."); + } + oldConnectionH.close(); + } else { + if (log.isDebugEnabled()) { + log.debug("No old connection for addr[" + addr + "] exists"); + } + } + } + return isOldExists; + } + + private void sendStatus(byte status) { + try { + output.writeByte(status); + output.flush(); + } catch (IOException e1) { + //ignored + } + } + + private void sendData(GossipData data) { + try { + data.writeTo(output); + output.flush(); + } catch (IOException e1) { + //ignored } - closeSocket(); } + public String toString() { + StringBuilder sb=new StringBuilder(); + sb.append("ConnectionHandler[peer: " + sock.getInetAddress()); + if(!logical_addrs.isEmpty()) + sb.append(", logical_addrs: " + Util.printListWithDelimiter(logical_addrs, ", ")); + sb.append("]"); + return sb.toString(); + } } - public static void main(String[] args) throws Exception { - String arg; int port=12001; - long expiry=GossipRouter.EXPIRY_TIME; - long timeout=GossipRouter.GOSSIP_REQUEST_TIMEOUT; - long routingTimeout=GossipRouter.ROUTING_CLIENT_REPLY_TIMEOUT; + int backlog=0; + long soLinger=-1; + long soTimeout=-1; + long expiry_time=0; + GossipRouter router=null; String bind_addr=null; boolean jmx=false; for(int i=0; i < args.length; i++) { - arg=args[i]; + String arg=args[i]; if("-port".equals(arg)) { port=Integer.parseInt(args[++i]); continue; @@ -1090,59 +910,90 @@ bind_addr=args[++i]; continue; } + if("-backlog".equals(arg)) { + backlog=Integer.parseInt(args[++i]); + continue; + } if("-expiry".equals(arg)) { - expiry=Long.parseLong(args[++i]); + expiry_time=Long.parseLong(args[++i]); continue; } + if("-jmx".equals(arg)) { + jmx=true; + continue; + } + // this option is not used and should be deprecated/removed in a future release if("-timeout".equals(arg)) { - timeout=Long.parseLong(args[++i]); + System.out.println(" -timeout is deprecated and will be ignored"); + ++i; continue; } + // this option is not used and should be deprecated/removed in a future release if("-rtimeout".equals(arg)) { - routingTimeout=Long.parseLong(args[++i]); + System.out.println(" -rtimeout is deprecated and will be ignored"); + ++i; continue; } - if("-jmx".equals(arg)) { - jmx=true; + if("-solinger".equals(arg)) { + soLinger=Long.parseLong(args[++i]); + continue; + } + if("-sotimeout".equals(arg)) { + soTimeout=Long.parseLong(args[++i]); continue; } help(); return; } - System.out.println("GossipRouter is starting. Enter quit to exit JVM"); + System.out.println("GossipRouter is starting. CTRL-C to exit JVM"); try { - router=new GossipRouter(port, bind_addr, expiry, timeout, routingTimeout, jmx); + router=new GossipRouter(port, bind_addr, jmx); + + if(backlog > 0) + router.setBacklog(backlog); + + if(soTimeout >= 0) + router.setSocketReadTimeout(soTimeout); + + if(soLinger >= 0) + router.setLingerTimeout(soLinger); + + if(expiry_time > 0) + router.setExpiryTime(expiry_time); + router.start(); } catch(Exception e) { System.err.println(e); } - String quit = ""; - while (!quit.startsWith("quit")) { - Scanner in = new Scanner(System.in); - try{ - quit = in.nextLine(); - } - catch(Exception e){} - } - - router.stop(); - router.cleanup(); } static void help() { System.out.println(); System.out.println("GossipRouter [-port ] [-bind_addr
        ] [options]"); - System.out.println("Options: "); - System.out.println(" -expiry - Time until a gossip cache entry expires."); - System.out.println(" -timeout - Number of millisecs the router waits to receive"); - System.out.println(" a gossip request after connection was established;"); - System.out.println(" upon expiration, the router initiates the routing"); - System.out.println(" protocol on the connection."); - System.out.println(" -rtimeout - Routing timeout"); - System.out.println(" -jmx - Expose attrs and operations via JMX"); - } - + System.out.println(); + System.out.println("Options:"); + System.out.println(); -} + System.out.println(" -backlog - Max queue size of backlogged connections. Must be"); + System.out.println(" greater than zero or the default of 1000 will be"); + System.out.println(" used."); + System.out.println(); + System.out.println(" -jmx - Expose attributes and operations via JMX."); + System.out.println(); + System.out.println(" -solinger - Time for setting SO_LINGER on connections. 0"); + System.out.println(" means do not set SO_LINGER. Must be greater than"); + System.out.println(" or equal to zero or the default of 2000 will be"); + System.out.println(" used."); + System.out.println(); + System.out.println(" -sotimeout - Time for setting SO_TIMEOUT on connections. 0"); + System.out.println(" means don't set SO_TIMEOUT. Must be greater than"); + System.out.println(" or equal to zero or the default of 3000 will be"); + System.out.println(" used."); + System.out.println(); + System.out.println(" -expiry - Time for closing idle connections. 0"); + System.out.println(" means don't expire."); + System.out.println(); + } +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/Interval.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/Interval.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/Interval.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/Interval.java 2011-10-18 11:22:35.000000000 +0000 @@ -8,7 +8,6 @@ /** * Interface which returns a time series, one value at a time calling next() * @author Bela Ban - * @version $Id: Interval.java,v 1.3 2007/08/10 12:32:17 belaban Exp $ */ public interface Interval { /** @return the next interval */ diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/IpAddress.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/IpAddress.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/IpAddress.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/IpAddress.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,17 +1,18 @@ -// $Id: IpAddress.java,v 1.49 2008/08/20 12:13:27 belaban Exp $ package org.jgroups.stack; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.Address; import org.jgroups.Global; +import org.jgroups.PhysicalAddress; import org.jgroups.util.Util; import java.io.*; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; +import java.net.Inet6Address; /** @@ -19,7 +20,7 @@ * stack (UDP). Contains an InetAddress and port. * @author Bela Ban */ -public class IpAddress implements Address { +public class IpAddress implements PhysicalAddress { private static final long serialVersionUID = 2592301708270771474L; @@ -200,7 +201,7 @@ if(!(obj instanceof IpAddress)) return false; IpAddress other=(IpAddress)obj; - boolean sameIP=false; + boolean sameIP; if(this.ip_addr != null) sameIP=this.ip_addr.equals(other.ip_addr); else @@ -227,10 +228,9 @@ if(ip_addr.isMulticastAddress()) sb.append(ip_addr.getHostAddress()); else { - String host_name=null; + String host_name; if(resolve_dns) { host_name=ip_addr.getHostName(); - // appendShortName(host_name, sb); } else { host_name=ip_addr.getHostAddress(); @@ -295,6 +295,8 @@ byte[] address=ip_addr.getAddress(); // 4 bytes (IPv4) or 16 bytes (IPv6) out.writeByte(address.length); // 1 byte out.write(address, 0, address.length); + if(ip_addr instanceof Inet6Address) + out.writeInt(((Inet6Address)ip_addr).getScopeId()); } else { out.writeByte(0); @@ -316,8 +318,14 @@ throw new IOException("length has to be " + Global.IPV4_SIZE + " or " + Global.IPV6_SIZE + " bytes (was " + len + " bytes)"); byte[] a = new byte[len]; // 4 bytes (IPv4) or 16 bytes (IPv6) - in.readFully(a); + in.readFully(a); + if(len == Global.IPV6_SIZE) { + int scope_id=in.readInt(); + this.ip_addr=Inet6Address.getByAddress(null, a, scope_id); + } + else { this.ip_addr=InetAddress.getByAddress(a); + } // changed from readShort(): we need the full 65535, with a short we'd only get up to 32K ! port=in.readUnsignedShort(); @@ -336,8 +344,11 @@ return size; // length (1 bytes) + 4 bytes for port + 1 for additional_data available int tmp_size=Global.BYTE_SIZE+ Global.SHORT_SIZE + Global.BYTE_SIZE; - if(ip_addr != null) + if(ip_addr != null) { tmp_size+=ip_addr.getAddress().length; // 4 bytes for IPv4 + if(ip_addr instanceof Inet6Address) + tmp_size+=Global.INT_SIZE; + } if(additional_data != null) tmp_size+=additional_data.length+Global.SHORT_SIZE; size=tmp_size; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/NakReceiverWindow.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/NakReceiverWindow.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/NakReceiverWindow.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/NakReceiverWindow.java 2011-10-18 11:22:35.000000000 +0000 @@ -3,21 +3,18 @@ package org.jgroups.stack; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.Address; import org.jgroups.Message; import org.jgroups.annotations.GuardedBy; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.util.RetransmitTable; import org.jgroups.util.TimeScheduler; -import java.util.List; -import java.util.Map; import java.util.LinkedList; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -47,24 +44,18 @@ * Example: * 1,2,3,5,6,8: low=1, highest_delivered=2 (or 3, depending on whether remove() was called !), highest_received=8 * - * @author Bela Ban May 27 1999, May 2004, Jan 2007 - * @author John Georgiadis May 8 2001 - * @version $Id: NakReceiverWindow.java,v 1.61 2008/10/08 12:41:32 belaban Exp $ + * @author Bela Ban */ public class NakReceiverWindow { public interface Listener { void missingMessageReceived(long seqno, Address original_sender); - void messageGapDetected(long from, long to, Address src); } private final ReadWriteLock lock=new ReentrantReadWriteLock(); - private final ReentrantLock up_lock=new ReentrantLock(); - - Address local_addr=null; - + private volatile boolean running=true; /** Lowest seqno, modified on stable(). On stable(), we purge msgs [low digest.highest_delivered] */ @GuardedBy("lock") @@ -80,24 +71,13 @@ private long highest_received=0; - /** ConcurrentMap. Maintains messages keyed by (sorted) sequence numbers */ - private final ConcurrentMap xmit_table=new ConcurrentHashMap(); - /** - * Messages that have been received in order are sent up the stack (= delivered to the application). Delivered - * messages are removed from NakReceiverWindow.xmit_table and moved to NakReceiverWindow.delivered_msgs, where - * they are later garbage collected (by STABLE). Since we do retransmits only from sent messages, never - * received or delivered messages, we can turn the moving to delivered_msgs off, so we don't keep the message - * around, and don't need to wait for garbage collection to remove them. + * ConcurrentMap. Maintains messages keyed by (sorted) sequence numbers */ - private boolean discard_delivered_msgs=false; + private final RetransmitTable xmit_table; - private final AtomicBoolean processing=new AtomicBoolean(false); - /** If value is > 0, the retransmit buffer is bounded: only the max_xmit_buf_size latest messages are kept, - * older ones are discarded when the buffer size is exceeded. A value <= 0 means unbounded buffers - */ - private int max_xmit_buf_size=0; + private final AtomicBoolean processing=new AtomicBoolean(false); /** if not set, no retransmitter thread will be started. Useful if * protocols do their own retransmission (e.g PBCAST) */ @@ -125,20 +105,37 @@ * @param sched the external scheduler to use for retransmission * requests of missing msgs. If it's not provided or is null, an internal */ - public NakReceiverWindow(Address sender, Retransmitter.RetransmitCommand cmd, long highest_delivered_seqno, long lowest_seqno, TimeScheduler sched) { - this(null, sender, cmd, highest_delivered_seqno, lowest_seqno, sched); + public NakReceiverWindow(Address sender, Retransmitter.RetransmitCommand cmd, long highest_delivered_seqno, + long lowest_seqno, TimeScheduler sched) { + this(sender, cmd, highest_delivered_seqno, lowest_seqno, sched, true); } - public NakReceiverWindow(Address local_addr, Address sender, Retransmitter.RetransmitCommand cmd, long highest_delivered_seqno, long lowest_seqno, TimeScheduler sched) { - this.local_addr=local_addr; + + public NakReceiverWindow(Address sender, Retransmitter.RetransmitCommand cmd, + long highest_delivered_seqno, long lowest_seqno, TimeScheduler sched, + boolean use_range_based_retransmitter) { + this(sender, cmd, highest_delivered_seqno, lowest_seqno, sched, use_range_based_retransmitter, + 5, 10000, 1.2, 5 * 60 * 1000, false); + } + + + public NakReceiverWindow(Address sender, Retransmitter.RetransmitCommand cmd, + long highest_delivered_seqno, long lowest_seqno, TimeScheduler sched, + boolean use_range_based_retransmitter, + int num_rows, int msgs_per_row, double resize_factor, long max_compaction_time, + boolean automatic_purging) { highest_delivered=highest_delivered_seqno; highest_received=highest_delivered; low=Math.min(lowest_seqno, highest_delivered); if(sched == null) throw new IllegalStateException("timer has to be provided and cannot be null"); if(cmd != null) - retransmitter=new Retransmitter(sender, cmd, sched); + retransmitter=use_range_based_retransmitter? + new RangeBasedRetransmitter(sender, cmd, sched) : + new DefaultRetransmitter(sender, cmd, sched); + + xmit_table=new RetransmitTable(num_rows, msgs_per_row, low, resize_factor, max_compaction_time, automatic_purging); } @@ -156,21 +153,7 @@ this(sender, cmd, highest_delivered_seqno, 0, sched); } - /** - * Creates a new instance with the given retransmit command - * - * @param sender The sender associated with this instance - * @param cmd The command used to retransmit a missing message, will - * be invoked by the table. If null, the retransmit thread will not be started - * @param highest_delivered_seqno The next seqno to remove is highest_delivered_seqno +1 - */ - public NakReceiverWindow(Address sender, Retransmitter.RetransmitCommand cmd, long highest_delivered_seqno) { - this(sender, cmd, highest_delivered_seqno, null); - } - - public ReentrantLock getLock() { - return up_lock; - } + public AtomicBoolean getProcessing() { return processing; @@ -180,17 +163,17 @@ retransmitter.setRetransmitTimeouts(timeouts); } - + @Deprecated public void setDiscardDeliveredMessages(boolean flag) { - this.discard_delivered_msgs=flag; } + @Deprecated public int getMaxXmitBufSize() { - return max_xmit_buf_size; + return 0; } + @Deprecated public void setMaxXmitBufSize(int max_xmit_buf_size) { - this.max_xmit_buf_size=max_xmit_buf_size; } public void setListener(Listener l) { @@ -231,6 +214,17 @@ } + public int getRetransmiTableSize() {return xmit_table.size();} + + public int getRetransmitTableCapacity() {return xmit_table.capacity();} + + public double getRetransmitTableFillFactor() {return xmit_table.getFillFactor();} + + public void compact() { + xmit_table.compact(); + } + + /** * Adds a message according to its seqno (sequence number). *

        @@ -244,49 +238,44 @@ * * @return True if the message was added successfully, false otherwise (e.g. duplicate message) */ - public boolean add(long seqno, Message msg) { + public boolean add(final long seqno, final Message msg) { long old_next, next_to_add; + int num_xmits=0; lock.writeLock().lock(); try { + if(!running) + return false; + next_to_add=highest_received +1; old_next=next_to_add; // Case #1: we received the expected seqno: most common path if(seqno == next_to_add) { - xmit_table.put(new Long(seqno), msg); + xmit_table.put(seqno, msg); return true; } // Case #2: we received a message that has already been delivered: discard it if(seqno <= highest_delivered) { - if(log.isTraceEnabled()) { - StringBuilder sb=new StringBuilder("seqno "); - sb.append(seqno).append(" is smaller than ").append(next_to_add).append("); discarding message"); - log.trace(sb); - } + if(log.isTraceEnabled()) + log.trace("seqno " + seqno + " is smaller than " + next_to_add + "); discarding message"); return false; } - // Case #3: we finally received a missing message. // Case #2 handled seqno <= highest_delivered, so this + // Case #3: we finally received a missing message. Case #2 handled seqno <= highest_delivered, so this // seqno *must* be between highest_delivered and next_to_add if(seqno < next_to_add) { - Message tmp=xmit_table.putIfAbsent(seqno, msg); // only set message if not yet received (bela July 23 2003) - if(tmp == null) { // key/value was not present - int num_xmits=retransmitter.remove(seqno); - if(log.isTraceEnabled()) - log.trace(new StringBuilder("added missing msg ").append(msg.getSrc()).append('#').append(seqno)); - if(listener != null && num_xmits > 0) { - try {listener.missingMessageReceived(seqno, msg.getSrc());} catch(Throwable t) {} - } - return true; - } - else { // key/value was present - return false; - } + Message existing=xmit_table.putIfAbsent(seqno, msg); + if(existing != null) + return false; // key/value was present + num_xmits=retransmitter.remove(seqno); + if(log.isTraceEnabled()) + log.trace(new StringBuilder("added missing msg ").append(msg.getSrc()).append('#').append(seqno)); + return true; } - // Case #4: we received a seqno higher than expected: add NULL_MSG values for missing messages, add to Retransmitter + // Case #4: we received a seqno higher than expected: add to Retransmitter if(seqno > next_to_add) { xmit_table.put(seqno, msg); retransmitter.add(old_next, seqno -1); // BUT: add only null messages to xmitter @@ -298,129 +287,80 @@ } finally { highest_received=Math.max(highest_received, seqno); - // setSmoothedLossRate(); lock.writeLock().unlock(); } + + if(listener != null && num_xmits > 0) { + try {listener.missingMessageReceived(seqno, msg.getSrc());} catch(Throwable t) {} + } + return true; } public Message remove() { - Message retval=null; + return remove(true, false); + } - lock.writeLock().lock(); + + public Message remove(boolean acquire_lock, boolean remove_msg) { + Message retval; + + if(acquire_lock) + lock.writeLock().lock(); try { - long next_to_remove=highest_delivered +1; - retval=xmit_table.get(next_to_remove); + long next=highest_delivered +1; + retval=remove_msg? xmit_table.remove(next) : xmit_table.get(next); if(retval != null) { // message exists and is ready for delivery - if(discard_delivered_msgs) { - Address sender=retval.getSrc(); - if(!local_addr.equals(sender)) { // don't remove if we sent the message ! - xmit_table.remove(next_to_remove); - } - } - highest_delivered=next_to_remove; + highest_delivered=next; return retval; } - - // message has not yet been received (gap in the message sequence stream) - // drop all messages that have not been received - if(max_xmit_buf_size > 0 && xmit_table.size() > max_xmit_buf_size) { - highest_delivered=next_to_remove; - retransmitter.remove(next_to_remove); - } return null; } finally { - // setSmoothedLossRate(); - lock.writeLock().unlock(); + if(acquire_lock) + lock.writeLock().unlock(); } } /** - * Removes a message from the NakReceiverWindow. The message is in order. If the next message cannot be removed - * (e.g. because there is no message available, or because the next message is not in order), null will - * be returned. When null is returned, processing is set to false. This is needed to atomically remove a message - * and set the atomic boolean variable to false. - * @param processing - * @return - */ - public Message remove(final AtomicBoolean processing) { - Message retval=null; - boolean found=false; - - lock.writeLock().lock(); - try { - long next_to_remove=highest_delivered +1; - retval=xmit_table.get(next_to_remove); - found=retval != null; - - if(retval != null) { // message exists and is ready for delivery - if(discard_delivered_msgs) { - Address sender=retval.getSrc(); - if(!local_addr.equals(sender)) { // don't remove if we sent the message ! - xmit_table.remove(next_to_remove); - } - } - highest_delivered=next_to_remove; - return retval; - } - - // message has not yet been received (gap in the message sequence stream) - // drop all messages that have not been received - if(max_xmit_buf_size > 0 && xmit_table.size() > max_xmit_buf_size) { - highest_delivered=next_to_remove; - retransmitter.remove(next_to_remove); - } - return null; - } - finally { - if(!found) - processing.set(false); - // setSmoothedLossRate(); - lock.writeLock().unlock(); - } + * Removes as many messages as possible + * @return List A list of messages, or null if no available messages were found + */ + public List removeMany(final AtomicBoolean processing) { + return removeMany(processing, false, 0); } /** * Removes as many messages as possible + * @param remove_msgs Removes messages from xmit_table + * @param max_results Max number of messages to remove in one batch * @return List A list of messages, or null if no available messages were found */ - public List removeMany() { + public List removeMany(final AtomicBoolean processing, boolean remove_msgs, int max_results) { List retval=null; + int num_results=0; lock.writeLock().lock(); try { while(true) { - long next_to_remove=highest_delivered +1; - Message msg=xmit_table.get(next_to_remove); - + long next=highest_delivered +1; + Message msg=remove_msgs? xmit_table.remove(next) : xmit_table.get(next); if(msg != null) { // message exists and is ready for delivery - if(discard_delivered_msgs) { - Address sender=msg.getSrc(); - if(!local_addr.equals(sender)) { // don't remove if we sent the message ! - xmit_table.remove(next_to_remove); - } - } - highest_delivered=next_to_remove; + highest_delivered=next; if(retval == null) retval=new LinkedList(); retval.add(msg); - continue; - } - - // message has not yet been received (gap in the message sequence stream) - // drop all messages that have not been received - if(max_xmit_buf_size > 0 && xmit_table.size() > max_xmit_buf_size) { - highest_delivered=next_to_remove; - retransmitter.remove(next_to_remove); - continue; + if(max_results <= 0 || ++num_results < max_results) + continue; } + if((retval == null || retval.isEmpty()) && processing != null) + processing.set(false); return retval; } } @@ -430,30 +370,6 @@ } - public Message removeOOBMessage() { - lock.writeLock().lock(); - try { - Message retval=xmit_table.get(highest_delivered +1); - if(retval != null && retval.isFlagSet(Message.OOB)) { - return remove(); - } - return null; - } - finally { - lock.writeLock().unlock(); - } - } - - public boolean hasMessagesToRemove() { - lock.readLock().lock(); - try { - return xmit_table.get(highest_delivered + 1) != null; - } - finally { - lock.readLock().unlock(); - } - } - /** * Delete all messages <= seqno (they are stable, that is, have been received at all members). @@ -463,17 +379,14 @@ lock.writeLock().lock(); try { if(seqno > highest_delivered) { - if(log.isErrorEnabled()) - log.error("seqno " + seqno + " is > highest_delivered " + highest_delivered + "; ignoring stability message"); + if(log.isWarnEnabled()) + log.warn("seqno " + seqno + " is > highest_delivered (" + highest_delivered + ";) ignoring stability message"); return; } - + // we need to remove all seqnos *including* seqno - if(!xmit_table.isEmpty()) { - for(long i=low; i <= seqno; i++) { - xmit_table.remove(i); - } - } + xmit_table.purge(seqno); + // remove all seqnos below seqno from retransmission for(long i=low; i <= seqno; i++) { retransmitter.remove(i); @@ -489,13 +402,19 @@ /** - * Reset the retransmitter and the nak window
        + * Destroys the NakReceiverWindow. After this method returns, no new messages can be added and a new + * NakReceiverWindow should be used instead. Note that messages can still be removed though. */ - public void reset() { + public void destroy() { lock.writeLock().lock(); try { + running=false; retransmitter.reset(); - _reset(); + xmit_table.clear(); + low=0; + highest_delivered=0; // next (=first) to deliver will be 1 + highest_received=0; + highest_stability_seqno=0; } finally { lock.writeLock().unlock(); @@ -503,24 +422,22 @@ } - /** - * Stop the retransmitter and reset the nak window
        - */ - public void destroy() { - lock.writeLock().lock(); + /** Returns the lowest, highest delivered and highest received seqnos */ + public long[] getDigest() { + lock.readLock().lock(); try { - retransmitter.stop(); - _reset(); + long[] retval=new long[3]; + retval[0]=low; + retval[1]=highest_delivered; + retval[2]=highest_received; + return retval; } finally { - lock.writeLock().unlock(); + lock.readLock().unlock(); } } - - - /** * @return the lowest sequence number of a message that has been * delivered or is a candidate for delivery (by the next call to @@ -554,6 +471,17 @@ } + public long setHighestDelivered(long new_val) { + lock.writeLock().lock(); + try { + long retval=highest_delivered; + highest_delivered=new_val; + return retval; + } + finally { + lock.writeLock().unlock(); + } + } /** @@ -590,6 +518,23 @@ } + /** + * Returns a list of messages in the range [from .. to], including from and to + * @param from + * @param to + * @return A list of messages, or null if none in range [from .. to] was found + */ + public List get(long from, long to) { + lock.readLock().lock(); + try { + return xmit_table.get(from, to); + } + finally { + lock.readLock().unlock(); + } + } + + public int size() { lock.readLock().lock(); try { @@ -613,26 +558,26 @@ - /** * Prints xmit_table. Requires read lock to be present * @return String */ - String printMessages() { + protected String printMessages() { StringBuilder sb=new StringBuilder(); - sb.append('[').append(low).append(" : ").append(highest_delivered).append(" (").append(highest_received).append(")"); - if(xmit_table != null && !xmit_table.isEmpty()) { - int non_received=0; - - for(Map.Entry entry: xmit_table.entrySet()) { - if(entry.getValue() == null) - non_received++; + lock.readLock().lock(); + try { + sb.append('[').append(low).append(" : ").append(highest_delivered).append(" (").append(highest_received).append(")"); + if(xmit_table != null && !xmit_table.isEmpty()) { + int non_received=xmit_table.getNullMessages(highest_received); + sb.append(" (size=").append(xmit_table.size()).append(", missing=").append(non_received). + append(", highest stability=").append(highest_stability_seqno).append(')'); } - sb.append(" (size=").append(xmit_table.size()).append(", missing=").append(non_received). - append(", highest stability=").append(highest_stability_seqno).append(')'); + sb.append(']'); + return sb.toString(); + } + finally { + lock.readLock().unlock(); } - sb.append(']'); - return sb.toString(); } public String printLossRate() { @@ -646,25 +591,14 @@ return sb.toString(); } - /* ------------------------------- Private Methods -------------------------------------- */ - + public String printRetransmitStats() { + return retransmitter instanceof RangeBasedRetransmitter? ((RangeBasedRetransmitter)retransmitter).printStats() : "n/a"; + } + /* ------------------------------- Private Methods -------------------------------------- */ - /** - * Reset the Nak window. Should be called from within a writeLock() context. - *

        - * i. Delete all received entries
        - * ii. Reset all indices
        - */ - private void _reset() { - xmit_table.clear(); - low=0; - highest_delivered=0; // next (=first) to deliver will be 1 - highest_received=0; - highest_stability_seqno=0; - } /* --------------------------- End of Private Methods ----------------------------------- */ diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/Protocol.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/Protocol.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/Protocol.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/Protocol.java 2011-10-18 11:22:35.000000000 +0000 @@ -3,20 +3,22 @@ package org.jgroups.stack; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.Event; -import org.jgroups.util.ThreadFactory; -import org.jgroups.protocols.TP; +import org.jgroups.conf.ClassConfigurator; import org.jgroups.annotations.DeprecatedProperty; import org.jgroups.annotations.ManagedAttribute; import org.jgroups.annotations.Property; +import org.jgroups.jmx.ResourceDMBean; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.protocols.TP; +import org.jgroups.util.SocketFactory; +import org.jgroups.util.ThreadFactory; +import org.jgroups.util.Util; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** @@ -36,18 +38,55 @@ * constructor ! * * @author Bela Ban - * @version $Id: Protocol.java,v 1.65 2008/10/21 08:14:47 vlada Exp $ */ @DeprecatedProperty(names={"down_thread","down_thread_prio","up_thread","up_thread_prio"}) public abstract class Protocol { protected Protocol up_prot=null, down_prot=null; protected ProtocolStack stack=null; - @Property(description="Determines whether to collect statistics (and expose them via JMX). Default is true") + @Property(description="Determines whether to collect statistics (and expose them via JMX). Default is true",writable=true) protected boolean stats=true; + + @Property(description="Enables ergonomics: dynamically find the best values for properties at runtime") + protected boolean ergonomics=true; + + /** The name of the protocol. Is by default set to the protocol's classname. This property should rarely need to + * be set, e.g. only in cases where we want to create more than 1 protocol of the same class in the same stack */ + @Property(name="name",description="Give the protocol a different name if needed so we can have multiple " + + "instances of it in the same stack (also change ID)",writable=false) + protected String name=getClass().getSimpleName(); + + @Property(description="Give the protocol a different ID if needed so we can have multiple " + + "instances of it in the same stack",writable=false) + protected short id=ClassConfigurator.getProtocolId(getClass()); + protected final Log log=LogFactory.getLog(this.getClass()); + + /** + * Sets the level of a logger. This method is used to dynamically change the logging level of a + * running system, e.g. via JMX. The appender of a level needs to exist. + * @param level The new level. Valid values are "fatal", "error", "warn", "info", "debug", "trace" + * (capitalization not relevant) + */ + @Property(name="level", description="Sets the logger level (see javadocs)") + public void setLevel(String level) { + log.setLevel(level); + } + + public String getLevel() { + return log.getLevel(); + } + + public boolean isErgonomics() { + return ergonomics; + } + + public void setErgonomics(boolean ergonomics) { + this.ergonomics=ergonomics; + } + /** * Configures the protocol initially. A configuration string consists of name=value * items, separated by a ';' (semicolon), e.g.:

        @@ -79,6 +118,40 @@
                 throw new UnsupportedOperationException("use a setter instead");
             }
         
        +    public Object getValue(String name) {
        +        if(name == null) return null;
        +        Field field=Util.getField(getClass(), name);
        +        if(field == null)
        +            throw new IllegalArgumentException("field \"" + name + "\n not found");
        +        return Configurator.getField(field, this);
        +    }
        +
        +    public Protocol setValues(Map values) {
        +        if(values == null)
        +            return this;
        +        for(Map.Entry entry: values.entrySet()) {
        +            String attrname=entry.getKey();
        +            Object value=entry.getValue();
        +            Field field=Util.getField(getClass(), attrname);
        +            if(field != null) {
        +                Configurator.setField(field, this, value);
        +            }
        +        }
        +        return this;
        +    }
        +
        +
        +    public Protocol setValue(String name, Object value) {
        +        if(name == null || value == null)
        +            return this;
        +        Field field=Util.getField(getClass(), name);
        +        if(field == null)
        +            throw new IllegalArgumentException("field \"" + name + "\n not found");
        +        Configurator.setField(field, this, value);
        +        return this;
        +    }
        +
        +
             /**
              * @return
              * @deprecated Use a getter to get the actual instance variable
        @@ -120,6 +193,23 @@
                 return down_prot != null? down_prot.getThreadFactory(): null;
             }
         
        +    /**
        +     * Returns the SocketFactory associated with this protocol, if overridden in a subclass, or passes the call down
        +     * @return SocketFactory
        +     */
        +    public SocketFactory getSocketFactory() {
        +        return down_prot != null? down_prot.getSocketFactory() : null;
        +    }
        +
        +    /**
        +     * Sets a SocketFactory. Socket factories are typically provided by the transport ({@link org.jgroups.protocols.TP})
        +     * or {@link org.jgroups.protocols.TP.ProtocolAdapter}
        +     * @param factory
        +     */
        +    public void setSocketFactory(SocketFactory factory) {
        +        if(down_prot != null)
        +            down_prot.setSocketFactory(factory);
        +    }
         
             /** @deprecated up_thread was removed
              * @return false by default
        @@ -156,19 +246,14 @@
                 HashMap map=new HashMap();
                 for(Class clazz=this.getClass();clazz != null;clazz=clazz.getSuperclass()) {
                     Field[] fields=clazz.getDeclaredFields();
        -            for(Field field:fields) {
        -                if(field.isAnnotationPresent(ManagedAttribute.class)) {
        +            for(Field field: fields) {
        +                if(field.isAnnotationPresent(ManagedAttribute.class) ||
        +                        (field.isAnnotationPresent(Property.class) && field.getAnnotation(Property.class).exposeAsManagedAttribute())) {
                             String attributeName=field.getName();
        -                    Object value;
                             try {
                                 field.setAccessible(true);
        -                        value=field.get(this);
        -                        if(value != null) {
        -                            map.put(attributeName, value.toString());
        -                        }
        -                        else {
        -                            map.put(attributeName, null);
        -                        }
        +                        Object value=field.get(this);
        +                        map.put(attributeName, value != null? value.toString() : null);
                             }
                             catch(Exception e) {
                                 log.warn("Could not retrieve value of attribute (field) " + attributeName,e);
        @@ -177,24 +262,33 @@
                     }
         
                     Method[] methods=this.getClass().getMethods();
        -            for(Method method:methods) {
        -                if(method.isAnnotationPresent(ManagedAttribute.class)) {
        -                    ManagedAttribute annotation=method.getAnnotation(ManagedAttribute.class);
        -                    if(!annotation.writable() && (method.getName().startsWith("is")
        -                       || method.getName().startsWith("get"))) {
        -                        Object value=null;
        +            for(Method method: methods) {
        +                if(method.isAnnotationPresent(ManagedAttribute.class) ||
        +                        (method.isAnnotationPresent(Property.class) && method.getAnnotation(Property.class).exposeAsManagedAttribute())) {
        +
        +                    String method_name=method.getName();
        +                    if(method_name.startsWith("is") || method_name.startsWith("get")) {
                                 try {
        -                            value=method.invoke(this, new Object[0]);
        -                            String attributeName=methodNameToAttributeName(method.getName());
        -                            if(value != null) {
        -                                map.put(attributeName, value.toString());
        -                            }
        -                            else {
        -                                map.put(attributeName, null);
        -                            }
        +                            Object value=method.invoke(this);
        +                            String attributeName=Util.methodNameToAttributeName(method_name);
        +                            map.put(attributeName, value != null? value.toString() : null);
                                 }
                                 catch(Exception e) {
        -                            log.warn("Could not retrieve value of attribute (method) " + method.getName(),e);
        +                            log.warn("Could not retrieve value of attribute (method) " + method_name,e);
        +                        }
        +                    }
        +                    else if(method_name.startsWith("set")) {
        +                        String stem=method_name.substring(3);
        +                        Method getter=ResourceDMBean.findGetter(getClass(), stem);
        +                        if(getter != null) {
        +                            try {
        +                                Object value=getter.invoke(this);
        +                                String attributeName=Util.methodNameToAttributeName(method_name);
        +                                map.put(attributeName, value != null? value.toString() : null);
        +                            }
        +                            catch(Exception e) {
        +                                log.warn("Could not retrieve value of attribute (method) " + method_name, e);
        +                            }
                                 }
                             }
                         }
        @@ -203,19 +297,8 @@
                 return map;
             }
             
        -    private String methodNameToAttributeName(String methodName) {
        -        methodName=methodName.startsWith("get") || methodName.startsWith("set")? methodName.substring(3): methodName;
        -        methodName=methodName.startsWith("is")? methodName.substring(2) : methodName;
        -        Pattern p=Pattern.compile("[A-Z]");
        -        Matcher m=p.matcher(methodName.substring(1));
        -        StringBuffer sb=new StringBuffer();
        -        while(m.find()) {
        -            m.appendReplacement(sb, "_" + methodName.substring(m.end(), m.end() + 1).toLowerCase());
        -        }
        -        m.appendTail(sb);
        -        sb.insert(0, methodName.substring(0, 1).toLowerCase());
        -        return sb.toString();
        -    }
        +
        +
             
             /**
              * Called after instance has been created (null constructor) and before protocol is started.
        @@ -284,7 +367,17 @@
         
         
             /** All protocol names have to be unique ! */
        -    public abstract String getName();
        +    public String getName() {
        +        return name;
        +    }
        +
        +    public short getId() {
        +        return id;
        +    }
        +
        +    public void setId(short id) {
        +        this.id=id;
        +    }
         
             public Protocol getUpProtocol() {
                 return up_prot;
        diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/ProtocolStack.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/ProtocolStack.java
        --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/ProtocolStack.java	2009-01-05 12:42:12.000000000 +0000
        +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/ProtocolStack.java	2011-10-18 11:22:35.000000000 +0000
        @@ -1,14 +1,15 @@
        -
         package org.jgroups.stack;
         
         import org.jgroups.*;
         import org.jgroups.annotations.Property;
         import org.jgroups.conf.ClassConfigurator;
         import org.jgroups.conf.PropertyConverter;
        +import org.jgroups.conf.ProtocolConfiguration;
         import org.jgroups.protocols.TP;
         import org.jgroups.util.ThreadFactory;
         import org.jgroups.util.TimeScheduler;
         import org.jgroups.util.Tuple;
        +import org.jgroups.util.Util;
         
         import java.lang.reflect.Field;
         import java.lang.reflect.Method;
        @@ -16,7 +17,6 @@
         import java.util.Map.Entry;
         import java.util.concurrent.ConcurrentHashMap;
         import java.util.concurrent.ConcurrentMap;
        -import java.util.concurrent.locks.ReentrantLock;
         
         
         /**
        @@ -28,34 +28,24 @@
          * 

        * The ProtocolStack makes use of the Configurator to setup and initialize * stacks, and to destroy them again when not needed anymore - * + * * @author Bela Ban - * @version $Id: ProtocolStack.java,v 1.92 2008/10/29 09:19:32 belaban Exp $ */ public class ProtocolStack extends Protocol implements Transport { public static final int ABOVE = 1; // used by insertProtocol() public static final int BELOW = 2; // used by insertProtocol() /** - * Holds the shared transports, keyed by 'TP.singleton_name'. The values are - * the transport and the use count for init() (decremented by destroy()) and - * start() (decremented by stop() + * Holds the shared transports, keyed by 'TP.singleton_name'. The values are the transport and the use count for + * init() (decremented by destroy()) and start() (decremented by stop() */ private static final ConcurrentMap> singleton_transports=new ConcurrentHashMap>(); - - - private Protocol top_prot = null; - private Protocol bottom_prot = null; - private String setup_string; - private JChannel channel = null; - private volatile boolean stopped=true; - - /** - * Locks acquired by protocol below, need to get released on down(). See - * http://jira.jboss.com/jira/browse/JGRP-535 for details - */ - private final Map locks=new ConcurrentHashMap(); + private Protocol top_prot; + private Protocol bottom_prot; + // protected List configs; + private JChannel channel; + private volatile boolean stopped=true; private final TP.ProbeHandler props_handler=new TP.ProbeHandler() { @@ -68,20 +58,94 @@ map.put("props", tmp); return map; } + if(key.startsWith("print-protocols")) { + List prots=getProtocols(); + Collections.reverse(prots); + StringBuilder sb=new StringBuilder(); + for(Protocol prot: prots) + sb.append(prot.getName()).append("\n"); + HashMap map=new HashMap(1); + map.put("protocols", sb.toString()); + return map; + } + if(key.startsWith("remove-protocol")) { + key=key.substring("remove-protocol".length()); + int index=key.indexOf("="); + if(index != -1) { + String prot_name=key.substring(index +1); + if(prot_name != null && prot_name.length() > 0) { + try { + Protocol removed=removeProtocol(prot_name); + if(removed != null) + log.debug("removed protocol " + prot_name + " from stack"); + } + catch(Exception e) { + log.error("failed removing protocol " + prot_name, e); + } + } + } + } + if(key.startsWith("insert-protocol")) { + key=key.substring("insert-protocol".length()+1); + int index=key.indexOf("="); + if(index == -1) break; + + // 1. name of the protocol to be inserted + String prot_name=key.substring(0, index).trim(); + Protocol prot=null; + try { + prot=createProtocol(prot_name); + prot.init(); + prot.start(); + } + catch(Exception e) { + log.error("failed creating an instance of " + prot_name, e); + break; + } + + key=key.substring(index+1); + + index=key.indexOf('='); + if(index == -1) { + log.error("= missing in insert-protocol command"); + break; + } + + // 2. "above" or "below" + String tmp=key.substring(0, index); + if(!tmp.equalsIgnoreCase("above") && !tmp.equalsIgnoreCase("below")) { + log.error("Missing \"above\" or \"below\" in insert-protocol command"); + break; + } + + key=key.substring(index+1); + String neighbor_prot=key.trim(); + Protocol neighbor=findProtocol(neighbor_prot); + if(neighbor == null) { + log.error("Neighbor protocol " + neighbor_prot + " not found in stack"); + break; + } + int position=tmp.equalsIgnoreCase("above")? ABOVE : BELOW; + try { + insertProtocol(prot, position, neighbor.getClass()); + } + catch(Exception e) { + log.error("failed inserting protocol " + prot_name + " " + tmp + " " + neighbor_prot, e); + } + } } return null; } public String[] supportedKeys() { - return new String[]{"props"}; + return new String[]{"props", "print-protocols", "\nremove-protocol=", "\ninsert-protocol=;above | below="}; } }; - public ProtocolStack(JChannel channel, String setup_string) throws ChannelException { - - this.setup_string=setup_string; - this.channel=channel; + public ProtocolStack(JChannel channel) throws ChannelException { + // this.configs=configs; + this.channel=channel; Class tmp=ClassConfigurator.class; // load this class, trigger init() try { @@ -93,15 +157,14 @@ } - - /** Only used by Simulator; don't use */ - public ProtocolStack() throws ChannelException { - this(null,null); + /** Used for programmatic creation of ProtocolStack */ + public ProtocolStack() { } + public JChannel getChannel() {return channel;} - public String getSetupString() { - return setup_string; + public void setChannel(JChannel ch) { + this.channel=ch; } /** @@ -109,8 +172,6 @@ * @return */ public ThreadFactory getThreadFactory() { - - getTransport().getThreadFactory(); TP transport=getTransport(); return transport != null? transport.getThreadFactory() : null; } @@ -134,13 +195,6 @@ public static void setTimerThreadFactory(ThreadFactory f) { } - public Map getLocks() { - return locks; - } - - public Channel getChannel() { - return channel; - } /** @@ -153,27 +207,27 @@ if(transport != null) { timer=transport.getTimer(); if(timer != null) - return timer.getCorePoolSize(); + return timer.getMinThreads(); } return -1; } /** Returns all protocols in a list, from top to bottom. These are not copies of protocols, so modifications will affect the actual instances ! */ - public Vector getProtocols() { - Vector v=new Vector(); + public List getProtocols() { + List v=new ArrayList(15); Protocol p=top_prot; while(p != null) { - v.addElement(p); + v.add(p); p=p.getDownProtocol(); } return v; } - public Vector copyProtocols(ProtocolStack targetStack) throws IllegalAccessException, InstantiationException { - Vector list=getProtocols(); - Vector retval=new Vector(list.size()); + public List copyProtocols(ProtocolStack targetStack) throws IllegalAccessException, InstantiationException { + List list=getProtocols(); + List retval=new ArrayList(list.size()); for(Protocol prot: list) { Protocol new_prot=prot.getClass().newInstance(); new_prot.setProtocolStack(targetStack); @@ -200,7 +254,7 @@ if(annotation.name() != null) possible_names.add(annotation.name()); possible_names.add(methodName.substring(3)); - possible_names.add(Configurator.renameFromJavaCodingConvention(methodName.substring(3))); + possible_names.add(Util.methodNameToAttributeName(methodName)); Field field=findField(prot, possible_names); if(field != null) { Object value=Configurator.getField(field, prot); @@ -262,20 +316,36 @@ } public Map dumpStats(String protocol_name) { - Protocol p; - Map retval=new HashMap(), tmp; - String prot_name; + return dumpStats(protocol_name, null); + } - p=top_prot; - while(p != null) { - prot_name=p.getName(); - if(prot_name.equals(protocol_name)) { - tmp=p.dumpStats(); - if(tmp != null) - retval.put(prot_name, tmp); + + public Map dumpStats(String protocol_name, List attrs) { + Protocol prot=findProtocol(protocol_name); + if(prot == null) + return null; + + Map retval=new HashMap(), tmp; + tmp=prot.dumpStats(); + if(tmp != null) { + if(attrs != null && !attrs.isEmpty()) { + // weed out attrs not in list + for(Iterator it=tmp.keySet().iterator(); it.hasNext();) { + String attrname=it.next(); + boolean found=false; + for(String attr: attrs) { + if(attrname.startsWith(attr)) { + found=true; + break; // found + } + } + if(!found) + it.remove(); + } } - p=p.getDownProtocol(); + retval.put(protocol_name, tmp); } + return retval; } @@ -290,7 +360,7 @@ if(transport != null) { timer=transport.getTimer(); if(timer != null) - return timer.dumpTaskQueue(); + return timer.dumpTimerTasks(); } return ""; } @@ -301,7 +371,7 @@ */ public String printProtocolSpec(boolean include_properties) { StringBuilder sb=new StringBuilder(); - Vector protocols=getProtocols(); + List protocols=getProtocols(); if(protocols == null || protocols.isEmpty()) return null; boolean first_colon_printed=false; @@ -348,14 +418,14 @@ sb.append("\n"); while(prot != null) { - String name=prot.getName(); - if(name != null) { - if("ProtocolStack".equals(name)) + String prot_name=prot.getName(); + if(prot_name != null) { + if("ProtocolStack".equals(prot_name)) break; - sb.append(" <").append(name).append(" "); + sb.append(" <").append(prot_name).append(" "); Map tmpProps=getProps(prot); if(tmpProps != null) { - len=name.length(); + len=prot_name.length(); String s; for(Iterator> it=tmpProps.entrySet().iterator();it.hasNext();) { Entry entry=it.next(); @@ -383,7 +453,7 @@ private String printProtocolSpecAsPlainString(boolean print_props) { StringBuilder sb=new StringBuilder(); - Vector protocols=getProtocols(); + List protocols=getProtocols(); if(protocols == null) return null; @@ -436,7 +506,7 @@ if(annotation.name() != null) possible_names.add(annotation.name()); possible_names.add(methodName.substring(3)); - possible_names.add(Configurator.renameFromJavaCodingConvention(methodName.substring(3))); + possible_names.add(Util.methodNameToAttributeName(methodName)); Field field=findField(prot, possible_names); if(field != null) { Object value=Configurator.getField(field, prot); @@ -459,34 +529,83 @@ } - public void setup() throws Exception { + public void setup(List configs) throws Exception { if(top_prot == null) { - top_prot=getProtocolStackFactory().setupProtocolStack(); + top_prot=new Configurator(this).setupProtocolStack(configs); top_prot.setUpProtocol(this); this.setDownProtocol(top_prot); - bottom_prot=getBottomProtocol(); + bottom_prot=getBottomProtocol(); initProtocolStack(); } } - protected ProtocolStackFactory getProtocolStackFactory() { - return new Configurator(this); - } - - public void setup(ProtocolStack stack) throws Exception { + public void setup(ProtocolStack stack) throws Exception { if(top_prot == null) { - top_prot=getProtocolStackFactory().setupProtocolStack(stack); + top_prot=new Configurator(this).setupProtocolStack(stack); top_prot.setUpProtocol(this); this.setDownProtocol(top_prot); - bottom_prot=getBottomProtocol(); + bottom_prot=getBottomProtocol(); initProtocolStack(); } } + + + /** + * Adds a protocol at the tail of the protocol list + * @param prot + * @return + * @since 2.11 + */ + public ProtocolStack addProtocol(Protocol prot) { + if(prot == null) + return this; + prot.setUpProtocol(this); + if(bottom_prot == null) { + top_prot=bottom_prot=prot; + return this; + } + + prot.setDownProtocol(top_prot); + prot.getDownProtocol().setUpProtocol(prot); + top_prot=prot; + + return this; + } + + /** + * Adds a list of protocols + * @param prots + * @return + * @since 2.11 + */ + public ProtocolStack addProtocols(Protocol ... prots) { + if(prots != null) { + for(Protocol prot: prots) + addProtocol(prot); + } + return this; + } + + /** + * Adds a list of protocols + * @param prots + * @return + * @since 2.1 + */ + public ProtocolStack addProtocols(List prots) { + if(prots != null) { + for(Protocol prot: prots) + addProtocol(prot); + } + return this; + } + + /** * Inserts an already created (and initialized) protocol into the protocol list. Sets the links * to the protocols above and below correctly and adjusts the linked list of protocols accordingly. @@ -499,7 +618,7 @@ * @exception Exception Will be thrown when the new protocol cannot be created, or inserted. */ public void insertProtocol(Protocol prot, int position, String neighbor_prot) throws Exception { - if(neighbor_prot == null) throw new IllegalArgumentException("Configurator.insertProtocol(): neighbor_prot is null"); + if(neighbor_prot == null) throw new IllegalArgumentException("neighbor_prot is null"); if(position != ProtocolStack.ABOVE && position != ProtocolStack.BELOW) throw new IllegalArgumentException("position has to be ABOVE or BELOW"); @@ -509,12 +628,12 @@ if(position == ProtocolStack.BELOW && neighbor instanceof TP) throw new IllegalArgumentException("Cannot insert protocol " + prot.getName() + " below transport protocol"); - + insertProtocolInStack(prot, neighbor, position); } - private void insertProtocolInStack(Protocol prot, Protocol neighbor, int position) { + public void insertProtocolInStack(Protocol prot, Protocol neighbor, int position) { // connect to the protocol layer below and above if(position == ProtocolStack.BELOW) { prot.setUpProtocol(neighbor); @@ -534,25 +653,56 @@ neighbor.setUpProtocol(prot); } } - + private void checkAndSwitchTop(Protocol oldTop, Protocol newTop){ - if(oldTop == top_prot) + if(oldTop == top_prot) { top_prot = newTop; + top_prot.setUpProtocol(this); + } } public void insertProtocol(Protocol prot, int position, Class neighbor_prot) throws Exception { - if(neighbor_prot == null) throw new IllegalArgumentException("Configurator.insertProtocol(): neighbor_prot is null"); + if(neighbor_prot == null) throw new IllegalArgumentException("neighbor_prot is null"); if(position != ProtocolStack.ABOVE && position != ProtocolStack.BELOW) throw new IllegalArgumentException("position has to be ABOVE or BELOW"); Protocol neighbor=findProtocol(neighbor_prot); if(neighbor == null) throw new IllegalArgumentException("protocol \"" + neighbor_prot + "\" not found in " + stack.printProtocolSpec(false)); - + insertProtocolInStack(prot, neighbor, position); } + public void insertProtocol(Protocol prot, int position, Class ... neighbor_prots) throws Exception { + if(neighbor_prots == null) throw new IllegalArgumentException("neighbor_prots is null"); + if(position != ProtocolStack.ABOVE && position != ProtocolStack.BELOW) + throw new IllegalArgumentException("position has to be ABOVE or BELOW"); + + Protocol neighbor=findProtocol(neighbor_prots); + if(neighbor == null) + throw new IllegalArgumentException("protocol \"" + neighbor_prots.toString() + "\" not found in " + stack.printProtocolSpec(false)); + insertProtocolInStack(prot, neighbor, position); + } + + + public void insertProtocolAtTop(Protocol prot) { + if(prot == null) + throw new IllegalArgumentException("prot needs to be non-null"); + + // check if prot already exists (we cannot have more than 1 protocol of a given class) + Class clazz=prot.getClass(); + Protocol existing_instance=findProtocol(clazz); + if(existing_instance != null) + return; + + top_prot.up_prot=prot; + prot.down_prot=top_prot; + prot.up_prot=this; + top_prot=prot; + if(log.isDebugEnabled()) + log.debug("inserted " + prot + " at the top of the stack"); + } /** @@ -565,19 +715,63 @@ public Protocol removeProtocol(String prot_name) throws Exception { if(prot_name == null) return null; Protocol prot=findProtocol(prot_name); - if(prot == null) return null; + if(prot == null) return null; Protocol above=prot.getUpProtocol(), below=prot.getDownProtocol(); - checkAndSwitchTop(prot, below); + checkAndSwitchTop(prot, below); if(above != null) above.setDownProtocol(below); if(below != null) below.setUpProtocol(above); prot.setUpProtocol(null); prot.setDownProtocol(null); + try { + prot.stop(); + } + catch(Throwable t) { + log.error("failed stopping " + prot.getName() + ": " + t); + } + try { + prot.destroy(); + } + catch(Throwable t) { + log.error("failed destroying " + prot.getName() + ": " + t); + } return prot; } + public Protocol removeProtocol(Class ... protocols) { + Protocol retval=null; + if(protocols != null) + for(Class cl: protocols) { + Protocol tmp=removeProtocol(cl); + if(tmp != null) + retval=tmp; + } + + return retval; + } + + + public Protocol removeProtocol(Class prot) { + if(prot == null) + return null; + Protocol retval=findProtocol(prot); + if(retval == null) + return null; + + Protocol above=retval.getUpProtocol(), below=retval.getDownProtocol(); + checkAndSwitchTop(retval, below); + if(above != null) + above.setDownProtocol(below); + if(below != null) + below.setUpProtocol(above); + retval.setUpProtocol(null); + retval.setDownProtocol(null); + return retval; + } + + /** Returns a given protocol or null if not found */ public Protocol findProtocol(String name) { Protocol tmp=top_prot; @@ -590,7 +784,7 @@ } return null; } - + public Protocol getBottomProtocol() { Protocol curr_prot=this; while(curr_prot != null && curr_prot.getDownProtocol() !=null) { @@ -598,25 +792,82 @@ } return curr_prot; } - + public Protocol getTopProtocol() { return top_prot; } - + public Protocol findProtocol(Class clazz) { - Protocol tmp=top_prot; + Protocol tmp=top_prot; while(tmp != null) { Class protClass=tmp.getClass(); if(clazz.isAssignableFrom(protClass)){ return tmp; - } + } tmp=tmp.getDownProtocol(); } return null; } - + + /** + * Finds the first protocol of a list and returns it. Returns null if no protocol can be found + * @param classes A list of protocol classes to find + * @return Protocol The protocol found + */ + public Protocol findProtocol(Class ... classes) { + for(Class clazz: classes) { + Protocol prot=findProtocol(clazz); + if(prot != null) + return prot; + } + return null; + } + + + + protected Protocol createProtocol(String classname) throws Exception { + String defaultProtocolName=ProtocolConfiguration.protocol_prefix + '.' + classname; + Class clazz=null; + + try { + clazz=Util.loadClass(defaultProtocolName, getClass()); + } + catch(ClassNotFoundException e) { + } + + if(clazz == null) { + try { + clazz=Util.loadClass(classname, getClass()); + } + catch(ClassNotFoundException e) { + } + if(clazz == null) { + throw new Exception("unable to load class for protocol " + classname + " (either as an absolute - " + + classname + " - or relative - " + defaultProtocolName + " - package name)"); + } + } + + Protocol retval=(Protocol)clazz.newInstance(); + if(retval == null) + throw new Exception("creation of instance for protocol " + classname + "failed"); + retval.setProtocolStack(this); + return retval; + } + + + public void init() throws Exception { + List protocols=getProtocols(); + Collections.reverse(protocols); + top_prot=Configurator.connectProtocols(protocols); + top_prot.setUpProtocol(this); + this.setDownProtocol(top_prot); + bottom_prot=getBottomProtocol(); + Configurator.setDefaultValues(protocols); + initProtocolStack(); + } + public void initProtocolStack() throws Exception { - Vector protocols = getProtocols(); + List protocols = getProtocols(); Collections.reverse(protocols); for(Protocol prot: protocols) { if(prot instanceof TP) { @@ -667,15 +918,15 @@ } prot.destroy(); } - + /* *Do not null top_prot reference since we need recreation of channel properties (JChannel#getProperties) *during channel recreation, especially if those properties were modified after channel was created. *We modify channel properties after channel creation in some tests for example - * + * */ //top_prot=null; - } + } } @@ -685,10 +936,10 @@ * from top to bottom. * Each layer can perform some initialization, e.g. create a multicast socket */ - public void startStack(String cluster_name) throws Exception { + public void startStack(String cluster_name, Address local_addr) throws Exception { if(stopped == false) return; - Protocol above_prot=null; + Protocol above_prot=null; for(final Protocol prot: getProtocols()) { if(prot instanceof TP) { String singleton_name=((TP)prot).getSingletonName(); @@ -712,9 +963,9 @@ } if(above_prot != null) { - TP.ProtocolAdapter ad=new TP.ProtocolAdapter(cluster_name, prot.getName(), above_prot, prot, - transport.getThreadNamingPattern(), - transport.getLocalAddress()); + TP.ProtocolAdapter ad=new TP.ProtocolAdapter(cluster_name, local_addr, prot.getId(), + above_prot, prot, + transport.getThreadNamingPattern()); ad.setProtocolStack(above_prot.getProtocolStack()); above_prot.setDownProtocol(ad); up_prots.put(cluster_name, ad); @@ -725,8 +976,6 @@ ProtocolStack.RefCounter counter=val.getVal2(); short num_starts=counter.incrementStartCount(); if(num_starts >= 1) { - if(above_prot != null) - above_prot.up(new Event(Event.SET_LOCAL_ADDRESS, transport.getLocalAddress())); continue; } else { @@ -737,7 +986,7 @@ } } } - } + } prot.start(); above_prot=prot; } @@ -761,9 +1010,9 @@ if(stopped) return; for(final Protocol prot: getProtocols()) { if(prot instanceof TP) { - TP transport=(TP)prot; + TP transport=(TP)prot; if(transport.isSingleton()) { - String singleton_name=transport.getSingletonName(); + String singleton_name=transport.getSingletonName(); final Map up_prots=transport.getUpProtocols(); synchronized(up_prots) { up_prots.remove(cluster_name); @@ -828,12 +1077,6 @@ public Object down(Event evt) { - ReentrantLock lock=locks.remove(Thread.currentThread()); - if(lock != null && lock.isHeldByCurrentThread()) { - lock.unlock(); - if(log.isTraceEnabled()) - log.trace("released lock held by " + Thread.currentThread()); - } if(top_prot != null) return top_prot.down(evt); return null; @@ -868,17 +1111,17 @@ public short incrementInitCount(){ return init_count++; } - + public short decrementInitCount() { init_count=(short)Math.max(init_count -1, 0); return init_count; - } - + } + public short decrementStartCount() { start_count=(short)Math.max(start_count -1, 0); return start_count; } - + public short incrementStartCount() { return start_count++; } @@ -887,11 +1130,6 @@ return "init_count=" + init_count + ", start_count=" + start_count; } } - - public interface ProtocolStackFactory{ - public Protocol setupProtocolStack() throws Exception; - public Protocol setupProtocolStack(ProtocolStack copySource) throws Exception; - } -} +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/RangeBasedRetransmitter.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/RangeBasedRetransmitter.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/RangeBasedRetransmitter.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/RangeBasedRetransmitter.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,223 @@ + +package org.jgroups.stack; + +import org.jgroups.Address; +import org.jgroups.util.*; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.atomic.AtomicLong; + + +/** + * This retransmitter is specialized in maintaining ranges of seqnos, e.g. [3-20, [89-89], [100-120]. + * The ranges are stored in a sorted hashmap and the {@link Comparable#compareTo(Object)} method compares both ranges + * again ranges, and ranges against seqnos. The latter helps to find a range given a seqno, e.g. seqno 105 will find + * range [100-120].

        + * Each range is implemented by {@link org.jgroups.util.SeqnoRange}, which has a bitset of all missing seqnos. When + * a seqno is received, that bit set is updated; the bit corresponding to the seqno is set to 1. A task linked to + * the range periodically retransmits missing messages.

        + * When all bits are 1 (= all messages have been received), the range is removed from the hashmap and the retransmission + * task is cancelled. + * + * @author Bela Ban + */ +public class RangeBasedRetransmitter extends Retransmitter { + + + // todo: when JDK 6 is the baseline, convert the TreeMap to a TreeSet or ConcurrentSkipListSet and use ceiling() + /** Sorted hashmap storing the ranges */ + private final Map ranges=new ConcurrentSkipListMap(new SeqnoComparator()); + + /** Association between ranges and retransmission tasks */ + private final Map tasks=new ConcurrentHashMap(); + + + private final AtomicLong num_missing_seqnos=new AtomicLong(0); + private final AtomicLong num_ranges=new AtomicLong(0); + private final AtomicLong num_single_msgs=new AtomicLong(0); + + + /** + * Create a new Retransmitter associated with the given sender address + * @param sender the address from which retransmissions are expected or to which retransmissions are sent + * @param cmd the retransmission callback reference + * @param sched retransmissions scheduler + */ + public RangeBasedRetransmitter(Address sender, RetransmitCommand cmd, TimeScheduler sched) { + super(sender, cmd, sched); + } + + + + /** + * Add the given range [first_seqno, last_seqno] in the list of + * entries eligible for retransmission. If first_seqno > last_seqno, + * then the range [last_seqno, first_seqno] is added instead + */ + public void add(long first_seqno, long last_seqno) { + if(first_seqno > last_seqno) { + long tmp=first_seqno; + first_seqno=last_seqno; + last_seqno=tmp; + } + + num_missing_seqnos.addAndGet(last_seqno - first_seqno +1); + + // create a single seqno if we have no range or else a SeqnoRange + Seqno range=first_seqno == last_seqno? new Seqno(first_seqno) : new SeqnoRange(first_seqno, last_seqno); + if(range instanceof SeqnoRange) + num_ranges.incrementAndGet(); + else + num_single_msgs.incrementAndGet(); + + // each task needs its own retransmission interval, as they are stateful *and* mutable, so we *need* to copy ! + RangeTask new_task=new RangeTask(range, RETRANSMIT_TIMEOUTS.copy(), cmd, sender); + + Seqno old_range=ranges.put(range, range); + if(old_range != null) + log.error("new range " + range + " overlaps with old range " + old_range); + + tasks.put(range, new_task); + new_task.doSchedule(); // Entry adds itself to the timer + + if(log.isTraceEnabled()) + log.trace("added range " + sender + " [" + range + "]"); + } + + /** + * Remove the given sequence number from the list of seqnos eligible + * for retransmission. If there are no more seqno intervals in the + * respective entry, cancel the entry from the retransmission + * scheduler and remove it from the pending entries + */ + public int remove(long seqno) { + int retval=0; + Seqno range=ranges.get(new Seqno(seqno, true)); + if(range == null) + return 0; + + range.set(seqno); + if(log.isTraceEnabled()) + log.trace("removed " + sender + " #" + seqno + " from retransmitter"); + + // if the range has no missing messages, get the associated task and cancel it + if(range.getNumberOfMissingMessages() == 0) { + Task task=tasks.remove(range); + if(task != null) { + task.cancel(); + retval=task.getNumRetransmits(); + } + else + log.error("task for range " + range + " not found"); + ranges.remove(range); + if(log.isTraceEnabled()) + log.trace("all messages for " + sender + " [" + range + "] have been received; removing range"); + } + + return retval; + } + + /** + * Reset the retransmitter: clear all msgs and cancel all the + * respective tasks + */ + public void reset() { + synchronized(ranges) { + for(Seqno range: ranges.keySet()) { + // get task associated with range and cancel it + Task task=tasks.get(range); + if(task != null) { + task.cancel(); + tasks.remove(range); + } + } + + ranges.clear(); + } + + for(Task task: tasks.values()) + task.cancel(); + + num_missing_seqnos.set(0); + num_ranges.set(0); + num_single_msgs.set(0); + } + + + + public String toString() { + int missing_msgs=0; + + for(Seqno range: ranges.keySet()) + missing_msgs+=range.getNumberOfMissingMessages(); + + StringBuilder sb=new StringBuilder(); + sb.append(missing_msgs).append(" messages to retransmit"); + if(missing_msgs < 50) { + Collection all_missing_msgs=new LinkedList(); + for(Seqno range: ranges.keySet()) { + all_missing_msgs.addAll(range.getMessagesToRetransmit()); + } + sb.append(": ").append(all_missing_msgs); + } + return sb.toString(); + } + + + public int size() { + int retval=0; + + for(Seqno range: ranges.keySet()) + retval+=range.getNumberOfMissingMessages(); + return retval; + } + + + public String printStats() { + StringBuilder sb=new StringBuilder(); + sb.append("total seqnos=" + num_missing_seqnos); + sb.append(", single seqnos=" + num_single_msgs); + sb.append(", ranges=" + num_ranges); + double avg_seqnos_per_range=(double)(num_missing_seqnos.get() - num_single_msgs.get()) / num_ranges.get(); + sb.append(", seqnos / range: " + avg_seqnos_per_range); + return sb.toString(); + } + + + protected class RangeTask extends Task { + protected final Seqno range; + + protected RangeTask(Seqno range, Interval intervals, RetransmitCommand cmd, Address msg_sender) { + super(intervals, cmd, msg_sender); + this.range=range; + } + + + public String toString() { + return range.toString(); + } + + protected void callRetransmissionCommand() { + Collection missing=range.getMessagesToRetransmit(); + if(missing.isEmpty()) { + cancel(); + } + else { + for(Range range: missing) { + command.retransmit(range.low, range.high, msg_sender); + } + } + } + } + + + + /* ------------------------------- Private Methods -------------------------------------- */ + + + /* ---------------------------- End of Private Methods ------------------------------------ */ + + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/Retransmitter.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/Retransmitter.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/Retransmitter.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/Retransmitter.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,19 +1,17 @@ -// $Id: Retransmitter.java,v 1.25 2008/05/15 10:49:11 belaban Exp $ package org.jgroups.stack; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.Address; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.util.TimeScheduler; -import org.jgroups.util.Util; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; /** + * Abstract class of a retransmitter. * Maintains a pool of sequence numbers of messages that need to be retransmitted. Messages * are aged and retransmission requests sent according to age (configurable backoff). If a * TimeScheduler instance is given to the constructor, it will be used, otherwise Reransmitter @@ -23,33 +21,25 @@ * Whenever a message needs to be retransmitted, the RetransmitCommand.retransmit() method is called. * It can be used e.g. by an ack-based scheme (e.g. AckSenderWindow) to retransmit a message to the receiver, or * by a nak-based scheme to send a retransmission request to the sender of the missing message.
        - * Changes Aug 2007 (bela): the retransmitter was completely rewritten. Entry was removed, instead all tasks - * are directly placed into a hashmap, keyed by seqnos. When a message has been received, we simply remove - * the task from the hashmap and cancel it. This simplifies the code and avoids having to iterate through - * the (previous) message list linearly on removal. Performance is about the same, or slightly better in - * informal tests. * @author Bela Ban - * @version $Revision: 1.25 $ + * @version $Revision: 1.33 $ */ -public class Retransmitter { +public abstract class Retransmitter { - private static final long SEC=1000; /** Default retransmit intervals (ms) - exponential approx. */ - private Interval RETRANSMIT_TIMEOUTS=new StaticInterval(2 * SEC, 3 * SEC, 5 * SEC, 8 * SEC); - private Address sender=null; - private final ConcurrentMap msgs=new ConcurrentHashMap(11); - private RetransmitCommand cmd=null; - private TimeScheduler timer=null; - protected static final Log log=LogFactory.getLog(Retransmitter.class); + protected Interval RETRANSMIT_TIMEOUTS=new StaticInterval(300, 600, 1200, 2400); + protected final Address sender; + protected final RetransmitCommand cmd; + protected final TimeScheduler timer; + protected static final Log log=LogFactory.getLog(Retransmitter.class); /** Retransmit command (see Gamma et al.) used to retrieve missing messages */ public interface RetransmitCommand { /** - * Get the missing messages between sequence numbers - * first_seqno and last_seqno. This can either be done by sending a - * retransmit message to destination sender (nak-based scheme), or by - * retransmitting the missing message(s) to sender (ack-based scheme). + * Get the missing messages between sequence numbers first_seqno and last_seqno. + * This can either be done by sending a retransmit message to destination sender + * (nak-based scheme), or by retransmitting the missing message(s) to sender (ack-based scheme). * @param first_seqno The sequence number of the first missing message * @param last_seqno The sequence number of the last missing message * @param sender The destination of the member to which the retransmit request will be sent @@ -66,7 +56,9 @@ * @param sched retransmissions scheduler */ public Retransmitter(Address sender, RetransmitCommand cmd, TimeScheduler sched) { - init(sender, cmd, sched); + this.sender=sender; + this.cmd=cmd; + timer=sched; } @@ -78,112 +70,44 @@ /** - * Add the given range [first_seqno, last_seqno] in the list of - * entries eligible for retransmission. If first_seqno > last_seqno, - * then the range [last_seqno, first_seqno] is added instead - *

        - * If retransmitter thread is suspended, wake it up + * Add messages from first_seqno to last_seqno for retransmission */ - public void add(long first_seqno, long last_seqno) { - if(first_seqno > last_seqno) { - long tmp=first_seqno; - first_seqno=last_seqno; - last_seqno=tmp; - } - - Task new_task; - for(long seqno=first_seqno; seqno <= last_seqno; seqno++) { - // each task needs its own retransmission interval, as they are stateful *and* mutable, so we *need* to copy ! - new_task=new Task(seqno, RETRANSMIT_TIMEOUTS.copy(), cmd, sender); - Task old_task=msgs.putIfAbsent(seqno, new_task); - if(old_task == null) // only schedule if we actually *added* the new task ! - new_task.doSchedule(timer); // Entry adds itself to the timer - } - - } + public abstract void add(long first_seqno, long last_seqno); /** - * Remove the given sequence number from the list of seqnos eligible - * for retransmission. If there are no more seqno intervals in the - * respective entry, cancel the entry from the retransmission - * scheduler and remove it from the pending entries + * Remove the given sequence number from retransmission */ - public int remove(long seqno) { - Task task=msgs.remove(seqno); - if(task != null) { - task.cancel(); - return task.getNumRetransmits(); - } - return -1; - } + public abstract int remove(long seqno); /** - * Reset the retransmitter: clear all msgs and cancel all the - * respective tasks + * Reset the retransmitter: clear all msgs and cancel all the respective tasks */ - public void reset() { - for(Task task: msgs.values()) - task.cancel(); - msgs.clear(); - } - - - public void stop() { - reset(); - } - - - public String toString() { - int size=size(); - StringBuilder sb=new StringBuilder(); - sb.append(size).append(" messages to retransmit: ").append(msgs.keySet()); - return sb.toString(); - } + public abstract void reset(); - public int size() { - return msgs.size(); - } + public abstract int size(); /* ------------------------------- Private Methods -------------------------------------- */ - /** - * Init this object - * - * @param sender the address from which retransmissions are expected - * @param cmd the retransmission callback reference - * @param sched retransmissions scheduler - */ - private void init(Address sender, RetransmitCommand cmd, TimeScheduler sched) { - this.sender=sender; - this.cmd=cmd; - timer=sched; - } /* ---------------------------- End of Private Methods ------------------------------------ */ + protected abstract class Task implements TimeScheduler.Task { + protected final Interval intervals; + protected volatile Future future; + protected Address msg_sender=null; + protected int num_retransmits=0; + protected RetransmitCommand command; + protected volatile boolean cancelled=false; - - /** - * The retransmit task executed by the scheduler in regular intervals - */ - private static class Task implements TimeScheduler.Task { - private final Interval intervals; - private long seqno=-1; - private Future future; - private Address sender=null; - protected int num_retransmits=0; - private RetransmitCommand command; - - protected Task(long seqno, Interval intervals, RetransmitCommand cmd, Address sender) { - this.seqno=seqno; + protected Task(Interval intervals, RetransmitCommand cmd, Address msg_sender) { this.intervals=intervals; this.command=cmd; - this.sender=sender; + this.msg_sender=msg_sender; } public int getNumRetransmits() { @@ -194,41 +118,41 @@ return intervals.next(); } - public void doSchedule(TimeScheduler timer) { - future=timer.scheduleWithDynamicInterval(this); + public void doSchedule() { + if(cancelled) { + return; + } + long delay=intervals.next(); + future=timer.schedule(this, delay, TimeUnit.MILLISECONDS); } public void cancel() { + if(!cancelled) { + cancelled=true; + } if(future != null) - future.cancel(false); + future.cancel(true); } public void run() { - command.retransmit(seqno, seqno, sender); - num_retransmits++; + if(cancelled) { + return; + } + try { + callRetransmissionCommand(); + num_retransmits++; + } + catch(Throwable t) { + if(log.isErrorEnabled()) + log.error("failed retransmission task", t); + } + doSchedule(); } - public String toString() { - return String.valueOf(seqno); - } - } - - - - - - static class MyXmitter implements Retransmitter.RetransmitCommand { - - public void retransmit(long first_seqno, long last_seqno, Address sender) { - System.out.println("-- " + new java.util.Date() + ": retransmit(" + first_seqno + ", " + - last_seqno + ", " + sender + ')'); - } + protected abstract void callRetransmissionCommand(); } - static void sleep(long timeout) { - Util.sleep(timeout); - } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/RouterStub.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/RouterStub.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/RouterStub.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/RouterStub.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,210 +1,292 @@ package org.jgroups.stack; +import org.jgroups.Address; +import org.jgroups.PhysicalAddress; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.protocols.PingData; +import org.jgroups.protocols.TUNNEL.StubReceiver; +import org.jgroups.util.Util; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.net.DatagramSocket; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketException; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jgroups.Address; -import org.jgroups.util.Util; +import java.util.ArrayList; +import java.util.List; /** * Client stub that talks to a remote GossipRouter - * * @author Bela Ban - * @version $Id: RouterStub.java,v 1.33 2008/10/31 06:57:20 belaban Exp $ */ public class RouterStub { - public final static int STATUS_CONNECTED = 0; - - public final static int STATUS_DISCONNECTED = 1; + public static enum ConnectionStatus {INITIAL, CONNECTION_BROKEN, CONNECTION_ESTABLISHED, CONNECTED,DISCONNECTED}; - private String router_host = null; // name of the router host + private final String router_host; // name of the router host - private int router_port = 0; // port on which router listens on - // router_host + private final int router_port; // port on which router listens on - private Socket sock = null; // socket connecting to the router + private Socket sock=null; // socket connecting to the router - private DataOutputStream output = null; // output stream associated with - // sock + private DataOutputStream output=null; - private DataInputStream input = null; // input stream associated with sock + private DataInputStream input=null; - private Address local_addr = null; // addr of group mbr. Once assigned, - // remains the same + private volatile ConnectionStatus connectionState=ConnectionStatus.INITIAL; - private volatile int connectionState = STATUS_DISCONNECTED; + private static final Log log=LogFactory.getLog(RouterStub.class); - private static final Log log = LogFactory.getLog(RouterStub.class); + private final ConnectionListener conn_listener; - private ConnectionListener conn_listener; + private final InetAddress bind_addr; - private String groupname = null; + private int sock_conn_timeout=3000; // max number of ms to wait for socket establishment to + // GossipRouter - private InetAddress bind_addr = null; + private int sock_read_timeout=3000; // max number of ms to wait for socket reads (0 means block + // forever, or until the sock is closed) - private DatagramSocket my_sock = null; + private boolean tcp_nodelay=true; + + private StubReceiver receiver; public interface ConnectionListener { - void connectionStatusChange(int state); + void connectionStatusChange(RouterStub stub, ConnectionStatus state); } /** * Creates a stub for a remote Router object. - * - * @param routerHost - * The name of the router's host - * @param routerPort - * The router's port + * @param routerHost The name of the router's host + * @param routerPort The router's port + * @throws SocketException */ - public RouterStub(String routerHost,int routerPort,InetAddress bindAddress){ - router_host = routerHost != null ? routerHost : "localhost"; - router_port = routerPort; - bind_addr = bindAddress; + public RouterStub(String routerHost, int routerPort, InetAddress bindAddress, ConnectionListener l) { + router_host=routerHost != null? routerHost : "localhost"; + router_port=routerPort; + bind_addr=bindAddress; + conn_listener=l; + } + + public synchronized void setReceiver(StubReceiver receiver) { + this.receiver = receiver; + } + + public synchronized StubReceiver getReceiver() { + return receiver; } - public boolean isConnected() { - return connectionState == STATUS_CONNECTED; + public boolean isTcpNoDelay() { + return tcp_nodelay; } - public void setConnectionListener(ConnectionListener conn_listener) { - this.conn_listener = conn_listener; + public void setTcpNoDelay(boolean tcp_nodelay) { + this.tcp_nodelay=tcp_nodelay; } - public synchronized Address getLocalAddress() throws SocketException { - if(local_addr == null){ - my_sock = new DatagramSocket(0, bind_addr); - local_addr = new IpAddress(bind_addr, my_sock.getLocalPort()); + public synchronized void interrupt() { + if(receiver != null) { + Thread thread = receiver.getThread(); + if(thread != null) + thread.interrupt(); + } + } + + public synchronized void join(long wait) throws InterruptedException { + if(receiver != null) { + Thread thread = receiver.getThread(); + if(thread != null) + thread.join(wait); } - return local_addr; } + + public int getSocketConnectionTimeout() { + return sock_conn_timeout; + } + + public void setSocketConnectionTimeout(int sock_conn_timeout) { + this.sock_conn_timeout=sock_conn_timeout; + } + + public int getSocketReadTimeout() { + return sock_read_timeout; + } + + public void setSocketReadTimeout(int sock_read_timeout) { + this.sock_read_timeout=sock_read_timeout; + } + + public boolean isConnected() { + return !(connectionState == ConnectionStatus.CONNECTION_BROKEN || connectionState == ConnectionStatus.INITIAL); + } + + public ConnectionStatus getConnectionStatus() { + return connectionState; + } + + /** - * Register this process with the router under groupname. - * - * @param groupname - * The name of the group under which to register + * Register this process with the router under group. + * @param group The name of the group under which to register */ - public synchronized void connect(String groupname) throws Exception { - if(groupname == null || groupname.length() == 0) - throw new Exception("groupname is null"); - - if(!isConnected()){ - this.groupname = groupname; - try{ - sock = new Socket(router_host, router_port, bind_addr, 0); - sock.setSoLinger(true, 5); - output = new DataOutputStream(sock.getOutputStream()); - GossipData req = new GossipData(GossipRouter.CONNECT, - groupname, - getLocalAddress(), - null); - req.writeTo(output); - output.flush(); - input = new DataInputStream(sock.getInputStream()); - boolean connectedOk = input.readBoolean(); - if(connectedOk) - connectionStateChanged(STATUS_CONNECTED); - else - throw new Exception("Failed to get connection ack from gossip router"); - }catch(Exception e){ - if(log.isWarnEnabled()) - log.warn(this + " failed connecting to " + router_host + ":" + router_port); + public synchronized void connect(String group, Address addr, String logical_name, List phys_addrs) throws Exception { + doConnect(); + GossipData request=new GossipData(GossipRouter.CONNECT, group, addr, logical_name, phys_addrs); + request.writeTo(output); + output.flush(); + byte result = input.readByte(); + if(result == GossipRouter.CONNECT_OK) { + connectionStateChanged(ConnectionStatus.CONNECTED); + } else { + connectionStateChanged(ConnectionStatus.DISCONNECTED); + throw new Exception("Connect failed received from GR " + getGossipRouterAddress()); + } + } + + public synchronized void doConnect() throws Exception { + if(!isConnected()) { + try { + sock=new Socket(); + sock.bind(new InetSocketAddress(bind_addr, 0)); + sock.setSoTimeout(sock_read_timeout); + sock.setSoLinger(true, 2); + sock.setTcpNoDelay(tcp_nodelay); + sock.setKeepAlive(true); + Util.connect(sock, new InetSocketAddress(router_host, router_port), sock_conn_timeout); + output=new DataOutputStream(sock.getOutputStream()); + input=new DataInputStream(sock.getInputStream()); + connectionStateChanged(ConnectionStatus.CONNECTION_ESTABLISHED); + } + catch(Exception e) { Util.close(sock); Util.close(input); Util.close(output); - connectionStateChanged(STATUS_DISCONNECTED); - throw e; + connectionStateChanged(ConnectionStatus.CONNECTION_BROKEN); + throw new Exception("Could not connect to " + getGossipRouterAddress() , e); } } } - public synchronized void disconnect() { - try{ - GossipData req = new GossipData(GossipRouter.DISCONNECT, groupname, local_addr, null); - req.writeTo(output); + /** + * Checks whether the connection is open + * @return + */ + public synchronized void checkConnection() { + GossipData request=new GossipData(GossipRouter.PING); + try { + request.writeTo(output); + output.flush(); + } + catch(IOException e) { + connectionStateChanged(ConnectionStatus.CONNECTION_BROKEN); + } + } + + + public synchronized void disconnect(String group, Address addr) { + try { + GossipData request=new GossipData(GossipRouter.DISCONNECT, group, addr); + request.writeTo(output); output.flush(); - }catch(Exception e){ - }finally{ + } + catch(Exception e) { + } finally { + connectionStateChanged(ConnectionStatus.DISCONNECTED); + } + } + + public synchronized void destroy() { + try { + GossipData request = new GossipData(GossipRouter.CLOSE); + request.writeTo(output); + output.flush(); + } catch (Exception e) { + } finally { Util.close(output); Util.close(input); Util.close(sock); - Util.close(my_sock); - sock = null; - connectionStateChanged(STATUS_DISCONNECTED); } } + + + /* + * Used only in testing, never access socket directly + * + */ + public Socket getSocket() { + return sock; + } - public String toString() { - return "RouterStub[local_address=" + local_addr - + ",router_host=" - + router_host - + ",router_port=" - + router_port - + ",connected=" - + isConnected() - + "]"; - } - - public void sendToAllMembers(byte[] data, int offset, int length) throws Exception { - // null destination represents mcast - sendToSingleMember(null, data, offset, length); - } - - public synchronized void sendToSingleMember(Address dest, byte[] data, int offset, int length) throws Exception { - if(isConnected()){ - try{ - // 1. Group name - output.writeUTF(groupname); - - // 2. Destination address (null in case of mcast) - Util.writeAddress(dest, output); - - // 3. Length of byte buffer - output.writeInt(data.length); - - // 4. Byte buffer - output.write(data, 0, data.length); - - output.flush(); - - }catch(SocketException se){ - if(log.isWarnEnabled()) - log.warn("Router stub " + this - + " did not send message to " - + (dest == null ? "mcast" - : dest + " since underlying socket is closed")); - connectionStateChanged(STATUS_DISCONNECTED); - }catch(Exception e){ - if(log.isErrorEnabled()) - log.error("Router stub " + this + " failed sending message to router"); - connectionStateChanged(STATUS_DISCONNECTED); - throw new Exception("dest=" + dest + " (" + length + " bytes)", e); + + public synchronized List getMembers(final String group) throws Exception { + List retval=new ArrayList(); + try { + + if(!isConnected() || input == null) throw new Exception ("not connected"); + // we might get a spurious SUSPECT message from the router, just ignore it + if(input.available() > 0) // fixes https://jira.jboss.org/jira/browse/JGRP-1151 + input.skipBytes(input.available()); + + GossipData request=new GossipData(GossipRouter.GOSSIP_GET, group, null); + request.writeTo(output); + output.flush(); + + short num_rsps=input.readShort(); + for(int i=0; i < num_rsps; i++) { + PingData rsp=new PingData(); + rsp.readFrom(input); + retval.add(rsp); } + } + catch(Exception e) { + connectionStateChanged(ConnectionStatus.CONNECTION_BROKEN); + throw new Exception("Connection to " + getGossipRouterAddress() + " broken. Could not send GOSSIP_GET request", e); } + return retval; + } + + public InetSocketAddress getGossipRouterAddress() { + return new InetSocketAddress(router_host, router_port); + } + + public String toString() { + return "RouterStub[localsocket=" + ((sock != null) ? sock.getLocalSocketAddress().toString() + : "null")+ ",router_host=" + router_host + "::" + router_port + + ",connected=" + isConnected() + "]"; + } + + public void sendToAllMembers(String group, byte[] data, int offset, int length) throws Exception { + sendToMember(group, null, data, offset, length); // null destination represents mcast } - public DataInputStream getInputStream() throws IOException { - if(!isConnected()){ - throw new IOException("InputStream is closed"); + public synchronized void sendToMember(String group, Address dest, byte[] data, int offset, int length) throws Exception { + try { + GossipData request = new GossipData(GossipRouter.MESSAGE, group, dest, data, offset, length); + request.writeTo(output); + output.flush(); + } catch (Exception e) { + connectionStateChanged(ConnectionStatus.CONNECTION_BROKEN); + throw new Exception("Connection to " + getGossipRouterAddress() + + " broken. Could not send message to " + dest, e); } + } + + public DataInputStream getInputStream() { return input; } - private void connectionStateChanged(int newState) { - boolean notify = connectionState != newState; - connectionState = newState; - if(notify && conn_listener != null){ - try{ - conn_listener.connectionStatusChange(newState); - }catch(Throwable t){ + private void connectionStateChanged(ConnectionStatus newState) { + boolean notify=connectionState != newState; + connectionState=newState; + if(notify && conn_listener != null) { + try { + conn_listener.connectionStatusChange(this, newState); + } + catch(Throwable t) { log.error("failed notifying ConnectionListener " + conn_listener, t); } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/RouterStubManager.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/RouterStubManager.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/RouterStubManager.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/RouterStubManager.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,213 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2009, Red Hat Middleware LLC, and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This 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 software 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 software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jgroups.stack; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.jgroups.Address; +import org.jgroups.Event; +import org.jgroups.PhysicalAddress; +import org.jgroups.annotations.GuardedBy; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.util.TimeScheduler; + +public class RouterStubManager implements RouterStub.ConnectionListener { + + @GuardedBy("reconnectorLock") + private final Map> futures = new HashMap>(); + private final Lock reconnectorLock = new ReentrantLock(); + private final List stubs; + + private final Protocol owner; + private final TimeScheduler timer; + private final String channelName; + private final Address logicalAddress; + private final long interval; + + protected final Log log; + + public RouterStubManager(Protocol owner, String channelName, Address logicalAddress, long interval) { + this.owner = owner; + this.stubs = new CopyOnWriteArrayList(); + this.log = LogFactory.getLog(owner.getClass()); + this.timer = owner.getTransport().getTimer(); + this.channelName = channelName; + this.logicalAddress = logicalAddress; + this.interval = interval; + } + + private RouterStubManager(Protocol p) { + this(p,null,null,0L); + } + + public List getStubs(){ + return stubs; + } + + public RouterStub createAndRegisterStub(String routerHost, int routerPort, InetAddress bindAddress) { + RouterStub s = new RouterStub(routerHost,routerPort,bindAddress,this); + unregisterAndDestroyStub(s.getGossipRouterAddress()); + stubs.add(s); + return s; + } + + public void registerStub(RouterStub s) { + unregisterAndDestroyStub(s.getGossipRouterAddress()); + stubs.add(s); + } + + public boolean unregisterStub(final RouterStub s) { + return stubs.remove(s); + } + + public RouterStub unregisterStub(final InetSocketAddress address) { + if(address == null) + throw new IllegalArgumentException("Cannot remove null address"); + for (RouterStub s : stubs) { + if (s.getGossipRouterAddress().equals(address)) { + stubs.remove(address); + return s; + } + } + return null; + } + + public boolean unregisterAndDestroyStub(final InetSocketAddress address) { + RouterStub unregisteredStub = unregisterStub(address); + if(unregisteredStub !=null) { + unregisteredStub.destroy(); + return true; + } + return false; + } + + public void disconnectStubs() { + for (RouterStub stub : stubs) { + try { + stub.disconnect(channelName, logicalAddress); + } catch (Exception e) { + } + } + } + + public void destroyStubs() { + for (RouterStub s : stubs) { + stopReconnecting(s); + s.destroy(); + } + stubs.clear(); + } + + public void startReconnecting(final RouterStub stub) { + reconnectorLock.lock(); + try { + InetSocketAddress routerAddress = stub.getGossipRouterAddress(); + Future f = futures.get(routerAddress); + if (f != null) { + f.cancel(true); + futures.remove(routerAddress); + } + + final Runnable reconnector = new Runnable() { + public void run() { + try { + if (log.isTraceEnabled()) log.trace("Reconnecting " + stub); + String logical_name = org.jgroups.util.UUID.get(logicalAddress); + PhysicalAddress physical_addr = (PhysicalAddress) owner.down(new Event( + Event.GET_PHYSICAL_ADDRESS, logicalAddress)); + List physical_addrs = Arrays.asList(physical_addr); + stub.connect(channelName, logicalAddress, logical_name, physical_addrs); + if (log.isTraceEnabled()) log.trace("Reconnected " + stub); + } catch (Throwable ex) { + if (log.isWarnEnabled()) + log.warn("failed reconnecting stub to GR at "+ stub.getGossipRouterAddress() + ": " + ex); + } + } + }; + f = timer.scheduleWithFixedDelay(reconnector, 0, interval, TimeUnit.MILLISECONDS); + futures.put(stub.getGossipRouterAddress(), f); + } finally { + reconnectorLock.unlock(); + } + } + + public void stopReconnecting(final RouterStub stub) { + reconnectorLock.lock(); + try { + InetSocketAddress routerAddress = stub.getGossipRouterAddress(); + Future f = futures.get(stub.getGossipRouterAddress()); + if (f != null) { + f.cancel(true); + futures.remove(routerAddress); + } + + final Runnable pinger = new Runnable() { + public void run() { + try { + if(log.isTraceEnabled()) log.trace("Pinging " + stub); + stub.checkConnection(); + if(log.isTraceEnabled()) log.trace("Pinged " + stub); + } catch (Throwable ex) { + if (log.isWarnEnabled()) + log.warn("failed pinging stub, GR at " + stub.getGossipRouterAddress()+ ": " + ex); + } + } + }; + f = timer.scheduleWithFixedDelay(pinger, 0, interval, TimeUnit.MILLISECONDS); + futures.put(stub.getGossipRouterAddress(), f); + } finally { + reconnectorLock.unlock(); + } + } + + + public void connectionStatusChange(RouterStub stub, RouterStub.ConnectionStatus newState) { + if (newState == RouterStub.ConnectionStatus.CONNECTION_BROKEN) { + stub.interrupt(); + stub.destroy(); + startReconnecting(stub); + } else if (newState == RouterStub.ConnectionStatus.CONNECTED) { + stopReconnecting(stub); + } else if (newState == RouterStub.ConnectionStatus.DISCONNECTED) { + // wait for disconnect ack; + try { + stub.join(interval); + } catch (InterruptedException e) { + } + } + } + + public static RouterStubManager emptyGossipClientStubManager(Protocol p) { + return new RouterStubManager(p); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/StateTransferInfo.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/StateTransferInfo.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/StateTransferInfo.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/StateTransferInfo.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: StateTransferInfo.java,v 1.16 2008/01/22 10:44:35 belaban Exp $ package org.jgroups.stack; @@ -14,7 +13,6 @@ * layer. The state is retrieved from 'target'. If target is null, then the state will be retrieved from the oldest * member (usually the coordinator). * @author Bela Ban - * @version $Id: StateTransferInfo.java,v 1.16 2008/01/22 10:44:35 belaban Exp $ */ public class StateTransferInfo { public Address target=null; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/stack/StaticInterval.java libjgroups-java-2.12.2.Final/src/org/jgroups/stack/StaticInterval.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/stack/StaticInterval.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/stack/StaticInterval.java 2011-10-18 11:22:35.000000000 +0000 @@ -10,7 +10,6 @@ * so it shouldn't be shared between instances, as {@link #next()} will modify the state. * @author John Giorgiadis * @author Bela Ban - * @version $Id: StaticInterval.java,v 1.3 2007/08/10 12:47:38 belaban Exp $ */ public class StaticInterval implements Interval { private int next=0; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/SuspectedException.java libjgroups-java-2.12.2.Final/src/org/jgroups/SuspectedException.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/SuspectedException.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/SuspectedException.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: SuspectedException.java,v 1.4 2006/04/29 03:25:32 belaban Exp $ package org.jgroups; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/SuspectEvent.java libjgroups-java-2.12.2.Final/src/org/jgroups/SuspectEvent.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/SuspectEvent.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/SuspectEvent.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: SuspectEvent.java,v 1.3 2005/07/17 11:38:05 chrislott Exp $ package org.jgroups; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/TimeoutException.java libjgroups-java-2.12.2.Final/src/org/jgroups/TimeoutException.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/TimeoutException.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/TimeoutException.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: TimeoutException.java,v 1.4 2006/02/16 08:41:32 belaban Exp $ package org.jgroups; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/Transport.java libjgroups-java-2.12.2.Final/src/org/jgroups/Transport.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/Transport.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/Transport.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: Transport.java,v 1.2 2005/07/17 11:38:05 chrislott Exp $ package org.jgroups; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/UnblockEvent.java libjgroups-java-2.12.2.Final/src/org/jgroups/UnblockEvent.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/UnblockEvent.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/UnblockEvent.java 2011-10-18 11:22:35.000000000 +0000 @@ -3,7 +3,6 @@ /** * Trivial object that represents a block event. * @author Bela Ban - * @version $Id: UnblockEvent.java,v 1.1 2006/09/27 12:33:06 belaban Exp $ */ public class UnblockEvent { public String toString() {return "UnblockEvent";} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/UpHandler.java libjgroups-java-2.12.2.Final/src/org/jgroups/UpHandler.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/UpHandler.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/UpHandler.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: UpHandler.java,v 1.4 2007/01/11 12:57:28 belaban Exp $ package org.jgroups; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/AckCollector.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/AckCollector.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/AckCollector.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/AckCollector.java 2011-10-18 11:22:35.000000000 +0000 @@ -8,23 +8,27 @@ import java.util.*; /** + * Collects acks from a number of nodes, waits for all acks. Can also be time bounded * @author Bela Ban - * @version $Id: AckCollector.java,v 1.14 2007/10/26 18:24:00 vlada Exp $ */ public class AckCollector { /** List: list of members from whom we haven't received an ACK yet */ private final List missing_acks; - private final Set received_acks=new HashSet(); private final Promise all_acks_received=new Promise(); private final Set
        suspected_mbrs=new HashSet
        (); + private int expected_acks=0; public AckCollector() { missing_acks=new ArrayList(); + expected_acks=0; } public AckCollector(ViewId v, List l) { - missing_acks=new ArrayList(l); + missing_acks=new ArrayList(l); + if(v != null) { + expected_acks=l != null? l.size() : 0; + } } public String printMissing() { @@ -33,19 +37,25 @@ } } - public String printReceived() { + @Deprecated + public static String printReceived() { + return "n/a"; + } + + public String printSuspected() { synchronized(this) { - return received_acks.toString(); + return suspected_mbrs.toString(); } - } + } - public void reset(ViewId v, List
        members) { + public void reset(Collection
        members) { synchronized(this) { suspected_mbrs.clear(); missing_acks.clear(); - received_acks.clear(); - if(members != null && !members.isEmpty()) + if(members != null && !members.isEmpty()) { missing_acks.addAll(members); + expected_acks=members.size(); + } missing_acks.removeAll(suspected_mbrs); all_acks_received.reset(); } @@ -57,10 +67,20 @@ } } + @Deprecated + public static int receivedAcks() { + return -1; + } + + public int expectedAcks() { + synchronized(this) { + return expected_acks; + } + } + public void ack(Object member) { synchronized(this) { missing_acks.remove(member); - received_acks.add(member); if(missing_acks.isEmpty()) all_acks_received.setResult(Boolean.TRUE); } @@ -82,7 +102,9 @@ public void handleView(View v) { if(v == null) return; Vector
        mbrs=v.getMembers(); - suspected_mbrs.retainAll(mbrs); + synchronized(this) { + suspected_mbrs.retainAll(mbrs); + } } public boolean waitForAllAcks() { @@ -100,6 +122,6 @@ } public String toString() { - return "missing=" + printMissing() + ", received=" + printReceived(); + return "missing=" + printMissing(); } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/AdditionalDataUUID.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/AdditionalDataUUID.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/AdditionalDataUUID.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/AdditionalDataUUID.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,95 @@ +package org.jgroups.util; + +import org.jgroups.Global; + +import java.io.*; +import java.security.SecureRandom; + +/** + * Subclass of {@link org.jgroups.util.UUID} which adds a string as payload. An instance of this can be fed to + * {@link org.jgroups.JChannel#setAddressGenerator(org.jgroups.stack.AddressGenerator)}, with the address generator + * creating PayloadUUIDs. + * @author Bela Ban + */ +public class AdditionalDataUUID extends UUID { + private static final long serialVersionUID=-8299077012964139735L; + + // don't need this as we already added AdditonalDataUUID to jg-magic-map.xml + // static { + // ClassConfigurator.add((short)3333, PayloadUUID.class); + // } + + protected byte[] payload; + + public AdditionalDataUUID() { + } + + protected AdditionalDataUUID(byte[] data, byte[] payload) { + super(data); + this.payload=payload; + } + + public static AdditionalDataUUID randomUUID(byte[] payload) { + return new AdditionalDataUUID(generateRandomBytes(), payload); + } + + public static AdditionalDataUUID randomUUID(String logical_name, byte[] payload) { + AdditionalDataUUID retval=new AdditionalDataUUID(generateRandomBytes(), payload); + UUID.add(retval, logical_name); + return retval; + } + + + protected static byte[] generateRandomBytes() { + SecureRandom ng=numberGenerator; + if(ng == null) + numberGenerator=ng=new SecureRandom(); + + byte[] randomBytes=new byte[16]; + ng.nextBytes(randomBytes); + return randomBytes; + } + + public int size() { + int retval=super.size() + Global.BYTE_SIZE; + if(payload != null) + retval+=payload.length; + return retval; + } + + public void writeTo(DataOutputStream out) throws IOException { + super.writeTo(out); + Util.writeByteBuffer(payload, out); + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + super.readFrom(in); + payload=Util.readByteBuffer(in); + } + + public void writeExternal(ObjectOutput out) throws IOException { + super.writeExternal(out); + if(payload != null) { + out.writeInt(payload.length); + out.write(payload, 0, payload.length); + } + else + out.writeInt(0); + } + + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + super.readExternal(in); + int size=in.readInt(); + if(size > 0) { + payload=new byte[size]; + in.read(payload, 0, size); + } + } + + public String toString() { + if(print_uuids) + return toStringLong() + (payload == null? "" : " (" + payload.length + " bytes)"); + return super.toString() + (payload == null? "" : " (" + payload.length + " bytes)"); + } + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/AgeOutCache.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/AgeOutCache.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/AgeOutCache.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/AgeOutCache.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,101 @@ +package org.jgroups.util; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.*; + +/** Cache which removes its elements after a certain time + * @author Bela Ban + */ +public class AgeOutCache { + private final TimeScheduler timer; + private long timeout; + private final ConcurrentMap map=new ConcurrentHashMap(); + private Handler handler=null; + + public interface Handler { + void expired(K key); + } + + + public AgeOutCache(TimeScheduler timer, long timeout) { + this.timer=timer; + this.timeout=timeout; + } + + public AgeOutCache(TimeScheduler timer, long timeout, Handler handler) { + this(timer, timeout); + this.handler=handler; + } + + public long getTimeout() { + return timeout; + } + + public void setTimeout(long timeout) { + this.timeout=timeout; + } + + public Handler getHandler() { + return handler; + } + + public void setHandler(Handler handler) { + this.handler=handler; + } + + public void add(final K key) { + Future future=timer.schedule(new Runnable() { + public void run() { + if(handler != null) { + try { + handler.expired(key); + } + catch(Throwable t) { + } + } + Future tmp=map.remove(key); + if(tmp != null) + tmp.cancel(true); + } + }, timeout, TimeUnit.MILLISECONDS); + Future result=map.putIfAbsent(key, future); + if(result != null) + future.cancel(true); + } + + public boolean contains(K key) { + return key != null && map.containsKey(key); + } + + public void remove(K key) { + Future future=map.remove(key); + if(future != null) + future.cancel(true); + } + + public void removeAll(Collection keys) { + if(keys != null) { + for(K key: keys) + remove(key); + } + } + + public void clear() { + for(Future future: map.values()) + future.cancel(true); + map.clear(); + } + + public int size() { + return map.size(); + } + + public String toString() { + StringBuilder sb=new StringBuilder(); + for(Map.Entry entry: map.entrySet()) { + sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); + } + return sb.toString(); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/BoundedList.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/BoundedList.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/BoundedList.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/BoundedList.java 2011-10-18 11:22:35.000000000 +0000 @@ -7,7 +7,6 @@ * A bounded subclass of LinkedList, oldest elements are removed once max capacity is exceeded. Note that this * class is not synchronized (like LinkedList). * @author Bela Ban Nov 20, 2003 - * @version $Id: BoundedList.java,v 1.5 2007/08/08 12:02:05 belaban Exp $ */ public class BoundedList extends ConcurrentLinkedQueue { int max_capacity=10; @@ -35,6 +34,10 @@ return super.add(obj); } + public boolean addIfAbsent(T obj) { + return obj != null && !contains(obj) && add(obj); + } + public T removeFromHead() { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/Buffer.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/Buffer.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/Buffer.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/Buffer.java 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ * Buffer with an offset and length. Will be replaced with NIO equivalent once JDK 1.4 becomes baseline. This class is * immutable. Note that the underlying byte[] buffer must not be changed as long as this Buffer instance is in use ! * @author Bela Ban - * @version $Id: Buffer.java,v 1.6 2008/07/21 13:53:12 belaban Exp $ */ public class Buffer { private final byte[] buf; @@ -17,6 +16,10 @@ this.length=length; } + public Buffer(byte[] buf) { + this(buf, 0, buf.length); + } + public byte[] getBuf() { return buf; } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/Command.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/Command.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/Command.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/Command.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: Command.java,v 1.3 2006/04/05 05:33:57 belaban Exp $ package org.jgroups.util; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/ConcurrentLinkedBlockingQueue.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/ConcurrentLinkedBlockingQueue.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/ConcurrentLinkedBlockingQueue.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/ConcurrentLinkedBlockingQueue.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,145 @@ +package org.jgroups.util; + +import java.util.Collection; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.LockSupport; + +/** + * Attempt at writing a fast transfer queue, which is bounded. The take() method blocks until there is an element, but + * the offer() method drops the element and returns if the queue is full (doesn't block). I thought this class would + * be useful in ThreadPoolExecutor, as a replacement for LinkedBlockingQueue, but the perf didn't change. I'll keep it + * for later reference ... + * @author Bela Ban + */ +public class ConcurrentLinkedBlockingQueue extends ConcurrentLinkedQueue implements BlockingQueue { + private static final long serialVersionUID=-8884995454506956809L; + + private final int capacity; + + private final Lock lock=new ReentrantLock(); + private final Condition not_empty=lock.newCondition(); + + private final AtomicInteger waiting_takers=new AtomicInteger(0); + + + public ConcurrentLinkedBlockingQueue(int capacity) { + this.capacity=capacity; + } + + + + + // The methods below are used by ThreadPoolExecutor /////////// + + /** + * Drops elements if capacity has been reached. That's OK for the ThreadPoolExecutor as dropped messages + * will get retransmitted + * @param t + * @return + */ + public boolean offer(T t) { + boolean retval=size() < capacity && super.offer(t); + if(waiting_takers.get() > 0) { + lock.lock(); + try { + not_empty.signal(); + } + finally { + lock.unlock(); + } + } + + return retval; + } + + public T take() throws InterruptedException { + T retval=null; + for(;;) { + retval=poll(); + if(retval != null) + return retval; + while(size() == 0) { + waiting_takers.incrementAndGet(); + lock.lockInterruptibly(); + try { + not_empty.await(); + } + finally { + lock.unlock(); + waiting_takers.decrementAndGet(); + } + } + } + } + + public T poll() { + return super.poll(); + } + + public T poll(long timeout, TimeUnit unit) throws InterruptedException { + long sleep_time_nanos=TimeUnit.NANOSECONDS.convert(timeout, unit); + long target_time_nanos=System.nanoTime() + sleep_time_nanos; + sleep_time_nanos/=5; + T el=null; + + while(System.nanoTime() < target_time_nanos) { + if((el=poll()) != null) + return el; + LockSupport.parkNanos(sleep_time_nanos); + } + + return el; + } + + public boolean remove(Object o) { + return super.remove(o); + } + + public int remainingCapacity() { + return capacity - size(); + } + + public int drainTo(Collection c) { + int count=0; + if(c == null) + return count; + + for(;;) { + T el=poll(); + if(el == null) + break; + c.add(el); + count++; + } + + return count; + } + + ///////////////////////////////////////////////////////////////// + + + + public void put(T t) throws InterruptedException { + super.offer(t); + } + + public boolean offer(T t, long timeout, TimeUnit unit) throws InterruptedException { + return offer(t); + } + + + + + + + + public int drainTo(Collection c, int maxElements) { + return drainTo(c); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/CreditMap.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/CreditMap.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/CreditMap.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/CreditMap.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,311 @@ +package org.jgroups.util; + +import org.jgroups.Address; +import org.jgroups.annotations.GuardedBy; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Maintains credits for senders, when credits fall below 0, a sender blocks until new credits have been received. + * @author Bela Ban + */ +public class CreditMap { + protected final long max_credits; + + @GuardedBy("lock") + protected final Map credits=new HashMap(); + protected long min_credits; + protected long accumulated_credits=0; + protected final Lock lock=new ReentrantLock(); + protected final Condition credits_available=lock.newCondition(); + protected int num_blockings=0; + protected long total_block_time=0; + + + public CreditMap(long max_credits) { + this.max_credits=max_credits; + min_credits=max_credits; + } + + public long getAccumulatedCredits() { + return accumulated_credits; + } + + public long getMinCredits() { + return min_credits; + } + + public int getNumBlockings() { + return num_blockings; + } + + public long getTotalBlockTime() { + return total_block_time; + } + + public Set
        keys() { + lock.lock(); + try { + return credits.keySet(); + } + finally { + lock.unlock(); + } + } + + public Long get(Address member) { + lock.lock(); + try { + return credits.get(member); + } + finally { + lock.unlock(); + } + } + + public Long remove(Address key) { + lock.lock(); + try { + Long retval=credits.remove(key); + flushAccumulatedCredits(); + long new_min=computeLowestCredit(); + if(new_min > min_credits) { + min_credits=new_min; + credits_available.signalAll(); + } + return retval; + } + finally { + lock.unlock(); + } + } + + public Long putIfAbsent(Address key) { + lock.lock(); + try { + flushAccumulatedCredits(); + Long val=credits.get(key); + return val != null? val : credits.put(key, max_credits); + } + finally { + lock.unlock(); + } + } + + + public List
        getMembersWithInsufficientCredits(long credit_needed) { + List
        retval=new LinkedList
        (); + + lock.lock(); + try { + if(credit_needed > min_credits) { + flushAccumulatedCredits(); + for(Map.Entry entry: credits.entrySet()) { + if(entry.getValue().longValue() < credit_needed) + retval.add(entry.getKey()); + } + } + return retval; + } + finally { + lock.unlock(); + } + } + + + public List> getMembersWithCreditsLessThan(long min_credits) { + List> retval=new LinkedList>(); + + lock.lock(); + try { + flushAccumulatedCredits(); + for(Map.Entry entry: credits.entrySet()) { + if(entry.getValue().longValue() <= min_credits ) + retval.add(new Tuple(entry.getKey(), entry.getValue())); + } + return retval; + } + finally { + lock.unlock(); + } + } + + + /** + * Decrements credits bytes from all. Returns true if successful, or false if not. Blocks for timeout ms + * (if greater than 0). + * + * @param credits Number of bytes to decrement from all members + * @param timeout Number of milliseconds to wait until more credits have been received + * @return True if decrementing credits bytes succeeded, false otherwise + */ + public boolean decrement(long credits, long timeout) { + lock.lock(); + try { + if(decrement(credits)) + return true; + + if(timeout <= 0) + return false; + + long start=System.currentTimeMillis(); + try { + + // System.out.println("^^^^^^ blocking: credits: " + this.credits); + + credits_available.await(timeout, TimeUnit.MILLISECONDS); + } + catch(InterruptedException e) { + } + finally { + total_block_time+=System.currentTimeMillis() - start; + num_blockings++; + } + + return decrement(credits); + } + finally { + lock.unlock(); + } + } + + + public void replenish(Address sender, long new_credits) { + if(sender == null) + return; + + lock.lock(); + try { + Long val=credits.get(sender); + if(val == null) + return; + + boolean potential_update=val.longValue() - accumulated_credits <= min_credits; + decrementAndAdd(sender, new_credits); + if(potential_update) { + long new_min=computeLowestCredit(); + if(new_min > min_credits) { + min_credits=new_min; + credits_available.signalAll(); + } + } + } + finally { + lock.unlock(); + } + } + + + public void replenishAll() { + lock.lock(); + try { + flushAccumulatedCredits(); + for(Map.Entry entry: credits.entrySet()) + entry.setValue(max_credits); + min_credits=computeLowestCredit(); + credits_available.signalAll(); + } + finally { + lock.unlock(); + } + } + + + public void clear() { + lock.lock(); + try { + num_blockings=0; + total_block_time=0; + credits.clear(); + credits_available.signalAll(); + } + finally { + lock.unlock(); + } + } + + public String toString() { + StringBuilder sb=new StringBuilder(); + lock.lock(); + try { + for(Map.Entry entry: credits.entrySet()) { + sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); + } + sb.append("min_credits=" + min_credits + ", accumulated=" + accumulated_credits); + } + finally { + lock.unlock(); + } + return sb.toString(); + } + + // need to be called with lock held + protected boolean decrement(long credits) { + if(credits <= min_credits) { + accumulated_credits+=credits; + min_credits-=credits; + return true; + } + return false; + } + + /** Needs to be called with lock held */ + protected long computeLowestCredit() { + long lowest=max_credits; + for(long cred: credits.values()) + lowest=Math.min(cred, lowest); + return lowest; + } + + public long computeLowestCreditWithAccumulated() { + long lowest=max_credits; + for(long cred: credits.values()) + lowest=Math.min(cred, lowest); + return lowest - accumulated_credits; + } + + /** + * Decrements credits bytes from all elements and add new_credits to member (if non null). + * The lowest credit needs to be greater than min_credits. Needs to be called with lock held + * @param member The member to which new_credits are added. NOP if null + * @param new_credits Number of bytes to add to member. NOP if 0. + */ + protected void decrementAndAdd(Address member, long new_credits) { + boolean replenish=member != null && new_credits > 0; + + if(accumulated_credits > 0) { + for(Map.Entry entry: this.credits.entrySet()) { + entry.setValue(Math.max(0, entry.getValue().longValue() - accumulated_credits)); + if(replenish) { + Address tmp=entry.getKey(); + if(tmp.equals(member)) + entry.setValue(Math.min(max_credits, entry.getValue().longValue() + new_credits)); + } + } + accumulated_credits=0; + } + else { + if(replenish) { + Long val=this.credits.get(member); + if(val != null) + this.credits.put(member, Math.min(max_credits, val.longValue() + new_credits)); + } + } + } + + // Called with lock held + protected void flushAccumulatedCredits() { + if(accumulated_credits > 0) { + for(Map.Entry entry: this.credits.entrySet()) { + entry.setValue(Math.max(0, entry.getValue().longValue() - accumulated_credits)); + } + accumulated_credits=0; + } + } + + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/DefaultSocketFactory.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/DefaultSocketFactory.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/DefaultSocketFactory.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/DefaultSocketFactory.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,113 @@ +package org.jgroups.util; + +import java.net.*; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Default implementation, ignores service names + * @author Bela Ban + */ +public class DefaultSocketFactory implements SocketFactory { + // Maintains information about open sockets + protected final Map sockets=new ConcurrentHashMap(); + + public Socket createSocket(String service_name) throws IOException { + return add(new Socket(), service_name); + } + + public Socket createSocket(String service_name, String host, int port) throws IOException { + return add(new Socket(host, port), service_name); + } + + public Socket createSocket(String service_name, InetAddress address, int port) throws IOException { + return add(new Socket(address, port), service_name); + } + + public Socket createSocket(String service_name, String host, int port, InetAddress localAddr, int localPort) throws IOException { + return add(new Socket(host, port, localAddr, localPort), service_name); + } + + public Socket createSocket(String service_name, InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException { + return add(new Socket(address, port, localAddr, localPort), service_name); + } + + public ServerSocket createServerSocket(String service_name) throws IOException { + return add(new ServerSocket(), service_name); + } + + public ServerSocket createServerSocket(String service_name, int port) throws IOException { + return add(new ServerSocket(port), service_name); + } + + public ServerSocket createServerSocket(String service_name, int port, int backlog) throws IOException { + return add(new ServerSocket(port, backlog), service_name); + } + + public ServerSocket createServerSocket(String service_name, int port, int backlog, InetAddress bindAddr) throws IOException { + return add(new ServerSocket(port, backlog, bindAddr), service_name); + } + + public DatagramSocket createDatagramSocket(String service_name) throws SocketException { + return add(new DatagramSocket(), service_name); + } + + public DatagramSocket createDatagramSocket(String service_name, SocketAddress bindaddr) throws SocketException { + return add(new DatagramSocket(bindaddr), service_name); + } + + public DatagramSocket createDatagramSocket(String service_name, int port) throws SocketException { + return add(new DatagramSocket(port), service_name); + } + + public DatagramSocket createDatagramSocket(String service_name, int port, InetAddress laddr) throws SocketException { + return add(new DatagramSocket(port, laddr), service_name); + } + + public MulticastSocket createMulticastSocket(String service_name) throws IOException { + return add(new MulticastSocket(), service_name); + } + + public MulticastSocket createMulticastSocket(String service_name, int port) throws IOException { + return add(new MulticastSocket(port), service_name); + } + + public MulticastSocket createMulticastSocket(String service_name, SocketAddress bindaddr) throws IOException { + return add(new MulticastSocket(bindaddr), service_name); + } + + public void close(Socket sock) throws IOException { + remove(sock); + Util.close(sock); + } + + public void close(ServerSocket sock) throws IOException { + remove(sock); + Util.close(sock); + } + + public void close(DatagramSocket sock) { + remove(sock); + Util.close(sock); + } + + public Map getSockets() { + return sockets; + } + + + + protected T add(T sock, String service_name) { + if(sock != null) { + String tmp=service_name == null? "n/a" : service_name; + sockets.put(sock, tmp); + } + return sock; + } + + protected void remove(T sock) { + if(sock != null) + sockets.remove(sock); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/DefaultThreadFactory.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/DefaultThreadFactory.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/DefaultThreadFactory.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/DefaultThreadFactory.java 2011-10-18 11:22:35.000000000 +0000 @@ -9,7 +9,6 @@ * * @author Vladimir Blagojevic * @author Bela Ban - * @version $Id: DefaultThreadFactory.java,v 1.7 2008/10/22 09:04:44 vlada Exp $ */ public class DefaultThreadFactory implements ThreadFactory, ThreadManager { protected final ThreadGroup group; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/DefaultTimeScheduler.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/DefaultTimeScheduler.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/DefaultTimeScheduler.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/DefaultTimeScheduler.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,296 @@ + +package org.jgroups.util; + + +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.Global; + +import java.util.concurrent.*; + + +/** + * Implementation of {@link org.jgroups.util.TimeScheduler} by extending + * {@link java.util.concurrent.ScheduledThreadPoolExecutor} to keep tasks sorted. Tasks will get executed in order + * of execution time (by using a {@link java.util.concurrent.DelayQueue} internally. + * + * @author Bela Ban + */ +public class DefaultTimeScheduler extends ScheduledThreadPoolExecutor implements TimeScheduler { + + /** How many core threads */ + private static int TIMER_DEFAULT_NUM_THREADS=3; + + + protected static final Log log=LogFactory.getLog(DefaultTimeScheduler.class); + + + + static { + String tmp; + try { + tmp=System.getProperty(Global.TIMER_NUM_THREADS); + if(tmp != null) + TIMER_DEFAULT_NUM_THREADS=Integer.parseInt(tmp); + } + catch(Exception e) { + log.error("could not set number of timer threads", e); + } + } + + private ThreadDecorator threadDecorator=null; + + /** + * Create a scheduler that executes tasks in dynamically adjustable intervals + */ + public DefaultTimeScheduler() { + this(TIMER_DEFAULT_NUM_THREADS); + } + + public DefaultTimeScheduler(ThreadFactory factory) { + this(factory, TIMER_DEFAULT_NUM_THREADS); + } + + public DefaultTimeScheduler(ThreadFactory factory, int max_threads) { + super(max_threads, factory); + setRejectedExecutionHandler(new ShutdownRejectedExecutionHandler(getRejectedExecutionHandler())); + } + + public DefaultTimeScheduler(int corePoolSize) { + super(corePoolSize); + setRejectedExecutionHandler(new ShutdownRejectedExecutionHandler(getRejectedExecutionHandler())); + } + + public void setThreadFactory(ThreadFactory factory) { + super.setThreadFactory(factory); + } + + public ThreadDecorator getThreadDecorator() { + return threadDecorator; + } + + public void setThreadDecorator(ThreadDecorator threadDecorator) { + this.threadDecorator=threadDecorator; + } + + public String dumpTimerTasks() { + return getQueue().toString(); + } + + + public int getCurrentThreads() { + return super.getPoolSize(); + } + + public int getMinThreads() { + return super.getCorePoolSize(); + } + + public void setMinThreads(int size) { + super.setCorePoolSize(size); + } + + public int getMaxThreads() { + return super.getMaximumPoolSize(); + } + + public void setMaxThreads(int size) { + super.setMaximumPoolSize(size); + } + + public long getKeepAliveTime() { + return super.getKeepAliveTime(TimeUnit.MILLISECONDS); + } + + public void setKeepAliveTime(long time) { + super.setKeepAliveTime(time, TimeUnit.MILLISECONDS); + } + + /** + * Schedule a task for execution at varying intervals. After execution, the task will get rescheduled after + * {@link org.jgroups.util.DefaultTimeScheduler.Task#nextInterval()} milliseconds. The task is neve done until nextInterval() + * return a value <= 0 or the task is cancelled. + * @param task the task to execute + * @param relative scheduling scheme: true:
        + * Task is rescheduled relative to the last time it actually started execution

        + * false:
        Task is scheduled relative to its last execution schedule. This has the effect + * that the time between two consecutive executions of the task remains the same.

        + * Note that relative is always true; we always schedule the next execution relative to the last *actual* + * (not scheduled) execution + */ + public Future scheduleWithDynamicInterval(Task task) { + if(task == null) + throw new NullPointerException(); + + if (isShutdown()) + return null; + + TaskWrapper task_wrapper=new TaskWrapper(task); + task_wrapper.doSchedule(); // calls schedule() in ScheduledThreadPoolExecutor + return task_wrapper; + } + + + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + return super.scheduleWithFixedDelay(new RobustRunnable(command), initialDelay, delay, unit); + } + + /** + * Answers the number of tasks currently in the queue. + * @return The number of tasks currently in the queue. + */ + public int size() { + return getQueue().size(); + } + + + + /** + * Stop the scheduler if it's running. Switch to stopped, if it's + * suspended. Clear the task queue, cancelling all un-executed tasks + * + * @throws InterruptedException if interrupted while waiting for thread + * to return + */ + public void stop() { + java.util.List tasks=shutdownNow(); + for(Runnable task: tasks) { + if(task instanceof Future) { + Future future=(Future)task; + future.cancel(true); + } + } + getQueue().clear(); + try { + awaitTermination(Global.THREADPOOL_SHUTDOWN_WAIT_TIME, TimeUnit.MILLISECONDS); + } + catch(InterruptedException e) { + } + } + + + + + @Override + protected void afterExecute(Runnable r, Throwable t) + { + try { + super.afterExecute(r, t); + } + finally { + if(threadDecorator != null) + threadDecorator.threadReleased(Thread.currentThread()); + } + } + + + public String toString() { + return getClass().getSimpleName(); + } + + /** + * Class which catches exceptions in run() - https://jira.jboss.org/jira/browse/JGRP-1062 + */ + static class RobustRunnable implements Runnable { + final Runnable command; + + public RobustRunnable(Runnable command) { + this.command=command; + } + + public void run() { + if(command != null) { + try { + command.run(); + } + catch(Throwable t) { + if(log.isErrorEnabled()) + log.error("exception executing task " + command + ": " + t); + } + } + } + } + + + private class TaskWrapper implements Runnable, Future { + private final Task task; + private volatile Future future; // cannot be null ! + private volatile boolean cancelled=false; + + + public TaskWrapper(Task task) { + this.task=task; + } + + public Future getFuture() { + return future; + } + + public void run() { + try { + if(cancelled) { + if(future != null) + future.cancel(true); + return; + } + if(future != null && future.isCancelled()) + return; + task.run(); + } + catch(Throwable t) { + log.error("failed running task " + task, t); + } + + if(cancelled) { + if(future != null) + future.cancel(true); + return; + } + if(future != null && future.isCancelled()) + return; + + doSchedule(); + } + + + public void doSchedule() { + long next_interval=task.nextInterval(); + if(next_interval <= 0) { + if(log.isTraceEnabled()) + log.trace("task will not get rescheduled as interval is " + next_interval); + } + else { + future=schedule(this, next_interval, TimeUnit.MILLISECONDS); + if(cancelled) + future.cancel(true); + } + } + + + public boolean cancel(boolean mayInterruptIfRunning) { + boolean retval=!isDone(); + cancelled=true; + if(future != null) + future.cancel(mayInterruptIfRunning); + return retval; + } + + public boolean isCancelled() { + return cancelled; + } + + public boolean isDone() { + return cancelled || (future == null || future.isDone()); + } + + public V get() throws InterruptedException, ExecutionException { + return null; + } + + public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return null; + } + + } + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/Digest.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/Digest.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/Digest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/Digest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,7 +1,7 @@ package org.jgroups.util; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.Address; import org.jgroups.Global; import org.jgroups.annotations.Immutable; @@ -25,7 +25,6 @@ * lost. Therefore we periodically gossip and include the last message seqno. Members who haven't seen * it (e.g. because msg was dropped) will request a retransmission. See DESIGN for details. * @author Bela Ban - * @version $Id: Digest.java,v 1.9 2008/01/22 10:44:33 belaban Exp $ */ public class Digest implements Externalizable, Streamable { @@ -34,7 +33,7 @@ protected final Map senders; protected static final Log log=LogFactory.getLog(Digest.class); - + private static final long serialVersionUID=6611464897656359215L; /** Used for externalization */ @@ -266,20 +265,36 @@ StringBuilder sb=new StringBuilder(); boolean first=true; if(senders.isEmpty()) return "[]"; - Map.Entry entry; - Address key; - Entry val; - for(Iterator> it=senders.entrySet().iterator(); it.hasNext();) { - entry=it.next(); - key=entry.getKey(); - val=entry.getValue(); - if(!first) { + for(Map.Entry entry: senders.entrySet()) { + Address key=entry.getKey(); + Entry val=entry.getValue(); + if(!first) sb.append(", "); - } - else { + else + first=false; + sb.append(key).append(": ").append('[').append(val.low_seqno).append(" : "); + sb.append(val.highest_delivered_seqno); + if(val.highest_received_seqno >= 0) + sb.append(" (").append(val.highest_received_seqno).append(")"); + sb.append("]"); + } + return sb.toString(); + } + + public String toStringSorted() { + StringBuilder sb=new StringBuilder(); + boolean first=true; + if(senders.isEmpty()) return "[]"; + + TreeMap copy=new TreeMap(senders); + for(Map.Entry entry: copy.entrySet()) { + Address key=entry.getKey(); + Entry val=entry.getValue(); + if(!first) + sb.append(", "); + else first=false; - } sb.append(key).append(": ").append('[').append(val.low_seqno).append(" : "); sb.append(val.highest_delivered_seqno); if(val.highest_received_seqno >= 0) @@ -293,21 +308,15 @@ public String printHighestDeliveredSeqnos() { StringBuilder sb=new StringBuilder("["); boolean first=true; - Map.Entry entry; - Address key; - Entry val; TreeMap copy=new TreeMap(senders); - for(Iterator> it=copy.entrySet().iterator(); it.hasNext();) { - entry=it.next(); - key=entry.getKey(); - val=entry.getValue(); - if(!first) { + for(Map.Entry entry: copy.entrySet()) { + Address key=entry.getKey(); + Entry val=entry.getValue(); + if(!first) sb.append(", "); - } - else { + else first=false; - } sb.append(key).append("#").append(val.highest_delivered_seqno); } sb.append(']'); @@ -318,17 +327,12 @@ public String printHighestReceivedSeqnos() { StringBuilder sb=new StringBuilder(); boolean first=true; - Map.Entry entry; - Address key; - Entry val; - for(Iterator> it=senders.entrySet().iterator(); it.hasNext();) { - entry=it.next(); - key=entry.getKey(); - val=entry.getValue(); - if(!first) { + for(Map.Entry entry: senders.entrySet()) { + Address key=entry.getKey(); + Entry val=entry.getValue(); + if(!first) sb.append(", "); - } else { sb.append('['); first=false; @@ -352,14 +356,9 @@ public void writeTo(DataOutputStream out) throws IOException { out.writeShort(senders.size()); - Map.Entry entry; - Address key; - Entry val; - for(Iterator> it=senders.entrySet().iterator(); it.hasNext();) { - entry=it.next(); - key=entry.getKey(); - val=entry.getValue(); - Util.writeAddress(key, out); + for(Map.Entry entry: senders.entrySet()) { + Entry val=entry.getValue(); + Util.writeAddress(entry.getKey(), out); out.writeLong(val.low_seqno); out.writeLong(val.highest_delivered_seqno); out.writeLong(val.highest_received_seqno); @@ -384,8 +383,7 @@ long retval=Global.SHORT_SIZE; // number of elements in 'senders' if(!senders.isEmpty()) { Address addr=senders.keySet().iterator().next(); - int len=addr.size() + - 2 * Global.BYTE_SIZE; // presence byte, IpAddress vs other address + int len=Util.size(addr); len+=Entry.SIZE; // 3 longs in one Entry retval+=len * senders.size(); } @@ -412,6 +410,7 @@ private long highest_delivered_seqno=0; // the highest delivered seqno, e.g. in 1,2,4,5,7 --> 2 private long highest_received_seqno=0; //the highest received seqno, e.g. in 1,2,4,5,7 --> 7 final static int SIZE=Global.LONG_SIZE * 3; + private static final long serialVersionUID=-4468945932249281704L; public Entry() { } @@ -454,6 +453,10 @@ return low_seqno == other.low_seqno && highest_delivered_seqno == other.highest_delivered_seqno && highest_received_seqno == other.highest_received_seqno; } + public int hashCode() { + return (int)(low_seqno + highest_delivered_seqno + highest_received_seqno); + } + public String toString() { return new StringBuilder("low=").append(low_seqno).append(", highest delivered=").append(highest_delivered_seqno). append(", highest received=").append(highest_received_seqno).toString(); @@ -471,7 +474,7 @@ highest_received_seqno=in.readLong(); } - public int size() { + public static int size() { return SIZE; } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/DirectExecutor.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/DirectExecutor.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/DirectExecutor.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/DirectExecutor.java 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ /** * @author Bela Ban - * @version $Id: DirectExecutor.java,v 1.5 2007/11/21 14:00:14 belaban Exp $ */ public class DirectExecutor implements Executor { public void execute(Runnable command) { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/ExposedByteArrayInputStream.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/ExposedByteArrayInputStream.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/ExposedByteArrayInputStream.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/ExposedByteArrayInputStream.java 2011-10-18 11:22:35.000000000 +0000 @@ -5,7 +5,6 @@ /** * @author Bela Ban - * @version $Id: ExposedByteArrayInputStream.java,v 1.2 2008/10/28 09:08:32 belaban Exp $ */ public class ExposedByteArrayInputStream extends ByteArrayInputStream { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/ExposedByteArrayOutputStream.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/ExposedByteArrayOutputStream.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/ExposedByteArrayOutputStream.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/ExposedByteArrayOutputStream.java 2011-10-18 11:22:35.000000000 +0000 @@ -10,7 +10,6 @@ * Extends ByteArrayOutputStream, but exposes the internal buffer. This way we don't need to call * toByteArray() which copies the internal buffer * @author Bela Ban - * @version $Id: ExposedByteArrayOutputStream.java,v 1.7 2008/10/28 14:29:02 belaban Exp $ */ public class ExposedByteArrayOutputStream extends ByteArrayOutputStream { @@ -36,6 +35,10 @@ return buf; } + public Buffer getBuffer() { + return new Buffer(buf, 0, count); + } + public int getCapacity() { return buf.length; } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/ExposedDataOutputStream.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/ExposedDataOutputStream.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/ExposedDataOutputStream.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/ExposedDataOutputStream.java 2011-10-18 11:22:35.000000000 +0000 @@ -6,7 +6,6 @@ /** * @author Bela Ban - * @version $Id: ExposedDataOutputStream.java,v 1.3 2008/10/28 08:50:15 belaban Exp $ */ public class ExposedDataOutputStream extends DataOutputStream { /** diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/FIFOMessageQueue.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/FIFOMessageQueue.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/FIFOMessageQueue.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/FIFOMessageQueue.java 2011-10-18 11:22:35.000000000 +0000 @@ -14,8 +14,9 @@ * Only when A1 is done will A2 be processed, same for B2: it will get processed when B1 is done. Thus, messages * for different services are processed concurrently; messages from the same service are processed FIFO. * @author Bela Ban - * @version $Id: FIFOMessageQueue.java,v 1.8 2007/06/29 10:57:40 belaban Exp $ + * @deprecated Will be removed together with the Multiplexer in 3.0 */ +@Deprecated public class FIFOMessageQueue { /** Used for consolidated takes */ final BlockingQueue queue=new LinkedBlockingQueue(); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/FixedSizeBitSet.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/FixedSizeBitSet.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/FixedSizeBitSet.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/FixedSizeBitSet.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,225 @@ +package org.jgroups.util; + + + +/** + * Class copied from {@link java.util.BitSet}. Changes are that the FixedSizeBitSet doesn't expand, so access to it + * doesn't need to be synchronized, plus we only need a few methods so most methods of the old class have been removed. + * @author Bela Ban + */ +public class FixedSizeBitSet { + /* + * BitSets are packed into arrays of "words." Currently a word is a long, which consists of 64 bits, requiring + * 6 address bits. The choice of word size is determined purely by performance concerns. + */ + private final static int ADDRESS_BITS_PER_WORD=6; + private final static int BITS_PER_WORD=1 << ADDRESS_BITS_PER_WORD; + + /* Used to shift left or right for a partial word mask */ + private static final long WORD_MASK=0xffffffffffffffffL; + + private final long[] words; + private final int size; + + + + + /** + * Creates a bit set whose initial size is the range 0 through + * size-1. All bits are initially false. + * @param size the initial size of the bit set (in bits). + * @throws NegativeArraySizeException if the specified initial size is negative + */ + public FixedSizeBitSet(int size) { + if(size < 0) // nbits can't be negative; size 0 is OK + throw new NegativeArraySizeException("size < 0: " + size); + this.size=size; + words=new long[wordIndex(size - 1) + 1]; + } + + + + + /** + * Sets the bit at the specified index to true. + * @param index a bit index. + * @throws IndexOutOfBoundsException if the specified index is negative. + */ + public void set(int index) { + if(index < 0 || index >= size) + throw new IndexOutOfBoundsException("index: " + index); + + int wordIndex=wordIndex(index); + words[wordIndex]|=(1L << index); // Restores invariants + } + + + + /** + * Sets the bit specified by the index to false. + * @param index the index of the bit to be cleared. + * @throws IndexOutOfBoundsException if the specified index is negative. + */ + public void clear(int index) { + if(index < 0 || index >= size) + throw new IndexOutOfBoundsException("index: " + index); + + int wordIndex=wordIndex(index); + words[wordIndex]&=~(1L << index); + } + + + + /** + * Returns the value of the bit with the specified index. The value + * is true if the bit with the index index + * is currently set in this bit set; otherwise, the result is false. + * @param index the bit index. + * @return the value of the bit with the specified index. + * @throws IndexOutOfBoundsException if the specified index is negative. + */ + public boolean get(int index) { + if(index < 0 || index >= size) + throw new IndexOutOfBoundsException("index: " + index); + + int wordIndex=wordIndex(index); + return (words[wordIndex] & (1L << index)) != 0; + } + + + + /** + * Returns the index of the first bit that is set to true that occurs on or after + * the specified starting index. If no such bit exists then -1 is returned. + *

        + * To iterate over the true bits in a BitSet, + * use the following loop: + *

        + *

        +     * for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i+1)) {
        +     *     // operate on index i here
        +     * }
        + * @param fromIndex the index to start checking from (inclusive). + * @return the index of the next set bit. + * @throws IndexOutOfBoundsException if the specified index is negative. + */ + public int nextSetBit(int fromIndex) { + if(fromIndex < 0) + throw new IndexOutOfBoundsException("fromIndex: " + fromIndex); + if(fromIndex >= size) + return -1; + + int u=wordIndex(fromIndex); + long word=words[u] & (WORD_MASK << fromIndex); + + while(true) { + if(word != 0) + return (u * BITS_PER_WORD) + Long.numberOfTrailingZeros(word); + if(++u == words.length) + return -1; + word=words[u]; + } + } + + /** + * Returns the index of the first bit that is set to false + * that occurs on or after the specified starting index. + * @param fromIndex the index to start checking from (inclusive). + * @return the index of the next clear bit. + * @throws IndexOutOfBoundsException if the specified index is negative. + */ + public int nextClearBit(int fromIndex) { + // Neither spec nor implementation handle bitsets of maximal length. + // See 4816253. + if(fromIndex < 0) + throw new IndexOutOfBoundsException("fromIndex: " + fromIndex); + if(fromIndex >= size) + return -1; + + int u=wordIndex(fromIndex); + if(u >= words.length) + return fromIndex; + + long word=~words[u] & (WORD_MASK << fromIndex); + + while(true) { + if(word != 0) + return (u * BITS_PER_WORD) + Long.numberOfTrailingZeros(word); + if(++u == words.length) + return -1; + word=~words[u]; + } + } + + + + + + /** + * Returns the number of bits set to true in this bit set + * @return the number of bits set to true in this bit set + */ + public int cardinality() { + int sum=0; + for(int i=0; i < words.length; i++) + sum+=Long.bitCount(words[i]); + return sum; + } + + + + public int size() {return size;} + + + + + /** + * Returns a string representation of this bit set. For every index + * for which this BitSet contains a bit in the set + * state, the decimal representation of that index is included in + * the result. Such indices are listed in order from lowest to + * highest, separated by ", " (a comma and a space) and + * surrounded by braces, resulting in the usual mathematical + * notation for a set of integers.

        + * Overrides the toString method of Object. + *

        Example: + *

        +     * BitSet drPepper = new BitSet();
        + * Now drPepper.toString() returns "{}".

        + *

        +     * drPepper.set(2);
        + * Now drPepper.toString() returns "{2}".

        + *

        +     * drPepper.set(4);
        +     * drPepper.set(10);
        + * Now drPepper.toString() returns "{2, 4, 10}". + * @return a string representation of this bit set. + */ + public String toString() { + int numBits=(words.length > 128)? + cardinality() : words.length * BITS_PER_WORD; + StringBuilder b=new StringBuilder(6 * numBits + 2); + b.append('{'); + + int i=nextSetBit(0); + if(i != -1) { + b.append(i); + for(i=nextSetBit(i + 1); i >= 0; i=nextSetBit(i + 1)) { + int endOfRun=nextClearBit(i); + do { + b.append(", ").append(i); + } + while(++i < endOfRun); + } + } + + b.append('}'); + return b.toString(); + } + + + private static int wordIndex(int bitIndex) { + return bitIndex >> ADDRESS_BITS_PER_WORD; + } + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/FutureListener.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/FutureListener.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/FutureListener.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/FutureListener.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,18 @@ +package org.jgroups.util; + +import java.util.concurrent.Future; + +/** + * A listener that is called back when a future is done. FutureListener instances are attached to {@link + * NotifyingFuture}s by passing them in to {@link NotifyingFuture#setListener(FutureListener)} + *

        + * Note that the {@link #futureDone(Future)} callback is invoked when the future completes, regardless of how the future + * completes (i.e., normally, due to an exception, or cancelled}. As such, implementations should check the future + * passed in by calling future.get(). + * + * @author Manik Surtani + * @since 2.9 + */ +public interface FutureListener { + void futureDone(Future future); +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/GetNetworkInterfaces.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/GetNetworkInterfaces.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/GetNetworkInterfaces.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/GetNetworkInterfaces.java 2011-10-18 11:22:35.000000000 +0000 @@ -9,7 +9,6 @@ * Lists all network interfaces on a system * @author Bela Ban Dec 18 * @author 2003 - * @version $Id: GetNetworkInterfaces.java,v 1.2 2008/03/14 03:30:16 vlada Exp $ */ public class GetNetworkInterfaces { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/HashedTimingWheel.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/HashedTimingWheel.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/HashedTimingWheel.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/HashedTimingWheel.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,581 @@ +package org.jgroups.util; + + +import org.jgroups.Global; +import org.jgroups.annotations.Experimental; +import org.jgroups.annotations.Unsupported; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.*; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + + +/** + * Implementation of {@link TimeScheduler}. Uses a hashed timing wheel [1]. + * + * [1] http://www.cse.wustl.edu/~cdgill/courses/cs6874/TimingWheels.ppt + * + * @author Bela Ban + */ +@Experimental @Unsupported +public class HashedTimingWheel implements TimeScheduler, Runnable { + private final ThreadManagerThreadPoolExecutor pool; + + private Thread runner=null; + + private final Lock lock=new ReentrantLock(); + + protected volatile boolean running=false; + + protected static final Log log=LogFactory.getLog(HashedTimingWheel.class); + + protected ThreadDecorator threadDecorator=null; + + protected ThreadFactory timer_thread_factory=null; + + protected int wheel_size=200; // number of ticks on the timing wheel + + protected long tick_time=50L; // number of milliseconds a tick has + + protected final long ROTATION_TIME;// time for 1 lap + + protected final List[] wheel; + + protected int wheel_position=0; // current position of the wheel, run() advances it by one (every TICK_TIME ms) + + + /** + * Create a scheduler that executes tasks in dynamically adjustable intervals + */ + @SuppressWarnings("unchecked") + public HashedTimingWheel() { + ROTATION_TIME=wheel_size * tick_time; + wheel=new List[wheel_size]; + pool=new ThreadManagerThreadPoolExecutor(4, 10, + 5000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(5000), + Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); + init(); + } + + + @SuppressWarnings("unchecked") + public HashedTimingWheel(ThreadFactory factory, int min_threads, int max_threads, long keep_alive_time, int max_queue_size, + int wheel_size, long tick_time) { + this.wheel_size=wheel_size; + this.tick_time=tick_time; + ROTATION_TIME=wheel_size * tick_time; + wheel=new List[this.wheel_size]; + timer_thread_factory=factory; + pool=new ThreadManagerThreadPoolExecutor(min_threads, max_threads,keep_alive_time, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue(max_queue_size), + factory, new ThreadPoolExecutor.CallerRunsPolicy()); + init(); + } + + + @SuppressWarnings("unchecked") + public HashedTimingWheel(int corePoolSize) { + ROTATION_TIME=wheel_size * tick_time; + wheel=(List[])new List[wheel_size]; + pool=new ThreadManagerThreadPoolExecutor(corePoolSize, corePoolSize * 2, + 5000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(5000), + Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); + init(); + } + + + + public ThreadDecorator getThreadDecorator() { + return threadDecorator; + } + + public void setThreadDecorator(ThreadDecorator threadDecorator) { + this.threadDecorator=threadDecorator; + pool.setThreadDecorator(threadDecorator); + } + + public void setThreadFactory(ThreadFactory factory) { + pool.setThreadFactory(factory); + } + + public int getMinThreads() { + return pool.getCorePoolSize(); + } + + public void setMinThreads(int size) { + pool.setCorePoolSize(size); + } + + public int getMaxThreads() { + return pool.getMaximumPoolSize(); + } + + public void setMaxThreads(int size) { + pool.setMaximumPoolSize(size); + } + + public long getKeepAliveTime() { + return pool.getKeepAliveTime(TimeUnit.MILLISECONDS); + } + + public void setKeepAliveTime(long time) { + pool.setKeepAliveTime(time, TimeUnit.MILLISECONDS); + } + + public int getCurrentThreads() { + return pool.getPoolSize(); + } + + public int getQueueSize() { + return pool.getQueue().size(); + } + + + public String dumpTimerTasks() { + StringBuilder sb=new StringBuilder(); + + lock.lock(); + try { + for(List list: wheel) { + if(!list.isEmpty()) { + sb.append(list).append("\n"); + } + } + } + finally { + lock.unlock(); + } + + return sb.toString(); + } + + + + + public void execute(Runnable task) { + schedule(task, 0, TimeUnit.MILLISECONDS); + } + + + public Future schedule(Runnable work, long delay, TimeUnit unit) { + if(work == null) + return null; + + MyTask retval=null; + long time=TimeUnit.MILLISECONDS.convert(delay, unit); // execution time + + lock.lock(); + try { + int num_ticks=(int)Math.max(1, ((time % ROTATION_TIME) / tick_time)); + int position=(wheel_position + num_ticks) % wheel_size; + int rounds=(int)(time / ROTATION_TIME); + List list=wheel[position]; + retval=new MyTask(work, rounds); + list.add(retval); + } + finally { + lock.unlock(); + } + + return retval; + } + + + + public Future scheduleWithFixedDelay(Runnable task, long initial_delay, long delay, TimeUnit unit) { + if(task == null) + throw new NullPointerException(); + if (isShutdown()) + return null; + RecurringTask wrapper=new FixedIntervalTask(task, delay); + wrapper.doSchedule(initial_delay); + return wrapper; + } + + + public Future scheduleAtFixedRate(Runnable task, long initial_delay, long delay, TimeUnit unit) { + if(task == null) + throw new NullPointerException(); + if (isShutdown()) + return null; + RecurringTask wrapper=new FixedRateTask(task, delay); + wrapper.doSchedule(initial_delay); + return wrapper; + } + + + /** + * Schedule a task for execution at varying intervals. After execution, the task will get rescheduled after + * {@link org.jgroups.util.HashedTimingWheel.Task#nextInterval()} milliseconds. The task is neve done until nextInterval() + * return a value <= 0 or the task is cancelled. + * @param task the task to execute + * Task is rescheduled relative to the last time it actually started execution

        + * false:
        Task is scheduled relative to its last execution schedule. This has the effect + * that the time between two consecutive executions of the task remains the same.

        + * Note that relative is always true; we always schedule the next execution relative to the last *actual* + */ + public Future scheduleWithDynamicInterval(Task task) { + if(task == null) + throw new NullPointerException(); + if (isShutdown()) + return null; + RecurringTask task_wrapper=new DynamicIntervalTask(task); + task_wrapper.doSchedule(); // calls schedule() in ScheduledThreadPoolExecutor + return task_wrapper; + } + + + + + /** + * Returns the number of tasks currently in the timer + * @return The number of tasks currently in the timer + */ + public int size() { + int retval=0; + + lock.lock(); + try { + for(List list: wheel) + retval+=list.size(); + return retval; + } + finally { + lock.unlock(); + } + } + + + public String toString() { + return getClass().getSimpleName(); + } + + + /** + * Stops the timer, cancelling all tasks + * + * @throws InterruptedException if interrupted while waiting for thread to return + */ + public void stop() { + stopRunner(); + + List remaining_tasks=pool.shutdownNow(); + for(Runnable task: remaining_tasks) { + if(task instanceof Future) { + Future future=(Future)task; + future.cancel(true); + } + } + pool.getQueue().clear(); + try { + pool.awaitTermination(Global.THREADPOOL_SHUTDOWN_WAIT_TIME, TimeUnit.MILLISECONDS); + } + catch(InterruptedException e) { + } + } + + + public boolean isShutdown() { + return pool.isShutdown(); + } + + + public void run() { + final long base_time=System.currentTimeMillis(); + long next_time, sleep_time; + long cnt=0; + + while(running) { + try { + _run(); + next_time=base_time + (++cnt * tick_time); + sleep_time=Math.max(0, next_time - System.currentTimeMillis()); + Util.sleep(sleep_time); + } + catch(Throwable t) { + log.error("failed executing tasks(s)", t); + } + } + } + + + protected void _run() { + lock.lock(); + try { + wheel_position=(wheel_position +1) % wheel_size; + List list=wheel[wheel_position]; + if(list.isEmpty()) + return; + for(Iterator it=list.iterator(); it.hasNext();) { + MyTask tmp=it.next(); + if(tmp.getAndDecrementRound() <= 0) { + try { + pool.execute(tmp); + } + catch(Throwable t) { + log.error("failure submitting task to thread pool", t); + } + it.remove(); + } + } + } + finally { + lock.unlock(); + } + } + + + protected void init() { + for(int i=0; i < wheel.length; i++) + wheel[i]=new LinkedList(); + if(threadDecorator != null) + pool.setThreadDecorator(threadDecorator); + // pool.allowCoreThreadTimeOut(true); + startRunner(); + } + + + + protected void startRunner() { + running=true; + runner=timer_thread_factory != null? timer_thread_factory.newThread(this, "Timer runner") : new Thread(this, "Timer runner"); + runner.start(); + } + + protected void stopRunner() { + lock.lock(); + try { + running=false; + for(List list: wheel) { + if(!list.isEmpty()) { + for(MyTask task: list) + task.cancel(true); + list.clear(); + } + } + } + finally { + lock.unlock(); + } + } + + + + + /** + * Simple task wrapper, always executed by at most 1 thread. + */ + protected static class MyTask implements Future, Runnable { + protected final Runnable task; + protected volatile boolean cancelled=false; + protected volatile boolean done=false; + protected MyTask next; + protected int round; + + public MyTask(Runnable task, int round) { + this.task=task; + this.round=round; + } + + public int getRound() { + return round; + } + + public int getAndDecrementRound() { + return round--; + } + + public void setRound(int round) { + this.round=round; + } + + public boolean cancel(boolean mayInterruptIfRunning) { + boolean retval=!isDone(); + cancelled=true; + return retval; + } + + public boolean isCancelled() { + return cancelled; + } + + public boolean isDone() { + return done || cancelled; + } + + public Object get() throws InterruptedException, ExecutionException { + return null; + } + + public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return null; + } + + public void run() { + if(isDone()) + return; + try { + task.run(); + } + catch(Throwable t) { + log.error("failed executing task " + task, t); + } + finally { + done=true; + } + } + + public String toString() { + return task.toString(); + } + } + + + /** + * Task which executes multiple times. An instance of this class wraps the real task and intercepts run(): when + * called, it forwards the call to task.run() and then schedules another execution (until cancelled). The + * {@link #nextInterval()} method determines the time to wait until the next execution. + * @param + */ + private abstract class RecurringTask implements Runnable, Future { + protected final Runnable task; + protected volatile Future future; // cannot be null ! + protected volatile boolean cancelled=false; + + + public RecurringTask(Runnable task) { + this.task=task; + } + + /** + * The time to wait until the next execution + * @return Number of milliseconds to wait until the next execution is scheduled + */ + protected abstract long nextInterval(); + + protected boolean rescheduleOnZeroDelay() {return false;} + + public void doSchedule() { + long next_interval=nextInterval(); + if(next_interval <= 0 && !rescheduleOnZeroDelay()) { + if(log.isTraceEnabled()) + log.trace("task will not get rescheduled as interval is " + next_interval); + return; + } + + future=schedule(this, next_interval, TimeUnit.MILLISECONDS); + if(cancelled) + future.cancel(true); + } + + public void doSchedule(long next_interval) { + future=schedule(this, next_interval, TimeUnit.MILLISECONDS); + if(cancelled) + future.cancel(true); + } + + + public void run() { + if(cancelled) { + if(future != null) + future.cancel(true); + return; + } + + try { + task.run(); + } + catch(Throwable t) { + log.error("failed running task " + task, t); + } + if(!cancelled) + doSchedule(); + } + + + public boolean cancel(boolean mayInterruptIfRunning) { + boolean retval=!isDone(); + cancelled=true; + if(future != null) + future.cancel(mayInterruptIfRunning); + return retval; + } + + public boolean isCancelled() { + return cancelled; + } + + public boolean isDone() { + return cancelled || (future == null || future.isDone()); + } + + public V get() throws InterruptedException, ExecutionException { + return null; + } + + public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return null; + } + + public String toString() { + StringBuilder sb=new StringBuilder(); + sb.append(getClass().getSimpleName() + ": task=" + task + ", cancelled=" + isCancelled()); + return sb.toString(); + } + } + + + private class FixedIntervalTask extends RecurringTask { + final long interval; + + public FixedIntervalTask(Runnable task, long interval) { + super(task); + this.interval=interval; + } + + protected long nextInterval() { + return interval; + } + } + + private class FixedRateTask extends RecurringTask { + final long interval; + final long first_execution; + int num_executions=0; + + public FixedRateTask(Runnable task, long interval) { + super(task); + this.interval=interval; + this.first_execution=System.currentTimeMillis(); + } + + protected long nextInterval() { + long target_time=first_execution + (interval * ++num_executions); + return target_time - System.currentTimeMillis(); + } + + protected boolean rescheduleOnZeroDelay() {return true;} + } + + + private class DynamicIntervalTask extends RecurringTask { + + public DynamicIntervalTask(Task task) { + super(task); + } + + protected long nextInterval() { + if(task instanceof Task) + return ((Task)task).nextInterval(); + return 0; + } + } + + + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/Headers.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/Headers.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/Headers.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/Headers.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,15 +2,21 @@ import org.jgroups.Global; import org.jgroups.Header; +import org.jgroups.conf.ClassConfigurator; import java.util.HashMap; import java.util.Map; /** * Open addressing based implementation of a hashmap (not supporting the Map interface though) for message - * headers. The keys are strings and the values Headers, and they're stored in an array in the format - * key-1 | header-1 | key-2 | header-2. The array is populated from left to right, so any null slots can terminate - * an interation, or signal empty slots. + * headers. The keys are shorts (IDs) and the values Headers, and they're stored in 2 arrays: an ID array and a headers + * array. The indices of the IDs array corespond with the headers array, e.g. + *

        + * IDs:      id-1  | id-2  | id-3  | ... | id-n |
        + * Headers:  hdr-1 | hdr-2 | hdr-3 | ... | hdr-n |
        + * 
        + * + * The arrays are populated from left to right, and any 0 slots in 'ids' can terminate an interation, or signal empty slots. *
        * It is assumed that we only have a few headers, 3-4 on average. Note that getting a header for a given key and * putting a new key/header are operations with O(n) cost, so this implementation is not recommended for @@ -18,48 +24,54 @@ *
        * This class is not synchronized * @author Bela Ban - * @version $Id: Headers.java,v 1.14 2008/07/31 12:53:50 belaban Exp $ */ public class Headers { - /** Used to store strings and headers, e.g: name-1 | header-1 | name-2 | header-2 | null | null | name-3 | header-3 */ - private Object[] data; + private short[] ids; + private Header[] hdrs; /** Add space for 3 new elements when resizing */ - private static final int RESIZE_INCR=6; + private static final int RESIZE_INCR=3; - public Headers(int initial_capacity) { - data=new Object[initial_capacity << 1]; + public Headers(int capacity) { + ids=new short[capacity]; + hdrs=new Header[capacity]; } - public Headers(Headers hdrs) { - data=new Object[hdrs.data.length]; - System.arraycopy(hdrs.data, 0, this.data, 0, hdrs.data.length); + public Headers(Headers other) { + this(other.ids.length); + System.arraycopy(other.ids, 0, this.ids, 0, other.ids.length); + System.arraycopy(other.hdrs, 0, this.hdrs, 0, other.hdrs.length); } - public Object[] getRawData() { - return data; + public short[] getRawIDs() { + return ids; + } + + public Header[] getRawHeaders() { + return hdrs; } /** - * Returns the header associated with key - * @param key + * Returns the header associated with an ID + * @param id The ID * @return */ - public Header getHeader(String key) { - for(int i=0; i < data.length; i+=2) { - if(data[i] == null) + public Header getHeader(short id) { + for(int i=0; i < ids.length; i++) { + short current_id=ids[i]; + if(current_id == 0) return null; - if(data[i].equals(key)) - return (Header)data[i+1]; + if(current_id == id) + return hdrs[i]; } return null; } - public Map getHeaders() { - Map retval=new HashMap(data.length / 2); - for(int i=0; i < data.length; i+=2) { - if(data[i] != null) - retval.put((String)data[i], (Header)data[i+1]); + public Map getHeaders() { + Map retval=new HashMap(ids.length); + for(int i=0; i < ids.length; i++) { + if(ids[i] > 0) + retval.put(ids[i], hdrs[i]); else break; } @@ -69,13 +81,15 @@ public String printHeaders() { StringBuilder sb=new StringBuilder(); boolean first=true; - for(int i=0; i < data.length; i+=2) { - if(data[i] != null) { + for(int i=0; i < ids.length; i++) { + if(ids[i] > 0) { if(first) first=false; else sb.append(", "); - sb.append(data[i]).append(": ").append(data[i+1]); + Class clazz=ClassConfigurator.getProtocol(ids[i]); + String name=clazz != null? clazz.getSimpleName() : Short.toString(ids[i]); + sb.append(name).append(": ").append(hdrs[i]); } else break; @@ -85,8 +99,8 @@ /** Puts a header given a key into the hashmap. Overwrites potential existing entry. */ - public void putHeader(String key, Header hdr) { - _putHeader(key, hdr, 0, true); + public void putHeader(short id, Header hdr) { + _putHeader(id, hdr, 0, true); } @@ -94,27 +108,27 @@ /** * Puts a header given a key into the map, only if the key doesn't exist yet - * @param key + * @param id * @param hdr - * @return the previous value associated with the specified key, or - * null if there was no mapping for the key. + * @return the previous value associated with the specified id, or + * null if there was no mapping for the id. * (A null return can also indicate that the map - * previously associated null with the key, + * previously associated null with the id, * if the implementation supports null values.) */ - public Header putHeaderIfAbsent(String key, Header hdr) { - return _putHeader(key, hdr, 0, false); + public Header putHeaderIfAbsent(short id, Header hdr) { + return _putHeader(id, hdr, 0, false); } /** * - * @param key - * @return the header assoaicted with key + * @param id + * @return the header associated with key * @deprecated Use getHeader() instead. The issue with removing a header is described in * http://jira.jboss.com/jira/browse/JGRP-393 */ - public Header removeHeader(String key) { - return getHeader(key); + public Header removeHeader(short id) { + return getHeader(id); } public Headers copy() { @@ -123,11 +137,10 @@ public int marshalledSize() { int retval=0; - for(int i=0; i < data.length; i+=2) { - if(data[i] != null) { - retval+=((String)data[i]).length() +2; - retval+=(Global.SHORT_SIZE *2); // 2 for magic number, 2 for size (short) - retval+=((Header)data[i+1]).size(); + for(int i=0; i < ids.length; i++) { + if(ids[i] > 0) { + retval+=Global.SHORT_SIZE *2; // for protocol ID and magic number + retval+=hdrs[i].size(); } else break; @@ -137,8 +150,8 @@ public int size() { int retval=0; - for(int i=0; i < data.length; i+=2) { - if(data[i] != null) + for(int i=0; i < ids.length; i++) { + if(ids[i] > 0) retval++; else break; @@ -147,14 +160,14 @@ } public int capacity() { - return data.length / 2; + return ids.length; } public String printObjectHeaders() { StringBuilder sb=new StringBuilder(); - for(int i=0; i < data.length; i+=2) { - if(data[i] != null) - sb.append(data[i]).append(": ").append(data[i+1]).append('\n'); + for(int i=0; i < ids.length; i++) { + if(ids[i] > 0) + sb.append(ids[i]).append(": ").append(hdrs[i]).append('\n'); else break; } @@ -170,35 +183,41 @@ * Increases the capacity of the array and copies the contents of the old into the new array */ private void resize() { - int new_size=data.length + RESIZE_INCR; - Object[] new_data=new Object[new_size]; - System.arraycopy(data, 0, new_data, 0, data.length); - data=new_data; + int new_capacity=ids.length + RESIZE_INCR; + + short[] new_ids=new short[new_capacity]; + Header[] new_hdrs=new Header[new_capacity]; + + System.arraycopy(ids, 0, new_ids, 0, ids.length); + System.arraycopy(hdrs, 0, new_hdrs, 0, hdrs.length); + + ids=new_ids; + hdrs=new_hdrs; } - private Header _putHeader(String key, Header hdr, int start_index, boolean replace_if_present) { + private Header _putHeader(short id, Header hdr, int start_index, boolean replace_if_present) { int i=start_index; - while(i < data.length) { - if(data[i] == null) { - data[i]=key; - data[i+1]=hdr; + while(i < ids.length) { + if(ids[i] == 0) { + ids[i]=id; + hdrs[i]=hdr; return null; } - if(data[i].equals(key)) { - Header retval=(Header)data[i+1]; + if(ids[i] == id) { + Header retval=hdrs[i]; if(replace_if_present) { - data[i+1]=hdr; + hdrs[i]=hdr; } return retval; } - i+=2; - if(i >= data.length) { + i++; + if(i >= ids.length) { resize(); } } - throw new IllegalStateException("unable to add element " + key + ", index=" + i); // we should never come here + throw new IllegalStateException("unable to add element " + id + ", index=" + i); // we should never come here } -} +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/ImmutableReference.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/ImmutableReference.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/ImmutableReference.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/ImmutableReference.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,54 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2010, Red Hat, Inc. and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This 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 software 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 software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +package org.jgroups.util; + +/** + * Simple class that holds an immutable reference to another object (or to + * null). + * + * @author Brian Stansberry + * + */ +public class ImmutableReference { + + private final T referent; + + /** + * Create a new ImmutableReference. + * + * @param referent the object to refer to, or null + */ + public ImmutableReference(T referent) { + this.referent = referent; + } + + /** + * Gets the wrapped object, if there is one. + * + * @return the object passed to the constructor, or null if + * null was passed to the constructor + */ + public T get() { + return referent; + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/JUnitXMLReporter.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/JUnitXMLReporter.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/JUnitXMLReporter.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/JUnitXMLReporter.java 2011-10-18 11:22:35.000000000 +0000 @@ -19,7 +19,6 @@ * Copied from TestNG (www.testng.org) and modified * * @author Bela Ban - * @version $Id: JUnitXMLReporter.java,v 1.14 2008/11/12 16:01:21 rachmatowicz Exp $ */ public class JUnitXMLReporter extends TestListenerAdapter implements IInvokedMethodListener { private String output_dir=null; @@ -36,15 +35,15 @@ private PrintStream old_stdout=System.out; private PrintStream old_stderr=System.err; - private final ConcurrentMap> classes=new ConcurrentHashMap>(); + private final ConcurrentMap,List> classes=new ConcurrentHashMap,List>(); /** Map to keep systemout and systemerr associated with a class */ - private final ConcurrentMap> outputs=new ConcurrentHashMap>(); + private final ConcurrentMap,Tuple> outputs=new ConcurrentHashMap,Tuple>(); - public static InheritableThreadLocal local=new InheritableThreadLocal(); + public static InheritableThreadLocal> local=new InheritableThreadLocal>(); class MyOutput extends PrintStream { - final int type; // 1 == stdout, 2 == stderr + final int type; public MyOutput(String fileName,int type) throws FileNotFoundException { super(fileName); @@ -91,9 +90,8 @@ } private synchronized void append(String x, boolean newline) { - Class clazz=local.get(); + Class clazz=local.get(); if(clazz != null) { - // System.err.println("PRINT [" + Thread.currentThread() + "]: " + clazz.getName() + ": " + x); Tuple tuple=outputs.get(clazz); if(tuple != null) { StringBuffer sb=type == 1? tuple.getVal1() : tuple.getVal2(); @@ -114,7 +112,7 @@ /** Invoked before any method (configuration or test) is invoked */ public void beforeInvocation(IInvokedMethod method, ITestResult tr) { - Class real_class=tr.getTestClass().getRealClass(); + Class real_class=tr.getTestClass().getRealClass(); local.set(real_class); @@ -125,8 +123,6 @@ } outputs.putIfAbsent(real_class, new Tuple(new StringBuffer(), new StringBuffer())) ; - - // print(old_stdout, "before OK: ", real_class.getName(), tr.getName()); } /** Invoked after any method (configuration or test) is invoked */ @@ -136,18 +132,18 @@ /* Moved code from onTestStart() to beforeInvocation() to avoid output leaks (JGRP-850) */ public void onTestStart(ITestResult result) { - // old_stdout.println(Thread.currentThread() + " running " + real_class.getName() + "." + result.getName() + "()"); + } /** Invoked each time a test succeeds */ public void onTestSuccess(ITestResult tr) { - Class real_class=tr.getTestClass().getRealClass(); + Class real_class=tr.getTestClass().getRealClass(); addTest(real_class, tr); print(old_stdout, "OK: ", real_class.getName(), tr.getName()); } public void onTestFailedButWithinSuccessPercentage(ITestResult tr) { - Class real_class=tr.getTestClass().getRealClass(); + Class real_class=tr.getTestClass().getRealClass(); addTest(tr.getTestClass().getRealClass(), tr); print(old_stdout, "OK: ", real_class.getName(), tr.getName()); } @@ -156,7 +152,7 @@ * Invoked each time a test fails. */ public void onTestFailure(ITestResult tr) { - Class real_class=tr.getTestClass().getRealClass(); + Class real_class=tr.getTestClass().getRealClass(); addTest(tr.getTestClass().getRealClass(), tr); print(old_stderr, "FAIL: ", real_class.getName(), tr.getName()); } @@ -165,7 +161,7 @@ * Invoked each time a test is skipped. */ public void onTestSkipped(ITestResult tr) { - Class real_class=tr.getTestClass().getRealClass(); + Class real_class=tr.getTestClass().getRealClass(); addTest(tr.getTestClass().getRealClass(), tr); print(old_stdout, "SKIP: ", real_class.getName(), tr.getName()); } @@ -178,10 +174,9 @@ + "." + method_name + "()"); - // out.println(msg + classname + "." + method_name + "()"); } - private void addTest(Class clazz, ITestResult result) { + private void addTest(Class clazz, ITestResult result) { List results=classes.get(clazz); if(results == null) { results=new LinkedList(); @@ -248,34 +243,12 @@ public void onFinish(ITestContext context) { System.setOut(old_stdout); System.setErr(old_stderr); - - /* - * Not needed since we dump test output files as soon as all methods in a test class complete - * - * - * try { - generateReport(); - } - catch(IOException e) { - e.printStackTrace(); - }*/ - } - - /** - * generate the XML report given what we know from all the test results - */ - protected void generateReport() throws IOException { - for(Map.Entry> entry:classes.entrySet()) { - Class clazz=entry.getKey(); - List results=entry.getValue(); - generateReport(clazz, results); - } } /** * generate the XML report given what we know from all the test results */ - protected void generateReport(Class clazz, List results) throws IOException { + protected void generateReport(Class clazz, List results) throws IOException { int num_failures=getFailures(results); int num_skips=getSkips(results); @@ -381,9 +354,9 @@ FileWriter out) throws IOException { Test annotation=method.getAnnotation(Test.class); if(annotation != null && ex != null) { - Class[] expected_exceptions=annotation.expectedExceptions(); + Class[] expected_exceptions=annotation.expectedExceptions(); for(int i=0;i < expected_exceptions.length;i++) { - Class expected_exception=expected_exceptions[i]; + Class expected_exception=expected_exceptions[i]; if(expected_exception.equals(ex.getClass())) { return; } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/LazyThreadFactory.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/LazyThreadFactory.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/LazyThreadFactory.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/LazyThreadFactory.java 2011-10-18 11:22:35.000000000 +0000 @@ -8,7 +8,6 @@ /** * Lazily names threads: whenever the address or cluster name is changed, all threads are renamed * @author Bela Ban - * @version $Id: LazyThreadFactory.java,v 1.2 2008/06/17 15:31:35 belaban Exp $ */ public class LazyThreadFactory extends DefaultThreadFactory { private Collection> threads=new ConcurrentLinkedQueue>(); diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/MarshallerPool.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/MarshallerPool.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/MarshallerPool.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/MarshallerPool.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,74 @@ +package org.jgroups.util; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Provides a pool of output streams so we can do lock striping and have faster marshalling this way. + * @author Bela Ban + */ +@Deprecated +public class MarshallerPool { + final int pool_size; // number of pools + final int INITIAL_SIZE; // initial size of each pool + + final ExposedByteArrayOutputStream[] outs; + final ExposedDataOutputStream[] outputs; + final Lock[] locks; + + public MarshallerPool(int pool_size, int initial_size) { + this.pool_size=pool_size; + INITIAL_SIZE=initial_size; + + outs=new ExposedByteArrayOutputStream[pool_size]; + for(int i=0; i < outs.length; i++) + outs[i]=new ExposedByteArrayOutputStream(INITIAL_SIZE); + + outputs=new ExposedDataOutputStream[pool_size]; + for(int i=0; i < outputs.length; i++) + outputs[i]=new ExposedDataOutputStream(outs[i]); + + locks=new Lock[pool_size]; + for(int i=0; i < locks.length; i++) + locks[i]=new ReentrantLock(); + } + + + /** + * Returns a random output stream. To use it, the lock needs to be acquired. + * When done, it also needs to be released again. + * @return + */ + public Triple getOutputStream() { + int index=(int)Util.random(pool_size) -1; + return new Triple(locks[index], outs[index], outputs[index]); + } + + + public int[] getCapacities() { + int[] retval=new int[pool_size]; + for(int i=0; i < outs.length; i++) + retval[i]=outs[i].getCapacity(); + return retval; + } + + /** Closes all output streams. This releases the memory held by them */ + public void close() { + for(int i=0; i < pool_size; i++) { + try { + locks[i].tryLock(2000, TimeUnit.MILLISECONDS); + Util.close(outputs[i]); + } + catch(InterruptedException e) { + Thread.currentThread().interrupt(); + } + finally { + if(((ReentrantLock)locks[i]).isHeldByCurrentThread()) + locks[i].unlock(); + } + } + } + + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/MergeId.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/MergeId.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/MergeId.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/MergeId.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,57 @@ +package org.jgroups.util; + +import org.jgroups.Address; +import org.jgroups.Global; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.DataInputStream; + +/** ID to uniquely identify a merge + * @author Bela Ban + */ +public class MergeId implements Streamable { + private Address initiator; // must be non-null + private int id; + private static int LAST_ID=1; + + public MergeId() {} + + private MergeId(Address initiator, int id) { + this.initiator=initiator; + this.id=id; + } + + public synchronized static MergeId create(Address addr) { + int id=LAST_ID++; + if(addr == null) + throw new IllegalArgumentException("initiator has to be non null"); + return new MergeId(addr, id); + } + + public boolean equals(Object obj) { + return obj instanceof MergeId && initiator.equals(((MergeId)obj).initiator) && id == ((MergeId)obj).id; + } + + public int hashCode() { + return initiator.hashCode() + id; + } + + public int size() { + return Util.size(initiator) + Global.INT_SIZE; + } + + public void writeTo(DataOutputStream out) throws IOException { + Util.writeAddress(initiator, out); + out.writeInt(id); + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + initiator=Util.readAddress(in); + id=in.readInt(); + } + + public String toString() { + return initiator + "::" + id; + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/Metronome.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/Metronome.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/Metronome.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/Metronome.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,114 @@ +package org.jgroups.util; + +import java.util.Set; +import java.util.HashSet; +import java.util.Arrays; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.Condition; + +/** + * @author Bela Ban + */ +public class Metronome implements Runnable { + private final Set threads=new HashSet(); + private int tick=0; + private volatile Thread worker=null; + private int interval=10; + private final Lock lock=new ReentrantLock(); + private final Condition cond=lock.newCondition(); + + + public Metronome(int interval) { + this.interval=interval; + } + + public void add(final Thread ... thread) { + synchronized(threads) { + threads.addAll(Arrays.asList(thread)); + if(worker == null || !worker.isAlive()) { + worker=new Thread(this, "MetronomeThread"); + worker.setDaemon(true); + worker.start(); + } + } + } + + public void remove(final Thread thread) { + synchronized(threads) { + threads.remove(thread); + } + } + + public int getTick() { + lock.lock(); + try { + return tick; + } + finally { + lock.unlock(); + } + } + + public void waitFor(int tick) { + add(Thread.currentThread()); + while(true) { + lock.lock(); + try { + if(tick > this.tick) { + try { + cond.await(); + } + catch(InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + else + return; + } + finally { + lock.unlock(); + } + } + } + + public void stop() { + worker=null; + synchronized(threads) { + threads.clear(); + } + } + + + public void run() { + while(worker != null && Thread.currentThread().equals(worker)) { + boolean all_blocked=true; + synchronized(threads) { + if(threads.isEmpty()) + break; + + for(Thread tmp: threads) { + Thread.State state=tmp.getState(); + if(state != Thread.State.WAITING && state != Thread.State.BLOCKED) { + all_blocked=false; + // System.out.println("state of " + tmp + ": " + state); + break; + } + } + } + if(all_blocked) { + lock.lock(); + try { + tick++; + cond.signalAll(); + } + finally { + lock.unlock(); + } + } + + Util.sleep(interval); + } + worker=null; + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/MutableDigest.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/MutableDigest.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/MutableDigest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/MutableDigest.java 2011-10-18 11:22:35.000000000 +0000 @@ -8,7 +8,6 @@ /** * A mutable version of Digest (which is immutable * @author Bela Ban - * @version $Id: MutableDigest.java,v 1.7 2008/05/20 12:55:29 belaban Exp $ */ public class MutableDigest extends Digest { private boolean sealed=false; @@ -140,15 +139,19 @@ - /** - * Increments the sender's high_seqno by 1. - */ + /** Increments the sender's highest delivered seqno by 1 */ public void incrementHighestDeliveredSeqno(Address sender) { Entry entry=senders.get(sender); if(entry == null) return; checkSealed(); - Entry new_entry=new Entry(entry.getLow(), entry.getHighestDeliveredSeqno() +1, entry.getHighestReceivedSeqno()); + + long new_highest_delivered=entry.getHighestDeliveredSeqno() +1; + + // highest_received must be >= highest_delivered, but not smaller ! + long new_highest_received=Math.max(entry.getHighestReceivedSeqno(), new_highest_delivered); + + Entry new_entry=new Entry(entry.getLow(), new_highest_delivered, new_highest_received); senders.put(sender, new_entry); } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/MyReceiver.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/MyReceiver.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/MyReceiver.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/MyReceiver.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,38 @@ +package org.jgroups.util; + +import org.jgroups.ReceiverAdapter; +import org.jgroups.Message; +import org.jgroups.View; + +import java.util.List; +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * Simple receiver which buffers all messages + * @author Bela Ban + */ +public class MyReceiver extends ReceiverAdapter { + protected final Collection msgs=new ConcurrentLinkedQueue(); + protected final String name; + + public MyReceiver(String name) { + this.name=name; + } + + public Collection getMsgs() { + return msgs; + } + + public void clear() {msgs.clear();} + + public void receive(Message msg) { + System.out.println("[" + name + "] received message " + msg); + msgs.add(msg); + } + + public void viewAccepted(View new_view) { + System.out.println("[" + name + "] view: " + new_view); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/NotifyingFuture.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/NotifyingFuture.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/NotifyingFuture.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/NotifyingFuture.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,25 @@ +package org.jgroups.util; + +import java.util.concurrent.Future; + + +/** + * A sub-interface of a Future, that allows for listeners to be attached so that observers can be notified when the + * future completes. + *

        + * See {@link FutureListener} for more details. + *

        + * + * @author Manik Surtani + * @since 2.9 + */ +public interface NotifyingFuture extends Future { + + /** + * Attaches a listener and returns the same future instance, to allow for 'building' futures. + * + * @param listener listener to attach + * @return the same future instance + */ + NotifyingFuture setListener(FutureListener listener); +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/NullFuture.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/NullFuture.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/NullFuture.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/NullFuture.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,42 @@ +package org.jgroups.util; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * @author Bela Ban + */ +public class NullFuture implements NotifyingFuture { + final T retval; + + public NullFuture(T retval) { + this.retval=retval; + } + + public boolean cancel(boolean mayInterruptIfRunning) { + return true; + } + + public boolean isCancelled() { + return true; + } + + public boolean isDone() { + return true; + } + + public T get() throws InterruptedException, ExecutionException { + return retval; + } + + public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return retval; + } + + public NotifyingFuture setListener(FutureListener listener) { + if(listener != null) + listener.futureDone(this); + return this; + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/obsolete/CondVar.java.txt libjgroups-java-2.12.2.Final/src/org/jgroups/util/obsolete/CondVar.java.txt --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/obsolete/CondVar.java.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/obsolete/CondVar.java.txt 2011-10-18 11:22:35.000000000 +0000 @@ -8,7 +8,6 @@ * matches the expected result, or a timeout occurs. First version used WaitableBoolean from util.concurrent, but * that class would not allow for timeouts. * @author Bela Ban - * @version $Id: CondVar.java.txt,v 1.1 2006/12/31 14:56:56 belaban Exp $ */ public class CondVar { Object cond; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/obsolete/List.java.txt libjgroups-java-2.12.2.Final/src/org/jgroups/util/obsolete/List.java.txt --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/obsolete/List.java.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/obsolete/List.java.txt 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: List.java.txt,v 1.1 2007/08/08 08:58:28 belaban Exp $ package org.jgroups.util; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/PayloadUUID.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/PayloadUUID.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/PayloadUUID.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/PayloadUUID.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,93 @@ +package org.jgroups.util; + +import org.jgroups.Global; + +import java.io.*; +import java.security.SecureRandom; + +/** + * Subclass of {@link UUID} which adds a string as payload. An instance of this can be fed to + * {@link org.jgroups.JChannel#setAddressGenerator(org.jgroups.stack.AddressGenerator)}, with the address generator + * creating PayloadUUIDs. + * @author Bela Ban + */ +public class PayloadUUID extends UUID { + private static final long serialVersionUID=-7042544908572216601L; + + // don't need this as we already added PayloadUUID to jg-magic-map.xml + // static { + // ClassConfigurator.add((short)2222, PayloadUUID.class); + // } + + protected String payload; + + public PayloadUUID() { + } + + protected PayloadUUID(byte[] data, String payload) { + super(data); + this.payload=payload; + } + + public static PayloadUUID randomUUID(String payload) { + return new PayloadUUID(generateRandomBytes(), payload); + } + + public static PayloadUUID randomUUID(String logical_name, String payload) { + PayloadUUID retval=new PayloadUUID(generateRandomBytes(), payload); + UUID.add(retval, logical_name); + return retval; + } + + public String getPayload() { + return payload; + } + + public void setPayload(String payload) { + this.payload=payload; + } + + protected static byte[] generateRandomBytes() { + SecureRandom ng=numberGenerator; + if(ng == null) + numberGenerator=ng=new SecureRandom(); + + byte[] randomBytes=new byte[16]; + ng.nextBytes(randomBytes); + return randomBytes; + } + + public int size() { + int retval=super.size() + Global.BYTE_SIZE; + if(payload != null) + retval+=payload.length() +2; + return retval; + } + + public void writeTo(DataOutputStream out) throws IOException { + super.writeTo(out); + Util.writeString(payload, out); + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + super.readFrom(in); + payload=Util.readString(in); + } + + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + super.readExternal(in); + payload=(String)in.readObject(); + } + + public void writeExternal(ObjectOutput out) throws IOException { + super.writeExternal(out); + out.writeObject(payload); + } + + public String toString() { + if(print_uuids) + return toStringLong() + (payload == null? "" : "(" + payload + ")"); + return super.toString() + (payload == null? "" : "(" + payload + ")"); + } + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/PortsManager.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/PortsManager.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/PortsManager.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/PortsManager.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,176 +0,0 @@ -package org.jgroups.util; - -import java.io.*; -import java.util.*; - -/** - * Maintains a list of ports used on this host, associated with time stamps. The ports are persisted into the - * temp file system. - * @author Bela Ban - * @version $Id: PortsManager.java,v 1.6 2009/01/05 07:38:06 belaban Exp $ - */ -public class PortsManager { - private String filename="jgroups-ports.txt"; - private String temp_dir=System.getProperty("java.io.tmpdir", "/tmp"); - private final static String file_separator=System.getProperty("file.separator", "/"); - private String absolute_name=temp_dir + file_separator + filename; - - /** Time after which a port can be removed and re-allocated */ - private long expiry_time=60 * 1000L; - - public PortsManager() { - } - - public PortsManager(long expiry_time) { - this.expiry_time=expiry_time; - } - - public PortsManager(String ports_file) { - if(ports_file != null) - this.absolute_name=ports_file; - } - - public PortsManager(long expiry_time, String ports_file) { - this.expiry_time=expiry_time; - if(ports_file != null) - absolute_name=ports_file; - } - - public PortsManager(long expiry_time, String filename, String temp_dir) { - this.expiry_time=expiry_time; - this.filename=filename; - this.temp_dir=temp_dir; - absolute_name=temp_dir + file_separator + filename; - } - - - public long getExpiryTime() { - return expiry_time; - } - - public void setExpiryTime(long expiry_time) { - this.expiry_time=expiry_time; - } - - /** Loads the file, weeds out expired ports, returns the next available port and saves the new port in the file */ - public int getNextAvailablePort(int start_port) { - Map map; - int retval=-1; - - try { - map=load(); - long current_time=System.currentTimeMillis(), timestamp; - if(map.isEmpty()) { - map.put(start_port, current_time); - store(map); - return start_port; - } - - // weed out expired ports - Map.Entry entry; - for(Iterator> it=map.entrySet().iterator(); it.hasNext();) { - entry=it.next(); - timestamp=entry.getValue(); - if(current_time - timestamp >= expiry_time) { - it.remove(); - } - } - - // find next available port - Set ports=map.keySet(); - while(ports.contains(start_port)) { - start_port++; - } - map.put(start_port, System.currentTimeMillis()); - store(map); - return start_port; - } - catch(IOException e) { - return retval; - } - } - - /** Loads the file, removes the port (if existent) and closes the file again */ - public void removePort(int port) { - Map map; - - try { - map=load(); - if(map.isEmpty()) { - return; - } - map.remove(port); - store(map); - } - catch(IOException e) { - } - } - - /** - * Updates the timestamp for the given port - * @param port - */ - public void updatePort(int port) { - Map map; - - try { - map=load(); - if(map.isEmpty()) { - return; - } - map.put(port, System.currentTimeMillis()); - store(map); - } - catch(IOException e) { - } - } - - - /** Deletes the underlying file. Used for unit testing, not recommended for regular use ! */ - public void deleteFile() { - File file=new File(absolute_name); - file.delete(); - } - - - private Map load() throws IOException { - InputStream in=null; - Map retval=new HashMap(); - try { - in=new FileInputStream(absolute_name); - Properties props=new Properties(); - props.load(in); - for(Iterator> it=props.entrySet().iterator(); it.hasNext();) { - Map.Entry entry=it.next(); - String keystr=(String)entry.getKey(), valstr=(String)entry.getValue(); - int key=Integer.parseInt(keystr); - long val=Long.parseLong(valstr); - retval.put(key, val); - } - return retval; - } - catch(Throwable t) { - return retval; - } - finally { - Util.close(in); - } - } - - private void store(Map map) throws IOException { - OutputStream out=null; - try { - out=new FileOutputStream(absolute_name); - Properties props=new Properties(); - for(Map.Entry entry: map.entrySet()) { - String key=entry.getKey().toString(); - String val=entry.getValue().toString(); - props.put(key, val); - } - props.store(out, "Persistent JGroups ports"); - } - finally { - Util.close(out); - } - } -} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/Promise.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/Promise.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/Promise.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/Promise.java 2011-10-18 11:22:35.000000000 +0000 @@ -14,13 +14,12 @@ * for the result at a later time, or immediately and it may block or not. Both the caller and responder have to * know the promise. * @author Bela Ban - * @version $Id: Promise.java,v 1.14 2007/11/19 13:55:57 belaban Exp $ */ public class Promise { private final Lock lock=new ReentrantLock(); private final Condition cond=lock.newCondition(); private T result=null; - private boolean hasResult=false; + private volatile boolean hasResult=false; public Lock getLock() { return lock; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/PropertiesToXML.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/PropertiesToXML.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/PropertiesToXML.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/PropertiesToXML.java 2011-10-18 11:22:35.000000000 +0000 @@ -7,11 +7,14 @@ import java.io.Reader; import java.io.StringWriter; import java.io.Writer; +import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URL; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -30,67 +33,100 @@ import org.jgroups.annotations.Experimental; import org.jgroups.annotations.Property; import org.jgroups.annotations.Unsupported; -import org.jgroups.stack.Configurator; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.stack.Protocol; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.Element; /** - * Iterates over all concrete Protocol classes and creates an XML file per - * Protocol class. + * Iterates over all concrete Protocol classes and creates tables with Protocol's properties. + * These tables are in turn then merged into docbook. * - *

        - * Each XML file in turn contains all protocol properties along with property - * description. The output XML snipet is conforming docbook format so that - * property descriptions can be easily added to master docbook JGroups - * documentation. + * Iterates over unsupported and experimental classes and creates tables listing those classes. + * These tables are in turn then merged into docbook. * * @author Vladimir Blagojevic - * @version $Id: PropertiesToXML.java,v 1.5 2008/11/03 15:14:57 vlada Exp $ * */ public class PropertiesToXML { + protected static final Log log = LogFactory.getLog(PropertiesToXML.class); + public static void main(String[] args) { + String input = "doc/manual/en/modules/protocols.xml"; + String input2 = "doc/manual/en/modules/installation.xml"; + + if (args.length != 1) { + help(); + return; + } + input = args[0]; + String temp_file = input + ".tmp"; + String temp_file2 = input2 + ".tmp"; try { + // first copy protocols.xml file into protocols-temp.xml + File f = new File(temp_file); + copy(new FileReader(new File(input)), new FileWriter(f)); + String s = fileToString(f); - File f = new File("doc/manual/en/modules/protocols.xml"); - String s = fileToString(f); - - Set> classes=getClasses("org.jgroups.protocols", Protocol.class); - classes.addAll(getClasses("org.jgroups.protocols.pbcast", Protocol.class)); + Set> classes = findClassesAssignableFrom("org.jgroups.protocols", Protocol.class); + classes.addAll(findClassesAssignableFrom("org.jgroups.protocols.pbcast", Protocol.class)); Properties props = new Properties(); - for(Class clazz:classes) { - classToXML(props,clazz); - } + for (Class clazz : classes) { + convertToDocbookTable(props, clazz); + } + + String result = Util.replaceProperties(s, props); + FileWriter fw = new FileWriter(f, false); + fw.write(result); + fw.flush(); + fw.close(); + - String result = Util.replaceProperties(s, props); - FileWriter fw = new FileWriter(f,false); + + // copy installation.xml file into installation-temp.xml + f = new File(temp_file2); + copy(new FileReader(new File(input2)), new FileWriter(f)); + s = fileToString(f); + + props = new Properties(); + List> unsupportedClasses = findClassesAnnotatedWith("org.jgroups",Unsupported.class); + convertToDocbookTable(props, unsupportedClasses, "Unsupported"); + List> experimentalClasses = findClassesAnnotatedWith("org.jgroups",Experimental.class); + convertToDocbookTable(props, experimentalClasses, "Experimental"); + + result = Util.replaceProperties(s, props); + fw = new FileWriter(f, false); fw.write(result); fw.flush(); fw.close(); - } - catch(Exception e) { + + } catch (Exception e) { e.printStackTrace(); } } - private static Set> getClasses(String packageName, Class assignableFrom) throws IOException, - ClassNotFoundException { - ClassLoader loader=Thread.currentThread().getContextClassLoader(); - Set> classes=new HashSet>(); - String path=packageName.replace('.', '/'); - URL resource=loader.getResource(path); - if(resource != null) { - String filePath=resource.getFile(); - if(filePath != null && new File(filePath).isDirectory()) { - for(String file:new File(filePath).list()) { - if(file.endsWith(".class")) { - String name=packageName + '.' + file.substring(0, file.indexOf(".class")); - Class clazz=Class.forName(name); - if(assignableFrom.isAssignableFrom(clazz)) + static void help() { + System.out.println("PropertiesToXML "); + } + + private static Set> findClassesAssignableFrom(String packageName, Class assignableFrom) + throws IOException, ClassNotFoundException { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + Set> classes = new HashSet>(); + String path = packageName.replace('.', '/'); + URL resource = loader.getResource(path); + if (resource != null) { + String filePath = resource.getFile(); + if (filePath != null && new File(filePath).isDirectory()) { + for (String file : new File(filePath).list()) { + if (file.endsWith(".class")) { + String name = packageName + '.' + file.substring(0, file.indexOf(".class")); + Class clazz = Class.forName(name); + if (assignableFrom.isAssignableFrom(clazz)) classes.add(clazz); } } @@ -98,144 +134,257 @@ } return classes; } + + private static List> findClassesAnnotatedWith(String packageName, Class a) throws IOException, ClassNotFoundException { + List> classes = new ArrayList>(); + recurse(classes, packageName, a); + return classes; + } - private static void classToXML(Properties props, Class clazz) throws ParserConfigurationException, - TransformerException { - boolean isConcreteClass=(clazz.getModifiers() & Modifier.ABSTRACT) == 0; - boolean isExperimental=clazz.isAnnotationPresent(Experimental.class); - boolean isUnsupported=clazz.isAnnotationPresent(Unsupported.class); - if(isConcreteClass && !isExperimental && !isUnsupported) { - Class protocol=clazz; - Document xmldoc=null; - DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance(); - DocumentBuilder builder=factory.newDocumentBuilder(); - DOMImplementation impl=builder.getDOMImplementation(); - xmldoc=impl.createDocument(null, "table", null); - Element tbody=createXMLTree(xmldoc); - Map nameToDescription = new TreeMap(); - - //iterate fields - for(Class clazzInLoop=clazz;clazzInLoop != null;clazzInLoop=clazzInLoop.getSuperclass()) { - Field[] fields=clazzInLoop.getDeclaredFields(); - for(Field field:fields) { - if(field.isAnnotationPresent(Property.class)) { - String property=field.getName(); - Property annotation=field.getAnnotation(Property.class); - String desc=annotation.description(); - nameToDescription.put(property, desc); - } + private static void recurse(List> classes, String packageName, Class a) throws ClassNotFoundException { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + String path = packageName.replace('.', '/'); + URL resource = loader.getResource(path); + if (resource != null) { + String filePath = resource.getFile(); + if (filePath != null && new File(filePath).isDirectory()) { + for (String file : new File(filePath).list()) { + if (file.endsWith(".class")) { + String name = packageName + '.' + file.substring(0, file.indexOf(".class")); + Class clazz = Class.forName(name); + if (clazz.isAnnotationPresent(a)) + classes.add(clazz); + } + else if (new File(filePath,file).isDirectory()) { + recurse(classes, packageName + "." + file, a); + } } } - - //iterate methods - Method[] methods=clazz.getMethods(); - for(Method method:methods) { - if(method.isAnnotationPresent(Property.class) && method.getName() - .startsWith("set")) { - - Property annotation=method.getAnnotation(Property.class); - String desc=annotation.description(); - - if(desc.length() > 0) { - - String name=annotation.name(); - if(name.length() < 1) { - name=Configurator.renameFromJavaCodingConvention(method.getName() - .substring(3)); + } + } + + private static void convertToDocbookTable(Properties props, List> clazzes, String title) + throws ParserConfigurationException, TransformerException { + + Document xmldoc = null; + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + DOMImplementation impl = builder.getDOMImplementation(); + xmldoc = impl.createDocument(null, "table", null); + Element tbody = createXMLTable(xmldoc, title); + + for (Class clazz : clazzes) { + Element row = xmldoc.createElement("row"); + Element entry = xmldoc.createElement("entry"); + entry.setTextContent(clazz.getPackage().getName()); + row.appendChild(entry); + + entry = xmldoc.createElement("entry"); + entry.setTextContent(clazz.getSimpleName()); + row.appendChild(entry); + tbody.appendChild(row); + } + + // do we have more than one property (superclass Protocol has only one property (stats)) + if (clazzes != null && clazzes.size() > 1) { + DOMSource domSource = new DOMSource(xmldoc); + StringWriter sw = new StringWriter(); + StreamResult streamResult = new StreamResult(sw); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer serializer = tf.newTransformer(); + serializer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1"); + serializer.setOutputProperty(OutputKeys.INDENT, "yes"); + serializer.transform(domSource, streamResult); + StringBuffer buffer = sw.getBuffer(); + buffer.delete(0, buffer.indexOf("table") - 1); + props.put(title, buffer.toString()); + } + } + + private static void convertToDocbookTable(Properties props, Class clazz) + throws ParserConfigurationException, TransformerException { + boolean isConcreteClass = (clazz.getModifiers() & Modifier.ABSTRACT) == 0; + boolean isExperimental = clazz.isAnnotationPresent(Experimental.class); + boolean isUnsupported = clazz.isAnnotationPresent(Unsupported.class); + // if(isConcreteClass && !isExperimental && !isUnsupported) { + if (isConcreteClass && !isUnsupported) { + Class protocol = clazz; + Document xmldoc = null; + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + DOMImplementation impl = builder.getDOMImplementation(); + xmldoc = impl.createDocument(null, "table", null); + Element tbody = createXMLTree(xmldoc, isExperimental); + Map nameToDescription = new TreeMap(); + + // iterate fields + for (Class clazzInLoop = clazz; clazzInLoop != null; clazzInLoop = clazzInLoop + .getSuperclass()) { + Field[] fields = clazzInLoop.getDeclaredFields(); + for (Field field : fields) { + if (field.isAnnotationPresent(Property.class)) { + String property = field.getName(); + Property annotation = field.getAnnotation(Property.class); + String desc = annotation.description(); + nameToDescription.put(property, desc); + } + } + } + + // iterate methods + Method[] methods = clazz.getMethods(); + for (Method method : methods) { + if (method.isAnnotationPresent(Property.class) + && method.getName().startsWith("set")) { + + Property annotation = method.getAnnotation(Property.class); + String desc = annotation.description(); + + if (desc.length() > 0) { + + String name = annotation.name(); + if (name.length() < 1) { + name = Util.methodNameToAttributeName(method.getName()); } nameToDescription.put(name, desc); } } - } - - //and write them out - for(Map.Entry e:nameToDescription.entrySet()){ - Element row=xmldoc.createElement("row"); - Element entry=xmldoc.createElement("entry"); + } + + // and write them out + for (Map.Entry e : nameToDescription.entrySet()) { + Element row = xmldoc.createElement("row"); + Element entry = xmldoc.createElement("entry"); entry.setTextContent(e.getKey()); row.appendChild(entry); - - entry=xmldoc.createElement("entry"); + + entry = xmldoc.createElement("entry"); entry.setTextContent(e.getValue()); - row.appendChild(entry); - tbody.appendChild(row); - } - - //do we have more than one property (superclass Protocol has only one property (stats)) - if(nameToDescription.size()>1) { - DOMSource domSource=new DOMSource(xmldoc); + row.appendChild(entry); + tbody.appendChild(row); + } + + // do we have more than one property (superclass Protocol has only one property (stats)) + if (nameToDescription.size() > 1) { + DOMSource domSource = new DOMSource(xmldoc); StringWriter sw = new StringWriter(); - StreamResult streamResult=new StreamResult(sw); - TransformerFactory tf=TransformerFactory.newInstance(); - Transformer serializer=tf.newTransformer(); + StreamResult streamResult = new StreamResult(sw); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer serializer = tf.newTransformer(); serializer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1"); serializer.setOutputProperty(OutputKeys.INDENT, "yes"); serializer.transform(domSource, streamResult); - StringBuffer buffer=sw.getBuffer(); - buffer.delete(0, buffer.indexOf("table")-1); - props.put(protocol.getSimpleName(), buffer.toString()); + StringBuffer buffer = sw.getBuffer(); + buffer.delete(0, buffer.indexOf("table") - 1); + props.put(protocol.getSimpleName(), buffer.toString()); } - } + } } - + private static String fileToString(File f) throws Exception { - StringWriter output=new StringWriter(); - FileReader input = new FileReader(f); - char[] buffer=new char[8 * 1024]; - int count=0; - int n=0; - while(-1 != (n=input.read(buffer))) { + StringWriter output = new StringWriter(); + FileReader input = new FileReader(f); + char[] buffer = new char[8 * 1024]; + int count = 0; + int n = 0; + while (-1 != (n = input.read(buffer))) { output.write(buffer, 0, n); - count+=n; - } + count += n; + } return output.toString(); } public static int copy(Reader input, Writer output) throws IOException { - char[] buffer=new char[8 * 1024]; - int count=0; - int n=0; - while(-1 != (n=input.read(buffer))) { - output.write(buffer, 0, n); - count+=n; + char[] buffer = new char[8 * 1024]; + int count = 0; + int n = 0; + try { + while (-1 != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + } finally { + output.flush(); + output.close(); } return count; } - - - private static Element createXMLTree(Document xmldoc) throws ParserConfigurationException { - Element root=xmldoc.getDocumentElement(); - Element title=xmldoc.createElement("title"); - title.setTextContent("Properties"); + private static Element createXMLTree(Document xmldoc, boolean experimental) + throws ParserConfigurationException { + + Element root = xmldoc.getDocumentElement(); + Element title = xmldoc.createElement("title"); + if (experimental) + title.setTextContent("Properties (experimental)"); + else + title.setTextContent("Properties"); root.appendChild(title); - Element tgroup=xmldoc.createElement("tgroup"); + Element tgroup = xmldoc.createElement("tgroup"); tgroup.setAttribute("cols", "2"); root.appendChild(tgroup); - Element colspec=xmldoc.createElement("colspec"); + Element colspec = xmldoc.createElement("colspec"); colspec.setAttribute("align", "left"); tgroup.appendChild(colspec); - Element thead=xmldoc.createElement("thead"); + Element thead = xmldoc.createElement("thead"); tgroup.appendChild(thead); - Element row=xmldoc.createElement("row"); + Element row = xmldoc.createElement("row"); thead.appendChild(row); - Element entry=xmldoc.createElement("entry"); + Element entry = xmldoc.createElement("entry"); entry.setAttribute("align", "center"); entry.setTextContent("Name"); row.appendChild(entry); - entry=xmldoc.createElement("entry"); + entry = xmldoc.createElement("entry"); entry.setAttribute("align", "center"); entry.setTextContent("Description"); row.appendChild(entry); - Element tbody=xmldoc.createElement("tbody"); - tgroup.appendChild(tbody); + Element tbody = xmldoc.createElement("tbody"); + tgroup.appendChild(tbody); + + return tbody; + } + + private static Element createXMLTable(Document xmldoc, String titleContent) + throws ParserConfigurationException { + + Element root = xmldoc.getDocumentElement(); + Element title = xmldoc.createElement("title"); + title.setTextContent(titleContent); + root.appendChild(title); + + Element tgroup = xmldoc.createElement("tgroup"); + tgroup.setAttribute("cols", "2"); + root.appendChild(tgroup); + + Element colspec = xmldoc.createElement("colspec"); + colspec.setAttribute("align", "left"); + tgroup.appendChild(colspec); + + Element thead = xmldoc.createElement("thead"); + tgroup.appendChild(thead); + + Element row = xmldoc.createElement("row"); + thead.appendChild(row); + + Element entry = xmldoc.createElement("entry"); + entry.setAttribute("align", "center"); + entry.setTextContent("Package"); + row.appendChild(entry); + + entry = xmldoc.createElement("entry"); + entry.setAttribute("align", "center"); + entry.setTextContent("Class"); + row.appendChild(entry); + + Element tbody = xmldoc.createElement("tbody"); + tgroup.appendChild(tbody); return tbody; } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/Proxy.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/Proxy.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/Proxy.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/Proxy.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: Proxy.java,v 1.6 2008/04/08 14:49:05 belaban Exp $ package org.jgroups.util; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/QueueClosedException.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/QueueClosedException.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/QueueClosedException.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/QueueClosedException.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: QueueClosedException.java,v 1.3 2006/11/13 17:42:11 bstansberry Exp $ package org.jgroups.util; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/Queue.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/Queue.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/Queue.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/Queue.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,10 +1,9 @@ -// $Id: Queue.java,v 1.30 2007/08/08 09:36:23 belaban Exp $ package org.jgroups.util; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.TimeoutException; import java.util.*; @@ -24,10 +23,10 @@ private Element head=null, tail=null; /*flag to determine the state of the queue*/ - private boolean closed=false; + private volatile boolean closed=false; /*current size of the queue*/ - private int size=0; + private volatile int size=0; /* Lock object for synchronization. Is notified when element is added */ private final Object mutex=new Object(); @@ -199,50 +198,6 @@ /** - * Adds a new object to the head of the queue - * basically (obj.equals(queue.remove(queue.add(obj)))) returns true - * If the queue has been closed with close(true) no exception will be - * thrown if the queue has not been flushed yet. - * @param obj - the object to be added to the queue - * @exception QueueClosedException exception if closed() returns true - */ - public void addAtHead(Object obj) throws QueueClosedException { - if(obj == null) { - if(log.isErrorEnabled()) log.error("argument must not be null"); - return; - } - - /*lock the queue from other threads*/ - synchronized(mutex) { - if(closed) - throw new QueueClosedException(); - if(this.num_markers > 0) - throw new QueueClosedException("Queue.addAtHead(): queue has been closed. You can not add more elements. " + - "Waiting for removal of remaining elements."); - - Element el=new Element(obj); - /*check the head element in the list*/ - if(head == null) { - /*this is the first object, we could have done add(obj) here*/ - head=el; - tail=head; - size=1; - } - else { - /*set the head element to be the child of this one*/ - el.next=head; - /*set the head to point to the recently added object*/ - head=el; - /*increase the size*/ - size++; - } - /*wake up all the threads that are waiting for the lock to be released*/ - mutex.notifyAll(); - } - } - - - /** * Removes 1 element from head or blocks * until next element has been added or until queue has been closed * @return the first element to be taken of the queue @@ -286,6 +241,11 @@ * Removes 1 element from the head. * If the queue is empty the operation will wait for timeout ms. * if no object is added during the timeout time, a Timout exception is thrown + * (bela Aug 2009) Note that the semantics of remove(long timeout) are weird - the method waits until an element has + * been added, but doesn't do so in a loop ! So if we have 10 threads waiting on an empty queue, and 1 thread + * adds an element, all 10 threads will return (but only 1 will have the element), therefore 9 will throw + * a TimeoutException ! If I change this to the 'correct' semantics, however (e.g. the method removeWait() below), + * GMS.ViewHandler doesn't work correctly anymore. I won't change this now, as Queue will get removed anyway in 3.0. * @param timeout - the number of milli seconds this operation will wait before it times out * @return the first object in the queue */ @@ -325,6 +285,40 @@ } + public Object removeWait(long timeout) throws QueueClosedException, TimeoutException { + synchronized(mutex) { + if(closed) + throw new QueueClosedException(); + + final long end_time=System.currentTimeMillis() + timeout; + long wait_time, current_time; + + /*if the queue size is zero, we want to wait until a new object is added*/ + while(size == 0 && (current_time=System.currentTimeMillis()) < end_time) { + if(closed) + throw new QueueClosedException(); + try { + /*release the mutex lock and wait no more than timeout ms*/ + wait_time=end_time - current_time; // guarnteed to be > 0 + mutex.wait(wait_time); + } + catch(InterruptedException ex) { + } + } + /*we either timed out, or got notified by the mutex lock object*/ + if(closed) + throw new QueueClosedException(); + + /*get the next value*/ + Object retval=removeInternal(); + /*null result means we timed out*/ + if(retval == null) throw new TimeoutException("timeout=" + timeout + "ms"); + + return retval; + } + } + + /** * removes a specific object from the queue. * the object is matched up using the Object.equals method. diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/Queue.java.concurrent libjgroups-java-2.12.2.Final/src/org/jgroups/util/Queue.java.concurrent --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/Queue.java.concurrent 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/Queue.java.concurrent 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: Queue.java.concurrent,v 1.1.1.1 2003/09/09 01:24:12 belaban Exp $ package org.jgroups.util; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/Range.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/Range.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/Range.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/Range.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: Range.java,v 1.5 2004/10/04 20:43:35 belaban Exp $ package org.jgroups.util; @@ -7,7 +6,7 @@ -public class Range implements Externalizable, Streamable { +public class Range implements Externalizable, Streamable, Comparable { public long low=-1; // first msg to be retransmitted public long high=-1; // last msg to be retransmitted @@ -22,19 +21,32 @@ } - public String toString() { return "[" + low + " : " + high + ']'; } + public int compareTo(Range other) { + if(low == other.low && high == other.high) + return 0; + return low < other.low? -1 : 1; + } + + public int hashCode() { + return (int)low; + } + + public boolean equals(Object obj) { + Range other=(Range)obj; + return compareTo(other) == 0; + } + public void writeExternal(ObjectOutput out) throws IOException { out.writeLong(low); out.writeLong(high); } - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { low=in.readLong(); high=in.readLong(); @@ -50,4 +62,6 @@ low=in.readLong(); high=in.readLong(); } + + } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/ResourceManager.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/ResourceManager.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/ResourceManager.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/ResourceManager.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,143 +2,237 @@ import org.jgroups.Global; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.net.DatagramSocket; -import java.net.ServerSocket; +import java.net.*; import java.rmi.server.UID; -import java.util.StringTokenizer; -import java.util.List; import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; /** - * Manages resources such as multicast addresses and multicast ports, and TCP ports. This class is mainly used for - * running unit tests in parallel (TestNG) and preventing clusters intended to be separate from joining each other. + * Manages resources such as multicast addresses and multicast ports, and TCP + * ports. This class is mainly used for running unit tests in parallel (TestNG) + * and preventing clusters intended to be separate from joining each other. + * * @author Bela Ban - * @version $Id: ResourceManager.java,v 1.3 2008/04/10 10:41:41 belaban Exp $ */ public class ResourceManager { - private static final IpAddressRep rep; - private static short mcast_port; - private static short tcp_port; - - - static { - String tmp_addr=System.getProperty(Global.INITIAL_MCAST_ADDR, "230.1.1.1"); - mcast_port=Short.valueOf(System.getProperty(Global.INITIAL_MCAST_PORT, "7000")); - tcp_port=Short.valueOf(System.getProperty(Global.INITIAL_TCP_PORT, "10000")); - try { - InetAddress tmp=InetAddress.getByName(tmp_addr); - if(!tmp.isMulticastAddress()) - throw new IllegalArgumentException("initial multicast address " + tmp_addr + - " is not a valid multicast (class D) address"); - rep=new IpAddressRep(tmp_addr); - } - catch(UnknownHostException e) { - throw new RuntimeException("initial multicast address " + tmp_addr + " is incorrect", e); - } - } - - private ResourceManager() { - } - - /** - * Returns the next available multicast address, e.g. "228.1.2.3". This class is a JVM singleton - * @return - */ - public static String getNextMulticastAddress() { - return rep.nextAddress(); - } - - public static synchronized short getNextMulticastPort(InetAddress bind_addr) throws Exception { - short port=mcast_port++; - try { - DatagramSocket sock=Util.createDatagramSocket(bind_addr, port); - port=(short)sock.getLocalPort(); - sock.close(); - ServerSocket srv_sock=Util.createServerSocket(bind_addr, port); - port=(short)srv_sock.getLocalPort(); - srv_sock.close(); - return port; - } - finally { - mcast_port=(short)(port +1); - } - } - - - public static synchronized List getNextTcpPorts(InetAddress bind_addr, int num_requested_ports) throws Exception { - short port=tcp_port++; - List retval=new ArrayList(num_requested_ports); - - for(int i=0; i < num_requested_ports; i++) { - ServerSocket sock=Util.createServerSocket(bind_addr, port); - port=(short)sock.getLocalPort(); - retval.add(port); - tcp_port=++port; - sock.close(); - } - return retval; - } - - - public static String getUniqueClusterName(String base_name) { - return base_name != null? base_name + "-" + new UID().toString() : new UID().toString(); - } - - public static String getUniqueClusterName() { - return getUniqueClusterName(null); - } - - public static void main(String[] args) throws Exception { - List ports=getNextTcpPorts(InetAddress.getByName("192.168.1.5"), 15); - System.out.println("ports = " + ports); - - - ports=getNextTcpPorts(InetAddress.getByName("192.168.1.5"), 5); - System.out.println("ports = " + ports); - - - } - - /** Representation of an IP address */ - static class IpAddressRep { - short a=225, b=MIN, c=MIN, d=MIN; - - private static final short MIN=1, MAX=250; - private static final char DOT='.'; - - IpAddressRep(String initial_addr) { - StringTokenizer tok=new StringTokenizer(initial_addr, ".", false); - a=Short.valueOf(tok.nextToken()); - b=Short.valueOf(tok.nextToken()); - c=Short.valueOf(tok.nextToken()); - d=Short.valueOf(tok.nextToken()); - } - - synchronized String nextAddress() { - StringBuilder sb=new StringBuilder(); - sb.append(a).append(DOT).append(b).append(DOT).append(c).append(DOT).append(d); - increment(); - return sb.toString(); - } - - private void increment() { - d++; - if(d > MAX) { - d=MIN; - c++; - if(c > MAX) { - c=MIN; - b++; - if(b > MAX) { - b=MIN; - a++; - if(a > MAX) - a=225; - } - } - } - } - } + private static final IpAddressRep rep; + private static short mcast_port; + private static short tcp_port; + private static SocketFactory socket_factory=new DefaultSocketFactory(); + + static { + + StackType type=Util.getIpStackType(); + + String tmp_addr = System.getProperty(Global.INITIAL_MCAST_ADDR, + type == StackType.IPv6? "ff0e::9:9:9" : "230.1.1.1"); + mcast_port = Short.valueOf(System.getProperty(Global.INITIAL_MCAST_PORT, "7000")); + tcp_port = Short.valueOf(System.getProperty(Global.INITIAL_TCP_PORT, "10000")); + try { + InetAddress tmp = InetAddress.getByName(tmp_addr); + if (!tmp.isMulticastAddress()) + throw new IllegalArgumentException("initial multicast address " + tmp_addr + " is not a valid multicast address"); + + if (tmp instanceof Inet4Address) + rep = new IPv4AddressRep(tmp_addr); + else + rep = new IPv6AddressRep(tmp_addr); + + } catch (UnknownHostException e) { + throw new RuntimeException("initial multicast address " + tmp_addr + " is incorrect", e); + } + } + + private ResourceManager() { + } + + /** + * Returns the next available multicast address, e.g. "228.1.2.3". This + * class is a JVM singleton + * + * @return + */ + public static String getNextMulticastAddress() { + return rep.nextAddress(); + } + + public static synchronized short getNextMulticastPort(InetAddress bind_addr) throws Exception { + short port = mcast_port; + try { + DatagramSocket sock = Util.createDatagramSocket(socket_factory, "jgroups.temp.resourcemgr.mcast_sock", bind_addr, port); + port = (short) sock.getLocalPort(); + socket_factory.close(sock); + return port; + } finally { + mcast_port = (short) (port + 1); + } + } + + public static synchronized List getNextTcpPorts(InetAddress bind_addr, int num_requested_ports) throws Exception { + short port = tcp_port++; + List retval = new ArrayList(num_requested_ports); + + for (int i = 0; i < num_requested_ports; i++) { + ServerSocket sock = Util.createServerSocket(socket_factory, "jgroups.temp.resourcemgr.srv_sock", bind_addr, port); + port = (short) sock.getLocalPort(); + retval.add(port); + tcp_port = ++port; + socket_factory.close(sock); + } + return retval; + } + + public static String getUniqueClusterName(String base_name) { + return base_name != null ? base_name + "-" + new UID().toString() + : new UID().toString(); + } + + public static String getUniqueClusterName() { + return getUniqueClusterName(null); + } + + public static void main(String[] args) throws Exception { + List ports = getNextTcpPorts(InetAddress.getByName("192.168.1.5"), 15); + System.out.println("ports = " + ports); + + ports = getNextTcpPorts(InetAddress.getByName("192.168.1.5"), 5); + System.out.println("ports = " + ports); + + } + + /* + * Interface for IpAddress representations + */ + public interface IpAddressRep { + public String nextAddress(); + } + + /** Representation of an IPv4 address */ + static class IPv4AddressRep implements IpAddressRep { + short a = 225, b = MIN, c = MIN, d = MIN; + + private static final short MIN = 1, MAX = 250; + private static final char DOT = '.'; + + IPv4AddressRep(String initial_addr) { + StringTokenizer tok = new StringTokenizer(initial_addr, ".", false); + a = Short.valueOf(tok.nextToken()); + b = Short.valueOf(tok.nextToken()); + c = Short.valueOf(tok.nextToken()); + d = Short.valueOf(tok.nextToken()); + } + + public synchronized String nextAddress() { + StringBuilder sb = new StringBuilder(); + sb.append(a).append(DOT).append(b).append(DOT).append(c) + .append(DOT).append(d); + increment(); + return sb.toString(); + } + + private void increment() { + d++; + if (d > MAX) { + d = MIN; + c++; + if (c > MAX) { + c = MIN; + b++; + if (b > MAX) { + b = MIN; + a++; + if (a > MAX) + a = 225; + } + } + } + } + } + + /** Representation of an IPv6 address */ + static class IPv6AddressRep implements IpAddressRep { + + byte[] bv = null; + InetAddress address = null; + + private static boolean carry = false; + + IPv6AddressRep(String initial_addr) { + + try { + address = InetAddress.getByName(initial_addr); + } catch (UnknownHostException e) { + // + throw new RuntimeException ("Multicast address " + initial_addr + " has incorrect format", e) ; + } catch (SecurityException e) { + // + throw new RuntimeException ("Security violation in accessing multicast address " + initial_addr, e) ; + } + + // get the byte representation + bv = address.getAddress(); + } + + public synchronized String nextAddress() { + // build the existing address, then create the next one + try { + address = InetAddress.getByAddress(bv); + } catch (UnknownHostException e) { + // + throw new RuntimeException ("Multicast address has incorrect length", e) ; + } + increment(); + + // strings are returned as hostname/IP address, so remove the hostname prefix + // before returning the string + String addressWithHostname = address.toString(); + + return addressWithHostname.substring(addressWithHostname.indexOf('/')+1) ; + } + + private void increment() { + + // process hex digits from right to left + for (int i = bv.length - 1; i >= 0; i--) { + + // increment the ith byte + bv[i] =incrementHexValue(bv[i]); + + // if carry, increment (i-1)th byte + if (carry) { + carry = false; + continue; + } + // otherwise, we are done + return ; + } + // if we reach here, incrementing no longer possible + throw new RuntimeException ("Cannot increment multicast address ") ; + } + + // increments a byte in hex and notes if carry req'd + // these bytes contain 2's complement representations + // of hex values "00" through "ff" + private static byte incrementHexValue(byte b) { + + // "00" to "7e" + if (b >= 0 && b < 127) + return (byte) (b + (byte) 1); + // "7f" + else if (b == 127) { + return (byte) -128; + } + // "80" to "fe" + else if (b >= -128 && b < -1) { + return (byte) (b + (byte) 1); + } + // "ff" + else if (b == -1) { + carry = true; + return (byte) 0; + } + return 0; + } + } } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/ResponseCollector.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/ResponseCollector.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/ResponseCollector.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/ResponseCollector.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,187 @@ +package org.jgroups.util; + +import org.jgroups.Address; +import org.jgroups.annotations.GuardedBy; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Collections; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.TimeUnit; + +/** Similar to AckCollector, but collects responses, not just acks. Null is not a valid key. + * @author Bela Ban + */ +public class ResponseCollector { + @GuardedBy("lock") + private final Map responses; + private final Lock lock=new ReentrantLock(false); + private final Condition cond=lock.newCondition(); + + + /** + * + * @param members List of members from which we expect responses + */ + public ResponseCollector(Collection

        members) { + responses=members != null? new HashMap(members.size()) : new HashMap(); + reset(members); + } + + public ResponseCollector(Address ... members) { + responses=members != null? new HashMap(members.length) : new HashMap(); + reset(members); + } + + public ResponseCollector() { + responses=new HashMap(); + } + + public void add(Address member, T data) { + if(member == null) + return; + lock.lock(); + try { + if(responses.containsKey(member)) { + responses.put(member, data); + cond.signalAll(); + } + } + finally { + lock.unlock(); + } + } + + public void remove(Address member) { + if(member == null) + return; + lock.lock(); + try { + responses.remove(member); + cond.signalAll(); + } + finally { + lock.unlock(); + } + } + + public void suspect(Address member) { + if(member == null) + return; + lock.lock(); + try { + if(responses.remove(member) != null) + cond.signalAll(); + } + finally { + lock.unlock(); + } + } + + public boolean hasAllResponses() { + lock.lock(); + try { + for(Map.Entry entry: responses.entrySet()) { + if(entry.getValue() == null) + return false; + } + return true; + } + finally { + lock.unlock(); + } + } + + public Map getResults() { + return Collections.unmodifiableMap(responses); + } + + public int size() { + lock.lock(); + try { + return responses.size(); + } + finally { + lock.unlock(); + } + } + + + /** + * Waits until all responses have been received, or until a timeout has elapsed. + * @param timeout Number of milliseconds to wait max. This value needs to be greater than 0, or else + * it will be adjusted to 2000 + * @return boolean True if all responses have been received within timeout ms, else false (e.g. if interrupted) + */ + public boolean waitForAllResponses(long timeout) { + if(timeout <= 0) + timeout=2000L; + long end_time=System.currentTimeMillis() + timeout; + long wait_time; + + lock.lock(); + try { + while(!hasAllResponses()) { + wait_time=end_time - System.currentTimeMillis(); + if(wait_time <= 0) + return false; + try { + cond.await(wait_time, TimeUnit.MILLISECONDS); + } + catch(InterruptedException e) { + Thread.currentThread().interrupt(); // set interrupt flag again + return false; + } + } + return true; + } + finally { + lock.unlock(); + } + } + + public void reset() { + reset((Collection
        )null); + } + + public void reset(Collection
        members) { + lock.lock(); + try { + responses.clear(); + if(members != null) { + for(Address mbr: members) + responses.put(mbr, null); + } + cond.signalAll(); + } + finally { + lock.unlock(); + } + } + + public void reset(Address ... members) { + lock.lock(); + try { + responses.clear(); + if(members != null) { + for(Address mbr: members) + responses.put(mbr, null); + } + cond.signalAll(); + } + finally { + lock.unlock(); + } + } + + + + public String toString() { + StringBuilder sb=new StringBuilder(); + sb.append(responses).append(", complete=").append(hasAllResponses()); + return sb.toString(); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/RetransmitTable.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/RetransmitTable.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/RetransmitTable.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/RetransmitTable.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,446 @@ +package org.jgroups.util; + +import org.jgroups.Message; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; + +import java.util.LinkedList; +import java.util.List; + +/** + * A store for messages to be retransmitted or delivered. Used on sender and receiver side, as a replacement for + * HashMap. RetransmitTable should use less memory than HashMap, as HashMap.Entry has 4 fields, plus arrays for storage. + *

        + * RetransmitTable maintains a matrix (an array of arrays) of messages. Messages are stored in the matrix by mapping + * their seqno to an index. E.g. when we have 10 rows of 1000 messages each, and first_seqno is 3000, then a message with + * seqno=5600, will be stored in the 3rd row, at index 600. + *

        + * Rows are removed when all messages in that row have been received.

        + * This class in not synchronized; the caller has to make sure access to it is synchronized + * @author Bela Ban + */ +public class RetransmitTable { + protected final int num_rows; + protected final int msgs_per_row; + protected final double resize_factor; + protected Message[][] matrix; + + /** The first seqno, at matrix[0][0] */ + protected long offset; + + protected int size=0; + + /** The highest seqno purged */ + protected long highest_seqno_purged; + + /** The highest seqno in the table */ + protected long highest_seqno; + + /** Time (in ms) after which a compaction should take place. 0 disables compaction */ + protected long max_compaction_time=DEFAULT_MAX_COMPACTION_TIME; + + /** The time when the last compaction took place. If a {@link #compact()} takes place and sees that the + * last compaction is more than max_compaction_time ms ago, a compaction will take place */ + protected long last_compaction_timestamp=0; + + /** By default, rows are only nulled and highest_seqno_purged is adjusted when {@link #purge(long)} is called. + * When automatic_purging is enabled (default is off), rows are purged and highest_seqno_purged is adjusted + * on {@link #remove(long)} */ + protected boolean automatic_purging; + + protected static final long DEFAULT_MAX_COMPACTION_TIME=2 * 60 * 1000L; + + protected static final double DEFAULT_RESIZE_FACTOR=1.2; + + protected static final Log log=LogFactory.getLog(RetransmitTable.class); + + + + + + public RetransmitTable() { + this(5, 10000, 0, DEFAULT_RESIZE_FACTOR); + } + + public RetransmitTable(int num_rows, int msgs_per_row, long offset) { + this(num_rows, msgs_per_row, offset, DEFAULT_RESIZE_FACTOR); + } + + public RetransmitTable(int num_rows, int msgs_per_row, long offset, double resize_factor) { + this(num_rows, msgs_per_row, offset, resize_factor, DEFAULT_MAX_COMPACTION_TIME, false); + } + + public RetransmitTable(int num_rows, int msgs_per_row, long offset, double resize_factor, long max_compaction_time, + boolean automatic_purging) { + this.num_rows=num_rows; + this.msgs_per_row=msgs_per_row; + this.resize_factor=resize_factor; + this.max_compaction_time=max_compaction_time; + this.automatic_purging=automatic_purging; + this.offset=this.highest_seqno_purged=this.highest_seqno=offset; + matrix=new Message[num_rows][]; + if(resize_factor <= 1) + throw new IllegalArgumentException("resize_factor needs to be > 1"); + } + + + public long getOffset() { + return offset; + } + + /** Returns the total capacity in the matrix */ + public int capacity() {return matrix.length * msgs_per_row;} + + /** Returns the numbers of messages in the table */ + public int size() {return size;} + + + public boolean isEmpty() {return size <= 0;} + + + public long getHighest() { + return highest_seqno; + } + + public long getHighestPurged() { + return highest_seqno_purged; + } + + public long getMaxCompactionTime() { + return max_compaction_time; + } + + public void setMaxCompactionTime(long max_compaction_time) { + this.max_compaction_time=max_compaction_time; + } + + public boolean isAutomaticPurging() { + return automatic_purging; + } + + public void setAutomaticPurging(boolean automatic_purging) { + this.automatic_purging=automatic_purging; + } + + /** Returns the ratio between size and capacity, as a percentage */ + public double getFillFactor() { + return size == 0? 0.0 : (int)(((double)size / capacity()) * 100); + } + + + /** + * Adds a new message to the index computed as a function of seqno + * @param seqno + * @param msg + * @return True if the element at the computed index was null, else false + */ + public boolean put(long seqno, Message msg) { + return putIfAbsent(seqno, msg) == null; + } + + /** + * Adds a message if the element at the given index is null. Returns null if no message existed at the given index, + * else returns the existing message and doesn't set the element. + * @param seqno + * @param msg + * @return The existing message, or null if there wasn't any + */ + public Message putIfAbsent(long seqno, Message msg) { + int row_index=computeRow(seqno); + if(row_index >= matrix.length) { + resize(seqno); + row_index=computeRow(seqno); + } + Message[] row=getRow(row_index); + int index=computeIndex(seqno); + Message existing_msg=row[index]; + if(existing_msg == null) { + row[index]=msg; + size++; + if(seqno > highest_seqno) + highest_seqno=seqno; + return null; + } + else + return existing_msg; + } + + public Message get(long seqno) { + int row_index=computeRow(seqno); + if(row_index < 0 || row_index >= matrix.length) + return null; + Message[] row=matrix[row_index]; + if(row == null) + return null; + int index=computeIndex(seqno); + return index >= 0? row[index] : null; + } + + + public List get(long from, long to) { + List retval=null; + for(long seqno=from; seqno <= to; seqno++) { + Message msg=get(seqno); + if(msg != null) { + if(retval == null) + retval=new LinkedList(); + retval.add(msg); + } + } + return retval; + } + + + /** Removes the message with seqno from the table, nulls the index */ + public Message remove(long seqno) { + int row_index=computeRow(seqno); + if(row_index < 0 || row_index >= matrix.length) + return null; + Message[] row=matrix[row_index]; + if(row == null) + return null; + int index=computeIndex(seqno); + if(index < 0) + return null; + Message existing_msg=row[index]; + if(existing_msg != null) { + row[index]=null; + size=Math.max(size-1, 0); // cannot be < 0 (well that would be a bug, but let's have this 2nd line of defense !) + if(automatic_purging) { + if(seqno > highest_seqno_purged) + highest_seqno_purged=seqno; + } + } + return existing_msg; + } + + /** Removes all elements. This method is usually called just before removing a retransmit table, so typically + * it is not used anymore after returning */ + public void clear() { + matrix=new Message[num_rows][]; + size=0; + offset=highest_seqno_purged=highest_seqno=0; + } + + + + /** + * Removes all messages less than or equal to seqno from the table. Does this by nulling entire rows in the matrix + * and nulling all elements < index(seqno) of the first row that cannot be removed + * @param seqno + */ + public void purge(long seqno) { + int num_rows_to_remove=(int)(seqno - offset) / msgs_per_row; + for(int i=0; i < num_rows_to_remove; i++) // Null all rows which can be fully removed + matrix[i]=null; + + int row_index=computeRow(seqno); + if(row_index < 0 || row_index >= matrix.length) + return; + + Message[] row=matrix[row_index]; + if(row != null) { + int index=computeIndex(seqno); + for(int i=0; i <= index; i++) // null all messages up to and including seqno in the given row + row[i]=null; + } + size=computeSize(); + if(seqno > highest_seqno_purged) + highest_seqno_purged=seqno; + + // see if compaction should be triggered + if(max_compaction_time <= 0) + return; + + long current_time=System.currentTimeMillis(); + if(last_compaction_timestamp > 0) { + if(current_time - last_compaction_timestamp >= max_compaction_time) { + compact(); + last_compaction_timestamp=current_time; + } + } + else + last_compaction_timestamp=current_time; + } + + + + /** Moves rows down the matrix, by removing purged rows. If resizing to accommodate seqno is still needed, computes + * a new size. Then either moves existing rows down, or copies them into a new array (if resizing took place) */ + protected void resize(long seqno) { + int num_rows_to_purge=(int)((highest_seqno_purged - offset) / msgs_per_row); + int row_index=computeRow(seqno) - num_rows_to_purge; + if(row_index < 0) + return; + + int new_size=Math.max(row_index +1, matrix.length); + if(new_size > matrix.length) { + Message[][] new_matrix=new Message[new_size][]; + System.arraycopy(matrix, num_rows_to_purge, new_matrix, 0, matrix.length - num_rows_to_purge); + matrix=new_matrix; + } + else if(num_rows_to_purge > 0) { + move(num_rows_to_purge); + } + + offset+=(num_rows_to_purge * msgs_per_row); + size=computeSize(); + } + + + /** Moves contents of matrix num_rows down. Avoids a System.arraycopy() */ + protected void move(int num_rows) { + if(num_rows <= 0 || num_rows > matrix.length) + return; + + int target_index=0; + for(int i=num_rows; i < matrix.length; i++) + matrix[target_index++]=matrix[i]; + + for(int i=matrix.length - num_rows; i < matrix.length; i++) + matrix[i]=null; + } + + + /** + * Moves the contents of matrix down by the number of purged rows and resizes the matrix accordingly. The + * capacity of the matrix should be size * resize_factor + */ + public void compact() { + // This is the range we need to copy into the new matrix (including from and to) + int from=computeRow(highest_seqno_purged), to=computeRow(highest_seqno); + int range=to - from +1; // e.g. from=3, to=5, new_size has to be [3 .. 5] (=3) + + int new_size=(int)Math.max(range * resize_factor, range +1); + new_size=Math.max(new_size, num_rows); // don't fall below the initial size defined + if(new_size < matrix.length) { + if(log.isTraceEnabled()) + log.trace("compacting matrix from " + matrix.length + " rows to " + new_size + " rows"); + Message[][] new_matrix=new Message[new_size][]; + System.arraycopy(matrix, from, new_matrix, 0, range); + matrix=new_matrix; + offset+=from * msgs_per_row; + size=computeSize(); + } + } + + + + /** Iterate from highest_seqno_purged to highest_seqno and add up non-null values */ + public int computeSize() { + int retval=0; + int from=computeRow(highest_seqno_purged), to=computeRow(highest_seqno); + for(int i=from; i <= to; i++) { + Message[] row=matrix[i]; + if(row == null) + continue; + for(int j=0; j < row.length; j++) { + if(row[j] != null) + retval++; + } + } + return retval; + } + + + /** Returns the number of null elements up to 'to' */ + public int getNullMessages(long to) { + int retval=0; + for(long i=offset; i <= to; i++) { + int row_index=computeRow(i); + if(row_index < 0 || row_index >= matrix.length) + continue; + Message[] row=matrix[row_index]; + if(row != null && row[computeIndex(i)] == null) + retval++; + } + return retval; + } + + + public String toString() { + StringBuilder sb=new StringBuilder(); + sb.append("size=" + size + ", capacity=" + capacity() + ", highest_purged=" + highest_seqno_purged + ", highest=" + highest_seqno); + return sb.toString(); + } + + /** Dumps the seqnos in the table as a list */ + public String dump() { + StringBuilder sb=new StringBuilder(); + boolean first=true; + for(int i=0; i < matrix.length; i++) { + Message[] row=matrix[i]; + if(row == null) + continue; + for(int j=0; j < row.length; j++) { + if(row[j] != null) { + long seqno=offset + (i * msgs_per_row) + j; + if(first) + first=false; + else + sb.append(", "); + sb.append(seqno); + } + } + } + return sb.toString(); + } + + /** Dumps the non-null in the table in a pseudo graphic way */ + public String dumpMatrix() { + StringBuilder sb=new StringBuilder(); + for(int i=0; i < matrix.length; i++) { + Message[] row=matrix[i]; + sb.append(i + ": "); + if(row == null) { + sb.append("\n"); + continue; + } + for(int j=0; j < row.length; j++) { + if(row[j] != null) + sb.append("* "); + else + sb.append(" "); + } + sb.append("\n"); + } + return sb.toString(); + } + + + + /** + * Returns a row. Creates a new row and inserts it at index if the row at index doesn't exist + * @param index + * @return A row + */ + protected Message[] getRow(int index) { + Message[] row=matrix[index]; + if(row == null) { + row=new Message[msgs_per_row]; + matrix[index]=row; + } + return row; + } + + + /** Computes and returns the row index for seqno */ + protected int computeRow(long seqno) { + int diff=(int)(seqno-offset); + if(diff < 0) return diff; + return diff / msgs_per_row; + } + + + /** Computes and returns the index within a row for seqno */ + protected int computeIndex(long seqno) { + int diff=(int)(seqno - offset); + if(diff < 0) + return diff; + return diff % msgs_per_row; + } + + + +} + diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/RingBuffer.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/RingBuffer.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/RingBuffer.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/RingBuffer.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,137 @@ +package org.jgroups.util; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicStampedReference; +import java.util.concurrent.locks.LockSupport; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * + * @author Bela Ban + */ +public class RingBuffer { + private final AtomicStampedReference[] queue; + private final int capacity; + private final AtomicLong next_to_add=new AtomicLong(0); + private final AtomicLong next_to_remove=new AtomicLong(0); + + + /*private final AtomicInteger successful_adds=new AtomicInteger(0); + private final AtomicInteger successful_removes=new AtomicInteger(0); + private final AtomicInteger failed_adds=new AtomicInteger(0); + private final AtomicInteger failed_removes=new AtomicInteger(0);*/ + + + // private final ConcurrentLinkedQueue operations=new ConcurrentLinkedQueue(); + + + @SuppressWarnings("unchecked") + public RingBuffer(int capacity) { + queue=new AtomicStampedReference[capacity]; + this.capacity=capacity; + for(int i=0; i < capacity; i++) + queue[i]=new AtomicStampedReference(null, -1); + + /*Runtime.getRuntime().addShutdownHook(new Thread() { + public void run() { + System.out.println("next_to_add=" + next_to_add + ", next_to_remove=" + next_to_remove); + System.out.println("successful_adds=" + successful_adds + ", successful_removes=" + successful_removes); + System.out.println("failed_adds=" + failed_adds + ", failed_removes=" + failed_removes); + +// System.out.println("\nOperations"); +// for(String op: operations) +// System.out.println(op); + } + });*/ + } + + /** + * Adds an elements into the buffer. Blocks if full + * @param el + */ + public void add(T el) { + if(el == null) + throw new IllegalArgumentException("null element"); + int counter=0; + long next=next_to_add.getAndIncrement(); + int stamp=(int)(next / capacity); + + while(true) { + if(next - next_to_remove.get() < capacity) { + int index=(int)(next % capacity); + + AtomicStampedReference ref=queue[index]; + + if(ref.compareAndSet(null, el, -1, stamp)) { + // operations.add("queue[" + index + "]=" + el + " (next=" + next + ", next_to_remove=" + next_to_remove + ")"); + // successful_adds.incrementAndGet(); + return; + } + else { +// failed_adds.incrementAndGet(); +// System.err.println("add(" + el + ") failed at index " + index + ": next_to_add=" + next_to_add + +// ", next=" + next + ", next_to_remove=" + next_to_remove + +// ", queue[" + index + "]=" + queue[index].getReference()); + } + } + + if(counter >= 5) + LockSupport.parkNanos(10); // sleep a little after N attempts -- make configurable + else + counter++; + } + } + + public T remove() { + long next=next_to_remove.get(); + if(next >= next_to_add.get()) + return null; + + int stamp=(int)(next / capacity); + + int index=(int)(next % capacity); + AtomicStampedReference ref=queue[index]; + T retval=ref.getReference(); + + + if(retval != null && ref.compareAndSet(retval, null, stamp, -1)) { + // operations.add("queue[" + index + "]: " + retval + " = null (next=" + next + ")"); + // successful_removes.incrementAndGet(); + next_to_remove.incrementAndGet(); + return retval; + } +// else +// failed_removes.incrementAndGet(); + + return null; + } + + public String dumpNonNullElements() { + StringBuilder sb=new StringBuilder(); + for(AtomicStampedReference ref: queue) + if(ref.getReference() != null) + sb.append(ref.getReference() + " "); + return sb.toString(); + } + + public int size() { + int size=0; + int index=(int)(next_to_remove.get() % capacity); + for(int i=index; i < index + capacity; i++) + if(queue[i % capacity].getReference() != null) + size++; + return size; + } + + public String toString() { + StringBuilder sb=new StringBuilder(); + sb.append(size() + " elements"); + if(size() < 100) { + sb.append(": ").append(dumpNonNullElements()); + } + + return sb.toString(); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/Rsp.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/Rsp.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/Rsp.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/Rsp.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: Rsp.java,v 1.7 2008/01/22 10:44:33 belaban Exp $ package org.jgroups.util; @@ -8,18 +7,18 @@ /** * class that represents a response from a communication */ -public class Rsp { +public class Rsp { /* flag that represents whether the response was received */ - boolean received=false; + boolean received; /* flag that represents whether the response was suspected */ - boolean suspected=false; + boolean suspected; /* The sender of this response */ - Address sender=null; + Address sender; /* the value from the response */ - Object retval=null; + T retval; public Rsp(Address sender) { @@ -31,7 +30,7 @@ this.suspected=suspected; } - public Rsp(Address sender, Object retval) { + public Rsp(Address sender, T retval) { this.sender=sender; this.retval=retval; received=true; @@ -46,11 +45,15 @@ return other.sender == null; } - public Object getValue() { + public int hashCode() { + return sender != null? sender.hashCode() : 0; + } + + public T getValue() { return retval; } - public void setValue(Object val) { + public void setValue(T val) { this.retval=val; } @@ -72,10 +75,12 @@ return suspected; } - public void setSuspected(boolean suspected) { + public boolean setSuspected(boolean suspected) { + boolean changed=!this.suspected && suspected; this.suspected=suspected; if(suspected) received=false; + return changed; } public String toString() { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/RspList.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/RspList.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/RspList.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/RspList.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: RspList.java,v 1.9 2007/07/30 10:40:45 belaban Exp $ package org.jgroups.util; @@ -13,10 +12,12 @@ * A RspList is a response list used in peer-to-peer protocols. This class is unsynchronized */ public class RspList implements Map { + public static final RspList EMPTY_RSP_LIST=new RspList(); /** Map */ final Map rsps=new HashMap(); + public RspList() { } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/SeqnoComparator.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/SeqnoComparator.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/SeqnoComparator.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/SeqnoComparator.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,39 @@ +package org.jgroups.util; + +import java.util.Comparator; + +/** + * @author Bela Ban + */ +public class SeqnoComparator implements Comparator { + public int compare(Seqno o1, Seqno o2) { + + // o1 and o2 are either Seqnos or SeqnoRanges, so we just compare on 'low' + if(!o1.isDummy() && !o2.isDummy()) + return o1.low == o2.low? 0 : o1.low < o2.low ? -1 : 1; + + // o2 must be a seqno or SeqnoRange; o1 must be a Seqno + if(o1.isDummy()) { + if(o2 instanceof SeqnoRange) + return _compare2(o1, (SeqnoRange)o2); + return _compare(o1, o2); + } + + // o2 is dummy + if(o1 instanceof SeqnoRange) + return _compare3((SeqnoRange)o1, o2); + return _compare(o1, o2); + } + + private static int _compare(Seqno o1, Seqno o2) { + return o1.low == o2.low? 0 : o1.low < o2.low? -1 : 1; + } + + private static int _compare2(Seqno o1, SeqnoRange o2) { + return o1.low >= o2.low && o1.low <= o2.high? 0 : o1.low < o2.low? -1 : 1; + } + + private static int _compare3(SeqnoRange o1, Seqno o2) { + return o2.low >= o1.low && o2.low <= o1.high? 0 : o1.low < o2.low ? -1 : 1; + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/Seqno.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/Seqno.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/Seqno.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/Seqno.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,107 @@ +package org.jgroups.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; + + +/** + * Keeps track of a single message to retransmit + * @author Bela Ban + */ +public class Seqno { + final long low; + byte flags=0; + + public static final byte DUMMY = 1 << 0; + public static final byte RECEIVED = 1 << 1; + + + public Seqno(long low) { + this.low=low; + } + + /** + * Only used to compare a long against a range in a TreeSet / TreeMap. Used to find a range given a seqno + * @param num + * @param dummy + */ + public Seqno(long num, boolean dummy) { + low=num; + if(dummy) + flags=Util.setFlag(flags, DUMMY); + } + + public boolean isDummy() { + return Util.isFlagSet(flags, DUMMY); + } + + public long getLow() { + return low; + } + + public boolean contains(long num) { + return low == num; + } + + public boolean get(long num) { + return low == num && received(); + } + + public void set(long num) { + if(low == num) + flags=Util.setFlag(flags, RECEIVED); + } + + public void clear(long num) { + if(low == num) + flags=Util.clearFlags(flags, RECEIVED); + } + + public int getNumberOfReceivedMessages() { + return received()? 1 : 0; + } + + public int getNumberOfMissingMessages() { + return received()? 0 : 1; + } + + public int size() { + return 1; + } + + public Collection getMessagesToRetransmit() { + final Collection retval=new ArrayList(1); + if(!received()) + retval.add(new Range(low, low)); + return retval; + } + + public int hashCode() { + return (int)low; + } + + public boolean equals(Object obj) { + return obj instanceof Seqno && low == ((Seqno)obj).low; + } + + public String toString() { + if(isDummy()) + return low + " (dummy)"; + return Long.toString(low); + } + + public String print() { + if(isDummy()) + return Long.toString(low); + return Long.toString(low); + } + + + + protected boolean received() { + return Util.isFlagSet(flags, RECEIVED); + } + + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/SeqnoRange.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/SeqnoRange.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/SeqnoRange.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/SeqnoRange.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,136 @@ +package org.jgroups.util; + +import java.util.ArrayList; +import java.util.Collection; + + +/** + * Keeps track of a range of messages to be retransmitted. A bit set is used to represent missing messages. + * Every non-received message has a corresponding bit set to 0, every received message is 1. + * @author Bela Ban + */ +public class SeqnoRange extends Seqno { + final long high; + final FixedSizeBitSet bits; + + public SeqnoRange(long low, long high) { + super(low); + this.high=high; + if(low > high) + throw new IllegalArgumentException("low (" + low + ") must be <= high (" + high + ")"); + int size=(int)((high - low) + 1); + bits=new FixedSizeBitSet(size); // starts out with all bits set to 0 (false) + } + + + public long getHigh() { + return high; + } + + public boolean contains(long num) { + return num >= low && num <= high; + } + + public boolean get(long num) { + return bits.get(getIndex((int)num)); + } + + public void set(long num) { + bits.set(getIndex((int)num)); + } + + public void set(long ... nums) { + if(nums != null) + for(long num: nums) + set(num); + } + + public void clear(long num) { + bits.clear(getIndex((int)num)); + } + + public void clear(long ... nums) { + if(nums != null) + for(long num: nums) + clear(num); + } + + public int getNumberOfReceivedMessages() { + return bits.cardinality(); + } + + public int getNumberOfMissingMessages() { + return size() - getNumberOfReceivedMessages(); + } + + public int size() { + return (int)((high - low) + 1); + } + + public Collection getMessagesToRetransmit() { + return getBits(false); + } + + + public String toString() { + return low + "-" + high; + } + + public String print() { + return low + "-" + high + ", set=" + printBits(true) + ", cleared=" + printBits(false); + } + + protected int getIndex(int num) { + if(num < low || num > high) + throw new IllegalArgumentException(num + " is outside the range " + toString()); + return (int)(num - low); + } + + public String printBits(boolean value) { + Collection ranges=getBits(value); + StringBuilder sb=new StringBuilder(); + if(ranges != null && !ranges.isEmpty()) { + boolean first=true; + for(Range range: ranges) { + if(first) + first=false; + else + sb.append(", "); + if(range.low == range.high) + sb.append(range.low); + else + sb.append(range.low).append("-").append(range.high); + } + } + return sb.toString(); + } + + /** + * Returns ranges of all bit set to value + * @param value If true, returns all bits set to 1, else 0 + * @return + */ + public Collection getBits(boolean value) { + int index=0; + int start_range=0, end_range=0; + int size=(int)((high - low) + 1); + final Collection retval=new ArrayList(size); + + while(index < size) { + start_range=value? bits.nextSetBit(index) : bits.nextClearBit(index); + if(start_range < 0 || start_range >= size) + break; + end_range=value? bits.nextClearBit(start_range) : bits.nextSetBit(start_range); + if(end_range < 0 || end_range >= size) { + retval.add(new Range(start_range + low, size-1+low)); + break; + } + retval.add(new Range(start_range + low, end_range-1+low)); + index=end_range; + } + + return retval; + } + + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/SeqnoTable.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/SeqnoTable.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/SeqnoTable.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/SeqnoTable.java 2011-10-18 11:22:35.000000000 +0000 @@ -5,17 +5,15 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * Maintains the highest received and highest delivered seqno per member * @author Bela Ban - * @version $Id: SeqnoTable.java,v 1.2 2008/03/12 09:57:20 belaban Exp $ */ public class SeqnoTable { private long next_to_receive=0; - private final ConcurrentMap map=new ConcurrentHashMap(); + private final ConcurrentMap map=Util.createConcurrentMap(); public SeqnoTable(long next_to_receive) { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/ShutdownRejectedExecutionHandler.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/ShutdownRejectedExecutionHandler.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/ShutdownRejectedExecutionHandler.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/ShutdownRejectedExecutionHandler.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,7 +2,7 @@ import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; -import org.apache.commons.logging.LogFactory; + /** * ShutdownRejectedExecutionHandler is a decorator RejectedExecutionHandler used @@ -14,7 +14,6 @@ * @author Vladimir Blagojevic * @see ThreadPoolExecutor * @see RejectedExecutionHandler - * @version $Id: ShutdownRejectedExecutionHandler.java,v 1.101 2008/04/08 * 14:49:05 belaban Exp $ */ public class ShutdownRejectedExecutionHandler implements RejectedExecutionHandler { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/SingletonAddress.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/SingletonAddress.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/SingletonAddress.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/SingletonAddress.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,98 @@ +package org.jgroups.util; + +import org.jgroups.Address; + +import java.io.*; + +/** + * Address with a cluster name. Used by TP.Bundler. + * @author Bela Ban + */ +public class SingletonAddress implements Address { + protected final String cluster_name; + protected final Address addr; + private static final long serialVersionUID=-7139682546627602986L; + + public SingletonAddress(String cluster_name, Address addr) { + this.cluster_name=cluster_name; + this.addr=addr; + if(cluster_name == null) + throw new NullPointerException("cluster_name must not be null"); + } + + public SingletonAddress() { + cluster_name=null; + addr=null; + } + + public Address getAddress() { + return addr; + } + + public String getClusterName() { + return cluster_name; + } + + public boolean isMulticastAddress() { + return false; + } + + public int size() { + return 0; + } + + public void writeExternal(ObjectOutput out) throws IOException { + throw new UnsupportedOperationException(); + } + + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + throw new UnsupportedOperationException(); + } + + public void writeTo(DataOutputStream out) throws IOException { + throw new UnsupportedOperationException(); + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + throw new UnsupportedOperationException(); + } + + public int hashCode() { + int retval=0; + if(cluster_name != null) + retval+=cluster_name.hashCode(); + if(addr != null) + retval+=addr.hashCode(); + return retval; + } + + public boolean equals(Object obj) { + if(!(obj instanceof Address)) + throw new IllegalArgumentException("argument is " + obj.getClass()); + return compareTo((Address)obj) == 0; + } + + public int compareTo(Address o) { + SingletonAddress other=(SingletonAddress)o; + if(this == other) + return 0; + if(other == null) + return 1; + int rc=cluster_name.compareTo(other.cluster_name); + if(rc != 0) + return rc; + if(addr == null && other.addr == null) + return 0; + if(addr == null && other.addr != null) + return -1; + if(addr != null && other.addr == null) + return 1; + + assert addr != null; // this is here to make the (incorrect) 'addr' NPE warning below disappear ! + return addr.compareTo(other.addr); + } + + public String toString() { + return cluster_name + (addr != null? ":" + addr.toString() : ""); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/SizeBoundedQueue.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/SizeBoundedQueue.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/SizeBoundedQueue.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/SizeBoundedQueue.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,133 +0,0 @@ -package org.jgroups.util; - - -import java.util.Map; -import java.util.Collection; -import java.util.Iterator; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.TimeUnit; - -/** - * Queue as described in http://jira.jboss.com/jira/browse/JGRP-376. However, this queue only works with - * {@link org.jgroups.protocols.TP.IncomingPacket} elements. - * The queue maintains a max number of bytes and a total of all of the messages in the internal queues. Whenever a - * message is added, we increment the total by the length of the message. When a message is removed, we decrement the - * total. Removal blocks until a message is available, addition blocks if the max size has been exceeded, until there - * is enough space to add another message. - * Note that the max size should always be greater than the size of the largest message to be received, otherwise an - * additon would always fail because msg.length > max size !
        - * Access patterns: this instance is always accessed by the thread pool only ! Concurrent take() or poll() methods, - * but only a single thread at a time calls put() !

        - * Note that as of Jan 2008, this class has not yet been used. - * @author Bela Ban - * @version $Id: SizeBoundedQueue.java,v 1.4 2008/02/04 13:43:14 belaban Exp $ - */ -public class SizeBoundedQueue implements BlockingQueue { - int max_size=1000 * 1000; - int capacity=0; - /** Map>. Maintains a list of unicast messages per sender */ - final Map ucast_msgs=new ConcurrentHashMap(10); - /** Map>. Maintains a list of multicast messages per sender */ - final Map mcast_msgs=new ConcurrentHashMap(10); - - - public boolean add(Object o) { - return false; - } - - public int drainTo(Collection c) { - return 0; - } - - public int drainTo(Collection c, int maxElements) { - return 0; - } - - public boolean offer(Object o) { - return false; - } - - public boolean offer(Object o, long timeout, TimeUnit unit) throws InterruptedException { - return false; - } - - public Object poll(long timeout, TimeUnit unit) throws InterruptedException { - return null; - } - - public void put(Object o) throws InterruptedException { - } - - public int remainingCapacity() { - return 0; - } - - public Object take() throws InterruptedException { - return null; - } - - - public Object element() { - return null; - } - - public Object peek() { - return null; - } - - public Object poll() { - return null; - } - - public Object remove() { - return null; - } - - public boolean addAll(Collection c) { - return false; - } - - public void clear() { - } - - public boolean contains(Object o) { - return false; - } - - public boolean containsAll(Collection c) { - return false; - } - - public boolean isEmpty() { - return false; - } - - public Iterator iterator() { - return null; - } - - public boolean remove(Object o) { - return false; - } - - public boolean removeAll(Collection c) { - return false; - } - - public boolean retainAll(Collection c) { - return false; - } - - public int size() { - return 0; - } - - public Object[] toArray() { - return new Object[0]; - } - - public Object[] toArray(Object[] a) { - return new Object[0]; - } -} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/SocketFactory.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/SocketFactory.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/SocketFactory.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/SocketFactory.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,55 @@ +package org.jgroups.util; + +import java.net.*; +import java.io.IOException; +import java.util.Map; + +/** + * Factory to create various types of sockets. For socket creation, a service name can be passed as argument: + * an implementation could look up a service description (e.g. port) and create the socket, ignoring the passed port and + * possibly also the bind address.

        + * Ephemeral ports can be created by passing 0 as port, or (if the port is ignored), an implementation could pass in + * a special service name (e.g. "EPHEMERAL"), this is implementation dependent.

        + * The socket creation methods have the same parameter lists as the socket constructors, e.g. + * {@link #createServerSocket(String, int, int)} is the same as {@link java.net.ServerSocket(int,int)}. + * @author Bela Ban + */ +public interface SocketFactory { + + // todo: should we include NIO socket channels too ? + // todo: how should service names be structured ? jgroups.udp.unicast_port ? + + // todo: should we really include creation of java.net.Sockets ? They don't listen on incoming ports. This would only + // be for socket configuration.... + Socket createSocket(String service_name) throws IOException; + Socket createSocket(String service_name, String host, int port) throws IOException; + Socket createSocket(String service_name, InetAddress address, int port) throws IOException; + Socket createSocket(String service_name, String host, int port, InetAddress localAddr, int localPort) throws IOException; + Socket createSocket(String service_name, InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException; + + ServerSocket createServerSocket(String service_name) throws IOException; + ServerSocket createServerSocket(String service_name, int port) throws IOException; + ServerSocket createServerSocket(String service_name, int port, int backlog) throws IOException; + ServerSocket createServerSocket(String service_name, int port, int backlog, InetAddress bindAddr) throws IOException; + + DatagramSocket createDatagramSocket(String service_name) throws SocketException; + DatagramSocket createDatagramSocket(String service_name, SocketAddress bindaddr) throws SocketException; + DatagramSocket createDatagramSocket(String service_name, int port) throws SocketException; + DatagramSocket createDatagramSocket(String service_name, int port, InetAddress laddr) throws SocketException; + + MulticastSocket createMulticastSocket(String service_name) throws IOException; + MulticastSocket createMulticastSocket(String service_name, int port) throws IOException; + MulticastSocket createMulticastSocket(String service_name, SocketAddress bindaddr) throws IOException; + + void close(Socket sock) throws IOException; + void close(ServerSocket sock) throws IOException; + void close(DatagramSocket sock); + + + /** + * Returns all open sockets. This method can be used to list or close all open sockets. + * @return A map of open sockets; keys are Sockets, ServerSockets, DatagramSockets or MulticastSockets, values are + * the service names. + */ + Map getSockets(); +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/StackType.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/StackType.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/StackType.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/StackType.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,8 @@ +package org.jgroups.util; + +/** + * @author Bela Ban + */ +public enum StackType { + IPv4, IPv6, Unknown +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/Streamable.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/Streamable.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/Streamable.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/Streamable.java 2011-10-18 11:22:35.000000000 +0000 @@ -8,7 +8,6 @@ * Implementations of Streamable can add their state directly to the output stream, enabling them to bypass costly * serialization * @author Bela Ban - * @version $Id: Streamable.java,v 1.2 2005/07/25 16:21:47 belaban Exp $ */ public interface Streamable { diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/ThreadDecorator.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/ThreadDecorator.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/ThreadDecorator.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/ThreadDecorator.java 2011-10-18 11:22:35.000000000 +0000 @@ -5,7 +5,6 @@ * it that the thread has been created or released from use. * * @author Brian Stansberry - * @version $Id: ThreadDecorator.java,v 1.2 2008/05/26 09:28:29 belaban Exp $ */ public interface ThreadDecorator { /** diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/ThreadManager.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/ThreadManager.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/ThreadManager.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/ThreadManager.java 2011-10-18 11:22:35.000000000 +0000 @@ -5,7 +5,6 @@ * {@link ThreadDecorator} to allow it to alter their state. * * @author Brian Stansberry - * @version $Id: ThreadManager.java,v 1.2 2008/05/26 09:28:29 belaban Exp $ */ public interface ThreadManager { /** diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/ThreadManagerThreadPoolExecutor.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/ThreadManagerThreadPoolExecutor.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/ThreadManagerThreadPoolExecutor.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/ThreadManagerThreadPoolExecutor.java 2011-10-18 11:22:35.000000000 +0000 @@ -6,7 +6,6 @@ /** * ThreadPoolExecutor subclass that implements @{link ThreadManager}. * @author Brian Stansberry - * @version $Id: ThreadManagerThreadPoolExecutor.java,v 1.3 2008/09/22 13:54:54 belaban Exp $ */ public class ThreadManagerThreadPoolExecutor extends ThreadPoolExecutor implements ThreadManager { private ThreadDecorator decorator; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/TimeScheduler2.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/TimeScheduler2.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/TimeScheduler2.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/TimeScheduler2.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,682 @@ + +package org.jgroups.util; + + +import org.jgroups.Global; +import org.jgroups.annotations.Experimental; +import org.jgroups.annotations.GuardedBy; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + + +/** + * Implementation of {@link org.jgroups.util.TimeScheduler}. Uses a thread pool and a single thread which waits for the + * next task to be executed. When ready, it passes the task to the associated pool to get executed. When multiple tasks + * are scheduled to get executed at the same time, they're collected in a queue associated with the task execution + * time, and are executed together. + * + * @author Bela Ban + */ +@Experimental +public class TimeScheduler2 implements TimeScheduler, Runnable { + private final ThreadManagerThreadPoolExecutor pool; + + private final ConcurrentSkipListMap tasks=new ConcurrentSkipListMap(); + + private Thread runner=null; + + private final Lock lock=new ReentrantLock(); + + private final Condition tasks_available=lock.newCondition(); + + @GuardedBy("lock") + private long next_execution_time=0; + + /** Needed to signal going from 0 tasks to non-zero (we cannot use tasks.isEmpty() here ...) */ + protected final AtomicBoolean no_tasks=new AtomicBoolean(true); + + protected volatile boolean running=false; + + protected static final Log log=LogFactory.getLog(TimeScheduler2.class); + + protected ThreadDecorator threadDecorator=null; + + protected ThreadFactory timer_thread_factory=null; + + protected static final long SLEEP_TIME=10000; + + + /** + * Create a scheduler that executes tasks in dynamically adjustable intervals + */ + public TimeScheduler2() { + pool=new ThreadManagerThreadPoolExecutor(4, 10, + 5000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(5000), + Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); + init(); + } + + + public TimeScheduler2(ThreadFactory factory, int min_threads, int max_threads, long keep_alive_time, int max_queue_size) { + timer_thread_factory=factory; + pool=new ThreadManagerThreadPoolExecutor(min_threads, max_threads,keep_alive_time, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue(max_queue_size), + factory, new ThreadPoolExecutor.CallerRunsPolicy()); + init(); + } + + public TimeScheduler2(int corePoolSize) { + pool=new ThreadManagerThreadPoolExecutor(corePoolSize, corePoolSize * 2, + 5000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(5000), + Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); + init(); + } + + + + public ThreadDecorator getThreadDecorator() { + return threadDecorator; + } + + public void setThreadDecorator(ThreadDecorator threadDecorator) { + this.threadDecorator=threadDecorator; + pool.setThreadDecorator(threadDecorator); + } + + public void setThreadFactory(ThreadFactory factory) { + pool.setThreadFactory(factory); + } + + public int getMinThreads() { + return pool.getCorePoolSize(); + } + + public void setMinThreads(int size) { + pool.setCorePoolSize(size); + } + + public int getMaxThreads() { + return pool.getMaximumPoolSize(); + } + + public void setMaxThreads(int size) { + pool.setMaximumPoolSize(size); + } + + public long getKeepAliveTime() { + return pool.getKeepAliveTime(TimeUnit.MILLISECONDS); + } + + public void setKeepAliveTime(long time) { + pool.setKeepAliveTime(time, TimeUnit.MILLISECONDS); + } + + public int getCurrentThreads() { + return pool.getPoolSize(); + } + + public int getQueueSize() { + return pool.getQueue().size(); + } + + + public String dumpTimerTasks() { + StringBuilder sb=new StringBuilder(); + for(Entry entry: tasks.values()) { + sb.append(entry.dump()).append("\n"); + } + return sb.toString(); + } + + + + + public void execute(Runnable task) { + schedule(task, 0, TimeUnit.MILLISECONDS); + } + + + public Future schedule(Runnable work, long delay, TimeUnit unit) { + if(work == null) + return null; + + Future retval=null; + + long key=System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(delay, unit); // execution time + Entry task=new Entry(work); + while(!isShutdown()) { + Entry existing=tasks.putIfAbsent(key, task); + if(existing == null) { + retval=task.getFuture(); + break; // break out of the while loop + } + if((retval=existing.add(work)) != null) + break; + } + + if(key < next_execution_time || no_tasks.compareAndSet(true, false)) { + if(key >= next_execution_time) + key=0L; + taskReady(key); + } + + return retval; + } + + + + public Future scheduleWithFixedDelay(Runnable task, long initial_delay, long delay, TimeUnit unit) { + if(task == null) + throw new NullPointerException(); + if (isShutdown()) + return null; + RecurringTask wrapper=new FixedIntervalTask(task, delay); + wrapper.doSchedule(initial_delay); + return wrapper; + } + + + public Future scheduleAtFixedRate(Runnable task, long initial_delay, long delay, TimeUnit unit) { + if(task == null) + throw new NullPointerException(); + if (isShutdown()) + return null; + RecurringTask wrapper=new FixedRateTask(task, delay); + wrapper.doSchedule(initial_delay); + return wrapper; + } + + + /** + * Schedule a task for execution at varying intervals. After execution, the task will get rescheduled after + * {@link org.jgroups.util.TimeScheduler2.Task#nextInterval()} milliseconds. The task is neve done until nextInterval() + * return a value <= 0 or the task is cancelled. + * @param task the task to execute + * Task is rescheduled relative to the last time it actually started execution

        + * false:
        Task is scheduled relative to its last execution schedule. This has the effect + * that the time between two consecutive executions of the task remains the same.

        + * Note that relative is always true; we always schedule the next execution relative to the last *actual* + */ + public Future scheduleWithDynamicInterval(Task task) { + if(task == null) + throw new NullPointerException(); + if (isShutdown()) + return null; + RecurringTask task_wrapper=new DynamicIntervalTask(task); + task_wrapper.doSchedule(); // calls schedule() in ScheduledThreadPoolExecutor + return task_wrapper; + } + + + + + /** + * Returns the number of tasks currently in the timer + * @return The number of tasks currently in the timer + */ + public int size() { + int retval=0; + Collection values=tasks.values(); + for(Entry entry: values) + retval+=entry.size(); + return retval; + } + + + public String toString() { + return getClass().getSimpleName(); + } + + + /** + * Stops the timer, cancelling all tasks + * + * @throws InterruptedException if interrupted while waiting for thread to return + */ + public void stop() { + stopRunner(); + + java.util.List remaining_tasks=pool.shutdownNow(); + for(Runnable task: remaining_tasks) { + if(task instanceof Future) { + Future future=(Future)task; + future.cancel(true); + } + } + pool.getQueue().clear(); + try { + pool.awaitTermination(Global.THREADPOOL_SHUTDOWN_WAIT_TIME, TimeUnit.MILLISECONDS); + } + catch(InterruptedException e) { + } + + for(Entry entry: tasks.values()) + entry.cancel(); + tasks.clear(); + } + + + public boolean isShutdown() { + return pool.isShutdown(); + } + + + public void run() { + while(running) { + try { + _run(); + } + catch(Throwable t) { + log.error("failed executing tasks(s)", t); + } + } + } + + + protected void _run() { + ConcurrentNavigableMap head_map; // head_map = entries which are <= curr time (ready to be executed) + if(!(head_map=tasks.headMap(System.currentTimeMillis(), true)).isEmpty()) { + final List keys=new LinkedList(); + for(Map.Entry entry: head_map.entrySet()) { + final Long key=entry.getKey(); + final Entry val=entry.getValue(); + pool.execute(new Runnable() { + public void run() { + val.execute(); + } + }); + keys.add(key); + } + tasks.keySet().removeAll(keys); + } + + if(tasks.isEmpty()) { + no_tasks.compareAndSet(false, true); + waitFor(); // sleeps until time elapses, or a task with a lower execution time is added + } + else + waitUntilNextExecution(); // waits until next execution, or a task with a lower execution time is added + } + + + protected void init() { + if(threadDecorator != null) + pool.setThreadDecorator(threadDecorator); + // pool.allowCoreThreadTimeOut(true); + startRunner(); + } + + /** + * Sleeps until the next task in line is ready to be executed + */ + protected void waitUntilNextExecution() { + lock.lock(); + try { + if(!running) + return; + next_execution_time=tasks.firstKey(); + long sleep_time=next_execution_time - System.currentTimeMillis(); + tasks_available.await(sleep_time, TimeUnit.MILLISECONDS); + } + catch(InterruptedException e) { + } + finally { + lock.unlock(); + } + } + + protected void waitFor() { + lock.lock(); + try { + if(!running) + return; + tasks_available.await(SLEEP_TIME, TimeUnit.MILLISECONDS); + } + catch(InterruptedException e) { + } + finally { + lock.unlock(); + } + } + + /** + * Signals that a task with a lower execution time than next_execution_time is ready + */ + protected void taskReady(long trigger_time) { + lock.lock(); + try { + if(trigger_time > 0) + next_execution_time=trigger_time; + tasks_available.signal(); + } + finally { + lock.unlock(); + } + } + + protected void startRunner() { + running=true; + runner=timer_thread_factory != null? timer_thread_factory.newThread(this, "Timer runner") : new Thread(this, "Timer runner"); + runner.start(); + } + + protected void stopRunner() { + lock.lock(); + try { + running=false; + tasks_available.signal(); + } + finally { + lock.unlock(); + } + } + + + + + private static class Entry { + private final MyTask task; // the task (wrapper) to execute + private MyTask last; // points to the last task + private final Lock lock=new ReentrantLock(); + + @GuardedBy("lock") + private boolean completed=false; // set to true when the task has been executed + + + public Entry(Runnable task) { + last=this.task=new MyTask(task); + } + + Future getFuture() { + return task; + } + + Future add(Runnable task) { + lock.lock(); + try { + if(completed) + return null; + MyTask retval=new MyTask(task); + last.next=retval; + last=last.next; + return retval; + } + finally { + lock.unlock(); + } + } + + void execute() { + lock.lock(); + try { + if(completed) + return; + completed=true; + + for(MyTask tmp=task; tmp != null; tmp=tmp.next) { + if(!(tmp.isCancelled() || tmp.isDone())) { + try { + tmp.run(); + } + catch(Throwable t) { + log.error("task execution failed", t); + } + finally { + tmp.done=true; + } + } + } + } + finally { + lock.unlock(); + } + } + + void cancel() { + lock.lock(); + try { + if(completed) + return; + for(MyTask tmp=task; tmp != null; tmp=tmp.next) + tmp.cancel(true); + } + finally { + lock.unlock(); + } + } + + int size() { + int retval=1; + for(MyTask tmp=task.next; tmp != null; tmp=tmp.next) + retval++; + return retval; + } + + public String toString() { + return size() + " tasks"; + } + + public String dump() { + StringBuilder sb=new StringBuilder(); + boolean first=true; + for(MyTask tmp=task; tmp != null; tmp=tmp.next) { + if(!first) + sb.append(", "); + else + first=false; + sb.append(tmp); + } + return sb.toString(); + } + + } + + + /** + * Simple task wrapper, always executed by at most 1 thread. + */ + protected static class MyTask implements Future, Runnable { + protected final Runnable task; + protected volatile boolean cancelled=false; + protected volatile boolean done=false; + protected MyTask next; + + public MyTask(Runnable task) { + this.task=task; + } + + public boolean cancel(boolean mayInterruptIfRunning) { + boolean retval=!isDone(); + cancelled=true; + return retval; + } + + public boolean isCancelled() { + return cancelled; + } + + public boolean isDone() { + return done || cancelled; + } + + public Object get() throws InterruptedException, ExecutionException { + return null; + } + + public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return null; + } + + public void run() { + if(isDone()) + return; + try { + task.run(); + } + catch(Throwable t) { + log.error("failed executing task " + task, t); + } + finally { + done=true; + } + } + + public String toString() { + return task.toString(); + } + } + + + /** + * Task which executes multiple times. An instance of this class wraps the real task and intercepts run(): when + * called, it forwards the call to task.run() and then schedules another execution (until cancelled). The + * {@link #nextInterval()} method determines the time to wait until the next execution. + * @param + */ + private abstract class RecurringTask implements Runnable, Future { + protected final Runnable task; + protected volatile Future future; // cannot be null ! + protected volatile boolean cancelled=false; + + + public RecurringTask(Runnable task) { + this.task=task; + } + + /** + * The time to wait until the next execution + * @return Number of milliseconds to wait until the next execution is scheduled + */ + protected abstract long nextInterval(); + + protected boolean rescheduleOnZeroDelay() {return false;} + + public void doSchedule() { + long next_interval=nextInterval(); + if(next_interval <= 0 && !rescheduleOnZeroDelay()) { + if(log.isTraceEnabled()) + log.trace("task will not get rescheduled as interval is " + next_interval); + return; + } + + future=schedule(this, next_interval, TimeUnit.MILLISECONDS); + if(cancelled) + future.cancel(true); + } + + public void doSchedule(long next_interval) { + future=schedule(this, next_interval, TimeUnit.MILLISECONDS); + if(cancelled) + future.cancel(true); + } + + + public void run() { + if(cancelled) { + if(future != null) + future.cancel(true); + return; + } + + try { + task.run(); + } + catch(Throwable t) { + log.error("failed running task " + task, t); + } + if(!cancelled) + doSchedule(); + } + + + public boolean cancel(boolean mayInterruptIfRunning) { + boolean retval=!isDone(); + cancelled=true; + if(future != null) + future.cancel(mayInterruptIfRunning); + return retval; + } + + public boolean isCancelled() { + return cancelled; + } + + public boolean isDone() { + return cancelled || (future == null || future.isDone()); + } + + public V get() throws InterruptedException, ExecutionException { + return null; + } + + public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return null; + } + + public String toString() { + StringBuilder sb=new StringBuilder(); + sb.append(getClass().getSimpleName() + ": task=" + task + ", cancelled=" + isCancelled()); + return sb.toString(); + } + } + + + private class FixedIntervalTask extends RecurringTask { + final long interval; + + public FixedIntervalTask(Runnable task, long interval) { + super(task); + this.interval=interval; + } + + protected long nextInterval() { + return interval; + } + } + + private class FixedRateTask extends RecurringTask { + final long interval; + final long first_execution; + int num_executions=0; + + public FixedRateTask(Runnable task, long interval) { + super(task); + this.interval=interval; + this.first_execution=System.currentTimeMillis(); + } + + protected long nextInterval() { + long target_time=first_execution + (interval * ++num_executions); + return target_time - System.currentTimeMillis(); + } + + protected boolean rescheduleOnZeroDelay() {return true;} + } + + + private class DynamicIntervalTask extends RecurringTask { + + public DynamicIntervalTask(Task task) { + super(task); + } + + protected long nextInterval() { + if(task instanceof Task) + return ((Task)task).nextInterval(); + return 0; + } + } + + + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/TimeScheduler.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/TimeScheduler.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/TimeScheduler.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/TimeScheduler.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,283 +2,171 @@ package org.jgroups.util; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jgroups.Global; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; -import java.util.concurrent.*; /** - * Fixed-delay & fixed-rate single thread scheduler - *

        - * The scheduler supports varying scheduling intervals by asking the task - * every time for its next preferred scheduling interval. Scheduling can - * either be fixed-delay or fixed-rate. The notions are - * borrowed from java.util.Timer and retain the same meaning. - * I.e. in fixed-delay scheduling, the task's new schedule is calculated - * as:
        - * new_schedule = time_task_starts + scheduling_interval - *

        - * In fixed-rate scheduling, the next schedule is calculated as:
        - * new_schedule = time_task_was_supposed_to_start + scheduling_interval - *

        - * The scheduler internally holds a queue (DelayQueue) of tasks sorted in ascending order - * according to their next execution time. A task is removed from the queue - * if it is cancelled, i.e. if TimeScheduler.Task.isCancelled() - * returns true. - *

        - * The scheduler extends ScheduledThreadPoolExecutor to keep tasks - * sorted. java.util.Timer uses an array arranged as a binary heap (DelayQueue). - *

        - * Initially, the scheduler is in SUSPENDed mode, start() - * need not be called: if a task is added, the scheduler gets started - * automatically. Calling start() starts the scheduler if it's - * suspended or stopped else has no effect. Once stop() is called, - * added tasks will not restart it: start() has to be called to - * restart the scheduler. + * Timer-like interface which allows for execution of tasks. Taks can be executed + *

          + *
        • one time only + *
        • at recurring time intervals. Intervals can be fixed-delay or fixed-rate, + * see {@link java.util.concurrent.ScheduledExecutorService} for details + *
        • dynamic; at the end of the task execution, a task is asked what the next execution time should be. To do this, + * method {@link org.jgroups.util.TimeScheduler.Task#nextInterval()} needs to be implemented. + *
        + * * @author Bela Ban - * @version $Id: TimeScheduler.java,v 1.28 2008/06/23 05:58:04 belaban Exp $ */ -public class TimeScheduler extends ScheduledThreadPoolExecutor implements ThreadManager { +public interface TimeScheduler extends ThreadManager { - /** The interface that submitted tasks must implement */ + /** The interface that dynamic tasks + * ({@link TimeScheduler#scheduleWithDynamicInterval(org.jgroups.util.TimeScheduler.Task)}) must implement */ public interface Task extends Runnable { - /** @return the next schedule interval. If <= 0 the task will not be re-scheduled */ + /** @return the next scheduled interval. If <= 0 the task will not be re-scheduled */ long nextInterval(); } - /** How many core threads */ - private static int TIMER_DEFAULT_NUM_THREADS=3; - - - protected static final Log log=LogFactory.getLog(TimeScheduler.class); - - - - static { - String tmp; - try { - tmp=System.getProperty(Global.TIMER_NUM_THREADS); - if(tmp != null) - TIMER_DEFAULT_NUM_THREADS=Integer.parseInt(tmp); - } - catch(Exception e) { - log.error("could not set number of timer threads", e); - } - } - - private ThreadDecorator threadDecorator=null; /** - * Create a scheduler that executes tasks in dynamically adjustable intervals + * Executes command with zero required delay. This has effect equivalent to schedule(command, 0, anyUnit). + * + * @param command the task to execute + * @throws java.util.concurrent.RejectedExecutionException at discretion of RejectedExecutionHandler, + * if task cannot be accepted for execution because the executor has been shut down. + * @throws NullPointerException if command is null */ - public TimeScheduler() { - this(TIMER_DEFAULT_NUM_THREADS); - } - - public TimeScheduler(ThreadFactory factory) { - this(factory, TIMER_DEFAULT_NUM_THREADS); - } - - public TimeScheduler(ThreadFactory factory, int max_threads) { - super(max_threads, factory); - setRejectedExecutionHandler(new ShutdownRejectedExecutionHandler(getRejectedExecutionHandler())); - } - - public TimeScheduler(int corePoolSize) { - super(corePoolSize); - setRejectedExecutionHandler(new ShutdownRejectedExecutionHandler(getRejectedExecutionHandler())); - } - - public ThreadDecorator getThreadDecorator() { - return threadDecorator; - } - - public void setThreadDecorator(ThreadDecorator threadDecorator) { - this.threadDecorator=threadDecorator; - } - - public String dumpTaskQueue() { - return getQueue().toString(); - } + public void execute(Runnable command); + + /** + * Creates and executes a one-shot action that becomes enabled after the given delay. + * + * @param command the task to execute + * @param delay the time from now to delay execution + * @param unit the time unit of the delay parameter + * @return a ScheduledFuture representing pending completion of the task and whose get() method + * will return null upon completion + * @throws java.util.concurrent.RejectedExecutionException if the task cannot be scheduled for execution + * @throws NullPointerException if command is null + */ + public Future schedule(Runnable command, long delay, TimeUnit unit); + + + + /** + * Creates and executes a periodic action that becomes enabled first after the given initial delay, and + * subsequently with the given delay between the termination of one execution and the commencement of the next. + * If any execution of the task encounters an exception, subsequent executions are suppressed. + * Otherwise, the task will only terminate via cancellation or termination of the executor. + * + * @param command the task to execute + * @param initialDelay the time to delay first execution + * @param delay the delay between the termination of one execution and the commencement of the next + * @param unit the time unit of the initialDelay and delay parameters + * @return a ScheduledFuture representing pending completion of the task, and whose get() + * method will throw an exception upon cancellation + * @throws java.util.concurrent.RejectedExecutionException if the task cannot be scheduled for execution + * @throws NullPointerException if command is null + * @throws IllegalArgumentException if delay less than or equal to zero + */ + public Future scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit); + + + /** + * Creates and executes a periodic action that becomes enabled first + * after the given initial delay, and subsequently with the given + * period; that is executions will commence after + * initialDelay then initialDelay+period, then + * initialDelay + 2 * period, and so on. + * If any execution of the task + * encounters an exception, subsequent executions are suppressed. + * Otherwise, the task will only terminate via cancellation or + * termination of the executor. If any execution of this task + * takes longer than its period, then subsequent executions + * may start late, but will not concurrently execute. + * + * @param command the task to execute + * @param initialDelay the time to delay first execution + * @param period the period between successive executions + * @param unit the time unit of the initialDelay and period parameters + * @return a ScheduledFuture representing pending completion of + * the task, and whose get() method will throw an + * exception upon cancellation + * @throws java.util.concurrent.RejectedExecutionException if the task cannot be + * scheduled for execution + * @throws NullPointerException if command is null + * @throws IllegalArgumentException if period less than or equal to zero + */ + public Future scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit); + /** * Schedule a task for execution at varying intervals. After execution, the task will get rescheduled after - * {@link org.jgroups.util.TimeScheduler.Task#nextInterval()} milliseconds. The task is neve done until nextInterval() - * return a value <= 0 or the task is cancelled. + * {@link org.jgroups.util.TimeScheduler.Task#nextInterval()} milliseconds. The task is never done until + * nextInterval() return a value <= 0 or the task is cancelled. * @param task the task to execute - * @param relative scheduling scheme: true:
        - * Task is rescheduled relative to the last time it actually started execution

        - * false:
        Task is scheduled relative to its last execution schedule. This has the effect - * that the time between two consecutive executions of the task remains the same.

        - * Note that relative is always true; we always schedule the next execution relative to the last *actual* - * (not scheduled) execution - */ - public ScheduledFuture scheduleWithDynamicInterval(Task task, boolean relative) { - if(task == null) - throw new NullPointerException(); - - if (isShutdown()) - return null; - - TaskWrapper task_wrapper=new TaskWrapper(task); - task_wrapper.doSchedule(); // calls schedule() in ScheduledThreadPoolExecutor - return task_wrapper; - } - + */ + public Future scheduleWithDynamicInterval(Task task); + public void setThreadFactory(ThreadFactory factory); /** - * Add a task for execution at adjustable intervals - * @param t the task to execute + * Returns a list of tasks currently waiting for execution. If there are a lot of tasks, the returned string + * should probably only return the number of tasks rather than a full dump. + * @return */ - public ScheduledFuture scheduleWithDynamicInterval(Task t) { - return scheduleWithDynamicInterval(t, true); - } + public String dumpTimerTasks(); /** - * Answers the number of tasks currently in the queue. - * @return The number of tasks currently in the queue. + * Returns the configured core threads, or -1 if not applicable + * @return */ - public int size() { - return getQueue().size(); - } + public int getMinThreads(); + + /** Sets the core pool size. Can be ignored if not applicable */ + public void setMinThreads(int size); /** - * Start the scheduler, if it's suspended or stopped + * Returns the configured max threads, or -1 if not applicable + * @return */ - public void start() { - ; - } + public int getMaxThreads(); + /** Sets the max pool size. Can be ignored if not applicable */ + public void setMaxThreads(int size); - /** - * Stop the scheduler if it's running. Switch to stopped, if it's - * suspended. Clear the task queue, cancelling all un-executed tasks - * - * @throws InterruptedException if interrupted while waiting for thread - * to return - */ - public void stop() throws InterruptedException { - java.util.List tasks=shutdownNow(); - for(Runnable task: tasks) { - if(task instanceof Future) { - Future future=(Future)task; - future.cancel(true); - } - } - getQueue().clear(); - awaitTermination(Global.THREADPOOL_SHUTDOWN_WAIT_TIME, TimeUnit.MILLISECONDS); - } + /** Returns the keep alive time (in ms) of the thread pool, or -1 if not applicable */ + public long getKeepAliveTime(); + /** Sets the keep alive time (in ms) of the thread pool. Can be ignored if not applicable */ + public void setKeepAliveTime(long time); + /** + * Returns the current threads in the pool, or -1 if not applicable + * @return + */ + public int getCurrentThreads(); - @Override - protected void afterExecute(Runnable r, Throwable t) - { - try { - super.afterExecute(r, t); - } - finally { - if(threadDecorator != null) - threadDecorator.threadReleased(Thread.currentThread()); - } - } + /** + * Returns the number of tasks currently in the queue. + * @return The number of tasks currently in the queue. + */ + public int size(); - private class TaskWrapper implements Runnable, ScheduledFuture { - private final Task task; - private ScheduledFuture future; // cannot be null ! - private boolean cancelled=false; - - - public TaskWrapper(Task task) { - this.task=task; - } - - public ScheduledFuture getFuture() { - return future; - } - - public void run() { - try { - if(cancelled) { - if(future != null) - future.cancel(true); - return; - } - if(future != null && future.isCancelled()) - return; - task.run(); - } - catch(Throwable t) { - log.error("failed running task " + task, t); - } - - if(cancelled) { - if(future != null) - future.cancel(true); - return; - } - if(future != null && future.isCancelled()) - return; - - doSchedule(); - } - - - public void doSchedule() { - long next_interval=task.nextInterval(); - if(next_interval <= 0) { - if(log.isTraceEnabled()) - log.trace("task will not get rescheduled as interval is " + next_interval); - System.out.println("task will not get rescheduled as interval is " + next_interval); - } - else { - future=schedule(this, next_interval, TimeUnit.MILLISECONDS); - if(cancelled) - future.cancel(true); - } - } - - public int compareTo(Delayed o) { - long my_delay=future.getDelay(TimeUnit.MILLISECONDS), their_delay=o.getDelay(TimeUnit.MILLISECONDS); - return my_delay < their_delay? -1 : my_delay > their_delay? 1 : 0; - } - - public long getDelay(TimeUnit unit) { - return future != null? future.getDelay(unit) : -1; - } - - public boolean cancel(boolean mayInterruptIfRunning) { - cancelled=true; - if(future != null) - future.cancel(mayInterruptIfRunning); - return cancelled; - } - - public boolean isCancelled() { - return cancelled || (future != null && future.isCancelled()); - } - - public boolean isDone() { - return future == null || future.isDone(); - } - - public V get() throws InterruptedException, ExecutionException { - return null; - } - - public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - return null; - } - } + /** + * Stops the scheduler if running, cancelling all pending tasks + */ + public void stop(); + + /** Returns true if stop() has been called, false otherwise */ + public boolean isShutdown(); } diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/todo.txt libjgroups-java-2.12.2.Final/src/org/jgroups/util/todo.txt --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/todo.txt 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/todo.txt 2011-10-18 11:22:35.000000000 +0000 @@ -2,7 +2,6 @@ Todo List ========= -$Id: todo.txt,v 1.1.1.1 2003/09/09 01:24:12 belaban Exp $ Currently Proxy resides on the server system, listens on multiple diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/TopologyUUID.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/TopologyUUID.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/TopologyUUID.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/TopologyUUID.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,155 @@ +package org.jgroups.util; + +import org.jgroups.Global; + +import java.io.*; +import java.security.SecureRandom; + +/** + * Subclass of {@link org.jgroups.util.UUID} which adds 3 strings (siteId, rackId and machineId)as payload. + * An instance of this can be fed to {@link org.jgroups.JChannel#setAddressGenerator(org.jgroups.stack.AddressGenerator)}, + * with the address generator creating TopologyUUIDs.

        + * Mainly used by TopologyAwareConsistentHash in Infinispan (www.infinispan.org). + * @author Bela Ban + */ +public class TopologyUUID extends UUID { + private static final long serialVersionUID = 6057688946243812544L; + protected String site_id; + protected String rack_id; + protected String machine_id; + + public TopologyUUID() { + } + + protected TopologyUUID(byte[] data, String site_id, String rack_id, String machine_id) { + super(data); + this.site_id=site_id; + this.rack_id=rack_id; + this.machine_id=machine_id; + } + + public static TopologyUUID randomUUID(String site_id, String rack_id, String machine_id) { + return new TopologyUUID(generateRandomBytes(), site_id, rack_id, machine_id); + } + + public static TopologyUUID randomUUID(String logical_name, String site_id, String rack_id, String machine_id) { + TopologyUUID retval=new TopologyUUID(generateRandomBytes(), site_id, rack_id, machine_id); + UUID.add(retval, logical_name); + return retval; + } + + public String getSiteId() { + return site_id; + } + + public void setSiteId(String site_id) { + this.site_id=site_id; + } + + public String getRackId() { + return rack_id; + } + + public void setRackId(String rack_id) { + this.rack_id=rack_id; + } + + public String getMachineId() { + return machine_id; + } + + public void setMachineId(String machine_id) { + this.machine_id=machine_id; + } + + public boolean isSameSite(TopologyUUID addr) { + return addr != null + && ((site_id != null && site_id.equals(addr.getSiteId())) || (site_id == null && addr.getSiteId() == null)); + } + + public boolean isSameRack(TopologyUUID addr) { + return addr != null + && ((rack_id != null && rack_id.equals(addr.getRackId())) || (rack_id == null && addr.getRackId() == null)); + } + + public boolean isSameMachine(TopologyUUID addr) { + return addr != null + && ((machine_id != null && machine_id.equals(addr.getMachineId())) || (machine_id == null && addr.getMachineId() == null)); + } + + + public int size() { + int retval=super.size() + 3 * Global.BYTE_SIZE; + if(site_id != null) + retval+= site_id.length() +2; + if(rack_id != null) + retval+=rack_id.length() +2; + if(machine_id != null) + retval+=machine_id.length() +2; + return retval; + } + + public void writeTo(DataOutputStream out) throws IOException { + super.writeTo(out); + Util.writeString(site_id, out); + Util.writeString(rack_id, out); + Util.writeString(machine_id, out); + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + super.readFrom(in); + site_id=Util.readString(in); + rack_id=Util.readString(in); + machine_id=Util.readString(in); + } + + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + super.readExternal(in); + site_id=(String)in.readObject(); + rack_id=(String)in.readObject(); + machine_id=(String)in.readObject(); + } + + public void writeExternal(ObjectOutput out) throws IOException { + super.writeExternal(out); + out.writeObject(site_id); + out.writeObject(rack_id); + out.writeObject(machine_id); + } + + public String toString() { + if(print_uuids) + return toStringLong() + (site_id == null? "" : "(" + site_id + ")"); + return super.toString() + (site_id == null? "" : "(" + site_id + ")"); + } + + public String toStringDetailed() { + if(print_uuids) + return toStringLong() + "(" + printDetails() + ")"; + return super.toString() + "(" + printDetails() + ")"; + } + + + protected static byte[] generateRandomBytes() { + SecureRandom ng=numberGenerator; + if(ng == null) + numberGenerator=ng=new SecureRandom(); + + byte[] randomBytes=new byte[16]; + ng.nextBytes(randomBytes); + return randomBytes; + } + + protected String printDetails() { + StringBuilder sb=new StringBuilder(); + if(site_id != null) + sb.append(site_id); + sb.append(":"); + if(rack_id != null) + sb.append(rack_id); + sb.append(":"); + if(machine_id != null) + sb.append(machine_id); + return sb.toString(); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/Triple.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/Triple.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/Triple.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/Triple.java 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ * Holds 3 values, useful when we have a map with a key, but more than 1 value and we don't want to create a separate * holder object for the values, and don't want to pass the values as a list or array. * @author Bela Ban - * @version $Id: Triple.java,v 1.1 2007/11/23 12:50:49 belaban Exp $ */ public class Triple { private V1 val1; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/Tuple.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/Tuple.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/Tuple.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/Tuple.java 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ * Holds 2 values, useful when we have a map with a key, but more than 1 value and we don't want to create a separate * holder object for the values, and don't want to pass the values as a list or array. * @author Bela Ban - * @version $Id: Tuple.java,v 1.1 2007/11/23 12:50:48 belaban Exp $ */ public class Tuple { private V1 val1; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/UnmodifiableVector.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/UnmodifiableVector.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/UnmodifiableVector.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/UnmodifiableVector.java 2011-10-18 11:22:35.000000000 +0000 @@ -5,7 +5,6 @@ /** * Vector which cannot be modified * @author Bela Ban - * @version $Id: UnmodifiableVector.java,v 1.3 2006/12/09 22:59:34 belaban Exp $ */ public class UnmodifiableVector extends Vector { Vector v; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/Util.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/Util.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/Util.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/Util.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,40 +1,49 @@ package org.jgroups.util; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.*; import org.jgroups.auth.AuthToken; import org.jgroups.blocks.Connection; import org.jgroups.conf.ClassConfigurator; -import org.jgroups.protocols.FD; -import org.jgroups.protocols.PingHeader; -import org.jgroups.protocols.PingRsp; +import org.jgroups.jmx.JmxConfigurator; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.protocols.*; +import org.jgroups.protocols.pbcast.FLUSH; +import org.jgroups.protocols.pbcast.GMS; import org.jgroups.stack.IpAddress; +import org.jgroups.stack.ProtocolStack; import javax.management.MBeanServer; import javax.management.MBeanServerFactory; import java.io.*; +import java.lang.annotation.Annotation; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; +import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.net.*; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.security.MessageDigest; import java.text.NumberFormat; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Collection of various utility routines that can not be assigned to other classes. * @author Bela Ban - * @version $Id: Util.java,v 1.183 2008/12/11 12:55:51 vlada Exp $ */ public class Util { private static NumberFormat f; - private static Map PRIMITIVE_TYPES=new HashMap(10); + private static Map PRIMITIVE_TYPES=new HashMap(15); private static final byte TYPE_NULL = 0; private static final byte TYPE_STREAMABLE = 1; private static final byte TYPE_SERIALIZABLE = 2; @@ -54,7 +63,15 @@ public static final int MAX_PORT=65535; // highest port allocatable static boolean resolve_dns=false; - static boolean JGROUPS_COMPAT=false; + private static short COUNTER=1; + + private static Pattern METHOD_NAME_TO_ATTR_NAME_PATTERN=Pattern.compile("[A-Z]+"); + private static Pattern ATTR_NAME_TO_METHOD_NAME_PATTERN=Pattern.compile("_."); + + + protected static int CCHM_INITIAL_CAPACITY=16; + protected static float CCHM_LOAD_FACTOR=0.75f; + protected static int CCHM_CONCURRENCY_LEVEL=16; /** * Global thread group to which all (most!) JGroups threads belong @@ -62,6 +79,10 @@ private static ThreadGroup GLOBAL_GROUP=new ThreadGroup("JGroups") { public void uncaughtException(Thread t, Throwable e) { LogFactory.getLog("org.jgroups").error("uncaught exception in " + t + " (thread group=" + GLOBAL_GROUP + " )", e); + final ThreadGroup tgParent = getParent(); + if(tgParent != null) { + tgParent.uncaughtException(t,e); + } } }; @@ -69,6 +90,10 @@ return GLOBAL_GROUP; } + public static enum AddressScope {GLOBAL, SITE_LOCAL, LINK_LOCAL, LOOPBACK, NON_LOOPBACK}; + + private static StackType ip_stack_type=_getIpStackType(); + static { /* Trying to get value of resolve_dns. PropertyPermission not granted if @@ -81,15 +106,9 @@ } f=NumberFormat.getNumberInstance(); f.setGroupingUsed(false); + // f.setMinimumFractionDigits(2); f.setMaximumFractionDigits(2); - try { - String tmp=Util.getProperty(new String[]{Global.MARSHALLING_COMPAT}, null, null, false, "false"); - JGROUPS_COMPAT=Boolean.valueOf(tmp).booleanValue(); - } - catch (SecurityException ex){ - } - PRIMITIVE_TYPES.put(Boolean.class, new Byte(TYPE_BOOLEAN)); PRIMITIVE_TYPES.put(Byte.class, new Byte(TYPE_BYTE)); PRIMITIVE_TYPES.put(Character.class, new Byte(TYPE_CHAR)); @@ -100,8 +119,28 @@ PRIMITIVE_TYPES.put(Short.class, new Byte(TYPE_SHORT)); PRIMITIVE_TYPES.put(String.class, new Byte(TYPE_STRING)); PRIMITIVE_TYPES.put(byte[].class, new Byte(TYPE_BYTEARRAY)); - } + if(ip_stack_type == StackType.Unknown) + ip_stack_type=StackType.IPv6; + + try { + String cchm_initial_capacity=System.getProperty(Global.CCHM_INITIAL_CAPACITY); + if(cchm_initial_capacity != null) + CCHM_INITIAL_CAPACITY=Integer.valueOf(cchm_initial_capacity); + } catch(SecurityException ex) {} + + try { + String cchm_load_factor=System.getProperty(Global.CCHM_LOAD_FACTOR); + if(cchm_load_factor != null) + CCHM_LOAD_FACTOR=Float.valueOf(cchm_load_factor); + } catch(SecurityException ex) {} + + try { + String cchm_concurrency_level=System.getProperty(Global.CCHM_CONCURRENCY_LEVEL); + if(cchm_concurrency_level != null) + CCHM_CONCURRENCY_LEVEL=Integer.valueOf(cchm_concurrency_level); + } catch(SecurityException ex) {} + } public static void assertTrue(boolean condition) { @@ -161,27 +200,82 @@ } - public static void assertNull(Object val) { - assertNotNull(null, val); + + /** + * Blocks until all channels have the same view + * @param timeout How long to wait (max in ms) + * @param interval Check every interval ms + * @param channels The channels which should form the view. The expected view size is channels.length. + * Must be non-null + */ + public static void blockUntilViewsReceived(long timeout, long interval, Channel ... channels) throws TimeoutException { + final int expected_size=channels.length; + + if(interval > timeout) + throw new IllegalArgumentException("interval needs to be smaller than timeout"); + final long end_time=System.currentTimeMillis() + timeout; + while(System.currentTimeMillis() < end_time) { + boolean all_ok=true; + for(Channel ch: channels) { + View view=ch.getView(); + if(view == null || view.size() != expected_size) { + all_ok=false; + break; + } + } + if(all_ok) + return; + Util.sleep(interval); + } + throw new TimeoutException(); + } + + + public static void addFlush(Channel ch, FLUSH flush) { + if(ch == null || flush == null) + throw new IllegalArgumentException("ch and flush have to be non-null"); + ProtocolStack stack=ch.getProtocolStack(); + stack.insertProtocolAtTop(flush); } + public static void setScope(Message msg, short scope) { + SCOPE.ScopeHeader hdr=SCOPE.ScopeHeader.createMessageHeader(scope); + msg.putHeader(Global.SCOPE_ID, hdr); + msg.setFlag(Message.SCOPED); + } + + public static short getScope(Message msg) { + SCOPE.ScopeHeader hdr=(SCOPE.ScopeHeader)msg.getHeader(Global.SCOPE_ID); + return hdr != null? hdr.getScope() : 0; + } + + public static SCOPE.ScopeHeader getScopeHeader(Message msg) { + return (SCOPE.ScopeHeader)msg.getHeader(Global.SCOPE_ID); + } + + /** - * Verifies that val is <= max memory - * @param buf_name - * @param val + * Utility method. If the dest address is IPv6, convert scoped link-local addrs into unscoped ones + * @param sock + * @param dest + * @param sock_conn_timeout + * @throws IOException */ - public static void checkBufferSize(String buf_name, long val) { - // sanity check that max_credits doesn't exceed memory allocated to VM by -Xmx - long max_mem=Runtime.getRuntime().maxMemory(); - if(val > max_mem) { - throw new IllegalArgumentException(buf_name + "(" + Util.printBytes(val) + ") exceeds max memory allocated to VM (" + - Util.printBytes(max_mem) + ")"); + public static void connect(Socket sock, SocketAddress dest, int sock_conn_timeout) throws IOException { + if(dest instanceof InetSocketAddress) { + InetAddress addr=((InetSocketAddress)dest).getAddress(); + if(addr instanceof Inet6Address) { + Inet6Address tmp=(Inet6Address)addr; + if(tmp.getScopeId() != 0) { + dest=new InetSocketAddress(InetAddress.getByAddress(tmp.getAddress()), ((InetSocketAddress)dest).getPort()); + } + } } + sock.connect(dest, sock_conn_timeout); } - public static void close(InputStream inp) { if(inp != null) try {inp.close();} catch(IOException e) {} @@ -230,6 +324,48 @@ } } + /** Drops messages to/from other members and then closes the channel. Note that this member won't get excluded from + * the view until failure detection has kicked in and the new coord installed the new view */ + public static void shutdown(Channel ch) throws Exception { + DISCARD discard=new DISCARD(); + discard.setLocalAddress(ch.getAddress()); + discard.setDiscardAll(true); + ProtocolStack stack=ch.getProtocolStack(); + TP transport=stack.getTransport(); + stack.insertProtocol(discard, ProtocolStack.ABOVE, transport.getClass()); + + //abruptly shutdown FD_SOCK just as in real life when member gets killed non gracefully + FD_SOCK fd = (FD_SOCK) ch.getProtocolStack().findProtocol("FD_SOCK"); + if(fd != null) + fd.stopServerSocket(false); + + View view=ch.getView(); + if (view != null) { + ViewId vid = view.getViewId(); + List

        members = Arrays.asList(ch.getAddress()); + + ViewId new_vid = new ViewId(ch.getAddress(), vid.getId() + 1); + View new_view = new View(new_vid, members); + + // inject view in which the shut down member is the only element + GMS gms = (GMS) stack.findProtocol(GMS.class); + gms.installView(new_view); + } + Util.close(ch); + } + + + public static byte setFlag(byte bits, byte flag) { + return bits |= flag; + } + + public static boolean isFlagSet(byte bits, byte flag) { + return (bits & flag) == flag; + } + + public static byte clearFlags(byte bits, byte flag) { + return bits &= ~flag; + } /** @@ -237,95 +373,62 @@ */ public static Object objectFromByteBuffer(byte[] buffer) throws Exception { if(buffer == null) return null; - if(JGROUPS_COMPAT) - return oldObjectFromByteBuffer(buffer); return objectFromByteBuffer(buffer, 0, buffer.length); } public static Object objectFromByteBuffer(byte[] buffer, int offset, int length) throws Exception { if(buffer == null) return null; - if(JGROUPS_COMPAT) - return oldObjectFromByteBuffer(buffer, offset, length); Object retval=null; - InputStream in=null; - ByteArrayInputStream in_stream=new ByteArrayInputStream(buffer, offset, length); - byte b=(byte)in_stream.read(); + byte type=buffer[offset]; - try { - switch(b) { - case TYPE_NULL: - return null; - case TYPE_STREAMABLE: - in=new DataInputStream(in_stream); - retval=readGenericStreamable((DataInputStream)in); - break; - case TYPE_SERIALIZABLE: // the object is Externalizable or Serializable - in=new ObjectInputStream(in_stream); // changed Nov 29 2004 (bela) + + switch(type) { + case TYPE_NULL: + return null; + case TYPE_STREAMABLE: + ByteArrayInputStream in_stream=new ExposedByteArrayInputStream(buffer, offset+1, length-1); + InputStream in=new DataInputStream(in_stream); + retval=readGenericStreamable((DataInputStream)in); + break; + case TYPE_SERIALIZABLE: // the object is Externalizable or Serializable + in_stream=new ExposedByteArrayInputStream(buffer, offset+1, length-1); + in=new ObjectInputStream(in_stream); // changed Nov 29 2004 (bela) + try { retval=((ObjectInputStream)in).readObject(); - break; - case TYPE_BOOLEAN: - in=new DataInputStream(in_stream); - retval=Boolean.valueOf(((DataInputStream)in).readBoolean()); - break; - case TYPE_BYTE: - in=new DataInputStream(in_stream); - retval=new Byte(((DataInputStream)in).readByte()); - break; - case TYPE_CHAR: - in=new DataInputStream(in_stream); - retval=new Character(((DataInputStream)in).readChar()); - break; - case TYPE_DOUBLE: - in=new DataInputStream(in_stream); - retval=new Double(((DataInputStream)in).readDouble()); - break; - case TYPE_FLOAT: - in=new DataInputStream(in_stream); - retval=new Float(((DataInputStream)in).readFloat()); - break; - case TYPE_INT: - in=new DataInputStream(in_stream); - retval=new Integer(((DataInputStream)in).readInt()); - break; - case TYPE_LONG: - in=new DataInputStream(in_stream); - retval=new Long(((DataInputStream)in).readLong()); - break; - case TYPE_SHORT: - in=new DataInputStream(in_stream); - retval=new Short(((DataInputStream)in).readShort()); - break; - case TYPE_STRING: - in=new DataInputStream(in_stream); - if(((DataInputStream)in).readBoolean()) { // large string - ObjectInputStream ois=new ObjectInputStream(in); - try { - return ois.readObject(); - } - finally { - ois.close(); - } - } - else { - retval=((DataInputStream)in).readUTF(); - } - break; - case TYPE_BYTEARRAY: - in=new DataInputStream(in_stream); - int len=((DataInputStream)in).readInt(); - byte[] tmp=new byte[len]; - in.read(tmp, 0, tmp.length); - retval=tmp; - break; - default: - throw new IllegalArgumentException("type " + b + " is invalid"); - } - return retval; - } - finally { - Util.close(in); + } + finally { + Util.close(in); + } + break; + case TYPE_BOOLEAN: + return ByteBuffer.wrap(buffer, offset + 1, length - 1).get() == 1; + case TYPE_BYTE: + return ByteBuffer.wrap(buffer, offset + 1, length - 1).get(); + case TYPE_CHAR: + return ByteBuffer.wrap(buffer, offset + 1, length - 1).getChar(); + case TYPE_DOUBLE: + return ByteBuffer.wrap(buffer, offset + 1, length - 1).getDouble(); + case TYPE_FLOAT: + return ByteBuffer.wrap(buffer, offset + 1, length - 1).getFloat(); + case TYPE_INT: + return ByteBuffer.wrap(buffer, offset + 1, length - 1).getInt(); + case TYPE_LONG: + return ByteBuffer.wrap(buffer, offset + 1, length - 1).getLong(); + case TYPE_SHORT: + return ByteBuffer.wrap(buffer, offset + 1, length - 1).getShort(); + case TYPE_STRING: + byte[] tmp=new byte[length -1]; + System.arraycopy(buffer, offset +1, tmp, 0, length -1); + return new String(tmp); + case TYPE_BYTEARRAY: + tmp=new byte[length -1]; + System.arraycopy(buffer, offset +1, tmp, 0, length -1); + return tmp; + default: + throw new IllegalArgumentException("type " + type + " is invalid"); } + return retval; } @@ -333,21 +436,77 @@ /** * Serializes/Streams an object into a byte buffer. - * The object has to implement interface Serializable or Externalizable - * or Streamable. Only Streamable objects are interoperable w/ jgroups-me + * The object has to implement interface Serializable or Externalizable or Streamable. */ public static byte[] objectToByteBuffer(Object obj) throws Exception { + if(obj == null) + return ByteBuffer.allocate(Global.BYTE_SIZE).put(TYPE_NULL).array(); - if(JGROUPS_COMPAT) - return oldObjectToByteBuffer(obj); + if(obj instanceof Streamable) { + final ExposedByteArrayOutputStream out_stream=new ExposedByteArrayOutputStream(128); + final ExposedDataOutputStream out=new ExposedDataOutputStream(out_stream); + out_stream.write(TYPE_STREAMABLE); + writeGenericStreamable((Streamable)obj, out); + return out_stream.toByteArray(); + } - byte[] result=null; - final ByteArrayOutputStream out_stream=new ByteArrayOutputStream(512); + Byte type=PRIMITIVE_TYPES.get(obj.getClass()); + if(type == null) { // will throw an exception if object is not serializable + final ExposedByteArrayOutputStream out_stream=new ExposedByteArrayOutputStream(128); + out_stream.write(TYPE_SERIALIZABLE); + ObjectOutputStream out=new ObjectOutputStream(out_stream); + out.writeObject(obj); + out.close(); + return out_stream.toByteArray(); + } + + switch(type.byteValue()) { + case TYPE_BOOLEAN: + return ByteBuffer.allocate(Global.BYTE_SIZE * 2).put(TYPE_BOOLEAN) + .put(((Boolean)obj).booleanValue()? (byte)1 : (byte)0).array(); + case TYPE_BYTE: + return ByteBuffer.allocate(Global.BYTE_SIZE *2).put(TYPE_BYTE).put(((Byte)obj).byteValue()).array(); + case TYPE_CHAR: + return ByteBuffer.allocate(Global.BYTE_SIZE *3).put(TYPE_CHAR).putChar(((Character)obj).charValue()).array(); + case TYPE_DOUBLE: + return ByteBuffer.allocate(Global.BYTE_SIZE + Global.DOUBLE_SIZE).put(TYPE_DOUBLE) + .putDouble(((Double)obj).doubleValue()).array(); + case TYPE_FLOAT: + return ByteBuffer.allocate(Global.BYTE_SIZE + Global.FLOAT_SIZE).put(TYPE_FLOAT) + .putFloat(((Float)obj).floatValue()).array(); + case TYPE_INT: + return ByteBuffer.allocate(Global.BYTE_SIZE + Global.INT_SIZE).put(TYPE_INT) + .putInt(((Integer)obj).intValue()).array(); + case TYPE_LONG: + return ByteBuffer.allocate(Global.BYTE_SIZE + Global.LONG_SIZE).put(TYPE_LONG) + .putLong(((Long)obj).longValue()).array(); + case TYPE_SHORT: + return ByteBuffer.allocate(Global.BYTE_SIZE + Global.SHORT_SIZE).put(TYPE_SHORT) + .putShort(((Short)obj).shortValue()).array(); + case TYPE_STRING: + String str=(String)obj; + byte[] buf=new byte[str.length()]; + for(int i=0; i < buf.length; i++) + buf[i]=(byte)str.charAt(i); + return ByteBuffer.allocate(Global.BYTE_SIZE + buf.length).put(TYPE_STRING).put(buf, 0, buf.length).array(); + case TYPE_BYTEARRAY: + buf=(byte[])obj; + return ByteBuffer.allocate(Global.BYTE_SIZE + buf.length).put(TYPE_BYTEARRAY) + .put(buf, 0, buf.length).array(); + default: + throw new IllegalArgumentException("type " + type + " is invalid"); + } + + } + + + /* public static Buffer objectToBuffer(Object obj) throws Exception { + final ExposedByteArrayOutputStream out_stream=new ExposedByteArrayOutputStream(512); if(obj == null) { out_stream.write(TYPE_NULL); out_stream.flush(); - return out_stream.toByteArray(); + return out_stream.getBuffer(); } OutputStream out=null; @@ -355,12 +514,12 @@ try { if(obj instanceof Streamable) { // use Streamable if we can out_stream.write(TYPE_STREAMABLE); - out=new DataOutputStream(out_stream); + out=new ExposedDataOutputStream(out_stream); writeGenericStreamable((Streamable)obj, (DataOutputStream)out); } else if((type=PRIMITIVE_TYPES.get(obj.getClass())) != null) { out_stream.write(type.byteValue()); - out=new DataOutputStream(out_stream); + out=new ExposedDataOutputStream(out_stream); switch(type.byteValue()) { case TYPE_BOOLEAN: ((DataOutputStream)out).writeBoolean(((Boolean)obj).booleanValue()); @@ -421,9 +580,8 @@ finally { Util.close(out); } - result=out_stream.toByteArray(); - return result; - } + return out_stream.getBuffer(); + }*/ @@ -524,25 +682,25 @@ retval=Boolean.valueOf(in.readBoolean()); break; case TYPE_BYTE: - retval=new Byte(in.readByte()); + retval=Byte.valueOf(in.readByte()); break; case TYPE_CHAR: - retval=new Character(in.readChar()); + retval=Character.valueOf(in.readChar()); break; case TYPE_DOUBLE: - retval=new Double(in.readDouble()); + retval=Double.valueOf(in.readDouble()); break; case TYPE_FLOAT: - retval=new Float(in.readFloat()); + retval=Float.valueOf(in.readFloat()); break; case TYPE_INT: - retval=new Integer(in.readInt()); + retval=Integer.valueOf(in.readInt()); break; case TYPE_LONG: - retval=new Long(in.readLong()); + retval=Long.valueOf(in.readLong()); break; case TYPE_SHORT: - retval=new Short(in.readShort()); + retval=Short.valueOf(in.readShort()); break; case TYPE_STRING: if(in.readBoolean()) { // large string @@ -561,7 +719,7 @@ case TYPE_BYTEARRAY: int len=in.readInt(); byte[] tmpbuf=new byte[len]; - in.read(tmpbuf, 0, tmpbuf.length); + in.readFully(tmpbuf, 0, tmpbuf.length); retval=tmpbuf; break; default: @@ -573,73 +731,10 @@ - /** For backward compatibility in JBoss 4.0.2 */ - public static Object oldObjectFromByteBuffer(byte[] buffer) throws Exception { - if(buffer == null) return null; - return oldObjectFromByteBuffer(buffer, 0, buffer.length); - } - - public static Object oldObjectFromByteBuffer(byte[] buffer, int offset, int length) throws Exception { - if(buffer == null) return null; - Object retval=null; - - try { // to read the object as an Externalizable - ByteArrayInputStream in_stream=new ByteArrayInputStream(buffer, offset, length); - ObjectInputStream in=new ObjectInputStream(in_stream); // changed Nov 29 2004 (bela) - retval=in.readObject(); - in.close(); - } - catch(StreamCorruptedException sce) { - try { // is it Streamable? - ByteArrayInputStream in_stream=new ByteArrayInputStream(buffer, offset, length); - DataInputStream in=new DataInputStream(in_stream); - retval=readGenericStreamable(in); - in.close(); - } - catch(Exception ee) { - IOException tmp=new IOException("unmarshalling failed"); - tmp.initCause(ee); - throw tmp; - } - } - - if(retval == null) - return null; - return retval; - } - - - - - /** - * Serializes/Streams an object into a byte buffer. - * The object has to implement interface Serializable or Externalizable - * or Streamable. Only Streamable objects are interoperable w/ jgroups-me - */ - public static byte[] oldObjectToByteBuffer(Object obj) throws Exception { - byte[] result=null; - final ByteArrayOutputStream out_stream=new ByteArrayOutputStream(512); - if(obj instanceof Streamable) { // use Streamable if we can - DataOutputStream out=new DataOutputStream(out_stream); - writeGenericStreamable((Streamable)obj, out); - out.close(); - } - else { - ObjectOutputStream out=new ObjectOutputStream(out_stream); - out.writeObject(obj); - out.close(); - } - result=out_stream.toByteArray(); - return result; - } - - - - public static Streamable streamableFromByteBuffer(Class cl, byte[] buffer) throws Exception { if(buffer == null) return null; Streamable retval=null; - ByteArrayInputStream in_stream=new ByteArrayInputStream(buffer); + ByteArrayInputStream in_stream=new ExposedByteArrayInputStream(buffer); DataInputStream in=new DataInputStream(in_stream); // changed Nov 29 2004 (bela) retval=(Streamable)cl.newInstance(); retval.readFrom(in); @@ -651,7 +746,7 @@ public static Streamable streamableFromByteBuffer(Class cl, byte[] buffer, int offset, int length) throws Exception { if(buffer == null) return null; Streamable retval=null; - ByteArrayInputStream in_stream=new ByteArrayInputStream(buffer, offset, length); + ByteArrayInputStream in_stream=new ExposedByteArrayInputStream(buffer, offset, length); DataInputStream in=new DataInputStream(in_stream); // changed Nov 29 2004 (bela) retval=(Streamable)cl.newInstance(); retval.readFrom(in); @@ -661,8 +756,8 @@ public static byte[] streamableToByteBuffer(Streamable obj) throws Exception { byte[] result=null; - final ByteArrayOutputStream out_stream=new ByteArrayOutputStream(512); - DataOutputStream out=new DataOutputStream(out_stream); + final ByteArrayOutputStream out_stream=new ExposedByteArrayOutputStream(512); + DataOutputStream out=new ExposedDataOutputStream(out_stream); obj.writeTo(out); result=out_stream.toByteArray(); out.close(); @@ -672,20 +767,15 @@ public static byte[] collectionToByteBuffer(Collection
        c) throws Exception { byte[] result=null; - final ByteArrayOutputStream out_stream=new ByteArrayOutputStream(512); - DataOutputStream out=new DataOutputStream(out_stream); + final ByteArrayOutputStream out_stream=new ExposedByteArrayOutputStream(512); + DataOutputStream out=new ExposedDataOutputStream(out_stream); Util.writeAddresses(c, out); result=out_stream.toByteArray(); out.close(); return result; } - public static int size(Address addr) { - int retval=Global.BYTE_SIZE; // presence byte - if(addr != null) - retval+=addr.size() + Global.BYTE_SIZE; // plus type of address - return retval; - } + public static void writeAuthToken(AuthToken token, DataOutputStream out) throws IOException{ Util.writeString(token.getName(), out); @@ -705,29 +795,87 @@ } } - public static void writeAddress(Address addr, DataOutputStream out) throws IOException { - if(addr == null) { + + public static void writeView(View view, DataOutputStream out) throws IOException { + if(view == null) { out.writeBoolean(false); return; } + out.writeBoolean(true); + out.writeBoolean(view instanceof MergeView); + view.writeTo(out); + } + public static View readView(DataInputStream in) throws IOException, InstantiationException, IllegalAccessException { + if(in.readBoolean() == false) + return null; + boolean isMergeView=in.readBoolean(); + View view; + if(isMergeView) + view=new MergeView(); + else + view=new View(); + view.readFrom(in); + return view; + } + + public static void writeViewId(ViewId vid, DataOutputStream out) throws IOException { + if(vid == null) { + out.writeBoolean(false); + return; + } out.writeBoolean(true); - if(addr instanceof IpAddress) { - // regular case, we don't need to include class information about the type of Address, e.g. JmsAddress - out.writeBoolean(true); - addr.writeTo(out); + vid.writeTo(out); + } + + public static ViewId readViewId(DataInputStream in) throws IOException, InstantiationException, IllegalAccessException { + if(in.readBoolean() == false) + return null; + ViewId retval=new ViewId(); + retval.readFrom(in); + return retval; + } + + + public static void writeAddress(Address addr, DataOutputStream out) throws IOException { + byte flags=0; + boolean streamable_addr=true; + + if(addr == null) { + flags=Util.setFlag(flags, Address.NULL); + out.writeByte(flags); + return; + } + + Class clazz=addr.getClass(); + if(clazz.equals(UUID.class)) { + flags=Util.setFlag(flags, Address.UUID_ADDR); + } + else if(clazz.equals(IpAddress.class)) { + flags=Util.setFlag(flags, Address.IP_ADDR); } else { - out.writeBoolean(false); - writeOtherAddress(addr, out); + streamable_addr=false; } + + out.writeByte(flags); + if(streamable_addr) + addr.writeTo(out); + else + writeOtherAddress(addr, out); } public static Address readAddress(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { - Address addr=null; - if(in.readBoolean() == false) + byte flags=in.readByte(); + if(Util.isFlagSet(flags, Address.NULL)) return null; - if(in.readBoolean()) { + + Address addr; + if(Util.isFlagSet(flags, Address.UUID_ADDR)) { + addr=new UUID(); + addr.readFrom(in); + } + else if(Util.isFlagSet(flags, Address.IP_ADDR)) { addr=new IpAddress(); addr.readFrom(in); } @@ -737,21 +885,39 @@ return addr; } - private static Address readOtherAddress(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { - int b=in.read(); - short magic_number; - String classname; - Class cl=null; - Address addr; - if(b == 1) { - magic_number=in.readShort(); - cl=ClassConfigurator.get(magic_number); - } - else { - classname=in.readUTF(); - cl=ClassConfigurator.get(classname); + public static int size(Address addr) { + int retval=Global.BYTE_SIZE; // flags + if(addr != null) { + if(addr instanceof UUID || addr instanceof IpAddress) + retval+=addr.size(); + else { + retval+=Global.SHORT_SIZE; // magic number + retval+=addr.size(); + } } - addr=(Address)cl.newInstance(); + return retval; + } + + public static int size(View view) { + int retval=Global.BYTE_SIZE; // presence + if(view != null) + retval+=view.serializedSize() + Global.BYTE_SIZE; // merge view or regular view + return retval; + } + + public static int size(ViewId vid) { + int retval=Global.BYTE_SIZE; // presence + if(vid != null) + retval+=vid.serializedSize(); + return retval; + } + + private static Address readOtherAddress(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + short magic_number=in.readShort(); + Class cl=ClassConfigurator.get(magic_number); + if(cl == null) + throw new RuntimeException("class for magic number " + magic_number + " not found"); + Address addr=(Address)cl.newInstance(); addr.readFrom(in); return addr; } @@ -760,16 +926,10 @@ short magic_number=ClassConfigurator.getMagicNumber(addr.getClass()); // write the class info - if(magic_number == -1) { - out.write(0); - out.writeUTF(addr.getClass().getName()); - } - else { - out.write(1); - out.writeShort(magic_number); - } + if(magic_number == -1) + throw new RuntimeException("magic number " + magic_number + " not found"); - // write the data itself + out.writeShort(magic_number); addr.writeTo(out); } @@ -779,7 +939,7 @@ * @param out * @throws IOException */ - public static void writeAddresses(Collection
        v, DataOutputStream out) throws IOException { + public static void writeAddresses(Collection v, DataOutputStream out) throws IOException { if(v == null) { out.writeShort(-1); return; @@ -799,7 +959,7 @@ * @throws IllegalAccessException * @throws InstantiationException */ - public static Collection
        readAddresses(DataInputStream in, Class cl) throws IOException, IllegalAccessException, InstantiationException { + public static Collection readAddresses(DataInputStream in, Class cl) throws IOException, IllegalAccessException, InstantiationException { short length=in.readShort(); if(length < 0) return null; Collection
        retval=(Collection
        )cl.newInstance(); @@ -818,7 +978,7 @@ * @param addrs Collection
        * @return long size */ - public static long size(Collection
        addrs) { + public static long size(Collection addrs) { int retval=Global.SHORT_SIZE; // number of elements if(addrs != null && !addrs.isEmpty()) { Address addr=addrs.iterator().next(); @@ -913,30 +1073,60 @@ } } - public static void writeObject(Object obj, DataOutputStream out) throws Exception { - if(obj == null || !(obj instanceof Streamable)) { - byte[] buf=objectToByteBuffer(obj); - out.writeShort(buf.length); - out.write(buf, 0, buf.length); - } - else { - out.writeShort(-1); - writeGenericStreamable((Streamable)obj, out); - } + public static void writeClass(Class classObject, DataOutputStream out) throws IOException { + short magic_number=ClassConfigurator.getMagicNumber(classObject); + // write the magic number or the class name + if(magic_number == -1) { + out.writeBoolean(false); + out.writeUTF(classObject.getName()); + } + else { + out.writeBoolean(true); + out.writeShort(magic_number); + } } - public static Object readObject(DataInputStream in) throws Exception { - short len=in.readShort(); - Object retval=null; - if(len == -1) { - retval=readGenericStreamable(in); + public static Class readClass(DataInputStream in) throws IOException, ClassNotFoundException { + Class clazz; + boolean use_magic_number = in.readBoolean(); + if(use_magic_number) { + short magic_number=in.readShort(); + clazz=ClassConfigurator.get(magic_number); + if (clazz==null) { + throw new ClassNotFoundException("Class for magic number "+magic_number+" cannot be found."); + } } else { - byte[] buf=new byte[len]; - in.readFully(buf, 0, len); - retval=objectFromByteBuffer(buf); + String classname=in.readUTF(); + clazz=ClassConfigurator.get(classname); + if (clazz==null) { + throw new ClassNotFoundException(classname); + } } - return retval; + + return clazz; + } + + public static void writeObject(Object obj, DataOutputStream out) throws Exception { + if(obj instanceof Streamable) { + out.writeInt(-1); + writeGenericStreamable((Streamable)obj, out); + } + else { + byte[] buf=objectToByteBuffer(obj); + out.writeInt(buf.length); + out.write(buf, 0, buf.length); + } + } + + public static Object readObject(DataInputStream in) throws Exception { + int len=in.readInt(); + if(len == -1) + return readGenericStreamable(in); + + byte[] buf=new byte[len]; + in.readFully(buf, 0, len); + return objectFromByteBuffer(buf); } @@ -976,7 +1166,7 @@ if(length == -1) return null; byte[] tmp=new byte[length]; - in.read(tmp, 0, tmp.length); + in.readFully(tmp, 0, tmp.length); return new String(tmp, 0, tmp.length); } @@ -1029,14 +1219,39 @@ return sb.toString(); } + public static String readStringFromStdin(String message) throws Exception { + System.out.print(message); + System.out.flush(); + System.in.skip(System.in.available()); + BufferedReader reader=new BufferedReader(new InputStreamReader(System.in)); + return reader.readLine().trim(); + } + + public static long readLongFromStdin(String message) throws Exception { + String tmp=readStringFromStdin(message); + return Long.parseLong(tmp); + } + + public static double readDoubleFromStdin(String message) throws Exception { + String tmp=readStringFromStdin(message); + return Double.parseDouble(tmp); + } + public static int readIntFromStdin(String message) throws Exception { + String tmp=readStringFromStdin(message); + return Integer.parseInt(tmp); + } public static void writeByteBuffer(byte[] buf, DataOutputStream out) throws IOException { + writeByteBuffer(buf, 0, buf.length, out); + } + + public static void writeByteBuffer(byte[] buf, int offset, int length, DataOutputStream out) throws IOException { if(buf != null) { out.write(1); - out.writeInt(buf.length); - out.write(buf, 0, buf.length); + out.writeInt(length); + out.write(buf, offset, length); } else { out.write(0); @@ -1048,7 +1263,7 @@ if(b == 1) { b=in.readInt(); byte[] buf=new byte[b]; - in.read(buf, 0, buf.length); + in.readFully(buf, 0, buf.length); return buf; } return null; @@ -1057,7 +1272,7 @@ public static Buffer messageToByteBuffer(Message msg) throws IOException { ExposedByteArrayOutputStream output=new ExposedByteArrayOutputStream(512); - DataOutputStream out=new DataOutputStream(output); + DataOutputStream out=new ExposedDataOutputStream(output); out.writeBoolean(msg != null); if(msg != null) @@ -1070,7 +1285,7 @@ } public static Message byteBufferToMessage(byte[] buffer, int offset, int length) throws Exception { - ByteArrayInputStream input=new ByteArrayInputStream(buffer, offset, length); + ByteArrayInputStream input=new ExposedByteArrayInputStream(buffer, offset, length); DataInputStream in=new DataInputStream(input); if(!in.readBoolean()) @@ -1091,7 +1306,7 @@ */ public static Buffer msgListToByteBuffer(List xmit_list) throws IOException { ExposedByteArrayOutputStream output=new ExposedByteArrayOutputStream(512); - DataOutputStream out=new DataOutputStream(output); + DataOutputStream out=new ExposedDataOutputStream(output); Buffer retval=null; out.writeInt(xmit_list.size()); @@ -1107,7 +1322,7 @@ public static List byteBufferToMessageList(byte[] buffer, int offset, int length) throws Exception { List retval=null; - ByteArrayInputStream input=new ByteArrayInputStream(buffer, offset, length); + ByteArrayInputStream input=new ExposedByteArrayInputStream(buffer, offset, length); DataInputStream in=new DataInputStream(input); int size=in.readInt(); @@ -1138,6 +1353,10 @@ return obj2.equals(obj1); } + public static boolean sameViewId(ViewId one, ViewId two) { + return one.getId() == two.getId() && one.getCoordAddress().equals(two.getCoordAddress()); + } + public static boolean match(long[] a1, long[] a2) { if(a1 == null && a2 == null) @@ -1204,7 +1423,9 @@ System.out.println(msg); try { - return System.in.read(); + int ret=System.in.read(); + System.in.skip(System.in.available()); + return ret; } catch(IOException e) { return 0; @@ -1212,6 +1433,10 @@ } + + + + /** Returns a random value in the range [1 - range] */ public static long random(long range) { return (long)((Math.random() * range) % range) + 1; @@ -1228,6 +1453,16 @@ sleep(r); } + /** Sleeps between floor and ceiling milliseconds, chosen randomly */ + public static void sleepRandom(long floor, long ceiling) { + if(ceiling - floor<= 0) { + return; + } + long diff = ceiling - floor; + long r=(int)((Math.random() * 100000) % diff) + floor; + sleep(r); + } + /** Tosses a coin weighted with probability and returns true or false. Example: if probability=0.8, @@ -1314,8 +1549,7 @@ sb.append("empty"); } else { - for(Iterator it=values.iterator(); it.hasNext();) { - Object o=it.next(); + for(Object o: values) { String s=null; if(o instanceof Event) { Event event=(Event)o; @@ -1329,17 +1563,17 @@ if(type == Event.MSG) { s+="["; Message m=(Message)event.getArg(); - Map headers=new HashMap(m.getHeaders()); - for(Iterator i=headers.keySet().iterator(); i.hasNext();) { - Object headerKey=i.next(); - Object value=headers.get(headerKey); + Map headers=new HashMap(m.getHeaders()); + for(Map.Entry entry: headers.entrySet()) { + short id=entry.getKey(); + Header value=entry.getValue(); String headerToString=null; if(value instanceof FD.FdHeader) { headerToString=value.toString(); } else if(value instanceof PingHeader) { - headerToString=headerKey + "-"; + headerToString=ClassConfigurator.getProtocol(id) + "-"; if(((PingHeader)value).type == PingHeader.GET_MBRS_REQ) { headerToString+="GMREQ"; } @@ -1352,13 +1586,10 @@ } } else { - headerToString=headerKey + "-" + (value == null ? "null" : value.toString()); + headerToString=ClassConfigurator.getProtocol(id) + "-" + (value == null ? "null" : value.toString()); } s+=headerToString; - - if(i.hasNext()) { - s+=","; - } + s+=" "; } s+="]"; } @@ -1491,6 +1722,12 @@ return sb.toString(); } + + /** + * MByte nowadays doesn't mean 1024 * 1024 bytes, but 1 million bytes, see http://en.wikipedia.org/wiki/Megabyte + * @param bytes + * @return + */ public static String printBytes(long bytes) { double tmp; @@ -1511,6 +1748,65 @@ } + public static String printTime(long time, TimeUnit unit) { + long ns=TimeUnit.NANOSECONDS.convert(time, unit); + long us=TimeUnit.MICROSECONDS.convert(time, unit); + long ms=TimeUnit.MILLISECONDS.convert(time, unit); + long secs=TimeUnit.SECONDS.convert(time, unit); + + if(secs > 0) return secs + "s"; + if(ms > 0) return ms + "ms"; + if(us > 0) return us + " us"; + return ns + "ns"; + } + + + public static String format(double value) { + return f.format(value); + } + + + public static long readBytesLong(String input) { + Tuple tuple=readBytes(input); + double num=Double.parseDouble(tuple.getVal1()); + return (long)(num * tuple.getVal2()); + } + + public static int readBytesInteger(String input) { + Tuple tuple=readBytes(input); + double num=Double.parseDouble(tuple.getVal1()); + return (int)(num * tuple.getVal2()); + } + + public static double readBytesDouble(String input) { + Tuple tuple=readBytes(input); + double num=Double.parseDouble(tuple.getVal1()); + return num * tuple.getVal2(); + } + + private static Tuple readBytes(String input) { + input=input.trim().toLowerCase(); + + int index=-1; + long factor=1; + + if((index=input.indexOf("k")) != -1) + factor=1000; + else if((index=input.indexOf("kb")) != -1) + factor=1000; + else if((index=input.indexOf("m")) != -1) + factor=1000000; + else if((index=input.indexOf("mb")) != -1) + factor=1000000; + else if((index=input.indexOf("g")) != -1) + factor=1000000000; + else if((index=input.indexOf("gb")) != -1) + factor=1000000000; + + String str=index != -1? input.substring(0, index) : input; + return new Tuple(str, factor); + } + public static String printBytes(double bytes) { double tmp; @@ -1551,6 +1847,43 @@ } + /* public static String[] components(String path, String separator) { + if(path == null || path.length() == 0) + return null; + String[] tmp=path.split(separator + "+"); // multiple separators could be present + if(tmp == null) + return null; + if(tmp.length == 0) + return null; + + if(tmp[0].length() == 0) { + tmp[0]=separator; + if(tmp.length > 1) { + String[] retval=new String[tmp.length -1]; + retval[0]=tmp[0] + tmp[1]; + System.arraycopy(tmp, 2, retval, 1, tmp.length-2); + return retval; + } + return tmp; + } + return tmp; + }*/ + + + public static String[] components(String path, String separator) { + if(path == null || path.length() == 0) + return null; + String[] tmp=path.split(separator + "+"); // multiple separators could be present + if(tmp == null) + return null; + if(tmp.length == 0) + return null; + + if(tmp[0].length() == 0) + tmp[0]=separator; + return tmp; + } + /** Fragments a byte buffer into smaller fragments of (max.) frag_size. Example: a byte buffer of 1024 bytes and a frag_size of 248 gives 4 fragments @@ -1666,65 +1999,18 @@ } - -// /** -// Peeks for view on the channel until n views have been received or timeout has elapsed. -// Used to determine the view in which we want to start work. Usually, we start as only -// member in our own view (1st view) and the next view (2nd view) will be the full view -// of all members, or a timeout if we're the first member. If a non-view (a message or -// block) is received, the method returns immediately. -// @param channel The channel used to peek for views. Has to be operational. -// @param number_of_views The number of views to wait for. 2 is a good number to ensure that, -// if there are other members, we start working with them included in our view. -// @param timeout Number of milliseconds to wait until view is forced to return. A value -// of <= 0 means wait forever. -// */ -// public static View peekViews(Channel channel, int number_of_views, long timeout) { -// View retval=null; -// Object obj=null; -// int num=0; -// long start_time=System.currentTimeMillis(); - -// if(timeout <= 0) { -// while(true) { -// try { -// obj=channel.peek(0); -// if(obj == null || !(obj instanceof View)) -// break; -// else { -// retval=(View)channel.receive(0); -// num++; -// if(num >= number_of_views) -// break; -// } -// } -// catch(Exception ex) { -// break; -// } -// } -// } -// else { -// while(timeout > 0) { -// try { -// obj=channel.peek(timeout); -// if(obj == null || !(obj instanceof View)) -// break; -// else { -// retval=(View)channel.receive(timeout); -// num++; -// if(num >= number_of_views) -// break; -// } -// } -// catch(Exception ex) { -// break; -// } -// timeout=timeout - (System.currentTimeMillis() - start_time); -// } -// } - -// return retval; -// } + public static String printMapWithDelimiter(Map map, String delimiter) { + boolean first=true; + StringBuilder sb=new StringBuilder(); + for(Map.Entry entry: map.entrySet()) { + if(first) + first=false; + else + sb.append(delimiter); + sb.append(entry.getKey()).append("=").append(entry.getValue()); + } + return sb.toString(); + } @@ -1777,14 +2063,7 @@ } public static String array2String(Object[] array) { - StringBuilder ret=new StringBuilder("["); - - if(array != null) { - for(int i=0; i < array.length; i++) - ret.append(array[i]).append(" "); - } - ret.append(']'); - return ret.toString(); + return Arrays.toString(array); } /** Returns true if all elements of c match obj */ @@ -1811,6 +2090,22 @@ return retval; } + public static List
        leftMembers(Collection
        old_list, Collection
        new_list) { + if(old_list == null || new_list == null) + return null; + List
        retval=new ArrayList
        (old_list); + retval.removeAll(new_list); + return retval; + } + + public static List
        newMembers(List
        old_list, List
        new_list) { + if(old_list == null || new_list == null) + return null; + List
        retval=new ArrayList
        (new_list); + retval.removeAll(old_list); + return retval; + } + /** * Selects a random subset of members according to subset_percentage and returns them. @@ -1835,6 +2130,91 @@ } + + public static boolean containsViewId(Collection views, ViewId vid) { + for(View view: views) { + ViewId tmp=view.getVid(); + if(Util.sameViewId(vid, tmp)) + return true; + } + return false; + } + + + + /** + * Determines the members which take part in a merge. The resulting list consists of all merge coordinators + * plus members outside a merge partition, e.g. for views A={B,A,C}, B={B,C} and C={B,C}, the merge coordinator + * is B, but the merge participants are B and A. + * @param map + * @return + */ + public static Collection
        determineMergeParticipants(Map map) { + Set
        coords=new HashSet
        (); + Set
        all_addrs=new HashSet
        (); + + if(map == null) + return Collections.emptyList(); + + for(View view: map.values()) + all_addrs.addAll(view.getMembers()); + + for(View view: map.values()) { + Address coord=view.getCreator(); + if(coord != null) + coords.add(coord); + } + + for(Address coord: coords) { + View view=map.get(coord); + Collection
        mbrs=view != null? view.getMembers() : null; + if(mbrs != null) + all_addrs.removeAll(mbrs); + } + coords.addAll(all_addrs); + return coords; + } + + + /** + * This is the same or a subset of {@link #determineMergeParticipants(java.util.Map)} and contains only members + * which are currently sub-partition coordinators. + * @param map + * @return + */ + public static Collection
        determineMergeCoords(Map map) { + Set
        retval=new HashSet
        (); + if(map != null) { + for(View view: map.values()) { + Address coord=view.getCreator(); + if(coord != null) + retval.add(coord); + } + } + return retval; + } + + + /** + * Returns the rank of a member in a given view + * @param view The view + * @param addr The address of a member + * @return A value between 1 and view.size(). The first member has rank 1, the second 2 and so on. If the + * member is not found, 0 is returned + */ + public static int getRank(View view, Address addr) { + if(view == null || addr == null) + return 0; + List
        members=view.getMembers(); + for(int i=0; i < members.size(); i++) { + Address mbr=members.get(i); + if(mbr.equals(addr)) + return i+1; + } + return 0; + } + + public static Object pickRandomElement(List list) { if(list == null) return null; int size=list.size(); @@ -1849,54 +2229,150 @@ return array[index]; } + /** + * Returns the object next to element in list + * @param list + * @param obj + * @param + * @return + */ + public static T pickNext(List list, T obj) { + if(list == null || obj == null) + return null; + Object[] array=list.toArray(); + for(int i=0; i < array.length; i++) { + T tmp=(T)array[i]; + if(tmp != null && tmp.equals(obj)) + return (T)array[(i+1) % array.length]; + } + return null; + } + + /** Returns the next min(N,list.size()) elements after obj */ + public static List pickNext(List list, T obj, int num) { + List retval=new ArrayList(); + if(list == null || list.size() < 2) + return retval; + int index=list.indexOf(obj); + if(index != -1) { + for(int i=1; i <= num && i < list.size(); i++) { + T tmp=list.get((index +i) % list.size()); + if(!retval.contains(tmp)) + retval.add(tmp); + } + } + return retval; + } + + + public static View createView(Address coord, long id, Address ... members) { + Vector
        mbrs=new Vector
        (); + mbrs.addAll(Arrays.asList(members)); + return new View(coord, id, mbrs); + } + + + public static Address createRandomAddress() { + return createRandomAddress(generateLocalName()); + } + + public static Address createRandomAddress(String name) { + UUID retval=UUID.randomUUID(); + UUID.add(retval, name); + return retval; + } + + public static Object[][] createTimer() { + return new Object[][] { + {new DefaultTimeScheduler(5)}, + {new TimeScheduler2()}, + {new HashedTimingWheel(5)} + }; + } /** * Returns all members that left between 2 views. All members that are element of old_mbrs but not element of * new_mbrs are returned. */ - public static Vector
        determineLeftMembers(Vector
        old_mbrs, Vector
        new_mbrs) { + public static Vector
        determineLeftMembers(List
        old_mbrs, List
        new_mbrs) { Vector
        retval=new Vector
        (); - Address mbr; - if(old_mbrs == null || new_mbrs == null) return retval; for(int i=0; i < old_mbrs.size(); i++) { - mbr=old_mbrs.elementAt(i); + Address mbr=old_mbrs.get(i); if(!new_mbrs.contains(mbr)) - retval.addElement(mbr); + retval.add(mbr); } + return retval; + } + + /** + * Returns the members which joined between 2 subsequent views + * @param old_mbrs + * @param new_mbrs + * @return + */ + public static List
        determineNewMembers(List
        old_mbrs, List
        new_mbrs) { + if(old_mbrs == null || new_mbrs == null) + return new ArrayList
        (); + + List
        retval=new ArrayList
        (new_mbrs); + retval.removeAll(old_mbrs); return retval; } - public static String printMembers(Vector v) { - StringBuilder sb=new StringBuilder("("); + + public static String printViews(Collection views) { + StringBuilder sb=new StringBuilder(); boolean first=true; - Object el; + for(View view: views) { + if(first) + first=false; + else + sb.append(", "); + sb.append(view.getVid()); + } + return sb.toString(); + } - if(v != null) { - for(int i=0; i < v.size(); i++) { - if(!first) - sb.append(", "); - else - first=false; - el=v.elementAt(i); - sb.append(el); - } + public static String print(Collection objs) { + StringBuilder sb=new StringBuilder(); + boolean first=true; + for(T obj: objs) { + if(first) + first=false; + else + sb.append(", "); + sb.append(obj); } - sb.append(')'); return sb.toString(); } - public static String printPingRsps(List rsps) { + public static String print(Map map) { + StringBuilder sb=new StringBuilder(); + boolean first=true; + for(Map.Entry entry: map.entrySet()) { + if(first) + first=false; + else + sb.append(", "); + sb.append(entry.getKey()).append("=").append(entry.getValue()); + } + return sb.toString(); + } + + + + public static String printPingData(List rsps) { StringBuilder sb=new StringBuilder(); if(rsps != null) { int total=rsps.size(); int servers=0, clients=0, coords=0; - for(PingRsp rsp: rsps) { + for(PingData rsp: rsps) { if(rsp.isCoord()) coords++; if(rsp.isServer()) @@ -1958,28 +2434,7 @@ } } -// /* double writes are not required.*/ -// public static void doubleWriteBuffer( -// ByteBuffer buf, -// WritableByteChannel out) -// throws Exception -// { -// if (buf.limit() > 1) -// { -// int actualLimit = buf.limit(); -// buf.limit(1); -// writeFully(buf,out); -// buf.limit(actualLimit); -// writeFully(buf,out); -// } -// else -// { -// buf.limit(0); -// writeFully(buf,out); -// buf.limit(1); -// writeFully(buf,out); -// } -// } + public static long sizeOf(String classname) { @@ -2010,16 +2465,12 @@ } public static int sizeOf(Streamable inst) { - byte[] data; - ByteArrayOutputStream output; - DataOutputStream out; - try { - output=new ByteArrayOutputStream(); - out=new DataOutputStream(output); + ByteArrayOutputStream output=new ExposedByteArrayOutputStream(); + DataOutputStream out=new ExposedDataOutputStream(output); inst.writeTo(out); out.flush(); - data=output.toByteArray(); + byte[] data=output.toByteArray(); return data.length; } catch(Exception ex) { @@ -2066,10 +2517,82 @@ return loader.loadClass(classname); } } - catch(Throwable t) { - } - - throw new ClassNotFoundException(classname); + catch(Throwable t) { + } + + throw new ClassNotFoundException(classname); + } + + + public static Field[] getAllDeclaredFields(final Class clazz) { + return getAllDeclaredFieldsWithAnnotations(clazz); + } + + public static Field[] getAllDeclaredFieldsWithAnnotations(final Class clazz, Class ... annotations) { + List list=new ArrayList(30); + for(Class curr=clazz; curr != null; curr=curr.getSuperclass()) { + Field[] fields=curr.getDeclaredFields(); + if(fields != null) { + for(Field field: fields) { + if(annotations != null && annotations.length > 0) { + for(Class annotation: annotations) { + if(field.isAnnotationPresent(annotation)) + list.add(field); + } + } + else + list.add(field); + } + } + } + + Field[] retval=new Field[list.size()]; + for(int i=0; i < list.size(); i++) + retval[i]=list.get(i); + return retval; + } + + public static Method[] getAllDeclaredMethods(final Class clazz) { + return getAllDeclaredMethodsWithAnnotations(clazz); + } + + public static Method[] getAllDeclaredMethodsWithAnnotations(final Class clazz, Class ... annotations) { + List list=new ArrayList(30); + for(Class curr=clazz; curr != null; curr=curr.getSuperclass()) { + Method[] methods=curr.getDeclaredMethods(); + if(methods != null) { + for(Method method: methods) { + if(annotations != null && annotations.length > 0) { + for(Class annotation: annotations) { + if(method.isAnnotationPresent(annotation)) + list.add(method); + } + } + else + list.add(method); + } + } + } + + Method[] retval=new Method[list.size()]; + for(int i=0; i < list.size(); i++) + retval[i]=list.get(i); + return retval; + } + + public static Field getField(final Class clazz, String field_name) { + if(clazz == null || field_name == null) + return null; + + Field field=null; + for(Class curr=clazz; curr != null; curr=curr.getSuperclass()) { + try { + return curr.getDeclaredField(field_name); + } + catch(NoSuchFieldException e) { + } + } + return field; } @@ -2100,7 +2623,7 @@ catch(Throwable t) { } } - + try { loader=ClassLoader.getSystemClassLoader(); if(loader != null) { @@ -2168,12 +2691,11 @@ public static List parseCommaDelimitedStrings(String l) { return parseStringList(l, ","); } - + /** - * Input is "daddy[8880],sindhu[8880],camille[5555]. Return List of - * IpAddresses + * Input is "daddy[8880],sindhu[8880],camille[5555]. Returns a list of IpAddresses */ - public static List parseCommaDelimetedHosts(String hosts, int port_range) throws UnknownHostException { + public static List parseCommaDelimitedHosts(String hosts, int port_range) throws UnknownHostException { StringTokenizer tok=new StringTokenizer(hosts, ","); String t; IpAddress addr; @@ -2184,7 +2706,7 @@ String host=t.substring(0, t.indexOf('[')); host=host.trim(); int port=Integer.parseInt(t.substring(t.indexOf('[') + 1, t.indexOf(']'))); - for(int i=port;i < port + port_range;i++) { + for(int i=port;i <= port + port_range;i++) { addr=new IpAddress(host, i); retval.add(addr); } @@ -2193,6 +2715,31 @@ } + /** + * Input is "daddy[8880],sindhu[8880],camille[5555]. Return List of + * InetSocketAddress + */ + public static List parseCommaDelimitedHosts2(String hosts, int port_range) + throws UnknownHostException { + + StringTokenizer tok=new StringTokenizer(hosts, ","); + String t; + InetSocketAddress addr; + Set retval=new HashSet(); + + while(tok.hasMoreTokens()) { + t=tok.nextToken().trim(); + String host=t.substring(0, t.indexOf('[')); + host=host.trim(); + int port=Integer.parseInt(t.substring(t.indexOf('[') + 1, t.indexOf(']'))); + for(int i=port;i < port + port_range;i++) { + addr=new InetSocketAddress(host, i); + retval.add(addr); + } + } + return Collections.unmodifiableList(new LinkedList(retval)); + } + public static List parseStringList(String l, String separator) { List tmp=new LinkedList(); StringTokenizer tok=new StringTokenizer(l, separator); @@ -2381,17 +2928,47 @@ public static String shortName(String hostname) { - int index; - StringBuilder sb=new StringBuilder(); - if(hostname == null) return null; - index=hostname.indexOf('.'); + int index=hostname.indexOf('.'); if(index > 0 && !Character.isDigit(hostname.charAt(0))) - sb.append(hostname.substring(0, index)); + return hostname.substring(0, index); else - sb.append(hostname); - return sb.toString(); + return hostname; + } + + public static boolean startFlush(Channel c, List
        flushParticipants, int numberOfAttempts, long randomSleepTimeoutFloor,long randomSleepTimeoutCeiling) { + boolean successfulFlush = false; + int attemptCount = 0; + while(attemptCount < numberOfAttempts){ + successfulFlush = c.startFlush(flushParticipants, false); + if(successfulFlush) + break; + Util.sleepRandom(randomSleepTimeoutFloor,randomSleepTimeoutCeiling); + attemptCount++; + } + return successfulFlush; + } + + public static boolean startFlush(Channel c, List
        flushParticipants) { + return startFlush(c,flushParticipants,4,1000,5000); + } + + public static boolean startFlush(Channel c, int numberOfAttempts, long randomSleepTimeoutFloor,long randomSleepTimeoutCeiling) { + boolean successfulFlush = false; + int attemptCount = 0; + while(attemptCount < numberOfAttempts){ + successfulFlush = c.startFlush(false); + if(successfulFlush) + break; + Util.sleepRandom(randomSleepTimeoutFloor,randomSleepTimeoutCeiling); + attemptCount++; + } + return successfulFlush; + } + + public static boolean startFlush(Channel c) { + return startFlush(c,4,1000,5000); } public static String shortName(InetAddress hostname) { @@ -2404,14 +2981,54 @@ return sb.toString(); } + public static String generateLocalName() { + String retval=null; + try { + retval=shortName(InetAddress.getLocalHost().getHostName()); + } + catch(UnknownHostException e) { + retval="localhost"; + } + + long counter=Util.random(Short.MAX_VALUE *2); + return retval + "-" + counter; + } + + + + + public synchronized static short incrCounter() { + short retval=COUNTER++; + if(COUNTER >= Short.MAX_VALUE) + COUNTER=1; + return retval; + } + + + public static ConcurrentMap createConcurrentMap(int initial_capacity, float load_factor, int concurrency_level) { + return new ConcurrentHashMap(initial_capacity, load_factor, concurrency_level); + } + + public static ConcurrentMap createConcurrentMap(int initial_capacity) { + return new ConcurrentHashMap(initial_capacity); + } + + public static ConcurrentMap createConcurrentMap() { + return new ConcurrentHashMap(CCHM_INITIAL_CAPACITY, CCHM_LOAD_FACTOR, CCHM_CONCURRENCY_LEVEL); + } + + public static Map createHashMap() { + return new HashMap(CCHM_INITIAL_CAPACITY, CCHM_LOAD_FACTOR); + } + /** Finds first available port starting at start_port and returns server socket */ - public static ServerSocket createServerSocket(int start_port) { + public static ServerSocket createServerSocket(SocketFactory factory, String service_name, int start_port) { ServerSocket ret=null; while(true) { try { - ret=new ServerSocket(start_port); + ret=factory.createServerSocket(service_name, start_port); } catch(BindException bind_ex) { start_port++; @@ -2424,12 +3041,12 @@ return ret; } - public static ServerSocket createServerSocket(InetAddress bind_addr, int start_port) { + public static ServerSocket createServerSocket(SocketFactory factory, String service_name, InetAddress bind_addr, int start_port) { ServerSocket ret=null; while(true) { try { - ret=new ServerSocket(start_port, 50, bind_addr); + ret=factory.createServerSocket(service_name, start_port, 50, bind_addr); } catch(BindException bind_ex) { start_port++; @@ -2443,6 +3060,42 @@ } + /** + * Finds first available port starting at start_port and returns server + * socket. Will not bind to port >end_port. Sets srv_port + */ + public static ServerSocket createServerSocket(SocketFactory factory, String service_name, InetAddress bind_addr, + int start_port, int end_port) throws Exception { + ServerSocket ret=null; + int original_start_port=start_port; + + while(true) { + try { + if(bind_addr == null) + ret=factory.createServerSocket(service_name, start_port); + else { + // changed (bela Sept 7 2007): we accept connections on all NICs + ret=factory.createServerSocket(service_name, start_port, 50, bind_addr); + } + } + catch(SocketException bind_ex) { + if(start_port == end_port) + throw new BindException("No available port to bind to in range [" + original_start_port + " .. " + end_port + "]"); + if(bind_addr != null && !bind_addr.isLoopbackAddress()) { + NetworkInterface nic=NetworkInterface.getByInetAddress(bind_addr); + if(nic == null) + throw new BindException("bind_addr " + bind_addr + " is not a valid interface: " + bind_ex); + } + start_port++; + continue; + } + break; + } + return ret; + } + + + /** * Creates a DatagramSocket bound to addr. If addr is null, socket won't be bound. If address is already in use, @@ -2451,17 +3104,17 @@ * @param port The port which the socket should use. If 0, a random port will be used. If > 0, but port is already * in use, it will be incremented until an unused port is found, or until MAX_PORT is reached. */ - public static DatagramSocket createDatagramSocket(InetAddress addr, int port) throws Exception { + public static DatagramSocket createDatagramSocket(SocketFactory factory, String service_name, InetAddress addr, int port) throws Exception { DatagramSocket sock=null; if(addr == null) { if(port == 0) { - return new DatagramSocket(); + return factory.createDatagramSocket(service_name); } else { while(port < MAX_PORT) { try { - return new DatagramSocket(port); + return factory.createDatagramSocket(service_name, port); } catch(BindException bind_ex) { // port already used port++; @@ -2473,7 +3126,7 @@ if(port == 0) port=1024; while(port < MAX_PORT) { try { - return new DatagramSocket(port, addr); + return factory.createDatagramSocket(service_name, port, addr); } catch(BindException bind_ex) { // port already used port++; @@ -2484,11 +3137,8 @@ } - public static MulticastSocket createMulticastSocket(int port) throws IOException { - return createMulticastSocket(null, port, null); - } - public static MulticastSocket createMulticastSocket(InetAddress mcast_addr, int port, Log log) throws IOException { + public static MulticastSocket createMulticastSocket(SocketFactory factory, String service_name, InetAddress mcast_addr, int port, Log log) throws IOException { if(mcast_addr != null && !mcast_addr.isMulticastAddress()) throw new IllegalArgumentException("mcast_addr (" + mcast_addr + ") is not a valid multicast address"); @@ -2496,27 +3146,27 @@ MulticastSocket retval=null; try { - retval=new MulticastSocket(saddr); + retval=factory.createMulticastSocket(service_name, saddr); } catch(IOException ex) { if(log != null && log.isWarnEnabled()) { StringBuilder sb=new StringBuilder(); String type=mcast_addr != null ? mcast_addr instanceof Inet4Address? "IPv4" : "IPv6" : "n/a"; sb.append("could not bind to " + mcast_addr + " (" + type + " address)"); - sb.append("; make sure your mcast_addr is of the same type as the IP stack (IPv4 or IPv6)."); + sb.append("; make sure your mcast_addr is of the same type as the preferred IP stack (IPv4 or IPv6)"); + sb.append(" by checking the value of the system properties java.net.preferIPv4Stack and java.net.preferIPv6Addresses."); sb.append("\nWill ignore mcast_addr, but this may lead to cross talking " + - "(see http://www.jboss.com/wiki/Edit.jsp?page=CrossTalking for details). "); + "(see http://www.jboss.org/community/docs/DOC-9469 for details). "); sb.append("\nException was: " + ex); - log.warn(sb); + log.warn(sb.toString()); } } if(retval == null) - retval=new MulticastSocket(port); + retval=factory.createMulticastSocket(service_name, port); return retval; } - /** * Returns the address of the interface to use defined by bind_addr and bind_interface * @param props @@ -2525,67 +3175,185 @@ * @throws SocketException */ public static InetAddress getBindAddress(Properties props) throws UnknownHostException, SocketException { - boolean ignore_systemprops=Util.isBindAddressPropertyIgnored(); - String bind_addr=Util.getProperty(new String[]{Global.BIND_ADDR, Global.BIND_ADDR_OLD}, props, "bind_addr", - ignore_systemprops, null); - String bind_interface=Util.getProperty(new String[]{Global.BIND_INTERFACE, null}, props, "bind_interface", - ignore_systemprops, null); - InetAddress retval=null, bind_addr_host=null; - - if(bind_addr != null) { - bind_addr_host=InetAddress.getByName(bind_addr); - } - - if(bind_interface != null) { - NetworkInterface intf=NetworkInterface.getByName(bind_interface); - if(intf != null) { - for(Enumeration addresses=intf.getInetAddresses(); addresses.hasMoreElements();) { - InetAddress addr=addresses.nextElement(); - if(bind_addr == null) { - retval=addr; - break; - } - else { - if(bind_addr_host != null) { - if(bind_addr_host.equals(addr)) { - retval=addr; - break; - } - } - else if(addr.getHostAddress().trim().equalsIgnoreCase(bind_addr)) { - retval=addr; - break; - } - } - } + + // determine the desired values for bind_addr_str and bind_interface_str + boolean ignore_systemprops=Util.isBindAddressPropertyIgnored(); + String bind_addr_str =Util.getProperty(new String[]{Global.BIND_ADDR, Global.BIND_ADDR_OLD}, props, "bind_addr", + ignore_systemprops, null); + String bind_interface_str =Util.getProperty(new String[]{Global.BIND_INTERFACE, null}, props, "bind_interface", + ignore_systemprops, null); + + InetAddress bind_addr=null; + NetworkInterface bind_intf=null; + + StackType ip_version=Util.getIpStackType(); + + // 1. if bind_addr_str specified, get bind_addr and check version + if(bind_addr_str != null) { + bind_addr=InetAddress.getByName(bind_addr_str); + + // check that bind_addr_host has correct IP version + boolean hasCorrectVersion = ((bind_addr instanceof Inet4Address && ip_version == StackType.IPv4) || + (bind_addr instanceof Inet6Address && ip_version == StackType.IPv6)) ; + if (!hasCorrectVersion) + throw new IllegalArgumentException("bind_addr " + bind_addr_str + " has incorrect IP version") ; + } + + // 2. if bind_interface_str specified, get interface and check that it has correct version + if(bind_interface_str != null) { + bind_intf=NetworkInterface.getByName(bind_interface_str); + if(bind_intf != null) { + + // check that the interface supports the IP version + boolean supportsVersion = interfaceHasIPAddresses(bind_intf, ip_version) ; + if (!supportsVersion) + throw new IllegalArgumentException("bind_interface " + bind_interface_str + " has incorrect IP version") ; } else { - throw new UnknownHostException("network interface " + bind_interface + " not found"); - } - } + // (bind_intf == null) + throw new UnknownHostException("network interface " + bind_interface_str + " not found"); + } + } + + // 3. intf and bind_addr are both are specified, bind_addr needs to be on intf + if (bind_intf != null && bind_addr != null) { + + boolean hasAddress = false ; + + // get all the InetAddresses defined on the interface + Enumeration addresses = bind_intf.getInetAddresses() ; + + while (addresses != null && addresses.hasMoreElements()) { + // get the next InetAddress for the current interface + InetAddress address = (InetAddress) addresses.nextElement() ; + + // check if address is on interface + if (bind_addr.equals(address)) { + hasAddress = true ; + break ; + } + } + + if (!hasAddress) { + throw new IllegalArgumentException("network interface " + bind_interface_str + " does not contain address " + bind_addr_str); + } + + } + // 4. if only interface is specified, get first non-loopback address on that interface, + else if (bind_intf != null) { + bind_addr = getAddress(bind_intf, AddressScope.NON_LOOPBACK) ; + } + // 5. if neither bind address nor bind interface is specified, get the first non-loopback + // address on any interface + else if (bind_addr == null) { + bind_addr = getNonLoopbackAddress() ; + } + + // if we reach here, if bind_addr == null, we have tried to obtain a bind_addr but were not successful + // in such a case, using a loopback address of the correct version is our only option + boolean localhost = false; + if (bind_addr == null) { + bind_addr = getLocalhost(ip_version); + localhost = true; + } + + //http://jira.jboss.org/jira/browse/JGRP-739 + //check all bind_address against NetworkInterface.getByInetAddress() to see if it exists on the machine + //in some Linux setups NetworkInterface.getByInetAddress(InetAddress.getLocalHost()) returns null, so skip + //the check in that case + if(!localhost && NetworkInterface.getByInetAddress(bind_addr) == null) { + throw new UnknownHostException("Invalid bind address " + bind_addr); + } + + if(props != null) { + props.remove("bind_addr"); + props.remove("bind_interface"); + } + return bind_addr; + } + + /** + * Method used by PropertyConverters.BindInterface to check that a bind_address is + * consistent with a specified interface + * + * Idea: + * 1. We are passed a bind_addr, which may be null + * 2. If non-null, check that bind_addr is on bind_interface - if not, throw exception, + * otherwise, return the original bind_addr + * 3. If null, get first non-loopback address on bind_interface, using stack preference to + * get the IP version. If no non-loopback address, then just return null (i.e. the + * bind_interface did not influence the decision). + * + */ + public static InetAddress validateBindAddressFromInterface(InetAddress bind_addr, String bind_interface_str) throws UnknownHostException, SocketException { + NetworkInterface bind_intf=null; - boolean localhost = false; - if (bind_addr != null) { - retval = InetAddress.getByName(bind_addr); - } - else { - retval = InetAddress.getLocalHost(); - localhost = true; - } - - //http://jira.jboss.org/jira/browse/JGRP-739 - //check all bind_address against NetworkInterface.getByInetAddress() to see if it exists on the machine - //in some Linux setups NetworkInterface.getByInetAddress(InetAddress.getLocalHost()) returns null, so skip - //the check in that case - if(NetworkInterface.getByInetAddress(retval) == null && !localhost) { - throw new UnknownHostException("Invalid bind address " + retval); - } + if(bind_addr != null && bind_addr.isLoopbackAddress()) + return bind_addr; - if(props != null) { - props.remove("bind_addr"); - props.remove("bind_interface"); + // 1. if bind_interface_str is null, or empty, no constraint on bind_addr + if (bind_interface_str == null || bind_interface_str.trim().length() == 0) + return bind_addr; + + // 2. get the preferred IP version for the JVM - it will be IPv4 or IPv6 + StackType ip_version = getIpStackType(); + + // 3. if bind_interface_str specified, get interface and check that it has correct version + bind_intf=NetworkInterface.getByName(bind_interface_str); + if(bind_intf != null) { + // check that the interface supports the IP version + boolean supportsVersion = interfaceHasIPAddresses(bind_intf, ip_version) ; + if (!supportsVersion) + throw new IllegalArgumentException("bind_interface " + bind_interface_str + " has incorrect IP version") ; } - return retval; + else { + // (bind_intf == null) + throw new UnknownHostException("network interface " + bind_interface_str + " not found"); + } + + // 3. intf and bind_addr are both are specified, bind_addr needs to be on intf + if (bind_addr != null) { + + boolean hasAddress = false ; + + // get all the InetAddresses defined on the interface + Enumeration addresses = bind_intf.getInetAddresses() ; + + while (addresses != null && addresses.hasMoreElements()) { + // get the next InetAddress for the current interface + InetAddress address = (InetAddress) addresses.nextElement() ; + + // check if address is on interface + if (bind_addr.equals(address)) { + hasAddress = true ; + break ; + } + } + + if (!hasAddress) { + String bind_addr_str = bind_addr.getHostAddress(); + throw new IllegalArgumentException("network interface " + bind_interface_str + " does not contain address " + bind_addr_str); + } + + } + // 4. if only interface is specified, get first non-loopback address on that interface, + else { + bind_addr = getAddress(bind_intf, AddressScope.NON_LOOPBACK) ; + } + + + //http://jira.jboss.org/jira/browse/JGRP-739 + //check all bind_address against NetworkInterface.getByInetAddress() to see if it exists on the machine + //in some Linux setups NetworkInterface.getByInetAddress(InetAddress.getLocalHost()) returns null, so skip + //the check in that case + if(bind_addr != null && NetworkInterface.getByInetAddress(bind_addr) == null) { + throw new UnknownHostException("Invalid bind address " + bind_addr); + } + + // if bind_addr == null, we have tried to obtain a bind_addr but were not successful + // in such a case, return the original value of null so the default will be applied + + return bind_addr; } @@ -2593,6 +3361,10 @@ return checkForPresence("os.name", "linux"); } + public static boolean checkForHp() { + return checkForPresence("os.name", "hp"); + } + public static boolean checkForSolaris() { return checkForPresence("os.name", "sun"); } @@ -2601,6 +3373,10 @@ return checkForPresence("os.name", "win"); } + public static boolean checkForMac() { + return checkForPresence("os.name", "mac"); + } + private static boolean checkForPresence(String key, String value) { try { String tmp=System.getProperty(key); @@ -2625,126 +3401,173 @@ } - public static int getJavaVersion() { - String version=System.getProperty("java.version"); - int retval=0; - if(version != null) { - if(version.startsWith("1.2")) - return 12; - if(version.startsWith("1.3")) - return 13; - if(version.startsWith("1.4")) - return 14; - if(version.startsWith("1.5")) - return 15; - if(version.startsWith("5")) - return 15; - if(version.startsWith("1.6")) - return 16; - if(version.startsWith("6")) - return 16; - } - return retval; - } - public static Vector unmodifiableVector(Vector v) { if(v == null) return null; return new UnmodifiableVector(v); } - public static String memStats(boolean gc) { - StringBuilder sb=new StringBuilder(); - Runtime rt=Runtime.getRuntime(); - if(gc) - rt.gc(); - long free_mem, total_mem, used_mem; - free_mem=rt.freeMemory(); - total_mem=rt.totalMemory(); - used_mem=total_mem - free_mem; - sb.append("Free mem: ").append(free_mem).append("\nUsed mem: ").append(used_mem); - sb.append("\nTotal mem: ").append(total_mem); - return sb.toString(); + + + /** IP related utilities */ + + public static InetAddress getLocalhost(StackType ip_version) throws UnknownHostException { + if (ip_version == StackType.IPv4) + return InetAddress.getByName("127.0.0.1") ; + else + return InetAddress.getByName("::1") ; } -// public static InetAddress getFirstNonLoopbackAddress() throws SocketException { -// Enumeration en=NetworkInterface.getNetworkInterfaces(); -// while(en.hasMoreElements()) { -// NetworkInterface i=(NetworkInterface)en.nextElement(); -// for(Enumeration en2=i.getInetAddresses(); en2.hasMoreElements();) { -// InetAddress addr=(InetAddress)en2.nextElement(); -// if(!addr.isLoopbackAddress()) -// return addr; -// } -// } -// return null; -// } + /** + * Returns the first non-loopback address on any interface on the current host. + */ + public static InetAddress getNonLoopbackAddress() throws SocketException { + return getAddress(AddressScope.NON_LOOPBACK); + } - public static InetAddress getFirstNonLoopbackAddress() throws SocketException { - Enumeration en=NetworkInterface.getNetworkInterfaces(); - boolean preferIpv4=Boolean.getBoolean(Global.IPv4); - boolean preferIPv6=Boolean.getBoolean(Global.IPv6); - while(en.hasMoreElements()) { - NetworkInterface i=(NetworkInterface)en.nextElement(); - for(Enumeration en2=i.getInetAddresses(); en2.hasMoreElements();) { - InetAddress addr=(InetAddress)en2.nextElement(); - if(!addr.isLoopbackAddress()) { - if(addr instanceof Inet4Address) { - if(preferIPv6) - continue; - return addr; - } - if(addr instanceof Inet6Address) { - if(preferIpv4) - continue; - return addr; - } + + + /** + * Returns the first address on any interface of the current host, which satisfies scope + */ + public static InetAddress getAddress(AddressScope scope) throws SocketException { + InetAddress address=null ; + + Enumeration intfs=NetworkInterface.getNetworkInterfaces(); + while(intfs.hasMoreElements()) { + NetworkInterface intf=(NetworkInterface)intfs.nextElement(); + try { + if(intf.isUp()) { + address=getAddress(intf, scope) ; + if(address != null) + return address; } } + catch (SocketException e) { + } } - return null; - } - - public static boolean isIPv4Stack() { - return getIpStack()== 4; + return null ; } - public static boolean isIPv6Stack() { - return getIpStack() == 6; - } - public static short getIpStack() { - short retval=2; - if(Boolean.getBoolean(Global.IPv4)) { - retval=4; - } + /** + * Returns the first address on the given interface on the current host, which satisfies scope + * + * @param intf the interface to be checked + */ + public static InetAddress getAddress(NetworkInterface intf, AddressScope scope) throws SocketException { + StackType ip_version=Util.getIpStackType(); + for(Enumeration addresses=intf.getInetAddresses(); addresses.hasMoreElements();) { + InetAddress addr=(InetAddress)addresses.nextElement(); + boolean match; + switch(scope) { + case GLOBAL: + match=!addr.isLoopbackAddress() && !addr.isLinkLocalAddress() && !addr.isSiteLocalAddress(); + break; + case SITE_LOCAL: + match=addr.isSiteLocalAddress(); + break; + case LINK_LOCAL: + match=addr.isLinkLocalAddress(); + break; + case LOOPBACK: + match=addr.isLoopbackAddress(); + break; + case NON_LOOPBACK: + match=!addr.isLoopbackAddress(); + break; + default: + throw new IllegalArgumentException("scope " + scope + " is unknown"); + } - if(Boolean.getBoolean(Global.IPv6)) { - retval=6; + if(match) { + if((addr instanceof Inet4Address && ip_version == StackType.IPv4) || + (addr instanceof Inet6Address && ip_version == StackType.IPv6)) + return addr; + } } - return retval; + return null ; } - public static InetAddress getFirstNonLoopbackIPv6Address() throws SocketException { - Enumeration en=NetworkInterface.getNetworkInterfaces(); - while(en.hasMoreElements()) { - NetworkInterface i=(NetworkInterface)en.nextElement(); - for(Enumeration en2=i.getInetAddresses(); en2.hasMoreElements();) { - InetAddress addr=(InetAddress)en2.nextElement(); - if(!addr.isLoopbackAddress()) { - if(addr instanceof Inet4Address) { - continue; - } - if(addr instanceof Inet6Address) { - return addr; - } + + + + /** + * A function to check if an interface supports an IP version (i.e has addresses + * defined for that IP version). + * + * @param intf + * @return + */ + public static boolean interfaceHasIPAddresses(NetworkInterface intf, StackType ip_version) throws SocketException, UnknownHostException { + boolean supportsVersion = false ; + if (intf != null) { + // get all the InetAddresses defined on the interface + Enumeration addresses = intf.getInetAddresses() ; + while (addresses != null && addresses.hasMoreElements()) { + // get the next InetAddress for the current interface + InetAddress address = (InetAddress) addresses.nextElement() ; + + // check if we find an address of correct version + if ((address instanceof Inet4Address && (ip_version == StackType.IPv4)) || + (address instanceof Inet6Address && (ip_version == StackType.IPv6))) { + supportsVersion = true ; + break ; } } } - return null; + else { + throw new UnknownHostException("network interface " + intf + " not found") ; + } + return supportsVersion ; + } + + public static StackType getIpStackType() { + return ip_stack_type; + } + + /** + * Tries to determine the type of IP stack from the available interfaces and their addresses and from the + * system properties (java.net.preferIPv4Stack and java.net.preferIPv6Addresses) + * @return StackType.IPv4 for an IPv4 only stack, StackYTypeIPv6 for an IPv6 only stack, and StackType.Unknown + * if the type cannot be detected + */ + private static StackType _getIpStackType() { + boolean isIPv4StackAvailable = isStackAvailable(true) ; + boolean isIPv6StackAvailable = isStackAvailable(false) ; + + // if only IPv4 stack available + if (isIPv4StackAvailable && !isIPv6StackAvailable) { + return StackType.IPv4; + } + // if only IPv6 stack available + else if (isIPv6StackAvailable && !isIPv4StackAvailable) { + return StackType.IPv6; + } + // if dual stack + else if (isIPv4StackAvailable && isIPv6StackAvailable) { + // get the System property which records user preference for a stack on a dual stack machine + if(Boolean.getBoolean(Global.IPv4)) // has preference over java.net.preferIPv6Addresses + return StackType.IPv4; + if(Boolean.getBoolean(Global.IPv6)) + return StackType.IPv6; + return StackType.IPv6; + } + return StackType.Unknown; } + + + public static boolean isStackAvailable(boolean ipv4) { + Collection all_addrs=getAllAvailableAddresses(); + for(InetAddress addr: all_addrs) + if(ipv4 && addr instanceof Inet4Address || (!ipv4 && addr instanceof Inet6Address)) + return true; + return false; + } + + public static List getAllAvailableInterfaces() throws SocketException { List retval=new ArrayList(10); NetworkInterface intf; @@ -2755,6 +3578,40 @@ return retval; } + public static Collection getAllAvailableAddresses() { + Set retval=new HashSet(); + Enumeration en; + + try { + en=NetworkInterface.getNetworkInterfaces(); + if(en == null) + return retval; + while(en.hasMoreElements()) { + NetworkInterface intf=(NetworkInterface)en.nextElement(); + Enumeration addrs=intf.getInetAddresses(); + while(addrs.hasMoreElements()) + retval.add(addrs.nextElement()); + } + } + catch(SocketException e) { + e.printStackTrace(); + } + + return retval; + } + + public static void checkIfValidAddress(InetAddress bind_addr, String prot_name) throws Exception { + if(bind_addr.isAnyLocalAddress() || bind_addr.isLoopbackAddress()) + return; + Collection addrs=getAllAvailableAddresses(); + for(InetAddress addr: addrs) { + if(addr.equals(bind_addr)) + return; + } + throw new BindException("[" + prot_name + "] " + bind_addr + " is not a valid address on any local network interface"); + } + + /** * Returns a value associated wither with one or more system properties, or found in the props map @@ -2814,30 +3671,59 @@ } - public static MBeanServer getMBeanServer() { - ArrayList servers=MBeanServerFactory.findMBeanServer(null); - if(servers == null || servers.isEmpty()) - return null; - // return 'jboss' server if available - for(int i=0; i < servers.size(); i++) { - MBeanServer srv=(MBeanServer)servers.get(i); - if("jboss".equalsIgnoreCase(srv.getDefaultDomain())) - return srv; - } + public static boolean isCoordinator(JChannel ch) { + return isCoordinator(ch.getView(), ch.getAddress()); + } - // return first available server - return (MBeanServer)servers.get(0); + + public static boolean isCoordinator(View view, Address local_addr) { + if(view == null || local_addr == null) + return false; + Vector
        mbrs=view.getMembers(); + return !(mbrs == null || mbrs.isEmpty()) && local_addr.equals(mbrs.firstElement()); } + public static MBeanServer getMBeanServer() { + ArrayList servers = MBeanServerFactory.findMBeanServer(null); + if (servers != null && !servers.isEmpty()) { + // return 'jboss' server if available + for (int i = 0; i < servers.size(); i++) { + MBeanServer srv = (MBeanServer) servers.get(i); + if ("jboss".equalsIgnoreCase(srv.getDefaultDomain())) + return srv; + } - - public static void main(String args[]) throws Exception { - System.out.println("IPv4: " + isIPv4Stack()); - System.out.println("IPv6: " + isIPv6Stack()); + // return first available server + return (MBeanServer) servers.get(0); + } + else { + //if it all fails, create a default + return MBeanServerFactory.createMBeanServer(); + } + } + + + public static void registerChannel(JChannel channel, String name) { + MBeanServer server=Util.getMBeanServer(); + if(server != null) { + try { + JmxConfigurator.registerChannel(channel, + server, + (name != null? name : "jgroups"), + channel.getClusterName(), + true); + } + catch(Exception e) { + e.printStackTrace(); + } + } } + + + public static String generateList(Collection c, String separator) { if(c == null) return null; StringBuilder sb=new StringBuilder(); @@ -2854,24 +3740,24 @@ } return sb.toString(); } - - + + /** * Go through the input string and replace any occurance of ${p} with the * props.getProperty(p) value. If there is no such property p defined, then * the ${p} reference will remain unchanged. - * + * * If the property reference is of the form ${p:v} and there is no such * property p, then the default value v will be returned. - * + * * If the property reference is of the form ${p1,p2} or ${p1,p2:v} then the * primary and the secondary properties will be tried in turn, before * returning either the unchanged input, or the default value. - * + * * The property ${/} is replaced with System.getProperty("file.separator") * value and the property ${:} is replaced with * System.getProperty("path.separator"). - * + * * @param string - * the string with possible ${} references * @param props - @@ -2879,10 +3765,10 @@ * System.getProperty() * @return the input string with all property references replaced if any. If * there are no valid references the input string will be returned. - * @throws java.lang.AccessControlException + * @throws {@link java.security.AccessControlException} * when not authorised to retrieved system properties */ - public static String replaceProperties(final String string, final Properties props) { + public static String replaceProperties(final String string, final Properties props) { /** File separator value */ final String FILE_SEPARATOR=File.separator; @@ -2900,7 +3786,7 @@ final int SEEN_DOLLAR=1; final int IN_BRACKET=2; final char[] chars=string.toCharArray(); - StringBuffer buffer=new StringBuffer(); + StringBuilder buffer=new StringBuilder(); boolean properties=false; int state=NORMAL; int start=0; @@ -2959,7 +3845,7 @@ value=System.getProperty(realKey); if(value == null) { - // Check for a composite key, "key1,key2" + // Check for a composite key, "key1,key2" value=resolveCompositeKey(realKey, props); // Not a composite key either, use the specified default @@ -3000,9 +3886,9 @@ * Try to resolve a "key" from the provided properties by checking if it is * actually a "key1,key2", in which case try first "key1", then "key2". If * all fails, return null. - * + * * It also accepts "key1," and ",key2". - * + * * @param key * the key to resolve * @param props @@ -3036,7 +3922,27 @@ // Return whatever we've found or null return value; } - + +// /** +// * Replaces variables with values from system properties. If a system property is not found, the property is +// * removed from the output string +// * @param input +// * @return +// */ +// public static String substituteVariables(String input) throws Exception { +// Collection configs=Configurator.parseConfigurations(input); +// for(Configurator.ProtocolConfiguration config: configs) { +// for(Iterator> it=config.getProperties().entrySet().iterator(); it.hasNext();) { +// Map.Entry entry=it.next(); +// +// +// } +// } +// +// +// return null; +// } + /** * Replaces variables of ${var:default} with System.getProperty(var, default). If no variables are found, returns @@ -3174,10 +4080,69 @@ } + public static String methodNameToAttributeName(String methodName) { + methodName=methodName.startsWith("get") || methodName.startsWith("set")? methodName.substring(3): methodName; + methodName=methodName.startsWith("is")? methodName.substring(2) : methodName; + // Pattern p=Pattern.compile("[A-Z]+"); + Matcher m=METHOD_NAME_TO_ATTR_NAME_PATTERN.matcher(methodName); + StringBuffer sb=new StringBuffer(); + while(m.find()) { + int start=m.start(), end=m.end(); + String str=methodName.substring(start, end).toLowerCase(); + if(str.length() > 1) { + String tmp1=str.substring(0, str.length() -1); + String tmp2=str.substring(str.length() -1); + str=tmp1 + "_" + tmp2; + } + if(start == 0) { + m.appendReplacement(sb, str); + } + else + m.appendReplacement(sb, "_" + str); + } + m.appendTail(sb); + return sb.toString(); + } + -} + public static String attributeNameToMethodName(String attr_name) { + if(attr_name.contains("_")) { + // Pattern p=Pattern.compile("_."); + Matcher m=ATTR_NAME_TO_METHOD_NAME_PATTERN.matcher(attr_name); + StringBuffer sb=new StringBuffer(); + while(m.find()) { + m.appendReplacement(sb, attr_name.substring(m.end() - 1, m.end()).toUpperCase()); + } + m.appendTail(sb); + char first=sb.charAt(0); + if(Character.isLowerCase(first)) { + sb.setCharAt(0, Character.toUpperCase(first)); + } + return sb.toString(); + } + else { + if(Character.isLowerCase(attr_name.charAt(0))) { + return attr_name.substring(0, 1).toUpperCase() + attr_name.substring(1); + } + else { + return attr_name; + } + } + } + /** + * Runs a task on a separate thread + * @param task + * @param factory + * @param group + * @param thread_name + */ + public static void runAsync(Runnable task, ThreadFactory factory, ThreadGroup group, String thread_name) { + Thread thread=factory.newThread(group, task, thread_name); + thread.start(); + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/UUID.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/UUID.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/UUID.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/UUID.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,338 @@ +package org.jgroups.util; + +import org.jgroups.Address; +import org.jgroups.Global; +import org.jgroups.blocks.LazyRemovalCache; + +import java.io.*; +import java.security.SecureRandom; +import java.util.Collection; +import java.util.Map; + +/** Logical address which is unique over space and time. + *
        + * Copied from java.util.UUID, but unneeded fields from the latter have been removed. UUIDs needs to + * have a small memory footprint. + * @author Bela Ban + */ +public class UUID implements Address, Streamable, Comparable
        { + protected long mostSigBits; + protected long leastSigBits; + protected byte[] additional_data; + + /** The random number generator used by this class to create random based UUIDs */ + protected static volatile SecureRandom numberGenerator=null; + + /** Keeps track of associations between logical addresses (UUIDs) and logical names */ + protected static LazyRemovalCache cache; + + private static final long serialVersionUID=3972962439975931228L; + + protected static boolean print_uuids=false; + + protected static final int SIZE=Global.LONG_SIZE * 2 + Global.BYTE_SIZE; + + protected static final LazyRemovalCache.Printable print_function=new LazyRemovalCache.Printable() { + public java.lang.String print(Address key, String val) { + return val + ": " + (key instanceof UUID? ((UUID)key).toStringLong() : key) + "\n"; + } + }; + + + static { + String tmp; + + int max_elements=500; + long max_age=5000L; + + try { + tmp=Util.getProperty(new String[]{Global.UUID_CACHE_MAX_ELEMENTS}, null, null, false, "500"); + if(tmp != null) + max_elements=Integer.valueOf(tmp); + } + catch(Throwable t) { + } + + try { + tmp=Util.getProperty(new String[]{Global.UUID_CACHE_MAX_AGE}, null, null, false, "5000"); + if(tmp != null) + max_age=Long.valueOf(tmp); + } + catch(Throwable t) { + } + + cache=new LazyRemovalCache(max_elements, max_age); + + + /* Trying to get value of jgroups.print_uuids. PropertyPermission not granted if + * running in an untrusted environment with JNLP */ + try { + tmp=Util.getProperty(new String[]{Global.PRINT_UUIDS}, null, null, false, "false"); + print_uuids=Boolean.valueOf(tmp).booleanValue(); + } + catch (SecurityException ex){ + } + } + + + public UUID() { + } + + + public UUID(long mostSigBits, long leastSigBits) { + this.mostSigBits = mostSigBits; + this.leastSigBits = leastSigBits; + } + + /** Private constructor which uses a byte array to construct the new UUID */ + protected UUID(byte[] data) { + long msb = 0; + long lsb = 0; + if(data.length != 16) + throw new RuntimeException("UUID needs a 16-byte array"); + for (int i=0; i<8; i++) + msb = (msb << 8) | (data[i] & 0xff); + for (int i=8; i<16; i++) + lsb = (lsb << 8) | (data[i] & 0xff); + this.mostSigBits = msb; + this.leastSigBits = lsb; + } + + + public static void add(Address uuid, String logical_name) { + cache.add(uuid, logical_name); // overwrite existing entry + } + + public static void add(Map map) { + if(map == null) return; + for(Map.Entry entry: map.entrySet()) + add(entry.getKey(), entry.getValue()); + } + + public static String get(Address logical_addr) { + return cache.get(logical_addr); + } + + /** Returns a copy of the cache's contents */ + public static Map getContents() { + return cache.contents(); + } + + public static void remove(Address addr) { + cache.remove(addr); + } + + public static void removeAll(Collection
        mbrs) { + cache.removeAll(mbrs); + } + + public static void retainAll(Collection
        logical_addrs) { + cache.retainAll(logical_addrs); + } + + public static String printCache() { + return cache.printCache(print_function); + } + + /** + * Returns the additional_data. + * @return byte[] + * @since 2.8 + * @deprecated Will be removed in 3.0. This was only added to be backwards compatible with 2.7 + */ + public final byte[] getAdditionalData() { + return additional_data; + } + + /** + * Sets the additional_data. + * @param additional_data The additional_data to set + * @since 2.8 + * @deprecated Will be removed in 3.0. This was only added to be backwards compatible with 2.7 + */ + public final void setAdditionalData(byte[] additional_data) { + this.additional_data=additional_data; + } + + + /** + * Static factory to retrieve a type 4 (pseudo randomly generated) UUID. + * The {@code UUID} is generated using a cryptographically strong pseudo + * random number generator. + * @return A randomly generated {@code UUID} + */ + public static UUID randomUUID() { + SecureRandom ng=numberGenerator; + if(ng == null) + numberGenerator=ng=new SecureRandom(); + + byte[] randomBytes=new byte[16]; + ng.nextBytes(randomBytes); + return new UUID(randomBytes); + } + + + public long getLeastSignificantBits() { + return leastSigBits; + } + + /** + * Returns the most significant 64 bits of this UUID's 128 bit value. + * @return The most significant 64 bits of this UUID's 128 bit value + */ + public long getMostSignificantBits() { + return mostSigBits; + } + + + + + public String toString() { + if(print_uuids) + return toStringLong(); + String val=cache.get(this); + return val != null? val : toStringLong(); + } + + /** + * Returns a {@code String} object representing this {@code UUID}. + * + *

        The UUID string representation is as described by this BNF: + *

        +     * {@code
        +     * UUID                   =  "-"  "-"
        +     *                           "-"
        +     *                           "-"
        +     *                          
        +     * time_low               = 4*
        +     * time_mid               = 2*
        +     * time_high_and_version  = 2*
        +     * variant_and_sequence   = 2*
        +     * node                   = 6*
        +     * hexOctet               = 
        +     * hexDigit               =
        +     *       "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
        +     *       | "a" | "b" | "c" | "d" | "e" | "f"
        +     *       | "A" | "B" | "C" | "D" | "E" | "F"
        +     * }
        + * + * @return A string representation of this {@code UUID} + */ + public String toStringLong() { + return (digits(mostSigBits >> 32, 8) + "-" + + digits(mostSigBits >> 16, 4) + "-" + + digits(mostSigBits, 4) + "-" + + digits(leastSigBits >> 48, 4) + "-" + + digits(leastSigBits, 12)); + } + + /** Returns val represented by the specified number of hex digits. */ + protected static String digits(long val, int digits) { + long hi = 1L << (digits * 4); + return Long.toHexString(hi | (val & (hi - 1))).substring(1); + } + + /** + * Returns a hash code for this {@code UUID}. + * @return A hash code value for this {@code UUID} + */ + public int hashCode() { + return (int)((mostSigBits >> 32) ^ + mostSigBits ^ + (leastSigBits >> 32) ^ + leastSigBits); + } + + /** + * Compares this object to the specified object. The result is {@code + * true} if and only if the argument is not {@code null}, is a {@code UUID} + * object, has the same variant, and contains the same value, bit for bit, + * as this {@code UUID}. + * @param obj The object to be compared + * @return {@code true} if the objects are the same; {@code false} otherwise + */ + public boolean equals(Object obj) { + if (!(obj instanceof UUID)) + return false; + UUID id = (UUID)obj; + return this == id || (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits); + } + + + /** + * Compares this UUID with the specified UUID. + *

        The first of two UUIDs is greater than the second if the most + * significant field in which the UUIDs differ is greater for the first UUID. + * @param other {@code UUID} to which this {@code UUID} is to be compared + * @return -1, 0 or 1 as this {@code UUID} is less than, equal to, or greater than {@code val} + */ + public int compareTo(Address other) { + UUID val=(UUID)other; + if(this == val) + return 0; + return (this.mostSigBits < val.mostSigBits ? -1 : + (this.mostSigBits > val.mostSigBits ? 1 : + (this.leastSigBits < val.leastSigBits ? -1 : + (this.leastSigBits > val.leastSigBits ? 1 : + 0)))); + } + + + + public void writeTo(DataOutputStream out) throws IOException { + out.writeLong(leastSigBits); + out.writeLong(mostSigBits); + if(additional_data != null) { + out.writeBoolean(true); // 1 byte + out.writeShort(additional_data.length); + out.write(additional_data, 0, additional_data.length); + } + else + out.writeBoolean(false); + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + leastSigBits=in.readLong(); + mostSigBits=in.readLong(); + if(in.readBoolean() == false) + return; + int len=in.readUnsignedShort(); + if(len > 0) { + additional_data=new byte[len]; + in.readFully(additional_data, 0, additional_data.length); + } + } + + public boolean isMulticastAddress() { + return false; + } + + public int size() { + int retval=SIZE; + if(additional_data != null) + retval+=additional_data.length + Global.SHORT_SIZE; + return retval; + } + + public Object clone() throws CloneNotSupportedException { + UUID ret=new UUID(mostSigBits, leastSigBits); + if(additional_data != null) { + ret.additional_data=new byte[additional_data.length]; + System.arraycopy(additional_data, 0, ret.additional_data, 0, additional_data.length); + } + return ret; + } + + public void writeExternal(ObjectOutput out) throws IOException { + out.writeLong(leastSigBits); + out.writeLong(mostSigBits); + } + + + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + leastSigBits=in.readLong(); + mostSigBits=in.readLong(); + } + + +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/util/XMLSchemaGenerator.java libjgroups-java-2.12.2.Final/src/org/jgroups/util/XMLSchemaGenerator.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/util/XMLSchemaGenerator.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/util/XMLSchemaGenerator.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,210 @@ +package org.jgroups.util; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.util.HashSet; +import java.util.Set; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.jgroups.Version; +import org.jgroups.annotations.Property; +import org.jgroups.stack.Configurator; +import org.jgroups.stack.Protocol; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Iterates over all concrete Protocol classes and creates XML schema used for validation of + * configuration files. + * + * https://jira.jboss.org/jira/browse/JGRP-448 + * + * @author Vladimir Blagojevic + * + */ +public class XMLSchemaGenerator { + + public static void main(String[] args) { + + String outputDir = "./"; + + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + if ("-o".equals(arg)) { + outputDir = args[++i]; + continue; + } else { + System.out.println("XMLSchemaGenerator -o "); + return; + } + } + + File f = new File(outputDir, "JGroups-" + Version.major + "." + Version.minor + ".xsd"); + try { + FileWriter fw = new FileWriter(f, false); + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + DOMImplementation impl = builder.getDOMImplementation(); + Document xmldoc = impl.createDocument("http://www.w3.org/2001/XMLSchema", "xs:schema", + null); + xmldoc.getDocumentElement().setAttribute("targetNamespace", "urn:org:jgroups"); + xmldoc.getDocumentElement().setAttribute("elementFormDefault", "qualified"); + + Element xsElement = xmldoc.createElement("xs:element"); + xsElement.setAttribute("name", "config"); + xmldoc.getDocumentElement().appendChild(xsElement); + + Element complexType = xmldoc.createElement("xs:complexType"); + xsElement.appendChild(complexType); + + Element allType = xmldoc.createElement("xs:choice"); + allType.setAttribute("maxOccurs", "unbounded"); + complexType.appendChild(allType); + + Set> classes = getClasses("org.jgroups.protocols", Protocol.class); + for (Class clazz : classes) { + classToXML(xmldoc, allType, clazz, ""); + } + classes = getClasses("org.jgroups.protocols.pbcast", Protocol.class); + for (Class clazz : classes) { + classToXML(xmldoc, allType, clazz, "pbcast."); + } + + DOMSource domSource = new DOMSource(xmldoc); + StreamResult streamResult = new StreamResult(fw); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer serializer = tf.newTransformer(); + serializer.setOutputProperty(OutputKeys.METHOD, "xml"); + serializer.setOutputProperty(OutputKeys.INDENT, "yes"); + serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "1"); + serializer.transform(domSource, streamResult); + + fw.flush(); + fw.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static Set> getClasses(String packageName, Class assignableFrom) + throws IOException, ClassNotFoundException { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + Set> classes = new HashSet>(); + String path = packageName.replace('.', '/'); + URL resource = loader.getResource(path); + if (resource != null) { + String filePath = resource.getFile(); + if (filePath != null && new File(filePath).isDirectory()) { + for (String file : new File(filePath).list()) { + if (file.endsWith(".class")) { + String name = packageName + '.' + file.substring(0, file.indexOf(".class")); + Class clazz = Class.forName(name); + if (assignableFrom.isAssignableFrom(clazz)) + classes.add(clazz); + } + } + } + } + return classes; + } + + private static void classToXML(Document xmldoc, Element parent, Class clazz, + String preAppendToSimpleClassName) throws Exception { + + boolean isConcreteClass = (clazz.getModifiers() & Modifier.ABSTRACT) == 0; + if (isConcreteClass && !clazz.isAnonymousClass()) { + parent.appendChild(createXMLTree(xmldoc, clazz, preAppendToSimpleClassName)); + } + } + + private static Element createXMLTree(Document xmldoc, Class clazz, + String preAppendToSimpleClassName) throws Exception { + + Element classElement = xmldoc.createElement("xs:element"); + String elementName = preAppendToSimpleClassName + clazz.getSimpleName(); + if(elementName == null || elementName.length()==0) { + throw new IllegalArgumentException("Cannot create empty attribute name for element xs:element, class is " + clazz); + } + classElement.setAttribute("name",elementName); + + Element complexType = xmldoc.createElement("xs:complexType"); + classElement.appendChild(complexType); + + // iterate fields + for (Class clazzInLoop = clazz; clazzInLoop != null; clazzInLoop = clazzInLoop.getSuperclass()) { + Field[] fields = clazzInLoop.getDeclaredFields(); + for (Field field : fields) { + if (field.isAnnotationPresent(Property.class)) { + String property = field.getName(); + Property r = field.getAnnotation(Property.class); + boolean annotationRedefinesName = r.name().length() > 0 + && r.deprecatedMessage().length() == 0; + if (annotationRedefinesName) { + property = r.name(); + } + if(property == null || property.length()==0) { + throw new IllegalArgumentException("Cannot create empty attribute name for element xs:attribute, field is " + field); + } + Element attributeElement = xmldoc.createElement("xs:attribute"); + attributeElement.setAttribute("name", property); + + // Agreement with Bela Ban on Jan-20-2009 (Go Obama!!!) to treat all types as + // xs:string since we do not know where users are going to use + // replacement tokens in configuration files. Therefore, the type becomes + // indeterminate. + // attributeElement.setAttribute("type", fieldToXMLSchemaAttributeType(field)); + attributeElement.setAttribute("type", "xs:string"); + complexType.appendChild(attributeElement); + + Element annotationElement = xmldoc.createElement("xs:annotation"); + attributeElement.appendChild(annotationElement); + + Element documentationElement = xmldoc.createElement("xs:documentation"); + documentationElement.setTextContent(r.description()); + annotationElement.appendChild(documentationElement); + } + } + } + + // iterate methods + Method[] methods = clazz.getMethods(); + for (Method method : methods) { + if (method.isAnnotationPresent(Property.class) && method.getName().startsWith("set")) { + + Property annotation = method.getAnnotation(Property.class); + String name = annotation.name(); + if (name.length() < 1) { + name = Util.methodNameToAttributeName(method.getName()); + } + Element attributeElement = xmldoc.createElement("xs:attribute"); + attributeElement.setAttribute("name", name); + attributeElement.setAttribute("type", "xs:string"); + complexType.appendChild(attributeElement); + + String desc = annotation.description(); + if (desc.length() > 0) { + Element annotationElement = xmldoc.createElement("xs:annotation"); + attributeElement.appendChild(annotationElement); + + Element documentationElement = xmldoc.createElement("xs:documentation"); + documentationElement.setTextContent(annotation.description()); + annotationElement.appendChild(documentationElement); + } + } + } + return classElement; + } +} diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/Version.java libjgroups-java-2.12.2.Final/src/org/jgroups/Version.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/Version.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/Version.java 2011-10-18 11:22:35.000000000 +0000 @@ -15,19 +15,17 @@ * Z = 0-63 for micro versions * * @author Bela Ban - * @version $Id: Version.java,v 1.66 2008/12/05 11:37:45 belaban Exp $ * Holds version information for JGroups. */ @Immutable public class Version { public static final short major = 2; - public static final short minor = 7; - public static final short micro = 0; - public static final String description="2.7.0.GA"; + public static final short minor = 12; + public static final short micro = 2; + public static final String description="2.12.2.Final"; public static final short version=encode(major, minor, micro); public static final String string_version=print(version); - public static final String cvs="$Id: Version.java,v 1.66 2008/12/05 11:37:45 belaban Exp $"; private static final int MAJOR_SHIFT = 11; private static final int MINOR_SHIFT = 6; @@ -43,7 +41,6 @@ */ public static void main(String[] args) { System.out.println("\nVersion: " + description); - System.out.println("CVS: " + cvs + "\n"); } @@ -52,7 +49,7 @@ * @return String with description */ public static String printDescription() { - return "JGroups " + description + " [" + cvs + "]"; + return "JGroups " + description; } /** diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/ViewId.java libjgroups-java-2.12.2.Final/src/org/jgroups/ViewId.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/ViewId.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/ViewId.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: ViewId.java,v 1.11 2006/08/14 16:05:09 belaban Exp $ package org.jgroups; diff -Nru libjgroups-java-2.7.0.GA/src/org/jgroups/View.java libjgroups-java-2.12.2.Final/src/org/jgroups/View.java --- libjgroups-java-2.7.0.GA/src/org/jgroups/View.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/src/org/jgroups/View.java 2011-10-18 11:22:35.000000000 +0000 @@ -9,18 +9,18 @@ import java.util.HashMap; import java.util.Map; import java.util.Vector; +import java.util.Collection; /** * A view is a local representation of the current membership of a group. * Only one view is installed in a channel at a time. * Views contain the address of its creator, an ID and a list of member addresses. - * These adresses are ordered, and the first address is always the coordinator of the view. + * These addresses are ordered, and the first address is always the coordinator of the view. * This way, each member of the group knows who the new coordinator will be if the current one * crashes or leaves the group. * The views are sent between members using the VIEW_CHANGE event * @author Bela Ban - * @version $Id: View.java,v 1.20 2008/02/20 15:51:57 belaban Exp $ */ public class View implements Externalizable, Cloneable, Streamable { /* A view is uniquely identified by its ViewID @@ -37,7 +37,8 @@ * or leaves the group. */ protected Vector

        members=null; - + + @Deprecated protected Map payload=null; private static final long serialVersionUID=7027860705519930293L; @@ -60,6 +61,10 @@ this.members=members; } + public View(ViewId vid, Collection
        members) { + this.vid=vid; + this.members=new Vector
        (members); + } /** * Creates a new view @@ -68,7 +73,7 @@ * @param id The lamport timestamp of this view * @param members Contains a list of all the members in the view, can be empty but not null. */ - public View(Address creator, long id, Vector
        members) { + public View(Address creator, long id, Collection
        members) { this(new ViewId(creator, id), members); } @@ -83,6 +88,8 @@ return vid; } + public ViewId getViewId() {return vid;} + /** * returns the creator of this view * if this view was created with the empty constructur, null will be returned @@ -149,9 +156,15 @@ } + public View copy() { + ViewId vid2=vid != null ? (ViewId)vid.clone() : null; + Vector
        members2=members != null ? new Vector
        (members) : null; + return new View(vid2, members2); + } + + /** * creates a copy of this view - * * @return a copy of this view */ public Object clone() { @@ -182,6 +195,7 @@ * exceed 65000 bytes ! * @param key * @param value + * @deprecated Will be removed in 3.0 */ public void addPayload(String key, Object value) { if(payload == null) { @@ -190,10 +204,22 @@ payload.put(key, value); } + /** + * + * @param key + * @return + * @deprecated Will be removed in 3.0 + */ public Object removePayload(String key) { return payload != null? payload.remove(key) : null; } + /** + * + * @param key + * @return + * @deprecated Will be removed in 3.0 + */ public Object getPayload(String key) { if(payload != null) return payload.get(key); @@ -220,7 +246,7 @@ } } - + @SuppressWarnings("unchecked") public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { vid=(ViewId)in.readObject(); members=(Vector
        )in.readObject(); @@ -257,7 +283,7 @@ } } - + @SuppressWarnings("unchecked") public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { boolean b; // vid: @@ -273,7 +299,7 @@ short payloadLength=in.readShort(); if(payloadLength > 0) { byte[] buffer=new byte[payloadLength]; - in.read(buffer); + in.readFully(buffer); try { payload=(Map)Util.objectFromByteBuffer(buffer); } diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/DistributedLockManagerTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/DistributedLockManagerTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/DistributedLockManagerTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/DistributedLockManagerTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -16,7 +16,6 @@ * Testcase for the DistributedLockManager * * @author Robert Schaffar-Taurok (robert@fusion.at) - * @version $Id: DistributedLockManagerTest.java,v 1.11 2008/08/08 17:07:29 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=true) public class DistributedLockManagerTest extends ChannelTestBase { @@ -63,7 +62,7 @@ throw new IllegalStateException("obj1 should not be locked"); } catch (LockNotGrantedException ex) { - // everything is ok + System.out.println("got a lock not granted exception - expected"); } lockManager2.lock("obj2", "owner2", 1000); @@ -74,7 +73,7 @@ throw new IllegalStateException("obj2 should not be released"); } catch (LockNotReleasedException ex) { - // everything is ok + System.out.println("got a lock not released exception, as expected"); } lockManager1.unlock("obj2", "owner2"); diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/ExecutingServiceTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/ExecutingServiceTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/ExecutingServiceTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/ExecutingServiceTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,606 @@ +package org.jgroups.blocks; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Queue; +import java.util.Random; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.Callable; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; + +import org.jgroups.Address; +import org.jgroups.Global; +import org.jgroups.JChannel; +import org.jgroups.blocks.executor.ExecutionCompletionService; +import org.jgroups.blocks.executor.ExecutionRunner; +import org.jgroups.blocks.executor.ExecutionService; +import org.jgroups.blocks.executor.ExecutionService.DistributedFuture; +import org.jgroups.blocks.executor.Executions; +import org.jgroups.conf.ClassConfigurator; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; +import org.jgroups.protocols.CENTRAL_EXECUTOR; +import org.jgroups.protocols.Executing.Owner; +import org.jgroups.stack.Protocol; +import org.jgroups.stack.ProtocolStack; +import org.jgroups.tests.ChannelTestBase; +import org.jgroups.util.NotifyingFuture; +import org.jgroups.util.Streamable; +import org.jgroups.util.Util; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** Tests {@link org.jgroups.blocks.executor.ExecutionService} + * @author wburns + */ +@Test(groups=Global.STACK_DEPENDENT,sequential=true) +public class ExecutingServiceTest extends ChannelTestBase { + protected static Log log = LogFactory.getLog(ExecutingServiceTest.class); + protected static AtomicReference requestBlocker = + new AtomicReference(); + + protected JChannel c1, c2, c3; + protected ExecutionService e1, e2, e3; + protected ExecutionRunner er1, er2, er3; + + @BeforeClass + protected void init() throws Exception { + c1=createChannel(true, 3, "A"); + addExecutingProtocol(c1); + er1=new ExecutionRunner(c1); + c1.connect("ExecutionServiceTest"); + + c2=createChannel(c1, "B"); + er2=new ExecutionRunner(c2); + c2.connect("ExecutionServiceTest"); + + c3=createChannel(c1, "C"); + er3=new ExecutionRunner(c3); + c3.connect("ExecutionServiceTest"); + + LogFactory.getLog(ExecutionRunner.class).setLevel("trace"); + } + + @AfterClass + protected void cleanup() { + Util.close(c3,c2,c1); + } + + @BeforeMethod + protected void createExecutors() { + e1=new ExecutionService(c1); + e2=new ExecutionService(c2); + e3=new ExecutionService(c3); + + // Clear out the queue, in case if test doesn't clear it + SleepingStreamableCallable.canceledThreads.clear(); + // Reset the barrier in case test failed on the barrier + SleepingStreamableCallable.barrier.reset(); + } + + public static class ExposedExecutingProtocol extends CENTRAL_EXECUTOR { + + public ExposedExecutingProtocol() { + // We use the same id as the CENTRAL_EXECUTOR + id=ClassConfigurator.getProtocolId(CENTRAL_EXECUTOR.class); + } + + // @see org.jgroups.protocols.Executing#sendRequest(org.jgroups.Address, org.jgroups.protocols.Executing.Type, long, java.lang.Object) + @Override + protected void sendRequest(Address dest, Type type, long requestId, + Object object) { + CyclicBarrier barrier = requestBlocker.get(); + if (barrier != null) { + try { + barrier.await(); + } + catch (InterruptedException e) { + assert false : "Exception while waiting: " + e.toString(); + } + catch (BrokenBarrierException e) { + assert false : "Exception while waiting: " + e.toString(); + } + } + super.sendRequest(dest, type, requestId, object); + } + + public Queue getAwaitingConsumerQueue() { + return _awaitingConsumer; + } + + public Queue getRequestsFromCoordinator() { + return _runRequests; + } + + public Lock getLock() { + return _consumerLock; + } + } + + /** + * This class is to be used to test to make sure that when a non callable + * is to be serialized that it works correctly. + *

        + * This class provides a few constructors that shouldn't be used. They + * are just present to possibly poke holes in the constructor array offset. + * @param The type that the value can be returned as + * @author wburns + */ + protected static class SimpleCallable implements Callable { + final V _object; + + // This constructor shouldn't be used + public SimpleCallable(String noUse) { + throw new UnsupportedOperationException(); + } + + // This constructor shouldn't be used + public SimpleCallable(Integer noUse) { + throw new UnsupportedOperationException(); + } + + public SimpleCallable(V object) { + _object = object; + } + + // This constructor shouldn't be used + public SimpleCallable() { + throw new UnsupportedOperationException(); + } + + @Override + public V call() throws Exception { + return _object; + } + } + + protected static class SleepingStreamableCallable implements Callable, Streamable { + long millis; + + public static BlockingQueue canceledThreads = new LinkedBlockingQueue(); + public static CyclicBarrier barrier = new CyclicBarrier(2); + + public SleepingStreamableCallable() { + + } + + public SleepingStreamableCallable(long millis) { + this.millis=millis; + } + + @Override + public void writeTo(DataOutputStream out) throws IOException { + out.writeLong(millis); + } + + @Override + public void readFrom(DataInputStream in) throws IOException, + IllegalAccessException, InstantiationException { + millis = in.readLong(); + } + + @Override + public Void call() throws Exception { + barrier.await(); + try { + Thread.sleep(millis); + } + catch (InterruptedException e) { + Thread interruptedThread = Thread.currentThread(); + if (log.isTraceEnabled()) + log.trace("Submitted cancelled thread - " + interruptedThread); + canceledThreads.offer(interruptedThread); + } + return null; + } + + // @see java.lang.Object#toString() + @Override + public String toString() { + return "SleepingStreamableCallable [timeout=" + millis + "]"; + } + } + + protected static class SimpleStreamableCallable implements Callable, Streamable { + V _object; + + public SimpleStreamableCallable() { + + } + + public SimpleStreamableCallable(V object) { + _object = object; + } + + @Override + public V call() throws Exception { + return _object; + } + + // @see java.lang.Object#toString() + @Override + public String toString() { + return "SimpleSerializableCallable [value=" + _object + "]"; + } + + @Override + public void writeTo(DataOutputStream out) throws IOException { + try { + Util.writeObject(_object, out); + } + catch (IOException e) { + throw e; + } + catch (Exception e) { + throw new IOException(e); + } + } + + @SuppressWarnings("unchecked") + @Override + public void readFrom(DataInputStream in) throws IOException, + IllegalAccessException, InstantiationException { + try { + _object = (V)Util.readObject(in); + } + catch (IOException e) { + throw e; + } + catch (Exception e) { + throw new IOException(e); + } + } + } + + @Test + public void testSimpleSerializableCallableSubmit() + throws InterruptedException, ExecutionException, TimeoutException { + Long value = Long.valueOf(100); + Callable callable = new SimpleStreamableCallable(value); + Thread consumer = new Thread(er2); + consumer.start(); + NotifyingFuture future = e1.submit(callable); + Long returnValue = future.get(10L, TimeUnit.SECONDS); + // We try to stop the thread. + consumer.interrupt(); + assert value == returnValue : "The value returned doesn't match"; + + consumer.join(2000); + assert !consumer.isAlive() : "Consumer did not stop correctly"; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void testSimpleSerializableCallableConcurrently() + throws InterruptedException, ExecutionException, TimeoutException { + Thread[] consumers = {new Thread(er1), new Thread(er2), new Thread(er3)}; + + for (Thread thread : consumers) { + thread.start(); + } + + Random random = new Random(); + + int count = 100; + Future[] futures1 = new Future[count]; + Future[] futures2 = new Future[count]; + Future[] futures3 = new Future[count]; + StringBuilder builder = new StringBuilder("base"); + for (int i = 0; i < count; i++) { + builder.append(random.nextInt(10)); + String value = builder.toString(); + futures1[i] = e1.submit(new SimpleStreamableCallable(value)); + futures2[i] = e2.submit(new SimpleStreamableCallable(value)); + futures3[i] = e3.submit(new SimpleStreamableCallable(value)); + } + + for (int i = 0; i < count; i++) { + // All 3 of the futures should have returned the same value + Object value = futures1[i].get(10L, TimeUnit.SECONDS); + assert value.equals(futures2[i].get(10L, TimeUnit.SECONDS)); + assert value.equals(futures3[i].get(10L, TimeUnit.SECONDS)); + + // Make sure that same value is what it should be + CharSequence seq = builder.subSequence(0, 5+i); + assert value.equals(seq); + } + + for (Thread consumer : consumers) { + // We try to stop the thread. + consumer.interrupt(); + + consumer.join(2000); + assert !consumer.isAlive() : "Consumer did not stop correctly"; + } + } + + /** + * Interrupts can have a lot of timing issues, so we run it a lot to make + * sure we find all the issues. + * @throws InterruptedException + * @throws BrokenBarrierException + * @throws TimeoutException + */ + @Test + public void testInterruptWhileRunningAlot() throws InterruptedException, BrokenBarrierException, TimeoutException { + for (int i = 0; i < 500; ++i) + testInterruptTaskRequestWhileRunning(); + } + + protected void testInterruptTaskRequestWhileRunning() + throws InterruptedException, BrokenBarrierException, TimeoutException { + Callable callable = new SleepingStreamableCallable(10000); + Thread consumer = new Thread(er2); + consumer.start(); + NotifyingFuture future = e1.submit(callable); + + // We wait until it is ready + SleepingStreamableCallable.barrier.await(5, TimeUnit.SECONDS); + if (log.isTraceEnabled()) + log.trace("Cancelling future by interrupting"); + future.cancel(true); + + Thread cancelled = SleepingStreamableCallable.canceledThreads.poll(2, + TimeUnit.SECONDS); + + if (log.isTraceEnabled()) + log.trace("Cancelling task by interrupting"); + // We try to stop the thread now which should now stop the runner + consumer.interrupt(); + assert cancelled != null : "There was no cancelled thread"; + + consumer.join(2000); + assert !consumer.isAlive() : "Consumer did not stop correctly"; + } + + @Test + public void testInterruptTaskRequestBeforeRunning() + throws InterruptedException, TimeoutException { + Callable callable = new SleepingStreamableCallable(10000); + NotifyingFuture future = e1.submit(callable); + + // Now we make sure that + ExposedExecutingProtocol protocol = + (ExposedExecutingProtocol)c1.getProtocolStack().findProtocol( + ExposedExecutingProtocol.class); + Queue queue = protocol.getAwaitingConsumerQueue(); + Lock lock = protocol.getLock(); + + lock.lock(); + try { + assert queue.peek() != null : "The object in queue doesn't match"; + } + finally { + lock.unlock(); + } + // This should remove the task before it starts, since the consumer is + // not yet running + future.cancel(false); + + lock.lock(); + try { + assert queue.peek() == null : "There should be no more objects in the queue"; + } + finally { + lock.unlock(); + } + } + + @Test + public void testExecutorAwaitTerminationNoInterrupt() throws InterruptedException, + BrokenBarrierException, TimeoutException { + testExecutorAwaitTermination(false); + } + + @Test + public void testExecutorAwaitTerminationInterrupt() throws InterruptedException, + BrokenBarrierException, TimeoutException { + testExecutorAwaitTermination(true); + } + + protected void testExecutorAwaitTermination(boolean interrupt) + throws InterruptedException, BrokenBarrierException, TimeoutException { + Thread consumer = new Thread(er2); + consumer.start(); + // We send a task that waits for 101 milliseconds and then finishes + Callable callable = new SleepingStreamableCallable(101); + e1.submit(callable); + + // We wait for the thread to start + SleepingStreamableCallable.barrier.await(2, TimeUnit.SECONDS); + + if (interrupt) { + if (log.isTraceEnabled()) + log.trace("Cancelling futures by interrupting"); + e1.shutdownNow(); + // We wait for the task to be interrupted. + assert SleepingStreamableCallable.canceledThreads.poll(2, + TimeUnit.SECONDS) != null : + "Thread wasn't interrupted due to our request"; + } + else { + e1.shutdown(); + } + + assert e1.awaitTermination(2, TimeUnit.SECONDS) : "Executor didn't terminate fast enough"; + + try { + e1.submit(callable); + assert false : "Task was submitted, where as it should have been rejected"; + } + catch (RejectedExecutionException e) { + // We should have received this exception + } + + if (log.isTraceEnabled()) + log.trace("Cancelling task by interrupting"); + // We try to stop the thread. + consumer.interrupt(); + + consumer.join(2000); + assert !consumer.isAlive() : "Consumer did not stop correctly"; + } + + @Test + public void testNonSerializableCallable() throws SecurityException, + NoSuchMethodException, InterruptedException, ExecutionException, + TimeoutException { + Thread consumer = new Thread(er2); + consumer.start(); + + Long value = Long.valueOf(100); + + @SuppressWarnings("rawtypes") + Constructor constructor = + SimpleCallable.class.getConstructor(Object.class); + constructor.getGenericParameterTypes(); + @SuppressWarnings("unchecked") + Callable callable = (Callable)Executions.serializableCallable( + constructor, value); + + NotifyingFuture future = e1.submit(callable); + Long returnValue = future.get(10L, TimeUnit.SECONDS); + // We try to stop the thread. + consumer.interrupt(); + assert value == returnValue : "The value returned doesn't match"; + + consumer.join(2000); + assert !consumer.isAlive() : "Consumer did not stop correctly"; + } + + @Test + public void testExecutionCompletionService() throws InterruptedException { + Thread consumer1 = new Thread(er2); + consumer1.start(); + Thread consumer2 = new Thread(er3); + consumer2.start(); + + ExecutionCompletionService service = new ExecutionCompletionService(e1); + + // The sleeps will not occur until both threads get there due to barrier + // This should result in future2 always ending first since the sleep + // is 3 times smaller + Future future1 = service.submit(new SleepingStreamableCallable(300)); + Future future2 = service.submit(new SleepingStreamableCallable(100)); + + assert service.poll(2, TimeUnit.SECONDS) == future2 : "The task either didn't come back or was in wrong order"; + assert service.poll(2, TimeUnit.SECONDS) == future1 : "The task either didn't come back or was in wrong order"; + + // We try to stop the threads. + consumer1.interrupt(); + consumer2.interrupt(); + + consumer1.join(2000); + assert !consumer1.isAlive() : "Consumer did not stop correctly"; + consumer2.join(2000); + assert !consumer2.isAlive() : "Consumer did not stop correctly"; + } + + @Test + public void testCoordinatorWentDownWhileSendingMessage() throws Exception { + // It is 3 calls. + // The first is the original message sending to the coordinator + // The second is the new message to send the request to the new coordinator + // The last is our main method below waiting for others + final CyclicBarrier barrier = new CyclicBarrier(3); + + requestBlocker.set(barrier); + + final Callable callable = new SimpleStreamableCallable(23); + ExecutorService service = Executors.newCachedThreadPool(); + service.submit(new Runnable() { + + @Override + public void run() { + e2.submit(callable); + } + }); + + service.submit(new Runnable() { + @Override + public void run() { + // We close the coordinator + Util.close(c1); + } + }); + + barrier.await(2, TimeUnit.SECONDS); + + requestBlocker.getAndSet(null).reset(); + + // We need to reconnect the channel now + c1=createChannel(c2, "A"); + addExecutingProtocol(c1); + er1=new ExecutionRunner(c1); + c1.connect("ExecutionServiceTest"); + + service.shutdown(); + service.awaitTermination(2, TimeUnit.SECONDS); + + // Now we make sure that the new coordinator has the requests + ExposedExecutingProtocol protocol = + (ExposedExecutingProtocol)c2.getProtocolStack().findProtocol( + ExposedExecutingProtocol.class); + Queue runnables = protocol.getAwaitingConsumerQueue(); + + assert runnables.size() == 1 : "There is no runnable in the queue"; + Runnable task = runnables.iterator().next(); + assert task instanceof DistributedFuture : "The task wasn't a distributed future like we thought"; + assert callable == ((DistributedFuture)task).getCallable() : "The inner callable wasn't the same"; + + Queue requests = protocol.getRequestsFromCoordinator(); + assert requests.size() == 1 : "There is no request in the coordinator queue - " + requests.size(); + Owner owner = requests.iterator().next(); + assert owner.getAddress().equals(c2.getAddress()) : "The request Address doesn't match"; + assert owner.getRequestId() == 0 : "We only had 1 request so it should be zero still"; + } + + @Test + public void testInvokeAnyCalls() throws InterruptedException, ExecutionException { + Thread consumer1 = new Thread(er2); + consumer1.start(); + Thread consumer2 = new Thread(er3); + consumer2.start(); + + Collection> callables = new ArrayList>(); + + callables.add(new SimpleStreamableCallable((long)10)); + callables.add(new SimpleStreamableCallable((long)100)); + Long value = e1.invokeAny(callables); + + assert value == 10 || value == 100 : "The task didn't return the right value"; + + // We try to stop the threads. + consumer1.interrupt(); + consumer2.interrupt(); + + consumer1.join(2000); + assert !consumer1.isAlive() : "Consumer did not stop correctly"; + consumer2.join(2000); + assert !consumer2.isAlive() : "Consumer did not stop correctly"; + } + + protected void addExecutingProtocol(JChannel ch) { + ProtocolStack stack=ch.getProtocolStack(); + Protocol protocol = new ExposedExecutingProtocol(); + protocol.setLevel("trace"); + stack.insertProtocolAtTop(protocol); + } +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/LockServiceTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/LockServiceTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/LockServiceTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/LockServiceTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,375 @@ +package org.jgroups.blocks; + +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; + +import org.jgroups.Global; +import org.jgroups.JChannel; +import org.jgroups.blocks.locking.LockService; +import org.jgroups.protocols.CENTRAL_LOCK; +import org.jgroups.stack.Protocol; +import org.jgroups.stack.ProtocolStack; +import org.jgroups.tests.ChannelTestBase; +import org.jgroups.util.Util; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** Tests {@link org.jgroups.blocks.locking.LockService} + * @author Bela Ban + */ +@Test(groups=Global.STACK_DEPENDENT,sequential=true) +public class LockServiceTest extends ChannelTestBase { + protected JChannel c1, c2, c3; + protected LockService s1, s2, s3; + protected Lock lock; + protected static final String LOCK="sample-lock"; + + + @BeforeClass + protected void init() throws Exception { + c1=createChannel(true, 3, "A"); + addLockingProtocol(c1); + s1=new LockService(c1); + c1.connect("LockServiceTest"); + + c2=createChannel(c1, "B"); + s2=new LockService(c2); + c2.connect("LockServiceTest"); + + c3=createChannel(c1, "C"); + s3=new LockService(c3); + c3.connect("LockServiceTest"); + + lock=s1.getLock(LOCK); + } + + + @AfterClass + protected void cleanup() { + Util.close(c3,c2,c1); + } + + @BeforeMethod + protected void unlockAll() { + s3.unlockAll(); + s2.unlockAll(); + s1.unlockAll(); + } + + + public void testSimpleLock() { + lock(lock, LOCK); + unlock(lock, LOCK); + } + + public void testLockingOfAlreadyAcquiredLock() { + lock(lock, LOCK); + lock(lock, LOCK); + unlock(lock, LOCK); + } + + public void testUnsuccessfulTryLock() { + System.out.println("s1:\n" + s1.printLocks() + + "\ns2:\n" + s2.printLocks() + + "\ns3:\n" + s3.printLocks()); + + + Lock lock2=s2.getLock(LOCK); + lock(lock2, LOCK); + try { + boolean rc=tryLock(lock, LOCK); + assert !rc; + unlock(lock, LOCK); + } + finally { + unlock(lock2, LOCK); + } + } + + public void testUnsuccessfulTryLockTimeout() throws InterruptedException { + Lock lock2=s2.getLock(LOCK); + lock(lock2, LOCK); + try { + boolean rc=tryLock(lock, 1000, LOCK); + assert !rc; + } + finally { + unlock(lock2, LOCK); + } + } + + + public void testLockInterrupt() { + // Interrupt ourselves before trying to acquire lock + Thread.currentThread().interrupt(); + + lock.lock(); + try { + System.out.println("Locks we have: " + s1.printLocks()); + if(Thread.interrupted()) { + System.out.println("We still have interrupt flag set, as it should be"); + } + else { + assert false : "Interrupt status was lost - we don't want this!"; + } + } + finally { + lock.unlock(); + } + } + + public void testTryLockInterrupt() { + // Interrupt ourselves before trying to acquire lock + Thread.currentThread().interrupt(); + + lock.tryLock(); + try { + System.out.println("Locks we have: " + s1.printLocks()); + if(Thread.interrupted()) { + System.out.println("We still have interrupt flag set, as it should be"); + } + else { + assert false : "Interrupt status was lost - we don't want this!"; + } + } + finally { + lock.unlock(); + } + } + + @Test(expectedExceptions=InterruptedException.class) + public void testLockInterruptibly() throws InterruptedException { + // Interrupt ourselves before trying to acquire lock + Thread.currentThread().interrupt(); + + lock.lockInterruptibly(); + try { + System.out.println("Locks we have: " + s1.printLocks()); + if(Thread.interrupted()) { + System.out.println("We still have interrupt flag set, as it should be"); + } + else { + assert false : "Interrupt status was lost - we don't want this!"; + } + } + finally { + lock.unlock(); + } + } + + + public void testSuccessfulSignalAllTimeout() throws InterruptedException, BrokenBarrierException { + Lock lock2=s2.getLock(LOCK); + Thread locker=new Signaller(true); + boolean rc=tryLock(lock2, 5000, LOCK); + assert rc; + locker.start(); + assert awaitNanos(lock2.newCondition(), TimeUnit.SECONDS.toNanos(5), LOCK) > 0 : "Condition was not signalled"; + unlock(lock2, LOCK); + } + + + public void testSuccessfulTryLockTimeout() throws InterruptedException, BrokenBarrierException { + final CyclicBarrier barrier=new CyclicBarrier(2); + Thread locker=new Locker(barrier); + locker.start(); + barrier.await(); + boolean rc=tryLock(lock, 10000, LOCK); + assert rc; + unlock(lock, LOCK); + } + + + public void testConcurrentLockRequests() throws Exception { + int NUM=10; + final CyclicBarrier barrier=new CyclicBarrier(NUM +1); + TryLocker[] lockers=new TryLocker[NUM]; + for(int i=0; i < lockers.length; i++) { + lockers[i]=new TryLocker(lock, barrier, 500); + lockers[i].start(); + } + barrier.await(); + for(TryLocker locker: lockers) + locker.join(); + int num_acquired=0; + for(TryLocker locker: lockers) { + if(locker.acquired) { + num_acquired++; + } + } + assert num_acquired == 1; + } + + public void testConcurrentLockRequestsFromDifferentMembers() throws Exception { + int NUM=10; + final CyclicBarrier barrier=new CyclicBarrier(NUM +1); + TryLocker[] lockers=new TryLocker[NUM]; + LockService[] services=new LockService[]{s1, s2, s3}; + + for(int i=0; i < lockers.length; i++) { + Lock mylock=services[i % services.length].getLock(LOCK); + lockers[i]=new TryLocker(mylock, barrier, 500); + lockers[i].start(); + } + barrier.await(); + for(TryLocker locker: lockers) + locker.join(); + int num_acquired=0; + for(TryLocker locker: lockers) { + if(locker.acquired) { + num_acquired++; + } + } + assert num_acquired == 1; + } + + + + + protected class Locker extends Thread { + protected final CyclicBarrier barrier; + + public Locker(CyclicBarrier barrier) { + this.barrier=barrier; + } + + public void run() { + lock(lock, LOCK); + try { + barrier.await(); + Util.sleep(500); + } + catch(Exception e) { + } + finally { + unlock(lock, LOCK); + } + } + } + + protected class Signaller extends Thread { + protected final boolean all; + + public Signaller(boolean all) { + this.all=all; + } + + public void run() { + lock(lock, LOCK); + try { + Util.sleep(500); + + if (all) { + signallingAll(lock.newCondition(), LOCK); + } + else { + signalling(lock.newCondition(), LOCK); + } + } + catch(Exception e) { + e.printStackTrace(); + } + finally { + unlock(lock, LOCK); + } + } + } + + protected static class TryLocker extends Thread { + protected final Lock mylock; + protected final CyclicBarrier barrier; + protected final long timeout; + protected boolean acquired; + + public TryLocker(Lock mylock, CyclicBarrier barrier, long timeout) { + this.mylock=mylock; + this.barrier=barrier; + this.timeout=timeout; + } + + public boolean isAcquired() { + return acquired; + } + + public void run() { + try { + barrier.await(); + } + catch(Exception e) { + e.printStackTrace(); + } + + try { + acquired=tryLock(mylock, timeout, LOCK); + Util.sleep(timeout * 2); + } + catch(InterruptedException e) { + e.printStackTrace(); + } + finally { + unlock(mylock, LOCK); + } + } + } + + + protected static void lock(Lock lock, String name) { + System.out.println("[" + Thread.currentThread().getId() + "] locking " + name); + lock.lock(); + System.out.println("[" + Thread.currentThread().getId() + "] locked " + name); + } + + protected static boolean tryLock(Lock lock, String name) { + System.out.println("[" + Thread.currentThread().getId() + "] tryLocking " + name); + boolean rc=lock.tryLock(); + System.out.println("[" + Thread.currentThread().getId() + "] " + (rc? "locked " : "failed locking") + name); + return rc; + } + + protected static boolean tryLock(Lock lock, long timeout, String name) throws InterruptedException { + System.out.println("[" + Thread.currentThread().getId() + "] tryLocking " + name); + boolean rc=lock.tryLock(timeout, TimeUnit.MILLISECONDS); + System.out.println("[" + Thread.currentThread().getId() + "] " + (rc? "locked " : "failed locking ") + name); + return rc; + } + + protected static void unlock(Lock lock, String name) { + if(lock == null) + return; + System.out.println("[" + Thread.currentThread().getId() + "] releasing " + name); + lock.unlock(); + System.out.println("[" + Thread.currentThread().getId() + "] released " + name); + } + + protected static long awaitNanos(Condition condition, long nanoSeconds, + String name) throws InterruptedException { + System.out.println("[" + Thread.currentThread().getId() + "] waiting for signal - released lock " + name); + long value = condition.awaitNanos(nanoSeconds); + System.out.println("[" + Thread.currentThread().getId() + "] waited for signal - obtained lock " + name); + return value; + } + + protected static void signalling(Condition condition, String name) { + System.out.println("[" + Thread.currentThread().getId() + "] signalling " + name); + condition.signal(); + System.out.println("[" + Thread.currentThread().getId() + "] signalled " + name); + } + + protected static void signallingAll(Condition condition, String name) { + System.out.println("[" + Thread.currentThread().getId() + "] signalling all " + name); + condition.signalAll(); + System.out.println("[" + Thread.currentThread().getId() + "] signalled " + name); + } + + protected void addLockingProtocol(JChannel ch) { + ProtocolStack stack=ch.getProtocolStack(); + Protocol lockprot = new CENTRAL_LOCK(); + lockprot.setLevel("trace"); + stack.insertProtocolAtTop(lockprot); + } +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/MuxMessageDispatcherTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/MuxMessageDispatcherTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/MuxMessageDispatcherTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/MuxMessageDispatcherTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,218 @@ +package org.jgroups.blocks; + +import java.util.Map; + +import org.jgroups.Address; +import org.jgroups.Channel; +import org.jgroups.Global; +import org.jgroups.JChannel; +import org.jgroups.Message; +import org.jgroups.blocks.mux.MuxMessageDispatcher; +import org.jgroups.blocks.mux.MuxUpHandler; +import org.jgroups.tests.ChannelTestBase; +import org.jgroups.util.Rsp; +import org.jgroups.util.Util; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * @author Paul Ferraro + */ +@Test(groups=Global.STACK_DEPENDENT) +public class MuxMessageDispatcherTest extends ChannelTestBase { + + private JChannel[] channels = new JChannel[2]; + + private MessageDispatcher[] dispatchers = new MessageDispatcher[2]; + private MessageDispatcher[][] muxDispatchers = new MessageDispatcher[2][2]; + private MethodCall method = new MethodCall("getName", new Object[0], new Class[0]); + + @BeforeClass + void setUp() throws Exception { + + channels[0] = createChannel(true); + channels[1] = createChannel(channels[0]); + + for (int i = 0; i < dispatchers.length; i++) { + + dispatchers[i] = new MessageDispatcher(channels[i], null, null, new MuxRequestListener("dispatcher[" + i + "]")); + + channels[i].setUpHandler(new MuxUpHandler(dispatchers[i].getProtocolAdapter())); + + for (int j = 0; j < muxDispatchers[i].length; j++) { + muxDispatchers[i][j] = new MuxMessageDispatcher((short) j, channels[i], null, null, new MuxRequestListener("muxDispatcher[" + i + "][" + j + "]")); + } + + channels[i].connect("MuxMessageDispatcherTest"); + + Util.sleep(1000); + } + } + + @AfterClass + void tearDown() throws Exception { + for (int i = 0; i < dispatchers.length; ++i) { + channels[i].disconnect(); + channels[i].close(); + dispatchers[i].stop(); + + for (int j = 0; j < muxDispatchers[i].length; ++j) { + muxDispatchers[i][j].stop(); + } + } + } + + public void testCastMessage() throws Exception { + + Message message = new Message(); + + // Validate normal dispatchers + Map responses = dispatchers[0].castMessage(null, message, RequestOptions.SYNC()); + + Assert.assertEquals(responses.size(), 2); + + for (int i = 0; i < dispatchers.length; ++i) { + + verifyResponse(responses, channels[i], "dispatcher[" + i + "]"); + } + + // Validate muxed dispatchers + for (int j = 0; j < muxDispatchers[0].length; ++j) { + + responses = muxDispatchers[0][j].castMessage(null, message, RequestOptions.SYNC()); + + Assert.assertEquals(responses.size(), 2); + + for (int i = 0; i < dispatchers.length; ++i) { + + verifyResponse(responses, channels[i], "muxDispatcher[" + i + "][" + j + "]"); + } + } + + final Address address = channels[0].getAddress(); + + RspFilter filter = new RspFilter() { + + public boolean isAcceptable(Object response, Address sender) { + return !sender.equals(address); + } + + public boolean needMoreResponses() { + return true; + } + }; + + // Validate muxed rpc dispatchers w/filter + responses = muxDispatchers[0][0].castMessage(null, message, RequestOptions.SYNC().setRspFilter(filter)); + + Assert.assertEquals(responses.size(), 2); + verifyResponse(responses, channels[0], null); + verifyResponse(responses, channels[1], "muxDispatcher[1][0]"); + + muxDispatchers[1][0].stop(); + + // Validate stopped mux dispatcher response is auto-filtered + responses = muxDispatchers[0][0].castMessage(null, message, RequestOptions.SYNC().setRspFilter(null)); + + Assert.assertEquals(responses.size(), 2); + verifyResponse(responses, channels[0], "muxDispatcher[0][0]"); + verifyResponse(responses, channels[1], null); + + // Validate stopped mux dispatcher response is auto-filtered and custom filter is applied + responses = muxDispatchers[0][0].castMessage(null, message, RequestOptions.SYNC().setRspFilter(filter)); + + Assert.assertEquals(responses.size(), 2); + verifyResponse(responses, channels[0], null); + verifyResponse(responses, channels[1], null); + + muxDispatchers[1][0].start(); + + // Validate restarted mux dispatcher functions normally + responses = muxDispatchers[0][0].castMessage(null, message, RequestOptions.SYNC().setRspFilter(null)); + + Assert.assertEquals(responses.size(), 2); + verifyResponse(responses, channels[0], "muxDispatcher[0][0]"); + verifyResponse(responses, channels[1], "muxDispatcher[1][0]"); + } + + public void testSendMessage() throws Throwable { + + final Address address = channels[1].getAddress(); + Message message = new Message(address); + + // Validate normal dispatchers + Object response = dispatchers[0].sendMessage(message, RequestOptions.SYNC()); + + Assert.assertEquals(response, "dispatcher[1]"); + + // Validate muxed dispatchers + for (int j = 0; j < muxDispatchers[0].length; ++j) { + + response = muxDispatchers[0][j].sendMessage(message, RequestOptions.SYNC()); + + Assert.assertEquals(response, "muxDispatcher[1][" + j + "]"); + } + + // Filter testing is disabled for now pending filter improvements in JGroups 3 + +// // Validate muxed rpc dispatchers w/filter +// +// RspFilter filter = new RspFilter() { +// +// @Override +// public boolean isAcceptable(Object response, Address sender) { +// return !sender.equals(address); +// } +// +// @Override +// public boolean needMoreResponses() { +// return true; +// } +// }; +// +// response = muxDispatchers[0][0].callRemoteMethod(address, method, RequestOptions.SYNC.setRspFilter(filter)); +// +// Assert.assertNull(address); +// +// // Validate stopped mux dispatcher response is auto-filtered +// muxDispatchers[1][0].stop(); +// +// response = muxDispatchers[0][0].callRemoteMethod(address, method, RequestOptions.SYNC.setRspFilter(null)); +// +// Assert.assertNull(address); +// +// // Validate restarted mux dispatcher functions normally +// muxDispatchers[1][0].start(); +// +// response = muxDispatchers[0][0].callRemoteMethod(address, method, RequestOptions.SYNC.setRspFilter(null)); +// +// Assert.assertEquals(response, "muxDispatcher[1][0]"); + } + + private static void verifyResponse(Map responses, Channel channel, Object expected) { + Rsp response = responses.get(channel.getAddress()); + String address = channel.getAddress().toString(); + Assert.assertNotNull(response, address); + Assert.assertFalse(response.wasSuspected(), address); + if (expected != null) { + Assert.assertTrue(response.wasReceived(), address); + Assert.assertEquals(response.getValue(), expected, address); + } else { + Assert.assertFalse(response.wasReceived(), address); + } + } + + public static class MuxRequestListener implements RequestHandler { + private final String name; + + public MuxRequestListener(String name) { + this.name = name; + } + + public Object handle(Message msg) { + return this.name; + } + } +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/MuxRpcDispatcherTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/MuxRpcDispatcherTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/MuxRpcDispatcherTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/MuxRpcDispatcherTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,217 @@ +package org.jgroups.blocks; + +import org.jgroups.Address; +import org.jgroups.Channel; +import org.jgroups.Global; +import org.jgroups.JChannel; +import org.jgroups.blocks.mux.MuxRpcDispatcher; +import org.jgroups.blocks.mux.MuxUpHandler; +import org.jgroups.tests.ChannelTestBase; +import org.jgroups.util.Rsp; +import org.jgroups.util.Util; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.util.Map; + +/** + * @author Paul Ferraro + */ +@Test(groups=Global.STACK_DEPENDENT) +public class MuxRpcDispatcherTest extends ChannelTestBase { + + private JChannel[] channels = new JChannel[2]; + + private RpcDispatcher[] dispatchers = new RpcDispatcher[2]; + private RpcDispatcher[][] muxDispatchers = new RpcDispatcher[2][2]; + + @BeforeClass + void setUp() throws Exception { + + channels[0] = createChannel(true); + channels[1] = createChannel(channels[0]); + + for (int i = 0; i < dispatchers.length; i++) { + + dispatchers[i] = new RpcDispatcher(channels[i], null, null, new Server("dispatcher[" + i + "]")); + + channels[i].setUpHandler(new MuxUpHandler(dispatchers[i].getProtocolAdapter())); + + for (int j = 0; j < muxDispatchers[i].length; j++) { + muxDispatchers[i][j] = new MuxRpcDispatcher((short) j, channels[i], null, null, new Server("muxDispatcher[" + i + "][" + j + "]")); + } + + channels[i].connect("MuxRpcDispatcherTest"); + + Util.sleep(1000); + } + } + + @AfterClass + void tearDown() throws Exception { + for (int i = 0; i < dispatchers.length; ++i) { + channels[i].disconnect(); + channels[i].close(); + dispatchers[i].stop(); + + for (int j = 0; j < muxDispatchers[i].length; ++j) { + muxDispatchers[i][j].stop(); + } + } + } + + public void testMulticastRPCs() throws Exception { + + MethodCall method = new MethodCall("getName", new Object[0], new Class[0]); + + // Validate normal dispatchers + Map responses = dispatchers[0].callRemoteMethods(null, method, RequestOptions.SYNC()); + + Assert.assertEquals(responses.size(), 2); + + for (int i = 0; i < dispatchers.length; ++i) { + + verifyResponse(responses, channels[i], "dispatcher[" + i + "]"); + } + + // Validate muxed dispatchers + for (int j = 0; j < muxDispatchers[0].length; ++j) { + + responses = muxDispatchers[0][j].callRemoteMethods(null, method, RequestOptions.SYNC()); + + Assert.assertEquals(responses.size(), 2); + + for (int i = 0; i < dispatchers.length; ++i) { + + verifyResponse(responses, channels[i], "muxDispatcher[" + i + "][" + j + "]"); + } + } + + // Validate muxed rpc dispatchers w/filter + final Address address = channels[0].getAddress(); + + RspFilter filter = new RspFilter() { + + public boolean isAcceptable(Object response, Address sender) { + return !sender.equals(address); + } + + public boolean needMoreResponses() { + return true; + } + }; + + responses = muxDispatchers[0][0].callRemoteMethods(null, method, RequestOptions.SYNC().setRspFilter(filter)); + + Assert.assertEquals(responses.size(), 2); + verifyResponse(responses, channels[0], null); + verifyResponse(responses, channels[1], "muxDispatcher[1][0]"); + + // Validate stopped mux dispatcher response is auto-filtered + muxDispatchers[1][0].stop(); + + responses = muxDispatchers[0][0].callRemoteMethods(null, method, RequestOptions.SYNC().setRspFilter(null)); + + Assert.assertEquals(responses.size(), 2); + verifyResponse(responses, channels[0], "muxDispatcher[0][0]"); + verifyResponse(responses, channels[1], null); + + // Validate stopped mux dispatcher response is auto-filtered and custom filter is applied + responses = muxDispatchers[0][0].callRemoteMethods(null, method, RequestOptions.SYNC().setRspFilter(filter)); + + Assert.assertEquals(responses.size(), 2); + verifyResponse(responses, channels[0], null); + verifyResponse(responses, channels[1], null); + + // Validate restarted mux dispatcher functions normally + muxDispatchers[1][0].start(); + + responses = muxDispatchers[0][0].callRemoteMethods(null, method, RequestOptions.SYNC().setRspFilter(null)); + + Assert.assertEquals(responses.size(), 2); + verifyResponse(responses, channels[0], "muxDispatcher[0][0]"); + verifyResponse(responses, channels[1], "muxDispatcher[1][0]"); + } + + public void testUnicastRPCs() throws Throwable { + + MethodCall method = new MethodCall("getName", new Object[0], new Class[0]); + + final Address address = channels[1].getAddress(); + + // Validate normal dispatchers + Object response = dispatchers[0].callRemoteMethod(address, method, RequestOptions.SYNC()); + + Assert.assertEquals(response, "dispatcher[1]"); + + // Validate muxed dispatchers + for (int j = 0; j < muxDispatchers[0].length; ++j) { + + response = muxDispatchers[0][j].callRemoteMethod(address, method, RequestOptions.SYNC()); + + Assert.assertEquals(response, "muxDispatcher[1][" + j + "]"); + } + + // Filter testing is disabled for now pending filter improvements in JGroups 3 + +// // Validate muxed rpc dispatchers w/filter +// +// RspFilter filter = new RspFilter() { +// +// @Override +// public boolean isAcceptable(Object response, Address sender) { +// return !sender.equals(address); +// } +// +// @Override +// public boolean needMoreResponses() { +// return true; +// } +// }; +// +// response = muxDispatchers[0][0].callRemoteMethod(address, method, RequestOptions.SYNC.setRspFilter(filter)); +// +// Assert.assertNull(address); +// +// // Validate stopped mux dispatcher response is auto-filtered +// muxDispatchers[1][0].stop(); +// +// response = muxDispatchers[0][0].callRemoteMethod(address, method, RequestOptions.SYNC.setRspFilter(null)); +// +// Assert.assertNull(address); +// +// // Validate restarted mux dispatcher functions normally +// muxDispatchers[1][0].start(); +// +// response = muxDispatchers[0][0].callRemoteMethod(address, method, RequestOptions.SYNC.setRspFilter(null)); +// +// Assert.assertEquals(response, "muxDispatcher[1][0]"); + } + + private static void verifyResponse(Map responses, Channel channel, Object expected) { + Rsp response = responses.get(channel.getAddress()); + String address = channel.getAddress().toString(); + Assert.assertNotNull(response, address); + Assert.assertFalse(response.wasSuspected(), address); + if (expected != null) { + Assert.assertTrue(response.wasReceived(), address); + Assert.assertEquals(response.getValue(), expected, address); + } else { + Assert.assertFalse(response.wasReceived(), address); + } + } + + public static class Server { + private final String name; + + public Server(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + } +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/PullPushShunTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/PullPushShunTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/PullPushShunTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/PullPushShunTest.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,61 +0,0 @@ -package org.jgroups.blocks; - -import org.testng.annotations.*; -import org.jgroups.*; -import org.jgroups.tests.ChannelTestBase; -import org.jgroups.util.Util; - -/** - * @author Bela Ban - * @version $Id: PullPushShunTest.java,v 1.10 2008/08/08 17:07:30 vlada Exp $ - */ -@Test(groups=Global.STACK_DEPENDENT) -public class PullPushShunTest extends ChannelTestBase { - private JChannel channel; - private PullPushAdapter adapter; - - public void testShunningandReconnect() throws Exception { - Address old_local_addr, new_local_addr; - channel=createChannel(true); - channel.setOpt(Channel.AUTO_RECONNECT, Boolean.TRUE); - channel.addChannelListener(new ChannelListener() { - - public void channelConnected(Channel channel) { - System.out.println("-- channelConnected()"); - } - - public void channelDisconnected(Channel channel) { - System.out.println("-- channelDisconnected()"); - } - - public void channelClosed(Channel channel) { - System.out.println("-- channelClosed()"); - } - - public void channelShunned() { - System.out.println("-- channelShunned()"); - } - - public void channelReconnected(Address addr) { - System.out.println("-- channelReconnected(" + addr + ")"); - } - }); - channel.connect("PullPushTestShun"); - adapter=new PullPushAdapter(channel, null, null); - assertEquals(1, channel.getView().getMembers().size()); - old_local_addr=channel.getLocalAddress(); - assertNotNull(old_local_addr); - - Util.sleep(1000); - System.out.println("shunning channel"); - shun(); - Util.sleep(5000); - new_local_addr=channel.getLocalAddress(); - assertNotNull(new_local_addr); - channel.close(); - } - - private void shun() { - channel.up(new Event(Event.EXIT)); - } -} diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/RpcDispatcherAnycastMultipleCallsTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/RpcDispatcherAnycastMultipleCallsTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/RpcDispatcherAnycastMultipleCallsTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/RpcDispatcherAnycastMultipleCallsTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -72,13 +72,6 @@ performTest(true, 3, true); } - public void test2InstancesMcastExcludeSelf() throws Exception { - performTest(false, 2, true); - } - - public void test3InstancesMcastExcludeSelf() throws Exception { - performTest(false, 3, true); - } /** diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/RpcDispatcherAnycastServerObject.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/RpcDispatcherAnycastServerObject.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/RpcDispatcherAnycastServerObject.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/RpcDispatcherAnycastServerObject.java 2011-10-18 11:22:35.000000000 +0000 @@ -31,7 +31,7 @@ // we need to copy the vector, otherwise the modification below will throw an exception because the underlying // vector is unmodifiable Vector

        v=new Vector
        (c.getView().getMembers()); - if(excludeSelf) v.remove(c.getLocalAddress()); + if(excludeSelf) v.remove(c.getAddress()); RspList rsps=d.callRemoteMethods(v, "doSomething", new Object[]{}, new Class[]{}, GroupRequest.GET_ALL, 10000, useAnycast); Map.Entry entry; for(Iterator it=rsps.entrySet().iterator(); it.hasNext();) { diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/RpcDispatcherAnycastTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/RpcDispatcherAnycastTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/RpcDispatcherAnycastTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/RpcDispatcherAnycastTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -14,7 +14,6 @@ /** * @author Bela Ban - * @version $Id: RpcDispatcherAnycastTest.java,v 1.11 2008/11/25 21:32:19 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT) public class RpcDispatcherAnycastTest extends ChannelTestBase { @@ -27,19 +26,19 @@ ServerObject obj=new ServerObject(null); disp=new RpcDispatcher(ch, null, null, obj); ch.connect("RpcDispatcherAnycastTest"); - obj.setAddress(ch.getLocalAddress()); + obj.setAddress(ch.getAddress()); ch2=createChannel(ch); ServerObject obj2=new ServerObject(null); disp2=new RpcDispatcher(ch2, null, null, obj2); ch2.connect("RpcDispatcherAnycastTest"); - obj2.setAddress(ch2.getLocalAddress()); + obj2.setAddress(ch2.getAddress()); ch3=createChannel(ch); ServerObject obj3=new ServerObject(null); disp3=new RpcDispatcher(ch3, null, null, obj3); ch3.connect("RpcDispatcherAnycastTest"); - obj3.setAddress(ch3.getLocalAddress()); + obj3.setAddress(ch3.getAddress()); } @AfterMethod diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/RpcDispatcherExceptionTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/RpcDispatcherExceptionTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/RpcDispatcherExceptionTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/RpcDispatcherExceptionTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -12,7 +12,6 @@ /** * @author Bela Ban - * @version $Id: RpcDispatcherExceptionTest.java,v 1.9 2008/08/08 17:07:30 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=true) public class RpcDispatcherExceptionTest extends ChannelTestBase { @@ -47,7 +46,7 @@ @Test(expectedExceptions=NotSerializableException.class) public void testUnserializableValue2() throws Throwable { - disp.callRemoteMethod(channel.getLocalAddress(), "foo", new Object[]{new Pojo()}, new Class[]{Pojo.class}, + disp.callRemoteMethod(channel.getAddress(), "foo", new Object[]{new Pojo()}, new Class[]{Pojo.class}, GroupRequest.GET_ALL, 5000); } diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/RpcDispatcherInterruptTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/RpcDispatcherInterruptTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/RpcDispatcherInterruptTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/RpcDispatcherInterruptTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -20,7 +20,6 @@ /** * Tests interruption of a blocked call with the timeout and a thread pool * @author Bela Ban - * @version $Id: RpcDispatcherInterruptTest.java,v 1.10 2008/08/08 17:07:30 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT) public class RpcDispatcherInterruptTest extends ChannelTestBase { diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/RpcDispatcherSerializationTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/RpcDispatcherSerializationTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/RpcDispatcherSerializationTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/RpcDispatcherSerializationTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -27,7 +27,6 @@ @BeforeClass protected void setUp() throws Exception { channel=createChannel(true); - channel.setOpt(Channel.AUTO_RECONNECT, Boolean.TRUE); disp=new RpcDispatcher(channel, null, null, target); channel.connect("RpcDispatcherSerializationTest"); diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/RpcDispatcherTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/RpcDispatcherTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/RpcDispatcherTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/RpcDispatcherTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -10,15 +10,15 @@ import org.jgroups.protocols.TP; import org.jgroups.stack.Protocol; import org.jgroups.tests.ChannelTestBase; -import org.jgroups.util.Rsp; -import org.jgroups.util.RspList; -import org.jgroups.util.Util; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; +import org.jgroups.util.*; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import java.util.Map; -import java.util.Vector; +import java.util.*; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * A collection of tests to test the RpcDispatcher. @@ -40,7 +40,6 @@ * This also applies to the return value of callRemoteMethod(...). * * @author Bela Ban - * @version $Id: RpcDispatcherTest.java,v 1.24 2008/11/27 16:03:20 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=true) public class RpcDispatcherTest extends ChannelTestBase { @@ -53,18 +52,21 @@ // specify return value sizes which may generate timeouts or OOMEs with 64Mb heap final static int[] HUGESIZES={10000000, 20000000}; - @BeforeClass + @BeforeMethod protected void setUp() throws Exception { c1=createChannel(true, 3); + c1.setName("A"); final String GROUP="RpcDispatcherTest"; disp1=new RpcDispatcher(c1, null, null, new ServerObject(1)); c1.connect(GROUP); c2=createChannel(c1); + c2.setName("B"); disp2=new RpcDispatcher(c2, null, null, new ServerObject(2)); c2.connect(GROUP); c3=createChannel(c1); + c3.setName("C"); disp3=new RpcDispatcher(c3, null, null, new ServerObject(3)); c3.connect(GROUP); @@ -73,7 +75,7 @@ assert view.size() == 3 : "view=" + view; } - @AfterClass + @AfterMethod protected void tearDown() throws Exception { disp3.stop(); disp2.stop(); @@ -81,7 +83,6 @@ Util.close(c3, c2, c1); } - @Test(groups="first") public void testEmptyConstructor() throws Exception { RpcDispatcher d1=new RpcDispatcher(), d2=new RpcDispatcher(); JChannel channel1=null, channel2=null; @@ -108,7 +109,7 @@ System.out.println("view channel 1= " + view); assert view.size() == 2; - RspList rsps=d1.callRemoteMethods(null, "foo", null, (Class[])null, GroupRequest.GET_ALL, 5000); + RspList rsps=d1.callRemoteMethods(null, "foo", null, null, new RequestOptions(Request.GET_ALL, 5000)); System.out.println("rsps:\n" + rsps); assert rsps.size() == 2; for(Rsp rsp: rsps.values()) { @@ -126,7 +127,7 @@ d1.setServerObject(server_object); d2.setServerObject(server_object); - rsps=d2.callRemoteMethods(null, "foobar", null, (Class[])null, GroupRequest.GET_ALL, 5000); + rsps=d2.callRemoteMethods(null, "foobar", null, null, new RequestOptions(Request.GET_ALL, 5000)); System.out.println("rsps:\n" + rsps); assert rsps.size() == 2; for(Rsp rsp: rsps.values()) { @@ -154,33 +155,176 @@ * from servers 2 and 3 are accepted. * */ - @Test(groups="first") public void testResponseFilter() { final long timeout = 10 * 1000 ; + + RequestOptions options=new RequestOptions(Request.GET_ALL, timeout, false, + new RspFilter() { + int num=0; + public boolean isAcceptable(Object response, Address sender) { + boolean retval=((Integer)response).intValue() > 1; + if(retval) + num++; + return retval; + } + + public boolean needMoreResponses() { + return num < 2; + } + }); - RspList rsps=disp1.callRemoteMethods(null, "foo", null, null,GroupRequest.GET_ALL, timeout, false, - new RspFilter() { - int num=0; - public boolean isAcceptable(Object response, Address sender) { - boolean retval=((Integer)response).intValue() > 1; - // System.out.println("-- received " + response + " from " + - // sender + ": " + (retval ? "OK" : "NOTOK")); - if(retval) - num++; - return retval; - } - - public boolean needMoreResponses() { - return num < 2; - } - }); + RspList rsps=disp1.callRemoteMethods(null, "foo", null, null, options); System.out.println("responses are:\n" + rsps); assertEquals("there should be three response values", 3, rsps.size()); assertEquals("number of responses received should be 2", 2, rsps.numReceived()); } + public void testFuture() throws Exception { + MethodCall sleep=new MethodCall("sleep", new Object[]{1000L}, new Class[]{long.class}); + Future future; + future=disp1.callRemoteMethodsWithFuture(null, sleep, new RequestOptions(Request.GET_ALL, 5000L, false, null)); + assert !future.isDone(); + assert !future.isCancelled(); + try { + future.get(300, TimeUnit.MILLISECONDS); + assert false : "we should not get here, get(300) should have thrown a TimeoutException"; + } + catch(TimeoutException e) { + System.out.println("got TimeoutException - as expected"); + } + + assert !future.isDone(); + + RspList result=future.get(6000L, TimeUnit.MILLISECONDS); + System.out.println("result:\n" + result); + assert result != null; + assert result.size() == 3; + assert future.isDone(); + } + + + public void testNotifyingFuture() throws Exception { + MethodCall sleep=new MethodCall("sleep", new Object[]{1000L}, new Class[]{long.class}); + NotifyingFuture future; + MyFutureListener listener=new MyFutureListener(); + future=disp1.callRemoteMethodsWithFuture(null, sleep, new RequestOptions(Request.GET_ALL, 5000L, false, null)); + future.setListener(listener); + assert !future.isDone(); + assert !future.isCancelled(); + assert !listener.isDone(); + Util.sleep(2000); + assert listener.isDone(); + RspList result=future.get(1L, TimeUnit.MILLISECONDS); + System.out.println("result:\n" + result); + assert result != null; + assert result.size() == 3; + assert future.isDone(); + } + + public void testNotifyingFutureWithDelayedListener() throws Exception { + MethodCall sleep=new MethodCall("sleep", new Object[]{1000L}, new Class[]{long.class}); + NotifyingFuture future; + MyFutureListener listener=new MyFutureListener(); + future=disp1.callRemoteMethodsWithFuture(null, sleep, new RequestOptions(Request.GET_ALL, 5000L, false, null)); + assert !future.isDone(); + assert !future.isCancelled(); + + Util.sleep(2000); + future.setListener(listener); + assert listener.isDone(); + RspList result=future.get(1L, TimeUnit.MILLISECONDS); + System.out.println("result:\n" + result); + assert result != null; + assert result.size() == 3; + assert future.isDone(); + } + + + public void testMultipleFutures() throws Exception { + MethodCall sleep=new MethodCall("sleep", new Object[]{100L}, new Class[]{long.class}); + List> futures=new ArrayList>(); + long target=System.currentTimeMillis() + 30000L; + + Future future; + RequestOptions options=new RequestOptions(Request.GET_ALL, 30000L, false, null); + for(int i=0; i < 10; i++) { + future=disp1.callRemoteMethodsWithFuture(null, sleep, options); + futures.add(future); + } + + List> rsps=new ArrayList>(); + while(!futures.isEmpty() && System.currentTimeMillis() < target) { + for(Iterator> it=futures.iterator(); it.hasNext();) { + future=it.next(); + if(future.isDone()) { + it.remove(); + rsps.add(future); + } + } + System.out.println("pending responses: " + futures.size()); + Util.sleep(200); + } + System.out.println("\n" + rsps.size() + " responses:\n"); + for(Future tmp: rsps) { + System.out.println(tmp); + } + } + + public void testMultipleNotifyingFutures() throws Exception { + MethodCall sleep=new MethodCall("sleep", new Object[]{100L}, new Class[]{long.class}); + List listeners=new ArrayList(); + RequestOptions options=new RequestOptions(Request.GET_ALL, 30000L, false, null); + for(int i=0; i < 10; i++) { + MyFutureListener listener=new MyFutureListener(); + listeners.add(listener); + disp1.callRemoteMethodsWithFuture(null, sleep, options).setListener(listener); + } + + Util.sleep(1000); + for(int i=0; i < 10; i++) { + boolean all_done=true; + for(MyFutureListener listener: listeners) { + boolean done=listener.isDone(); + System.out.print(done? "+ " : "- "); + if(!listener.isDone()) + all_done=false; + } + if(all_done) + break; + Util.sleep(500); + System.out.println(""); + } + + for(MyFutureListener listener: listeners) { + assert listener.isDone(); + } + + } + + + + + public void testFutureCancel() throws Exception { + MethodCall sleep=new MethodCall("sleep", new Object[]{1000L}, new Class[]{long.class}); + Future future; + future=disp1.callRemoteMethodsWithFuture(null, sleep, new RequestOptions(Request.GET_ALL, 5000L)); + assert !future.isDone(); + assert !future.isCancelled(); + future.cancel(true); + assert future.isDone(); + assert future.isCancelled(); + + future=disp1.callRemoteMethodsWithFuture(null, sleep, new RequestOptions(Request.GET_ALL, 0)); + assert !future.isDone(); + assert !future.isCancelled(); + future.cancel(true); + assert future.isDone(); + assert future.isCancelled(); + } + + /** * Test the ability of RpcDispatcher to handle large argument and return values * with multicast RPC calls. @@ -192,7 +336,6 @@ * The expected behaviour is that all RPC requests complete successfully. * */ - @Test(groups="first",enabled=false) public void testLargeReturnValue() { setProps(c1, c2, c3); for(int i=0; i < SIZES.length; i++) { @@ -225,7 +368,6 @@ /** * Tests a method call to {A,B,C} where C left *before* the call. http://jira.jboss.com/jira/browse/JGRP-620 */ - @Test(dependsOnGroups="first") public void testMethodInvocationToNonExistingMembers() { final int timeout = 5 * 1000 ; @@ -243,7 +385,7 @@ // make an RPC call using C's now outdated view of membership System.out.println("calling method foo() in " + members + " (view=" + c2.getView() + ")"); - RspList rsps=disp1.callRemoteMethods(members, "foo", null, (Class[])null, GroupRequest.GET_ALL, timeout); + RspList rsps=disp1.callRemoteMethods(members, "foo", null, null, new RequestOptions(Request.GET_ALL, timeout)); // all responses System.out.println("responses:\n" + rsps); @@ -266,11 +408,10 @@ * The expected behaviour is that all RPC requests complete successfully. * */ - @Test(groups="first") public void testLargeReturnValueUnicastCall() throws Throwable { setProps(c1, c2, c3); for(int i=0; i < SIZES.length; i++) { - _testLargeValueUnicastCall(c1.getLocalAddress(), SIZES[i]); + _testLargeValueUnicastCall(c1.getAddress(), SIZES[i]); } } @@ -305,7 +446,8 @@ final long timeout = 20 * 1000 ; System.out.println("\ntesting with " + size + " bytes"); - RspList rsps=disp1.callRemoteMethods(null, "largeReturnValue", new Object[]{size}, new Class[]{int.class}, GroupRequest.GET_ALL, timeout); + RspList rsps=disp1.callRemoteMethods(null, "largeReturnValue", new Object[]{size}, new Class[]{int.class}, + new RequestOptions(Request.GET_ALL, timeout)); System.out.println("rsps:"); assert rsps.size() == 3 : "there should be three responses to the RPC call but only " + rsps.size() + " were received: " + rsps; @@ -340,7 +482,8 @@ final long timeout = 20 * 1000 ; System.out.println("\ntesting with " + size + " bytes"); - RspList rsps=disp1.callRemoteMethods(null, "largeReturnValue", new Object[]{size}, new Class[]{int.class}, GroupRequest.GET_ALL, timeout); + RspList rsps=disp1.callRemoteMethods(null, "largeReturnValue", new Object[]{size}, new Class[]{int.class}, + new RequestOptions(Request.GET_ALL, timeout)); System.out.println("rsps:"); assert rsps != null; assert rsps.size() == 3 : "there should be three responses to the RPC call but only " + rsps.size() + @@ -393,7 +536,8 @@ System.out.println("\ntesting unicast call with " + size + " bytes"); assertNotNull(dst); - Object retval=disp1.callRemoteMethod(dst, "largeReturnValue", new Object[]{size}, new Class[]{int.class}, GroupRequest.GET_ALL, timeout); + Object retval=disp1.callRemoteMethod(dst, "largeReturnValue", new Object[]{size}, new Class[]{int.class}, + new RequestOptions(Request.GET_ALL, timeout)); // it's possible that an exception was raised if (retval instanceof java.lang.Throwable) { @@ -425,6 +569,13 @@ this.i=i; } public int foo() {return i;} + + public static long sleep(long timeout) { + // System.out.println("sleep()"); + long start=System.currentTimeMillis(); + Util.sleep(timeout); + return System.currentTimeMillis() - start; + } public static byte[] largeReturnValue(int size) { @@ -432,5 +583,15 @@ } } + private static class MyFutureListener implements FutureListener { + private boolean done; + + public void futureDone(Future future) { + done=true; + } + + public boolean isDone() {return done;} + } + } \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/RpcDispatcherUnicastMethodExceptionTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/RpcDispatcherUnicastMethodExceptionTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/RpcDispatcherUnicastMethodExceptionTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/RpcDispatcherUnicastMethodExceptionTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -10,7 +10,6 @@ /** * @author Bela Ban - * @version $Id: RpcDispatcherUnicastMethodExceptionTest.java,v 1.11 2008/08/08 17:07:30 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=true) public class RpcDispatcherUnicastMethodExceptionTest extends ChannelTestBase { @@ -57,7 +56,7 @@ public void testMethodWithoutException() throws Throwable { - Object retval=disp.callRemoteMethod(channel.getLocalAddress(), "foo", null, (Class[])null, GroupRequest.GET_ALL, 5000); + Object retval=disp.callRemoteMethod(channel.getAddress(), "foo", null, (Class[])null, GroupRequest.GET_ALL, 5000); System.out.println("retval: " + retval); assertNotNull(retval); } @@ -65,25 +64,25 @@ @Test(expectedExceptions=TimeoutException.class) public void testMethodWithException() throws Throwable { - Object retval=disp.callRemoteMethod(channel.getLocalAddress(), "bar", null, (Class[])null, GroupRequest.GET_ALL, 5000); + Object retval=disp.callRemoteMethod(channel.getAddress(), "bar", null, (Class[])null, GroupRequest.GET_ALL, 5000); System.out.println("retval: " + retval); } @Test(expectedExceptions=IllegalArgumentException.class) public void testMethodWithException2() throws Throwable { - Object retval=disp.callRemoteMethod(channel.getLocalAddress(), "foobar", null, (Class[])null, GroupRequest.GET_ALL, 5000); + Object retval=disp.callRemoteMethod(channel.getAddress(), "foobar", null, (Class[])null, GroupRequest.GET_ALL, 5000); System.out.println("retval: " + retval); } @Test(expectedExceptions=AssertionError.class) public void testMethodWithError() throws Throwable { - Object retval=disp.callRemoteMethod(channel.getLocalAddress(), "foofoobar", null, (Class[])null, GroupRequest.GET_ALL, 5000); + Object retval=disp.callRemoteMethod(channel.getAddress(), "foofoobar", null, (Class[])null, GroupRequest.GET_ALL, 5000); System.out.println("retval: " + retval); } @Test(expectedExceptions=Throwable.class) public void testMethodWithThrowable() throws Throwable { - Object retval=disp.callRemoteMethod(channel.getLocalAddress(), "fooWithThrowable", null, (Class[])null, GroupRequest.GET_ALL, 5000); + Object retval=disp.callRemoteMethod(channel.getAddress(), "fooWithThrowable", null, (Class[])null, GroupRequest.GET_ALL, 5000); System.out.println("retval: " + retval); } diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/RpcDispatcherUnitTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/RpcDispatcherUnitTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/blocks/RpcDispatcherUnitTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/blocks/RpcDispatcherUnitTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,141 @@ +package org.jgroups.blocks; + + +import org.jgroups.Global; +import org.jgroups.JChannel; +import org.jgroups.View; +import org.jgroups.Address; +import org.jgroups.tests.ChannelTestBase; +import org.jgroups.util.*; +import org.testng.annotations.*; + +import java.util.List; +import java.util.Arrays; + +/** + * @author Bela Ban + */ +@Test(groups=Global.STACK_DEPENDENT,sequential=true) +public class RpcDispatcherUnitTest extends ChannelTestBase { + private RpcDispatcher d1, d2, d3; + private JChannel c1, c2, c3; + private ServerObject o1, o2, o3; + private Address a1, a2, a3; + private List
        members; + + + @BeforeClass + protected void setUp() throws Exception { + o1=new ServerObject(); + o2=new ServerObject(); + o3=new ServerObject(); + + c1=createChannel(true, 3); + c1.setName("A"); + final String GROUP="RpcDispatcherUnitTest"; + d1=new RpcDispatcher(c1, null, null, o1); + c1.connect(GROUP); + + c2=createChannel(c1); + c2.setName("B"); + d2=new RpcDispatcher(c2, null, null, o2); + c2.connect(GROUP); + + c3=createChannel(c1); + c3.setName("C"); + d3=new RpcDispatcher(c3, null, null, o3); + c3.connect(GROUP); + + System.out.println("c1.view=" + c1.getView() + "\nc2.view=" + c2.getView() + "\nc3.view=" + c3.getView()); + View view=c3.getView(); + assert view.size() == 3 : "view=" + view; + + a1=c1.getAddress(); + a2=c2.getAddress(); + a3=c3.getAddress(); + members=Arrays.asList(a1, a2, a3); + } + + @BeforeMethod + protected void reset() { + o1.reset(); + o2.reset(); + o3.reset(); + } + + @AfterClass + protected void tearDown() throws Exception { + d3.stop(); + d2.stop(); + d1.stop(); + Util.close(c3, c2, c1); + } + + + public void testInvocationOnEntireGroup() { + RspList rsps=d1.callRemoteMethods(null, "foo", null, null, RequestOptions.SYNC()); + System.out.println("rsps:\n" + rsps); + assert rsps.size() == 3; + assert o1.wasCalled() && o2.wasCalled() && o3.wasCalled(); + } + + public void testInvocationOnEntireGroupWithTargetList() { + RspList rsps=d1.callRemoteMethods(members, "foo", null, null, RequestOptions.SYNC()); + System.out.println("rsps:\n" + rsps); + assert rsps.size() == 3; + assert o1.wasCalled() && o2.wasCalled() && o3.wasCalled(); + } + + + /** Invoke a method on all but myself */ + public void testInvocationWithExclusionOfSelf() { + RequestOptions options=new RequestOptions(Request.GET_ALL, 5000).setExclusionList(a1); + RspList rsps=d1.callRemoteMethods(null, "foo", null, null, options); + Util.sleep(500); + System.out.println("rsps:\n" + rsps); + assert rsps.size() == 2; + assert rsps.containsKey(a2) && rsps.containsKey(a3); + assert !o1.wasCalled() && o2.wasCalled() && o3.wasCalled(); + } + + public void testInvocationWithExclusionOfTwo() { + RequestOptions options=new RequestOptions(Request.GET_ALL, 5000).setExclusionList(a2, a3); + RspList rsps=d1.callRemoteMethods(null, "foo", null, null, options); + Util.sleep(500); + System.out.println("rsps:\n" + rsps); + assert rsps.size() == 1; + assert rsps.containsKey(a1); + assert o1.wasCalled() && !o2.wasCalled() && !o3.wasCalled(); + } + + public void testInvocationOnEmptyTargetSet() { + RequestOptions options=new RequestOptions(Request.GET_ALL, 5000).setExclusionList(a1, a2, a3); + RspList rsps=d1.callRemoteMethods(null, "foo", null, null, options); + Util.sleep(500); + System.out.println("rsps:\n" + rsps); + assert rsps.isEmpty(); + assert !o1.wasCalled() && !o2.wasCalled() && !o3.wasCalled(); + } + + + + private static class ServerObject { + boolean called=false; + + public boolean wasCalled() { + return called; + } + + public void reset() { + called=false; + } + + public boolean foo() { + called=true; + return called; + } + } + + + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/protocols/FRAG2_Test.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/protocols/FRAG2_Test.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/protocols/FRAG2_Test.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/protocols/FRAG2_Test.java 2011-10-18 11:22:35.000000000 +0000 @@ -23,7 +23,7 @@ * Tests the fragmentation (FRAG2) protocol for http://jira.jboss.com/jira/browse/JGRP-215 * @author Bela Ban */ -@Test(groups={Global.STACK_DEPENDENT}) +@Test(groups={Global.STACK_INDEPENDENT}) public class FRAG2_Test extends ChannelTestBase { private IpAddress a1; private Vector
        members; diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/protocols/GMS_MergeTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/protocols/GMS_MergeTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/protocols/GMS_MergeTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/protocols/GMS_MergeTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,760 @@ +package org.jgroups.protocols; + + +import org.jgroups.*; +import org.jgroups.conf.ProtocolStackConfigurator; +import org.jgroups.conf.ClassConfigurator; +import org.jgroups.protocols.pbcast.GMS; +import org.jgroups.protocols.pbcast.NAKACK; +import org.jgroups.protocols.pbcast.STABLE; +import org.jgroups.stack.ProtocolStack; +import org.jgroups.tests.ChannelTestBase; +import org.jgroups.util.Digest; +import org.jgroups.util.MergeId; +import org.jgroups.util.Util; +import org.testng.annotations.Test; +import org.w3c.dom.Element; + +import java.io.File; +import java.net.URL; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Tests the GMS protocol for merging functionality + * @author Bela Ban + */ +@Test(groups={Global.STACK_INDEPENDENT}, sequential=true) +public class GMS_MergeTest extends ChannelTestBase { + static final String simple_props="SHARED_LOOPBACK:PING(timeout=1000):" + + "pbcast.NAKACK(use_mcast_xmit=false;gc_lag=0;log_discard_msgs=false;log_not_found_msgs=false)" + + ":UNICAST:pbcast.STABLE(stability_delay=200):pbcast.GMS:FC:FRAG2"; + + static final String flush_props=simple_props + ":pbcast.FLUSH"; + + static final short GMS_ID=ClassConfigurator.getProtocolId(GMS.class); + + + + public static void testMergeRequestTimeout() throws Exception { + _testMergeRequestTimeout(simple_props, "testMergeRequestTimeout"); + } + + public static void testMergeRequestTimeoutWithFlush() throws Exception { + _testMergeRequestTimeout(flush_props, "testMergeRequestTimeoutWithFlush"); + } + + public static void testSimpleMerge() throws Exception { + _testSimpleMerge(simple_props, "testSimpleMerge"); + } + + public static void testSimpleMergeWithFlush() throws Exception { + _testSimpleMerge(flush_props, "testSimpleMergeWithFlush"); + } + + public static void testConcurrentMergeTwoPartitions() throws Exception { + _testConcurrentMergeTwoPartitions(simple_props, "testConcurrentMergeTwoPartitions"); + } + + public static void testConcurrentMergeTwoPartitionsWithFlush() throws Exception { + _testConcurrentMergeTwoPartitions(flush_props, "testConcurrentMergeTwoPartitionsWithFlush"); + } + + public static void testConcurrentMergeMultiplePartitions() throws Exception { + _testConcurrentMergeMultiplePartitions(simple_props, "testConcurrentMergeMultiplePartitions"); + } + + public static void testConcurrentMergeMultiplePartitionsWithFlush() throws Exception { + _testConcurrentMergeMultiplePartitions(flush_props, "testConcurrentMergeMultiplePartitionsWithFlush"); + } + + public static void testMergeAsymmetricPartitions() throws Exception { + _testMergeAsymmetricPartitions(simple_props, "testMergeAsymmetricPartitions"); + } + + public static void testMergeAsymmetricPartitionsWithFlush() throws Exception { + _testMergeAsymmetricPartitions(flush_props, "testMergeAsymmetricPartitionsWithFlush"); + } + + public static void testMergeAsymmetricPartitions2() throws Exception { + _testMergeAsymmetricPartitions2(simple_props, "testMergeAsymmetricPartitions2"); + } + + public static void testMergeAsymmetricPartitionsWithFlush2() throws Exception { + _testMergeAsymmetricPartitions2(flush_props, "testMergeAsymmetricPartitionsWithFlush2"); + } + + + /** + * Simulates the death of a merge leader after having sent a MERG_REQ. Because there is no MergeView or CANCEL_MERGE + * message, the MergeCanceller has to null merge_id after a timeout + */ + static void _testMergeRequestTimeout(String props, String cluster_name) throws Exception { + JChannel c1=new JChannel(props); + try { + c1.connect(cluster_name); + Message merge_request=new Message(); + GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.MERGE_REQ); + MergeId new_merge_id=MergeId.create(c1.getAddress()); + hdr.setMergeId(new_merge_id); + merge_request.putHeader(GMS_ID, hdr); + GMS gms=(GMS)c1.getProtocolStack().findProtocol(GMS.class); + gms.setMergeTimeout(2000); + MergeId merge_id=gms.getMergeId(); + assert merge_id == null; + System.out.println("starting merge"); + gms.up(new Event(Event.MSG, merge_request)); + merge_id=gms.getMergeId(); + System.out.println("merge_id = " + merge_id); + assert new_merge_id.equals(merge_id); + + long timeout=gms.getMergeTimeout() * 2; + System.out.println("sleeping for " + timeout + " ms, then fetching merge_id: should be null (cancelled by the MergeCanceller)"); + long target_time=System.currentTimeMillis() + timeout; + while(System.currentTimeMillis() < target_time) { + merge_id=gms.getMergeId(); + if(merge_id == null) + break; + Util.sleep(500); + } + + merge_id=gms.getMergeId(); + System.out.println("merge_id = " + merge_id); + assert merge_id == null : "MergeCanceller didn't kick in"; + } + finally { + Util.close(c1); + } + } + + + static void _testSimpleMerge(String props, String cluster_name) throws Exception { + JChannel[] channels=null; + try { + channels=create(props, true, cluster_name, "A", "B", "C", "D"); + print(channels); + View view=channels[channels.length -1].getView(); + assert view.size() == channels.length : "view is " + view; + + System.out.println("\ncreating partitions: "); + String[][] partitions=generate(new String[]{"A", "B"}, new String[]{"C", "D"}); + createPartitions(channels, partitions); + print(channels); + checkViews(channels, "A", "A", "B"); + checkViews(channels, "B", "A", "B"); + checkViews(channels, "C", "C", "D"); + checkViews(channels, "C", "C", "D"); + + System.out.println("\ndigests:"); + printDigests(channels); + + Address leader=determineLeader(channels, "A", "C"); + long end_time=System.currentTimeMillis() + 30000; + do { + System.out.println("\n==== injecting merge events into " + leader + " ===="); + injectMergeEvent(channels, leader, "A", "C"); + Util.sleep(1000); + if(allChannelsHaveViewOf(channels, channels.length)) + break; + } + while(end_time > System.currentTimeMillis()); + + System.out.println("\n"); + print(channels); + assertAllChannelsHaveViewOf(channels, channels.length); + + System.out.println("\ndigests:"); + printDigests(channels); + } + finally { + System.out.println("closing channels"); + close(channels); + System.out.println("done"); + } + } + + + static void _testConcurrentMergeTwoPartitions(String props, String cluster_name) throws Exception { + JChannel[] channels=null; + try { + channels=create(props, true, cluster_name, "A", "B", "C", "D"); + print(channels); + View view=channels[channels.length -1].getView(); + assert view.size() == channels.length : "view is " + view; + + System.out.println("\ncreating partitions: "); + String[][] partitions=generate(new String[]{"A", "B"}, new String[]{"C", "D"}); + createPartitions(channels, partitions); + print(channels); + checkViews(channels, "A", "A", "B"); + checkViews(channels, "B", "A", "B"); + checkViews(channels, "C", "C", "D"); + checkViews(channels, "D", "C", "D"); + + long end_time=System.currentTimeMillis() + 30000; + do { + System.out.println("\n==== injecting merge events into A and C concurrently ===="); + injectMergeEvent(channels, "C", "A", "C"); + injectMergeEvent(channels, "A", "A", "C"); + Util.sleep(1000); + if(allChannelsHaveViewOf(channels, 4)) + break; + } + while(end_time > System.currentTimeMillis()); + + System.out.println("\n"); + print(channels); + assertAllChannelsHaveViewOf(channels, 4); + } + finally { + close(channels); + } + } + + + static void _testConcurrentMergeMultiplePartitions(String props, String cluster_name) throws Exception { + JChannel[] channels=null; + try { + channels=create(props, true, cluster_name, "A", "B", "C", "D", "E", "F", "G", "H"); + print(channels); + View view=channels[channels.length -1].getView(); + assert view.size() == channels.length : "view is " + view; + assertAllChannelsHaveViewOf(channels, 8); + + System.out.println("\ncreating partitions: "); + String[][] partitions=generate(new String[]{"A", "B"}, + new String[]{"C", "D"}, + new String[]{"E", "F"}, + new String[]{"G", "H"}); + createPartitions(channels, partitions); + print(channels); + checkViews(channels, "A", "A", "B"); + checkViews(channels, "B", "A", "B"); + checkViews(channels, "C", "C", "D"); + checkViews(channels, "D", "C", "D"); + checkViews(channels, "E", "E", "F"); + checkViews(channels, "F", "E", "F"); + checkViews(channels, "G", "G", "H"); + checkViews(channels, "H", "G", "H"); + + long end_time=System.currentTimeMillis() + 30000; + do { + System.out.println("\n==== injecting merge event into A, C, E and G concurrently ===="); + injectMergeEvent(channels, "G", "A", "C", "E", "G"); + injectMergeEvent(channels, "E", "A", "C", "E", "G"); + injectMergeEvent(channels, "A", "A", "C", "E", "G"); + injectMergeEvent(channels, "C", "A", "C", "E", "G"); + Util.sleep(1000); + if(allChannelsHaveViewOf(channels, 8)) + break; + } + while(end_time > System.currentTimeMillis()); + + print(channels); + assertAllChannelsHaveViewOf(channels, 8); + } + finally { + close(channels); + } + } + + + /** + * Tests the merge of the following partitions: + *
          + *
        • A: {B, A, C} + *
        • B: {B, C} + *
        • C: {B, C} + * + * JIRA: https://jira.jboss.org/jira/browse/JGRP-1031 + * @throws Exception + */ + static void _testMergeAsymmetricPartitions(String props, String cluster_name) throws Exception { + JChannel[] channels=null; + MyReceiver[] receivers; + final int NUM=10; + try { + // use simple IDs for UUIDs, so sorting on merge will NOT change the view order + channels=create(props, true, cluster_name, "B", "A", "C"); + receivers=new MyReceiver[channels.length]; + for(int i=0; i < channels.length; i++) { + receivers[i]=new MyReceiver(channels[i].getName()); + channels[i].setReceiver(receivers[i]); + } + + JChannel a=findChannel("A", channels), b=findChannel("B", channels), c=findChannel("C", channels); + print(channels); + View view=channels[channels.length -1].getView(); + assert view.size() == channels.length : "view is " + view; + + System.out.println("sending " + NUM + " msgs:"); + for(int i=0; i < NUM; i++) + for(JChannel ch: channels) + ch.send(null, null, "Number #" + i + " from " + ch.getAddress()); + + waitForNumMessages(NUM * channels.length, 10000, 1000, receivers); + checkMessages(NUM * channels.length, receivers); + + System.out.println("\ncreating partitions: "); + applyView(channels, "A", "B", "A", "C"); + applyView(channels, "B", "B", "C"); + applyView(channels, "C", "B", "C"); + + print(channels); + checkViews(channels, "A", "B", "A", "C"); + checkViews(channels, "B", "B", "C"); + checkViews(channels, "C", "B", "C"); + + for(MyReceiver receiver: receivers) + receiver.clear(); + + DISCARD discard=new DISCARD(); + discard.addIgnoreMember(b.getAddress()); + discard.addIgnoreMember(c.getAddress()); + + // A should drop all traffic from B or C + a.getProtocolStack().insertProtocol(discard, ProtocolStack.ABOVE, SHARED_LOOPBACK.class); + + System.out.println("B and C exchange " + NUM + " messages, A discards them"); + for(int i=0; i < NUM; i++) + b.send(null, null, "message #" + i +" from B"); + for(int i=0; i < NUM; i++) + c.send(null, null, "message #" + i +" from C"); + waitForNumMessages(NUM * 2, 10000, 500, receivers[0], receivers[2]); // A *does* receiver B's and C's messages ! + checkMessages(NUM * 2, receivers[0], receivers[2]); + checkMessages(0, receivers[1]); + + Digest da=((NAKACK)a.getProtocolStack().findProtocol(NAKACK.class)).getDigest(), + db=((NAKACK)b.getProtocolStack().findProtocol(NAKACK.class)).getDigest(), + dc=((NAKACK)c.getProtocolStack().findProtocol(NAKACK.class)).getDigest(); + + System.out.println("Digest A: " + da + "\nDigest B: " + db + "\nDigest C: " + dc); + System.out.println("Running stability protocol on B and C now"); + +// a.getProtocolStack().findProtocol(STABLE.class).setLevel("trace"); +// b.getProtocolStack().findProtocol(STABLE.class).setLevel("trace"); +// c.getProtocolStack().findProtocol(STABLE.class).setLevel("trace"); + + for(int i=0; i < 3; i++) { + ((STABLE)b.getProtocolStack().findProtocol(STABLE.class)).runMessageGarbageCollection(); + ((STABLE)c.getProtocolStack().findProtocol(STABLE.class)).runMessageGarbageCollection(); + Util.sleep(300); + } + + db=((NAKACK)a.getProtocolStack().findProtocol(NAKACK.class)).getDigest(); + db=((NAKACK)b.getProtocolStack().findProtocol(NAKACK.class)).getDigest(); + dc=((NAKACK)c.getProtocolStack().findProtocol(NAKACK.class)).getDigest(); + System.out.println("(after purging)\nDigest A: " + da + "\nDigest B: " + db + "\nDigest C: " + dc); + + // now enable traffic reception of B and C on A: + discard.removeIgnoredMember(b.getAddress()); + discard.removeIgnoredMember(c.getAddress()); + + Address leader=b.getAddress(); + + long end_time=System.currentTimeMillis() + 12000; + do { + System.out.println("\n==== injecting merge event into " + leader + " ===="); + injectMergeEvent(channels, leader, "B", "A", "C"); + Util.sleep(3000); + if(allChannelsHaveView(channels, b.getView())) + break; + } + while(end_time > System.currentTimeMillis()); + + System.out.println("\n"); + print(channels); + assertAllChannelsHaveView(channels, b.getView()); + } + finally { + close(channels); + } + } + + +/** + * Tests the merge of the following partitions: + *
            + *
          • A: {A,B} + *
          • B: {B} + * + * JIRA: https://jira.jboss.org/jira/browse/JGRP-1031 + * @throws Exception + */ + static void _testMergeAsymmetricPartitions2(String props, String cluster_name) throws Exception { + JChannel[] channels=null; + MyReceiver[] receivers; + final int NUM=10; + try { + // use simple IDs for UUIDs, so sorting on merge will NOT change the view order + channels=create(props, true, cluster_name, "A", "B"); + receivers=new MyReceiver[channels.length]; + for(int i=0; i < channels.length; i++) { + receivers[i]=new MyReceiver(channels[i].getName()); + channels[i].setReceiver(receivers[i]); + } + + JChannel a=findChannel("A", channels), b=findChannel("B", channels); + print(channels); + View view=channels[channels.length -1].getView(); + assert view.size() == channels.length : "view is " + view; + + System.out.println("sending " + NUM + " msgs:"); + for(int i=0; i < NUM; i++) + for(JChannel ch: channels) + ch.send(null, null, "Number #" + i + " from " + ch.getAddress()); + + waitForNumMessages(NUM * channels.length, 10000, 1000, receivers); + checkMessages(NUM * channels.length, receivers); + + System.out.println("\ncreating partitions: "); + applyView(channels, "B", "B"); // B has view {B} + + print(channels); + checkViews(channels, "A", "A", "B"); // A: {A,B} + checkViews(channels, "B", "B"); // B: {B} + + for(MyReceiver receiver: receivers) + receiver.clear(); + + Digest da=((NAKACK)a.getProtocolStack().findProtocol(NAKACK.class)).getDigest(); + Digest db=((NAKACK)b.getProtocolStack().findProtocol(NAKACK.class)).getDigest(); + System.out.println("(after purging)\nDigest A: " + da + "\nDigest B: " + db); + + long end_time=System.currentTimeMillis() + 12000; + do { + System.out.println("\n==== injecting merge event ===="); + injectMergeEvent(channels, a.getAddress(), "A", "B"); + injectMergeEvent(channels, b.getAddress(), "A", "B"); + Util.sleep(3000); + if(allChannelsHaveView(channels, a.getView())) + break; + } + while(end_time > System.currentTimeMillis()); + + /* long end_time=System.currentTimeMillis() + 12000; + do { + if(allChannelsHaveView(channels, a.getView())) + break; + Util.sleep(3000); + } + while(end_time > System.currentTimeMillis());*/ + + System.out.println("\n"); + print(channels); + assertAllChannelsHaveView(channels, a.getView()); + } + finally { + close(channels); + } + } + + + + /** + * First name is the channel name, the rest is the view to be applied + * @param members + */ + private static void applyView(JChannel[] channels, String member, String ... members) throws Exception { + JChannel ch=findChannel(member, channels); + View view=createView(members, channels); + GMS gms=(GMS)ch.getProtocolStack().findProtocol(GMS.class); + gms.installView(view); + } + + + private static boolean allChannelsHaveViewOf(JChannel[] channels, int count) { + for(JChannel ch: channels) { + if(ch.getView().size() != count) + return false; + } + return true; + } + + private static boolean allChannelsHaveView(JChannel[] channels, View view) { + for(JChannel ch: channels) { + if(!ch.getView().equals(view)) + return false; + } + return true; + } + + private static void assertAllChannelsHaveView(JChannel[] channels, View view) { + for(JChannel ch: channels) { + View v=ch.getView(); + assert v.equals(view) : "expected view " + view + " but got " + v + " for channel " + ch.getName(); + } + } + + private static void assertAllChannelsHaveViewOf(JChannel[] channels, int count) { + for(JChannel ch: channels) + assert ch.getView().size() == count : ch.getName() + " has view " + ch.getView() + " (should have " + count + " mbrs)"; + } + + + private static void close(JChannel[] channels) { + if(channels == null) return; + for(int i=channels.length -1; i >= 0; i--) { + JChannel ch=channels[i]; + Util.close(ch); + } + } + + + /*private static JChannel[] create(String cluster_name, String ... names) throws Exception { + return create(false, cluster_name, names); + }*/ + + private static JChannel[] create(String props, boolean simple_ids, String cluster_name, String ... names) throws Exception { + JChannel[] retval=new JChannel[names.length]; + for(int i=0; i < retval.length; i++) { + JChannel ch; + if(simple_ids) { + ch=new MyChannel(props); + ((MyChannel)ch).setId(i+1); + } + else + ch=new JChannel(props); + ch.setName(names[i]); + retval[i]=ch; + ch.connect(cluster_name); + if(i == 0) + Util.sleep(3000); + } + return retval; + } + + private static void createPartitions(JChannel[] channels, String[]... partitions) throws Exception { + checkUniqueness(partitions); + List views=new ArrayList(partitions.length); + for(String[] partition: partitions) { + View view=createView(partition, channels); + views.add(view); + } + applyViews(views, channels); + } + + private static void injectMergeEvent(JChannel[] channels, String leader, String ... coordinators) { + Address leader_addr=leader != null? findAddress(leader, channels) : determineLeader(channels); + injectMergeEvent(channels, leader_addr, coordinators); + } + + private static void injectMergeEvent(JChannel[] channels, Address leader_addr, String ... coordinators) { + Map views=new HashMap(); + for(String tmp: coordinators) { + Address coord=findAddress(tmp, channels); + views.put(coord, findView(tmp, channels)); + } + + JChannel coord=findChannel(leader_addr, channels); + GMS gms=(GMS)coord.getProtocolStack().findProtocol(GMS.class); + gms.setLevel("trace"); + gms.up(new Event(Event.MERGE, views)); + } + + private static Address determineLeader(JChannel[] channels, String ... coords) { + Membership membership=new Membership(); + for(String coord: coords) + membership.add(findAddress(coord, channels)); + membership.sort(); + return membership.elementAt(0); + } + + + private static String[][] generate(String[] ... partitions) { + String[][] retval=new String[partitions.length][]; + System.arraycopy(partitions, 0, retval, 0, partitions.length); + return retval; + } + + private static void checkUniqueness(String[] ... partitions) throws Exception { + Set set=new HashSet(); + for(String[] partition: partitions) { + for(String tmp: partition) { + if(!set.add(tmp)) + throw new Exception("partitions are overlapping: element " + tmp + " is in multiple partitions"); + } + } + } + + private static View createView(String[] partition, JChannel[] channels) throws Exception { + Vector
            members=new Vector
            (partition.length); + for(String tmp: partition) { + Address addr=findAddress(tmp, channels); + if(addr == null) + throw new Exception(tmp + " not associated with a channel"); + members.add(addr); + } + return new View(members.firstElement(), 10, members); + } + + private static void checkViews(JChannel[] channels, String channel_name, String ... members) { + JChannel ch=findChannel(channel_name, channels); + View view=ch.getView(); + assert view.size() == members.length : "view is " + view + ", members: " + Arrays.toString(members); + for(String member: members) { + Address addr=findAddress(member, channels); + assert view.getMembers().contains(addr) : "view " + view + " does not contain " + addr; + } + } + + private static JChannel findChannel(String tmp, JChannel[] channels) { + for(JChannel ch: channels) { + if(ch.getName().equals(tmp)) + return ch; + } + return null; + } + + private static JChannel findChannel(Address addr, JChannel[] channels) { + for(JChannel ch: channels) { + if(ch.getAddress().equals(addr)) + return ch; + } + return null; + } + + private static Address findAddress(String tmp, JChannel[] channels) { + for(JChannel ch: channels) { + if(ch.getName().equals(tmp)) + return ch.getAddress(); + } + return null; + } + + private static View findView(String tmp, JChannel[] channels) { + for(JChannel ch: channels) { + if(ch.getName().equals(tmp)) + return ch.getView(); + } + return null; + } + + private static void applyViews(List views, JChannel[] channels) { + for(View view: views) { + Collection
            members=view.getMembers(); + for(JChannel ch: channels) { + Address addr=ch.getAddress(); + if(members.contains(addr)) { + GMS gms=(GMS)ch.getProtocolStack().findProtocol(GMS.class); + gms.installView(view); + } + } + } + } + + private static void print(JChannel[] channels) { + for(JChannel ch: channels) { + System.out.println(ch.getName() + ": " + ch.getView()); + } + } + + private static void printDigests(JChannel[] channels) { + for(JChannel ch: channels) { + NAKACK nak=(NAKACK)ch.getProtocolStack().findProtocol(NAKACK.class); + Digest digest=nak.getDigest(); + System.out.println(ch.getName() + ": " + digest.toStringSorted()); + } + } + + static void waitForNumMessages(int num_msgs, long timeout, long interval, MyReceiver ... receivers) { + long target_time=System.currentTimeMillis() + timeout; + while(System.currentTimeMillis() < target_time) { + boolean all_received=true; + for(MyReceiver receiver: receivers) { + if(receiver.getNumMsgs() < num_msgs) { + all_received=false; + break; + } + } + if(all_received) + break; + Util.sleep(interval); + } + } + + static void checkMessages(int expected, MyReceiver ... receivers) { + for(MyReceiver receiver: receivers) + System.out.println(receiver.name + ": " + receiver.getNumMsgs()); + for(MyReceiver receiver: receivers) + assert receiver.getNumMsgs() == expected : "[" + receiver.name + "] expected " + expected + + " msgs, but received " + receiver.getNumMsgs(); + } + + + private static class MyChannel extends JChannel { + protected int id=0; + + public MyChannel() throws ChannelException { + super(); + } + + public MyChannel(File properties) throws ChannelException { + super(properties); + } + + public MyChannel(Element properties) throws ChannelException { + super(properties); + } + + public MyChannel(URL properties) throws ChannelException { + super(properties); + } + + public MyChannel(String properties) throws ChannelException { + super(properties); + } + + public MyChannel(ProtocolStackConfigurator configurator) throws ChannelException { + super(configurator); + } + + public MyChannel(JChannel ch) throws ChannelException { + super(ch); + } + + + public void setId(int id) { + this.id=id; + } + + protected void setAddress() { + Address old_addr=local_addr; + local_addr=new org.jgroups.util.UUID(id, id); + + if(old_addr != null) + down(new Event(Event.REMOVE_ADDRESS, old_addr)); + if(name == null || name.length() == 0) // generate a logical name if not set + name=Util.generateLocalName(); + if(name != null && name.length() > 0) + org.jgroups.util.UUID.add(local_addr, name); + + Event evt=new Event(Event.SET_LOCAL_ADDRESS, local_addr); + down(evt); + if(up_handler != null) + up_handler.up(evt); + } + } + + private static class MyReceiver extends ReceiverAdapter { + private final String name; + private AtomicInteger num_msgs=new AtomicInteger(0); + + public MyReceiver(String name) { + this.name=name; + } + + public int getNumMsgs() {return num_msgs.get();} + + public void clear() {num_msgs.set(0);} + + public void receive(Message msg) { + num_msgs.incrementAndGet(); + } + + public void viewAccepted(View new_view) { + System.out.println("[" + name + "] view=" + new_view); + } + } + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/protocols/S3_PINGTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/protocols/S3_PINGTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/protocols/S3_PINGTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/protocols/S3_PINGTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,132 @@ +package org.jgroups.protocols; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.jgroups.Global; +import org.jgroups.protocols.S3_PING.Utils; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@Test(groups={Global.STACK_INDEPENDENT}) +public class S3_PINGTest { + private S3_PING ping; + + @BeforeMethod + public void setUp() { + ping = new S3_PING(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testValidatePropertiesWithPreSignedPutSet() { + ping.pre_signed_put_url = "http://s3.amazonaws.com/test-bucket/node1"; + ping.validateProperties(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testValidatePropertiesWithPreSignedDeleteSet() { + ping.pre_signed_delete_url = "http://s3.amazonaws.com/test-bucket/node1"; + ping.validateProperties(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testValidatePropertiesWithBothPreSignedSetButNoBucket() { + ping.pre_signed_put_url = "http://s3.amazonaws.com/"; + ping.pre_signed_delete_url = "http://s3.amazonaws.com/"; + ping.validateProperties(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testValidatePropertiesWithBothPreSignedSetButNoFile() { + ping.pre_signed_put_url = "http://s3.amazonaws.com/test-bucket"; + ping.pre_signed_delete_url = "http://s3.amazonaws.com/test-bucket"; + ping.validateProperties(); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testValidatePropertiesWithBothPreSignedSetButTooManySubdirectories() { + ping.pre_signed_put_url = "http://s3.amazonaws.com/test-bucket/subdir/DemoCluster/node1"; + ping.pre_signed_delete_url = "http://s3.amazonaws.com/test-bucket/subdir/DemoCluster/node1"; + ping.validateProperties(); + } + + @Test + public void testValidatePropertiesWithBothPreSignedSetToValid() { + ping.pre_signed_put_url = "http://s3.amazonaws.com/test-bucket/node1"; + ping.pre_signed_delete_url = "http://s3.amazonaws.com/test-bucket/node1"; + ping.validateProperties(); + } + + @Test + public void testValidatePropertiesWithBothPreSignedSetToValidSubdirectory() { + ping.pre_signed_put_url = "http://s3.amazonaws.com/test-bucket/DemoCluster/node1"; + ping.pre_signed_delete_url = "http://s3.amazonaws.com/test-bucket/DemoCluster/node1"; + ping.validateProperties(); + } + + @Test + public void testUsingPreSignedUrlWhenNotSet() { + Assert.assertFalse(ping.usingPreSignedUrls()); + } + + @Test + public void testUsingPreSignedUrlWhenSet() { + ping.pre_signed_put_url = "http://s3.amazonaws.com/test-bucket/node1"; + Assert.assertTrue(ping.usingPreSignedUrls()); + } + + @Test + public void testGenerateQueryStringAuthenticationWithBasicGet() { + String expectedUrl = "http://s3.amazonaws.com/test-bucket/node1?AWSAccessKeyId=abcd&Expires=1234567890&Signature=Khyk4bU1A3vaed9woyp%2B5qepazQ%3D"; + String encodedUrl = + Utils.generateQueryStringAuthentication("abcd", "efgh", "get", + "test-bucket", "node1", + new HashMap(), new HashMap(), + 1234567890); + Assert.assertEquals(encodedUrl, expectedUrl); + } + + @Test + public void testGenerateQueryStringAuthenticationWithBasicPost() { + String expectedUrl = "http://s3.amazonaws.com/test-bucket/node1?AWSAccessKeyId=abcd&Expires=1234567890&Signature=%2BsCW1Fc20UUvIqPjeGXkyN960sk%3D"; + String encodedUrl = + Utils.generateQueryStringAuthentication("abcd", "efgh", "POST", + "test-bucket", "node1", + new HashMap(), new HashMap(), + 1234567890); + Assert.assertEquals(encodedUrl, expectedUrl); + } + + @Test + public void testGenerateQueryStringAuthenticationWithBasicPutAndHeaders() { + Map headers = new HashMap(); + headers.put("x-amz-acl", Arrays.asList("public-read")); + String expectedUrl = "http://s3.amazonaws.com/test-bucket/subdir/node1?AWSAccessKeyId=abcd&Expires=1234567890&Signature=GWu2Mm5MysW83YDgS2R0Jakthes%3D"; + String encodedUrl = + Utils.generateQueryStringAuthentication("abcd", "efgh", "put", + "test-bucket", "subdir/node1", + new HashMap(), headers, + 1234567890); + Assert.assertEquals(encodedUrl, expectedUrl); + } + + @Test + public void testGeneratePreSignedUrlForPut() { + String expectedUrl = "http://s3.amazonaws.com/test-bucket/subdir/node1?AWSAccessKeyId=abcd&Expires=1234567890&Signature=GWu2Mm5MysW83YDgS2R0Jakthes%3D"; + String preSignedUrl = S3_PING.generatePreSignedUrl("abcd", "efgh", "put", + "test-bucket", "subdir/node1", + 1234567890); + Assert.assertEquals(preSignedUrl, expectedUrl); + } + + @Test + public void testGeneratePreSignedUrlForDelete() { + String expectedUrl = "http://s3.amazonaws.com/test-bucket/subdir/node1?AWSAccessKeyId=abcd&Expires=1234567890&Signature=qbEMukqq0KIpZVjXaDi0VxepSVo%3D"; + String preSignedUrl = S3_PING.generatePreSignedUrl("abcd", "efgh", "delete", + "test-bucket", "subdir/node1", + 1234567890); + Assert.assertEquals(preSignedUrl, expectedUrl); + } +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/protocols/STATE_TRANSFER_Test.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/protocols/STATE_TRANSFER_Test.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/protocols/STATE_TRANSFER_Test.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/protocols/STATE_TRANSFER_Test.java 2011-10-18 11:22:35.000000000 +0000 @@ -17,7 +17,6 @@ * after the setState will be validated to ensure the total ordering of msg delivery.

            * This should cover the fix introduced by rev. 1.12 * @author Wenbo Zhu - * @version $Id: STATE_TRANSFER_Test.java,v 1.18 2008/08/08 17:07:19 vlada Exp $ */ @Test(groups={Global.STACK_DEPENDENT,"known-failures"}) public class STATE_TRANSFER_Test extends ChannelTestBase { @@ -39,7 +38,7 @@ coord=null; } - class Coordinator implements ChannelListener { + class Coordinator extends ChannelListenerAdapter { private JChannel channel=null; private int cnt=0; // the state @@ -55,25 +54,10 @@ protected Coordinator() throws Exception { channel=createChannel(true); channel.setOpt(Channel.LOCAL, Boolean.FALSE); - channel.setOpt(Channel.AUTO_RECONNECT, Boolean.TRUE); channel.addChannelListener(this); channel.connect(GROUP_NAME); } - public void channelConnected(Channel channel) { - } - - public void channelDisconnected(Channel channel) { - } - - public void channelClosed(Channel channel) { - } - - public void channelShunned() { - } - - public void channelReconnected(Address addr) { // n/a. now - } public void recvLoop() throws Exception { Thread task=new Thread(new Runnable() { @@ -82,10 +66,6 @@ while(!closed) { try { tmp=channel.receive(0); - if(tmp instanceof ExitEvent) { - // System.err.println("-- received EXIT, waiting for ChannelReconnected callback"); - break; - } if(tmp instanceof GetStateEvent) { synchronized(Coordinator.this) { // System.err.println("-- GetStateEvent, cnt=" + cnt); @@ -162,9 +142,6 @@ for(;counter < timeout;SECONDS.sleep(1),counter++) { try { tmp=channel.receive(0); - if(tmp instanceof ExitEvent) { - break; - } if(tmp instanceof SetStateEvent) { cnt=((Integer)Util.objectFromByteBuffer(((SetStateEvent)tmp).getArg())).intValue(); // System.err.println("-- SetStateEvent, cnt=" + cnt); diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/protocols/UNICAST_ContentionTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/protocols/UNICAST_ContentionTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/protocols/UNICAST_ContentionTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/protocols/UNICAST_ContentionTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,196 @@ +package org.jgroups.protocols; + + +import org.jgroups.*; +import org.jgroups.stack.Protocol; +import org.jgroups.util.Util; +import org.testng.annotations.Test; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.DataProvider; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Tests for contention on UNICAST, measured by the number of retransmissions in UNICAST + * @author Bela Ban + */ +@Test(groups=Global.STACK_INDEPENDENT, sequential=true) +public class UNICAST_ContentionTest { + JChannel c1, c2; + static final String unicast_props="SHARED_LOOPBACK(thread_pool.queue_max_size=5000;" + + "thread_pool.rejection_policy=discard;thread_pool.min_threads=20;thread_pool.max_threads=20;" + + "oob_thread_pool.rejection_policy=discard;enable_bundling=true)"+ + ":UNICAST(timeout=300,600,1200)"; + static final String unicast2_props=unicast_props.replace("UNICAST", "UNICAST2"); + static final int NUM_THREADS=200; + static final int NUM_MSGS=100; + static final int SIZE=1000; // default size of a message in bytes + + @AfterMethod + protected void tearDown() throws Exception { + Util.close(c2, c1); + } + + @Test(dataProvider="provider") + public void testSimpleMessageReception(String props) throws Exception { + c1=new JChannel(props); + c2=new JChannel(props); + MyReceiver r1=new MyReceiver("c1"), r2=new MyReceiver("c2"); + c1.setReceiver(r1); + c2.setReceiver(r2); + c1.connect("testSimpleMessageReception"); + c2.connect("testSimpleMessageReception"); + + int NUM=100; + Address c1_addr=c1.getLocalAddress(), c2_addr=c2.getLocalAddress(); + for(int i=1; i <= NUM; i++) { + c1.send(c1_addr, null, "bla"); + c1.send(c2_addr, null, "bla"); + c2.send(c2_addr, null, "bla"); + c2.send(c1_addr, null, "bla"); + } + + for(int i=0; i < 10; i++) { + if(r1.getNum() == NUM * 2 && r2.getNum() == NUM * 2) + break; + Util.sleep(500); + } + + System.out.println("c1 received " + r1.getNum() + " msgs, " + getNumberOfRetransmissions(c1) + " retransmissions"); + System.out.println("c2 received " + r2.getNum() + " msgs, " + getNumberOfRetransmissions(c2) + " retransmissions"); + + assert r1.getNum() == NUM * 2: "expected " + NUM *2 + ", but got " + r1.getNum(); + assert r2.getNum() == NUM * 2: "expected " + NUM *2 + ", but got " + r2.getNum(); + } + + + /** + * Multiple threads (NUM_THREADS) send messages (NUM_MSGS) + * @throws Exception + */ + @Test(dataProvider="provider") + public void testMessageReceptionUnderHighLoad(String props) throws Exception { + CountDownLatch latch=new CountDownLatch(1); + c1=new JChannel(props); + c2=new JChannel(props); + MyReceiver r1=new MyReceiver("c1"), r2=new MyReceiver("c2"); + c1.setReceiver(r1); + c2.setReceiver(r2); + c1.connect("testSimpleMessageReception"); + c2.connect("testSimpleMessageReception"); + + Address c1_addr=c1.getLocalAddress(), c2_addr=c2.getLocalAddress(); + MySender[] c1_senders=new MySender[NUM_THREADS]; + for(int i=0; i < c1_senders.length; i++) { + c1_senders[i]=new MySender(c1, c2_addr, latch); + c1_senders[i].start(); + } + MySender[] c2_senders=new MySender[NUM_THREADS]; + for(int i=0; i < c2_senders.length; i++) { + c2_senders[i]=new MySender(c2, c1_addr, latch); + c2_senders[i].start(); + } + + Util.sleep(500); + latch.countDown(); // starts all threads + + long NUM_EXPECTED_MSGS=NUM_THREADS * NUM_MSGS; + + for(int i=0; i < 100; i++) { + if(r1.getNum() == NUM_EXPECTED_MSGS && r2.getNum() == NUM_EXPECTED_MSGS) + break; + Util.sleep(500); + UNICAST2 unicast2=(UNICAST2)c1.getProtocolStack().findProtocol(UNICAST2.class); + if(unicast2 != null) + unicast2.sendStableMessages(); + unicast2=(UNICAST2)c2.getProtocolStack().findProtocol(UNICAST2.class); + if(unicast2 != null) + unicast2.sendStableMessages(); + } + + System.out.println("c1 received " + r1.getNum() + " msgs, " + getNumberOfRetransmissions(c1) + " retransmissions"); + System.out.println("c2 received " + r2.getNum() + " msgs, " + getNumberOfRetransmissions(c2) + " retransmissions"); + + assert r1.getNum() == NUM_EXPECTED_MSGS : "expected " + NUM_EXPECTED_MSGS + ", but got " + r1.getNum(); + assert r2.getNum() == NUM_EXPECTED_MSGS : "expected " + NUM_EXPECTED_MSGS + ", but got " + r2.getNum(); + } + + @DataProvider + public static Object[][] provider() { + return new Object[][] { + {unicast_props}, + {unicast2_props} + }; + } + + + private static long getNumberOfRetransmissions(JChannel ch) { + Protocol prot=ch.getProtocolStack().findProtocol(UNICAST.class, UNICAST2.class); + if(prot instanceof UNICAST) + return ((UNICAST)prot).getNumberOfRetransmissions(); + if(prot instanceof UNICAST2) + return ((UNICAST2)prot).getNumberOfRetransmissions(); + return -1; + } + + + + private static class MySender extends Thread { + private final JChannel ch; + private final Address dest; + private final CountDownLatch latch; + private final byte[] buf=new byte[SIZE]; + + public MySender(JChannel ch, Address dest, CountDownLatch latch) { + this.ch=ch; + this.dest=dest; + this.latch=latch; + } + + + public void run() { + try { + latch.await(); + } + catch(InterruptedException e) { + e.printStackTrace(); + } + for(int i=0; i < NUM_MSGS; i++) { + try { + Message msg=new Message(dest, null, buf); + ch.send(msg); + } + catch(Exception e) { + e.printStackTrace(); + } + } + } + } + + + + private static class MyReceiver extends ReceiverAdapter { + final String name; + final AtomicInteger num=new AtomicInteger(0); + static final long MOD=NUM_MSGS * NUM_THREADS / 10; + + public MyReceiver(String name) { + this.name=name; + } + + public void receive(Message msg) { + if(num.incrementAndGet() % MOD == 0) { + System.out.println("[" + name + "] received " + getNum() + " msgs"); + } + } + + public int getNum() { + return num.get(); + } + } + + + + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/AddDataTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/AddDataTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/AddDataTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/AddDataTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -14,12 +14,12 @@ import org.jgroups.ReceiverAdapter; import org.jgroups.stack.IpAddress; import org.jgroups.util.Util; +import org.jgroups.util.UUID; import org.testng.annotations.Test; /** * * @author Bela Ban - * @version $Id: AddDataTest.java,v 1.21 2008/08/08 17:07:11 vlada Exp $ */ @Test(groups={Global.STACK_DEPENDENT},sequential=false) public class AddDataTest extends ChannelTestBase { @@ -34,7 +34,7 @@ m.put("additional_data", new byte[] { 'b', 'e', 'l', 'a' }); c.down(new Event(Event.CONFIG, m)); c.connect("AddDataTest.testadditionalData()"); - IpAddress addr=(IpAddress)c.getLocalAddress(); + UUID addr=(UUID)c.getAddress(); System.out.println("address is " + addr); assert addr.getAdditionalData() != null; assert addr.getAdditionalData()[0] == 'b'; @@ -76,7 +76,7 @@ if(mcast) ch1.send(new Message(null, null, buf)); else { - Address dest=ch2.getLocalAddress(); + Address dest=ch2.getAddress(); ch1.send(new Message(dest, null, buf)); } @@ -84,7 +84,7 @@ List list=receiver.getMsgs(); assert !list.isEmpty(); Message msg=list.get(0); - IpAddress src=(IpAddress)msg.getSrc(); + UUID src=(UUID)msg.getSrc(); assert src != null; assert src.getAdditionalData() != null; assert src.getAdditionalData().length == 4; diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ChannelConcurrencyTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ChannelConcurrencyTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ChannelConcurrencyTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ChannelConcurrencyTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -10,7 +10,6 @@ import java.util.concurrent.TimeUnit; import org.jgroups.Channel; -import org.jgroups.Global; import org.jgroups.JChannel; import org.jgroups.Message; import org.jgroups.blocks.GroupRequest; @@ -28,7 +27,6 @@ /** * Tests concurrent startup * @author Brian Goose - * @version $Id: ChannelConcurrencyTest.java,v 1.17 2008/08/25 16:10:46 vlada Exp $ */ @Test(groups="broken",sequential=true) public class ChannelConcurrencyTest extends ChannelTestBase{ @@ -96,14 +94,14 @@ for(int i=0;i < channels.length;i++) { log.info("#" + (i + 1) + ": " - + channels[i].getLocalAddress() + + channels[i].getAddress() + ": " + channels[i].getView()); } } for(final JChannel channel:channels) { - assertEquals("View ok for channel " + channel.getLocalAddress(), count, channel.getView().size()); + assertEquals("View ok for channel " + channel.getAddress(), count, channel.getView().size()); } assertTrue("All channels were succefully connected",successConnecting); } @@ -185,7 +183,7 @@ GroupRequest.GET_ALL, 2500); for(Object o:rsp.getResults()) { - assertEquals("Wrong result received at " + c.getLocalAddress(), i, o); + assertEquals("Wrong result received at " + c.getAddress(), i, o); } } } diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ChannelTestBase.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ChannelTestBase.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ChannelTestBase.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ChannelTestBase.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,23 +1,31 @@ package org.jgroups.tests; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.jgroups.*; -import org.jgroups.blocks.RpcDispatcher; +import org.jgroups.logging.Log; +import org.jgroups.logging.LogFactory; import org.jgroups.protocols.BasicTCP; import org.jgroups.protocols.TCPPING; -import org.jgroups.protocols.UDP; import org.jgroups.protocols.TP; -import org.jgroups.protocols.pbcast.GMS; +import org.jgroups.protocols.UDP; +import org.jgroups.protocols.pbcast.FLUSH; import org.jgroups.stack.Protocol; import org.jgroups.stack.ProtocolStack; +import org.jgroups.stack.IpAddress; import org.jgroups.util.ResourceManager; import org.jgroups.util.Util; +import org.jgroups.util.StackType; import org.testng.AssertJUnit; -import org.testng.annotations.*; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Optional; +import org.testng.annotations.Parameters; +import org.testng.annotations.Test; import java.io.InputStream; import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.net.InetAddress; import java.util.*; import java.util.concurrent.Semaphore; @@ -29,58 +37,80 @@ * @author Bela Ban * @author Vladimir Blagojevic * @author Brian Stansberry - * @version $Id$ */ -@Test(groups="base", sequential=true) +@Test(groups = "base", sequential = true) public class ChannelTestBase { - protected String channel_conf="udp.xml"; - - protected boolean use_blocking=false; + protected String channel_conf = "udp.xml"; - private String bind_addr="127.0.0.1"; - - protected final Log log=LogFactory.getLog(this.getClass()); + protected boolean use_blocking = false; + protected boolean use_flush = false; + private String bind_addr = null; + protected final Log log = LogFactory.getLog(this.getClass()); @BeforeClass - @Parameters(value = { "channel.conf", "use_blocking" }) - protected void initializeBase(@Optional("udp.xml") String chconf, - @Optional("false") String use_blocking) throws Exception { - Test annotation = this.getClass().getAnnotation(Test.class); - // this should never ever happen! - if (annotation == null) - throw new Exception("Test is not marked with @Test annotation"); - - List groups = Arrays.asList(annotation.groups()); - boolean testRequiresFlush = groups.contains(Global.FLUSH); - - //don't change chconf - should be equal to optional parameter above - if (testRequiresFlush && chconf.equals("udp.xml")) { - this.channel_conf = "flush-udp.xml"; - this.use_blocking = true; - } else { - this.channel_conf = chconf; - this.use_blocking = Boolean.parseBoolean(use_blocking); - } - - bind_addr = Util.getBindAddress(null).getHostAddress(); - } - + @Parameters(value = { "channel.conf", "use_blocking" }) + protected void initializeBase(@Optional("udp.xml") String chconf, @Optional("false") String use_blocking) throws Exception { + Test annotation = this.getClass().getAnnotation(Test.class); + // this should never ever happen! + if (annotation == null) + throw new Exception("Test is not marked with @Test annotation"); + + StackType type=Util.getIpStackType(); + bind_addr=type == StackType.IPv6 ? "::1" : "127.0.0.1"; + + List groups = Arrays.asList(annotation.groups()); + boolean testRequiresFlush = groups.contains(Global.FLUSH); + + this.use_blocking = testRequiresFlush || Boolean.parseBoolean(use_blocking); + this.use_flush = testRequiresFlush; + this.channel_conf = chconf; + + boolean ignore_systemprops=Util.isBindAddressPropertyIgnored(); + bind_addr = Util.getProperty(new String[]{Global.BIND_ADDR, Global.BIND_ADDR_OLD}, null, "bind_addr", + ignore_systemprops, bind_addr); + // bind_addr = Util.getBindAddress(null).getHostAddress(); + } + + @BeforeMethod + protected static void startTestHeader(java.lang.reflect.Method m) { + System.out.println("\n================ Starting test " + m.getName() + + " ================\n"); + } + + @AfterClass(alwaysRun = true) + protected void nullifyInstanceFields() { + for (Class current = this.getClass(); current.getSuperclass() != null; current = current.getSuperclass()) { + Field[] fields = current.getDeclaredFields(); + for (Field f : fields) { + try { + if (!Modifier.isStatic(f.getModifiers()) && !f.getDeclaringClass().isPrimitive()) { + f.setAccessible(true); + f.set(this, null); + } + } catch (Exception e) { + } + } + } + } - - protected String getBindAddress(){ + protected String getBindAddress() { return bind_addr; } protected boolean useBlocking() { - return use_blocking; + return use_blocking; } protected void setUseBlocking(boolean flag) { - use_blocking=flag; + use_blocking = flag; + } + + protected boolean useFlush() { + return use_flush; } protected final static void assertTrue(boolean condition) { @@ -99,7 +129,6 @@ Util.assertFalse(message, condition); } - protected final static void assertEquals(String message, Object val1, Object val2) { Util.assertEquals(message, val1, val2); } @@ -112,35 +141,39 @@ Util.assertNotNull(message, val); } - protected final static void assertNotNull(Object val) { Util.assertNotNull(null, val); } - protected final static void assertNull(String message, Object val) { - Util.assertNull(message, val); + Util.assertNull(message, val); } - protected final static void assertNull(Object val) { Util.assertNotNull(null, val); } - - - /** - * Creates a channel and modifies the configuration such that no other channel will able to join this - * one even if they have the same cluster name (if unique = true). This is done by modifying mcast_addr and mcast_port with UDP, - * and by changing TCP.start_port, TCP.port_range and TCPPING.initial_hosts with TCP. Mainly used to - * run TestNG tests concurrently. Note that MuxChannels are not currently supported. - * @param num The number of channels we will create. Only important (for port_range) with TCP, ignored by UDP + * Creates a channel and modifies the configuration such that no other channel will able to join + * this one even if they have the same cluster name (if unique = true). This is done by + * modifying mcast_addr and mcast_port with UDP, and by changing TCP.start_port, TCP.port_range + * and TCPPING.initial_hosts with TCP. Mainly used to run TestNG tests concurrently. Note that + * MuxChannels are not currently supported. + * + * @param num + * The number of channels we will create. Only important (for port_range) with TCP, + * ignored by UDP * @return * @throws Exception */ protected JChannel createChannel(boolean unique, int num) throws Exception { - return (JChannel)new DefaultChannelTestFactory().createChannel(unique, num); + return (JChannel) new DefaultChannelTestFactory().createChannel(unique, num); + } + + protected JChannel createChannel(boolean unique, int num, String name) throws Exception { + JChannel ch=(JChannel)new DefaultChannelTestFactory().createChannel(unique, num); + ch.setName(name); + return ch; } protected JChannel createChannel() throws Exception { @@ -152,7 +185,13 @@ } protected JChannel createChannel(JChannel ch) throws Exception { - return (JChannel)new DefaultChannelTestFactory().createChannel(ch); + return (JChannel) new DefaultChannelTestFactory().createChannel(ch); + } + + protected JChannel createChannel(JChannel ch, String name) throws Exception { + JChannel retval=(JChannel) new DefaultChannelTestFactory().createChannel(ch); + retval.setName(name); + return retval; } protected static String getUniqueClusterName() { @@ -163,116 +202,80 @@ return ResourceManager.getUniqueClusterName(base_name); } - - - /** * Default channel factory used in junit tests */ protected class DefaultChannelTestFactory { - + public JChannel createChannel() throws Exception { - return createChannel(channel_conf, useBlocking()); + return createChannel(channel_conf); } public Channel createChannel(boolean unique, int num) throws Exception { - JChannel c=createChannel(channel_conf, useBlocking()); - if(unique) { + JChannel c = createChannel(channel_conf); + if(unique) makeUnique(c, num); - } - muteLocalAddress(c); return c; } public Channel createChannel(final JChannel ch) throws Exception { - Map channelOptions=new HashMap(); - boolean useBlocking=(Boolean)ch.getOpt(Channel.BLOCK); - channelOptions.put(Channel.BLOCK, useBlocking); - - log.info("Using configuration file " + channel_conf); - JChannel retval=new JChannel(ch); - for(Map.Entry entry : channelOptions.entrySet()) { - Integer key=entry.getKey(); - Object value=entry.getValue(); - retval.setOpt(key, value); - } + JChannel retval = new JChannel(ch); + retval.setOpt(Channel.BLOCK, ch.getOpt(Channel.BLOCK)); + if(useFlush()) + Util.addFlush(retval, new FLUSH()); return retval; } - private JChannel createChannel(String configFile, boolean useBlocking) throws Exception { - Map channelOptions=new HashMap(); - channelOptions.put(Channel.BLOCK, useBlocking); - - log.info("Using configuration file " + configFile); - JChannel ch=new JChannel(configFile); - for(Map.Entry entry : channelOptions.entrySet()) { - Integer key=entry.getKey(); - Object value=entry.getValue(); - ch.setOpt(key, value); - } + private JChannel createChannel(String configFile) throws Exception { + JChannel ch = new JChannel(configFile); + ch.setOpt(Channel.BLOCK, useBlocking()); + if(useFlush()) + Util.addFlush(ch, new FLUSH()); return ch; } - private void muteLocalAddress(Channel c) { - ProtocolStack stack=c.getProtocolStack(); - Protocol gms=stack.findProtocol(GMS.class); - if(gms != null) { - ((GMS)gms).setPrintLocalAddress(false); - } - } - protected void makeUnique(Channel channel, int num) throws Exception { - String str=Util.getProperty(new String[] { Global.UDP_MCAST_ADDR, - "jboss.partition.udpGroup" }, - null, - "mcast_addr", - false, - null); - if(str != null) - makeUnique(channel, num, str); - else - makeUnique(channel, num, null); - + String str = Util.getProperty(new String[]{ Global.UDP_MCAST_ADDR, "jboss.partition.udpGroup" }, + null, "mcast_addr", false, null); + makeUnique(channel, num, str); } - + protected void makeUnique(Channel channel, int num, String mcast_address) throws Exception { - ProtocolStack stack=channel.getProtocolStack(); - Protocol transport=stack.getTransport(); - if(transport instanceof UDP) { - short mcast_port=ResourceManager.getNextMulticastPort(InetAddress.getByName(bind_addr)); - ((UDP)transport).setMulticastPort(mcast_port); - if(mcast_address != null) { - ((UDP)transport).setMulticastAddress(mcast_address); - } - else { - String mcast_addr=ResourceManager.getNextMulticastAddress(); - ((UDP)transport).setMulticastAddress(mcast_addr); + ProtocolStack stack = channel.getProtocolStack(); + Protocol transport = stack.getTransport(); + + if (transport instanceof UDP) { + short mcast_port = ResourceManager.getNextMulticastPort(InetAddress.getByName(bind_addr)); + ((UDP) transport).setMulticastPort(mcast_port); + if (mcast_address != null) { + ((UDP) transport).setMulticastAddress(InetAddress.getByName(mcast_address)); + } else { + String mcast_addr = ResourceManager.getNextMulticastAddress(); + ((UDP) transport).setMulticastAddress(InetAddress.getByName(mcast_addr)); } - } - else if(transport instanceof BasicTCP) { - List ports=ResourceManager.getNextTcpPorts(InetAddress.getByName(bind_addr), num); - ((TP)transport).setBindPort(ports.get(0)); - ((TP)transport).setPortRange(num); + } else if (transport instanceof BasicTCP) { + List ports = ResourceManager.getNextTcpPorts(InetAddress.getByName(bind_addr), num); + ((TP) transport).setBindPort(ports.get(0)); + ((TP) transport).setPortRange(num); - Protocol ping=stack.findProtocol(TCPPING.class); - if(ping == null) + Protocol ping = stack.findProtocol(TCPPING.class); + if (ping == null) throw new IllegalStateException("TCP stack must consist of TCP:TCPPING - other config are not supported"); - List initial_hosts=new LinkedList(); - for(short port: ports) { + List initial_hosts = new LinkedList(); + for (short port : ports) { initial_hosts.add(bind_addr + "[" + port + "]"); } - String tmp=Util.printListWithDelimiter(initial_hosts, ","); - ((TCPPING)ping).setInitialHosts(tmp); - } - else { + String tmp = Util.printListWithDelimiter(initial_hosts, ","); + List init_hosts = Util.parseCommaDelimitedHosts(tmp, 0); + ((TCPPING) ping).setInitialHosts(init_hosts); + } else { throw new IllegalStateException("Only UDP and TCP are supported as transport protocols"); } } } - - + interface EventSequence { List getEvents(); @@ -282,40 +285,31 @@ /** * Base class for all aplications using channel */ - protected abstract class ChannelApplication implements EventSequence, Runnable, MemberRetrievable,ExtendedReceiver { - protected Channel channel; - protected Thread thread; + protected abstract class ChannelApplication extends ExtendedReceiverAdapter implements EventSequence, Runnable { + protected Channel channel; + protected Thread thread; protected Throwable exception; - protected String name; - protected RpcDispatcher dispatcher; protected List events; - - public ChannelApplication(String name, boolean useDispatcher) throws Exception { - this.name=name; - channel=createChannel(true,4); - events=Collections.synchronizedList(new LinkedList()); - if(useDispatcher) { - dispatcher=new RpcDispatcher(channel, this, this, this); - } - else { - channel.setReceiver(this); - } + + public ChannelApplication(String name) throws Exception { + channel = createChannel(true, 4); + init(name); } - - public ChannelApplication(JChannel copySource,String name, boolean useDispatcher) throws Exception { - this.name=name; - channel=createChannel(copySource); - events=Collections.synchronizedList(new LinkedList()); - if(useDispatcher) { - dispatcher=new RpcDispatcher(channel, this, this, this); - } - else { - channel.setReceiver(this); - } + + public ChannelApplication(JChannel copySource, String name) throws Exception { + channel = createChannel(copySource); + init(name); + } + + protected void init(String name) { + events = Collections.synchronizedList(new LinkedList()); + channel.setName(name); + channel.setReceiver(this); } /** * Method allowing implementation of specific test application level logic + * * @throws Exception */ protected abstract void useChannel() throws Exception; @@ -323,390 +317,215 @@ public void run() { try { useChannel(); - } - catch(Exception e) { - log.error(name + ": " + e.getLocalizedMessage(), e); - exception=e; // Save it for the test to check + } catch (Exception e) { + log.error(channel.getName() + ": " + e.getLocalizedMessage(), e); + exception = e; // Save it for the test to check } } public List
            getMembers() { - List
            result=null; - View v=channel.getView(); - if(v != null) { - result=v.getMembers(); + List
            result = null; + View v = channel.getView(); + if (v != null) { + result = v.getMembers(); } return result; } - public Address getLocalAddress() { - return channel.getLocalAddress(); + return channel.getAddress(); } public void start() { - thread=new Thread(this, getName()); + thread = new Thread(this, getName()); thread.start(); - Address a=getLocalAddress(); - boolean connected=a != null; - if(connected) { - log.info("Thread for channel " + a + "[" + getName() + "] started"); - } - else { - log.info("Thread for channel [" + getName() + "] started"); - } } - public Channel getChannel() { return channel; } public String getName() { - return name; + return channel != null ? channel.getName() : "n/a"; } public void cleanup() { - if(thread != null && thread.isAlive()) { + if (thread != null && thread.isAlive()) thread.interrupt(); - } - Address a=getLocalAddress(); - boolean connected=a != null; - if(connected) { - log.info("Closing channel " + a + "[" + getName() + "]"); - } - else { - log.info("Closing channel [" + getName() + "]"); - } try { channel.close(); - log.info("Closed channel " + a + "[" + getName() + "]"); - } - catch(Throwable t) { - log.warn("Got exception while closing channel " + a + "[" + getName() + "]"); + } catch (Throwable t) { + log.warn("Exception while closing channel " + getName(), t); } } - + public List getEvents() { return events; } - public void block() { events.add(new BlockEvent()); - log.debug("Channel " + getLocalAddress() + "[" + getName() + "] in blocking"); } public byte[] getState() { events.add(new GetStateEvent(null, null)); - log.debug("Channel getState " + getLocalAddress() + "[" + getName() + "] "); return null; } public void getState(OutputStream ostream) { events.add(new GetStateEvent(null, null)); - log.debug("Channel getState " + getLocalAddress() + "[" + getName() + "]"); } public byte[] getState(String state_id) { events.add(new GetStateEvent(null, state_id)); - log.debug("Channel getState " + getLocalAddress() + "[" + getName() + " state id =" + state_id); return null; } public void getState(String state_id, OutputStream ostream) { events.add(new GetStateEvent(null, state_id)); - log.debug("Channel getState " + getLocalAddress() + "[" + getName() + "] state id =" + state_id); - } - - public void receive(Message msg) { } public void setState(byte[] state) { events.add(new SetStateEvent(null, null)); - log.debug("Channel setState " + getLocalAddress() + "[" + getName() + "] "); } public void setState(InputStream istream) { events.add(new SetStateEvent(null, null)); - log.debug("Channel setState " + getLocalAddress() + "[" + getName() + "]"); } public void setState(String state_id, byte[] state) { events.add(new SetStateEvent(null, null)); - log.debug("Channel setState " + getLocalAddress() - + "[" - + getName() - + "] state id =" - + state_id - + ", state size is " - + state.length); } public void setState(String state_id, InputStream istream) { events.add(new SetStateEvent(null, null)); - log.debug("Channel setState " + getLocalAddress() + "[" + getName() + "] state id " + state_id); - } - - public void suspect(Address suspected_mbr) { - log.debug("Channel " + getLocalAddress() - + "[" - + getName() - + "] suspecting " - + suspected_mbr); } public void unblock() { events.add(new UnblockEvent()); - log.debug("Channel " + getLocalAddress() + "[" + getName() + "] unblocking"); } public void viewAccepted(View new_view) { events.add(new_view); - log.info("Channel " + getLocalAddress() - + "[" - + getName() - + "] accepted view " - + new_view); + log.info(getLocalAddress() + ": view=" + new_view); } } /** - * Channel with semaphore allows application to go through fine-grained - * synchronous step control. + * Channel with semaphore allows application to go through fine-grained synchronous step + * control. *

            - * PushChannelApplicationWithSemaphore application will not proceed to - * useChannel() until it acquires permit from semphore. After useChannel() - * completes the acquired permit will be released. Test driver should - * control how semaphore tickets are given and acquired. + * PushChannelApplicationWithSemaphore application will not proceed to useChannel() until it + * acquires permit from semphore. After useChannel() completes the acquired permit will be + * released. Test driver should control how semaphore tickets are given and acquired. */ protected abstract class PushChannelApplicationWithSemaphore extends ChannelApplication { protected Semaphore semaphore; - public PushChannelApplicationWithSemaphore(String name, - Semaphore semaphore, - boolean useDispatcher) throws Exception { - super(name, useDispatcher); - this.semaphore=semaphore; - } - - public PushChannelApplicationWithSemaphore(JChannel copySource, String name, - Semaphore semaphore, - boolean useDispatcher) throws Exception { - super(copySource,name, useDispatcher); - this.semaphore=semaphore; + public PushChannelApplicationWithSemaphore(String name, Semaphore semaphore) + throws Exception { + super(name); + this.semaphore = semaphore; } - protected PushChannelApplicationWithSemaphore(String name, Semaphore semaphore) throws Exception { - this(name, semaphore, false); + public PushChannelApplicationWithSemaphore(JChannel copySource, String name, + Semaphore semaphore) throws Exception { + super(copySource, name); + this.semaphore = semaphore; } - public void run() { - boolean acquired=false; + boolean acquired = false; try { - acquired=semaphore.tryAcquire(60000L, TimeUnit.MILLISECONDS); - if(!acquired) { - throw new Exception(name + " cannot acquire semaphore"); - } + acquired = semaphore.tryAcquire(60000L, TimeUnit.MILLISECONDS); + if (!acquired) + throw new Exception(channel.getAddress() + ": cannot acquire semaphore"); useChannel(); - } - catch(Exception e) { - log.error(name + ": " + e.getLocalizedMessage(), e); - exception=e; // Save it for the test to check - } - finally { - if(acquired) { + } catch (Exception e) { + exception = e; // Save it for the test to check + } finally { + if (acquired) { semaphore.release(); } } } } - - protected void checkEventStateTransferSequence(EventSequence receiver) { - List events=receiver.getEvents(); + protected static void checkEventStateTransferSequence(EventSequence receiver) { + List events = receiver.getEvents(); assertNotNull(events); - final String validSequence="([b][vgs]*[u])+"; - // translate the eventTrace to an eventString - try { - assertTrue("Invalid event sequence " + events, validateEventString(translateEventTrace(events), validSequence)); - } - catch(Exception e) { + final String validSequence = "([b][vgs]*[u])+"; + // translate the eventTrace to an eventString + try { + assertTrue("Invalid event sequence " + events, validateEventString( + translateEventTrace(events), validSequence)); + } catch (Exception e) { AssertJUnit.fail("Invalid event sequence " + events); - } - } - + } + } + /** - * Method for translating event traces into event strings, where each event - * in the trace is represented by a letter. + * Method for translating event traces into event strings, where each event in the trace is + * represented by a letter. */ protected static String translateEventTrace(List et) throws Exception { - StringBuffer eventString=new StringBuffer(); - for(Iterator it=et.iterator();it.hasNext();) { - Object obj=it.next(); - if(obj instanceof BlockEvent) + StringBuilder eventString = new StringBuilder(); + for (Iterator it = et.iterator(); it.hasNext();) { + Object obj = it.next(); + if (obj instanceof BlockEvent) eventString.append("b"); - else if(obj instanceof UnblockEvent) + else if (obj instanceof UnblockEvent) eventString.append("u"); - else if(obj instanceof SetStateEvent) + else if (obj instanceof SetStateEvent) eventString.append("s"); - else if(obj instanceof GetStateEvent) + else if (obj instanceof GetStateEvent) eventString.append("g"); - else if(obj instanceof View) + else if (obj instanceof View) eventString.append("v"); else throw new Exception("Unrecognized event type in event trace"); } String s = eventString.toString(); - //if it ends with block, strip it out because it will be regarded as error sequence - while(s.endsWith("b")){ - s = s.substring(0, s.length()-1); + // if it ends with block, strip it out because it will be regarded as error sequence + while (s.endsWith("b")) { + s = s.substring(0, s.length() - 1); } return s; } - + /** - * Method for validating event strings against event string specifications, - * where a specification is a regular expression involving event symbols. - * e.g. [b]([sgv])[u] + * Method for validating event strings against event string specifications, where a + * specification is a regular expression involving event symbols. e.g. [b]([sgv])[u] */ protected static boolean validateEventString(String eventString, String spec) { - Pattern pattern=null; - Matcher matcher=null; + Pattern pattern = null; + Matcher matcher = null; // set up the regular expression specification - pattern=Pattern.compile(spec); + pattern = Pattern.compile(spec); // set up the actual event string - matcher=pattern.matcher(eventString); + matcher = pattern.matcher(eventString); // check if the actual string satisfies the specification - if(matcher.find()) { + if (matcher.find()) { // a match has been found, but we need to check that the whole event string // matches, and not just a substring - if(!(matcher.start() == 0 && matcher.end() == eventString.length())) { + if (!(matcher.start() == 0 && matcher.end() == eventString.length())) { // match on full eventString not found - System.err.println("event string invalid (proper substring matched): event string = " + eventString - + ", specification = " - + spec - + "matcher.start() " - + matcher.start() - + " matcher.end() " - + matcher.end()); + System.err + .println("event string invalid (proper substring matched): event string = " + + eventString + + ", specification = " + + spec + + "matcher.start() " + + matcher.start() + + " matcher.end() " + matcher.end()); return false; } - } - else { + } else { return false; } return true; } - - protected interface MemberRetrievable { - public List
            getMembers(); - public Address getLocalAddress(); - } - - - - /** - * Checks each channel in the parameter array to see if it has the exact same view as other channels in an array. - */ - protected static boolean areViewsComplete(MemberRetrievable[] channels, int memberCount) { - for(int i=0; i < memberCount; i++) { - if(!isViewComplete(channels[i], memberCount)) { - return false; - } - } - - return true; - } - - /** - * Loops, continually calling - * {@link #areViewsComplete(org.jgroups.tests.ChannelTestBase.MemberRetrievable[], int)} - * until it either returns true or timeout ms have elapsed. - * @param channels channels which must all have consistent views - * @param timeout max number of ms to loop - * @throws RuntimeException if timeout ms have elapse without all channels - * having the same number of members. - */ - protected static void blockUntilViewsReceived(MemberRetrievable[] channels, long timeout) { - blockUntilViewsReceived(channels, channels.length, timeout); - } - - protected static void blockUntilViewsReceived(Collection channels, long timeout) { - blockUntilViewsReceived(channels, channels.size(), timeout); - } - - /** - * Loops, continually calling - * {@link #areViewsComplete(org.jgroups.tests.ChannelTestBase.MemberRetrievable[], int)} - * until it either returns true or timeout ms have elapsed. - * @param channels channels which must all have consistent views - * @param timeout max number of ms to loop - * @throws RuntimeException if timeout ms have elapse without all channels - * having the same number of members. - */ - protected static void blockUntilViewsReceived(MemberRetrievable[] channels, int count, long timeout) { - long failTime=System.currentTimeMillis() + timeout; - - while(System.currentTimeMillis() < failTime) { - Util.sleep(100); - if(areViewsComplete(channels, count)) { - return; - } - } - - StringBuilder sb=new StringBuilder(); - for(MemberRetrievable c : channels) { - sb.append(c.getLocalAddress() + ",view=" + c.getMembers() + "|"); - } - throw new RuntimeException("timed out before caches had complete views. Views are " + sb.toString()); - } - - protected static void blockUntilViewsReceived(Collection channels, int count, long timeout) { - long failTime=System.currentTimeMillis() + timeout; - - while(System.currentTimeMillis() < failTime) { - Util.sleep(100); - if(areViewsComplete((MemberRetrievable[])channels.toArray(new MemberRetrievable[channels.size()]), count)) { - return; - } - } - - throw new RuntimeException("timed out before caches had complete views"); - } - - protected static boolean isViewComplete(MemberRetrievable channel, int memberCount) { - - List members=channel.getMembers(); - if(members == null || memberCount > members.size()) { - return false; - } - else if(memberCount < members.size()) { - // This is an exceptional condition - StringBuilder sb=new StringBuilder("Channel at address "); - sb.append(channel.getLocalAddress()); - sb.append(" had "); - sb.append(members.size()); - sb.append(" members; expecting "); - sb.append(memberCount); - sb.append(". Members were ("); - for(int j=0; j < members.size(); j++) { - if(j > 0) { - sb.append(", "); - } - sb.append(members.get(j)); - } - sb.append(')'); - - throw new IllegalStateException(sb.toString()); - } - - return true; - } - - } diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ChannelTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ChannelTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ChannelTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ChannelTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -13,7 +13,6 @@ /** * Tests various methods in JChannel * @author Bela Ban - * @version $Id: ChannelTest.java,v 1.23 2008/10/13 14:20:37 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=false) public class ChannelTest extends ChannelTestBase { @@ -28,15 +27,15 @@ assert c1.isOpen(); assert c1.isConnected(); - assert c1.getLocalAddress() != null; + assert c1.getAddress() != null; assert c1.getView() != null; - assert c1.getView().getMembers().contains(c1.getLocalAddress()); + assert c1.getView().getMembers().contains(c1.getAddress()); c1.connect("testBasicOperations"); c1.disconnect(); assert c1.isConnected() == false; assert c1.isOpen(); - assert c1.getLocalAddress() == null; + assert c1.getAddress() == null; assert c1.getView() == null; assert c1.getClusterName() == null; @@ -54,7 +53,7 @@ assert c1.isConnected() == false; assert c1.isOpen() == false; - assert c1.getLocalAddress() == null; + assert c1.getAddress() == null; assert c1.getView() == null; assert c1.getClusterName() == null; @@ -69,18 +68,18 @@ assert c1.isOpen(); assert c1.isConnected(); - assert c1.getLocalAddress() != null; + assert c1.getAddress() != null; assert c1.getView() != null; - assert c1.getView().getMembers().contains(c1.getLocalAddress()); - assert c1.getView().getMembers().contains(c2.getLocalAddress()); + assert c1.getView().getMembers().contains(c1.getAddress()); + assert c1.getView().getMembers().contains(c2.getAddress()); assert c2.isOpen(); assert c2.isConnected(); - assert c2.getLocalAddress() != null; + assert c2.getAddress() != null; assert c2.getView() != null; - assert c2.getView().getMembers().contains(c2.getLocalAddress()); - assert c2.getView().getMembers().contains(c1.getLocalAddress()); + assert c2.getView().getMembers().contains(c2.getAddress()); + assert c2.getView().getMembers().contains(c1.getAddress()); c2.close(); Util.sleep(1000); @@ -88,16 +87,16 @@ assert c2.isOpen() == false; assert c2.isConnected() == false; - assert c2.getLocalAddress() == null; + assert c2.getAddress() == null; assert c2.getView() == null; assert c1.isOpen(); assert c1.isConnected(); - assert c1.getLocalAddress() != null; + assert c1.getAddress() != null; assert c1.getView() != null; - assert c1.getView().getMembers().contains(c1.getLocalAddress()); - assert c1.getView().getMembers().contains(c2.getLocalAddress()) == false; + assert c1.getView().getMembers().contains(c1.getAddress()); + assert c1.getView().getMembers().contains(c2.getAddress()) == false; } finally { Util.close(c1,c2); @@ -133,7 +132,7 @@ try { ch1.connect("testIsConnectedOnFirstViewChange"); ch2.connect("testIsConnectedOnFirstViewChange"); - assertFalse(tmp.isConnected()); + assert tmp.isConnected(); } finally { Util.close(ch1,ch2); @@ -143,7 +142,7 @@ @Test - public void testNoViewIsReceivedAferDisconnect() throws Exception { + public void testNoViewIsReceivedAfterDisconnect() throws Exception { JChannel ch1 = createChannel(true,2); Channel ch2=createChannel(ch1); MyViewChecker ra = new MyViewChecker(ch2); @@ -163,7 +162,7 @@ } @Test - public void testNoViewIsReceivedAferClose() throws Exception { + public void testNoViewIsReceivedAfterClose() throws Exception { JChannel ch1 = createChannel(true,2); Channel ch2=createChannel(ch1); MyViewChecker ra = new MyViewChecker(ch2); @@ -330,7 +329,7 @@ } public void viewAccepted(View new_view) { - receivedViewWhenDisconnected = !new_view.containsMember(ch.getLocalAddress()); + receivedViewWhenDisconnected = !new_view.containsMember(ch.getAddress()); } } diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/CloseTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/CloseTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/CloseTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/CloseTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: CloseTest.java,v 1.23 2008/08/08 17:07:11 vlada Exp $ package org.jgroups.tests; @@ -9,6 +8,8 @@ import org.testng.annotations.Test; import java.util.Vector; +import java.util.List; +import java.util.ArrayList; /** @@ -80,85 +81,77 @@ public void testViewChangeReceptionOnChannelCloseByParticipant() throws Exception { Address a1, a2; Vector members; + MyReceiver r1=new MyReceiver(), r2=new MyReceiver(); + c1.set(createChannel(true)); + c1.get().setReceiver(r1); System.out.println("-- connecting c1"); - final String GROUP=getUniqueClusterName("CloseTest.testViewChangeReceptionOnChannelCloseByParticipant"); + final String GROUP="CloseTest.testViewChangeReceptionOnChannelCloseByParticipant"; c1.get().connect(GROUP); Util.sleep(500); // time to receive its own view - dumpMessages("c1", c1.get()); - a1=c1.get().getLocalAddress(); + System.out.println("c1: " + r1.getViews()); + a1=c1.get().getAddress(); c2.set(createChannel(c1.get())); + c2.get().setReceiver(r2); System.out.println("-- connecting c2"); + r1.clearViews(); c2.get().connect(GROUP); Util.sleep(500); // time to receive its own view - a2=c2.get().getLocalAddress(); - dumpMessages("c2", c2.get()); + a2=c2.get().getAddress(); + System.out.println("c2: " + r2.getViews()); System.out.println("-- closing c2"); c2.get().close(); - Object obj=c1.get().receive(100); - assertTrue(obj instanceof View); - View v=(View)obj; + Util.sleep(500); + View v=r1.getViews().get(0); members=v.getMembers(); System.out.println("-- first view of c1: " + v); Assert.assertEquals(2, members.size()); assertTrue(members.contains(a1)); assertTrue(members.contains(a2)); - obj=c1.get().receive(100); - assertTrue(obj instanceof View); - v=(View)obj; + v=r1.getViews().get(1); members=v.getMembers(); System.out.println("-- second view of c1: " + v); - Assert.assertEquals(1, members.size()); - assertTrue(members.contains(a1)); - assertFalse(members.contains(a2)); + assert 1 == members.size(); + assert members.contains(a1); + assert !members.contains(a2); } @Test public void testViewChangeReceptionOnChannelCloseByCoordinator() throws Exception { Address a1, a2; Vector members; - Object obj; - View v; + MyReceiver r1=new MyReceiver(), r2=new MyReceiver(); + final String GROUP=getUniqueClusterName("CloseTest.testViewChangeReceptionOnChannelCloseByCoordinator"); c1.set(createChannel(true)); + c1.get().setReceiver(r1); c1.get().connect(GROUP); Util.sleep(500); // time to receive its own view - dumpMessages("c1", c1.get()); - a1=c1.get().getLocalAddress(); + a1=c1.get().getAddress(); c2.set(createChannel(c1.get())); + c2.get().setReceiver(r2); c2.get().connect(GROUP); Util.sleep(500); // time to receive its own view - a2=c2.get().getLocalAddress(); - v=(View)c2.get().receive(1); + a2=c2.get().getAddress(); + View v=r2.getViews().get(0); members=v.getMembers(); - Assert.assertEquals(2, members.size()); - assertTrue(members.contains(a2)); + assert 2 == members.size(); + assert members.contains(a2); + r2.clearViews(); c1.get().close(); - Util.sleep(500); + Util.sleep(1000); - System.out.println("queue of c2 is " + c2.get().dumpQueue()); - assertTrue("found 0 messages in channel", c2.get().getNumMessages() > 0); - obj=c2.get().receive(0); - assertTrue(obj instanceof View); - v=(View)obj; + v=r2.getViews().get(0); members=v.getMembers(); - Assert.assertEquals(1, members.size()); - assertFalse(members.contains(a1)); - assertTrue(members.contains(a2)); - + assert 1 == members.size(); + assert !members.contains(a1); + assert members.contains(a2); assert c2.get().getNumMessages() == 0; } - private static void dumpMessages(String msg, Channel ch) throws Exception { - while(ch.getNumMessages() > 0) { - Object obj=ch.receive(0); - if(obj instanceof View) - System.out.println(msg + ": " + obj); - } - } @Test public void testConnectDisconnectConnectCloseSequence() throws Exception { @@ -238,24 +231,7 @@ } } - @Test - public void testCreationAndCloseLoop() throws Exception { - System.out.println("-- creating channel --"); - ch.set(createChannel(true)); - final String GROUP=getUniqueClusterName("CloseTest.testCreationAndCloseLoop"); - for(int i=1; i <= 5; i++) { - System.out.println("-- connecting channel (attempt #" + i + " ) --"); - ch.get().connect(GROUP); - System.out.println("-- closing channel --"); - ch.get().close(); - - System.out.println("-- reopening channel --"); - ch.get().open(); - new DefaultChannelTestFactory().makeUnique(ch.get(), 1); - } - ch.get().close(); - } - + @Test public void testMultipleConnectsAndDisconnects() throws Exception { @@ -331,4 +307,14 @@ } + private static class MyReceiver extends ReceiverAdapter { + final List views=new ArrayList(); + public void viewAccepted(View new_view) { + views.add(new_view); + System.out.println("new_view = " + new_view); + } + public List getViews() {return views;} + public void clearViews() {views.clear();} + } + } diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ConcurrentCloseTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ConcurrentCloseTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ConcurrentCloseTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ConcurrentCloseTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -9,7 +9,6 @@ /** * @author Bela Ban - * @version $Id: ConcurrentCloseTest.java,v 1.11 2008/08/08 17:07:12 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=true) public class ConcurrentCloseTest extends ChannelTestBase { @@ -61,7 +60,7 @@ public void run() { try { barrier.await(); - System.out.println("closing channel for " + channel.getLocalAddress()); + System.out.println("closing channel for " + channel.getAddress()); channel.close(); } catch(Exception e) { diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ConcurrentFlushTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ConcurrentFlushTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ConcurrentFlushTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ConcurrentFlushTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,348 @@ +package org.jgroups.tests; + +import org.jgroups.*; +import org.jgroups.util.Util; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.LinkedList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Tests concurrent and partial flushes + * @author Manik Surtani + * @author Bela Ban + */ +@Test(groups=Global.FLUSH, sequential=true) +public class ConcurrentFlushTest extends ChannelTestBase { + + JChannel c1, c2, c3; + + @AfterMethod + public void tearDown() throws Exception { + Util.close(c3, c2, c1); + } + + public boolean useBlocking() { + return true; + } + + + + /** + * Tests A.startFlush(), followed by another A.startFlush() + */ + @Test + public void testTwoStartFlushesOnSameMemberWithTotalFlush() throws Exception { + c1=createChannel(true, 3); + c1.connect("testTwoStartFlushes"); + c2=createChannel(c1); + c2.connect("testTwoStartFlushes"); + assertViewsReceived(c1, c2); + + boolean rc=startFlush(c1, true); + assert rc; + + rc=startFlush(c1, false); + assert rc; + + rc=startFlush(c1, 1, 500, false); + assert !rc; + stopFlush(c1); + + rc=startFlush(c1, true); + assert rc; + } + + /** + * Tests A.startFlush(), followed by another A.startFlush() + */ + public void testTwoStartFlushesOnDifferentMembersWithTotalFlush() throws Exception { + c1=createChannel(true, 3); + c1.connect("testTwoStartFlushesOnDifferentMembersWithTotalFlush"); + c2=createChannel(c1); + c2.connect("testTwoStartFlushesOnDifferentMembersWithTotalFlush"); + assertViewsReceived(c1, c2); + + boolean rc=startFlush(c1, false); + assert rc; + + rc=startFlush(c2, 1, 500, false); + assert !rc; + + stopFlush(c1); + + rc=startFlush(c2, false); + assert rc; + stopFlush(c2); + + rc=startFlush(c1, false); + assert rc; + stopFlush(c2); // c2 can actually stop a flush started by c1 + + rc=startFlush(c2, true); + assert rc; + } + + /** + * Tests 2 channels calling FLUSH simultaneously + */ + @Test + public void testConcurrentFlush() throws Exception { + c1=createChannel(true, 2); + c1.connect("testConcurrentFlush"); + c2=createChannel(c1); + c2.connect("testConcurrentFlush"); + + assertViewsReceived(c1, c2); + + final CountDownLatch startFlushLatch=new CountDownLatch(1); + final CountDownLatch stopFlushLatch=new CountDownLatch(1); + final CountDownLatch flushStartReceived=new CountDownLatch(2); + final CountDownLatch flushStopReceived=new CountDownLatch(2); + + Thread t1=new Thread() { + public void run() { + try { + startFlushLatch.await(); + boolean rc=Util.startFlush(c1); + System.out.println("t1: rc=" + rc); + } + catch(InterruptedException e) { + interrupt(); + } + + try { + stopFlushLatch.await(); + } + catch(InterruptedException e) { + interrupt(); + } + finally { + c1.stopFlush(); + } + } + }; + + Thread t2=new Thread() { + public void run() { + try { + startFlushLatch.await(); + boolean rc=Util.startFlush(c2); + System.out.println("t2: rc=" + rc); + } + catch(InterruptedException e) { + interrupt(); + } + + try { + stopFlushLatch.await(); + } + catch(InterruptedException e) { + interrupt(); + } + finally { + c2.stopFlush(); + } + } + }; + + Listener l1=new Listener("c1", c1, flushStartReceived, flushStopReceived); + Listener l2=new Listener("c2", c2, flushStartReceived, flushStopReceived); + t1.start(); + t2.start(); + + startFlushLatch.countDown(); + + assertTrue(flushStartReceived.await(60, TimeUnit.SECONDS)); + + // at this stage both channels should have started a flush + stopFlushLatch.countDown(); + + t1.join(); + t2.join(); + + assertTrue(flushStopReceived.await(60, TimeUnit.SECONDS)); + + + assert l1.blockReceived; + assert l1.unblockReceived; + assert l2.blockReceived; + assert l2.unblockReceived; + } + + /** + * Tests 2 channels calling partial FLUSHes and one calling FLUSH simultaneously + */ + @Test + public void testConcurrentFlushAndPartialFlush() throws Exception { + c1=createChannel(true, 3); + c1.connect("testConcurrentFlushAndPartialFlush"); + + c2=createChannel(c1); + c2.connect("testConcurrentFlushAndPartialFlush"); + + c3=createChannel(c1); + c3.connect("testConcurrentFlushAndPartialFlush"); + assertViewsReceived(c1, c2, c3); + + final CountDownLatch startFlushLatch=new CountDownLatch(1); + final CountDownLatch stopFlushLatch=new CountDownLatch(1); + + // 2 because either total or partial has to finish first + final CountDownLatch flushStartReceived=new CountDownLatch(2); + + // 5 because we have total and partial flush + final CountDownLatch flushStopReceived=new CountDownLatch(5); + + Thread t1=new Thread() { + public void run() { + try { + startFlushLatch.await(); + boolean rc=Util.startFlush(c1); + System.out.println("t1: rc=" + rc); + } + catch(InterruptedException e) { + interrupt(); + } + + try { + stopFlushLatch.await(); + } + catch(InterruptedException e) { + interrupt(); + } + finally { + c1.stopFlush(); + } + } + }; + + Thread t2=new Thread() { + public void run() { + try { + startFlushLatch.await(); + // partial, only between c2 and c3 + boolean rc=Util.startFlush(c2, (Arrays.asList(c2.getLocalAddress(), c3.getLocalAddress()))); + System.out.println("t2: partial flush rc=" + rc); + } + catch(InterruptedException e) { + interrupt(); + } + + try { + stopFlushLatch.await(); + } + catch(InterruptedException e) { + interrupt(); + } + finally { + c2.stopFlush(Arrays.asList(c2.getLocalAddress(), c3.getLocalAddress())); + } + } + }; + + Listener l1=new Listener("c1", c1, flushStartReceived, flushStopReceived); + Listener l2=new Listener("c2", c2, flushStartReceived, flushStopReceived); + Listener l3=new Listener("c3", c3, flushStartReceived, flushStopReceived); + + t1.start(); + t2.start(); + + startFlushLatch.countDown(); + + assertTrue(flushStartReceived.await(60, TimeUnit.SECONDS)); + + // at this stage both channels should have started a flush? + stopFlushLatch.countDown(); + + t1.join(); + t2.join(); + + assertTrue(flushStopReceived.await(60, TimeUnit.SECONDS)); + + assert l1.blockReceived; + assert l1.unblockReceived; + assert l2.blockReceived; + assert l2.unblockReceived; + assert l3.blockReceived; + assert l3.unblockReceived; + } + + private boolean startFlush(Channel ch, boolean automatic_resume) { + log.debug("starting flush on " + ch.getAddress() + " with automatic resume=" + automatic_resume); + boolean result=Util.startFlush(ch); + if(automatic_resume) { + ch.stopFlush(); + } + return result; + } + + private boolean startFlush(Channel ch, int num_attempts, long timeout, boolean automatic_resume) { + log.debug("starting flush on " + ch.getAddress() + " with automatic resume=" + automatic_resume); + boolean result=Util.startFlush(ch, num_attempts, 10, timeout); + if(automatic_resume) { + ch.stopFlush(); + } + return result; + } + + private void stopFlush(Channel ch) { + log.debug("calling stopFlush()"); + ch.stopFlush(); + } + + private static void assertViewsReceived(JChannel... channels) { + for(JChannel c : channels) + assertEquals(c.getView().getMembers().size(), channels.length); + } + + private static class Listener extends ExtendedReceiverAdapter implements EventSequence { + final String name; + boolean blockReceived, unblockReceived; + JChannel channel; + CountDownLatch flushStartReceived, flushStopReceived; + final List events=new LinkedList(); + + + Listener(String name, JChannel channel, CountDownLatch flushStartReceived, CountDownLatch flushStopReceived) { + this.name=name; + this.channel=channel; + this.flushStartReceived=flushStartReceived; + this.flushStopReceived=flushStopReceived; + this.channel.setReceiver(this); + } + + public void unblock() { + unblockReceived=true; + if(flushStopReceived != null) + flushStopReceived.countDown(); + events.add(new UnblockEvent()); + } + + public void block() { + blockReceived=true; + if(flushStartReceived != null) + flushStartReceived.countDown(); + events.add(new BlockEvent()); + } + + public List getEvents() { + return events; + } + + public void viewAccepted(View new_view) { + events.add(new_view); + } + + public String getName() { + return name; + } + + } + + +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ConcurrentStackTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ConcurrentStackTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ConcurrentStackTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ConcurrentStackTest.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,243 +0,0 @@ -package org.jgroups.tests; - -import org.jgroups.*; -import org.jgroups.protocols.TP; -import org.jgroups.util.Util; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import java.util.*; -import java.util.concurrent.CyclicBarrier; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Tests the TLS - * @author Bela Ban - * @version $Id: ConcurrentStackTest.java,v 1.11 2008/08/08 17:07:11 vlada Exp $ - */ -@Test(groups=Global.STACK_DEPENDENT,sequential=true) -public class ConcurrentStackTest extends ChannelTestBase { - JChannel ch1, ch2, ch3; - final static int NUM=25, EXPECTED=NUM * 3; - final static long SLEEPTIME=100; - CyclicBarrier barrier; - - - @BeforeMethod - void setUp() throws Exception { - barrier=new CyclicBarrier(4); - ch1=createChannel(true,3); - ch2=createChannel(ch1); - ch3=createChannel(ch1); - } - - @AfterMethod - protected void tearDown() throws Exception { - Util.close(ch3, ch2, ch1); - barrier.reset(); - } - - - - @Test - public void testSequentialDelivery() throws Exception { - doIt(false); - } - - @Test - public void testConcurrentDelivery() throws Exception { - doIt(true); - } - - - private void doIt(boolean concurrent) throws Exception { - long start, stop, diff; - setConcurrent(ch1, concurrent); - setConcurrent(ch2, concurrent); - setConcurrent(ch3, concurrent); - - MyReceiver r1=new MyReceiver("R1"), r2=new MyReceiver("R2"), r3=new MyReceiver("R3"); - ch1.setReceiver(r1); ch2.setReceiver(r2); ch3.setReceiver(r3); - - ch1.connect("ConcurrentStackTest"); - ch2.connect("ConcurrentStackTest"); - ch3.connect("ConcurrentStackTest"); - View v=ch3.getView(); - assert v.size() == 3 : "view is " + v; - - new Thread(new Sender(ch1)) {}.start(); - new Thread(new Sender(ch2)) {}.start(); - new Thread(new Sender(ch3)) {}.start(); - barrier.await(); // start senders - start=System.currentTimeMillis(); - - Exception ex=null; - - try { - barrier.await((long)(EXPECTED * SLEEPTIME * 1.3), TimeUnit.MILLISECONDS); // wait for all receivers - } - catch(java.util.concurrent.TimeoutException e) { - ex=e; - } - - - stop=System.currentTimeMillis(); - diff=stop - start; - - System.out.println("Total time: " + diff + " ms\n"); - - checkFIFO(r1); - checkFIFO(r2); - checkFIFO(r3); - /* - * TODO Vladimir Feb 15th 2007 - * Re-enable once acceptable bounds are known for all stacks - * checkTime(diff, threadless); - * - * */ - - if(ex != null) - throw ex; - } - - private void checkFIFO(MyReceiver r) { - List> msgs=r.getMessages(); - Map> map=new HashMap>(); - for(Pair p: msgs) { - Address sender=p.key; - List list=map.get(sender); - if(list == null) { - list=new LinkedList(); - map.put(sender, list); - } - list.add(p.val); - } - - boolean fifo=true; - List
            incorrect_receivers=new LinkedList
            (); - System.out.println("Checking FIFO for " + r.getName() + ":"); - for(Address addr: map.keySet()) { - List list=map.get(addr); - print(addr, list); - if(!verifyFIFO(list)) { - fifo=false; - incorrect_receivers.add(addr); - } - } - System.out.print("\n"); - - if(!fifo) - assert false : "The following receivers didn't receive all messages in FIFO order: " + incorrect_receivers; - } - - - private static boolean verifyFIFO(List list) { - List list2=new LinkedList(list); - Collections.sort(list2); - return list.equals(list2); - } - - private static void print(Address addr, List list) { - StringBuilder sb=new StringBuilder(); - sb.append(addr).append(": "); - for(Integer i: list) - sb.append(i).append(" "); - System.out.println(sb); - } - - - - private static void setConcurrent(JChannel ch1, boolean concurrent) { - TP transport=ch1.getProtocolStack().getTransport(); - transport.setUseConcurrentStack(concurrent); - ThreadPoolExecutor default_pool=(ThreadPoolExecutor)transport.getDefaultThreadPool(); - if(default_pool != null) { - default_pool.setCorePoolSize(1); - default_pool.setMaximumPoolSize(100); - } - transport.setThreadPoolQueueEnabled(false); - } - - - private class Sender implements Runnable { - Channel ch; - Address local_addr; - - public Sender(Channel ch) { - this.ch=ch; - local_addr=ch.getLocalAddress(); - } - - public void run() { - Message msg; - try { - barrier.await(); - } - catch(Throwable t) { - return; - } - - for(int i=1; i <= NUM; i++) { - msg=new Message(null, null, new Integer(i)); - try { - ch.send(msg); - } - catch(Exception e) { - e.printStackTrace(); - } - } - } - } - - - private class Pair { - K key; - V val; - - public Pair(K key, V val) { - this.key=key; - this.val=val; - } - - public String toString() { - return key + "::" + val; - } - } - - private class MyReceiver extends ReceiverAdapter { - String name; - final List> msgs=new LinkedList>(); - AtomicInteger count=new AtomicInteger(0); - - public MyReceiver(String name) { - this.name=name; - } - - public void receive(Message msg) { - Util.sleep(SLEEPTIME); - Pair pair=new Pair(msg.getSrc(), (Integer)msg.getObject()); - synchronized(msgs) { - msgs.add(pair); - } - if(count.incrementAndGet() >= EXPECTED) { - try { - barrier.await(); - } - catch(Exception e) { - e.printStackTrace(); - } - } - } - - public List> getMessages() {return msgs;} - - public String getName() { - return name; - } - } - - -} diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ConcurrentStartFlushTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ConcurrentStartFlushTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ConcurrentStartFlushTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ConcurrentStartFlushTest.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,168 +0,0 @@ -package org.jgroups.tests; - -import org.jgroups.*; -import org.jgroups.util.Util; -import org.testng.annotations.AfterClass; -import org.testng.annotations.Test; -import org.testng.annotations.BeforeClass; - -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.CyclicBarrier; - -/** - * Tests flush phases started concurrently by different members - * @author Bela Ban - * @version $Id: ConcurrentStartFlushTest.java,v 1.11 2008/06/25 23:11:52 vlada Exp $ - */ -@Test(groups={Global.FLUSH},sequential=true) -public class ConcurrentStartFlushTest extends ChannelTestBase { - private Receiver r1, r2, r3; - JChannel c1,c2,c3; - private static final long TIMEOUT=10000L; - - - @BeforeClass - void init() throws Exception { - c1 = createChannel(true,3); - r1=new Receiver("C1", c1); - - c2 = createChannel(c1); - r2=new Receiver("C2", c2); - - c3 = createChannel(c1); - r3=new Receiver("C3", c3); - c1.setReceiver(r1); - c2.setReceiver(r2); - c3.setReceiver(r3); - - final String GROUP=getUniqueClusterName("ConcurrentStartFlushTest"); - c1.connect(GROUP); - c2.connect(GROUP); - c3.connect(GROUP); - } - - - @AfterClass - protected void tearDown() throws Exception { - Util.close(c3,c2,c1); - } - - public void testSimpleFlush() throws Exception { - CyclicBarrier barrier=new CyclicBarrier(2); - Flusher flusher_one=new Flusher(c1, barrier); - - flusher_one.start(); - Util.sleep(1000); - - barrier.await(); - flusher_one.join(); - - //let async events propagate up - Util.sleep(500); - - checkEventStateTransferSequence(r1); - checkEventStateTransferSequence(r2); - checkEventStateTransferSequence(r3); - } - - - public void testConcurrentFlush() throws Exception { - CyclicBarrier barrier=new CyclicBarrier(3); - - Flusher flusher_one=new Flusher(c1, barrier); - Flusher flusher_three=new Flusher(c3, barrier); - - flusher_one.start(); - flusher_three.start(); - Util.sleep(1000); - - barrier.await(); - flusher_one.join(); - flusher_three.join(); - - //let async events propagate up - Util.sleep(500); - - checkEventStateTransferSequence(r1); - checkEventStateTransferSequence(r2); - checkEventStateTransferSequence(r3); - } - - - public void testFlushStartedByOneButCompletedByOther() throws Exception { - boolean rc=c1.startFlush(TIMEOUT, false); - assertTrue(rc); - Util.sleep(500); - - Util.sleep(1000); - c2.stopFlush(); - - rc=c2.startFlush(TIMEOUT, false); - assertTrue(rc); - - - Util.sleep(1000); - c1.stopFlush(); - - //let async events propagate up - Util.sleep(500); - checkEventStateTransferSequence(r1); - checkEventStateTransferSequence(r2); - checkEventStateTransferSequence(r3); - } - - private static class Flusher extends Thread { - final Channel channel; - final CyclicBarrier barrier; - - public Flusher(Channel channel, CyclicBarrier barrier) { - this.channel=channel; - this.barrier=barrier; - } - - public void run() { - try { - barrier.await(); - boolean rc=channel.startFlush(TIMEOUT, false); - Util.sleep(500); - channel.stopFlush(); - } - catch(Throwable t) { - t.printStackTrace(); - } - } - } - - private static class Receiver extends ExtendedReceiverAdapter implements EventSequence { - final String name; - final Channel channel; - final List events; - - public Receiver(String name, Channel channel) { - this.name=name; - this.channel=channel; - this.events=new LinkedList(); - } - - public List getEvents() { - return events; - } - - public void block() { - events.add(new BlockEvent()); - } - - public void unblock() { - events.add(new UnblockEvent()); - } - - public void viewAccepted(View new_view) { - events.add(new_view); - } - - public String getName() { - return name; - } - } -} diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ConcurrentStartupTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ConcurrentStartupTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ConcurrentStartupTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ConcurrentStartupTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,70 +1,36 @@ package org.jgroups.tests; -import org.testng.annotations.*; -import org.jgroups.Address; -import org.jgroups.JChannel; -import org.jgroups.Message; -import org.jgroups.View; -import org.jgroups.Global; +import org.jgroups.*; import org.jgroups.util.Util; -import org.testng.Assert; -import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import java.io.*; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; -import java.util.Map; -import java.util.TreeMap; +import java.util.Set; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** - * Tests concurrent startup with state transfer. - * + * Tests concurrent startup and message sending directly after joining. See doc/design/ConcurrentStartupTest.txt + * for details. This will only work 100% correctly with FLUSH support.
            + * [1] http://jira.jboss.com/jira/browse/JGRP-236 * @author bela - * @version $Id: ConcurrentStartupTest.java,v 1.48 2008/11/21 14:41:07 vlada Exp $ */ + @Test(groups={Global.FLUSH},sequential=true) public class ConcurrentStartupTest extends ChannelTestBase { - private AtomicInteger mod = new AtomicInteger(1); - - - @BeforeMethod - protected void setUp() throws Exception { - mod.set(1); - } + private AtomicInteger mod = new AtomicInteger(0); - public void testConcurrentStartupLargeState() { - concurrentStartupHelper(true, false); - } - - public void testConcurrentStartupSmallState() { - concurrentStartupHelper(false, true); - } - /** - * Tests concurrent startup and message sending directly after joining See - * doc/design/ConcurrentStartupTest.txt for details. This will only work - * 100% correctly once we have FLUSH support (JGroups 2.4) - * - * NOTE: This test is not guaranteed to pass at 100% rate until combined - * join and state transfer using one FLUSH phase is introduced (Jgroups - * 2.5)[1]. - * - * [1] http://jira.jboss.com/jira/browse/JGRP-236 - * - * - */ - protected void concurrentStartupHelper(boolean largeState, boolean useDispatcher) { - String[] names=null; + public void testConcurrentStartupWithState() { + final String[] names=new String[] { "A", "B", "C", "D" }; + final int count=names.length; - names=new String[] { "A", "B", "C", "D" }; - - int count=names.length; - - ConcurrentStartupChannel[] channels=new ConcurrentStartupChannel[count]; + final ConcurrentStartupChannel[] channels=new ConcurrentStartupChannel[count]; try { // Create a semaphore and take all its permits Semaphore semaphore=new Semaphore(count); @@ -72,184 +38,128 @@ // Create activation threads that will block on the semaphore for(int i=0;i < count;i++) { - if(largeState) { - if(i == 0) - channels[i]=new ConcurrentStartupChannelWithLargeState(semaphore, - names[i], - useDispatcher); - else - channels[i]=new ConcurrentStartupChannelWithLargeState((JChannel)channels[0].getChannel(), - semaphore, - names[i], - useDispatcher); - } - else { - if(i == 0) - channels[i]=new ConcurrentStartupChannel(names[i], semaphore, useDispatcher); - else - channels[i]=new ConcurrentStartupChannel((JChannel)channels[0].getChannel(), - names[i], - semaphore, - useDispatcher); - } - - // Release one ticket at a time to allow the thread to start - // working + if(i == 0) + channels[i]=new ConcurrentStartupChannel(names[i], semaphore); + else + channels[i]=new ConcurrentStartupChannel((JChannel)channels[0].getChannel(), names[i], semaphore); channels[i].start(); semaphore.release(1); - //sleep at least a second and max second and a half - Util.sleepRandom(1500); + if(i == 0) + Util.sleep(1500); // sleep after the first node to educe the chances of a merge } // Make sure everyone is in sync + Channel[] tmp=new Channel[channels.length]; + for(int i=0; i < channels.length; i++) + tmp[i]=channels[i].getChannel(); - blockUntilViewsReceived(channels, 60000); + Util.blockUntilViewsReceived(30000, 500, tmp); + System.out.println(">>>> all nodes have the same view <<<<"); - // Sleep to ensure the threads get all the semaphore tickets - Util.sleep(2000); - - // Reacquire the semaphore tickets; when we have them all - // we know the threads are done + // Re-acquire the semaphore tickets; when we have them all we know the threads are done boolean acquired=semaphore.tryAcquire(count, 20, TimeUnit.SECONDS); if(!acquired) { log.warn("Most likely a bug, analyse the stack below:"); log.warn(Util.dumpThreads()); } - // Sleep to ensure async message arrive - Util.sleep(3000); - - // do test verification - for(ConcurrentStartupChannel channel:channels) { - log.info(channel.getName() + "=" + channel.getList()); - } - for(ConcurrentStartupChannel channel:channels) { - log.info(channel.getName() + "=" + channel.getModifications()); - } - - for(ConcurrentStartupChannel channel:channels) { - Assert.assertEquals(channel.getList().size(), - count, - channel.getName() + " should have " + count + " elements"); - } + // Sleep to ensure async messages arrive + System.out.println("Waiting for all channels to have received the " + count + " messages:"); + long end_time=System.currentTimeMillis() + 10000L; + while(System.currentTimeMillis() < end_time) { + boolean terminate=true; + for(ConcurrentStartupChannel ch: channels) { + if(ch.getList().size() != count) { + terminate=false; + break; + } + } + if(terminate) + break; + else + Util.sleep(500); + } + + System.out.println("++++++++++++++++++++++++++++++++++++++++++++++++"); + for(ConcurrentStartupChannel channel:channels) + System.out.println(channel.getName() + ": state=" + channel.getList()); + System.out.println("++++++++++++++++++++++++++++++++++++++++++++++++"); + + //Vladimir Oct 1st 2009: if merged occurred state is corrupted. Do not verify state in that case. + for(ConcurrentStartupChannel ch: channels) { + Set
            list=ch.getList(); + if(!ch.merged) + assert list.size() == count : ": list is " + list + ", should have " + count + " elements"; + } + System.out.println(">>>> done, all messages received by all channels <<<<"); + for (ConcurrentStartupChannel channel : channels) + if(!channel.merged) + checkEventStateTransferSequence(channel); } catch(Exception ex) { - log.warn("Exception encountered during test", ex); - assert false:ex.getLocalizedMessage(); } finally { - for(ConcurrentStartupChannel channel:channels) { + for(ConcurrentStartupChannel channel: channels) + channel.getChannel().setReceiver(null); // silence the receivers so they don't logs views + for(ConcurrentStartupChannel channel: channels) channel.cleanup(); - Util.sleep(2000); // remove before 2.6 GA - } - - for(ConcurrentStartupChannel channel:channels) { - checkEventStateTransferSequence(channel); - } } } - protected int getMod() { + protected int getMod() { return mod.incrementAndGet(); - } - - protected class ConcurrentStartupChannelWithLargeState extends ConcurrentStartupChannel { - - //depends on retry_timeout parameter in FLUSH - private static final long TRANSFER_TIME = 1000; - public ConcurrentStartupChannelWithLargeState(Semaphore semaphore, - String name, - boolean useDispatcher) throws Exception{ - super(name, semaphore, useDispatcher); - } - - public ConcurrentStartupChannelWithLargeState(JChannel ch, Semaphore semaphore, - String name, - boolean useDispatcher) throws Exception{ - super(ch,name, semaphore, useDispatcher); - } - - public void setState(byte[] state) { - Util.sleep(TRANSFER_TIME); - super.setState(state); - } - - public byte[] getState() { - Util.sleep(TRANSFER_TIME); - return super.getState(); - } - - public void getState(OutputStream ostream) { - Util.sleep(TRANSFER_TIME); - super.getState(ostream); - } - - public void setState(InputStream istream) { - Util.sleep(TRANSFER_TIME); - super.setState(istream); - } } - protected class ConcurrentStartupChannel extends PushChannelApplicationWithSemaphore { - private final List
            l = new LinkedList
            (); + - private final Map mods = new TreeMap(); + protected class ConcurrentStartupChannel extends PushChannelApplicationWithSemaphore { + private final Set
            state=new HashSet
            (); + boolean merged = false; - public ConcurrentStartupChannel(String name,Semaphore semaphore,boolean useDispatcher) throws Exception{ - super(name, semaphore, useDispatcher); - } - - public ConcurrentStartupChannel(JChannel ch,String name,Semaphore semaphore,boolean useDispatcher) throws Exception{ - super(ch,name, semaphore, useDispatcher); + public ConcurrentStartupChannel(String name,Semaphore semaphore) throws Exception{ + super(name, semaphore); } - public void useChannel() throws Exception { - channel.connect("test", null, null, 25000); - channel.send(null, null, channel.getLocalAddress()); + public ConcurrentStartupChannel(JChannel ch,String name,Semaphore semaphore) throws Exception{ + super(ch,name, semaphore); } - List
            getList() { - return l; + public void useChannel() throws Exception { + channel.connect("test", null, null, 25000); // join and state transfer + channel.send(null, null, channel.getAddress()); } - Map getModifications() { - return mods; + Set
            getList() { + synchronized(state) { + return state; + } } public void receive(Message msg) { if(msg.getBuffer() == null) return; Address obj = (Address)msg.getObject(); - log.info("-- [#" + getName() + " (" + channel.getLocalAddress() + ")]: received " + obj); - synchronized(this){ - l.add(obj); - Integer key = new Integer(getMod()); - mods.put(key, obj); + log.info(channel.getAddress() + ": received " + obj); + synchronized(state) { + state.add(obj); } } public void viewAccepted(View new_view) { - super.viewAccepted(new_view); - synchronized(this){ - Integer key = new Integer(getMod()); - mods.put(key, new_view.getVid()); + super.viewAccepted(new_view); + if(new_view instanceof MergeView) { + merged = true; } } + @SuppressWarnings("unchecked") public void setState(byte[] state) { super.setState(state); try{ List
            tmp = (List) Util.objectFromByteBuffer(state); - synchronized(this){ - l.clear(); - l.addAll(tmp); - log.info("-- [#" + getName() - + " (" - + channel.getLocalAddress() - + ")]: state is " - + l); - Integer key = new Integer(getMod()); - mods.put(key, tmp); + synchronized(this.state) { + this.state.addAll(tmp); + log.info(channel.getAddress() + ": state is " + this.state); } }catch(Exception e){ e.printStackTrace(); @@ -259,8 +169,8 @@ public byte[] getState() { super.getState(); List
            tmp = null; - synchronized(this){ - tmp = new LinkedList
            (l); + synchronized(state) { + tmp = new LinkedList
            (state); try{ return Util.objectToByteBuffer(tmp); }catch(Exception e){ @@ -276,8 +186,8 @@ try{ oos = new ObjectOutputStream(ostream); List
            tmp = null; - synchronized(this){ - tmp = new LinkedList
            (l); + synchronized(state) { + tmp = new LinkedList
            (state); } oos.writeObject(tmp); oos.flush(); @@ -288,22 +198,17 @@ } } + @SuppressWarnings("unchecked") public void setState(InputStream istream) { super.setState(istream); ObjectInputStream ois = null; try{ ois = new ObjectInputStream(istream); List
            tmp = (List) ois.readObject(); - synchronized(this){ - l.clear(); - l.addAll(tmp); - log.info("-- [#" + getName() - + " (" - + channel.getLocalAddress() - + ")]: state is " - + l); - Integer key = new Integer(getMod()); - mods.put(key, tmp); + synchronized(state){ + // state.clear(); + state.addAll(tmp); + log.info(channel.getAddress() + ": state is " + state); } }catch(Exception e){ e.printStackTrace(); @@ -312,6 +217,4 @@ } } } - - -} +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ConcurrentStateTransferTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ConcurrentStateTransferTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ConcurrentStateTransferTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ConcurrentStateTransferTest.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,319 +0,0 @@ -package org.jgroups.tests; - - -import org.testng.annotations.*; -import org.jgroups.Address; -import org.jgroups.Channel; -import org.jgroups.Global; -import org.jgroups.JChannel; -import org.jgroups.Message; -import org.jgroups.View; -import org.jgroups.util.Util; -import org.testng.Assert; -import org.testng.annotations.BeforeMethod; - -import java.io.*; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Tests concurrent state transfer with flush. - * - * @author bela - * @version $Id: ConcurrentStateTransferTest.java,v 1.15 2008/11/21 15:47:18 vlada Exp $ - */ -@Test(groups={Global.FLUSH},sequential=true) -public class ConcurrentStateTransferTest extends ChannelTestBase { - - private final AtomicInteger mod = new AtomicInteger(1); - - @BeforeMethod - public void setUp() throws Exception { - mod.set(1); - } - - - @Test - public void testConcurrentLargeStateTransfer() { - concurrentStateTranferHelper(true, false); - } - - @Test - public void testConcurrentSmallStateTranfer() { - concurrentStateTranferHelper(false, true); - } - - /** - * Tests concurrent state transfer. This test should pass at 100% rate when - * [1] is solved. - * - * [1]http://jira.jboss.com/jira/browse/JGRP-332 - * - * - */ - protected void concurrentStateTranferHelper(boolean largeState, boolean useDispatcher) { - String[] names=new String[] { "A", "B", "C", "D" }; - - int count=names.length; - ConcurrentStateTransfer[] channels=new ConcurrentStateTransfer[count]; - - // Create a semaphore and take all its tickets - Semaphore semaphore=new Semaphore(count); - - try { - - semaphore.acquire(count); - // Create activation threads that will block on the semaphore - for(int i=0;i < count;i++) { - if(largeState) { - if(i == 0) - channels[i]=new ConcurrentLargeStateTransfer(names[i], - semaphore, - useDispatcher); - else - channels[i]=new ConcurrentLargeStateTransfer((JChannel)channels[0].getChannel(), - names[i], - semaphore, - useDispatcher); - - } - else { - if(i == 0) - channels[i]=new ConcurrentStateTransfer(names[i], semaphore, useDispatcher); - else - channels[i]=new ConcurrentStateTransfer((JChannel)channels[0].getChannel(), - names[i], - semaphore, - useDispatcher); - } - - // Start threads and let them join the channel - channels[i].start(); - //Util.sleep(2000); - } - - // Make sure everyone is in sync - - blockUntilViewsReceived(channels, 60000); - - Util.sleep(2000); - // Unleash hell ! - semaphore.release(count); - - // Sleep to ensure the threads get all the semaphore tickets - Util.sleep(2000); - - // Reacquire the semaphore tickets; when we have them all - // we know the threads are done - boolean acquired=semaphore.tryAcquire(count, 30, TimeUnit.SECONDS); - if(!acquired) { - log.warn("Most likely a bug, analyse the stack below:"); - log.warn(Util.dumpThreads()); - } - - // Sleep to ensure async message arrive - Util.sleep(2000); - - // do test verification - for(ConcurrentStateTransfer channel:channels) { - log.info(channel.getName() + "=" + channel.getList()); - } - for(ConcurrentStateTransfer channel:channels) { - log.info(channel.getName() + "=" + channel.getModifications()); - } - - for(ConcurrentStateTransfer channel:channels) { - Assert.assertEquals(channel.getList().size(), - count, - channel.getName() + " should have " + count + " elements"); - } - } - catch(Exception ex) { - log.warn("Exception encountered during test", ex); - assert false:ex.getLocalizedMessage(); - } - finally { - for(ConcurrentStateTransfer channel:channels) { - channel.cleanup(); - Util.sleep(2000); - } - for(ConcurrentStateTransfer channel:channels) { - checkEventStateTransferSequence(channel); - } - } - } - - protected int getMod() { - return mod.incrementAndGet(); - } - - protected class ConcurrentStateTransfer extends PushChannelApplicationWithSemaphore{ - private final List
            l = new LinkedList
            (); - - Channel ch; - - private final Map mods = new TreeMap(); - - - public ConcurrentStateTransfer(String name,Semaphore semaphore,boolean useDispatcher) throws Exception{ - super(name, semaphore, useDispatcher); - channel.connect("ConcurrentStateTransfer"); - } - - public ConcurrentStateTransfer(JChannel ch, String name,Semaphore semaphore,boolean useDispatcher) throws Exception{ - super(ch,name, semaphore, useDispatcher); - channel.connect("ConcurrentStateTransfer"); - } - - public void useChannel() throws Exception { - boolean success = channel.getState(null, 30000); - log.info("channel.getState at " + getName() - + getLocalAddress() - + " returned " - + success); - channel.send(null, null, channel.getLocalAddress()); - } - - List
            getList() { - return l; - } - - Map getModifications() { - return mods; - } - - public void receive(Message msg) { - if(msg.getBuffer() == null) - return; - Address obj = (Address)msg.getObject(); - log.info("-- [#" + getName() + " (" + channel.getLocalAddress() + ")]: received " + obj); - synchronized(this){ - l.add(obj); - Integer key = new Integer(getMod()); - mods.put(key, obj); - } - } - - public void viewAccepted(View new_view) { - super.viewAccepted(new_view); - synchronized(this){ - Integer key = new Integer(getMod()); - mods.put(key, new_view.getVid()); - } - } - - public void setState(byte[] state) { - super.setState(state); - try{ - List
            tmp = (List) Util.objectFromByteBuffer(state); - synchronized(this){ - l.clear(); - l.addAll(tmp); - log.info("-- [#" + getName() - + " (" - + channel.getLocalAddress() - + ")]: state is " - + l); - Integer key = new Integer(getMod()); - mods.put(key, tmp); - } - }catch(Exception e){ - e.printStackTrace(); - } - } - - public byte[] getState() { - super.getState(); - List
            tmp = null; - synchronized(this){ - tmp = new LinkedList
            (l); - try{ - return Util.objectToByteBuffer(tmp); - }catch(Exception e){ - e.printStackTrace(); - return null; - } - } - } - - public void getState(OutputStream ostream) { - super.getState(ostream); - ObjectOutputStream oos = null; - try{ - oos = new ObjectOutputStream(ostream); - List
            tmp = null; - synchronized(this){ - tmp = new LinkedList
            (l); - } - oos.writeObject(tmp); - oos.flush(); - }catch(IOException e){ - e.printStackTrace(); - }finally{ - Util.close(oos); - } - } - - public void setState(InputStream istream) { - super.setState(istream); - ObjectInputStream ois = null; - try{ - ois = new ObjectInputStream(istream); - List
            tmp = (List) ois.readObject(); - synchronized(this){ - l.clear(); - l.addAll(tmp); - log.info("-- [#" + getName() - + " (" - + channel.getLocalAddress() - + ")]: state is " - + l); - Integer key = new Integer(getMod()); - mods.put(key, tmp); - } - }catch(Exception e){ - e.printStackTrace(); - }finally{ - Util.close(ois); - } - } - } - - protected class ConcurrentLargeStateTransfer extends ConcurrentStateTransfer { - //depends on retry_timeout parameter in FLUSH - private static final long TRANSFER_TIME = 1000; - public ConcurrentLargeStateTransfer(String name,Semaphore semaphore,boolean useDispatcher) throws Exception{ - super(name, semaphore, useDispatcher); - } - - public ConcurrentLargeStateTransfer(JChannel channel,String name,Semaphore semaphore,boolean useDispatcher) throws Exception{ - super(channel,name, semaphore, useDispatcher); - } - - public void setState(byte[] state) { - Util.sleep(TRANSFER_TIME); - super.setState(state); - } - - public byte[] getState() { - Util.sleep(TRANSFER_TIME); - return super.getState(); - } - - public void getState(OutputStream ostream) { - Util.sleep(TRANSFER_TIME); - super.getState(ostream); - } - - public void setState(InputStream istream) { - Util.sleep(TRANSFER_TIME); - super.setState(istream); - } - } - -} diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ConnectStressTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ConnectStressTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ConnectStressTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ConnectStressTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,12 +2,10 @@ package org.jgroups.tests; -import org.jgroups.Channel; +import org.jgroups.Global; import org.jgroups.JChannel; import org.jgroups.View; -import org.jgroups.Global; import org.jgroups.protocols.MERGE2; -import org.jgroups.protocols.VIEW_SYNC; import org.jgroups.protocols.pbcast.GMS; import org.jgroups.protocols.pbcast.NAKACK; import org.jgroups.stack.ProtocolStack; @@ -20,16 +18,13 @@ /** * Creates 1 channel, then creates NUM channels, all try to join the same channel concurrently. * @author Bela Ban Nov 20 2003 - * @version $Id: ConnectStressTest.java,v 1.38 2008/11/12 13:32:56 belaban Exp $ */ -@Test(groups=Global.STACK_DEPENDENT, sequential=false) -public class ConnectStressTest { +@Test(groups={Global.STACK_INDEPENDENT}, sequential=true) +public class ConnectStressTest extends ChannelTestBase { static final int NUM=20; private final CyclicBarrier barrier=new CyclicBarrier(NUM+1); private final MyThread[] threads=new MyThread[NUM]; - static final String groupname="ConcurrentTestDemo"; - static final String props="udp.xml"; - + static final String groupname="ConnectStressTest"; @@ -39,36 +34,52 @@ public void testConcurrentJoinsAndLeaves() throws Exception { + JChannel first=null; + channel_conf="udp.xml"; + for(int i=0; i < threads.length; i++) { - threads[i]=new MyThread(i, barrier); + JChannel ch=null; + if(i == 0) { + ch=createChannel(true, NUM); + first=ch; + } + else + ch=createChannel(first); + ch.setName("C" + i); + changeProperties(ch); + threads[i]=new MyThread(ch, i+1, barrier); threads[i].start(); } barrier.await(); // causes all threads to call Channel.connect() + System.out.println("*** Starting the connect phase ***"); - // coordinator attempts to get complete view within 50 (5*10) seconds - int min=NUM, max=0; - for(int i=0; i < 20; i++) { - for(MyThread thread: threads) { - JChannel ch=thread.getChannel(); - View view=ch.getView(); - if(view != null) { - int size=view.size(); - min=Math.min(size, NUM); - max=Math.max(size, max); - } + long target_time=System.currentTimeMillis() + 60000L; + while(System.currentTimeMillis() < target_time) { + View view=threads[0].getChannel().getView(); + if(view != null) { + int size=view.size(); + System.out.println("channel[0].view has " + size + " members (expected: " + NUM + ")"); + if(size >= NUM) + break; } + Util.sleep(1000L); + } + + for(MyThread thread: threads) { + View view=thread.getChannel().getView(); + if(view != null) + System.out.println(thread.getName() + ": size=" + view.size() + ", view-id: " + view.getViewId()); + } - if(min >= NUM && max >= NUM) - break; - System.out.println("min=" + min + ", max=" + max); - Util.sleep(2000); + for(MyThread thread: threads) { + View view=thread.getChannel().getView(); + int size=view.size(); + assert size == NUM : "view doesn't have size of " + NUM + " (has " + size + "): " + view; } - System.out.println("reached " + NUM + " members: min=" + min + ", max=" + max); - assert min >= NUM && max >= NUM : "min=" + min + ", max=" + max + ", expected: " + NUM; - System.out.println("Starting the disconnect phase"); + System.out.println("*** Starting the disconnect phase ***"); for(int i=0; i < threads.length; i++) { MyThread thread=threads[i]; @@ -83,10 +94,11 @@ public static class MyThread extends Thread { private final CyclicBarrier barrier; - private JChannel ch=null; + private final JChannel ch; - public MyThread(int i, CyclicBarrier barrier) { + public MyThread(JChannel channel, int i, CyclicBarrier barrier) { super("thread #" + i); + this.ch=channel; this.barrier=barrier; } @@ -104,8 +116,6 @@ public void run() { try { - ch=new JChannel(props); - changeProperties(ch); barrier.await(); // wait for all threads to be running ch.connect(groupname); } @@ -115,7 +125,6 @@ } private static void changeProperties(JChannel ch) { - ch.setOpt(Channel.AUTO_RECONNECT, true); ProtocolStack stack=ch.getProtocolStack(); GMS gms=(GMS)stack.findProtocol("GMS"); if(gms != null) { @@ -128,9 +137,6 @@ merge.setMinInterval(2000); merge.setMaxInterval(5000); } - VIEW_SYNC sync=(VIEW_SYNC)stack.findProtocol(VIEW_SYNC.class); - if(sync != null) - sync.setAverageSendInterval(5000); NAKACK nakack=(NAKACK)stack.findProtocol(NAKACK.class); if(nakack != null) nakack.setLogDiscardMsgs(false); diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ConnectTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ConnectTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ConnectTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ConnectTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -7,13 +7,13 @@ import org.jgroups.stack.ProtocolStack; import org.jgroups.util.Promise; import org.jgroups.util.Util; +import org.jgroups.util.UUID; import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; /** * Runs through multiple channel connect and disconnects, without closing the channel. - * @version $Id: ConnectTest.java,v 1.20 2008/08/08 17:07:11 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=true) public class ConnectTest extends ChannelTestBase { @@ -48,7 +48,7 @@ channel.connect("ConnectTest.testgroup-2"); View view=channel.getView(); assert view.size() == 1; - assert view.containsMember(channel.getLocalAddress()); + assert view.containsMember(channel.getAddress()); } @@ -61,6 +61,7 @@ coordinator=createChannel(true); changeProps(coordinator); coordinator.connect("ConnectTest.testgroup-3"); + print(coordinator, "coordinator"); view=coordinator.getView(); System.out.println("-- view for coordinator: " + view); assert view.size() == 1; @@ -68,6 +69,7 @@ channel=createChannel(coordinator); changeProps(channel); channel.connect("ConnectTest.testgroup-4"); + print(channel, "channel"); view=channel.getView(); System.out.println("-- view for channel: " + view); assert view.size() == 1; @@ -75,12 +77,20 @@ channel.disconnect(); channel.connect("ConnectTest.testgroup-3"); + print(channel, "channel"); view=channel.getView(); System.out.println("-- view for channel: " + view); assert view.size() == 2; - assert view.containsMember(channel.getLocalAddress()); - assert view.containsMember(coordinator.getLocalAddress()); + assert view.containsMember(channel.getAddress()); + assert view.containsMember(coordinator.getAddress()); + } + + + static void print(JChannel ch, String msg) { + System.out.println(msg + ": name=" + ch.getName() + ", addr=" + ch.getAddress() + + ", UUID=" + ch.getAddressAsUUID() + "\nUUID cache:\n" + UUID.printCache() + + "\nLogical_addr_cache:\n" + ch.getProtocolStack().getTransport().printLogicalAddressCache()); } diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/Deadlock2Test.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/Deadlock2Test.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/Deadlock2Test.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/Deadlock2Test.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: Deadlock2Test.java,v 1.20 2008/10/10 14:53:30 belaban Exp $ package org.jgroups.tests; @@ -6,9 +5,11 @@ import org.jgroups.Address; import org.jgroups.Global; import org.jgroups.JChannel; +import org.jgroups.Message; import org.jgroups.blocks.GroupRequest; import org.jgroups.blocks.MethodCall; import org.jgroups.blocks.RpcDispatcher; +import org.jgroups.blocks.RequestOptions; import org.jgroups.util.RspList; import org.jgroups.util.Util; import org.testng.Assert; @@ -24,11 +25,10 @@ * @author John Giorgiadis * @author Ovidiu Feodorov * * - * @version $Revision: 1.20 $ + * @version $Revision: 1.23 $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=true) public class Deadlock2Test extends ChannelTestBase { - private static boolean DEADLOCK_DETECTION = true; private String name = "Deadlock2Test"; private JChannel c1, c2; @@ -50,10 +50,10 @@ public void testOneChannel() throws Exception { c1 = createChannel(true); ServerObject serverObject = new ServerObject("obj1"); - RpcDispatcher disp=new RpcDispatcher(c1, null, null, serverObject, DEADLOCK_DETECTION); + RpcDispatcher disp=new RpcDispatcher(c1, null, null, serverObject); serverObject.setRpcDispatcher(disp); c1.connect(name); - Address localAddress = c1.getLocalAddress(); + Address localAddress = c1.getAddress(); // call the nested group method on itself MethodCall call = new MethodCall("outerMethod", new Object[0], new Class[0]); @@ -91,16 +91,16 @@ c1 = createChannel(true); obj1 = new ServerObject("obj1"); - RpcDispatcher disp1=new RpcDispatcher(c1, null, null, obj1, DEADLOCK_DETECTION); + RpcDispatcher disp1=new RpcDispatcher(c1, null, null, obj1); obj1.setRpcDispatcher(disp1); c1.connect(name); c2 = createChannel(c1); obj2 = new ServerObject("obj2"); - RpcDispatcher disp2=new RpcDispatcher(c2, null, null, obj2, DEADLOCK_DETECTION); + RpcDispatcher disp2=new RpcDispatcher(c2, null, null, obj2); obj2.setRpcDispatcher(disp2); c2.connect(name); - Address localAddress2 = c2.getLocalAddress(); + Address localAddress2 = c2.getAddress(); // call a point-to-point method on Member 2 that triggers a nested distributed RPC MethodCall call = new MethodCall("outerMethod", new Object[0], new Class[0]); @@ -115,19 +115,19 @@ c1 = createChannel(true); obj1 = new ServerObject("obj1"); - RpcDispatcher disp1=new RpcDispatcher(c1, null, null, obj1, DEADLOCK_DETECTION); + RpcDispatcher disp1=new RpcDispatcher(c1, null, null, obj1); obj1.setRpcDispatcher(disp1); c1.connect(name); c2 = createChannel(c1); obj2 = new ServerObject("obj2"); - RpcDispatcher disp2=new RpcDispatcher(c2, null, null, obj2, DEADLOCK_DETECTION); + RpcDispatcher disp2=new RpcDispatcher(c2, null, null, obj2); obj2.setRpcDispatcher(disp2); c2.connect(name); Vector
            dests=new Vector
            (); - dests.add(c1.getLocalAddress()); - dests.add(c2.getLocalAddress()); + dests.add(c1.getAddress()); + dests.add(c2.getAddress()); // call a point-to-point method on Member 2 that triggers a nested distributed RPC MethodCall call = new MethodCall("outerMethod", new Object[0], new Class[0]); @@ -164,7 +164,9 @@ log("**** outerMethod() received, calling innerMethod() on all members"); MethodCall call = new MethodCall("innerMethod", new Object[0], new Class[0]); // RspList rspList = disp.callRemoteMethods(null, call, GroupRequest.GET_ALL, 5000); - RspList rspList = disp.callRemoteMethods(null, call, GroupRequest.GET_ALL, 0, false, true); + RequestOptions opts=new RequestOptions(GroupRequest.GET_ALL, 0, false, null, (byte)0); + opts.setFlags(Message.OOB); + RspList rspList = disp.callRemoteMethods(null, call, opts); Vector results = rspList.getResults(); log("results of calling innerMethod():\n" + rspList); StringBuilder sb=new StringBuilder("outerMethod["); diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/DiscardTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/DiscardTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/DiscardTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/DiscardTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -18,14 +18,13 @@ * by discarding 10% of all network-bound messages * * @author Bela Ban - * @version $Id: DiscardTest.java,v 1.21 2008/08/08 17:07:12 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=true) public class DiscardTest extends ChannelTestBase { JChannel ch1, ch2; static final long NUM_MSGS=10000; static final int MSG_SIZE=1000; - private static final String GROUP=getUniqueClusterName("DiscardTest"); + private static final String GROUP="DiscardTest"; final Promise ch1_all_received=new Promise(); final Promise ch2_all_received=new Promise(); @@ -72,9 +71,9 @@ } ch1.connect(GROUP); - ch1_addr=ch1.getLocalAddress(); + ch1_addr=ch1.getAddress(); ch2.connect(GROUP); - ch2_addr=ch2.getLocalAddress(); + ch2_addr=ch2.getAddress(); Util.sleep(2000); View v=ch2.getView(); diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/DuplicateTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/DuplicateTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/DuplicateTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/DuplicateTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -3,19 +3,20 @@ import org.jgroups.*; import org.jgroups.protocols.DUPL; +import org.jgroups.protocols.UNICAST2; import org.jgroups.protocols.pbcast.NAKACK; +import org.jgroups.protocols.pbcast.STABLE; import org.jgroups.stack.ProtocolStack; import org.jgroups.util.Tuple; import org.jgroups.util.Util; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; +import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import java.util.LinkedList; -import java.util.List; +import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; /** @@ -24,7 +25,6 @@ * unicast, (2) multicast, (3) regular and (4) OOB messages. The receiver(s) then check for the presence of duplicate * messages. * @author Bela Ban - * @version $Id: DuplicateTest.java,v 1.9 2008/08/08 17:07:12 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=true) public class DuplicateTest extends ChannelTestBase { @@ -33,16 +33,14 @@ private MyReceiver r1, r2, r3; - @BeforeClass - void classInit() throws Exception { - createChannels(true, true, (short)2, (short)2); - a1=c1.getLocalAddress(); - a2=c2.getLocalAddress(); - a3=c3.getLocalAddress(); - } - @BeforeMethod void init() throws Exception { + createChannels(true, true, (short)5, (short)5); + c1.setName("C1"); c2.setName("C2"); c3.setName("C3"); + a1=c1.getAddress(); + a2=c2.getAddress(); + a3=c3.getAddress(); + r1=new MyReceiver("C1"); r2=new MyReceiver("C2"); r3=new MyReceiver("C3"); @@ -51,33 +49,39 @@ c3.setReceiver(r3); } - @AfterClass + @AfterMethod void tearDown() throws Exception { + removeDUPL(c3, c2, c1); Util.close(c3, c2, c1); } public void testRegularUnicastsToSelf() throws Exception { - send(c1, c1.getLocalAddress(), false, 10); + send(c1, c1.getAddress(), false, 10); + sendStableMessages(c1, c2, c3); check(r1, 1, false, new Tuple(a1, 10)); } public void testOOBUnicastsToSelf() throws Exception { - send(c1, c1.getLocalAddress(), true, 10); + send(c1, c1.getAddress(), true, 10); + sendStableMessages(c1,c2,c3); check(r1, 1, true, new Tuple(a1, 10)); } public void testRegularUnicastsToOthers() throws Exception { - send(c1, c2.getLocalAddress(), false, 10); - send(c1, c3.getLocalAddress(), false, 10); + send(c1, c2.getAddress(), false, 10); + send(c1, c3.getAddress(), false, 10); + sendStableMessages(c1,c2,c3); check(r2, 1, false, new Tuple(a1, 10)); check(r3, 1, false, new Tuple(a1, 10)); } + @Test(invocationCount=10) public void testOOBUnicastsToOthers() throws Exception { - send(c1, c2.getLocalAddress(), true, 10); - send(c1, c3.getLocalAddress(), true, 10); + send(c1, c2.getAddress(), true, 10); + send(c1, c3.getAddress(), true, 10); + sendStableMessages(c1,c2,c3); check(r2, 1, true, new Tuple(a1, 10)); check(r3, 1, true, new Tuple(a1, 10)); } @@ -85,6 +89,7 @@ public void testRegularMulticastToAll() throws Exception { send(c1, null /** multicast */, false, 10); + sendStableMessages(c1,c2,c3); check(r1, 1, false, new Tuple(a1, 10)); check(r2, 1, false, new Tuple(a1, 10)); check(r3, 1, false, new Tuple(a1, 10)); @@ -93,6 +98,7 @@ public void testOOBMulticastToAll() throws Exception { send(c1, null /** multicast */, true, 10); + sendStableMessages(c1,c2,c3); check(r1, 1, true, new Tuple(a1, 10)); check(r2, 1, true, new Tuple(a1, 10)); check(r3, 1, true, new Tuple(a1, 10)); @@ -103,28 +109,32 @@ send(c1, null /** multicast */, false, 10); send(c2, null /** multicast */, false, 10); send(c3, null /** multicast */, false, 10); + sendStableMessages(c1,c2,c3); check(r1, 3, false, new Tuple(a1, 10), new Tuple(a2, 10), new Tuple(a3, 10)); check(r2, 3, false, new Tuple(a1, 10), new Tuple(a2, 10), new Tuple(a3, 10)); check(r3, 3, false, new Tuple(a1, 10), new Tuple(a2, 10), new Tuple(a3, 10)); } + @Test(invocationCount=5) public void testOOBMulticastToAll3Senders() throws Exception { send(c1, null /** multicast */, true, 10); send(c2, null /** multicast */, true, 10); send(c3, null /** multicast */, true, 10); + sendStableMessages(c1,c2,c3); check(r1, 3, true, new Tuple(a1, 10), new Tuple(a2, 10), new Tuple(a3, 10)); check(r2, 3, true, new Tuple(a1, 10), new Tuple(a2, 10), new Tuple(a3, 10)); check(r3, 3, true, new Tuple(a1, 10), new Tuple(a2, 10), new Tuple(a3, 10)); } public void testMixedMulticastsToAll3Members() throws Exception { - send(c1, null /** multicast */, false, true, 10); - send(c2, null /** multicast */, false, true, 10); - send(c3, null /** multicast */, false, true, 10); - check(r1, 3, true, new Tuple(a1, 10), new Tuple(a2, 10), new Tuple(a3, 10)); - check(r2, 3, true, new Tuple(a1, 10), new Tuple(a2, 10), new Tuple(a3, 10)); - check(r3, 3, true, new Tuple(a1, 10), new Tuple(a2, 10), new Tuple(a3, 10)); - } + send(c1, null /** multicast */, false, true, 10); + send(c2, null /** multicast */, false, true, 10); + send(c3, null /** multicast */, false, true, 10); + sendStableMessages(c1,c2,c3); + check(r1, 3, true, new Tuple(a1, 10), new Tuple(a2, 10), new Tuple(a3, 10)); + check(r2, 3, true, new Tuple(a1, 10), new Tuple(a2, 10), new Tuple(a3, 10)); + check(r3, 3, true, new Tuple(a1, 10), new Tuple(a2, 10), new Tuple(a3, 10)); + } private static void send(Channel sender_channel, Address dest, boolean oob, int num_msgs) throws Exception { @@ -145,24 +155,35 @@ sender_channel.send(msg); } - // sending is asynchronous, we need to give the receivers some time to receive all msgs. Retransmission - // can for example delay message delivery - Util.sleep(2000); } - /* private static void send(Channel sender_channel, Address dest, boolean oob, int num_msgs) throws Exception { - long seqno=1; - for(int i=0; i < num_msgs; i++) { - Message msg=new Message(dest, null, seqno++); - if(oob) - msg.setFlag(Message.OOB); - sender_channel.send(msg); - } - // sending is asynchronous, we need to give the receivers some time to receive all msgs. Retransmission - // can for example delay message delivery - Util.sleep(500); - }*/ + private static void sendStableMessages(JChannel ... channels) { + for(JChannel ch: channels) { + STABLE stable=(STABLE)ch.getProtocolStack().findProtocol(STABLE.class); + if(stable != null) + stable.runMessageGarbageCollection(); + } + } + + protected static void removeDUPL(JChannel ... channels) { + for(JChannel ch: channels) { + DUPL dupl=(DUPL)ch.getProtocolStack().findProtocol(DUPL.class); + if(dupl != null) { + dupl.setCopyMulticastMsgs(false); + dupl.setCopyUnicastMsgs(false); + } + } + } + + + private static void sendUnicastStableMessages(JChannel ... channels) { + for(JChannel ch: channels) { + UNICAST2 unicast=(UNICAST2)ch.getProtocolStack().findProtocol(UNICAST2.class); + if(unicast != null) + unicast.sendStableMessages(); + } + } private void createChannels(boolean copy_multicasts, boolean copy_unicasts, int num_outgoing_copies, int num_incoming_copies) throws Exception { @@ -182,32 +203,50 @@ } - private static void check(MyReceiver receiver, int expected_size, boolean oob, Tuple... vals) { - Map> msgs=receiver.getMsgs(); + private void check(MyReceiver receiver, int expected_size, boolean oob, Tuple... vals) { + Map> msgs=receiver.getMsgs(); + + for(int i=0; i < 10; i++) { + if(msgs.size() >= expected_size) + break; + Util.sleep(1000); + } assert msgs.size() == expected_size : "expected size=" + expected_size + ", msgs: " + msgs.keySet(); + + for(Tuple tuple: vals) { Address addr=tuple.getVal1(); - List list=msgs.get(addr); - System.out.println("[" + receiver.getName() + "]: " + addr + ": " + list); + Collection list=msgs.get(addr); assert list != null : "no list available for " + addr; - assert list.size() == tuple.getVal2() : "list's size is not " + tuple.getVal2() +", list: " + list; + + int expected_values=tuple.getVal2(); + for(int i=0; i < 20; i++) { + if(list.size() >= expected_values) + break; + Util.sleep(1000); + sendStableMessages(c1,c2,c3); + } + + System.out.println("[" + receiver.getName() + "]: " + addr + ": " + list); + assert list.size() == expected_values : addr + "'s list's size is not " + tuple.getVal2() + + ", list: " + list + " (size=" + list.size() + ")"; if(!oob) // if OOB messages, ordering is not guaranteed check(addr, list); else - checkPresence(addr, list); + checkPresence(list); } } - private static void check(Address addr, List list) { - long id=list.get(0); + private static void check(Address addr, Collection list) { + long id=list.iterator().next(); for(long val: list) { assert val == id : "[" + addr + "]: val=" + val + " (expected " + id + "): list is " + list; id++; } } - private static void checkPresence(Address addr, List list) { + private static void checkPresence(Collection list) { for(long l=1; l <= 10; l++) { assert list.contains(l) : l + " is not in the list " + list; } @@ -218,7 +257,7 @@ private static class MyReceiver extends ReceiverAdapter { final String name; - private final ConcurrentMap> msgs=new ConcurrentHashMap>(); + private final ConcurrentMap> msgs=new ConcurrentHashMap>(); public MyReceiver(String name) { this.name=name; @@ -228,17 +267,18 @@ return name; } - public ConcurrentMap> getMsgs() { + public Map> getMsgs() { return msgs; } public void receive(Message msg) { Address addr=msg.getSrc(); Long val=(Long)msg.getObject(); - List list=msgs.get(addr); + + Collection list=msgs.get(addr); if(list == null) { - list=new LinkedList(); - List tmp=msgs.putIfAbsent(addr, list); + list=new ConcurrentLinkedQueue(); + Collection tmp=msgs.putIfAbsent(addr, list); if(tmp != null) list=tmp; } @@ -253,7 +293,7 @@ public String toString() { StringBuilder sb=new StringBuilder(); sb.append("receiver " + name).append(":\n"); - for(Map.Entry> entry: msgs.entrySet()) { + for(Map.Entry> entry: msgs.entrySet()) { sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); } return sb.toString(); diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/EncryptMessageOrderTestCase.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/EncryptMessageOrderTestCase.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/EncryptMessageOrderTestCase.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/EncryptMessageOrderTestCase.java 2011-10-18 11:22:35.000000000 +0000 @@ -8,9 +8,7 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import java.io.IOException; -import java.io.ObjectInput; -import java.io.ObjectOutput; +import java.io.*; import java.util.Iterator; import java.util.Vector; @@ -24,7 +22,6 @@ *
          • -msg_num n - n is number of messages to send; *
          • -debug - pop-up protocol debugger; * - * $Id: EncryptMessageOrderTestCase.java,v 1.15 2008/08/08 17:07:12 vlada Exp $ */ @Test(groups={Global.STACK_INDEPENDENT,"broken"},sequential=true) public class EncryptMessageOrderTestCase { @@ -37,6 +34,8 @@ String groupName = "ENCRYPT_ORDER_TEST"; + static final short ID=100; + boolean orderCounterFailure = false; public static final String properties ="EncryptNoKeyStore.xml"; @@ -145,7 +144,7 @@ Long travelTime=new Long(System.currentTimeMillis() - ((Long)message).longValue()); try { - Assert.assertEquals(counter, ((EncryptOrderTestHeader)jgMessage.getHeader("EncryptOrderTest")).seqno); + Assert.assertEquals(counter, ((EncryptOrderTestHeader)jgMessage.getHeader(ID)).seqno); counter++; } catch (Exception e){ e.printStackTrace(); @@ -179,7 +178,7 @@ for(int i=0; i < MESSAGE_NUMBER; i++) { Long message=new Long(System.currentTimeMillis()); Message jgMessage=new Message(); - jgMessage.putHeader("EncryptOrderTest", new EncryptOrderTestHeader(i)); + jgMessage.putHeader(ID, new EncryptOrderTestHeader(i)); jgMessage.setObject(message); sender.send(jgMessage); @@ -282,39 +281,39 @@ } public static class EncryptOrderTestHeader extends Header{ - long seqno = -1; // either reg. NAK_ACK_MSG or first_seqno in retransmissions - private static final long serialVersionUID=-7995117434876250849L; + long seqno = -1; // either reg. NAK_ACK_MSG or first_seqno in retransmissions public EncryptOrderTestHeader() { - } + } + + public EncryptOrderTestHeader(long seqno){ + this.seqno = seqno; + } + + public int size(){ + return Global.LONG_SIZE; + } + + public void writeTo(DataOutputStream out) throws IOException { + out.writeLong(seqno); + } - public EncryptOrderTestHeader(long seqno){ - this.seqno = seqno; - } - - public int size(){ - return 512; - } - - public void writeExternal(ObjectOutput out) throws IOException{ - out.writeLong(seqno); - } - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{ - seqno = in.readLong(); - } - - public EncryptOrderTestHeader copy(){ - return new EncryptOrderTestHeader(seqno); - } - - public String toString(){ - StringBuilder ret = new StringBuilder(); - ret.append("[ENCRYPT_ORDER_TEST: seqno=" + seqno); - ret.append(']'); - - return ret.toString(); - } - } + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + seqno=in.readLong(); + } + + public EncryptOrderTestHeader copy(){ + return new EncryptOrderTestHeader(seqno); + } + + public String toString(){ + StringBuilder ret = new StringBuilder(); + ret.append("[ENCRYPT_ORDER_TEST: seqno=" + seqno); + ret.append(']'); + + return ret.toString(); + } + + } } diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/FifoOrderTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/FifoOrderTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/FifoOrderTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/FifoOrderTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,171 +1,222 @@ package org.jgroups.tests; -import org.testng.annotations.Test; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.AfterMethod; import org.jgroups.*; +import org.jgroups.protocols.TP; import org.jgroups.util.Util; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; -import java.nio.ByteBuffer; -import java.util.Arrays; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; -/** Tests FIFO order or messages +/** + * Tests the concurrent stack (TP) * @author Bela Ban - * @version $Id: FifoOrderTest.java,v 1.4 2008/10/13 14:21:12 vlada Exp $ */ -@Test(groups="broken",sequential=true) -public class FifoOrderTest extends ChannelTestBase { - private static final int NUM_MSGS=5000; - private static final int NUM_NODES=4; - private final JChannel[] channels=new JChannel[NUM_NODES]; +@Test(groups=Global.STACK_DEPENDENT,sequential=true) +public class FifoOrderTest extends ChannelTestBase { + JChannel ch1, ch2, ch3; + final static int NUM=25, EXPECTED=NUM * 3; + final static long SLEEPTIME=100; + CyclicBarrier barrier; - @BeforeMethod - void setup() throws Exception { - channels[0]=createChannel(true, 4); - channels[0].connect("FifoOrderTest-Group"); - for(int i=1; i < channels.length; i++) { - channels[i]=createChannel(channels[0]); - } - for(int i=1; i < channels.length; i++) { - channels[i].connect("FifoOrderTest-Group"); - } - for(int i=0; i < channels.length; i++) { - log.info("view[" + i + "]: " + channels[i].getView()); - } - assert channels[0].getView().size() == NUM_NODES; + void setUp() throws Exception { + barrier=new CyclicBarrier(4); + ch1=createChannel(true,3); + ch2=createChannel(ch1); + ch3=createChannel(ch1); } - @AfterMethod - void cleanup() { - for(int i=channels.length -1; i <=0 ; i--) { - channels[i].disconnect(); + protected void tearDown() throws Exception { + Util.close(ch3, ch2, ch1); + barrier.reset(); + } + + + @Test + public void testFifoDelivery() throws Exception { + long start, stop, diff; + modifyDefaultThreadPool(ch1); + modifyDefaultThreadPool(ch2); + modifyDefaultThreadPool(ch3); + + MyReceiver r1=new MyReceiver("R1"), r2=new MyReceiver("R2"), r3=new MyReceiver("R3"); + ch1.setReceiver(r1); ch2.setReceiver(r2); ch3.setReceiver(r3); + + ch1.connect("ConcurrentStackTest"); + ch2.connect("ConcurrentStackTest"); + ch3.connect("ConcurrentStackTest"); + View v=ch3.getView(); + assert v.size() == 3 : "view is " + v; + + new Thread(new Sender(ch1)) {}.start(); + new Thread(new Sender(ch2)) {}.start(); + new Thread(new Sender(ch3)) {}.start(); + barrier.await(); // start senders + start=System.currentTimeMillis(); + + Exception ex=null; + + try { + System.out.println("waiting for all messages to be received"); + barrier.await((long)(EXPECTED * SLEEPTIME * 1.5), TimeUnit.MILLISECONDS); // wait for all receivers } - for(int i=channels.length -1; i <=0 ; i--) { - channels[i].close(); + catch(java.util.concurrent.TimeoutException e) { + ex=e; } - } - - public void testFIFO() { - MyReceiver receiver=new MyReceiver(); - for(JChannel channel: channels) { - channel.setReceiver(receiver); - } + stop=System.currentTimeMillis(); + diff=stop - start; - Sender[] senders=new Sender[NUM_NODES]; - for(int i=0; i < channels.length; i++) { - senders[i]=new Sender(i, channels[i]); - senders[i].start(); - } + System.out.println("Total time: " + diff + " ms\n"); - for(Sender sender: senders) { - try { - sender.join(); - } - catch(InterruptedException e) { + checkFIFO(r1); + checkFIFO(r2); + checkFIFO(r3); + if(ex != null) + throw ex; + } + + private void checkFIFO(MyReceiver r) { + Map> map=r.getMessages(); + + boolean fifo=true; + List
            incorrect_receivers=new LinkedList
            (); + System.out.println("Checking FIFO for " + r.getName() + ":"); + for(Address addr: map.keySet()) { + List list=map.get(addr); + print(addr, list); + if(!verifyFIFO(list)) { + fifo=false; + incorrect_receivers.add(addr); } } + System.out.print("\n"); - while(true) { - Util.sleep(2000); - long[] seqnos=receiver.getSeqnos(); - if(receiver.isDone()) - break; - log.info("seqnos: " + Arrays.toString(seqnos)); - } + if(!fifo) + assert false : "The following receivers didn't receive all messages in FIFO order: " + incorrect_receivers; + } + + + private static boolean verifyFIFO(List list) { + List list2=new LinkedList(list); + Collections.sort(list2); + return list.equals(list2); + } + + private static void print(Address addr, List list) { + StringBuilder sb=new StringBuilder(); + sb.append(addr).append(": "); + for(Integer i: list) + sb.append(i).append(" "); + System.out.println(sb); + } - long[] seqnos=receiver.getSeqnos(); - log.info("seqnos: " + Arrays.toString(seqnos)); - for(long seqno: seqnos) - assert seqno == NUM_MSGS; - if(!receiver.isCorrect()) { - assert false : receiver.getErrorMessages(); + + private static void modifyDefaultThreadPool(JChannel ch1) { + TP transport=ch1.getProtocolStack().getTransport(); + ThreadPoolExecutor default_pool=(ThreadPoolExecutor)transport.getDefaultThreadPool(); + if(default_pool != null) { + default_pool.setCorePoolSize(1); + default_pool.setMaximumPoolSize(100); } + transport.setThreadPoolQueueEnabled(false); } - class Sender extends Thread { - final int index; - final JChannel ch; - static final int LENGTH=Global.LONG_SIZE + Global.BYTE_SIZE; + private class Sender implements Runnable { + final Channel ch; + final Address local_addr; - public Sender(int index, JChannel ch) { - this.index=index; + public Sender(Channel ch) { this.ch=ch; + local_addr=ch.getAddress(); } public void run() { - for(int i=1; i <= NUM_MSGS; i++) { - ByteBuffer buf=ByteBuffer.allocate(LENGTH); - buf.put((byte)index); buf.putLong(i); - buf.rewind(); - Message msg=new Message(null, null, buf.array()); - try { + Message msg; + try { + barrier.await(); + } + catch(Throwable t) { + return; + } + + for(int i=1; i <= NUM; i++) { + msg=new Message(null, null, new Integer(i)); + try { ch.send(msg); } catch(Exception e) { e.printStackTrace(); } } - log.info("Sender #" + index + " done (sent " + NUM_MSGS + " msgs"); } } - class MyReceiver extends ReceiverAdapter { - private final long[] seqnos=new long[NUM_NODES]; - private final StringBuilder sb=new StringBuilder(); - private static final int print=NUM_MSGS / 10; - private boolean correct=true; - MyReceiver() { - for(int i=0; i < seqnos.length; i++) - seqnos[i]=1; - } + private class Pair { + K key; + V val; - public long[] getSeqnos() { - return seqnos; + public Pair(K key, V val) { + this.key=key; + this.val=val; } - public boolean isCorrect() { - return correct; + public String toString() { + return key + "::" + val; } + } + + private class MyReceiver extends ReceiverAdapter { + String name; + final ConcurrentMap> msgs=new ConcurrentHashMap>(); + + AtomicInteger count=new AtomicInteger(0); - public String getErrorMessages() { - return sb.toString(); + public MyReceiver(String name) { + this.name=name; } public void receive(Message msg) { - ByteBuffer buf=ByteBuffer.wrap(msg.getBuffer()); - int index=buf.get(); - long new_seqno=buf.getLong(); + Util.sleep(SLEEPTIME); + Address sender=msg.getSrc(); + List list=msgs.get(sender); + if(list == null) { + list=new LinkedList(); + List tmp=msgs.putIfAbsent(sender, list); + if(tmp != null) + list=tmp; + } + Integer num=(Integer)msg.getObject(); + list.add(num); // no concurrent access: FIFO per sender ! (No need to synchronize on list) - long current_seqno=seqnos[index]; - if(current_seqno +1 == new_seqno) { - seqnos[index]++; - if(new_seqno % print == 0) { - log.info(sender + ": " + new_seqno); + if(count.incrementAndGet() >= EXPECTED) { + System.out.println("[" + name + "]: received all messages (" + count.get() + ")"); + try { + barrier.await(); } - } - else { - synchronized(sb) { - sb.append(sender + ": ").append(current_seqno).append("\n"); + catch(Exception e) { + e.printStackTrace(); } } } + public ConcurrentMap> getMessages() {return msgs;} - public boolean isDone() { - for(long seqno: seqnos) { - if(seqno < NUM_MSGS) - return false; - } - return true; + public String getName() { + return name; } } + + } diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/FlushCloseOpenTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/FlushCloseOpenTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/FlushCloseOpenTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/FlushCloseOpenTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,129 @@ +package org.jgroups.tests; + +import org.jgroups.*; +import org.jgroups.util.Util; +import org.testng.annotations.Test; + +import java.io.*; +import java.util.concurrent.atomic.AtomicLong; +import java.util.List; +import java.util.ArrayList; + +/** + * + * coord.stopFlush() and coord.close() called in sequence cause flush lockup + * See https://jira.jboss.org/jira/browse/JGRP-959 + * + * + * @author vladimir + * @since 2.8 + */ +@Test(groups = Global.FLUSH, sequential = true) +public class FlushCloseOpenTest extends ChannelTestBase { + + @Test + public void testAndLoop() throws Exception { + + for (int i = 1; i <= 4; i++) { + Channel channel = createChannel(true, 2); + ReceiverImpl receiver = new ReceiverImpl(); + channel.setReceiver(receiver); + channel.setName("A"); + channel.connect("testClust"); + + Channel channel2 = createChannel((JChannel) channel); + ReceiverImpl receiver2 = new ReceiverImpl(); + channel2.setReceiver(receiver2); + channel.setName("B"); + channel2.connect("testClust"); + + sendMessage(channel, "msg1"); + sendMessage(channel2, "msg2"); + + assert Util.startFlush(channel); + assertCount(receiver, 2, receiver2, 2); + channel.stopFlush(); + + channel.close(); + channel = createChannel((JChannel) channel2); + channel.setReceiver(receiver); + channel.setName("A"); + channel.connect("testClust"); + + sendMessage(channel2, "msg3"); + + assert Util.startFlush(channel2); + assertCount(receiver, 3, receiver2, 3); + channel.stopFlush(); + channel2.close(); + channel2 = createChannel((JChannel) channel); + channel2.setReceiver(receiver2); + channel.setName("B"); + channel2.connect("testClust"); + + sendMessage(channel2, "msg4"); + assert Util.startFlush(channel2); + assertCount(receiver, 4, receiver2, 4); + channel2.stopFlush(); + + channel.close(); + channel2.close(); + receiver.receiveCount.set(0); + receiver2.receiveCount.set(0); + System.out.println("***** Round " + i + " done *****"); + } + } + + private void sendMessage(Channel channel, Serializable obj) throws Exception { + if (!channel.isConnected()) { + log.warn("Channel disconnected in send, discarding msg"); + return; + } + Message msg = new Message(null, null, obj); + log.debug("Sending message: " + msg); + channel.send(msg); + log.debug("Sent message: " + msg); + } + + private void assertCount(ReceiverImpl srv1, long srv1Count, ReceiverImpl srv2, long srv2Count) + throws InterruptedException { + long start = System.currentTimeMillis(); + for (int i = 0; i < 1000; i++) { + if (srv1.receiveCount.get() == srv1Count && srv2.receiveCount.get() == srv2Count) { + break; + } + Thread.sleep(10L); + } + assert srv1Count == srv1.receiveCount.get() : "expected " + srv1Count + " but got " + + srv1.receiveCount; + assert srv2Count == srv2.receiveCount.get() : "expected " + srv2Count + " but got " + + srv2.receiveCount; + log.info("assert OK in " + (System.currentTimeMillis() - start) + "ms"); + } + + private class ReceiverImpl extends ReceiverAdapter { + final List msgs = new ArrayList(); + public final AtomicLong receiveCount = new AtomicLong(); + + public List getMsgs() { + return msgs; + } + + @Override + public void receive(Message msg) { + + try { + Object data = msg.getObject(); + msgs.add(data); + receiveCount.incrementAndGet(); + log.debug("Received msg: " + data); + } catch (Exception e) { + log.error("Receive failed", e); + } + } + + @Override + public void viewAccepted(View new_view) { + } + } +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/FlushTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/FlushTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/FlushTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/FlushTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,7 +1,9 @@ package org.jgroups.tests; - import org.jgroups.*; +import org.jgroups.protocols.FD; +import org.jgroups.protocols.FD_ALL; +import org.jgroups.protocols.pbcast.FLUSH; import org.jgroups.util.Util; import org.testng.annotations.Test; @@ -16,30 +18,27 @@ import java.util.concurrent.TimeUnit; /** - * Tests the FLUSH protocol, requires flush-udp.xml in ./conf to be present and - * configured to use FLUSH + * Tests the FLUSH protocol. Adds a FLUSH layer on top of the stack unless already present. Should + * work with any stack. * * @author Bela Ban - * @version $Id: FlushTest.java,v 1.76 2008/11/20 17:54:49 vlada Exp $ */ -@Test(groups=Global.FLUSH,sequential=true) +@Test(groups = Global.FLUSH, sequential = false) public class FlushTest extends ChannelTestBase { - + @Test public void testSingleChannel() throws Exception { Semaphore s = new Semaphore(1); - FlushTestReceiver receivers[] = new FlushTestReceiver[] { new FlushTestReceiver("c1", - s, - 0, - FlushTestReceiver.CONNECT_ONLY) }; + FlushTestReceiver receivers[] = new FlushTestReceiver[] { new FlushTestReceiver("c1", s, 0, + FlushTestReceiver.CONNECT_ONLY) }; receivers[0].start(); s.release(1); // Make sure everyone is in sync - blockUntilViewsReceived(receivers, 60000); - - // Sleep to ensure the threads get all the semaphore tickets - Util.sleep(1000); + Channel[] tmp = new Channel[receivers.length]; + for (int i = 0; i < receivers.length; i++) + tmp[i] = receivers[i].getChannel(); + Util.blockUntilViewsReceived(60000, 1000, tmp); // Reacquire the semaphore tickets; when we have them all // we know the threads are done @@ -52,51 +51,51 @@ /** * Tests issue #1 in http://jira.jboss.com/jira/browse/JGRP-335 - * @throws Exception + * + * @throws Exception */ @Test public void testJoinFollowedByUnicast() throws Exception { - JChannel c1=null; - JChannel c2=null; + JChannel c1 = null; + JChannel c2 = null; try { - c1=createChannel(true, 2); + c1 = createChannel(true, 2); c1.setReceiver(new SimpleReplier(c1, true)); c1.connect("testJoinFollowedByUnicast"); - Address target=c1.getLocalAddress(); - Message unicast_msg=new Message(target); + Address target = c1.getAddress(); + Message unicast_msg = new Message(target); - c2=createChannel(c1); + c2 = createChannel(c1); c2.setReceiver(new SimpleReplier(c2, false)); c2.connect("testJoinFollowedByUnicast"); // now send unicast, this might block as described in the case c2.send(unicast_msg); // if we don't get here this means we'd time out - } - finally { + } finally { Util.close(c2, c1); } } /** * Tests issue #2 in http://jira.jboss.com/jira/browse/JGRP-335 - * @throws Exception + * + * @throws Exception */ @Test public void testStateTransferFollowedByUnicast() throws Exception { - JChannel c1=null; - JChannel c2=null; + JChannel c1 = null; + JChannel c2 = null; try { - - c1=createChannel(true, 2); + c1 = createChannel(true, 2); c1.setReceiver(new SimpleReplier(c1, true)); c1.connect("testStateTransferFollowedByUnicast"); - Address target=c1.getLocalAddress(); - Message unicast_msg=new Message(target); + Address target = c1.getAddress(); + Message unicast_msg = new Message(target); - c2=createChannel(c1); + c2 = createChannel(c1); c2.setReceiver(new SimpleReplier(c2, false)); c2.connect("testStateTransferFollowedByUnicast"); @@ -104,193 +103,219 @@ c2.getState(null, 10000); // now send unicast, this might block as described in the case c2.send(unicast_msg); - } - finally { + } finally { Util.close(c2, c1); } } @Test + public void testSequentialFlushInvocation() throws Exception { + Channel channel = null, channel2 = null, channel3 = null; + try { + channel = createChannel(true, 3); + channel.setName("A"); + + channel2 = createChannel((JChannel) channel); + channel2.setName("B"); + + channel3 = createChannel((JChannel) channel); + channel3.setName("C"); + + channel.connect("x"); + channel2.connect("x"); + channel3.connect("x"); + + //we need to sleep here since coordinator (channel) + //might be unblocked before channel3.connect() returns + Util.sleep(500); + + for (int i = 0; i < 100; i++) { + System.out.print("flush #" + i + ": "); + long start = System.currentTimeMillis(); + boolean status = channel.startFlush(false); + channel.stopFlush(); + long diff = System.currentTimeMillis() - start; + System.out.println(status ? " OK (in " + diff + " ms)" : " FAIL"); + assert status; + } + } finally { + Util.close(channel, channel2, channel3); + } + } + + @Test public void testFlushWithCrashedFlushCoordinator() throws Exception { - JChannel c1 = null; - JChannel c2 = null; - JChannel c3 = null; - - try { - c1 = createChannel(true, 3); - c1.connect("testFlushWithCrashedFlushCoordinator"); - - c2 = createChannel(c1); - c2.connect("testFlushWithCrashedFlushCoordinator"); - - c3 = createChannel(c1); - c3.connect("testFlushWithCrashedFlushCoordinator"); - - // start flush - c2.startFlush(false); - - // and then kill the flush coordinator - ((JChannel) c2).shutdown(); - - Util.sleep(8000); - - // cluster should not hang and two remaining members should have a - // correct view - assertTrue("corret view size", c1.getView().size() == 2); - assertTrue("corret view size", c3.getView().size() == 2); - } finally { - Util.close(c3, c2, c1); - } - } - + JChannel c1 = null; + JChannel c2 = null; + JChannel c3 = null; + + try { + c1 = createChannel(true, 3, "C1"); changeProps(c1); + c1.connect("testFlushWithCrashedFlushCoordinator"); + + c2 = createChannel(c1, "C2"); changeProps(c2); + c2.connect("testFlushWithCrashedFlushCoordinator"); + + c3 = createChannel(c1, "C3"); changeProps(c3); + c3.connect("testFlushWithCrashedFlushCoordinator"); + + + + + System.out.println("shutting down flush coordinator C2"); + // send out START_FLUSH and then return + c2.down(new Event(Event.SUSPEND_BUT_FAIL)); + + // now shut down C2. This means, after failure detection kicks in and the new coordinator takes over + // (either C1 or C3), that the current flush started by C2 will be cancelled and a new flush (by C1 or C3) + // will be started + Util.shutdown(c2); + + c1.getProtocolStack().findProtocol(FLUSH.class).setLevel("trace"); + c3.getProtocolStack().findProtocol(FLUSH.class).setLevel("trace"); + + Util.blockUntilViewsReceived(10000, 500, c1, c3); + + // cluster should not hang and two remaining members should have a correct view + assertTrue("correct view size", c1.getView().size() == 2); + assertTrue("correct view size", c3.getView().size() == 2); + + c1.getProtocolStack().findProtocol(FLUSH.class).setLevel("warn"); + c3.getProtocolStack().findProtocol(FLUSH.class).setLevel("warn"); + + } finally { + Util.close(c3, c2, c1); + } + } + @Test - public void testFlushWithCrashedNonCoordinator() throws Exception { - JChannel c1 = null; - JChannel c2 = null; - JChannel c3 = null; - - try { - c1 = createChannel(true, 3); - c1.connect("testFlushWithCrashedFlushCoordinator"); - - c2 = createChannel(c1); - c2.connect("testFlushWithCrashedFlushCoordinator"); - - c3 = createChannel(c1); - c3.connect("testFlushWithCrashedFlushCoordinator"); - - // start flush - c2.startFlush(false); - - // and then kill the flush coordinator - ((JChannel) c3).shutdown(); - - c2.stopFlush(); - Util.sleep(8000); - - // cluster should not hang and two remaining members should have a - // correct view - assertTrue("corret view size", c1.getView().size() == 2); - assertTrue("corret view size", c2.getView().size() == 2); - } finally { - Util.close(c3, c2, c1); - } - } - - @Test - public void testFlushWithCrashedNonCoordinators() throws Exception { - JChannel c1 = null; - JChannel c2 = null; - JChannel c3 = null; - - try { - c1 = createChannel(true, 3); - c1.connect("testFlushWithCrashedFlushCoordinator"); - - c2 = createChannel(c1); - c2.connect("testFlushWithCrashedFlushCoordinator"); - - c3 = createChannel(c1); - c3.connect("testFlushWithCrashedFlushCoordinator"); - - // start flush - c2.startFlush(false); - - // and then kill members other than flush coordinator - ((JChannel) c3).shutdown(); - ((JChannel) c1).shutdown(); - - c2.stopFlush(); - Util.sleep(8000); - - // cluster should not hang and one remaining member should have a - // correct view - assertTrue("corret view size", c2.getView().size() == 1); - } finally { - Util.close(c3, c2, c1); - } - } - + public void testFlushWithCrashedParticipant() throws Exception { + JChannel c1 = null; + JChannel c2 = null; + JChannel c3 = null; + + try { + c1 = createChannel(true, 3, "C1"); changeProps(c1); + c1.connect("testFlushWithCrashedParticipant"); + + c2 = createChannel(c1, "C2"); changeProps(c2); + c2.connect("testFlushWithCrashedParticipant"); + + c3 = createChannel(c1, "C3"); changeProps(c3); + c3.connect("testFlushWithCrashedParticipant"); + + System.out.println("shutting down C3"); + Util.shutdown(c3); // kill a flush participant + + System.out.println("C2: starting flush"); + boolean rc=Util.startFlush(c2); + System.out.println("flush " + (rc? " was successful" : "failed")); + assert rc; + + System.out.println("stopping flush"); + c2.stopFlush(); + + System.out.println("waiting for view to contain C1 and C2"); + Util.blockUntilViewsReceived(10000, 500, c1, c2); + + // cluster should not hang and two remaining members should have a correct view + System.out.println("C1: view=" + c1.getView() + "\nC2: view=" + c2.getView()); + assertTrue("correct view size", c1.getView().size() == 2); + assertTrue("correct view size", c2.getView().size() == 2); + } finally { + Util.close(c3, c2, c1); + } + } + + @Test + public void testFlushWithCrashedParticipants() throws Exception { + JChannel c1 = null; + JChannel c2 = null; + JChannel c3 = null; + + try { + c1 = createChannel(true, 3, "C1"); changeProps(c1); + c1.connect("testFlushWithCrashedFlushCoordinator"); + + c2 = createChannel(c1, "C2"); changeProps(c2); + c2.connect("testFlushWithCrashedFlushCoordinator"); + + c3 = createChannel(c1, "C3"); changeProps(c3); + c3.connect("testFlushWithCrashedFlushCoordinator"); + + // and then kill members other than flush coordinator + Util.shutdown(c3); + Util.shutdown(c1); + + // start flush + Util.startFlush(c2); + + c2.stopFlush(); + Util.blockUntilViewsReceived(10000, 500, c2); + + // cluster should not hang and one remaining member should have a correct view + assertTrue("correct view size", c2.getView().size() == 1); + } finally { + Util.close(c3, c2, c1); + } + } + /** * Tests http://jira.jboss.com/jira/browse/JGRP-661 - * @throws Exception + * + * @throws Exception */ @Test public void testPartialFlush() throws Exception { - JChannel c1=null; - JChannel c2=null; + JChannel c1 = null; + JChannel c2 = null; try { - c1=createChannel(true, 2); + c1 = createChannel(true, 2); c1.setReceiver(new SimpleReplier(c1, true)); c1.connect("testPartialFlush"); - c2=createChannel(c1); + c2 = createChannel(c1); c2.setReceiver(new SimpleReplier(c2, false)); c2.connect("testPartialFlush"); - List
            members=new ArrayList
            (); + List
            members = new ArrayList
            (); members.add(c2.getLocalAddress()); - boolean flushedOk=c2.startFlush(members, false); + boolean flushedOk = Util.startFlush(c2, members); assertTrue("Partial flush worked", flushedOk); c2.stopFlush(members); - } - finally { + } finally { Util.close(c2, c1); } } - /** - * Tests emition of block/unblock/get|set state events in both mux and bare - * channel mode. In mux mode this test creates getFactoryCount() real - * channels and creates only one mux application on top of each channel. In - * bare channel mode 4 real channels are created. - * - */ + /** Tests the emition of block/unblock/get|set state events */ @Test - public void testBlockingNoStateTransfer() { - String[] names = {"A", "B", "C", "D"}; + public void testBlockingNoStateTransfer() throws Exception { + String[] names = { "A", "B", "C", "D" }; _testChannels(names, FlushTestReceiver.CONNECT_ONLY); } - - - - - /** - * Tests emition of block/unblock/set|get state events for both mux and bare - * channel depending on mux.on parameter. In mux mode there will be only one - * mux channel for each "real" channel created and the number of real - * channels created is getMuxFactoryCount(). - * - */ + /** Tests the emition of block/unblock/get|set state events */ @Test - public void testBlockingWithStateTransfer() { - String[] names = {"A", "B", "C", "D"}; + public void testBlockingWithStateTransfer() throws Exception { + String[] names = { "A", "B", "C", "D" }; _testChannels(names, FlushTestReceiver.CONNECT_AND_SEPARATE_GET_STATE); } - - /** - * Tests emition of block/unblock/set|get state events for both mux and bare - * channel depending on mux.on parameter. In mux mode there will be only one - * mux channel for each "real" channel created and the number of real - * channels created is getMuxFactoryCount(). - * - */ + + /** Tests the emition of block/unblock/get|set state events */ @Test - public void testBlockingWithConnectAndStateTransfer() { - String[] names = {"A", "B", "C", "D"}; + public void testBlockingWithConnectAndStateTransfer() throws Exception { + String[] names = { "A", "B", "C", "D" }; _testChannels(names, FlushTestReceiver.CONNECT_AND_GET_STATE); } - - - private void _testChannels(String names[], int connectType) { + private void _testChannels(String names[], int connectType) throws Exception { int count = names.length; - ArrayList channels = new ArrayList(count); - try{ + List channels = new ArrayList(count); + try { // Create a semaphore and take all its permits Semaphore semaphore = new Semaphore(count); semaphore.acquire(count); @@ -298,152 +323,145 @@ // Create channels and their threads that will block on the // semaphore boolean first = true; - for(String channelName:names){ + for (String channelName : names) { FlushTestReceiver channel = null; - if(first) - channel=new FlushTestReceiver(channelName, semaphore, 0, connectType); - else{ - channel=new FlushTestReceiver((JChannel)channels.get(0).getChannel(),channelName, semaphore, 0, connectType); + if (first) + channel = new FlushTestReceiver(channelName, semaphore, 0, connectType); + else { + channel = new FlushTestReceiver((JChannel) channels.get(0).getChannel(), + channelName, semaphore, 0, connectType); } channels.add(channel); - first = false; - // Release one ticket at a time to allow the thread to start - // working + // Release one ticket at a time to allow the thread to start working channel.start(); - semaphore.release(1); - Util.sleep(1000); + semaphore.release(1); + if (first) + Util.sleep(3000); // minimize changes of a merge happening + first = false; } - blockUntilViewsReceived(channels, 10000); - - // Sleep to ensure the threads get all the semaphore tickets - Util.sleep(1000); + Channel[] tmp = new Channel[channels.size()]; + int cnt = 0; + for (FlushTestReceiver receiver : channels) + tmp[cnt++] = receiver.getChannel(); + Util.blockUntilViewsReceived(30000, 1000, tmp); // Reacquire the semaphore tickets; when we have them all // we know the threads are done semaphore.tryAcquire(count, 40, TimeUnit.SECONDS); - - }catch(Exception ex){ - log.warn("Exception encountered during test", ex); - assert false : "Exception encountered during test execution: " + ex; - }finally{ - - //close all channels and .... - for(FlushTestReceiver app:channels){ + + Util.sleep(1000); //let all events propagate... + for (FlushTestReceiver app : channels) + app.getChannel().setReceiver(null); + for (FlushTestReceiver app : channels) app.cleanup(); - Util.sleep(2000); - } - + // verify block/unblock/view/get|set state sequences for all members - for (FlushTestReceiver receiver : channels) { - checkEventStateTransferSequence(receiver); - } + for (FlushTestReceiver receiver : channels) { + checkEventStateTransferSequence(receiver); + System.out.println("event sequence is OK"); + } + } + finally { + for (FlushTestReceiver app : channels) + app.cleanup(); + } + } + + private static void changeProps(JChannel ... channels) { + for(JChannel ch: channels) { + FD fd=(FD)ch.getProtocolStack().findProtocol(FD.class); + if(fd != null) { + fd.setTimeout(1000); + fd.setMaxTries(2); + } + FD_ALL fd_all=(FD_ALL)ch.getProtocolStack().findProtocol(FD_ALL.class); + if(fd_all != null) { + fd_all.setTimeout(2000); + fd_all.setInterval(800); + } } } private class FlushTestReceiver extends PushChannelApplicationWithSemaphore { private int connectMethod; - + public static final int CONNECT_ONLY = 1; - + public static final int CONNECT_AND_SEPARATE_GET_STATE = 2; - - public static final int CONNECT_AND_GET_STATE = 3; + + public static final int CONNECT_AND_GET_STATE = 3; int msgCount = 0; - protected FlushTestReceiver(String name, - Semaphore semaphore, - int msgCount, - int connectMethod) throws Exception{ + protected FlushTestReceiver(String name, Semaphore semaphore, int msgCount, + int connectMethod) throws Exception { super(name, semaphore); this.connectMethod = connectMethod; this.msgCount = msgCount; events = Collections.synchronizedList(new LinkedList()); - if(connectMethod == CONNECT_ONLY || connectMethod == CONNECT_AND_SEPARATE_GET_STATE) - channel.connect("FlushTestReceiver"); - - if(connectMethod == CONNECT_AND_GET_STATE){ - channel.connect("FlushTestReceiver",null,null, 25000); + if (connectMethod == CONNECT_ONLY || connectMethod == CONNECT_AND_SEPARATE_GET_STATE) + channel.connect("FlushTestReceiver"); + + if (connectMethod == CONNECT_AND_GET_STATE) { + channel.connect("FlushTestReceiver", null, null, 25000); } - } - - protected FlushTestReceiver(JChannel ch, String name, - Semaphore semaphore, - int msgCount, - int connectMethod) throws Exception{ - super(ch,name, semaphore,false); + } + + protected FlushTestReceiver(JChannel ch, String name, Semaphore semaphore, int msgCount, + int connectMethod) throws Exception { + super(ch, name, semaphore); this.connectMethod = connectMethod; this.msgCount = msgCount; events = Collections.synchronizedList(new LinkedList()); - if(connectMethod == CONNECT_ONLY || connectMethod == CONNECT_AND_SEPARATE_GET_STATE) + if (connectMethod == CONNECT_ONLY || connectMethod == CONNECT_AND_SEPARATE_GET_STATE) channel.connect("FlushTestReceiver"); - - if(connectMethod == CONNECT_AND_GET_STATE){ - channel.connect("FlushTestReceiver",null,null, 25000); - } - } - public void clear() { - events.clear(); + if (connectMethod == CONNECT_AND_GET_STATE) { + channel.connect("FlushTestReceiver", null, null, 25000); + } } public List getEvents() { return new LinkedList(events); } - public void block() { - events.add(new BlockEvent()); - } - - public void unblock() { - events.add(new UnblockEvent()); - } - - public void viewAccepted(View new_view) { - events.add(new_view); - } - public byte[] getState() { events.add(new GetStateEvent(null, null)); return new byte[] { 'b', 'e', 'l', 'a' }; } - public void setState(byte[] state) { - events.add(new SetStateEvent(null, null)); - } - public void getState(OutputStream ostream) { - events.add(new GetStateEvent(null, null)); + super.getState(ostream); byte[] payload = new byte[] { 'b', 'e', 'l', 'a' }; - try{ + try { ostream.write(payload); - }catch(IOException e){ + } catch (IOException e) { e.printStackTrace(); - }finally{ + } finally { Util.close(ostream); } } public void setState(InputStream istream) { - events.add(new SetStateEvent(null, null)); + super.setState(istream); byte[] payload = new byte[4]; - try{ + try { istream.read(payload); - }catch(IOException e){ + } catch (IOException e) { e.printStackTrace(); - }finally{ + } finally { Util.close(istream); } } - protected void useChannel() throws Exception { - if(connectMethod == CONNECT_AND_SEPARATE_GET_STATE){ + protected void useChannel() throws Exception { + if (connectMethod == CONNECT_AND_SEPARATE_GET_STATE) { channel.getState(null, 25000); } - if(msgCount > 0){ - for(int i = 0;i < msgCount;i++){ + if (msgCount > 0) { + for (int i = 0; i < msgCount; i++) { channel.send(new Message()); Util.sleep(100); } @@ -456,41 +474,38 @@ boolean handle_requests = false; - public SimpleReplier(Channel channel,boolean handle_requests){ + public SimpleReplier(Channel channel, boolean handle_requests) { this.channel = channel; this.handle_requests = handle_requests; } public void receive(Message msg) { Message reply = new Message(msg.getSrc()); - try{ - log.info("-- MySimpleReplier[" + channel.getLocalAddress() - + "]: received message from " - + msg.getSrc()); - if(handle_requests){ + try { + log.info("-- MySimpleReplier[" + channel.getAddress() + "]: received message from " + + msg.getSrc()); + if (handle_requests) { log.info(", sending reply"); channel.send(reply); - }else + } else System.out.println("\n"); - }catch(Exception e){ + } catch (Exception e) { e.printStackTrace(); } } public void viewAccepted(View new_view) { - log.info("-- MySimpleReplier[" + channel.getLocalAddress() - + "]: viewAccepted(" - + new_view - + ")"); + log.info("-- MySimpleReplier[" + channel.getAddress() + "]: viewAccepted(" + new_view + + ")"); } public void block() { - log.info("-- MySimpleReplier[" + channel.getLocalAddress() + "]: block()"); + log.info("-- MySimpleReplier[" + channel.getAddress() + "]: block()"); } public void unblock() { - log.info("-- MySimpleReplier[" + channel.getLocalAddress() + "]: unblock()"); + log.info("-- MySimpleReplier[" + channel.getAddress() + "]: unblock()"); } } -} +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/FlushWithChannelJoinsAndFailuresTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/FlushWithChannelJoinsAndFailuresTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/FlushWithChannelJoinsAndFailuresTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/FlushWithChannelJoinsAndFailuresTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,50 @@ +package org.jgroups.tests; + +import org.jgroups.Channel; +import org.jgroups.Global; +import org.jgroups.JChannel; +import org.jgroups.util.Util; +import org.testng.annotations.Test; + +/** + * + * Flush and join problems during constant node failures and constant joins + * https://jira.jboss.org/jira/browse/JGRP-985 + * + * + * @author vladimir + * @since 2.8 + */ +@Test(groups = Global.FLUSH, sequential = true) +public class FlushWithChannelJoinsAndFailuresTest extends ChannelTestBase { + + private static final String cName = "FlushWithChannelFailuresTest"; + + @Test + public void testAndLoop() throws Exception { + int numChannels = 10; + Channel channels[] = new Channel[numChannels]; + for (int j = 0; j < numChannels; j++) { + if (j == 0) { + channels[j] = createChannel(true, numChannels); + } else { + channels[j] = createChannel((JChannel) channels[0]); + } + channels[j].connect(cName); + } + for (int i = 1; i <= 2; i++) { + int killPositions[] = { 0, 3, 5, 8 }; + for (int index : killPositions) { + Util.shutdown(channels[index]); + } + for (int index : killPositions) { + channels[index] = createChannel((JChannel) channels[1]); + channels[index].connect(cName); + } + System.out.println("***** Round " + i + " done *****"); + } + for (Channel c : channels) { + assert c.getView().getMembers().size() == 10; + } + } +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/GossipClientTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/GossipClientTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/GossipClientTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/GossipClientTest.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,117 +0,0 @@ - -package org.jgroups.tests; - -import org.jgroups.Address; -import org.jgroups.Global; -import org.jgroups.stack.GossipClient; -import org.jgroups.stack.GossipRouter; -import org.jgroups.stack.IpAddress; -import org.jgroups.util.Util; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import java.util.List; - -/** - * Tests Gossip protocol primitives with the new GossipRouter. Since 2.2.1, the - * GossipRouter is supposed to answer Gossip requests too. - * - * @author Ovidiu Feodorov - * @author Bela Ban - * @version $Id: GossipClientTest.java,v 1.7 2008/12/10 15:15:55 vlada Exp $ - * @since 2.2.1 - */ -@Test(groups=Global.STACK_INDEPENDENT,sequential=true) -public class GossipClientTest extends ChannelTestBase{ - GossipClient client; - GossipRouter router; - private long expiryTime=1000; - - - - @BeforeClass - void setUp() throws Exception { - router = new GossipRouter(); - router.setExpiryTime(expiryTime); - router.start(); - client=new GossipClient(new IpAddress(getBindAddress(), 12001), expiryTime, 1000, null); - client.setRefresherEnabled(false); // don't refresh the registrations - } - - @AfterClass - void tearDown() throws Exception { - client.stop(); - router.stop(); - } - - - public void testEmptyGET() throws Exception { - String groupName="nosuchgroup"; - List mbrs=client.getMembers(groupName); - assert mbrs != null; - assert mbrs.isEmpty(); - } - - - /** - * Registers an address with a group and then sends a GET request for that group. - */ - public void test_REGISTER_GET() throws Exception { - String groupName="TESTGROUP"; - int mbrPort=7777; - Address mbr=new IpAddress(getBindAddress(), mbrPort); - client.register(groupName, mbr, true); - - List mbrs=client.getMembers(groupName); - assert mbrs.size() == 1; - assert mbrs.get(0).equals(new IpAddress(getBindAddress(), mbrPort)); - } - - public void test_REGISTER_UNREGISTER_GET() throws Exception { - String groupName="TESTGROUP-2"; - int mbrPort=7777; - Address mbr=new IpAddress(getBindAddress(), mbrPort); - client.register(groupName, mbr,true); - - List mbrs=client.getMembers(groupName); - assert mbrs.size() == 1; - assert mbrs.get(0).equals(new IpAddress(getBindAddress(), mbrPort)); - - client.unregister(groupName, mbr);// done asynchronous, on a separate thread - Util.sleep(500); - mbrs=client.getMembers(groupName); - assert mbrs != null; - assert mbrs.isEmpty(); - } - - - /** - * Test if a member is removed from group after EXPIRY_TIME ms. - */ - public void testExpiration() throws Exception { - String groupName="TESTGROUP-3"; - Address mbr=new IpAddress(getBindAddress(), 7777); - - client.register(groupName, mbr); - - Util.sleep(500); - List mbrs=client.getMembers(groupName); - int size=mbrs.size(); - assert size == 1 : "group " + groupName + " has " + size + " member(s)"; - assert mbrs.get(0).equals(mbr); - - // because the sweep is ran at fixed expiryTime intervals, if - // an entry was added immediately after a sweep run, it actually - // spends almost 2*expiryTime in cache. - Thread.sleep(2 * expiryTime); - - // send a second GET after more than EXPIRY_TIME ms - mbrs=client.getMembers(groupName); - assert mbrs == null || mbrs.isEmpty() : "group " + groupName + " has " + mbrs.size() + " member(s): " + mbrs; - } - - - - -} diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/GossipRouterTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/GossipRouterTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/GossipRouterTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/GossipRouterTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,17 +1,16 @@ package org.jgroups.tests; -import org.jgroups.stack.GossipRouter; +import org.jgroups.Global; import org.jgroups.JChannel; -import org.jgroups.View; import org.jgroups.ReceiverAdapter; -import org.jgroups.Global; +import org.jgroups.View; import org.jgroups.protocols.MERGE2; -import org.jgroups.protocols.TUNNEL; -import org.jgroups.protocols.PING; +import org.jgroups.stack.GossipRouter; import org.jgroups.util.Util; -import org.testng.Assert; +import org.jgroups.util.StackType; import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; +import org.testng.annotations.BeforeClass; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -21,16 +20,28 @@ /** * @author Bela Ban - * @version $Id: GossipRouterTest.java,v 1.8 2008/10/31 08:38:42 belaban Exp $ */ -@Test(groups=Global.STACK_INDEPENDENT, sequential=true) +@Test(groups={Global.STACK_INDEPENDENT,Global.GOSSIP_ROUTER},sequential=true) public class GossipRouterTest { final static String PROPS="tunnel.xml"; GossipRouter router; JChannel c1, c2; + String bind_addr=null; + + @BeforeClass + protected void setUp() { + bind_addr=Util.getProperty(Global.BIND_ADDR); + if(bind_addr == null) { + StackType type=Util.getIpStackType(); + if(type == StackType.IPv6) + bind_addr="::1"; + else + bind_addr="127.0.0.1"; + } + } - @AfterMethod + @AfterMethod (alwaysRun=true) protected void tearDown() throws Exception { if(router != null) { router.stop(); @@ -55,21 +66,17 @@ System.out.println("-- starting first channel"); c1=new JChannel(PROPS); changeMergeInterval(c1); - setReconnectInterval(c1); - setRefreshInterval(c1); c1.setReceiver(new MyReceiver("c1", done, lock, cond)); c1.connect("demo"); System.out.println("-- starting second channel"); c2=new JChannel(PROPS); changeMergeInterval(c2); - setReconnectInterval(c2); - setRefreshInterval(c2); c2.setReceiver(new MyReceiver("c2", done, lock, cond)); c2.connect("demo"); System.out.println("-- starting GossipRouter"); - router=new GossipRouter(12001, "127.0.0.1"); + router=new GossipRouter(12001, bind_addr); router.start(); System.out.println("-- waiting for merge to happen --"); @@ -87,10 +94,8 @@ Util.sleep(500); View view=c1.getView(); System.out.println("view=" + view); - Assert.assertEquals(2, view.size()); - - c2.close(); - c1.close(); + assert view.size() == 2 : "view=" + view; + Util.close(c2, c1); } private static void changeMergeInterval(JChannel c1) { @@ -101,20 +106,6 @@ } } - private static void setReconnectInterval(JChannel channel) { - TUNNEL tunnel=(TUNNEL)channel.getProtocolStack().getTransport(); - if(tunnel != null) { - tunnel.setReconnectInterval(2000); - } - } - - private static void setRefreshInterval(JChannel channel) { - PING ping=(PING)channel.getProtocolStack().findProtocol(PING.class); - if(ping != null) { - ping.setGossipRefresh(1000); - } - } - private static class MyReceiver extends ReceiverAdapter { private final String name; private final Lock lock; diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/JoinTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/JoinTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/JoinTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/JoinTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -18,7 +18,6 @@ /** * @author Bela Ban - * @version $Id: JoinTest.java,v 1.27 2008/08/08 17:07:11 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=true) public class JoinTest extends ChannelTestBase { diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/LargeStateTransferTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/LargeStateTransferTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/LargeStateTransferTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/LargeStateTransferTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -24,14 +24,12 @@ * greater than max_bundle_size, e.g. * ifconfig lo0 mtu 65000 * @author Bela Ban - * @version $Id: LargeStateTransferTest.java,v 1.20 2008/08/08 17:07:11 vlada Exp $ */ @Test(groups={Global.STACK_DEPENDENT}, sequential=true) public class LargeStateTransferTest extends ChannelTestBase { - JChannel provider, requester; - Promise p=new Promise(); - long start, stop; - final static int SIZE_1=100000, SIZE_2=1000000, SIZE_3=5000000, SIZE_4=10000000; + JChannel provider, requester; + Promise p=new Promise(); + final static int SIZE_1=100000, SIZE_2=1000000, SIZE_3=5000000, SIZE_4=10000000; protected boolean useBlocking() { @@ -42,8 +40,10 @@ @BeforeMethod protected void setUp() throws Exception { provider=createChannel(true, 2); + provider.setName("provider"); modifyStack(provider); requester=createChannel(provider); + requester.setName("requester"); setOOBPoolSize(provider, requester); } @@ -78,31 +78,32 @@ p.reset(); requester.setReceiver(new Requester(p)); requester.connect(GROUP); - View view=requester.getView(); - assert view.size() == 2 : "view is " + view + ", but should have 2 members"; - log("requesting state of " + size + " bytes"); - start=System.currentTimeMillis(); - boolean rc=requester.getState(provider.getLocalAddress(), 20000); - log.info("getState(): result=" + rc); - Object result=p.getResult(10000); - stop=System.currentTimeMillis(); - log("result=" + result + " bytes (in " + (stop-start) + "ms)"); + View requester_view=requester.getView(); + assert requester_view.size() == 2 : "requester view is " + requester_view + ", but should have 2 members"; + View provider_view=provider.getView(); + assert provider_view.size() == 2 : "provider view is " + provider_view + ", but should have 2 members"; + log("requesting state of " + Util.printBytes(size)); + long start=System.currentTimeMillis(); + requester.getState(provider.getAddress(), 20000); + Integer result=p.getResult(20000); + long stop=System.currentTimeMillis(); assertNotNull(result); - assertEquals(result, new Integer(size)); + log("received " + Util.printBytes(result) + " (in " + (stop-start) + "ms)"); + assert result == size : "result=" + result + ", expected=" + size; } private static void setOOBPoolSize(JChannel... channels) { for(JChannel channel: channels) { TP transport=channel.getProtocolStack().getTransport(); - transport.setOOBMinPoolSize(1); - transport.setOOBMaxPoolSize(2); + transport.setOOBThreadPoolMinThreads(1); + transport.setOOBThreadPoolMaxThreads(2); } } - void log(String msg) { - log.info(Thread.currentThread() + " -- "+ msg); + static void log(String msg) { + System.out.println(" -- "+ msg); } private static void modifyStack(JChannel ch) { @@ -113,8 +114,8 @@ } - private class Provider extends ExtendedReceiverAdapter { - byte[] state; + private static class Provider extends ExtendedReceiverAdapter { + private final byte[] state; public Provider(int size) { state=new byte[size]; @@ -124,20 +125,16 @@ return state; } - public void viewAccepted(View new_view) { - log.info("[provider] new_view = " + new_view); - } - public void getState(OutputStream ostream){ - ObjectOutputStream oos =null; + DataOutputStream out =null; try{ - oos=new ObjectOutputStream(ostream); - oos.writeInt(state.length); - oos.write(state); + out=new DataOutputStream(ostream); + out.writeInt(state.length); + out.write(state, 0, state.length); } catch (IOException e){} finally{ - Util.close(ostream); + Util.close(out); } } public void setState(byte[] state) { @@ -146,15 +143,11 @@ } - private class Requester extends ExtendedReceiverAdapter { - Promise p; + private static class Requester extends ExtendedReceiverAdapter { + private final Promise promise; public Requester(Promise p) { - this.p=p; - } - - public void viewAccepted(View new_view) { - log("[requester] new_view = " + new_view); + this.promise=p; } public byte[] getState() { @@ -162,22 +155,22 @@ } public void setState(byte[] state) { - p.setResult(new Integer(state.length)); + promise.setResult(new Integer(state.length)); } public void setState(InputStream istream) { - ObjectInputStream ois=null; + DataInputStream in=null; int size=0; - try{ - ois= new ObjectInputStream(istream); - size = ois.readInt(); - byte []stateReceived= new byte[size]; - ois.read(stateReceived); + try { + in=new DataInputStream(istream); + size=in.readInt(); + byte[] stateReceived=new byte[size]; + in.readFully(stateReceived, 0, stateReceived.length); } catch (IOException e) {} finally { - Util.close(ois); + Util.close(in); } - p.setResult(new Integer(size)); + promise.setResult(size); } } diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/LastMessageDroppedTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/LastMessageDroppedTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/LastMessageDroppedTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/LastMessageDroppedTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -16,7 +16,6 @@ /** * Tests the last message dropped problem in NAKACK (see doc/design/varia2.txt) * @author Bela Ban - * @version $Id: LastMessageDroppedTest.java,v 1.5 2008/08/08 17:07:11 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=true) public class LastMessageDroppedTest extends ChannelTestBase { diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/MergeStressTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/MergeStressTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/MergeStressTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/MergeStressTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: MergeStressTest.java,v 1.19 2008/09/22 14:20:22 vlada Exp $ package org.jgroups.tests; @@ -23,7 +22,6 @@ * GMS.handle_concurrent_startup has to be set to false. * * @author Bela Ban - * @version $Id: MergeStressTest.java,v 1.19 2008/09/22 14:20:22 vlada Exp $ */ @@ -180,7 +178,7 @@ stop=System.currentTimeMillis(); total_connect_time=stop - start; view=ch.getView(); - my_addr=ch.getLocalAddress(); + my_addr=ch.getAddress(); log(my_addr + " connected in " + total_connect_time + " msecs (" @@ -206,7 +204,7 @@ log("reached " + num_members + " members"); try { received_all_views.await(); - ch.shutdown(); + Util.shutdown(ch); disconnected.await(); } catch(Exception e) { diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/MergeTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/MergeTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/MergeTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/MergeTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,215 +1,231 @@ package org.jgroups.tests; -import org.jgroups.Global; -import org.jgroups.JChannel; -import org.jgroups.View; -import org.jgroups.protocols.DISCARD; -import org.jgroups.protocols.FD; +import org.jgroups.*; import org.jgroups.protocols.MERGE2; -import org.jgroups.protocols.MPING; -import org.jgroups.stack.Protocol; +import org.jgroups.protocols.MERGE3; +import org.jgroups.protocols.MERGEFAST; +import org.jgroups.protocols.pbcast.GMS; +import org.jgroups.protocols.pbcast.NAKACK; import org.jgroups.stack.ProtocolStack; import org.jgroups.util.Util; import org.testng.annotations.Test; -import java.net.InetAddress; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Properties; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; +import java.util.*; /** * Tests merging on all stacks * * @author vlada - * @version $Id: MergeTest.java,v 1.34 2008/07/24 17:36:26 vlada Exp $ */ @Test(groups=Global.FLUSH,sequential=true) public class MergeTest extends ChannelTestBase { @Test - public void testMerging2Members() { - String[] names = {"A", "B"}; - mergeHelper(names); + public void testMerging2Members() throws Exception { + mergeHelper("MergeTest.testMerging2Members", "A", "B"); } @Test - public void testMerging4Members() { - String[] names = {"A", "B", "C", "D"}; - mergeHelper(names); + public void testMerging4Members() throws Exception { + mergeHelper("MergeTest.testMerging4Members", "A", "B", "C", "D"); } - /** - * - * - */ - protected void mergeHelper(String[] names) { - int count=names.length; - //List channels = new ArrayList(); - MergeApplication[] channels=new MergeApplication[count]; + protected void mergeHelper(String cluster_name, String ... members) throws Exception { + JChannel[] channels=null; try { - // Create a semaphore and take all its permits - Semaphore semaphore=new Semaphore(count); - semaphore.acquire(count); - - // Create activation threads that will block on the semaphore - for(int i=0;i < count;i++) { - if(i == 0) - channels[i]=new MergeApplication(names[i], semaphore, false); - else - channels[i]=new MergeApplication((JChannel)channels[0].getChannel(), - names[i], - semaphore, - false); - - // Release one ticket at a time to allow the thread to start - // working - channels[i].start(); - semaphore.release(1); - //sleep at least a second and max second and a half - Util.sleepRandom(1500); + channels=createChannels(cluster_name, members); + print(channels); + + System.out.println("\ncreating partitions: "); + createPartitions(channels, members); + print(channels); + for(String member: members) { + JChannel ch=findChannel(member, channels); + assert ch.getView().size() == 1 : "view of " + ch.getAddress() + ": " + ch.getView(); + } + + System.out.println("\n==== injecting merge event ===="); + for(String member: members) { + injectMergeEvent(channels, member, members); + } + for(int i=0; i < 20; i++) { + System.out.print("."); + if(allChannelsHaveViewOf(channels, members.length)) + break; + Util.sleep(500); } + System.out.println("\n"); + print(channels); + assertAllChannelsHaveViewOf(channels, members.length); + } + finally { + if(channels != null) + close(channels); + } + } - // Make sure everyone is in sync + private JChannel[] createChannels(String cluster_name, String[] members) throws Exception { + JChannel[] retval=new JChannel[members.length]; + JChannel ch=null; + for(int i=0; i < retval.length; i++) { + JChannel tmp; + if(ch == null) { + ch=createChannel(true, members.length); + tmp=ch; + } + else { + tmp=createChannel(ch); + } + tmp.setName(members[i]); + ProtocolStack stack=tmp.getProtocolStack(); - blockUntilViewsReceived(channels, 60000); + NAKACK nakack=(NAKACK)stack.findProtocol(NAKACK.class); + if(nakack != null) + nakack.setLogDiscardMessages(false); - // Sleep to ensure the threads get all the semaphore tickets - Util.sleep(2000); + stack.removeProtocol(MERGE2.class, MERGE3.class, MERGEFAST.class); - int split=count / 2; + tmp.connect(cluster_name); + retval[i]=tmp; + } - for(int i=0;i < split;i++) { - DISCARD discard=(DISCARD)((JChannel)channels[i].getChannel()).getProtocolStack() - .findProtocol("DISCARD"); - for(int j=split;j < count;j++) { - discard.addIgnoreMember(channels[j].getLocalAddress()); - } - } + return retval; + } - for(int i=count - 1;i >= split;i--) { - DISCARD discard=(DISCARD)((JChannel)channels[i].getChannel()).getProtocolStack() - .findProtocol("DISCARD"); - for(int j=0;j < split;j++) { - discard.addIgnoreMember(channels[j].getLocalAddress()); - } - } + private static void close(JChannel[] channels) { + if(channels == null) return; + for(int i=channels.length -1; i <= 0; i--) { + JChannel ch=channels[i]; + Util.close(ch); + } + } - log.info("Waiting for split to be detected..."); - View view; - long stop=System.currentTimeMillis() + 35 * 1000; - do { - view=channels[0].channel.getView(); - if(view.size() == 1) - break; - else - Util.sleep(1000); - }while(System.currentTimeMillis() < stop); - // Util.sleep(35*1000); + private static void createPartitions(JChannel[] channels, String ... partitions) throws Exception { + checkUniqueness(partitions); + List views=new ArrayList(partitions.length); + for(String partition: partitions) { + View view=createView(partition, channels); + views.add(view); + } + applyViews(views, channels); + } - log.info("Waiting for merging to kick in...."); - for(int i=0;i < count;i++) { - channels[i].getChannel().getProtocolStack().removeProtocol("DISCARD"); - } + private static void checkUniqueness(String[] ... partitions) throws Exception { + Set set=new HashSet(); + for(String[] partition: partitions) { + for(String tmp: partition) { + if(!set.add(tmp)) + throw new Exception("partitions are overlapping: element " + tmp + " is in multiple partitions"); + } + } + } - //Either merge properly or time out... - //we check that each channel again has correct view - blockUntilViewsReceived(channels, 60000); - - // Re-acquire the semaphore tickets; when we have them all - // we know the threads are done - boolean acquired=semaphore.tryAcquire(count, 20, TimeUnit.SECONDS); - if(!acquired) { - log.warn("Most likely a bug, analyse the stack below:"); - log.warn(Util.dumpThreads()); - } - Util.sleep(1000); + private static void injectMergeEvent(JChannel[] channels, String leader, String ... coordinators) { + Address leader_addr=leader != null? findAddress(leader, channels) : determineLeader(channels); + injectMergeEvent(channels, leader_addr, coordinators); + } + + private static void injectMergeEvent(JChannel[] channels, Address leader_addr, String ... coordinators) { + Map views=new HashMap(); + for(String tmp: coordinators) { + Address coord=findAddress(tmp, channels); + views.put(coord, findView(tmp, channels)); + } + + JChannel coord=findChannel(leader_addr, channels); + GMS gms=(GMS)coord.getProtocolStack().findProtocol(GMS.class); + gms.setLevel("trace"); + gms.up(new Event(Event.MERGE, views)); + } + + + private static View createView(String partition, JChannel[] channels) throws Exception { + Vector
            members=new Vector
            (); + Address addr=findAddress(partition, channels); + if(addr == null) + throw new Exception(partition + " not associated with a channel"); + members.add(addr); + return new View(members.firstElement(), 10, members); + } + + + private static JChannel findChannel(String tmp, JChannel[] channels) { + for(JChannel ch: channels) { + if(ch.getName().equals(tmp)) + return ch; } - catch(Exception ex) { - log.warn("Exception encountered during test", ex); - assert false:ex.getLocalizedMessage(); + return null; + } + + private static JChannel findChannel(Address addr, JChannel[] channels) { + for(JChannel ch: channels) { + if(ch.getAddress().equals(addr)) + return ch; } - finally { - List channelsReversed=Arrays.asList(channels); - Collections.reverse(channelsReversed); - for(MergeApplication channel:channelsReversed) { - channel.cleanup(); - Util.sleep(2000); - } - if(useBlocking()) { - for(MergeApplication channel:channels) { - checkEventStateTransferSequence(channel); - } - } + return null; + } + + private static View findView(String tmp, JChannel[] channels) { + for(JChannel ch: channels) { + if(ch.getName().equals(tmp)) + return ch.getView(); } + return null; } - - protected class MergeApplication extends PushChannelApplicationWithSemaphore { - public MergeApplication(String name,Semaphore semaphore,boolean useDispatcher) throws Exception{ - super(name, semaphore, useDispatcher); - replaceDiscoveryProtocol((JChannel)channel); - addDiscardProtocol((JChannel)channel); - modiftFDAndMergeSettings((JChannel)channel); - } - - public MergeApplication(JChannel ch,String name,Semaphore semaphore,boolean useDispatcher) throws Exception{ - super(ch,name, semaphore, useDispatcher); - replaceDiscoveryProtocol((JChannel)channel); - addDiscardProtocol((JChannel)channel); - modiftFDAndMergeSettings((JChannel)channel); - } - - public void useChannel() throws Exception { - channel.connect("MergeApplication"); - } + private static boolean allChannelsHaveViewOf(JChannel[] channels, int count) { + for(JChannel ch: channels) { + if(ch.getView().size() != count) + return false; + } + return true; } - - - private static void addDiscardProtocol(JChannel ch) throws Exception { - ProtocolStack stack=ch.getProtocolStack(); - Protocol transport=stack.getTransport(); - DISCARD discard=new DISCARD(); - discard.setProtocolStack(ch.getProtocolStack()); - discard.start(); - stack.insertProtocol(discard, ProtocolStack.ABOVE, transport.getName()); + + private static void assertAllChannelsHaveViewOf(JChannel[] channels, int count) { + for(JChannel ch: channels) + assert ch.getView().size() == count : ch.getName() + " has view " + ch.getView(); + } + + + private static Address determineLeader(JChannel[] channels, String ... coords) { + Membership membership=new Membership(); + for(String coord: coords) + membership.add(findAddress(coord, channels)); + membership.sort(); + return membership.elementAt(0); } + + private static Address findAddress(String tmp, JChannel[] channels) { + for(JChannel ch: channels) { + if(ch.getName().equals(tmp)) + return ch.getAddress(); + } + return null; + } + + private static void applyViews(List views, JChannel[] channels) { + for(View view: views) { + Collection
            members=view.getMembers(); + for(JChannel ch: channels) { + Address addr=ch.getAddress(); + if(members.contains(addr)) { + GMS gms=(GMS)ch.getProtocolStack().findProtocol(GMS.class); + gms.installView(view); + } + } + } + } - private void replaceDiscoveryProtocol(JChannel ch) throws Exception { - ProtocolStack stack=ch.getProtocolStack(); - Protocol discovery=stack.removeProtocol("TCPPING"); - if(discovery != null){ - Protocol transport = stack.getTransport(); - MPING mping=new MPING(); - InetAddress bindAddress=Util.getBindAddress(new Properties()); - mping.setBindAddr(bindAddress); - mping.setMulticastAddress("230.3.3.3"); - mping.setMcastPort(7777); - stack.insertProtocol(mping, ProtocolStack.ABOVE, transport.getName()); - mping.setProtocolStack(ch.getProtocolStack()); - mping.init(); - mping.start(); - log.info("Replaced TCPPING with MPING. See http://wiki.jboss.org/wiki/Wiki.jsp?page=JGroupsMERGE2"); - } - } - - private static void modiftFDAndMergeSettings(JChannel ch) { - ProtocolStack stack=ch.getProtocolStack(); - - FD fd=(FD)stack.findProtocol("FD"); - if(fd != null) { - fd.setMaxTries(3); - fd.setTimeout(1000); - } - MERGE2 merge=(MERGE2)stack.findProtocol("MERGE2"); - if(merge != null) { - merge.setMinInterval(5000); - merge.setMaxInterval(10000); + + private static void print(JChannel[] channels) { + for(JChannel ch: channels) { + System.out.println(ch.getName() + ": " + ch.getView()); } } + + + } diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/MessageBundlingTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/MessageBundlingTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/MessageBundlingTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/MessageBundlingTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -16,7 +16,6 @@ /** * Measure the latency between messages with message bundling enabled at the transport level * @author Bela Ban - * @version $Id: MessageBundlingTest.java,v 1.17 2008/08/08 17:07:12 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=true) public class MessageBundlingTest extends ChannelTestBase { @@ -25,7 +24,7 @@ private final static long LATENCY=1500L; private final static long SLEEP=5000L; private static final boolean BUNDLING=true; - private static final int MAX_BYTES=20000; + private static final int MAX_BYTES=64000; @@ -46,7 +45,7 @@ public void testLatencyWithoutMessageBundling() throws Exception { createChannels("testLatencyWithoutMessageBundling"); Message tmp=new Message(); - setBundling(ch1, false, 20000, 30); + setBundling(ch1, false, MAX_BYTES, 30); r2.setNumExpectedMesssages(1); Promise promise=new Promise(); r2.setPromise(promise); @@ -78,8 +77,8 @@ Long time2=list.get(0); long diff=time2 - time; System.out.println("latency: " + diff + " ms"); - assertTrue("latency (" + diff + "ms) should be more than the bundling timeout (" + LATENCY + - "ms), but less than 2 times the LATENCY (" + LATENCY *2 + ")", diff >= LATENCY && diff <= LATENCY * 2); + assertTrue("latency (" + diff + "ms) should be less than 2 times the LATENCY (" + LATENCY *2 + ")", + diff <= LATENCY * 2); } @@ -101,8 +100,8 @@ Long time2=list.get(0); long diff=time2 - time; System.out.println("latency: " + diff + " ms"); - assertTrue("latency (" + diff + "ms) should be more than the bundling timeout (" + LATENCY + - "ms), but less than 2 times the LATENCY (" + LATENCY *2 + ")", diff >= LATENCY && diff <= LATENCY * 2); + assertTrue("latency (" + diff + "ms) should be less than 2 times the LATENCY (" + LATENCY *2 + ")", + diff <= LATENCY * 2); } @@ -141,11 +140,13 @@ private void createChannels(String cluster) throws Exception { ch1=createChannel(true, 2); + ch1.setName("A"); setBundling(ch1, BUNDLING, MAX_BYTES, LATENCY); setLoopback(ch1, false); ch1.setReceiver(new NullReceiver()); ch1.connect("MessageBundlingTest-" + cluster); ch2=createChannel(ch1); + ch2.setName("B"); // setBundling(ch2, BUNDLING, MAX_BYTES, LATENCY); // setLoopback(ch2, false); r2=new MyReceiver(); @@ -158,16 +159,16 @@ private static void setLoopback(JChannel ch, boolean b) { ProtocolStack stack=ch.getProtocolStack(); - Vector prots=stack.getProtocols(); - TP transport=(TP)prots.lastElement(); + List prots=stack.getProtocols(); + TP transport=(TP)prots.get(prots.size() -1); transport.setLoopback(b); } private static void setBundling(JChannel ch, boolean enabled, int max_bytes, long timeout) { ProtocolStack stack=ch.getProtocolStack(); - Vector prots=stack.getProtocols(); - TP transport=(TP)prots.lastElement(); + List prots=stack.getProtocols(); + TP transport=(TP)prots.get(prots.size() -1); transport.setEnableBundling(enabled); if(enabled) { transport.setMaxBundleSize(max_bytes); diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/MessageDispatcherUnitTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/MessageDispatcherUnitTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/MessageDispatcherUnitTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/MessageDispatcherUnitTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -3,9 +3,7 @@ import org.jgroups.Global; import org.jgroups.JChannel; import org.jgroups.Message; -import org.jgroups.blocks.GroupRequest; -import org.jgroups.blocks.MessageDispatcher; -import org.jgroups.blocks.RequestHandler; +import org.jgroups.blocks.*; import org.jgroups.protocols.TP; import org.jgroups.protocols.pbcast.GMS; import org.jgroups.stack.ProtocolStack; @@ -22,45 +20,45 @@ * Tests return values from MessageDispatcher.castMessage() * * @author Bela Ban - * @version $Id: MessageDispatcherUnitTest.java,v 1.10 2008/04/14 08:18:39 * belaban Exp $ */ @Test(groups=Global.STACK_DEPENDENT, sequential=true) public class MessageDispatcherUnitTest extends ChannelTestBase { - MessageDispatcher disp, disp2; - JChannel ch, ch2; + MessageDispatcher d1, d2; + JChannel c1, c2; @BeforeClass protected void setUp() throws Exception { - ch=createChannel(true); - GMS gms=(GMS)ch.getProtocolStack().findProtocol(GMS.class); + c1=createChannel(true); + c1.setName("A"); + GMS gms=(GMS)c1.getProtocolStack().findProtocol(GMS.class); if(gms != null) gms.setPrintLocalAddress(false); - disableBundling(ch); - disp=new MessageDispatcher(ch, null, null, null); - ch.connect("MessageDispatcherUnitTest"); + disableBundling(c1); + d1=new MessageDispatcher(c1, null, null, null); + c1.connect("MessageDispatcherUnitTest"); } @AfterClass protected void tearDown() throws Exception { - disp.stop(); - ch.close(); + d1.stop(); + c1.close(); Util.sleep(500); } @AfterMethod protected void closeSecondChannel() { - if(ch2 != null) { - disp2.stop(); - ch2.close(); + if(c2 != null) { + d2.stop(); + c2.close(); Util.sleep(500); } } public void testNullMessageToSelf() { MyHandler handler=new MyHandler(null); - disp.setRequestHandler(handler); - RspList rsps=disp.castMessage(null, new Message(), GroupRequest.GET_ALL, 0); + d1.setRequestHandler(handler); + RspList rsps=d1.castMessage(null, new Message(), new RequestOptions(Request.GET_ALL, 0)); System.out.println("rsps:\n" + rsps); assertNotNull(rsps); Assert.assertEquals(1, rsps.size()); @@ -81,36 +79,37 @@ } public void testNullMessageToAll() throws Exception { - disp.setRequestHandler(new MyHandler(null)); + d1.setRequestHandler(new MyHandler(null)); - ch2=createChannel(ch); - disableBundling(ch2); + c2=createChannel(c1); + c2.setName("B"); + disableBundling(c2); long stop, start=System.currentTimeMillis(); - disp2=new MessageDispatcher(ch2, null, null, new MyHandler(null)); + d2=new MessageDispatcher(c2, null, null, new MyHandler(null)); stop=System.currentTimeMillis(); - ch2.connect("MessageDispatcherUnitTest"); - Assert.assertEquals(2, ch2.getView().size()); - System.out.println("view: " + ch2.getView()); + c2.connect("MessageDispatcherUnitTest"); + Assert.assertEquals(2, c2.getView().size()); + System.out.println("view: " + c2.getView()); System.out.println("casting message"); start=System.currentTimeMillis(); - RspList rsps=disp.castMessage(null, new Message(), GroupRequest.GET_ALL, 0); + RspList rsps=d1.castMessage(null, new Message(), new RequestOptions(Request.GET_ALL, 0)); stop=System.currentTimeMillis(); System.out.println("rsps:\n" + rsps); System.out.println("call took " + (stop - start) + " ms"); assertNotNull(rsps); Assert.assertEquals(2, rsps.size()); - Rsp rsp=rsps.get(ch.getLocalAddress()); + Rsp rsp=rsps.get(c1.getAddress()); assertNotNull(rsp); Object ret=rsp.getValue(); assert ret == null; - rsp=rsps.get(ch2.getLocalAddress()); + rsp=rsps.get(c2.getAddress()); assertNotNull(rsp); ret=rsp.getValue(); assert ret == null; - Util.close(ch2); + Util.close(c2); } public void test200ByteMessageToAll() throws Exception { @@ -128,9 +127,9 @@ private void sendMessage(int size) { long start, stop; MyHandler handler=new MyHandler(new byte[size]); - disp.setRequestHandler(handler); + d1.setRequestHandler(handler); start=System.currentTimeMillis(); - RspList rsps=disp.castMessage(null, new Message(), GroupRequest.GET_ALL, 0); + RspList rsps=d1.castMessage(null, new Message(), new RequestOptions(Request.GET_ALL, 0)); stop=System.currentTimeMillis(); System.out.println("rsps:\n" + rsps); System.out.println("call took " + (stop - start) + " ms"); @@ -143,33 +142,34 @@ private void sendMessageToBothChannels(int size) throws Exception { long start, stop; - disp.setRequestHandler(new MyHandler(new byte[size])); + d1.setRequestHandler(new MyHandler(new byte[size])); - ch2=createChannel(ch); - disableBundling(ch2); - disp2=new MessageDispatcher(ch2, null, null, new MyHandler(new byte[size])); - ch2.connect("MessageDispatcherUnitTest"); - Assert.assertEquals(2, ch2.getView().size()); + c2=createChannel(c1); + c2.setName("B"); + disableBundling(c2); + d2=new MessageDispatcher(c2, null, null, new MyHandler(new byte[size])); + c2.connect("MessageDispatcherUnitTest"); + Assert.assertEquals(2, c2.getView().size()); System.out.println("casting message"); start=System.currentTimeMillis(); - RspList rsps=disp.castMessage(null, new Message(), GroupRequest.GET_ALL, 0); + RspList rsps=d1.castMessage(null, new Message(), new RequestOptions(Request.GET_ALL, 0)); stop=System.currentTimeMillis(); System.out.println("rsps:\n" + rsps); System.out.println("call took " + (stop - start) + " ms"); assertNotNull(rsps); Assert.assertEquals(2, rsps.size()); - Rsp rsp=rsps.get(ch.getLocalAddress()); + Rsp rsp=rsps.get(c1.getAddress()); assertNotNull(rsp); byte[] ret=(byte[])rsp.getValue(); Assert.assertEquals(size, ret.length); - rsp=rsps.get(ch2.getLocalAddress()); + rsp=rsps.get(c2.getAddress()); assertNotNull(rsp); ret=(byte[])rsp.getValue(); Assert.assertEquals(size, ret.length); - Util.close(ch2); + Util.close(c2); } private static void disableBundling(JChannel ch) { diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/NAKACK_OOB_Test.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/NAKACK_OOB_Test.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/NAKACK_OOB_Test.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/NAKACK_OOB_Test.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,124 +0,0 @@ -package org.jgroups.tests; - - -import org.jgroups.Global; -import org.jgroups.JChannel; -import org.jgroups.Message; -import org.jgroups.ReceiverAdapter; -import org.jgroups.protocols.DISCARD_PAYLOAD; -import org.jgroups.stack.ProtocolStack; -import org.jgroups.util.Util; -import org.testng.Assert; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -/** - * Tests the NAKACK protocol for OOB msgs, tests http://jira.jboss.com/jira/browse/JGRP-379 - * @author Bela Ban - * @version $Id: NAKACK_OOB_Test.java,v 1.12 2008/08/08 17:07:12 vlada Exp $ - */ -@Test(groups=Global.STACK_DEPENDENT,sequential=true) -public class NAKACK_OOB_Test extends ChannelTestBase { - JChannel ch1, ch2, ch3; - - - @BeforeMethod - public void setUp() throws Exception { - ch1=createChannel(true, 3); - ch2=createChannel(ch1); - ch3=createChannel(ch1); - } - - @AfterMethod - public void tearDown() throws Exception { - Util.close(ch3, ch2, ch1); - } - - - /** - * Tests http://jira.jboss.com/jira/browse/JGRP-379: we send 1, 2, 3, 4(OOB) and 5 to the cluster. - * Message with seqno 3 is discarded two times, so retransmission will make the receivers receive it *after* 4. - * Because 4 is marked as OOB, we will deliver 4 *immediately* (before 3 and 5), so the sequence of the messages - * at the receivers is 1 - 2 - 4 -3 - 5. - * Note that OOB messages *destroys* FIFO ordering (or whatever ordering properties are set) ! - * @throws Exception - */ - @Test - public void testOutOfBandMessages() throws Exception { - NAKACK_OOB_Test.MyReceiver receiver1=new NAKACK_OOB_Test.MyReceiver(); - NAKACK_OOB_Test.MyReceiver receiver2=new NAKACK_OOB_Test.MyReceiver(); - NAKACK_OOB_Test.MyReceiver receiver3=new NAKACK_OOB_Test.MyReceiver(); - ch1.setReceiver(receiver1); - ch2.setReceiver(receiver2); - ch3.setReceiver(receiver3); - - // all channels will discard messages with seqno #3 two times, the let them pass - ch1.getProtocolStack().insertProtocol(new DISCARD_PAYLOAD(), ProtocolStack.BELOW, "NAKACK"); - ch2.getProtocolStack().insertProtocol(new DISCARD_PAYLOAD(), ProtocolStack.BELOW, "NAKACK"); - ch3.getProtocolStack().insertProtocol(new DISCARD_PAYLOAD(), ProtocolStack.BELOW, "NAKACK"); - - ch1.connect("NAKACK_OOB_Test"); - ch2.connect("NAKACK_OOB_Test"); - ch3.connect("NAKACK_OOB_Test"); - - Assert.assertEquals(3, ch3.getView().getMembers().size()); - - for(int i=1; i <=5; i++) { - Message msg=new Message(null, null, new Long(i)); - if(i == 4) - msg.setFlag(Message.OOB); - System.out.println("-- sending message #" + i); - ch1.send(msg); - Util.sleep(100); - } - - Util.sleep(5000); // wait until retransmission of seqno #3 happens, so that 4 and 5 are received as well - - List seqnos1=receiver1.getSeqnos(); - List seqnos2=receiver2.getSeqnos(); - List seqnos3=receiver3.getSeqnos(); - - System.out.println("sequence numbers:"); - System.out.println("ch1: " + seqnos1); - System.out.println("ch2: " + seqnos2); - System.out.println("ch3: " + seqnos3); - - // expected sequence is: 1 2 4 3 5 ! Reason: 4 is sent OOB, does *not* wait until 3 has been retransmitted !! - Long[] expected_seqnos=new Long[]{1L,2L,4L,3L,5L}; - for(int i=0; i < expected_seqnos.length; i++) { - Long expected_seqno=expected_seqnos[i]; - Long received_seqno=seqnos1.get(i); - Assert.assertEquals(expected_seqno, received_seqno); - received_seqno=seqnos2.get(i); - Assert.assertEquals(expected_seqno, received_seqno); - received_seqno=seqnos3.get(i); - Assert.assertEquals(expected_seqno, received_seqno); - } - } - - - public static class MyReceiver extends ReceiverAdapter { - /** List of unicast sequence numbers */ - List seqnos=Collections.synchronizedList(new LinkedList()); - - public MyReceiver() { - } - - public List getSeqnos() { - return seqnos; - } - - public void receive(Message msg) { - if(msg != null) { - Long num=(Long)msg.getObject(); - seqnos.add(num); - } - } - } - -} diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/NAKACK_Test.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/NAKACK_Test.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/NAKACK_Test.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/NAKACK_Test.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,133 @@ +package org.jgroups.tests; + + +import org.jgroups.Global; +import org.jgroups.JChannel; +import org.jgroups.Message; +import org.jgroups.ReceiverAdapter; +import org.jgroups.protocols.DISCARD_PAYLOAD; +import org.jgroups.stack.ProtocolStack; +import org.jgroups.util.Util; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.Collection; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * Tests the NAKACK protocol for OOB and regular msgs, tests http://jira.jboss.com/jira/browse/JGRP-379 + * @author Bela Ban + */ +@Test(groups=Global.STACK_DEPENDENT,sequential=true) +public class NAKACK_Test extends ChannelTestBase { + JChannel c1, c2, c3; + + + @BeforeMethod + public void setUp() throws Exception { + c1=createChannel(true, 3); + c2=createChannel(c1); + c3=createChannel(c1); + } + + @AfterMethod + public void tearDown() throws Exception { + Util.close(c3, c2, c1); + } + + + /** + * Tests http://jira.jboss.com/jira/browse/JGRP-379: we send 1, 2, 3, 4(OOB) and 5 to the cluster. + * Message with seqno 3 is discarded two times, so retransmission will make the receivers receive it *after* 4. + * Note that OOB messages *destroys* FIFO ordering (or whatever ordering properties are set) ! + * @throws Exception + */ + @Test + @SuppressWarnings("unchecked") + public void testOutOfBandMessages() throws Exception { + NAKACK_Test.MyReceiver receiver1=new NAKACK_Test.MyReceiver(); + NAKACK_Test.MyReceiver receiver2=new NAKACK_Test.MyReceiver(); + NAKACK_Test.MyReceiver receiver3=new NAKACK_Test.MyReceiver(); + c1.setReceiver(receiver1); + c2.setReceiver(receiver2); + c3.setReceiver(receiver3); + + c1.getProtocolStack().insertProtocol(new DISCARD_PAYLOAD(), ProtocolStack.BELOW, "NAKACK"); + + c1.connect("NAKACK_OOB_Test"); + c2.connect("NAKACK_OOB_Test"); + c3.connect("NAKACK_OOB_Test"); + + assert c3.getView().getMembers().size() == 3 : "view is " + c3.getView() + ", expected view of 3 members"; + + for(int i=1; i <=5; i++) { + Message msg=new Message(null, null, new Long(i)); + if(i == 4) + msg.setFlag(Message.OOB); + System.out.println("-- sending message #" + i); + c1.send(msg); + Util.sleep(100); + } + + Collection seqnos1=receiver1.getSeqnos(); + Collection seqnos2=receiver2.getSeqnos(); + Collection seqnos3=receiver3.getSeqnos(); + + // wait until retransmission of seqno #3 happens, so that 4 and 5 are received as well + long target_time=System.currentTimeMillis() + 20000; + do { + if(seqnos1.size() >= 5 && seqnos2.size() >= 5 && seqnos3.size() >= 5) + break; + Util.sleep(500); + } + while(target_time > System.currentTimeMillis()); + + System.out.println("sequence numbers:"); + System.out.println("c1: " + seqnos1); + System.out.println("c2: " + seqnos2); + System.out.println("c3: " + seqnos3); + checkOrder(seqnos1, seqnos2, seqnos3); + } + + /** + * Checks whether the numbers are in order *after* removing 4: the latter is OOB and can therefore appear anywhere + * in the sequence + * @param lists + */ + private static void checkOrder(Collection ... lists) throws Exception { + for(Collection list: lists) { + list.remove(4L); + long prev_val=0; + for(long val: list) { + if(val <= prev_val) + throw new Exception("elements are not ordered in list: " + list); + prev_val=val; + } + } + } + + + + public static class MyReceiver extends ReceiverAdapter { + /** List of unicast sequence numbers */ + Collection seqnos=new ConcurrentLinkedQueue(); + + public MyReceiver() { + } + + public Collection getSeqnos() { + return seqnos; + } + + public void receive(Message msg) { + if(msg != null) { + Long num=(Long)msg.getObject(); + seqnos.add(num); + } + } + + public int size() {return seqnos.size();} + } + +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/OOBTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/OOBTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/OOBTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/OOBTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -4,6 +4,7 @@ import org.jgroups.protocols.DISCARD; import org.jgroups.protocols.TP; import org.jgroups.protocols.UNICAST; +import org.jgroups.protocols.UNICAST2; import org.jgroups.protocols.pbcast.NAKACK; import org.jgroups.protocols.pbcast.STABLE; import org.jgroups.stack.ProtocolStack; @@ -12,19 +13,16 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; +import java.util.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.ConcurrentLinkedQueue; /** * Tests whether OOB multicast/unicast messages are blocked by regular messages (which block) - should NOT be the case. * The class name is a misnomer, both multicast *and* unicast messages are tested * @author Bela Ban - * @version $Id: OOBTest.java,v 1.15 2009/01/05 08:58:58 belaban Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=true) public class OOBTest extends ChannelTestBase { @@ -33,12 +31,13 @@ @BeforeMethod public void init() throws Exception { c1=createChannel(true, 2); - // c1.setOpt(Channel.LOCAL, false); + c1.setName("C1"); c2=createChannel(c1); + c2.setName("C2"); setOOBPoolSize(c1, c2); setStableGossip(c1, c2); - c1.connect("OOBMcastTest"); - c2.connect("OOBMcastTest"); + c1.connect("OOBTest"); + c2.connect("OOBTest"); View view=c2.getView(); log.info("view = " + view); assert view.size() == 2 : "view is " + view; @@ -57,7 +56,7 @@ * received by B. */ public void testNonBlockingUnicastOOBMessage() throws ChannelNotConnectedException, ChannelClosedException { - Address dest=c2.getLocalAddress(); + Address dest=c2.getAddress(); send(dest); } @@ -73,23 +72,24 @@ public void testRegularAndOOBUnicasts() throws Exception { DISCARD discard=new DISCARD(); ProtocolStack stack=c1.getProtocolStack(); - stack.insertProtocol(discard, ProtocolStack.BELOW, UNICAST.class); + stack.insertProtocol(discard, ProtocolStack.BELOW, UNICAST.class, UNICAST2.class); - Address dest=c2.getLocalAddress(); + Address dest=c2.getAddress(); Message m1=new Message(dest, null, 1); Message m2=new Message(dest, null, 2); m2.setFlag(Message.OOB); Message m3=new Message(dest, null, 3); - MyReceiver receiver=new MyReceiver(); + MyReceiver receiver=new MyReceiver("C2"); c2.setReceiver(receiver); c1.send(m1); discard.setDropDownUnicasts(1); c1.send(m2); c1.send(m3); + sendStableMessages(c1,c2); Util.sleep(1000); // time for potential retransmission - List list=receiver.getMsgs(); + Collection list=receiver.getMsgs(); assert list.size() == 3 : "list is " + list; assert list.contains(1) && list.contains(2) && list.contains(3); } @@ -97,9 +97,9 @@ public void testRegularAndOOBUnicasts2() throws Exception { DISCARD discard=new DISCARD(); ProtocolStack stack=c1.getProtocolStack(); - stack.insertProtocol(discard, ProtocolStack.BELOW, UNICAST.class); + stack.insertProtocol(discard, ProtocolStack.BELOW, UNICAST.class, UNICAST2.class); - Address dest=c2.getLocalAddress(); + Address dest=c2.getAddress(); Message m1=new Message(dest, null, 1); Message m2=new Message(dest, null, 2); m2.setFlag(Message.OOB); @@ -107,7 +107,7 @@ m3.setFlag(Message.OOB); Message m4=new Message(dest, null, 4); - MyReceiver receiver=new MyReceiver(); + MyReceiver receiver=new MyReceiver("C2"); c2.setReceiver(receiver); c1.send(m1); @@ -120,10 +120,11 @@ c1.send(m4); Util.sleep(1000); // sleep some time to receive all messages - List list=receiver.getMsgs(); + Collection list=receiver.getMsgs(); int count=10; while(list.size() < 4 && --count > 0) { Util.sleep(500); // time for potential retransmission + sendStableMessages(c1,c2); } log.info("list = " + list); assert list.size() == 4 : "list is " + list; @@ -142,7 +143,7 @@ m2.setFlag(Message.OOB); Message m3=new Message(dest, null, 3); - MyReceiver receiver=new MyReceiver(); + MyReceiver receiver=new MyReceiver("C2"); c2.setReceiver(receiver); c1.send(m1); discard.setDropDownMulticasts(1); @@ -150,50 +151,136 @@ c1.send(m3); Util.sleep(500); - List list=receiver.getMsgs(); + Collection list=receiver.getMsgs(); for(int i=0; i < 10; i++) { log.info("list = " + list); if(list.size() == 3) break; Util.sleep(1000); // give the asynchronous msgs some time to be received + sendStableMessages(c1,c2); } assert list.size() == 3 : "list is " + list; assert list.contains(1) && list.contains(2) && list.contains(3); } - // @Test(invocationCount=5) + @Test(invocationCount=5) + @SuppressWarnings("unchecked") public void testRandomRegularAndOOBMulticasts() throws Exception { DISCARD discard=new DISCARD(); - discard.setLocalAddress(c1.getLocalAddress()); - discard.setDownDiscardRate(0.5); + discard.setLocalAddress(c1.getAddress()); + discard.setDownDiscardRate(0.5); ProtocolStack stack=c1.getProtocolStack(); stack.insertProtocol(discard, ProtocolStack.BELOW, NAKACK.class); - Address dest=null; // send to all - MyReceiver r1=new MyReceiver(), r2=new MyReceiver(); + MyReceiver r1=new MyReceiver("C1"), r2=new MyReceiver("C2"); c1.setReceiver(r1); c2.setReceiver(r2); - final int NUM_MSGS=100; - final int NUM_THREADS=10; - send(dest, NUM_MSGS, NUM_THREADS, 0.5); - List one=r1.getMsgs(), two=r2.getMsgs(); - for(int i=0; i < 20; i++) { + final int NUM_MSGS=20; + final int NUM_THREADS=10; + + send(null, NUM_MSGS, NUM_THREADS, 0.5); // send on random channel (c1 or c2) + + Collection one=r1.getMsgs(), two=r2.getMsgs(); + for(int i=0; i < 10; i++) { if(one.size() == NUM_MSGS && two.size() == NUM_MSGS) break; log.info("one size " + one.size() + ", two size " + two.size()); Util.sleep(1000); + sendStableMessages(c1,c2); + } + log.info("one size " + one.size() + ", two size " + two.size()); + + stack.removeProtocol("DISCARD"); + + for(int i=0; i < 5; i++) { + if(one.size() == NUM_MSGS && two.size() == NUM_MSGS) + break; + sendStableMessages(c1,c2); + Util.sleep(500); } - log.info("one size " + one.size() + ", two size " + two.size()); + System.out.println("C1 received " + one.size() + " messages ("+ NUM_MSGS + " expected)" + + "\nC2 received " + two.size() + " messages ("+ NUM_MSGS + " expected)"); + check(NUM_MSGS, one, two); } + /** + * Tests https://jira.jboss.org/jira/browse/JGRP-1079 + * @throws ChannelNotConnectedException + * @throws ChannelClosedException + */ + public void testOOBMessageLoss() throws ChannelNotConnectedException, ChannelClosedException { + Util.close(c2); // we only need 1 channel + MyReceiver receiver=new MySleepingReceiver("C1", 1000); + c1.setReceiver(receiver); + + TP transport=c1.getProtocolStack().getTransport(); + transport.setOOBRejectionPolicy("discard"); + + final int NUM=10; + + for(int i=1; i <= NUM; i++) { + Message msg=new Message(null, null, i); + msg.setFlag(Message.OOB); + c1.send(msg); + } + STABLE stable=(STABLE)c1.getProtocolStack().findProtocol(STABLE.class); + if(stable != null) + stable.runMessageGarbageCollection(); + Collection msgs=receiver.getMsgs(); + + for(int i=0; i < 20; i++) { + if(msgs.size() == NUM) + break; + Util.sleep(1000); + sendStableMessages(c1,c2); + } + + System.out.println("msgs = " + Util.print(msgs)); + + assert msgs.size() == NUM : "expected " + NUM + " messages but got " + msgs.size() + ", msgs=" + Util.print(msgs); + for(int i=1; i <= NUM; i++) { + assert msgs.contains(i); + } + } + + /** + * Tests https://jira.jboss.org/jira/browse/JGRP-1079 for unicast messages + * @throws ChannelNotConnectedException + * @throws ChannelClosedException + */ + public void testOOBUnicastMessageLoss() throws ChannelNotConnectedException, ChannelClosedException { + MyReceiver receiver=new MySleepingReceiver("C2", 1000); + c2.setReceiver(receiver); + + c1.getProtocolStack().getTransport().setOOBRejectionPolicy("discard"); + + final int NUM=10; + final Address dest=c2.getAddress(); + for(int i=1; i <= NUM; i++) { + Message msg=new Message(dest, null, i); + msg.setFlag(Message.OOB); + c1.send(msg); + } + + Collection msgs=receiver.getMsgs(); + + for(int i=0; i < 20; i++) { + if(msgs.size() == NUM) + break; + Util.sleep(1000); + // sendStableMessages(c1,c2); // not needed for unicasts ! + } + + assert msgs.size() == NUM : "expected " + NUM + " messages but got " + msgs.size() + ", msgs=" + Util.print(msgs); + for(int i=1; i <= NUM; i++) { + assert msgs.contains(i); + } + } + - private void send(final Address dest, final int num_msgs, final int num_threads, - final double oob_prob) throws Exception { - Channel sender; - boolean oob; - Message msg; + private void send(final Address dest, final int num_msgs, final int num_threads, final double oob_prob) throws Exception { if(num_threads <= 0) throw new IllegalArgumentException("number of threads <= 0"); @@ -210,12 +297,13 @@ public void run() { for(int j=0; j < msgs_per_thread; j++) { Channel sender=Util.tossWeightedCoin(0.5) ? c1 : c2; - boolean oob=Util.tossWeightedCoin(oob_prob); - Message msg=new Message(dest, null, counter.getAndIncrement()); + boolean oob=Util.tossWeightedCoin(oob_prob); + int num=counter.incrementAndGet(); + Message msg=new Message(dest, null, num); if(oob) - msg.setFlag(Message.OOB); + msg.setFlag(Message.OOB); try { - sender.send(msg); + sender.send(msg); } catch(Exception e) { e.printStackTrace(); @@ -233,9 +321,9 @@ for(int i=0; i < num_msgs; i++) { - sender=Util.tossWeightedCoin(0.5) ? c1 : c2; - oob=Util.tossWeightedCoin(oob_prob); - msg=new Message(dest, null, i); + Channel sender=Util.tossWeightedCoin(0.5) ? c1 : c2; + boolean oob=Util.tossWeightedCoin(oob_prob); + Message msg=new Message(dest, null, i); if(oob) msg.setFlag(Message.OOB); sender.send(msg); @@ -249,53 +337,63 @@ final int NUM=10; c2.setReceiver(receiver); - + System.out.println("[" + Thread.currentThread().getName() + "]: locking lock"); lock.lock(); - c1.send(new Message(dest, null, 1L)); - Util.sleep(1000); // this (regular) message needs to be received first - + c1.send(new Message(dest, null, 1)); for(int i=2; i <= NUM; i++) { - Message msg=new Message(dest, null, (long)i); + Message msg=new Message(dest, null, i); msg.setFlag(Message.OOB); c1.send(msg); } + sendStableMessages(c1, c2); + Util.sleep(500); - Util.sleep(500); // sleep some time to receive all messages - List list=receiver.getMsgs(); - for(int i=0; i < 10; i++) { - log.info("list = " + list); + List list=receiver.getMsgs(); + for(int i=0; i < 20; i++) { if(list.size() == NUM-1) break; + System.out.println("list = " + list); Util.sleep(1000); // give the asynchronous msgs some time to be received } System.out.println("list = " + list); assert list.size() == NUM-1 : "list is " + list; - assert list.contains(2L); + assert list.contains(2) && list.contains(10); + + System.out.println("[" + Thread.currentThread().getName() + "]: unlocking lock"); + lock.unlock(); - log.info("[" + Thread.currentThread().getName() + "]: unlocking lock"); - if(lock.isHeldByCurrentThread()) - lock.unlock(); - Util.sleep(10); + for(int i=0; i < 20; i++) { + if(list.size() == NUM) + break; + System.out.println("list = " + list); + Util.sleep(1000); // give the asynchronous msgs some time to be received + } - list=receiver.getMsgs(); System.out.println("list = " + list); assert list.size() == NUM : "list is " + list; - for(long i=1; i <= NUM; i++) + for(int i=1; i <= NUM; i++) assert list.contains(i); } - private void check(final int num_expected_msgs, List... lists) { - for(List list: lists) { + @SuppressWarnings("unchecked") + private void check(final int num_expected_msgs, Collection... lists) { + for(Collection list: lists) { log.info("list: " + list); } - for(List list: lists) { - assert list.size() == num_expected_msgs : "expected " + num_expected_msgs + " elements, but got " + - list.size() + " (list=" + list + ")"; - for(int i=0; i < num_expected_msgs; i++) - assert list.contains(i); + for(Collection list: lists) { + Collection missing=new TreeSet(); + if(list.size() != num_expected_msgs) { + for(int i=0; i < num_expected_msgs; i++) + missing.add(i); + + missing.removeAll(list); + assert list.size() == num_expected_msgs : "expected " + num_expected_msgs + " elements, but got " + + list.size() + " (list=" + list + "), missing=" + missing; + + } } } @@ -303,8 +401,8 @@ private static void setOOBPoolSize(JChannel... channels) { for(Channel channel: channels) { TP transport=channel.getProtocolStack().getTransport(); - transport.setOOBMinPoolSize(1); - transport.setOOBMaxPoolSize(2); + transport.setOOBThreadPoolMinThreads(1); + transport.setOOBThreadPoolMaxThreads(2); } } @@ -316,42 +414,66 @@ } } + private static void sendStableMessages(JChannel ... channels) { + for(JChannel ch: channels) { + STABLE stable=(STABLE)ch.getProtocolStack().findProtocol(STABLE.class); + if(stable != null) + stable.runMessageGarbageCollection(); + } + } + private static class BlockingReceiver extends ReceiverAdapter { final Lock lock; - final List msgs=Collections.synchronizedList(new LinkedList()); + final List msgs=Collections.synchronizedList(new LinkedList()); public BlockingReceiver(Lock lock) { this.lock=lock; } - public List getMsgs() { + public List getMsgs() { return msgs; } public void receive(Message msg) { - // System.out.println("[" + Thread.currentThread().getName() + "]: got " + (msg.isFlagSet(Message.OOB)? "OOB" : "regular") + " message " - // + "from " + msg.getSrc() + ": " + msg.getObject()); if(!msg.isFlagSet(Message.OOB)) { - // System.out.println("[" + Thread.currentThread().getName() + "]: acquiring lock"); lock.lock(); - // System.out.println("[" + Thread.currentThread().getName() + "]: acquired lock successfully"); lock.unlock(); } - msgs.add((Long)msg.getObject()); + msgs.add((Integer)msg.getObject()); } } private static class MyReceiver extends ReceiverAdapter { - private final List msgs=Collections.synchronizedList(new ArrayList()); + private final Collection msgs=new ConcurrentLinkedQueue(); + final String name; - public List getMsgs() { + public MyReceiver(String name) { + this.name=name; + } + + public Collection getMsgs() { return msgs; } public void receive(Message msg) { Integer val=(Integer)msg.getObject(); - msgs.add(val); + msgs.add(val); + } + } + + private static class MySleepingReceiver extends MyReceiver { + final long sleep_time; + + public MySleepingReceiver(String name, long sleep_time) { + super(name); + this.sleep_time=sleep_time; + } + + public void receive(Message msg) { + super.receive(msg); + System.out.println("-- received " + msg.getObject()); + Util.sleep(sleep_time); } } } diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/OverlappingMergeTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/OverlappingMergeTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/OverlappingMergeTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/OverlappingMergeTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,392 @@ +package org.jgroups.tests; + +import org.jgroups.*; +import org.jgroups.protocols.pbcast.GMS; +import org.jgroups.protocols.pbcast.NAKACK; +import org.jgroups.protocols.pbcast.STABLE; +import org.jgroups.stack.ProtocolStack; +import org.jgroups.util.Digest; +import org.jgroups.util.Tuple; +import org.jgroups.util.Util; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.*; + +/** + * Tests overlapping merges, e.g. A: {A,B}, B: {A,B} and C: {A,B,C}. Tests unicast as well as multicast seqno tables.
            + * Related JIRA: https://jira.jboss.org/jira/browse/JGRP-940 + * @author Bela Ban + */ +@Test(groups=Global.STACK_DEPENDENT,sequential=true) +public class OverlappingMergeTest extends ChannelTestBase { + protected JChannel a, b, c; + protected MyReceiver ra, rb, rc; + protected boolean multicast_transport; + + @BeforeMethod + protected void start() throws Exception { + a=createChannel(true, 3); + a.setName("A"); + ra=new MyReceiver("A", a); + a.setReceiver(ra); + + b=createChannel(a); + b.setName("B"); + rb=new MyReceiver("B", b); + b.setReceiver(rb); + + c=createChannel(a); + c.setName("C"); + rc=new MyReceiver("C", c); + c.setReceiver(rc); + modifyConfigs(a, b, c); + + a.connect("OverlappingMergeTest"); + b.connect("OverlappingMergeTest"); + c.connect("OverlappingMergeTest"); + View view=c.getView(); + assert view.size() == 3 : "view is " + view; + + multicast_transport=isMulticastTransport(a); + } + + @AfterMethod + protected void stop() throws Exception { + Util.close(c,b,a); + ra.clear(); rb.clear(); rc.clear(); + } + + public void testRegularMessageSending() throws Exception { + sendMessages(5, a, b, c); + checkReceivedMessages(make(ra, 15), make(rb,15), make(rc,15)); + } + + /** + * Verifies that unicasts are received correctly by all participants after an overlapping merge. The following steps + * are executed: + *
              + *
            1. Group is {A,B,C}, A is the coordinator + *
            2. MERGE2 is removed from all members + *
            3. VERIFY_SUSPECT is removed from all members + *
            4. Everyone sends 5 unicast messages to everyone else + *
            5. Everyone sends 5 multicasts + *
            6. A SUSPECT(A) event is injected into B's stack (GMS). This causes a new view {B,C} to be multicast by B + *
            7. B and C install {B,C} + *
            8. B and C trash the connection table for A in UNICAST + *
            9. A ignores the view, it still has view {A,B,C} and all connection tables intact in UNICAST + *
            10. We now inject a MERGE(A,B) event into A. This should ause A and B as coords to create a new MergeView {A,B,C} + *
            11. The merge already fails because the unicast between A and B fails due to the reason given below ! + * Once this is fixed, the next step below should work, too ! + *
            12. A sends a unicast to B and C. This should fail until JGRP-940 has been fixed ! + *
            13. Reason: B and C trashed A's conntables in UNICAST, but A didn't trash its conn tables for B and C, so + * we have non-matching seqnos ! + *
            + */ + public void testOverlappingMergeWithBC() throws Exception { + sendMessages(5, a, b, c); + checkReceivedMessages(make(ra, 15), make(rb,15), make(rc,15)); + + // Inject view {B,C} into B and C: + View new_view=Util.createView(b.getAddress(), 10, b.getAddress(), c.getAddress()); + System.out.println("\n ==== Injecting view " + new_view + " into B and C ===="); + injectView(new_view, b, c); + makeCoordinator(b); + assert Util.isCoordinator(a); + assert Util.isCoordinator(b); + assert !Util.isCoordinator(c); + + System.out.println("A's view: " + a.getView()); + System.out.println("B's view: " + b.getView()); + System.out.println("C's view: " + c.getView()); + assert a.getView().size() == 3 : "A's view is " + a.getView(); + assert b.getView().size() == 2 : "B's view is " + b.getView(); + assert c.getView().size() == 2 : "C's view is " + c.getView(); + + System.out.println("\n==== Sending messages while the cluster is partitioned ===="); + sendMessages(5, a, b, c); + if(multicast_transport) { + // B and C drop A's multicasts, but A will receive B's and C's multicasts + checkReceivedMessages(make(ra, 15), make(rb,10), make(rc,10)); + } + else { + // B and C drop A's multicasts, and won't send their multicasts to A (A only receives its owm multicasts) + checkReceivedMessages(make(ra, 5), make(rb,10), make(rc,10)); + } + + System.out.println("\n ==== Digests are:\n" + dumpDigests(a,b,c)); + + // start merging + Map views=new HashMap(); + views.put(a.getAddress(), a.getView()); + views.put(b.getAddress(), b.getView()); + views.put(c.getAddress(), c.getView()); + Event merge_evt=new Event(Event.MERGE, views); + JChannel merge_leader=determineMergeLeader(a, b); + System.out.println("\n==== Injecting a merge event (leader=" + merge_leader.getAddress() + ") ===="); + injectMergeEvent(merge_evt, merge_leader); + + System.out.println("\n==== checking views after merge ====:"); + for(int i=0; i < 10; i++) { + if(a.getView().size() == 3 && b.getView().size() == 3 && c.getView().size() == 3) { + System.out.println("views are correct: all views have a size of 3"); + break; + } + System.out.print("."); + runStableProtocol(a); runStableProtocol(b); runStableProtocol(c); + Util.sleep(1000); + } + + System.out.println("\n ==== Digests after the merge:\n" + dumpDigests(a,b,c)); + + View va=a.getView(), vb=b.getView(), vc=c.getView(); + System.out.println("\nA's view: " + va); + System.out.println("B's view: " + vb); + System.out.println("C's view: " + vc); + assert va.size() == 3 : "A's view is " + va; + assert vb.size() == 3 : "B's view is " + vb; + assert vc.size() == 3 : "C's view is " + vc; + + System.out.println("\n==== Sending messages after merge ===="); + sendMessages(5, a, b, c); + checkReceivedMessages(make(ra, 15), make(rb,15), make(rc,15)); + } + + + /** + * Verifies that unicasts are received correctly by all participants after an overlapping merge. The following steps + * are executed: + *
              + *
            1. Group is {A,B,C} + *
            2. Install view {A,C} in A and {A,B,C} in B and C + *
            3. Try to initiate a merge. This should FAIL until https://jira.jboss.org/jira/browse/JGRP-937 has + * been implemented: B and C's MERGE2 protocols will never send out merge requests as they see A as coord + *
            + */ + @Test(enabled=true) + public void testOverlappingMergeWithABC() throws Exception { + sendMessages(5, a, b, c); + checkReceivedMessages(make(ra, 15), make(rb,15), make(rc,15)); + + // Inject view {A,C} into A: + View new_view=Util.createView(a.getAddress(), 4, a.getAddress(), c.getAddress()); + System.out.println("\n ==== Injecting view " + new_view + " into A ===="); + injectView(new_view, a); + assertTrue(Util.isCoordinator(a)); + assertFalse(Util.isCoordinator(b)); + assertFalse(Util.isCoordinator(c)); + + System.out.println("A's view: " + a.getView()); + System.out.println("B's view: " + b.getView()); + System.out.println("C's view: " + c.getView()); + assertEquals("A's view is " + a.getView(), 2, a.getView().size()); + assertEquals("B's view is " + b.getView(), 3, b.getView().size()); + assertEquals("C's view is " + c.getView(), 3, c.getView().size()); + + + // start merging + Map views=new HashMap(); + views.put(a.getAddress(), a.getView()); + views.put(b.getAddress(), b.getView()); + views.put(c.getAddress(), c.getView()); + Event merge_evt=new Event(Event.MERGE, views); + System.out.println("\n==== Injecting a merge event (leader=" + a.getAddress() + ") ===="); + injectMergeEvent(merge_evt, a); + + System.out.println("\n==== checking views after merge ====:"); + for(int i=0; i < 10; i++) { + if(a.getView().size() == 3 && b.getView().size() == 3 && c.getView().size() == 3) { + System.out.println("views are correct: all views have a size of 3"); + break; + } + System.out.print("."); + for(JChannel ch: new JChannel[]{a,b,c}) + runStableProtocol(ch); + Util.sleep(1000); + } + + System.out.println("\n ==== Digests after the merge:\n" + dumpDigests(a,b,c)); + + View va=a.getView(), vb=b.getView(), vc=c.getView(); + System.out.println("\nA's view: " + va); + System.out.println("B's view: " + vb); + System.out.println("C's view: " + vc); + assertEquals("A's view is " + va, 3, va.size()); + assertEquals("B's view is " + vb, 3, vb.size()); + assertEquals("C's view is " + vc, 3, vc.size()); + + System.out.println("\n==== Sending messages after merge ===="); + sendMessages(5, a, b, c); + checkReceivedMessages(make(ra, 15), make(rb,15), make(rc,15)); + } + + + private static void makeCoordinator(JChannel ch) { + GMS gms=(GMS)ch.getProtocolStack().findProtocol(GMS.class); + gms.becomeCoordinator(); + } + + + private static String dumpDigests(JChannel ... channels) { + StringBuilder sb=new StringBuilder(); + for(JChannel ch: channels) { + sb.append(ch.getAddress()).append(": "); + NAKACK nakack=(NAKACK)ch.getProtocolStack().findProtocol(NAKACK.class); + Digest digest=nakack.getDigest(); + sb.append(digest).append("\n"); + } + return sb.toString(); + } + + private static JChannel determineMergeLeader(JChannel ... coords) { + Membership tmp=new Membership(); + for(JChannel ch: coords) { + tmp.add(ch.getAddress()); + } + tmp.sort(); + Address merge_leader=tmp.elementAt(0); + for(JChannel ch: coords) { + if(ch.getAddress().equals(merge_leader)) + return ch; + } + return null; + } + + private static void injectView(View view, JChannel ... channels) { + for(JChannel ch: channels) { + GMS gms=(GMS)ch.getProtocolStack().findProtocol(GMS.class); + gms.installView(view); + } + for(JChannel ch: channels) { + MyReceiver receiver=(MyReceiver)ch.getReceiver(); + System.out.println("[" + receiver.name + "] view=" + ch.getView()); + } + } + + + private static void injectMergeEvent(Event evt, JChannel ... channels) { + for(JChannel ch: channels) { + GMS gms=(GMS)ch.getProtocolStack().findProtocol(GMS.class); + gms.up(evt); + } + } + + + private void sendMessages(int num_msgs, JChannel... channels) throws Exception { + ra.clear(); rb.clear(); rc.clear(); + for(JChannel ch: channels) { + for(int i=1; i <= num_msgs; i++) + ch.send(null, null, "#" + i); + } + } + + private static void runStableProtocol(JChannel ch) { + STABLE stable=(STABLE)ch.getProtocolStack().findProtocol(STABLE.class); + if(stable != null) + stable.runMessageGarbageCollection(); + } + + protected boolean isMulticastTransport(JChannel ch) { + return ch.getProtocolStack().getTransport().supportsMulticasting(); + } + + + + protected void checkReceivedMessages(Tuple ... expected_messages) { + for(int i=0; i < 30; i++) { + boolean all_received=true; + for(Tuple tuple: expected_messages) { + MyReceiver receiver=tuple.getVal1(); + List mcasts=receiver.getMulticasts(); + int mcasts_received=mcasts.size(); + int expected_mcasts=tuple.getVal2(); + if(mcasts_received != expected_mcasts) { + all_received=false; + break; + } + runStableProtocol(receiver.ch); + } + if(all_received) + break; + Util.sleep(500); + } + + for(Tuple tuple: expected_messages) { + MyReceiver receiver=tuple.getVal1(); + List mcasts=receiver.getMulticasts(); + int mcasts_received=mcasts.size(); + System.out.println("receiver " + receiver + ": mcasts=" + mcasts_received); + } + + for(Tuple tuple: expected_messages) { + MyReceiver receiver=tuple.getVal1(); + List mcasts=receiver.getMulticasts(); + int mcasts_received=mcasts.size(); + int expected_mcasts=tuple.getVal2(); + assert mcasts_received == expected_mcasts : "(" + receiver.name + ") num_mcasts=" + print(mcasts) + + " expected: " + expected_mcasts + ")"; + } + } + + protected Tuple make(MyReceiver r, int expected_msgs) { + return new Tuple(r, expected_msgs); + } + + + private static String print(List msgs) { + StringBuilder sb=new StringBuilder(); + for(Message msg: msgs) { + sb.append(msg.getSrc()).append(": ").append(msg.getObject()).append(" "); + } + return sb.toString(); + } + + + private static void modifyConfigs(JChannel ... channels) throws Exception { + for(JChannel ch: channels) { + ProtocolStack stack=ch.getProtocolStack(); + stack.removeProtocol("MERGE2"); + stack.removeProtocol("FC"); + stack.removeProtocol("VERIFY_SUSPECT"); + NAKACK nak=(NAKACK)stack.findProtocol(NAKACK.class); + if(nak != null) + nak.setLogDiscardMessages(false); + } + } + + + + protected static class MyReceiver extends ReceiverAdapter { + final String name; + View view=null; + final JChannel ch; + final List mcasts=new ArrayList(20); + + public MyReceiver(String name, JChannel ch) { + this.name=name; + this.ch=ch; + } + + public void receive(Message msg) { + Address dest=msg.getDest(); + if(dest == null) + mcasts.add(msg); + } + + public void viewAccepted(View new_view) { + view=new_view; + } + + public List getMulticasts() { return mcasts; } + public void clear() {mcasts.clear();} + public Address getAddress() {return ch != null? ch.getAddress() : null;} + + public String toString() { + return name; + } + } + + + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/OverlappingUnicastMergeTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/OverlappingUnicastMergeTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/OverlappingUnicastMergeTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/OverlappingUnicastMergeTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,213 @@ +package org.jgroups.tests; + +import org.jgroups.*; +import org.jgroups.stack.ProtocolStack; +import org.jgroups.util.Util; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Tests overlapping merges, e.g. A: {A,B}, B: {A,B} and C: {A,B,C}. Tests unicast tables
            + * Related JIRA: https://jira.jboss.org/jira/browse/JGRP-940 + * @author Bela Ban + */ +@Test(groups=Global.STACK_DEPENDENT,sequential=true) +public class OverlappingUnicastMergeTest extends ChannelTestBase { + private JChannel a, b, c; + private MyReceiver ra, rb, rc; + + @BeforeMethod + void start() throws Exception { + ra=new MyReceiver("A"); rb=new MyReceiver("B"); rc=new MyReceiver("C"); + a=createChannel(true, 3); + a.setReceiver(ra); + + b=createChannel(a); + b.setReceiver(rb); + + c=createChannel(a); + c.setReceiver(rc); + + modifyConfigs(a, b, c); + + a.connect("OverlappingUnicastMergeTest"); + b.connect("OverlappingUnicastMergeTest"); + c.connect("OverlappingUnicastMergeTest"); + + View view=c.getView(); + assertEquals("view is " + view, 3, view.size()); + } + + @AfterMethod + void tearDown() throws Exception { + Util.close(c,b,a); + } + + + public void testWithAllViewsInSync() throws Exception { + sendAndCheckMessages(5, a, b, c); + } + + /** + * Verifies that unicasts are received correctly by all participants after an overlapping merge. The following steps + * are executed: + *
              + *
            1. Group is {A,B,C}, A is the coordinator + *
            2. MERGE2 is removed from all members + *
            3. VERIFY_SUSPECT is removed from all members + *
            4. Everyone sends 5 unicast messages to everyone else + *
            5. A VIEW(B,C) is injected into B and C + *
            6. B and C install {B,C} + *
            7. B and C trash the connection table for A in UNICAST + *
            8. A still has view {A,B,C} and all connection tables intact in UNICAST + *
            9. We now send N unicasts from everyone to everyone else, all the unicasts should be received. + *
            + */ + public void testWithViewBC() throws Exception { + + System.out.println("A's view: " + a.getView()); + + // Inject view {B,C} into B and C: + View new_view=Util.createView(b.getAddress(), 10, b.getAddress(), c.getAddress()); + injectView(new_view, b, c); + assertEquals("A's view is " + a.getView(), 3, a.getView().size()); + assertEquals("B's view is " + b.getView(), 2, b.getView().size()); + assertEquals("C's view is " + c.getView(), 2, c.getView().size()); + sendAndCheckMessages(5, a, b, c); + } + + public void testWithViewA() throws Exception { + // Inject view {A} into A, B and C: + View new_view=Util.createView(a.getAddress(), 10, a.getAddress()); + injectView(new_view, a, b, c); + sendAndCheckMessages(5, a, b, c); + } + + public void testWithViewC() throws Exception { + // Inject view {A} into A, B and C: + View new_view=Util.createView(c.getAddress(), 10, c.getAddress()); + injectView(new_view, a, b, c); + sendAndCheckMessages(5, a, b, c); + } + + public void testWithEveryoneHavingASingletonView() throws Exception { + // Inject view {A} into A, B and C: + injectView(Util.createView(a.getAddress(), 10, a.getAddress()), a); + injectView(Util.createView(b.getAddress(), 10, b.getAddress()), b); + injectView(Util.createView(c.getAddress(), 10, c.getAddress()), c); + sendAndCheckMessages(5, a, b, c); + } + + + private static void injectView(View view, JChannel ... channels) { + for(JChannel ch: channels) { + ch.down(new Event(Event.VIEW_CHANGE, view)); + ch.up(new Event(Event.VIEW_CHANGE, view)); + } + for(JChannel ch: channels) { + MyReceiver receiver=(MyReceiver)ch.getReceiver(); + System.out.println("[" + receiver.name + "] view=" + ch.getView()); + } + } + + + private void sendAndCheckMessages(int num_msgs, JChannel ... channels) throws Exception { + ra.clear(); rb.clear(); rc.clear(); + // 1. send unicast messages + Set
            mbrs=new HashSet
            (channels.length); + for(JChannel ch: channels) + mbrs.add(ch.getAddress()); + + for(JChannel ch: channels) { + Address addr=ch.getAddress(); + for(Address dest: mbrs) { + for(int i=1; i <= num_msgs; i++) { + ch.send(dest, null, "unicast msg #" + i + " from " + addr); + } + } + } + int total_msgs=num_msgs * channels.length; + MyReceiver[] receivers=new MyReceiver[channels.length]; + for(int i=0; i < channels.length; i++) + receivers[i]=(MyReceiver)channels[i].getReceiver(); + checkReceivedMessages(total_msgs, receivers); + } + + private static void checkReceivedMessages(int num_ucasts, MyReceiver ... receivers) { + for(int i=0; i < 20; i++) { + boolean all_received=true; + for(MyReceiver receiver: receivers) { + List ucasts=receiver.getUnicasts(); + int ucasts_received=ucasts.size(); + if(num_ucasts != ucasts_received) { + all_received=false; + break; + } + } + if(all_received) + break; + Util.sleep(500); + } + for(MyReceiver receiver: receivers) { + List ucasts=receiver.getUnicasts(); + int ucasts_received=ucasts.size(); + System.out.println("receiver " + receiver + ": ucasts=" + ucasts_received); + assertEquals("ucasts for " + receiver + ": " + print(ucasts), num_ucasts, ucasts_received); + } + } + + public static String print(List list) { + StringBuilder sb=new StringBuilder(); + for(Message msg: list) { + sb.append(msg.getSrc()).append(": ").append(msg.getObject()).append(" "); + } + return sb.toString(); + } + + private static void modifyConfigs(JChannel ... channels) throws Exception { + for(JChannel ch: channels) { + ProtocolStack stack=ch.getProtocolStack(); + stack.removeProtocol("MERGE2"); + stack.removeProtocol("VERIFY_SUSPECT"); + stack.removeProtocol("FC"); + } + } + + + + private static class MyReceiver extends ReceiverAdapter { + final String name; + final List ucasts=new ArrayList(20); + + public MyReceiver(String name) { + this.name=name; + } + + public void receive(Message msg) { + Address dest=msg.getDest(); + boolean mcast=dest == null; + if(!mcast) + ucasts.add(msg); + } + + public void viewAccepted(View new_view) { + // System.out.println("[" + name + "] " + new_view); + } + + public List getUnicasts() { return ucasts; } + public void clear() {ucasts.clear();} + + public String toString() { + return name; + } + } + + + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/PrioTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/PrioTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/PrioTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/PrioTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,160 @@ +package org.jgroups.tests; + +import org.jgroups.Global; +import org.jgroups.JChannel; +import org.jgroups.Message; +import org.jgroups.ReceiverAdapter; +import org.jgroups.conf.ClassConfigurator; +import org.jgroups.protocols.PRIO; +import org.jgroups.protocols.PrioHeader; +import org.jgroups.protocols.pbcast.NAKACK; +import org.jgroups.stack.ProtocolStack; +import org.jgroups.util.Util; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.CyclicBarrier; + +/** + * @author Bela Ban + */ +@Test(groups=Global.STACK_DEPENDENT,sequential=false) +public class PrioTest extends ChannelTestBase { + protected JChannel c1, c2; + protected PrioReceiver r1, r2; + protected static final short PRIO_ID=ClassConfigurator.getProtocolId(PRIO.class); + + @BeforeTest void init() throws Exception { + c1=createChannel(true, 2, "A"); + c1.getProtocolStack().insertProtocol(new PRIO(), ProtocolStack.ABOVE, NAKACK.class); + c2=createChannel(c1, "B"); + c1.connect("PrioTest"); + r1=new PrioReceiver(); + c1.setReceiver(r1); + c2.connect("PrioTest"); + r2=new PrioReceiver(); + c2.setReceiver(r2); + assert c2.getView().size() == 2; + } + + @AfterTest void destroy() { + Util.close(c2, c1); + } + + + public void testPrioritizedMessages() throws Exception { + byte[] prios={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30}; + PrioSender[] senders=new PrioSender[prios.length]; + final CyclicBarrier barrier=new CyclicBarrier(prios.length +1); + for(int i=0; i < prios.length; i++) { + senders[i]=new PrioSender(c1, prios[i], barrier); + senders[i].start(); + } + Util.sleep(500); + barrier.await(); // starts the senders + + for(PrioSender sender: senders) + sender.join(); + + List list1=r1.getMsgs(), list2=r2.getMsgs(); + for(int i=0; i < 20; i++) { + if(list1.size() == prios.length && list2.size() == prios.length) + break; + Util.sleep(1000); + } + + System.out.println("R1: " + Util.print(list1) + "\nR2: " + Util.print(list2)); + assert list1.size() == prios.length; + assert list2.size() == prios.length; + + // Mike: how can we test this ? It seems this is not deterministic... which is fine, I guess, but hard to test ! + checkOrdering(list1, list2); + } + + /** + * Verifies that the messages are predominantly in prioritized order. + * The latter means that messages with higher prio should be mostly delivered before messages with lower prio. + * 'Mostly' is needed because we cannot guarantee that all messages with higher prio are delivered before messages + * with lower prio. + * @param list1 + * @param list2 + */ + protected void checkOrdering(List list1, List list2) { + // check that the left half of the messages have a higher prio than the right half + System.out.print("checking the ordering of list1: "); + _check(list1); + + System.out.print("checking the ordering of list2: "); + _check(list2); + } + + protected void _check(List list) { + int middle=list.size() / 2; + + int sum=0; + for(int num: list) + sum+=num; + + double median_val=sum / list.size(); + + + // make sure the values [0 .. middle] have smaller values than list[middle] + int correct=0; + for(int i=0; i <= middle; i++) { + if(list.get(i) <= median_val) + correct++; + } + + // make sure the values [middle+1 .. list.size() -1] have bigger values than list[middle+1] + for(int i=middle+1; i < list.size() -1; i++) { + if(list.get(i) >= median_val) + correct++; + } + + double correct_percentage=correct / (double)list.size(); + System.out.println("OK. The percentage of correct values is " + (correct_percentage * 100) + "%"); + assert correct_percentage >= 0.7 : "FAIL. The percentage of correct values is " + (correct_percentage * 100) + "%"; + } + + + protected static class PrioSender extends Thread { + protected final JChannel ch; + protected final byte prio; + protected final CyclicBarrier barrier; + + public PrioSender(JChannel ch, byte prio, CyclicBarrier barrier) { + this.ch=ch; + this.prio=prio; + this.barrier=barrier; + } + + public void run() { + Message msg=new Message(null, null, new Integer(prio)); + PrioHeader hdr=new PrioHeader(prio); + msg.putHeader(PRIO_ID, hdr); + try { + barrier.await(); + ch.send(msg); + } + catch(Exception e) { + e.printStackTrace(); + } + } + } + + + protected static class PrioReceiver extends ReceiverAdapter { + protected final List msgs=new LinkedList(); + + public List getMsgs() { + return msgs; + } + + public void receive(Message msg) { + msgs.add((Integer)msg.getObject()); + } + } +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ReconciliationTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ReconciliationTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ReconciliationTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ReconciliationTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,6 +2,7 @@ import org.jgroups.*; import org.jgroups.protocols.DISCARD; +import org.jgroups.protocols.pbcast.NAKACK; import org.jgroups.stack.ProtocolStack; import org.jgroups.util.Util; import org.testng.Assert; @@ -9,17 +10,11 @@ import org.testng.annotations.Test; import java.io.*; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** - * Tests the FLUSH protocol, requires flush-udp.xml in ./conf to be present and - * configured to use FLUSH - * + * Various tests for the FLUSH protocol * @author Bela Ban - * @version $Id: ReconciliationTest.java,v 1.19 2008/11/26 00:22:04 vlada Exp $ */ @Test(groups=Global.FLUSH,sequential=true) public class ReconciliationTest extends ChannelTestBase { @@ -89,7 +84,7 @@ FlushTrigger t=new FlushTrigger() { public void triggerFlush() { JChannel channel=channels.get(0); - boolean rc=channel.startFlush(false); + boolean rc=Util.startFlush(channel); log.info("manual flush success=" + rc); channel.stopFlush(); }; @@ -116,14 +111,30 @@ FlushTrigger t=new FlushTrigger() { public void triggerFlush() { JChannel channel=channels.remove(channels.size() - 1); - channel.shutdown(); + try { + Util.shutdown(channel); + } + catch(Exception e) { + log.error("failed shutting down the channel", e); + } }; }; String apps[]={"A", "B", "C"}; reconciliationHelper(apps, t); } - private void reconciliationHelper(String[] names, FlushTrigger ft) throws Exception { + + /** + * Tests reconciliation. Creates N channels, based on 'names'. Say we have A, B and C. Then we have the second but + * last node (B) discard all messages from the last node (C). Then the last node (C) multicasts 5 messages. We check + * that the 5 messages have been received correctly by all nodes but the second-but-last node (B). Then we remove + * DISCARD from B and trigger a manual flush. After the flush, B should also have received the 5 messages sent + * by C. + * @param names + * @param ft + * @throws Exception + */ + protected void reconciliationHelper(String[] names, FlushTrigger ft) throws Exception { // create channels and setup receivers int channelCount=names.length; @@ -131,10 +142,12 @@ receivers=new ArrayList(names.length); for(int i=0;i < channelCount;i++) { JChannel channel; - if(i == 0) - channel=createChannel(true, names.length+2); + if(i == 0) { + channel=createChannel(true, names.length+2, names[i]); + modifyNAKACK(channel); + } else - channel=createChannel(channels.get(0)); + channel=createChannel(channels.get(0), names[i]); MyReceiver r=new MyReceiver(channel, names[i]); receivers.add(r); channels.add(channel); @@ -142,25 +155,27 @@ channel.connect("ReconciliationTest"); Util.sleep(250); } + + View view=channels.get(channels.size() -1).getView(); + System.out.println("view: " + view); + assert view.size() == channels.size(); + JChannel last=channels.get(channels.size() - 1); JChannel nextToLast=channels.get(channels.size() - 2); - insertDISCARD(nextToLast, last.getLocalAddress()); + System.out.println(nextToLast.getAddress() + " is now discarding messages from " + last.getAddress()); + insertDISCARD(nextToLast, last.getAddress()); String lastsName=names[names.length - 1]; String nextToLastName=names[names.length - 2]; printDigests(channels, "\nDigests before " + lastsName + " sends any messages:"); // now last sends 5 messages: - log.info("\n" + lastsName - + " sending 5 messages;" - + nextToLastName - + " will ignore them, but others will receive them"); - for(int i=1;i <= 5;i++) { + System.out.println("\n" + lastsName + " sending 5 messages; " + nextToLastName + " will ignore them, but others will receive them"); + for(int i=1;i <= 5;i++) last.send(null, null, new Integer(i)); - } - Util.sleep(1000); // until al messages have been received, this is - // asynchronous so we need to wait a bit + + Util.sleep(1000); // until al messages have been received, this is asynchronous so we need to wait a bit printDigests(channels, "\nDigests after " + lastsName + " sent messages:"); @@ -170,15 +185,15 @@ // check last (must have received its own messages) Map> map=lastReceiver.getMsgs(); Assert.assertEquals(map.size(), 1, "we should have only 1 sender, namely C at this time"); - List list=map.get(last.getLocalAddress()); - log.info(lastsName + ": messages received from " + lastsName + ",list=" + list); + List list=map.get(last.getAddress()); + System.out.println("\n" + lastsName + ": messages received from " + lastsName + ": " + list); Assert.assertEquals(list.size(), 5, "correct msgs: " + list); // check nextToLast (should have received none of last messages) map=nextToLastReceiver.getMsgs(); Assert.assertEquals(map.size(), 0, "we should have no sender at this time"); - list=map.get(last.getLocalAddress()); - log.info(nextToLastName + ": messages received from " + lastsName + " : " + list); + list=map.get(last.getAddress()); + System.out.println(nextToLastName + ": messages received from " + lastsName + ": " + list); assert list == null; List otherReceivers=receivers.subList(0, receivers.size() - 2); @@ -187,40 +202,48 @@ for(MyReceiver receiver:otherReceivers) { map=receiver.getMsgs(); Assert.assertEquals(map.size(), 1, "we should have only 1 sender"); - list=map.get(last.getLocalAddress()); - log.info(receiver.name + " messages received from " + lastsName + ":" + list); + list=map.get(last.getAddress()); + System.out.println(receiver.name + ": messages received from " + lastsName + ": " + list); Assert.assertEquals(list.size(), 5, "correct msgs" + list); } removeDISCARD(nextToLast); - Address address=last.getLocalAddress(); + Address address=last.getAddress(); ft.triggerFlush(); - int cnt=1000; + int cnt=20; View v; while((v=channels.get(0).getView()) != null && cnt > 0) { cnt--; if(v.size() == channels.size()) break; - Util.sleep(500); + Util.sleep(1000); } - - printDigests(channels, ""); + assert channels.get(0).getView().size() == channels.size(); + printDigests(channels, "\nDigests after reconciliation (B should have received the 5 messages from B now):"); // check that member with discard (should have received all missing // messages map=nextToLastReceiver.getMsgs(); Assert.assertEquals(map.size(), 1, "we should have 1 sender at this time"); list=map.get(address); - log.info(nextToLastName + ": messages received from " + lastsName + " : " + list); + System.out.println("\n" + nextToLastName + ": messages received from " + lastsName + " : " + list); Assert.assertEquals(5, list.size()); } - private void printDigests(List channels, String message) { - log.info(message); + /** Sets discard_delivered_msgs to false */ + protected void modifyNAKACK(JChannel ch) { + if(ch == null) return; + NAKACK nakack=(NAKACK)ch.getProtocolStack().findProtocol(NAKACK.class); + if(nakack != null) + nakack.setDiscardDeliveredMsgs(false); + } + + private static void printDigests(List channels, String message) { + System.out.println(message); for(JChannel channel:channels) { - log.info(channel.downcall(Event.GET_DIGEST_EVT)); + System.out.println("[" + channel.getAddress() + "] " + channel.downcall(Event.GET_DIGEST_EVT).toString()); } } @@ -241,7 +264,7 @@ void triggerFlush(); } - private static class MyReceiver extends ExtendedReceiverAdapter { + private class MyReceiver extends ExtendedReceiverAdapter { Map> msgs=new HashMap>(10); Channel channel; @@ -268,20 +291,17 @@ msgs.put(msg.getSrc(), list); } list.add((Integer)msg.getObject()); - System.out.println("[" + name + log.debug("[" + name + " / " - + channel.getLocalAddress() + + channel.getAddress() + "]: received message from " + msg.getSrc() + ": " + msg.getObject()); } - - public void viewAccepted(View new_view) { - System.out.println("[" + name + " / " + channel.getLocalAddress() + "]: " + new_view); - } } + // @Test(invocationCount=10) public void testVirtualSynchrony() throws Exception { JChannel c1 = createChannel(true,2); Cache cache_1 = new Cache(c1, "cache-1"); @@ -296,40 +316,38 @@ flush(c1, 5000); // flush all pending message out of the system so // everyone receives them - for(int i = 1;i <= 20;i++){ - if(i % 2 == 0){ - cache_1.put("key-" + i, Boolean.TRUE); // even numbers - }else{ - cache_2.put("key-" + i, Boolean.TRUE); // odd numbers - } + for(int i = 1;i <= 20;i++) { + if(i % 2 == 0) + cache_1.put(i, true); // even numbers + else + cache_2.put(i, true); // odd numbers } flush(c1, 5000); System.out.println("cache_1 (" + cache_1.size() - + " elements): " - + cache_1 - + "\ncache_2 (" - + cache_2.size() - + " elements): " - + cache_2); - Assert.assertEquals(cache_1.size(), cache_2.size()); - Assert.assertEquals(20, cache_1.size()); - - Util.close(c1,c2); - + + " elements): " + + cache_1 + + "\ncache_2 (" + + cache_2.size() + + " elements): " + + cache_2); + Assert.assertEquals(cache_1.size(), cache_2.size(), "cache 1: " + cache_1 + "\ncache 2: " + cache_2); + Assert.assertEquals(20, cache_1.size(), "cache 1: " + cache_1 + "\ncache 2: " + cache_2); + Util.close(c2,c1); } - private static void flush(Channel channel, long timeout) { + private void flush(Channel channel, long timeout) { if(channel.flushSupported()) { - boolean success=channel.startFlush(true); - System.out.println("startFlush(): " + success); + boolean success=Util.startFlush(channel); + channel.stopFlush(); + log.debug("startFlush(): " + success); assertTrue(success); } else Util.sleep(timeout); } - private static class Cache extends ExtendedReceiverAdapter { + private class Cache extends ExtendedReceiverAdapter { protected final Map data; Channel ch; @@ -393,6 +411,7 @@ return getState(); } + @SuppressWarnings("unchecked") public void setState(byte[] state) { Map m; try { @@ -437,6 +456,7 @@ getState(ostream); } + @SuppressWarnings("unchecked") public void setState(InputStream istream) { ObjectInputStream ois=null; try { @@ -477,14 +497,13 @@ public String toString() { synchronized(data) { - return data.toString(); + TreeMap map=new TreeMap(data); + return map.keySet().toString(); } } private void log(String msg) { - System.out.println("-- [" + name + "] " + msg); + log.debug("-- [" + name + "] " + msg); } - } - -} +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ReplicatedHashMapStartupTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ReplicatedHashMapStartupTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ReplicatedHashMapStartupTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ReplicatedHashMapStartupTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -20,7 +20,6 @@ * Tests concurrent startup or replicated hashmap. * * @author vlada - * @version $Id: ReplicatedHashMapStartupTest.java,v 1.6 2008/06/09 13:03:06 * belaban Exp $ */ @Test(groups= { Global.FLUSH }, sequential=true) @@ -69,7 +68,6 @@ else { c=createChannel(first); } - c.setOpt(Channel.AUTO_RECONNECT, true); ReplicatedHashMap map=new ReplicatedHashMap(c); channels.add(map); map.addNotifier(n); @@ -80,7 +78,7 @@ for(ReplicatedHashMap map:channels) { map.getChannel().connect("ReplicatedHashMapStartupTest"); map.start(0); - map.put(map.getChannel().getLocalAddress(), new Integer(1)); + map.put(map.getChannel().getAddress(), new Integer(1)); Util.sleep(100); } diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ReplicatedHashMapTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ReplicatedHashMapTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ReplicatedHashMapTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ReplicatedHashMapTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -17,7 +17,6 @@ * Test methods for ReplicatedHashMap * * @author Bela Ban - * @version $Id: ReplicatedHashMapTest.java,v 1.3 2007/08/22 11:23:28 belaban * Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=true) diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/SCOPE_Test.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/SCOPE_Test.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/SCOPE_Test.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/SCOPE_Test.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,223 @@ +package org.jgroups.tests; + + +import org.jgroups.*; +import org.jgroups.protocols.SCOPE; +import org.jgroups.protocols.UNICAST; +import org.jgroups.protocols.UNICAST2; +import org.jgroups.stack.Protocol; +import org.jgroups.stack.ProtocolStack; +import org.jgroups.util.Util; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Tests the SCOPE protocol (https://jira.jboss.org/jira/browse/JGRP-822) + * @author Bela Ban + */ +@Test(groups=Global.STACK_DEPENDENT,sequential=true) +public class SCOPE_Test extends ChannelTestBase { + JChannel c1, c2; + static final int NUM_MSGS=5; + static final long SLEEP_TIME=1000L; + + @BeforeMethod + void setUp() throws Exception { + c1=createChannel(true, 2); c1.setName("A"); + c2=createChannel(c1); c2.setName("B"); + } + + @AfterMethod + void tearDown() throws Exception { + Util.close(c2, c1); + } + + + public void testRegularMulticastMessages() throws Exception { + sendMessages(null, false); + } + + public void testScopedMulticastMessages() throws Exception { + sendMessages(null, true); + } + + public void testRegularUnicastMessages() throws Exception { + sendMessages(c2.getAddress(), false); + } + + public void testScopedUnicastMessages() throws Exception { + sendMessages(c2.getAddress(), true); + } + + public void testOrderWithScopedMulticasts() throws Exception { + ProtocolStack stack=c2.getProtocolStack(); + Protocol neighbor=stack.findProtocol(UNICAST.class, UNICAST2.class); + SCOPE scope=new SCOPE(); + stack.insertProtocolInStack(scope, neighbor, ProtocolStack.ABOVE); + scope.init(); + + c1.connect("SCOPE_Test"); + c2.connect("SCOPE_Test"); + assert c2.getView().size() == 2 : "c2.view is " + c2.getView(); + + MyScopedReceiver receiver=new MyScopedReceiver(); + c2.setReceiver(receiver); + Short[] scopes=new Short[]{'X', 'Y', 'Z'}; + + for(short scope_id: scopes) { + for(long i=1; i <=5; i++) { + Message msg=new Message(null, null, i); + msg.setScope(scope_id); + System.out.println("-- sending message " + (char)scope_id + "#" + i); + c1.send(msg); + } + } + + long target_time=System.currentTimeMillis() + NUM_MSGS * SLEEP_TIME * 2, start=System.currentTimeMillis(); + do { + if(receiver.size() >= NUM_MSGS * scopes.length) + break; + Util.sleep(100); + System.out.print("."); + } + while(target_time > System.currentTimeMillis()); + + long time=System.currentTimeMillis() - start; + + ConcurrentMap> msgs=receiver.getMsgs(); + System.out.println("seqnos:"); + for(Map.Entry> entry: msgs.entrySet()) { + short tmp=entry.getKey(); + System.out.println((char)tmp + ": " + entry.getValue()); + } + System.out.println(receiver.size() + " msgs in " + time + " ms"); + + assert receiver.size() == NUM_MSGS * scopes.length; + + assert time >= NUM_MSGS * SLEEP_TIME && time < NUM_MSGS *SLEEP_TIME * 2; + + System.out.println("checking order within the scopes:"); + + for(short scope_id: scopes) { + List list=msgs.get(scope_id); + for(int i=0; i < NUM_MSGS; i++) + assert (i+1) == list.get(i); + } + + System.out.println("OK, order is correct"); + } + + + + private void sendMessages(Address dest, boolean use_scopes) throws Exception { + if(use_scopes) { + ProtocolStack stack=c2.getProtocolStack(); + Protocol neighbor=stack.findProtocol(UNICAST.class, UNICAST2.class); + SCOPE scope=new SCOPE(); + stack.insertProtocolInStack(scope, neighbor, ProtocolStack.ABOVE); + scope.init(); + } + + c1.connect("SCOPE_Test"); + c2.connect("SCOPE_Test"); + assert c2.getView().size() == 2 : "c2.view is " + c2.getView(); + + MyReceiver receiver=new MyReceiver(); + c2.setReceiver(receiver); + + for(long i=1; i <=5; i++) { + Message msg=new Message(dest, null, i); + if(use_scopes) + msg.setScope((short)i); + System.out.println("-- sending message #" + i); + c1.send(msg); + } + + long target_time=System.currentTimeMillis() + NUM_MSGS * SLEEP_TIME * 2, start=System.currentTimeMillis(); + do { + if(receiver.size() >= NUM_MSGS) + break; + Util.sleep(100); + System.out.print("."); + } + while(target_time > System.currentTimeMillis()); + + long time=System.currentTimeMillis() - start; + + List seqnos=receiver.getSeqnos(); + System.out.println("\nsequence numbers: " + seqnos + " in " + time + " ms"); + + assert seqnos.size() == NUM_MSGS; + + if(use_scopes) { + assert time > SLEEP_TIME * 1 && time < NUM_MSGS * SLEEP_TIME; + for(int i=0; i < NUM_MSGS; i++) + assert seqnos.contains((long)i+1); + } + else { + assert time >= NUM_MSGS * SLEEP_TIME; + for(int i=0; i < NUM_MSGS; i++) + assert (i+1) == seqnos.get(i); + } + } + + + + + public static class MyReceiver extends ReceiverAdapter { + /** List of unicast sequence numbers */ + final List seqnos=Collections.synchronizedList(new LinkedList()); + + public MyReceiver() { + } + + public List getSeqnos() { + return seqnos; + } + + public void receive(Message msg) { + Util.sleep(SLEEP_TIME); + Long num=(Long)msg.getObject(); + seqnos.add(num); + } + + public int size() {return seqnos.size();} + } + + + public static class MyScopedReceiver extends ReceiverAdapter { + final ConcurrentMap> msgs=new ConcurrentHashMap>(); + + public void receive(Message msg) { + Util.sleep(SLEEP_TIME); + Short scope=msg.getScope(); + if(scope.shortValue() > 0) { + List list=msgs.get(scope); + if(list == null) { + list=new ArrayList(5); + List tmp=msgs.putIfAbsent(scope, list); + if(tmp != null) + list=tmp; + } + list.add((Long)msg.getObject()); + } + } + + public ConcurrentMap> getMsgs() { + return msgs; + } + + public int size() { + int retval=0; + for(List list: msgs.values()) + retval+=list.size(); + return retval; + } + } + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/SendAndReceiveTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/SendAndReceiveTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/SendAndReceiveTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/SendAndReceiveTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: SendAndReceiveTest.java,v 1.13 2008/04/14 08:18:39 belaban Exp $ package org.jgroups.tests; @@ -21,7 +20,7 @@ static final int NUM_MSGS=1000; static final long TIMEOUT=30000; - String props1="UDP(loopback=true;mcast_addr=228.8.8.8;mcast_port=27000;ip_ttl=1;" + + String props1="UDP(loopback=true;mcast_port=27000;ip_ttl=1;" + "mcast_send_buf_size=64000;mcast_recv_buf_size=64000):" + //"PIGGYBACK(max_wait_time=100;max_size=32000):" + "PING(timeout=2000;num_initial_members=3):" + @@ -33,9 +32,9 @@ "pbcast.STABLE(desired_avg_gossip=20000):" + "FRAG(frag_size=8096):" + "pbcast.GMS(join_timeout=5000;" + - "shun=false;print_local_addr=true)"; + "print_local_addr=true)"; - String props2="UDP(loopback=false;mcast_addr=228.8.8.8;mcast_port=27000;ip_ttl=1;" + + String props2="UDP(loopback=false;mcast_port=27000;ip_ttl=1;" + "mcast_send_buf_size=64000;mcast_recv_buf_size=64000):" + //"PIGGYBACK(max_wait_time=100;max_size=32000):" + "PING(timeout=2000;num_initial_members=3):" + @@ -47,7 +46,7 @@ "pbcast.STABLE(desired_avg_gossip=20000):" + "FRAG(frag_size=8096):" + "pbcast.GMS(join_timeout=5000;" + - "shun=false;print_local_addr=true)"; + "print_local_addr=true)"; String props3="SHARED_LOOPBACK:" + "PING(timeout=2000;num_initial_members=3):" + @@ -59,7 +58,7 @@ "pbcast.STABLE(desired_avg_gossip=20000):" + "FRAG(frag_size=8096):" + "pbcast.GMS(join_timeout=5000;" + - "shun=false;print_local_addr=true)"; + "print_local_addr=true)"; diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/SequencerFailoverTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/SequencerFailoverTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/SequencerFailoverTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/SequencerFailoverTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -4,8 +4,8 @@ import org.jgroups.*; +import org.jgroups.protocols.pbcast.GMS; import org.jgroups.util.Util; -import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -18,121 +18,135 @@ * Tests a SEQUENCER based stack: A, B and C. B starts multicasting messages with a monotonically increasing * number. Then A is crashed. C and B should receive *all* numbers *without* a gap. * @author Bela Ban - * @version $Id: SequencerFailoverTest.java,v 1.10 2008/04/14 08:18:40 belaban Exp $ */ @Test(groups=Global.STACK_INDEPENDENT,sequential=true) public class SequencerFailoverTest { - JChannel ch1, ch2, ch3; // ch1 is the coordinator - static final String GROUP="demo-group"; - static final int NUM_MSGS=50; + JChannel a, b, c; // A is the coordinator + static final String GROUP="SequencerFailoverTest"; + static final int NUM_MSGS=50; static final String props="sequencer.xml"; - @BeforeMethod public void setUp() throws Exception { - ch1=new JChannel(props); - ch1.connect(GROUP); - - ch2=new JChannel(props); - ch2.connect(GROUP); - - ch3=new JChannel(props); - ch3.connect(GROUP); + a=new JChannel(props); + a.setName("A"); + a.connect(GROUP); + + b=new JChannel(props); + b.setName("B"); + b.connect(GROUP); + + c=new JChannel(props); + c.setName("C"); + c.connect(GROUP); } @AfterMethod public void tearDown() throws Exception { - if(ch3 != null) { - ch3.close(); - ch3 = null; - } - if(ch2 != null) { - ch2.close(); - ch2 = null; - } + Util.close(c, b, a); } @Test public void testBroadcastSequence() throws Exception { - MyReceiver r2=new MyReceiver(), r3=new MyReceiver(); - ch2.setReceiver(r2); ch3.setReceiver(r3); + MyReceiver rb=new MyReceiver("B"), rc=new MyReceiver("C"); + b.setReceiver(rb); c.setReceiver(rc); - View v2=ch2.getView(), v3=ch3.getView(); + View v2=b.getView(), v3=c.getView(); System.out.println("ch2's view: " + v2 + "\nch3's view: " + v3); - Assert.assertEquals(v2, v3); + assert v2.equals(v3); new Thread() { public void run() { Util.sleep(3000); - System.out.println("** killing ch1"); - ch1.shutdown(); ch1=null; - System.out.println("** ch1 killed"); + System.out.println("** killing A"); + try { + Util.shutdown(a); + } + catch(Exception e) { + System.err.println("failed shutting down channel " + a.getAddress() + ", exception=" + e); + } + System.out.println("** A killed"); + injectSuspectEvent(a.getAddress(), b, c); + a=null; } }.start(); for(int i=1; i <= NUM_MSGS; i++) { Util.sleep(300); - ch2.send(new Message(null, null, new Integer(i))); + b.send(new Message(null, null, new Integer(i))); System.out.print("-- messages sent: " + i + "/" + NUM_MSGS + "\r"); } System.out.println(""); - v2=ch2.getView(); - v3=ch3.getView(); - System.out.println("ch2's view: " + v2 + "\nch3's view: " + v3); - Assert.assertEquals(v2, v3); - - Assert.assertEquals(2, v2.size()); + v2=b.getView(); + v3=c.getView(); + System.out.println("B's view: " + v2 + "\nC's view: " + v3); + assert v2.equals(v3); + assert v2.size() == 2; int s2, s3; for(int i=15000; i > 0; i-=1000) { - s2=r2.size(); s3=r3.size(); + s2=rb.size(); s3=rc.size(); if(s2 >= NUM_MSGS && s3 >= NUM_MSGS) { - System.out.print("ch2: " + s2 + " msgs, ch3: " + s3 + " msgs\r"); + System.out.print("B: " + s2 + " msgs, C: " + s3 + " msgs\r"); break; } Util.sleep(1000); - System.out.print("sleeping for " + (i/1000) + " seconds (ch2: " + s2 + " msgs, ch3: " + s3 + " msgs)\r"); + System.out.print("sleeping for " + (i/1000) + " seconds (B: " + s2 + " msgs, C: " + s3 + " msgs)\r"); + } + System.out.println("-- verifying messages on B and C"); + List list_b=rb.getList(), list_c=rc.getList(); + System.out.println("B: " + list_b + "\nC: " + list_c); + + assert list_b.size() == list_c.size(); + System.out.println("OK: both B and C have the same number of messages (" + list_b.size() + ")"); + + assert list_b.size() == NUM_MSGS && list_c.size() == NUM_MSGS; + System.out.println("OK: both B and C have the expected number (" + NUM_MSGS + ") of messages"); + + System.out.println("verifying B and C have the same order"); + for(int i=0; i < list_b.size(); i++) { + Integer el_b=list_b.get(i), el_c=list_c.get(i); + assert el_b.equals(el_c) : "element at index=" + i + " in B (" + el_b + + ") is different from element " + i + " in C (" + el_c + ")"; } - System.out.println("-- verifying messages on ch2 and ch3"); - verifyNumberOfMessages(NUM_MSGS, r2); - verifyNumberOfMessages(NUM_MSGS, r3); + System.out.println("OK: B and C's message are in the same order"); } - private static void verifyNumberOfMessages(int num_msgs, MyReceiver receiver) throws Exception { - List msgs=receiver.getList(); - System.out.println("list has " + msgs.size() + " msgs (should have " + NUM_MSGS + ")"); - Assert.assertEquals(num_msgs, msgs.size()); - int i=1; - for(Integer tmp: msgs) { - if(tmp != i) - throw new Exception("expected " + i + ", but got " + tmp); - i++; + + + + /** Injects SUSPECT event(suspected_mbr) into channels */ + private static void injectSuspectEvent(Address suspected_mbr, JChannel ... channels) { + Event evt=new Event(Event.SUSPECT, suspected_mbr); + for(JChannel ch: channels) { + GMS gms=(GMS)ch.getProtocolStack().findProtocol(GMS.class); + if(gms != null) + gms.up(evt); } } private static class MyReceiver extends ReceiverAdapter { - List list=new LinkedList(); + private final List list=new LinkedList(); + private final String name; - public List getList() { - return list; + public MyReceiver(String name) { + this.name=name; } + public List getList() {return list;} + public int size() {return list.size();} public void receive(Message msg) { - list.add((Integer)msg.getObject()); - } - - void clear() { - list.clear(); + Integer val=(Integer)msg.getObject(); + synchronized(list) { + list.add(val); + } } -// public void viewAccepted(View new_view) { -// System.out.println("** view: " + new_view); -// } + void clear() {list.clear();} } - } diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/SequencerOrderTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/SequencerOrderTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/SequencerOrderTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/SequencerOrderTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -3,15 +3,21 @@ package org.jgroups.tests; -import org.jgroups.*; +import org.jgroups.Global; +import org.jgroups.JChannel; +import org.jgroups.Message; +import org.jgroups.ReceiverAdapter; +import org.jgroups.protocols.SHUFFLE; +import org.jgroups.protocols.pbcast.NAKACK; +import org.jgroups.stack.ProtocolStack; import org.jgroups.util.Util; -import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; /** @@ -23,162 +29,168 @@ * concurrent senders; using one sender will cause NAKACK to FIFO order * the messages and the assertions in this test will still hold true, whether * SEQUENCER is present or not. - * @version $Id: SequencerOrderTest.java,v 1.9 2008/10/06 14:47:27 belaban Exp $ */ @Test(groups=Global.STACK_INDEPENDENT,sequential=true) public class SequencerOrderTest { - private JChannel ch1, ch2; - private MyReceiver r1, r2; - static final String GROUP="SequencerOrderGroup"; - static final int NUM_MSGS=1000; + private JChannel c1, c2, c3; + private MyReceiver r1, r2, r3; + static final String GROUP="SequencerOrderTest"; + static final int NUM_MSGS=50; // messages per thread + static final int NUM_THREADS=10; + static final int EXPECTED_MSGS=NUM_MSGS * NUM_THREADS; static final String props="sequencer.xml"; - + private Sender[] senders=new Sender[NUM_THREADS]; @BeforeMethod void setUp() throws Exception { - ch1=new JChannel(props); - ch1.connect(GROUP); + c1=new JChannel(props); + c1.setName("A"); + c1.connect(GROUP); + r1=new MyReceiver("A"); + c1.setReceiver(r1); + + c2=new JChannel(props); + c2.setName("B"); + c2.connect(GROUP); + r2=new MyReceiver("B"); + c2.setReceiver(r2); + + c3=new JChannel(props); + c3.setName("C"); + c3.connect(GROUP); + r3=new MyReceiver("C"); + c3.setReceiver(r3); + + AtomicInteger num=new AtomicInteger(1); - ch2=new JChannel(props); - ch2.connect(GROUP); + for(int i=0; i < senders.length; i++) { + senders[i]=new Sender(NUM_MSGS, num, c1, c2, c3); + } } @AfterMethod void tearDown() throws Exception { - if(ch2 != null) { - ch2.close(); - ch2 = null; - } - if(ch1 != null) { - ch1.close(); - ch1 = null; - } + Util.close(c3, c2, c1); } - @Test + @Test @SuppressWarnings("unchecked") public void testBroadcastSequence() throws Exception { - r1=new MyReceiver(ch1.getLocalAddress()); - ch1.setReceiver(r1); - r2=new MyReceiver(ch2.getLocalAddress()); - ch2.setReceiver(r2); + insertShuffle(c1, c2, c3); // use concurrent senders to send messages to the group + System.out.println("Starting " + senders.length + " sender threads (each sends " + NUM_MSGS + " messages)"); + for(Sender sender: senders) + sender.start(); + + for(Sender sender: senders) + sender.join(20000); + System.out.println("Ok, senders have completed"); + + final List l1=r1.getMsgs(); + final List l2=r2.getMsgs(); + final List l3=r3.getMsgs(); - Thread thread1 = new Thread() { - public void run() { - Util.sleep(300); - for(int i=1; i <= NUM_MSGS; i++) { - try { - ch1.send(new Message(null, null, new Integer(i))); - } catch (Exception e) { - throw new RuntimeException(e); - } - System.out.print("-- messages sent thread 1: " + i + "/" + NUM_MSGS + "\r"); - } - - } - }; - - Thread thread2 = new Thread() { - public void run() { - Util.sleep(300); - for(int i=1; i <= NUM_MSGS; i++) { - try { - ch2.send(new Message(null, null, new Integer(i))); - } catch (Exception e) { - throw new RuntimeException(e); - } - System.out.print("-- messages sent thread 2: " + i + "/" + NUM_MSGS + "\r"); - } - - } - }; - - thread1.start(); - thread2.start(); - thread1.join(); - thread2.join(); - - System.out.println(""); - System.out.println("-- verifying messages on ch1 and ch2"); - verifyNumberOfMessages(NUM_MSGS * 2); - verifyMessageOrder(r1.getMsgs()); - verifyMessageOrder(r2.getMsgs()); - verifySameOrder(); + System.out.println("-- verifying messages on A and B"); + verifyNumberOfMessages(EXPECTED_MSGS, l1, l2, l3); + verifySameOrder(EXPECTED_MSGS, l1, l2, l3); } - private void verifyNumberOfMessages(int num_msgs) throws Exception { - List l1=r1.getMsgs(); - List l2=r2.getMsgs(); + private static void insertShuffle(JChannel... channels) throws Exception { + for(JChannel ch: channels) { + SHUFFLE shuffle=new SHUFFLE(); + shuffle.setDown(false); + shuffle.setUp(true); + shuffle.setMaxSize(10); + shuffle.setMaxTime(1000); + ch.getProtocolStack().insertProtocol(shuffle, ProtocolStack.BELOW, NAKACK.class); + shuffle.init(); // gets the timer + } + } + private static void verifyNumberOfMessages(int num_msgs, List ... lists) throws Exception { long end_time=System.currentTimeMillis() + 10000; while(System.currentTimeMillis() < end_time) { - if(l1.size() >= num_msgs && l2.size() >= num_msgs) + boolean all_correct=true; + for(List list: lists) { + if(list.size() != num_msgs) { + all_correct=false; + break; + } + } + if(all_correct) break; - Util.sleep(500); + Util.sleep(1000); } - System.out.println("l1.size()=" + l1.size() + ", l2.size()=" + l2.size()); - Assert.assertEquals(l1.size(), num_msgs, "list 1 should have " + num_msgs + " elements"); - Assert.assertEquals(l2.size(), num_msgs, "list 2 should have " + num_msgs + " elements"); - } + for(int i=0; i < lists.length; i++) + System.out.println("list #" + (i+1) + ": " + lists[i]); - private void verifyMessageOrder(List list) throws Exception { - List l1=r1.getMsgs(); - List l2=r2.getMsgs(); - System.out.println("l1: " + l1); - System.out.println("l2: " + l2); - int i=1,j=1; - for(int count: list) { - if(count == i) - i++; - else if(count == j) - j++; - else - throw new Exception("got " + count + ", but expected " + i + " or " + j); - } + for(int i=0; i < lists.length; i++) + assert lists[i].size() == num_msgs : "list #" + (i+1) + " should have " + num_msgs + " elements"; + System.out.println("OK, all lists have the same size (" + num_msgs + ")\n"); } - private void verifySameOrder() throws Exception { - List l1=r1.getMsgs(); - List l2=r2.getMsgs(); - int[] arr1=new int[l1.size()]; - int[] arr2=new int[l2.size()]; - int index=0; - for(int el: l1) { - arr1[index++]=el; + private static void verifySameOrder(int expected_msgs, List ... lists) throws Exception { + for(int index=0; index < expected_msgs; index++) { + String val=null; + for(List list: lists) { + if(val == null) + val=list.get(index); + else { + String val2=list.get(index); + assert val.equals(val2) : "found different values at index " + index + ": " + val + " != " + val2; + } + } } - index=0; - for(int el: l2) { - arr2[index++]=el; + System.out.println("OK, all lists have the same order"); + } + + private static class Sender extends Thread { + final int num_msgs; + final JChannel[] channels; + final AtomicInteger num; + + public Sender(int num_msgs, AtomicInteger num, JChannel ... channels) { + this.num_msgs=num_msgs; + this.num=num; + this.channels=channels; } - int count1, count2; - for(int i=0; i < arr1.length; i++) { - count1=arr1[i]; count2=arr2[i]; - if(count1 != count2) - throw new Exception("lists are different at index " + i + ": count1=" + count1 + ", count2=" + count2); + public void run() { + for(int i=1; i <= num_msgs; i++) { + try { + JChannel ch=(JChannel)Util.pickRandomElement(channels); + String channel_name=ch.getName(); + ch.send(null, null, channel_name + ":" + num.getAndIncrement()); + } + catch(Exception e) { + } + } } } - private static class MyReceiver extends ReceiverAdapter { - Address local_addr; - List msgs=new LinkedList(); + final String name; + final List msgs=new LinkedList(); - private MyReceiver(Address local_addr) { - this.local_addr=local_addr; + private MyReceiver(String name) { + this.name=name; } - public List getMsgs() { + public List getMsgs() { return msgs; } public void receive(Message msg) { - msgs.add((Integer)msg.getObject()); + String val=(String)msg.getObject(); + if(val != null) { + synchronized(msgs) { + msgs.add(val); + } + } } } diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/SharedTransportTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/SharedTransportTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/SharedTransportTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/SharedTransportTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,13 +2,13 @@ import org.jgroups.*; import org.jgroups.conf.ConfiguratorFactory; -import org.jgroups.conf.ProtocolData; -import org.jgroups.conf.ProtocolParameter; +import org.jgroups.conf.ProtocolConfiguration; import org.jgroups.conf.ProtocolStackConfigurator; import org.jgroups.protocols.BasicTCP; import org.jgroups.protocols.TCPPING; import org.jgroups.protocols.TP; import org.jgroups.protocols.UDP; +import org.jgroups.stack.IpAddress; import org.jgroups.stack.Protocol; import org.jgroups.stack.ProtocolStack; import org.jgroups.util.ResourceManager; @@ -28,7 +28,6 @@ /** * Tests which test the shared transport * @author Bela Ban - * @version $Id: SharedTransportTest.java,v 1.25 2008/10/28 14:28:55 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=true) public class SharedTransportTest extends ChannelTestBase { @@ -117,8 +116,8 @@ b.setReceiver(r2); c.setReceiver(r3); - a.connect("a"); - c.connect("a"); + a.connect("cluster-1"); + c.connect("cluster-1"); View view=a.getView(); assert view.size() == 2; @@ -137,7 +136,7 @@ r1.clear(); r2.clear(); r3.clear(); - b.connect("b"); + b.connect("cluster-2"); a.send(new Message(null, null, "msg-3")); b.send(new Message(null, null, "msg-4")); @@ -154,6 +153,27 @@ } + public void testView4() throws Exception { + a=createSharedChannel(SINGLETON_1); + r1=new MyReceiver("A::" + SINGLETON_1); + a.setReceiver(r1); + + a.connect("cluster-X"); + a.send(new Message(null, null, "msg-1")); + + Util.sleep(1000); // async sending - wait a little + List list=r1.getList(); + assert list.size() == 1; + + a.send(new Message(null, null, "msg-2")); + a.send(new Message(null, null, "msg-3")); + a.send(new Message(null, null, "msg-4")); + Util.sleep(1000); // async sending - wait a little + + list=r1.getList(); + assert list.size() == 4; + } + public void testSharedTransportAndNonsharedTransport() throws Exception { a=createSharedChannel(SINGLETON_1); @@ -455,9 +475,9 @@ private JChannel createSharedChannel(String singleton_name) throws ChannelException { ProtocolStackConfigurator config=ConfiguratorFactory.getStackConfigurator(channel_conf); - ProtocolData[] protocols=config.getProtocolStack(); - ProtocolData transport=protocols[0]; - transport.getParameters().put(Global.SINGLETON_NAME, new ProtocolParameter(Global.SINGLETON_NAME, singleton_name)); + List protocols=config.getProtocolStack(); + ProtocolConfiguration transport=protocols.get(0); + transport.getProperties().put(Global.SINGLETON_NAME, singleton_name); return new JChannel(config); } @@ -470,7 +490,7 @@ if(transport instanceof UDP) { String mcast_addr=ResourceManager.getNextMulticastAddress(); short mcast_port=ResourceManager.getNextMulticastPort(bind_addr); - ((UDP)transport).setMulticastAddress(mcast_addr); + ((UDP)transport).setMulticastAddress(InetAddress.getByName(mcast_addr)); ((UDP)transport).setMulticastPort(mcast_port); } else if(transport instanceof BasicTCP) { @@ -487,7 +507,8 @@ initial_hosts.add(bind_addr + "[" + port + "]"); } String tmp=Util.printListWithDelimiter(initial_hosts, ","); - ((TCPPING)ping).setInitialHosts(tmp); + List init_hosts = Util.parseCommaDelimitedHosts(tmp, 1) ; + ((TCPPING)ping).setInitialHosts(init_hosts) ; } else { throw new IllegalStateException("Only UDP and TCP are supported as transport protocols"); diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ShunTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ShunTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ShunTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ShunTest.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,324 +0,0 @@ -package org.jgroups.tests; - - -import org.jgroups.*; -import org.jgroups.blocks.GroupRequest; -import org.jgroups.blocks.RpcDispatcher; -import org.jgroups.protocols.DISCARD; -import org.jgroups.protocols.FD; -import org.jgroups.stack.Protocol; -import org.jgroups.stack.ProtocolStack; -import org.jgroups.util.Rsp; -import org.jgroups.util.RspList; -import org.jgroups.util.Util; -import org.testng.Assert; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.Test; - -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Map; -import java.util.concurrent.Semaphore; - -/** - * Tests shunning of a channel - * - * @author vlada - * @version $Id: ShunTest.java,v 1.20 2008/08/08 17:07:12 vlada Exp $ - */ -@Test(groups="broken",sequential=false) -public class ShunTest extends ChannelTestBase { - JChannel c1, c2; - RpcDispatcher disp1, disp2; - - @AfterMethod - protected void tearDown() throws Exception { - if(disp2 != null) - disp2.stop(); - if(disp1 != null) - disp1.stop(); - Util.close(c2, c1); - } - - protected boolean useBlocking() { - return true; - } - - public void testShunning() { - connectAndShun(2,false); - } - - @Test(enabled=false) - public static long getCurrentTime() { - return System.currentTimeMillis(); - } - - /** - * Tests the case where (members A and B) member B is shunned, excluded by A , then closes and reopens the channel. - * After B has rejoined, it invokes an RPC and it should get valid return values from both A and B. - * @throws Exception - */ - public void testTwoMembersShun() throws Exception { - View view; - c1=createChannel(true, 2); - c1.setOpt(Channel.AUTO_GETSTATE, false); - c1.addChannelListener(new BelasChannelListener("C1")); - c2=createChannel(c1); - c2.setOpt(Channel.AUTO_GETSTATE, false); - c2.addChannelListener(new BelasChannelListener("C2")); - disp1=new RpcDispatcher(c1, null, new BelasReceiver("C1"), this); - disp2=new RpcDispatcher(c2, null, new BelasReceiver("C2"), this); - c1.connect("testTwoMembersShun"); - c2.connect("testTwoMembersShun"); - Assert.assertEquals(2, c1.getView().size()); - - RspList rsps=disp2.callRemoteMethods(null, "getCurrentTime", null, (Class[])null, GroupRequest.GET_ALL, 10000); - log.info(">> rsps:\n" + rsps); - Assert.assertEquals(2, rsps.size()); - - ProtocolStack stack=c1.getProtocolStack(); - stack.removeProtocol("VERIFY_SUSPECT"); - Protocol transport=stack.getTransport(); - log.info(">> suspecting C2:"); - transport.up(new Event(Event.SUSPECT, c2.getLocalAddress())); - - log.info(">> shunning C2:"); - - c2.up(new Event(Event.EXIT)); - - Util.sleep(1000); // give the closer thread time to close the channel - System.out.println("waiting for C2 to come back"); - int count=1; - while(true) { - view=c2.getView(); - // System.out.println("<< C2.view: " + view); - if((view != null && view.size() >= 2) || count >= 10) - break; - count++; - Util.sleep(1000); - } - view=c2.getView(); - log.info(">>> view is " + view + " <<<< (should have 2 members)"); - Assert.assertEquals(2, view.size()); - - Util.sleep(1000); - System.out.println("invoking RPC on shunned member"); - rsps=disp2.callRemoteMethods(null, "getCurrentTime", null, (Class[])null, GroupRequest.GET_ALL, 10000); - System.out.println(">> rsps:\n" + rsps); - Assert.assertEquals(2, rsps.size()); - for(Map.Entry entry: rsps.entrySet()) { - Rsp rsp=entry.getValue(); - assertFalse(rsp.wasSuspected()); - assertTrue(rsp.wasReceived()); - } - - c1.setReceiver(null); - c2.setReceiver(null); - c1.clearChannelListeners(); - c2.clearChannelListeners(); - } - - protected void connectAndShun(int shunChannelIndex, boolean useDispatcher) { - String[] names=null; - - names=new String[] { "A", "B", "C", "D" }; - - int count=names.length; - - ShunChannel[] channels=new ShunChannel[count]; - try { - // Create a semaphore and take all its permits - Semaphore semaphore=new Semaphore(count); - semaphore.acquire(count); - - // Create activation threads that will block on the semaphore - for(int i=0;i < count;i++) { - if(i == 0) - channels[i]=new ShunChannel(names[i], semaphore, useDispatcher); - - else - channels[i]=new ShunChannel((JChannel)channels[0].getChannel(), - names[i], - semaphore, - useDispatcher); - - JChannel c=(JChannel)channels[i].getChannel(); - c.addChannelListener(new MyChannelListener(channels)); - // Release one ticket at a time to allow the thread to start - // working - channels[i].start(); - semaphore.release(1); - //sleep at least a second and max second and a half - Util.sleep(2000); - } - - // block until we all have a valid view - - blockUntilViewsReceived(channels, 60000); - - ShunChannel shun=channels[shunChannelIndex]; - log.info("Start shun attempt"); - addDiscardProtocol((JChannel)shun.getChannel()); - - //allow shunning to kick in - Util.sleep(20000); - - //and then block until we all have a valid view or fail with timeout - blockUntilViewsReceived(channels, 60000); - - } - catch(Exception ex) { - log.warn("Exception encountered during test", ex); - assert false:ex.getLocalizedMessage(); - } - finally { - for(ShunChannel channel:channels) { - channel.cleanup(); - Util.sleep(2000); - } - - /* we sometimes have double BLOCK events for tcp stack - * TODO investigate why - for(ShunChannel channel:channels){ - if(useBlocking() && channel.getChannel().flushSupported()){ - checkEventStateTransferSequence(channel); - } - }*/ - } - } - - private static void modifyStack(JChannel ch) { - ProtocolStack stack=ch.getProtocolStack(); - - try { - ch.getProtocolStack().removeProtocol("VERIFY_SUSPECT"); - } catch (Exception e) { - e.printStackTrace(); - } - FD fd=(FD)stack.findProtocol("FD"); - if(fd != null) { - fd.setMaxTries(3); - fd.setTimeout(1000); - } - } - - private static void addDiscardProtocol(JChannel ch) throws Exception { - ProtocolStack stack=ch.getProtocolStack(); - Protocol transport=stack.getTransport(); - DISCARD discard=new DISCARD(); - discard.setUpDiscardRate(1.0); - discard.setProtocolStack(ch.getProtocolStack()); - discard.start(); - stack.insertProtocol(discard, ProtocolStack.ABOVE, transport.getName()); - } - - private static class MyChannelListener extends ChannelListenerAdapter{ - ShunChannel[] channels; - Channel channel; - - public MyChannelListener(ShunChannel[] channels) { - super(); - this.channels = channels; - } - - public void channelConnected(Channel channel) { - this.channel = channel; - } - - public void channelReconnected(Address addr) { - System.out.println("Channel reconnected , new address is " + addr); - } - - public void channelShunned() { - System.out.println("Shunned channel is " + channel.getLocalAddress()); - System.out.println("Removing discard "); - for (ShunChannel ch : channels) { - JChannel c = (JChannel)ch.getChannel(); - try { - c.getProtocolStack().removeProtocol("DISCARD"); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - } - - private class BelasChannelListener extends ChannelListenerAdapter { - final String name; - - public BelasChannelListener(String name) { - this.name=name; - } - - public void channelClosed(Channel channel) { - log.info("[" + name + "] channelClosed()"); - } - - public void channelConnected(Channel channel) { - log.info("[" + name + "] channelConnected()"); - } - - public void channelDisconnected(Channel channel) { - log.info("[" + name + "] channelDisconnected()"); - } - - public void channelReconnected(Address addr) { - log.info("[" + name + "] channelReconnected(" + addr + ")"); - } - - public void channelShunned() { - log.info("[" + name + "] channelShunned()"); - } - } - - private class BelasReceiver extends ReceiverAdapter { - final String name; - - public BelasReceiver(String name) { - this.name=name; - } - - public void viewAccepted(View new_view) { - log.info("[" + name + "] new_view = " + new_view); - } - } - - protected class ShunChannel extends PushChannelApplicationWithSemaphore { - - public ShunChannel(String name,Semaphore semaphore,boolean useDispatcher) throws Exception{ - super(name, semaphore, useDispatcher); - modifyStack((JChannel)channel); - } - - public ShunChannel(JChannel ch,String name,Semaphore semaphore,boolean useDispatcher) throws Exception{ - super(ch,name, semaphore, useDispatcher); - modifyStack((JChannel)channel); - } - - public void useChannel() throws Exception { - channel.connect("ShunChannel"); - channel.getState(null,5000); - channel.send(null, null, channel.getLocalAddress()); - } - - - public void setState(byte[] state) { - super.setState(state); - } - - public byte[] getState() { - super.getState(); - return new byte[]{'j','g','r','o','u','p','s'}; - } - - public void getState(OutputStream ostream) { - super.getState(ostream); - } - - public void setState(InputStream istream) { - super.setState(istream); - } - } - - -} diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/StateTransferTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/StateTransferTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/StateTransferTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/StateTransferTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,9 +1,6 @@ package org.jgroups.tests; -import org.jgroups.Channel; -import org.jgroups.Global; -import org.jgroups.JChannel; -import org.jgroups.Message; +import org.jgroups.*; import org.jgroups.util.Util; import org.testng.annotations.Test; @@ -14,53 +11,46 @@ import java.util.Set; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; /** - * Tests correct state transfer while other members continue sending messages to - * the group + * Tests correct state transfer while other members continue sending messages to the group * @author Bela Ban - * @version $Id: StateTransferTest.java,v 1.29 2008/08/08 17:07:11 vlada Exp $ */ -@Test(groups=Global.STACK_DEPENDENT,sequential=false) +@Test(groups=Global.STACK_DEPENDENT,sequential=true) public class StateTransferTest extends ChannelTestBase { - private static final int MSG_SEND_COUNT=10000; + static final int MSG_SEND_COUNT=5000; + static final String[] names= { "A", "B", "C", "D"}; + static final int APP_COUNT=names.length; - private static final int APP_COUNT=2; - - @Test + public void testStateTransferFromSelfWithRegularChannel() throws Exception { Channel ch=createChannel(true); ch.connect("StateTransferTest"); try { - boolean rc=ch.getState(null, 2000); - assert !rc : "getState() on singleton should return false"; + Address self=ch.getAddress(); + assert self != null; + boolean rc=ch.getState(self, 20000); + assert !rc : "getState() on self should return false"; } finally { ch.close(); } } - @Test + public void testStateTransferWhileSending() throws Exception { + StateTransferApplication[] apps=new StateTransferApplication[APP_COUNT]; try { - // Create a semaphore and take all its permits Semaphore semaphore=new Semaphore(APP_COUNT); semaphore.acquire(APP_COUNT); int from=0, to=MSG_SEND_COUNT; - String[] names= { "A", "B" }; - for(int i=0;i < apps.length;i++) { if(i == 0) apps[i]=new StateTransferApplication(semaphore, names[i], from, to); else - apps[i]=new StateTransferApplication((JChannel)apps[0].getChannel(), - semaphore, - names[i], - from, - to); + apps[i]=new StateTransferApplication((JChannel)apps[0].getChannel(), semaphore, names[i], from, to); from+=MSG_SEND_COUNT; to+=MSG_SEND_COUNT; } @@ -69,14 +59,16 @@ StateTransferApplication app=apps[i]; app.start(); semaphore.release(); - Util.sleep(4000); + //avoid merge + Util.sleep(3000); } // Make sure everyone is in sync + Channel[] tmp=new Channel[apps.length]; + for(int i=0; i < apps.length; i++) + tmp[i]=apps[i].getChannel(); - blockUntilViewsReceived(apps, 60000); - - Util.sleep(1000); + Util.blockUntilViewsReceived(60000, 1000, tmp); // Reacquire the semaphore tickets; when we have them all // we know the threads are done @@ -86,13 +78,33 @@ log.warn(Util.dumpThreads()); } + // Sleep to ensure async messages arrive + System.out.println("Waiting for all channels to have received the " + MSG_SEND_COUNT * APP_COUNT + " messages:"); + long end_time=System.currentTimeMillis() + 40000L; + while(System.currentTimeMillis() < end_time) { + boolean terminate=true; + for(StateTransferApplication app: apps) { + Map map=app.getMap(); + if(map.size() != MSG_SEND_COUNT * APP_COUNT) { + terminate=false; + break; + } + } + if(terminate) + break; + else + Util.sleep(500); + } + // have we received all and the correct messages? + System.out.println("++++++++++++++++++++++++++++++++++++++"); for(int i=0;i < apps.length;i++) { StateTransferApplication w=apps[i]; Map m=w.getMap(); log.info("map has " + m.size() + " elements"); assert m.size() == MSG_SEND_COUNT * APP_COUNT; } + System.out.println("++++++++++++++++++++++++++++++++++++++"); Set keys=apps[0].getMap().keySet(); for(int i=0;i < apps.length;i++) { @@ -103,18 +115,16 @@ } } finally { - for(StateTransferApplication app:apps) { + for(StateTransferApplication app: apps) + app.getChannel().setReceiver(null); + for(StateTransferApplication app: apps) app.cleanup(); - } } } protected class StateTransferApplication extends PushChannelApplicationWithSemaphore { - private final ReentrantLock mapLock=new ReentrantLock(); - - private Map map=new HashMap(MSG_SEND_COUNT * APP_COUNT); - + private final Map map=new HashMap(MSG_SEND_COUNT * APP_COUNT); private int from, to; public StateTransferApplication(Semaphore semaphore, String name, int from, int to) throws Exception { @@ -124,28 +134,30 @@ } public StateTransferApplication(JChannel copySource,Semaphore semaphore, String name, int from, int to) throws Exception { - super(copySource,name, semaphore,false); + super(copySource,name, semaphore); this.from=from; this.to=to; } - public Map getMap() { - Map result=null; - mapLock.lock(); - result=Collections.unmodifiableMap(map); - mapLock.unlock(); - return result; + public Map getMap() { + synchronized(map) { + return Collections.unmodifiableMap(map); + } } public void receive(Message msg) { Object[] data=(Object[])msg.getObject(); - mapLock.lock(); - map.put(data[0], data[1]); - int num_received=map.size(); - mapLock.unlock(); + int num_received=0; + boolean changed=false; + synchronized(map) { + int tmp_size=map.size(); + map.put(data[0], data[1]); + num_received=map.size(); + changed=tmp_size != num_received; + } - if(num_received % 1000 == 0) - log.info("received " + num_received); + if(changed && num_received % 1000 == 0) + log.info(channel.getAddress() + ": received " + num_received); // are we done? if(num_received >= MSG_SEND_COUNT * APP_COUNT) @@ -153,69 +165,57 @@ } public byte[] getState() { - byte[] result=null; - mapLock.lock(); - try { - result=Util.objectToByteBuffer(map); - } - catch(Exception e) { - e.printStackTrace(); - } - finally { - mapLock.unlock(); + synchronized(map) { + try { + return Util.objectToByteBuffer(map); + } + catch(Exception e) { + e.printStackTrace(); + } } - return result; + return null; } + @SuppressWarnings("unchecked") public void setState(byte[] state) { - mapLock.lock(); - try { - map=(Map)Util.objectFromByteBuffer(state); - } - catch(Exception e) { - e.printStackTrace(); - } - finally { - mapLock.unlock(); + synchronized(map) { + try { + Map tmp=(Map)Util.objectFromByteBuffer(state); + map.putAll(tmp); + log.info(channel.getAddress() + ": received state, map has " + map.size() + " elements"); + } + catch(Exception e) { + e.printStackTrace(); + } } - log.info("received state, map has " + map.size() + " elements"); - } public void getState(OutputStream ostream) { - ObjectOutputStream out; - mapLock.lock(); - try { - out=new ObjectOutputStream(ostream); - out.writeObject(map); - out.close(); - } - catch(IOException e) { - e.printStackTrace(); - } - finally { - mapLock.unlock(); + synchronized(map) { + try { + ObjectOutputStream out=new ObjectOutputStream(ostream); + out.writeObject(map); + out.close(); + } + catch(IOException e) { + e.printStackTrace(); + } } - } + @SuppressWarnings("unchecked") public void setState(InputStream istream) { - ObjectInputStream in; - mapLock.lock(); - try { - in=new ObjectInputStream(istream); - map=(Map)in.readObject(); - log.info("received state, map has " + map.size() + " elements"); - in.close(); - } - catch(IOException e) { - e.printStackTrace(); - } - catch(ClassNotFoundException e) { - e.printStackTrace(); - } - finally { - mapLock.unlock(); + synchronized(map) { + try { + ObjectInputStream in=new ObjectInputStream(istream); + Map tmp=(Map)in.readObject(); + Util.close(in); + map.putAll(tmp); + log.info(channel.getAddress() + ": received state, map has " + map.size() + " elements"); + } + catch(Exception e) { + e.printStackTrace(); + } } } @@ -224,19 +224,20 @@ try { acquired=semaphore.tryAcquire(60000L, TimeUnit.MILLISECONDS); if(!acquired) { - throw new Exception(name + " cannot acquire semaphore"); + throw new Exception(channel.getAddress() + " cannot acquire semaphore"); } useChannel(); } catch(Exception e) { - log.error(name + ": " + e.getLocalizedMessage(), e); - // Save it for the test to check + log.error(channel.getAddress() + ": " + e.getLocalizedMessage(), e); exception=e; } } protected void useChannel() throws Exception { - channel.connect("StateTransferTest", null, null, 10000); + System.out.println(channel.getName() + ": connecting and fetching the state"); + channel.connect("StateTransferTest", null, null, 30000); + System.out.println(channel.getName() + ": state transfer is done"); Object[] data=new Object[2]; for(int i=from; i < to; i++) { data[0]=new Integer(i); @@ -247,7 +248,7 @@ Util.sleep(50); if(i % 1000 == 0) - log.info("sent " + i); + log.info(channel.getAddress() + ": sent " + i); } catch(Exception e) { e.printStackTrace(); diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/StreamingStateTransferTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/StreamingStateTransferTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/StreamingStateTransferTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/StreamingStateTransferTest.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,375 +0,0 @@ -package org.jgroups.tests; - -import java.io.IOException; -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -import org.jgroups.Address; -import org.jgroups.Global; -import org.jgroups.JChannel; -import org.jgroups.Message; -import org.jgroups.util.Util; -import org.testng.Assert; -import org.testng.annotations.Test; - -/** - * Tests streaming state transfer. - * - * - * @author Vladimir Blagojevic - * @version $Id$ - * - */ -@Test(groups= { Global.FLUSH }, sequential=false) -public class StreamingStateTransferTest extends ChannelTestBase { - - @Test - public void testTransfer() { - String channelNames[]= { "A", "B", "C", "D" }; - transferHelper(channelNames, false); - } - - @Test - public void testRpcChannelTransfer() { - String channelNames[]= { "A", "B", "C", "D" }; - transferHelper(channelNames, true); - } - - private void transferHelper(String channelNames[], boolean useDispatcher) { - transferHelper(channelNames, false, false, useDispatcher); - } - - private void transferHelper(String channelNames[], - boolean crash, - boolean largeTransfer, - boolean useDispatcher) { - int channelCount=channelNames.length; - ArrayList channels=new ArrayList(channelCount); - - // Create a semaphore and take all its tickets - Semaphore semaphore=new Semaphore(channelCount); - - try { - - semaphore.acquire(channelCount); - boolean crashed=false; - // Create activation threads that will block on the semaphore - for(int i=0;i < channelCount;i++) { - StreamingStateTransferApplication channel=null; - if(i == 0) - channel=new StreamingStateTransferApplication(channelNames[i], - semaphore, - useDispatcher, - largeTransfer); - else - channel=new StreamingStateTransferApplication((JChannel)channels.get(0) - .getChannel(), - channelNames[i], - semaphore, - useDispatcher, - largeTransfer); - - // Start threads and let them join the channel - channels.add(channel); - semaphore.release(1); - channel.start(); - Util.sleep(2000); - - if(crash && !crashed && i > 2) { - StreamingStateTransferApplication coord=channels.remove(0); - coord.cleanup(); - crashed=true; - } - } - - blockUntilViewsReceived(channels, 60000); - - // Reacquire the semaphore tickets; when we have them all - // we know the threads are done - boolean acquired=semaphore.tryAcquire(channelCount, 60, TimeUnit.SECONDS); - if(!acquired) { - log.warn("Most likely a bug, analyse the stack below:"); - log.warn(Util.dumpThreads()); - } - - int getStateInvokedCount=0; - int setStateInvokedCount=0; - int partialGetStateInvokedCount=0; - int partialSetStateInvokedCount=0; - - Util.sleep(3000); - for(int i=0;i < channels.size();i++) { - StreamingStateTransferApplication current=channels.get(i); - if(current.getStateInvoked) { - getStateInvokedCount++; - } - if(current.setStateInvoked) { - setStateInvokedCount++; - } - if(current.partialGetStateInvoked) { - partialGetStateInvokedCount++; - } - if(current.partialSetStateInvoked) { - partialSetStateInvokedCount++; - } - Map> map=current.getMap(); - for(int j=0;j < channels.size();j++) { - StreamingStateTransferApplication app=channels.get(j); - List l=map.get(app.getLocalAddress()); - int size=l != null? l.size() : 0; - Assert.assertEquals(size, - StreamingStateTransferApplication.COUNT, - "Correct element count in map "); - } - } - - Assert.assertEquals(getStateInvokedCount, 1, "Correct invocation count of getState "); - Assert.assertEquals(setStateInvokedCount, - channelCount - 1, - "Correct invocation count of setState "); - Assert.assertEquals(partialGetStateInvokedCount, - 1, - "Correct invocation count of partial getState "); - Assert.assertEquals(partialSetStateInvokedCount, - channelCount - 1, - "Correct invocation count of partial setState "); - - } - catch(Exception ex) { - log.warn(ex); - } - finally { - for(int i=0;i < channels.size();i++) { - StreamingStateTransferApplication app=channels.get(i); - Util.sleep(500); - app.cleanup(); - } - } - } - - protected class StreamingStateTransferApplication extends PushChannelApplicationWithSemaphore { - private final Map> stateMap=new HashMap>(); - - public static final int COUNT=25; - - boolean partialSetStateInvoked=false; - - boolean partialGetStateInvoked=false; - - boolean setStateInvoked=false; - - boolean getStateInvoked=false; - - boolean largeTransfer=false; - - public StreamingStateTransferApplication(String name, - Semaphore s, - boolean useDispatcher, - boolean largeTransfer) throws Exception { - super(name, s, useDispatcher); - this.largeTransfer=largeTransfer; - channel.connect("StreamingStateTransferApplication"); - } - - public StreamingStateTransferApplication(JChannel ch, - String name, - Semaphore s, - boolean useDispatcher, - boolean largeTransfer) throws Exception { - super(ch, name, s, useDispatcher); - this.largeTransfer=largeTransfer; - channel.connect("StreamingStateTransferApplication"); - } - - public void receive(Message msg) { - Address sender=msg.getSrc(); - synchronized(stateMap) { - List list=stateMap.get(sender); - if(list == null) { - list=new ArrayList(); - stateMap.put(sender, list); - } - list.add((Integer)msg.getObject()); - } - } - - public Map> getMap() { - return stateMap; - } - - public void useChannel() throws Exception { - for(int i=0;i < COUNT;i++) { - channel.send(null, null, new Integer(i)); - } - channel.getState(null, 25000); - channel.getState(null, name, 25000); - } - - public void getState(OutputStream ostream) { - if(largeTransfer) - Util.sleep(4000); - - super.getState(ostream); - ObjectOutputStream oos=null; - try { - oos=new ObjectOutputStream(ostream); - HashMap copy=null; - synchronized(stateMap) { - copy=new HashMap(stateMap); - } - oos.writeObject(copy); - oos.flush(); - } - catch(IOException e) { - e.printStackTrace(); - } - finally { - getStateInvoked=true; - Util.close(oos); - } - } - - public byte[] getState() { - if(largeTransfer) - Util.sleep(4000); - - byte[] result=null; - try { - synchronized(stateMap) { - result=Util.objectToByteBuffer(stateMap); - } - } - catch(Exception e) { - e.printStackTrace(); - } - finally { - getStateInvoked=true; - } - return result; - } - - public void setState(byte[] state) { - if(largeTransfer) - Util.sleep(4000); - - Map result=null; - try { - result=(Map)Util.objectFromByteBuffer(state); - } - catch(Exception e) { - e.printStackTrace(); - } - finally { - setStateInvoked=true; - } - synchronized(stateMap) { - stateMap.clear(); - stateMap.putAll(result); - } - } - - public void setState(InputStream istream) { - if(largeTransfer) - Util.sleep(4000); - - super.setState(istream); - ObjectInputStream ois=null; - try { - ois=new ObjectInputStream(istream); - Map map=(Map)ois.readObject(); - synchronized(stateMap) { - stateMap.clear(); - stateMap.putAll(map); - } - } - catch(Exception e) { - e.printStackTrace(); - } - finally { - setStateInvoked=true; - Util.close(ois); - } - } - - public void setState(String stateId, byte[] state) { - if(largeTransfer) - Util.sleep(4000); - - Object nameTransfer=null; - try { - nameTransfer=Util.objectFromByteBuffer(state); - assertEquals("Got partial state requested ", nameTransfer, name); - } - catch(Exception e) { - e.printStackTrace(); - } - finally { - partialSetStateInvoked=true; - } - } - - public byte[] getState(String stateId) { - if(largeTransfer) - Util.sleep(4000); - - byte[] result=null; - try { - result=Util.objectToByteBuffer(stateId); - } - catch(Exception e) { - e.printStackTrace(); - } - finally { - partialGetStateInvoked=true; - } - return result; - } - - public void setState(String state_id, InputStream istream) { - if(largeTransfer) - Util.sleep(4000); - - super.setState(state_id, istream); - ObjectInputStream ois=null; - try { - ois=new ObjectInputStream(istream); - assertEquals("Got partial state requested ", ois.readObject(), name); - } - catch(Exception e) { - e.printStackTrace(); - } - finally { - partialSetStateInvoked=true; - Util.close(ois); - } - } - - public void getState(String state_id, OutputStream ostream) { - if(largeTransfer) - Util.sleep(4000); - - super.getState(state_id, ostream); - ObjectOutputStream oos=null; - try { - oos=new ObjectOutputStream(ostream); - oos.writeObject(state_id); - oos.flush(); - } - catch(IOException e) { - e.printStackTrace(); - } - finally { - partialGetStateInvoked=true; - Util.close(oos); - } - } - } -} diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/TCPGOSSIP_Test.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/TCPGOSSIP_Test.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/TCPGOSSIP_Test.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/TCPGOSSIP_Test.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,173 @@ +package org.jgroups.tests; + +import org.jgroups.Global; +import org.jgroups.JChannel; +import org.jgroups.View; +import org.jgroups.protocols.TCPGOSSIP; +import org.jgroups.stack.GossipRouter; +import org.jgroups.stack.Protocol; +import org.jgroups.util.StackType; +import org.jgroups.util.Util; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * Tests TCPGOSSIP protocol + * + * @author Vladimir Blagojevic + * + **/ +@Test(groups = { Global.STACK_INDEPENDENT, Global.GOSSIP_ROUTER }, sequential = true) +public class TCPGOSSIP_Test extends ChannelTestBase { + private JChannel channel, coordinator; + private final static String GROUP = "TCPGOSSIP_Test"; + private GossipRouter gossipRouter; + private static final String props = "tcpgossip.xml"; + + @BeforeClass + void startRouter() throws Exception { + String bind_addr = getRouterBindAddress(); + gossipRouter = new GossipRouter(12001, bind_addr); + gossipRouter.start(); + } + + private String getRouterBindAddress() { + String bind_addr = Util.getProperty(Global.BIND_ADDR); + if (bind_addr == null) { + StackType type = Util.getIpStackType(); + if (type == StackType.IPv6) + bind_addr = "::1"; + else + bind_addr = "127.0.0.1"; + } + return bind_addr; + } + + @AfterClass(alwaysRun = true) + void stopRouter() throws Exception { + gossipRouter.stop(); + } + + @AfterMethod(alwaysRun = true) + void tearDown() throws Exception { + Util.close(channel, coordinator); + } + + /** + * Tests connect-disconnect-connect sequence for a group with two members (using default + * configuration). + **/ + public void testDisconnectConnectTwo() throws Exception { + coordinator = new JChannel(props); + channel = new JChannel(props); + coordinator.connect(GROUP); + channel.connect("DisconnectTest.testgroup-1"); + channel.disconnect(); + channel.connect(GROUP); + View view = channel.getView(); + assert view.size() == 2; + assert view.containsMember(channel.getAddress()); + assert view.containsMember(coordinator.getAddress()); + } + + public void testAddInitialHosts() throws Exception { + coordinator = new JChannel(props); + channel = new JChannel(props); + coordinator.connect(GROUP); + channel.connect(GROUP); + TCPGOSSIP p = (TCPGOSSIP) channel.getProtocolStack().findProtocol(TCPGOSSIP.class); + String bind_addr = getRouterBindAddress(); + assert p.removeInitialHost(bind_addr, 12001); + p.addInitialHost(bind_addr, 12001); + + + View view = channel.getView(); + assert view.size() == 2; + assert view.containsMember(channel.getAddress()); + assert view.containsMember(coordinator.getAddress()); + } + + public void testConnectThree() throws Exception { + JChannel third = null; + try { + coordinator = new JChannel(props); + channel = new JChannel(props); + coordinator.connect(GROUP); + channel.connect(GROUP); + third = new JChannel(props); + third.connect(GROUP); + View view = channel.getView(); + assert channel.getView().size() == 3; + assert third.getView().size() == 3; + assert view.containsMember(channel.getAddress()); + assert view.containsMember(coordinator.getAddress()); + } finally { + Util.close(third); + } + } + + public void testConnectThreeChannelsWithGRDown() throws Exception { + JChannel third = null; + try { + coordinator = new JChannel(props); + channel = new JChannel(props); + coordinator.connect("testConnectThreeChannelsWithGRDown"); + channel.connect("testConnectThreeChannelsWithGRDown"); + + // kill router + gossipRouter.stop(); + + + // cannot discover others since GR is down + third = new JChannel(props); + third.connect("testConnectThreeChannelsWithGRDown"); + + + // restart and.... + gossipRouter.start(); + Util.blockUntilViewsReceived(60000, 500, coordinator, channel, third); + + // confirm they found each other + View view = channel.getView(); + assert channel.getView().size() == 3; + assert third.getView().size() == 3; + assert view.containsMember(channel.getLocalAddress()); + assert view.containsMember(coordinator.getLocalAddress()); + } finally { + Util.close(third); + } + } + + public void testConnectThreeChannelsWithGRAlreadyDown() throws Exception { + JChannel third = null; + try { + coordinator = new JChannel(props); + channel = new JChannel(props); + + // kill router + gossipRouter.stop(); + + // cannot discover others since GR is down + coordinator.connect("testConnectThreeChannelsWithGRAlreadyDown"); + channel.connect("testConnectThreeChannelsWithGRAlreadyDown"); + + third = new JChannel(props); + third.connect("testConnectThreeChannelsWithGRAlreadyDown"); + + // restart and.... + gossipRouter.start(); + Util.blockUntilViewsReceived(60000, 500, coordinator, channel, third); + + // confirm they found each other + View view = channel.getView(); + assert channel.getView().size() == 3; + assert third.getView().size() == 3; + assert view.containsMember(channel.getLocalAddress()); + assert view.containsMember(coordinator.getLocalAddress()); + } finally { + Util.close(third); + } + } +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ThreadFactoryTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ThreadFactoryTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/ThreadFactoryTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/ThreadFactoryTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -6,7 +6,6 @@ /** * @author Bela Ban - * @version $Id: ThreadFactoryTest.java,v 1.2 2008/10/22 09:04:42 vlada Exp $ */ @Test(groups=Global.FUNCTIONAL,sequential=true) public class ThreadFactoryTest { diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/TransportThreadPoolTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/TransportThreadPoolTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/TransportThreadPoolTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/TransportThreadPoolTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -12,12 +12,11 @@ import java.util.LinkedList; import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.Collection; +import java.util.concurrent.*; /** * @author Bela Ban - * @version $Id: TransportThreadPoolTest.java,v 1.8 2008/08/08 17:07:11 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=true) public class TransportThreadPoolTest extends ChannelTestBase { @@ -40,35 +39,40 @@ Receiver r1=new Receiver(), r2=new Receiver(); c1.setReceiver(r1); c2.setReceiver(r2); + c1.connect("TransportThreadPoolTest"); c2.connect("TransportThreadPoolTest"); - - c1.send(null, null, "hello world"); - c2.send(null, null, "bela"); - - Util.sleep(500); // need to sleep because message sending is asynchronous - assert r1.getMsgs().size() == 2; - assert r2.getMsgs().size() == 2; - + + Util.blockUntilViewsReceived(5000, 500, c1, c2); + assert c2.getView().size() == 2 : "view is " + c2.getView() + ", but should have had a size of 2"; + TP transport=c1.getProtocolStack().getTransport(); - ExecutorService thread_pool=Executors.newCachedThreadPool(); + ExecutorService thread_pool=Executors.newFixedThreadPool(2); transport.setDefaultThreadPool(thread_pool); transport=c2.getProtocolStack().getTransport(); - thread_pool=Executors.newCachedThreadPool(); + thread_pool=Executors.newFixedThreadPool(2); transport.setDefaultThreadPool(thread_pool); - + + c1.send(null, null, "hello world"); + c2.send(null, null, "bela"); c1.send(null, null, "message 3"); c2.send(null, null, "message 4"); - Util.sleep(500); - System.out.println("messages c1: " + print(r1.getMsgs()) + "\nmessages c2: " + print(r2.getMsgs())); + long start=System.currentTimeMillis(); + + r1.getLatch().await(3000, TimeUnit.MILLISECONDS); + r2.getLatch().await(3000, TimeUnit.MILLISECONDS); + + long diff=System.currentTimeMillis() - start; + System.out.println("messages c1: " + print(r1.getMsgs()) + "\nmessages c2: " + print(r2.getMsgs()) + + "\ntook " + diff + " ms"); assert r1.getMsgs().size() == 4; assert r2.getMsgs().size() == 4; } - private static String print(List msgs) { + private static String print(Collection msgs) { StringBuilder sb=new StringBuilder(); for(Message msg: msgs) { sb.append("\"" + msg.getObject() + "\"").append(" "); @@ -78,14 +82,21 @@ private static class Receiver extends ReceiverAdapter { - List msgs=new LinkedList(); + Collection msgs=new ConcurrentLinkedQueue(); + + final CountDownLatch latch = new CountDownLatch(4); - public List getMsgs() { + public Collection getMsgs() { return msgs; } + + public CountDownLatch getLatch(){ + return latch; + } public void receive(Message msg) { msgs.add(msg); + latch.countDown(); } } } diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/TUNNELDeadLockTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/TUNNELDeadLockTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/TUNNELDeadLockTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/TUNNELDeadLockTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,30 +1,28 @@ - package org.jgroups.tests; - - -import org.testng.annotations.*; +import org.jgroups.Global; import org.jgroups.JChannel; import org.jgroups.Message; -import org.jgroups.TimeoutException; -import org.jgroups.Global; +import org.jgroups.ReceiverAdapter; import org.jgroups.stack.GossipRouter; import org.jgroups.util.Promise; +import org.jgroups.util.StackType; +import org.jgroups.util.Util; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; /** * Test designed to make sure the TUNNEL doesn't lock the client and the GossipRouter * under heavy load. * * @author Ovidiu Feodorov - * @version $Id: TUNNELDeadLockTest.java,v 1.17 2008/10/31 10:12:51 belaban Exp $ * @see TUNNELDeadLockTest#testStress */ -@Test(groups=Global.STACK_INDEPENDENT,sequential=true) +@Test(groups={Global.STACK_INDEPENDENT, Global.GOSSIP_ROUTER},sequential=true) public class TUNNELDeadLockTest extends ChannelTestBase { private JChannel channel; - private Promise promise; + private Promise promise; private int receivedCnt; // the total number of the messages pumped down the channel @@ -39,12 +37,20 @@ @BeforeMethod void setUp() throws Exception { - promise=new Promise(); - gossipRouter=new GossipRouter(); + String bind_addr=Util.getProperty(Global.BIND_ADDR); + if(bind_addr == null) { + StackType type=Util.getIpStackType(); + if(type == StackType.IPv6) + bind_addr="::1"; + else + bind_addr="127.0.0.1"; + } + promise=new Promise(); + gossipRouter=new GossipRouter(GossipRouter.PORT,bind_addr); gossipRouter.start(); } - @AfterMethod + @AfterMethod(alwaysRun=true) void tearDown() throws Exception { // I prefer to close down the channel inside the test itself, for the // reason that the channel might be brought in an uncloseable state by @@ -52,11 +58,12 @@ // TO_DO: no elegant way to stop the Router threads and clean-up // resources. Use the Router administrative interface, when available. - - channel=null; + + channel.close(); promise.reset(); promise=null; gossipRouter.stop(); + System.out.println("Router stopped"); } @@ -78,38 +85,20 @@ public void testStress() throws Exception { channel=new JChannel("tunnel.xml"); channel.connect("agroup"); + channel.setReceiver(new ReceiverAdapter() { - // receiver thread - new Thread(new Runnable() { - public void run() { - try { - while(true) { - if(channel == null) - return; - Object o=channel.receive(10000); - if(o instanceof Message) { - receivedCnt++; - if(receivedCnt % 2000 == 0) - System.out.println("-- received " + receivedCnt); - if(receivedCnt == msgCount) { - // let the main thread know I got all msgs - promise.setResult(new Object()); - return; - } - } - } - } - catch(TimeoutException e) { - System.err.println("Timeout receiving from the channel. " + receivedCnt + - " msgs received so far."); + @Override + public void receive(Message msg) { + receivedCnt++; + if(receivedCnt % 2000 == 0) + System.out.println("-- received " + receivedCnt); + if(receivedCnt == msgCount) { + // let the main thread know I got all msgs + promise.setResult(Boolean.TRUE); } - catch(Exception e) { - System.err.println("Error receiving data"); - e.printStackTrace(); - } - } - }).start(); - + } + }); + // stress send messages - the sender thread new Thread(new Runnable() { public void run() { @@ -131,7 +120,7 @@ // wait for all the messages to come; if I don't see all of them in // mainTimeout ms, I fail the test - Object result=promise.getResult(mainTimeout); + Boolean result=promise.getResult(mainTimeout); if(result == null) { String msg= "The channel has failed to send/receive " + msgCount + " messages " + @@ -139,12 +128,6 @@ "timeout (currently " + mainTimeout + " ms). " + receivedCnt + " messages received so far."; assert false : msg; - } - - // don't close it in tearDown() because it hangs forever for a failed test. - channel.close(); + } } - - - } diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/TUNNEL_Test2.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/TUNNEL_Test2.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/TUNNEL_Test2.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/TUNNEL_Test2.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,346 @@ +package org.jgroups.tests; + +import org.jgroups.Global; +import org.jgroups.JChannel; +import org.jgroups.Message; +import org.jgroups.ReceiverAdapter; +import org.jgroups.View; +import org.jgroups.protocols.TUNNEL; +import org.jgroups.stack.GossipRouter; +import org.jgroups.stack.ProtocolStack; +import org.jgroups.util.Promise; +import org.jgroups.util.Util; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Ensures that a disconnected channel reconnects correctly, for different stack + * configurations. + * + * + **/ + +@Test(groups = {Global.STACK_INDEPENDENT,"known-failures",Global.GOSSIP_ROUTER}, sequential = true) +public class TUNNEL_Test2 extends ChannelTestBase { + private JChannel channel, coordinator; + private GossipRouter gr1, gr2; + private static final String props ="tunnel.xml"; + private static String bindAddress = "127.0.0.1"; + + + static { + try { + bindAddress = Util.getBindAddress(null).getHostAddress(); + } catch (Exception e) { + } + } + + @BeforeMethod + void startRouter() throws Exception { + gr1 = new GossipRouter(12003,bindAddress); + gr1.start(); + + gr2 = new GossipRouter(12004,bindAddress); + gr2.start(); + } + + @AfterMethod + void tearDown() throws Exception { + Util.close(channel, coordinator); + Util.sleep(1000); + gr1.stop(); + gr2.stop(); + } + + private void modifyChannel(JChannel... channels) throws Exception { + for (JChannel c : channels) { + ProtocolStack stack = c.getProtocolStack(); + TUNNEL t = (TUNNEL) stack.getBottomProtocol(); + String s = bindAddress + "[" + gr1.getPort() + "],"; + s+=bindAddress+"[" + gr2.getPort() + "]"; + t.setGossipRouterHosts(s); + t.init(); + } + } + + public void testSimpleConnect() throws Exception { + channel = new JChannel(props); + modifyChannel(channel); + channel.connect("testSimpleConnect"); + assert channel.getLocalAddress() != null; + assert channel.getView().size() == 1; + channel.disconnect(); + assert channel.getLocalAddress() == null; + assert channel.getView() == null; + } + + /** + * Tests connect with two members + * + **/ + public void testConnectTwoChannels() throws Exception { + coordinator = new JChannel(props); + channel = new JChannel(props); + modifyChannel(channel,coordinator); + coordinator.connect("testConnectTwoChannels"); + channel.connect("testConnectTwoChannels"); + View view = channel.getView(); + assert view.size() == 2; + assert view.containsMember(channel.getLocalAddress()); + assert view.containsMember(coordinator.getLocalAddress()); + + channel.disconnect(); + Util.sleep(1000); + view = coordinator.getView(); + assert view.size() == 1; + assert view.containsMember(coordinator.getLocalAddress()); + } + + /** + * Tests connect with two members but when both GR fail and restart + * + **/ + public void testConnectTwoChannelsBothGRDownReconnect() throws Exception { + coordinator = new JChannel(props); + channel = new JChannel(props); + modifyChannel(channel,coordinator); + coordinator.connect("testConnectTwoChannelsBothGRDownReconnect"); + channel.connect("testConnectTwoChannelsBothGRDownReconnect"); + Util.sleep(1000); + gr1.stop(); + gr2.stop(); + // give time to reconnect + Util.sleep(3000); + + gr1.start(); + gr2.start(); + + // give time to reconnect + Util.sleep(3000); + View view = coordinator.getView(); + assert view.size() == 2; + assert view.containsMember(coordinator.getLocalAddress()); + assert view.containsMember(channel.getLocalAddress()); + + view = channel.getView(); + assert view.size() == 2; + assert view.containsMember(coordinator.getLocalAddress()); + assert view.containsMember(channel.getLocalAddress()); + } + + public void testConnectThreeChannelsWithGRDown() throws Exception { + JChannel third = null; + coordinator = new JChannel(props); + channel = new JChannel(props); + modifyChannel(channel,coordinator); + coordinator.connect("testConnectThreeChannelsWithGRDown"); + channel.connect("testConnectThreeChannelsWithGRDown"); + + third = new JChannel(props); + modifyChannel(third); + third.connect("testConnectThreeChannelsWithGRDown"); + Util.sleep(1000); + View view = channel.getView(); + assert channel.getView().size() == 3; + assert third.getView().size() == 3; + assert view.containsMember(channel.getLocalAddress()); + assert view.containsMember(coordinator.getLocalAddress()); + + // kill router and recheck views + gr2.stop(); + Util.sleep(1000); + + view = channel.getView(); + assert channel.getView().size() == 3; + assert third.getView().size() == 3; + assert third.getView().containsMember(channel.getLocalAddress()); + assert third.getView().containsMember(coordinator.getLocalAddress()); + + } + + /** + * + **/ + public void testConnectSendMessage() throws Exception { + final Promise msgPromise = new Promise(); + coordinator = new JChannel(props); + modifyChannel(coordinator); + coordinator.connect("testConnectSendMessage"); + coordinator.setReceiver(new PromisedMessageListener(msgPromise)); + + channel = new JChannel(props); + modifyChannel(channel); + channel.connect("testConnectSendMessage"); + + channel.send(new Message(null, null, "payload")); + + Message msg = msgPromise.getResult(20000); + assert msg != null; + assert "payload".equals(msg.getObject()); + } + + /** + * + **/ + public void testConnectSendMessageSecondGRDown() throws Exception { + final Promise msgPromise = new Promise(); + coordinator = new JChannel(props); + modifyChannel(coordinator); + coordinator.connect("testConnectSendMessageSecondGRDown"); + coordinator.setReceiver(new PromisedMessageListener(msgPromise)); + + channel = new JChannel(props); + modifyChannel(channel); + channel.connect("testConnectSendMessageSecondGRDown"); + + Util.sleep(1000); + gr2.stop(); + + channel.send(new Message(null, null, "payload")); + + View view = coordinator.getView(); + assert view.size() == 2; + assert view.containsMember(coordinator.getLocalAddress()); + assert view.containsMember(channel.getLocalAddress()); + + view = channel.getView(); + assert view.size() == 2; + assert view.containsMember(coordinator.getLocalAddress()); + assert view.containsMember(channel.getLocalAddress()); + + Message msg = msgPromise.getResult(20000); + assert msg != null; + assert "payload".equals(msg.getObject()); + + } + + /** + * + **/ + public void testConnectSendMessageBothGRDown() throws Exception { + final Promise msgPromise = new Promise(); + coordinator = new JChannel(props); + modifyChannel(coordinator); + coordinator.connect("testConnectSendMessageBothGRDown"); + coordinator.setReceiver(new PromisedMessageListener(msgPromise)); + + channel = new JChannel(props); + modifyChannel(channel); + channel.connect("testConnectSendMessageBothGRDown"); + Util.sleep(1000); + gr1.stop(); + gr2.stop(); + + // give time to reconnect + Util.sleep(3000); + + gr1.start(); + gr2.start(); + + // give time to reconnect + Util.sleep(3000); + + + + channel.send(new Message(null, null, "payload")); + + View view = coordinator.getView(); + assert view.size() == 2; + assert view.containsMember(coordinator.getLocalAddress()); + assert view.containsMember(channel.getLocalAddress()); + + view = channel.getView(); + assert view.size() == 2; + assert view.containsMember(coordinator.getLocalAddress()); + assert view.containsMember(channel.getLocalAddress()); + + Message msg = msgPromise.getResult(20000); + assert msg != null; + assert "payload".equals(msg.getObject()); + } + + /** + * + **/ + public void testConnectSendMessageBothGRDownOnlyOneUp() throws Exception { + + final Promise msgPromise = new Promise(); + coordinator = new JChannel(props); + modifyChannel(coordinator); + coordinator.connect("testConnectSendMessageBothGRDownOnlyOneUp"); + coordinator.setReceiver(new PromisedMessageListener(msgPromise)); + + channel = new JChannel(props); + modifyChannel(channel); + channel.connect("testConnectSendMessageBothGRDownOnlyOneUp"); + + Util.sleep(1000); + gr1.stop(); + gr2.stop(); + + gr1.start(); + // give time to reconnect + Util.sleep(6000); + + + channel.send(new Message(null, null, "payload")); + + View view = coordinator.getView(); + assert view.size() == 2; + assert view.containsMember(coordinator.getLocalAddress()); + assert view.containsMember(channel.getLocalAddress()); + + view = channel.getView(); + assert view.size() == 2; + assert view.containsMember(coordinator.getLocalAddress()); + assert view.containsMember(channel.getLocalAddress()); + + Message msg = msgPromise.getResult(20000); + assert msg != null; + assert "payload".equals(msg.getObject()); + } + + public void testConnectSendMessageFirstGRDown() throws Exception { + + final Promise msgPromise = new Promise(); + coordinator = new JChannel(props); + modifyChannel(coordinator); + coordinator.connect("testConnectSendMessageFirstGRDown"); + coordinator.setReceiver(new PromisedMessageListener(msgPromise)); + + channel = new JChannel(props); + modifyChannel(channel); + channel.connect("testConnectSendMessageFirstGRDown"); + Util.sleep(1000); + gr1.stop(); + + channel.send(new Message(null, null, "payload")); + View view = coordinator.getView(); + assert view.size() == 2; + assert view.containsMember(coordinator.getLocalAddress()); + assert view.containsMember(channel.getLocalAddress()); + + view = channel.getView(); + assert view.size() == 2; + assert view.containsMember(coordinator.getLocalAddress()); + assert view.containsMember(channel.getLocalAddress()); + + Message msg = msgPromise.getResult(20000); + assert msg != null; + assert "payload".equals(msg.getObject()); + + } + + private static class PromisedMessageListener extends ReceiverAdapter { + private final Promise promise; + + public PromisedMessageListener(Promise promise) { + this.promise = promise; + } + + public void receive(Message msg) { + promise.setResult(msg); + } + } +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/TUNNEL_Test.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/TUNNEL_Test.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/TUNNEL_Test.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/TUNNEL_Test.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,14 +1,19 @@ - package org.jgroups.tests; -import org.jgroups.*; +import org.jgroups.Global; +import org.jgroups.JChannel; +import org.jgroups.Message; +import org.jgroups.ReceiverAdapter; +import org.jgroups.View; import org.jgroups.protocols.MERGE2; -import org.jgroups.protocols.TUNNEL; -import org.jgroups.protocols.PING; +import org.jgroups.protocols.FD; +import org.jgroups.protocols.FD_ALL; +import org.jgroups.protocols.pbcast.GMS; import org.jgroups.stack.GossipRouter; import org.jgroups.stack.ProtocolStack; import org.jgroups.util.Promise; +import org.jgroups.util.StackType; import org.jgroups.util.Util; import org.testng.annotations.AfterClass; import org.testng.annotations.AfterMethod; @@ -22,9 +27,8 @@ * * @author Ovidiu Feodorov * @author Bela Ban belaban@yahoo.com - * @version $Id: TUNNEL_Test.java,v 1.1 2008/10/31 09:20:11 belaban Exp $ **/ -@Test(groups=Global.STACK_INDEPENDENT,sequential=true) +@Test(groups={Global.STACK_INDEPENDENT, Global.GOSSIP_ROUTER},sequential=true) public class TUNNEL_Test extends ChannelTestBase{ private JChannel channel, coordinator; private final static String GROUP="TUNNEL_Test"; @@ -33,16 +37,24 @@ @BeforeClass void startRouter() throws Exception { - gossipRouter=new GossipRouter(); + String bind_addr=Util.getProperty(Global.BIND_ADDR); + if(bind_addr == null) { + StackType type=Util.getIpStackType(); + if(type == StackType.IPv6) + bind_addr="::1"; + else + bind_addr="127.0.0.1"; + } + gossipRouter=new GossipRouter(12001, bind_addr); gossipRouter.start(); } - @AfterClass + @AfterClass(alwaysRun=true) void stopRouter() throws Exception { gossipRouter.stop(); } - @AfterMethod + @AfterMethod(alwaysRun=true) void tearDown() throws Exception { Util.close(channel, coordinator); } @@ -57,9 +69,9 @@ channel = new JChannel(props); setProps(channel); channel.connect(GROUP); - assert channel.getLocalAddress() != null; + assert channel.getAddress() != null; channel.disconnect(); - assert channel.getLocalAddress() == null; + assert channel.getAddress() == null; } @@ -75,7 +87,7 @@ channel.connect("DisconnectTest.testgroup-2"); View view=channel.getView(); assert view.size() == 1; - assert view.containsMember(channel.getLocalAddress()); + assert view.containsMember(channel.getAddress()); } @@ -96,8 +108,8 @@ channel.connect(GROUP); View view=channel.getView(); assert view.size() == 2; - assert view.containsMember(channel.getLocalAddress()); - assert view.containsMember(coordinator.getLocalAddress()); + assert view.containsMember(channel.getAddress()); + assert view.containsMember(coordinator.getAddress()); } @@ -110,7 +122,7 @@ * to multicast messages. **/ public void testDisconnectConnectSendTwo_Default() throws Exception { - final Promise msgPromise=new Promise(); + final Promise msgPromise=new Promise(); coordinator=new JChannel(props); setProps(coordinator); coordinator.connect(GROUP); @@ -124,7 +136,7 @@ channel.send(new Message(null, null, "payload")); - Message msg=(Message)msgPromise.getResult(20000); + Message msg=msgPromise.getResult(20000); assert msg != null; assert "payload".equals(msg.getObject()); } @@ -142,8 +154,62 @@ channel.connect("DisconnectTest.testgroup-2"); View view=channel.getView(); assert view.size() == 1; - assert view.containsMember(channel.getLocalAddress()); + assert view.containsMember(channel.getAddress()); } + + public void testFailureDetection() throws Exception { + coordinator=new JChannel(props); + coordinator.setName("coord"); + setProps(coordinator); + coordinator.connect(GROUP); + + channel=new JChannel(props); + channel.setName("participant"); + setProps(channel); + channel.connect(GROUP); + + System.out.println("shutting down the participant channel"); + Util.shutdown(channel); + + GMS coord_gms=(GMS)coordinator.getProtocolStack().findProtocol(GMS.class); + if(coord_gms != null) + coord_gms.setLevel("trace"); + + View view; + long end_time=System.currentTimeMillis() + 10000; + while(System.currentTimeMillis() < end_time) { + view=coordinator.getView(); + if(view.size() == 1) + break; + Util.sleep(500); + } + view=coordinator.getView(); + assert view.size() == 1 : "coordinator's view is " + view + ", but we expected a view of 1 member"; + if(coord_gms != null) + coord_gms.setLevel("warn"); + } + + public void testConnectThree() throws Exception { + coordinator=new JChannel(props); + setProps(coordinator); + + channel=new JChannel(props); + setProps(channel); + + coordinator.connect(GROUP); + channel.connect(GROUP); + + JChannel third = new JChannel (props); + third.connect(GROUP); + + View view=channel.getView(); + assert channel.getView().size() == 3; + assert third.getView().size() == 3; + assert view.containsMember(channel.getAddress()); + assert view.containsMember(coordinator.getAddress()); + + Util.close(third); + } /** @@ -164,8 +230,8 @@ View view=channel.getView(); assert view.size() == 2; - assert view.containsMember(channel.getLocalAddress()); - assert view.containsMember(coordinator.getLocalAddress()); + assert view.containsMember(channel.getAddress()); + assert view.containsMember(coordinator.getAddress()); } @@ -203,24 +269,24 @@ merge.setMinInterval(1000); merge.setMaxInterval(3000); } - - TUNNEL tunnel=(TUNNEL)stack.getTransport(); - if(tunnel != null) { - tunnel.setReconnectInterval(2000); + FD fd=(FD)stack.findProtocol(FD.class); + if(fd != null) { + fd.setTimeout(1000); + fd.setMaxTries(2); } - - PING ping=(PING)stack.findProtocol(PING.class); - if(ping != null) { - ping.setGossipRefresh(1000); + FD_ALL fd_all=(FD_ALL)stack.findProtocol(FD_ALL.class); + if(fd_all != null) { + fd_all.setTimeout(2000); + fd_all.setInterval(600); } } private static class PromisedMessageListener extends ReceiverAdapter { - private final Promise promise; + private final Promise promise; - public PromisedMessageListener(Promise promise) { + public PromisedMessageListener(Promise promise) { this.promise=promise; } @@ -229,4 +295,4 @@ } } -} +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/UnicastEnableToTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/UnicastEnableToTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/UnicastEnableToTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/UnicastEnableToTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,9 +1,12 @@ package org.jgroups.tests; import org.jgroups.*; -import org.jgroups.stack.IpAddress; +import org.jgroups.stack.Protocol; +import org.jgroups.protocols.UNICAST; +import org.jgroups.protocols.UNICAST2; +import org.jgroups.util.UUID; import org.jgroups.util.Util; -import org.testng.Assert; +import org.jgroups.util.AgeOutCache; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -15,17 +18,27 @@ /** * Tests sending of unicasts to members not in the group (http://jira.jboss.com/jira/browse/JGRP-357) * @author Bela Ban - * @version $Id: UnicastEnableToTest.java,v 1.9 2008/08/08 17:07:11 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=true) public class UnicastEnableToTest extends ChannelTestBase { private JChannel c1=null, c2=null; private static final String GROUP="UnicastEnableToTest"; + AgeOutCache cache; @BeforeMethod protected void setUp() throws Exception { c1=createChannel(true); c1.connect(GROUP); + Protocol prot=c1.getProtocolStack().findProtocol(UNICAST.class, UNICAST2.class); + if(prot instanceof UNICAST) + cache=((UNICAST)prot).getAgeOutCache(); + else if(prot instanceof UNICAST2) + cache=((UNICAST2)prot).getAgeOutCache(); + else + throw new Exception("Neither UNICAST nor UNICAST2 are present in the stack"); + + if(cache != null) + cache.setTimeout(1000); } @AfterMethod @@ -35,14 +48,16 @@ public void testUnicastMessageToUnknownMember() throws Exception { - IpAddress addr=new IpAddress("127.0.0.1", 8976); + Address addr=UUID.randomUUID(); System.out.println("sending message to non-existing destination " + addr); - try { - c1.send(new Message(addr, null, "Hello world")); - assert false : "we should not get here; sending of message to " + addr + " should have failed"; - } - catch(IllegalArgumentException ex) { - System.out.println("received exception as expected"); + c1.send(new Message(addr, null, "Hello world")); + if(cache != null) { + System.out.println("age out cache:\n" + cache); + assert cache.size() == 1; + } + Util.sleep(1500); + if(cache != null) { + assert cache.size() == 0; } } @@ -50,15 +65,19 @@ public void testUnicastMessageToExistingMember() throws Exception { c2=createChannel(c1); c2.connect(GROUP); - Assert.assertEquals(2, c2.getView().size()); + assert 2 == c2.getView().size() : " view=" + c2.getView(); MyReceiver receiver=new MyReceiver(); c2.setReceiver(receiver); - Address dest=c2.getLocalAddress(); + Address dest=c2.getAddress(); c1.send(new Message(dest, null, "hello")); + if(cache != null) { + System.out.println("age out cache:\n" + cache); + assert cache.size() == 0; + } Util.sleep(500); List list=receiver.getMsgs(); System.out.println("channel2 received the following msgs: " + list); - Assert.assertEquals(1, list.size()); + assert 1 == list.size(); receiver.reset(); } @@ -66,30 +85,21 @@ public void testUnicastMessageToLeftMember() throws Exception { c2=createChannel(c1); c2.connect(GROUP); - Assert.assertEquals(2, c2.getView().size()); - Address dest=c2.getLocalAddress(); + assert 2 == c2.getView().size() : "view=" + c2.getView(); + Address dest=c2.getAddress(); c2.close(); Util.sleep(100); - try { - c1.send(new Message(dest, null, "hello")); - assert false : "we should not come here as message to previous member " + dest + " should throw exception"; - } - catch(IllegalArgumentException ex) { - System.out.println("got an exception, as expected"); - } + c1.send(new Message(dest, null, "hello")); + if(cache != null) { + System.out.println("age out cache:\n" + cache); + assert cache.size() == 1; + } + Util.sleep(1500); + if(cache != null) + assert cache.size() == 0 : "cache size is " + cache.size(); } - public void testUnicastMessageToLeftMemberWithEnableUnicastToEvent() throws Exception { - c2=createChannel(c1); - c2.connect(GROUP); - Assert.assertEquals(2, c2.getView().size()); - Address dest=c2.getLocalAddress(); - c2.close(); - Util.sleep(100); - c1.down(new Event(Event.ENABLE_UNICASTS_TO, dest)); - c1.send(new Message(dest, null, "hello")); - } private static class MyReceiver extends ExtendedReceiverAdapter { diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/UnicastLoopbackTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/UnicastLoopbackTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/UnicastLoopbackTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/UnicastLoopbackTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -13,7 +13,6 @@ * Tests unicasts to self (loopback of transport protocol) * @author Richard Achmatowicz 12 May 2008 * @author Bela Ban Dec 31 2003 - * @version $Id: UnicastLoopbackTest.java,v 1.14 2008/08/08 17:07:11 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=true) public class UnicastLoopbackTest extends ChannelTestBase { @@ -54,7 +53,7 @@ channel.setReceiver(receiver) ; channel.connect("demo-group") ; - Address local_addr=channel.getLocalAddress(); + Address local_addr=channel.getAddress(); // set the loopback property on transport setLoopbackProperty(channel, true) ; diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/UNICAST_OOB_Test.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/UNICAST_OOB_Test.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/UNICAST_OOB_Test.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/UNICAST_OOB_Test.java 2011-10-18 11:22:35.000000000 +0000 @@ -8,7 +8,9 @@ import org.jgroups.ReceiverAdapter; import org.jgroups.protocols.DISCARD_PAYLOAD; import org.jgroups.protocols.UNICAST; +import org.jgroups.protocols.UNICAST2; import org.jgroups.stack.ProtocolStack; +import org.jgroups.stack.Protocol; import org.jgroups.util.Util; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; @@ -21,22 +23,21 @@ /** * Tests the UNICAST protocol for OOB msgs, tests http://jira.jboss.com/jira/browse/JGRP-377 * @author Bela Ban - * @version $Id: UNICAST_OOB_Test.java,v 1.9 2008/08/08 17:07:12 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=true) public class UNICAST_OOB_Test extends ChannelTestBase { - JChannel ch1, ch2; + JChannel c1, c2; @BeforeMethod void setUp() throws Exception { - ch1=createChannel(true, 2); - ch2=createChannel(ch1); + c1=createChannel(true, 2); + c2=createChannel(c1); } @AfterMethod void tearDown() throws Exception { - Util.close(ch2, ch1); + Util.close(c2, c1); } @@ -50,42 +51,60 @@ /** + * Check that 4 is received before 3 */ private void sendMessages(boolean oob) throws Exception { DISCARD_PAYLOAD prot1=new DISCARD_PAYLOAD(); MyReceiver receiver=new MyReceiver(); - ch2.setReceiver(receiver); + c2.setReceiver(receiver); - // the second channel will discard the unicast messages with seqno #3 two times, the let them pass - ch2.getProtocolStack().insertProtocol(prot1, ProtocolStack.BELOW, UNICAST.class); + // the first channel will discard the unicast messages with seqno #3 two times, the let them pass down + ProtocolStack stack=c1.getProtocolStack(); + Protocol neighbor=stack.findProtocol(UNICAST.class, UNICAST2.class); + System.out.println("Found unicast protocol " + neighbor.getClass().getSimpleName()); + stack.insertProtocolInStack(prot1, neighbor, ProtocolStack.BELOW); + + c1.connect("UNICAST_OOB_Test"); + c2.connect("UNICAST_OOB_Test"); + assert c2.getView().size() == 2 : "ch2.view is " + c2.getView(); - ch1.connect("UNICAST_OOB_Test"); - ch2.connect("UNICAST_OOB_Test"); - assert ch2.getView().size() == 2 : "ch2.view is " + ch2.getView(); - - Address dest=ch2.getLocalAddress(); + Address dest=c2.getAddress(); for(int i=1; i <=5; i++) { Message msg=new Message(dest, null, new Long(i)); if(i == 4 && oob) msg.setFlag(Message.OOB); System.out.println("-- sending message #" + i); - ch1.send(msg); + c1.send(msg); Util.sleep(100); } - Util.sleep(5000); // wait until retransmission of seqno #3 happens, so that 4 and 5 are received as well + // wait until retransmission of seqno #3 happens, so that 4 and 5 are received as well + long target_time=System.currentTimeMillis() + 5000; + do { + if(receiver.size() >= 5) + break; + Util.sleep(500); + } + while(target_time > System.currentTimeMillis()); + List seqnos=receiver.getSeqnos(); System.out.println("sequence numbers: " + seqnos); - // expected sequence is: 1 2 4 3 5 ! Reason: 4 is sent OOB, does *not* wait until 3 has been retransmitted !! - Long[] expected_seqnos=oob? - new Long[]{new Long(1), new Long(2), new Long(4), new Long(3), new Long(5)} : // OOB - new Long[]{new Long(1), new Long(2), new Long(3), new Long(4), new Long(5)}; // regular - for(int i=0; i < expected_seqnos.length; i++) { - Long expected_seqno=expected_seqnos[i]; - Long received_seqno=seqnos.get(i); - assert expected_seqno.equals(received_seqno); + if(!oob) { + for(int i=0; i < 5; i++) + assert seqnos.get(i) == i+1 : " seqno is " + seqnos.get(i) + ", but expected " + i+1; + } + else { + // 4 needs to be received before 3. Reason: 4 is sent OOB, does *not* wait until 3 has been retransmitted ! + int index_3=-1, index_4=-1; + for(int i=0; i < 5; i++) { + if(seqnos.get(i) == 3) + index_3=i; + if(seqnos.get(i) == 4) + index_4=i; + } + assert index_4 < index_3 : "4 must come before 3 in list " + seqnos; } } @@ -109,6 +128,8 @@ seqnos.add(num); } } + + public int size() {return seqnos.size();} } diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/UnicastUnitTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/UnicastUnitTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/UnicastUnitTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/UnicastUnitTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -11,7 +11,6 @@ /** * Tests unicast functionality * @author Bela Ban - * @version $Id: UnicastUnitTest.java,v 1.7 2008/08/08 17:07:12 vlada Exp $ */ @Test(groups=Global.STACK_DEPENDENT,sequential=false) public class UnicastUnitTest extends ChannelTestBase { @@ -60,7 +59,7 @@ } public void viewAccepted(View new_view) { - Address local_addr=channel.getLocalAddress(); + Address local_addr=channel.getAddress(); assertNotNull(local_addr); System.out.println("[" + local_addr + "]: " + new_view); List
            members=new LinkedList
            (new_view.getMembers()); diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/UUIDCacheClearTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/UUIDCacheClearTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/UUIDCacheClearTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/UUIDCacheClearTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,114 @@ +package org.jgroups.tests; + + +import org.jgroups.*; +import org.jgroups.util.Util; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * Test whether physical addresses are fetched correctly after the UUID-physical address cache has been cleared + * @author Bela Ban + */ +@Test(groups=Global.STACK_DEPENDENT,sequential=true) +public class UUIDCacheClearTest extends ChannelTestBase { + + + @Test + public void testCacheClear() throws Exception { + JChannel c1=null, c2=null; + Address c1_addr, c2_addr; + MyReceiver r1=new MyReceiver(), r2=new MyReceiver(); + + try { + c1=createChannel(true, 2); + c1.setReceiver(r1); + c1.connect("testCacheClear"); + c2=createChannel(c1); + c2.setReceiver(r2); + c2.connect("testCacheClear"); + + assert c2.getView().size() == 2 : "view is " + c2.getView(); + + c1_addr=c1.getAddress(); + c2_addr=c2.getAddress(); + + // send one unicast message from c1 to c2 and vice versa + c1.send(c2_addr, null, "one"); + c2.send(c1_addr, null, "one"); + + List c1_list=r1.getList(); + List c2_list=r2.getList(); + + for(int i=0; i < 10; i++) { // poor man's way of waiting until we have 1 message in each receiver... :-) + if(!c1_list.isEmpty() && !c2_list.isEmpty()) + break; + Util.sleep(500); + } + assert c1_list.size() == 1 && c2_list.size() == 1; + + // now clear the caches and send message "two" + printCaches(c1, c2); + + System.out.println("clearing the caches"); + clearCache(c1,c2); + printCaches(c1, c2); + + + r1.clear(); + r2.clear(); + + // send one unicast message from c1 to c2 and vice versa + c1.send(c2_addr, null, "two"); + c2.send(c1_addr, null, "two"); + for(int i=0; i < 10; i++) { // poor man's way of waiting until we have 1 message in each receiver... :-) + if(!c1_list.isEmpty() && !c2_list.isEmpty()) + break; + Util.sleep(1000); + } + assert c1_list.size() == 1 && c2_list.size() == 1; + Message msg_from_1=c2_list.get(0); + Message msg_from_2=c1_list.get(0); + + assert msg_from_1.getSrc().equals(c1_addr); + assert msg_from_1.getObject().equals("two"); + + assert msg_from_2.getSrc().equals(c2_addr); + assert msg_from_2.getObject().equals("two"); + } + finally { + Util.close(c2, c1); + } + } + + private static void clearCache(JChannel ... channels) { + for(JChannel ch: channels) { + ch.getProtocolStack().getTransport().clearLogicalAddressCache(); + ch.down(new Event(Event.SET_LOCAL_ADDRESS, ch.getAddress())); + } + } + + private static void printCaches(JChannel ... channels) { + System.out.println("chaches:\n"); + for(JChannel ch: channels) { + System.out.println(ch.getAddress() + ":\n" + + ch.getProtocolStack().getTransport().printLogicalAddressCache()); + } + } + + private static class MyReceiver extends ReceiverAdapter { + private final List msgs=new ArrayList(4); + + public void receive(Message msg) { + msgs.add(msg); + } + + public void clear() {msgs.clear();} + public List getList() {return msgs;} + } + + + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/VirtualSynchronyTest.java libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/VirtualSynchronyTest.java --- libjgroups-java-2.7.0.GA/tests/junit/org/jgroups/tests/VirtualSynchronyTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit/org/jgroups/tests/VirtualSynchronyTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -30,7 +30,6 @@ * * * @author Vladimir Blagojevic - * @version $Id$ * */ @Test(groups=Global.STACK_INDEPENDENT,sequential=true) @@ -111,7 +110,7 @@ public String getAddress() { if(ch!=null && ch.isConnected()) { - return ch.getLocalAddress().toString(); + return ch.getAddress().toString(); } else { @@ -189,7 +188,7 @@ } } } else if (m instanceof String) { - assert currentView.getVid().getId() == Long.parseLong((String)m) : ch.getLocalAddress() + assert currentView.getVid().getId() == Long.parseLong((String)m) : ch.getAddress() + " received message from the wrong view. Message sender was " + msg.getSrc(); numberOfMessagesInView++; } @@ -199,7 +198,7 @@ View tmpView = (View) msg; if (currentView != null) { payload = new VSynchPayload(currentView.getVid(), - numberOfMessagesInView,ch.getLocalAddress()); + numberOfMessagesInView,ch.getAddress()); ch.send(tmpView.getMembers().get(0), null, payload); } numberOfMessagesInView = 0; diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/blocks/ConnectionMapTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/blocks/ConnectionMapTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/blocks/ConnectionMapTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/blocks/ConnectionMapTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,217 @@ +package org.jgroups.blocks; + +import org.jgroups.Address; +import org.jgroups.Global; +import org.jgroups.stack.IpAddress; +import org.jgroups.util.DefaultThreadFactory; +import org.jgroups.util.ResourceManager; +import org.jgroups.util.StackType; +import org.jgroups.util.Util; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + + +/** + * Tests ConnectionMap + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL,sequential=true) +public class ConnectionMapTest { + private TCPConnectionMap ct1, ct2; + static final InetAddress loopback_addr; + + static { + try { + StackType type=Util.getIpStackType(); + String tmp=type == StackType.IPv6? "::1" : "127.0.0.1"; + loopback_addr=InetAddress.getByName(tmp); + } + catch(UnknownHostException e) { + throw new RuntimeException("failed initializing loopback_addr", e); + } + } + + static byte[] data=new byte[]{'b', 'e', 'l', 'a'}; + + + protected int PORT1, PORT2; + protected Address addr1, addr2; + + + @BeforeMethod + protected void init() throws Exception { + List ports=ResourceManager.getNextTcpPorts(loopback_addr, 2); + PORT1=ports.get(0); + PORT2=ports.get(1); + addr1=new IpAddress(loopback_addr, PORT1); + addr2=new IpAddress(loopback_addr, PORT2); + } + + + + @AfterMethod + protected void tearDown() throws Exception { + if(ct2 != null) { + ct2.stop(); + ct2=null; + } + if(ct1 != null) { + ct1.stop(); + ct1=null; + } + } + + /** + * A connects to B and B connects to A at the same time. This test makes sure we only have one connection, + * not two, e.g. a spurious connection. Tests http://jira.jboss.com/jira/browse/JGRP-549.

            + * Turned concurrent test into a simple sequential test. We're going to replace this code with NIO2 soon anyway... + */ + public void testReuseOfConnection() throws Exception { + TCPConnectionMap.Receiver dummy=new TCPConnectionMap.Receiver() { + public void receive(Address sender, byte[] data, int offset, int length) {} + }; + + ct1=new TCPConnectionMap("ConnectionMapTest1", + new DefaultThreadFactory(Util.getGlobalThreadGroup(), "ConnectionMapTest", true), + null, dummy, loopback_addr, null, PORT1, PORT1); + ct1.start(); + + ct2=new TCPConnectionMap("ConnectionMapTest2", + new DefaultThreadFactory(Util.getGlobalThreadGroup(), "ConnectionMapTest", true), + null, dummy, loopback_addr, null, PORT2, PORT2); + ct2.start(); + + int num_conns; + num_conns=ct1.getNumConnections(); + assert num_conns == 0; + num_conns=ct2.getNumConnections(); + assert num_conns == 0; + + ct1.send(addr2, data, 0, data.length); + ct2.send(addr1, data, 0, data.length); + + String msg="ct1: " + ct1 + "\nct2: " + ct2; + System.out.println(msg); + + num_conns=ct1.getNumConnections(); + assert num_conns == 1 : "num_conns for ct1 is " + num_conns + ", " + msg; + num_conns=ct2.getNumConnections(); + assert num_conns == 1 : "num_conns for ct2 is " + num_conns + ", " + msg; + + assert ct1.connectionEstablishedTo(addr2) : "valid connection to peer"; + assert ct2.connectionEstablishedTo(addr1) : "valid connection to peer"; + } + + + + + public static void testBlockingQueue() { + final BlockingQueue queue=new LinkedBlockingQueue(); + + Thread taker=new Thread() { + + public void run() { + try { + System.out.println("taking an element from the queue"); + queue.take(); + System.out.println("clear"); + } + catch(InterruptedException e) { + } + } + }; + taker.start(); + + Util.sleep(500); + + queue.clear(); // does this release the taker thread ? + Util.interruptAndWaitToDie(taker); + assert !(taker.isAlive()) : "taker: " + taker; + } + + + public void testStopConnectionMapNoSendQueues() throws Exception { + ct1=new TCPConnectionMap("ConnectionMapTest1", + new DefaultThreadFactory(Util.getGlobalThreadGroup(), "ConnectionMapTest", true), + new DummyReceiver(), loopback_addr, null, PORT1, PORT1, 60000, 120000); + ct1.setUseSendQueues(false); + ct1.start(); + ct2=new TCPConnectionMap("ConnectionMapTest2", + new DefaultThreadFactory(Util.getGlobalThreadGroup(), "ConnectionMapTest", true), + new DummyReceiver(), loopback_addr, null, PORT2, PORT2, 60000, 120000); + ct2.setUseSendQueues(false); + ct2.start(); + _testStop(ct1, ct2); + } + + public void testStopConnectionMapWithSendQueues() throws Exception { + ct1=new TCPConnectionMap("ConnectionMapTest1", + new DefaultThreadFactory(Util.getGlobalThreadGroup(), "ConnectionMapTest", true), + new DummyReceiver(), loopback_addr, null, PORT1, PORT1, 60000, 120000); + ct1.start(); + ct2=new TCPConnectionMap("ConnectionMapTest2", + new DefaultThreadFactory(Util.getGlobalThreadGroup(), "ConnectionMapTest", true), + new DummyReceiver(), loopback_addr, null, PORT2, PORT2, 60000, 120000); + ct2.start(); + _testStop(ct1, ct2); + } + + + /* public void testStopConnectionMapNIONoSendQueues() throws Exception { + ct1=new ConnectionTableNIO(new DummyReceiver(), loopback_addr, null, PORT1, PORT1, 60000, 120000, false); + ct1.setUseSendQueues(false); + ct2=new ConnectionTableNIO(new DummyReceiver(), loopback_addr, null, PORT2, PORT2, 60000, 120000, false); + ct2.setUseSendQueues(false); + ct1.start(); + ct2.start(); + _testStop(ct1, ct2); + } + + + public void testStopConnectionMapNIOWithSendQueues() throws Exception { + ct1=new ConnectionTableNIO(new DummyReceiver(), loopback_addr, null, PORT1, PORT1, 60000, 120000, false); + ct2=new ConnectionTableNIO(new DummyReceiver(), loopback_addr, null, PORT2, PORT2, 60000, 120000, false); + ct1.start(); + ct2.start(); + _testStop(ct1, ct2); + }*/ + + + private void _testStop(TCPConnectionMap table1, TCPConnectionMap table2) throws Exception { + table1.send(addr1, data, 0, data.length); // send to self + assert table1.getNumConnections() == 0; + table1.send(addr2, data, 0, data.length); // send to other + + table2.send(addr2, data, 0, data.length); // send to self + table2.send(addr1, data, 0, data.length); // send to other + + + System.out.println("table1:\n" + table1 + "\ntable2:\n" + table2); + + int num_conns_table1=table1.getNumConnections(), num_conns_table2=table2.getNumConnections(); + assert num_conns_table1 == 1 : "table1 should have 1 connection, but has " + num_conns_table1 + ": " + table1; + assert num_conns_table2 == 1 : "table2 should have 1 connection, but has " + num_conns_table2 + ": " + table2; + + table2.stop(); + table1.stop(); + assert table1.getNumConnections() == 0 : "table1 should have 0 connections: " + table1; + assert table2.getNumConnections() == 0 : "table2 should have 0 connections: " + table2; + } + + + + + static class DummyReceiver implements TCPConnectionMap.Receiver { + public void receive(Address sender, byte[] data, int offset, int length) { + System.out.println("-- received " + length + " bytes from " + sender); + } + } + +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/blocks/ConnectionTableTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/blocks/ConnectionTableTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/blocks/ConnectionTableTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/blocks/ConnectionTableTest.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,239 +0,0 @@ -package org.jgroups.blocks; - -import org.jgroups.Address; -import org.jgroups.Global; -import org.jgroups.blocks.BasicConnectionTable.Connection; -import org.jgroups.stack.IpAddress; -import org.jgroups.util.Util; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.Test; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.CyclicBarrier; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; - - -/** - * Tests ConnectionTable - * @author Bela Ban - * @version $Id: ConnectionTableTest.java,v 1.13 2008/11/12 13:37:58 belaban Exp $ - */ -@Test(groups=Global.FUNCTIONAL,sequential=true) -public class ConnectionTableTest { - private BasicConnectionTable ct1, ct2; - static final InetAddress loopback_addr; - - static { - try { - loopback_addr=InetAddress.getByName("127.0.0.1"); - } - catch(UnknownHostException e) { - throw new RuntimeException("failed initializing loopback_addr", e); - } - } - - static byte[] data=new byte[]{'b', 'e', 'l', 'a'}; - final static int PORT1=7521, PORT2=8931; - static final Address addr1=new IpAddress(loopback_addr, PORT1), addr2=new IpAddress(loopback_addr, PORT2); - - - - - @AfterMethod - protected void tearDown() throws Exception { - if(ct2 != null) { - ct2.stop(); - ct2=null; - } - if(ct1 != null) { - ct1.stop(); - ct1=null; - } - } - - /** - * A connects to B and B connects to A at the same time. This test makes sure we only have one connection, - * not two, e.g. a spurious connection. Tests http://jira.jboss.com/jira/browse/JGRP-549 - */ - public void testConcurrentConnect() throws Exception { - Sender sender1, sender2; - CyclicBarrier barrier=new CyclicBarrier(3); - - ct1=new ConnectionTable(loopback_addr, PORT1); - ct1.start(); - ct2=new ConnectionTable(loopback_addr, PORT2); - ct2.start(); - BasicConnectionTable.Receiver dummy=new BasicConnectionTable.Receiver() { - public void receive(Address sender, byte[] data, int offset, int length) {} - }; - ct1.setReceiver(dummy); - ct2.setReceiver(dummy); - - sender1=new Sender((ConnectionTable)ct1, barrier, addr2, 0); - sender2=new Sender((ConnectionTable)ct2, barrier, addr1, 0); - - sender1.start(); sender2.start(); - Util.sleep(100); - - int num_conns; - System.out.println("ct1: " + ct1 + "ct2: " + ct2); - num_conns=ct1.getNumConnections(); - assert num_conns == 0; - num_conns=ct2.getNumConnections(); - assert num_conns == 0; - - barrier.await(10000, TimeUnit.MILLISECONDS); - sender1.join(); - sender2.join(); - - - System.out.println("ct1: " + ct1 + "\nct2: " + ct2); - num_conns=ct1.getNumConnections(); - assert num_conns == 1 : "num_conns is " + num_conns; - num_conns=ct2.getNumConnections(); - assert num_conns == 1; - - Util.sleep(500); - - System.out.println("ct1: " + ct1 + "\nct2: " + ct2); - num_conns=ct1.getNumConnections(); - assert num_conns == 1; - num_conns=ct2.getNumConnections(); - assert num_conns == 1; - - Connection connection = ct1.getConnection(addr2); - assert !(connection.isSocketClosed()) : "valid connection to peer"; - connection = ct2.getConnection(addr1); - assert !(connection.isSocketClosed()) : "valid connection to peer"; - } - - - private static class Sender extends Thread { - final ConnectionTable conn_table; - final CyclicBarrier barrier; - final Address dest; - final long sleep_time; - - public Sender(ConnectionTable conn_table, CyclicBarrier barrier, Address dest, long sleep_time) { - this.conn_table=conn_table; - this.barrier=barrier; - this.dest=dest; - this.sleep_time=sleep_time; - } - - public void run() { - try { - barrier.await(10000, TimeUnit.MILLISECONDS); - if(sleep_time > 0) - Util.sleep(sleep_time); - conn_table.send(dest, data, 0, data.length); - } - catch(Exception e) { - } - } - } - - - public static void testBlockingQueue() { - final BlockingQueue queue=new LinkedBlockingQueue(); - - Thread taker=new Thread() { - - public void run() { - try { - System.out.println("taking an element from the queue"); - queue.take(); - System.out.println("clear"); - } - catch(InterruptedException e) { - } - } - }; - taker.start(); - - Util.sleep(500); - - queue.clear(); // does this release the taker thread ? - Util.interruptAndWaitToDie(taker); - assert !(taker.isAlive()) : "taker: " + taker; - } - - - public void testStopConnectionTableNoSendQueues() throws Exception { - ct1=new ConnectionTable(new DummyReceiver(), loopback_addr, null, PORT1, PORT1, 60000, 120000); - ct1.setUseSendQueues(false); - ct1.start(); - ct2=new ConnectionTable(new DummyReceiver(), loopback_addr, null, PORT2, PORT2, 60000, 120000); - ct2.setUseSendQueues(false); - ct2.start(); - _testStop(ct1, ct2); - } - - public void testStopConnectionTableWithSendQueues() throws Exception { - ct1=new ConnectionTable(new DummyReceiver(), loopback_addr, null, PORT1, PORT1, 60000, 120000); - ct1.start(); - ct2=new ConnectionTable(new DummyReceiver(), loopback_addr, null, PORT2, PORT2, 60000, 120000); - ct2.start(); - _testStop(ct1, ct2); - } - - - public void testStopConnectionTableNIONoSendQueues() throws Exception { - ct1=new ConnectionTableNIO(new DummyReceiver(), loopback_addr, null, PORT1, PORT1, 60000, 120000, false); - ct1.setUseSendQueues(false); - ct2=new ConnectionTableNIO(new DummyReceiver(), loopback_addr, null, PORT2, PORT2, 60000, 120000, false); - ct2.setUseSendQueues(false); - ct1.start(); - ct2.start(); - _testStop(ct1, ct2); - } - - - public void testStopConnectionTableNIOWithSendQueues() throws Exception { - ct1=new ConnectionTableNIO(new DummyReceiver(), loopback_addr, null, PORT1, PORT1, 60000, 120000, false); - ct2=new ConnectionTableNIO(new DummyReceiver(), loopback_addr, null, PORT2, PORT2, 60000, 120000, false); - ct1.start(); - ct2.start(); - _testStop(ct1, ct2); - } - - - private static void _testStop(BasicConnectionTable table1, BasicConnectionTable table2) throws Exception { - table1.send(addr1, data, 0, data.length); // send to self - assert table1.getNumConnections() == 0; - table1.send(addr2, data, 0, data.length); // send to other - - table2.send(addr2, data, 0, data.length); // send to self - table2.send(addr1, data, 0, data.length); // send to other - - - System.out.println("table1:\n" + table1 + "\ntable2:\n" + table2); - - assert table1.getNumConnections() == 1; - assert table2.getNumConnections() == 1; - - table2.stop(); - table1.stop(); - assert table1.getNumConnections() == 0; - assert table2.getNumConnections() == 0; - } - - - - - static class DummyReceiver implements ConnectionTable.Receiver { - public void receive(Address sender, byte[] data, int offset, int length) { - System.out.println("-- received " + length + " bytes from " + sender); - } - } - - static class DummyReceiverNIO implements ConnectionTableNIO.Receiver { - public void receive(Address sender, byte[] data, int offset, int length) { - System.out.println("-- received " + length + " bytes from " + sender); - } - } - -} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/blocks/GroupRequestTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/blocks/GroupRequestTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/blocks/GroupRequestTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/blocks/GroupRequestTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,22 +2,20 @@ package org.jgroups.blocks; import org.jgroups.*; -import org.jgroups.stack.IpAddress; import org.jgroups.util.RspList; import org.jgroups.util.Util; import org.testng.Assert; import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import org.testng.annotations.BeforeClass; -import java.util.Vector; -import java.util.Arrays; import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Vector; /** * @author Bela Ban - * @version $Id: GroupRequestTest.java,v 1.9 2008/04/08 12:11:46 belaban Exp $ */ @Test(groups=Global.FUNCTIONAL,sequential=true) public class GroupRequestTest { @@ -26,9 +24,9 @@ @BeforeClass void init() throws UnknownHostException { - a1=new IpAddress("127.0.0.1", 1111); - a2=new IpAddress("127.0.0.1", 2222); - a3=new IpAddress("127.0.0.1", 3333); + a1=Util.createRandomAddress(); + a2=Util.createRandomAddress(); + a3=Util.createRandomAddress(); } @BeforeMethod @@ -82,7 +80,7 @@ new Message(null, a3, new Long(3))}; MyTransport transport=new MyDelayedTransport(true, responses, 500); dests.add(a3); - GroupRequest req=new GroupRequest(new Message(), transport, dests, GroupRequest.GET_FIRST, 0, 3); + GroupRequest req=new GroupRequest(new Message(), transport, dests, new RequestOptions(Request.GET_FIRST, 0)); req.setResponseFilter(new RspFilter() { int num_rsps=0; @@ -102,7 +100,6 @@ boolean rc=req.execute(); System.out.println("group request is " + req); assert rc; - Assert.assertEquals(0, req.getSuspects().size()); assert req.isDone(); RspList results=req.getResults(); Assert.assertEquals(3, results.size()); @@ -117,7 +114,7 @@ new Message(null, a3, new Long(3))}; MyTransport transport=new MyDelayedTransport(true, responses, 500); dests.add(a3); - GroupRequest req=new GroupRequest(new Message(), transport, dests, GroupRequest.GET_ALL, 0, 3); + GroupRequest req=new GroupRequest(new Message(), transport, dests, new RequestOptions(Request.GET_ALL, 0)); req.setResponseFilter(new RspFilter() { int num_rsps=0; @@ -138,7 +135,6 @@ boolean rc=req.execute(); System.out.println("group request is " + req); assert rc; - Assert.assertEquals(0, req.getSuspects().size()); assert req.isDone(); RspList results=req.getResults(); Assert.assertEquals(3, results.size()); @@ -161,7 +157,7 @@ int destCount = 10; // total timeout to hear from all members - final long timeout = destCount * 300; + final long timeout = destCount * 1000; // how long each destination should delay final long delay = 75L; @@ -169,7 +165,7 @@ dests = new Vector

            (); for (int i = 0; i < destCount; i++) { - Address addr = new IpAddress("127.0.0.1", Integer.parseInt(String.valueOf(i) + i + i + i)); + Address addr = Util.createRandomAddress(); dests.add(addr); // how long does this simulated destination take to execute? the sum is just less than the total timeout responses[i] = new Message(null, addr, new Long(i)); @@ -178,12 +174,11 @@ MyDelayedTransport tp = new MyDelayedTransport(async, responses, delay); // instantiate request with dummy correlator - GroupRequest req=new GroupRequest(new Message(), tp, dests, GroupRequest.GET_ALL, timeout, dests.size()); + GroupRequest req=new GroupRequest(new Message(), tp, dests, new RequestOptions(Request.GET_ALL, timeout)); tp.setGroupRequest(req); boolean rc = req.execute(); System.out.println("group request is " + req); assert rc; - Assert.assertEquals(0, req.getSuspects().size()); assert req.isDone(); RspList results = req.getResults(); Assert.assertEquals(dests.size(), results.size()); @@ -194,12 +189,11 @@ private void _testMessageReception(boolean async) throws Exception { Object[] responses=new Message[]{new Message(null, a1, new Long(1)),new Message(null, a2, new Long(2))}; MyTransport transport=new MyTransport(async, responses); - GroupRequest req=new GroupRequest(new Message(), transport, dests, GroupRequest.GET_ALL, 0, 2); + GroupRequest req=new GroupRequest(new Message(), transport, dests, new RequestOptions(Request.GET_ALL, 0)); transport.setGroupRequest(req); boolean rc=req.execute(); System.out.println("group request is " + req); assert rc; - Assert.assertEquals(0, req.getSuspects().size()); assert req.isDone(); RspList results=req.getResults(); Assert.assertEquals(2, results.size()); @@ -208,12 +202,11 @@ private void _testMessageReceptionWithSuspect(boolean async) throws Exception { Object[] responses=new Object[]{new Message(null, a1, new Long(1)), new SuspectEvent(a2)}; MyTransport transport=new MyTransport(async, responses); - GroupRequest req=new GroupRequest(new Message(), transport, dests, GroupRequest.GET_ALL, 0, 2); + GroupRequest req=new GroupRequest(new Message(), transport, dests, new RequestOptions(Request.GET_ALL, 0)); transport.setGroupRequest(req); boolean rc=req.execute(); System.out.println("group request is " + req); assert rc; - assert req.getSuspects().size() == 1; assert req.isDone(); RspList results=req.getResults(); assert results.size() == 2; @@ -224,17 +217,16 @@ Vector
            new_dests=new Vector
            (); new_dests.add(a1); new_dests.add(a2); - new_dests.add(new IpAddress("127.0.0.1", 3333)); + new_dests.add(a1); Object[] responses=new Object[]{new Message(null, a1, new Long(1)), - new View(new IpAddress("127.0.0.1", 9999), 322649, new_dests), + new View(Util.createRandomAddress(), 322649, new_dests), new Message(null, a2, new Long(2))}; MyTransport transport=new MyTransport(async, responses); - GroupRequest req=new GroupRequest(new Message(), transport, dests, GroupRequest.GET_ALL, 0, 2); + GroupRequest req=new GroupRequest(new Message(), transport, dests, new RequestOptions(Request.GET_ALL, 0)); transport.setGroupRequest(req); boolean rc=req.execute(); System.out.println("group request is " + req); assert rc; - Assert.assertEquals(req.getSuspects().size(), 0, "suspects are " + req.getSuspects()); assert req.isDone(); RspList results=req.getResults(); Assert.assertEquals(2, results.size()); @@ -245,16 +237,15 @@ Vector
            new_dests=new Vector
            (); new_dests.add(a2); Object[] responses=new Object[]{new Message(null, a2, new Long(1)), - new View(new IpAddress("127.0.0.1", 9999), 322649, new_dests)}; + new View(Util.createRandomAddress(), 322649, new_dests)}; MyTransport transport=new MyTransport(async, responses); - GroupRequest req=new GroupRequest(new Message(), transport, dests, GroupRequest.GET_ALL, 0, 2); + GroupRequest req=new GroupRequest(new Message(), transport, dests, new RequestOptions(Request.GET_ALL, 0)); transport.setGroupRequest(req); System.out.println("group request before execution: " + req); boolean rc=req.execute(); System.out.println("group request after execution: " + req); assert rc; - Assert.assertEquals(req.getSuspects().size(), 1, "suspects are " + req.getSuspects()); assert req.isDone(); RspList results=req.getResults(); Assert.assertEquals(2, results.size()); diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/blocks/LazyRemovalCacheTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/blocks/LazyRemovalCacheTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/blocks/LazyRemovalCacheTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/blocks/LazyRemovalCacheTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,147 @@ +package org.jgroups.blocks; + +import org.testng.annotations.Test; +import org.jgroups.Global; +import org.jgroups.util.UUID; +import org.jgroups.util.Util; + +import java.util.Arrays; +import java.util.List; + +/** + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL,sequential=false) +public class LazyRemovalCacheTest { + + public static void testAdd() { + LazyRemovalCache cache=new LazyRemovalCache(); + UUID uuid=UUID.randomUUID(); + cache.add(uuid, "node-1"); + System.out.println("cache = " + cache); + assert 1 == cache.size(); + String val=cache.get(uuid); + assert val != null && val.equals("node-1"); + + cache.remove(uuid); + System.out.println("cache = " + cache); + } + + public static void testRemoveAll() { + LazyRemovalCache cache=new LazyRemovalCache(10, 0); + List list=Arrays.asList(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()); + int cnt=1; + for(UUID uuid: list) + cache.add(uuid, "node-" + cnt++); + UUID uuid1=UUID.randomUUID(); + UUID uuid2=UUID.randomUUID(); + cache.add(uuid1, "foo"); + cache.add(uuid2, "bar"); + System.out.println("cache = " + cache); + assert cache.size() == 5; + + System.out.println("removing " + list); + cache.removeAll(list); + System.out.println("cache = " + cache); + assert cache.size() == 5; + assert cache.get(uuid1).equals("foo"); + assert cache.get(uuid2).equals("bar"); + + cache.removeMarkedElements(); + System.out.println("cache = " + cache); + assert cache.size() == 2; + assert cache.get(uuid1).equals("foo"); + assert cache.get(uuid2).equals("bar"); + } + + public static void testRetainAll() { + LazyRemovalCache cache=new LazyRemovalCache(10, 0); + List list=Arrays.asList(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()); + int cnt=1; + for(UUID uuid: list) + cache.add(uuid, "node-" + cnt++); + UUID uuid1=UUID.randomUUID(); + UUID uuid2=UUID.randomUUID(); + cache.add(uuid1, "foo"); + cache.add(uuid2, "bar"); + System.out.println("cache = " + cache); + assert cache.size() == 5; + + List retain=Arrays.asList(uuid1, uuid2); + System.out.println("retaining " + retain); + cache.retainAll(retain); + System.out.println("cache = " + cache); + assert cache.size() == 5; + assert cache.get(uuid1).equals("foo"); + assert cache.get(uuid2).equals("bar"); + + cache.removeMarkedElements(); + System.out.println("cache = " + cache); + assert cache.size() == 2; + assert cache.get(uuid1).equals("foo"); + assert cache.get(uuid2).equals("bar"); + } + + + public static void testRemovalOnExceedingMaxSize() { + LazyRemovalCache cache=new LazyRemovalCache(2, 0); + UUID u1=UUID.randomUUID(), u2=UUID.randomUUID(), u3=UUID.randomUUID(), u4=UUID.randomUUID(); + cache.add(u1, "u1"); cache.add(u2, "u2"); + assert cache.size() == 2; + cache.add(u3, "u3"); cache.add(u4, "u4"); + System.out.println("cache = " + cache); + assert cache.size() == 4; + + cache.remove(u3); + System.out.println("cache = " + cache); + assert cache.size() == 3; + + cache.remove(u1); + System.out.println("cache = " + cache); + assert cache.size() == 2; + + cache.remove(u4); + System.out.println("cache = " + cache); + assert cache.size() == 2; + + cache.removeMarkedElements(); + System.out.println("cache = " + cache); + assert cache.size() == 1; + } + + + public static void testRemovalOnExceedingMaxSizeAndMaxTime() { + LazyRemovalCache cache=new LazyRemovalCache(2, 1000); + UUID u1=UUID.randomUUID(), u2=UUID.randomUUID(), u3=UUID.randomUUID(), u4=UUID.randomUUID(); + cache.add(u1, "u1"); cache.add(u2, "u2"); + assert cache.size() == 2; + cache.add(u3, "u3"); cache.add(u4, "u4"); + System.out.println("cache = " + cache); + assert cache.size() == 4; + + cache.remove(u3); + System.out.println("cache = " + cache); + assert cache.size() == 4; + + cache.remove(u1); + System.out.println("cache = " + cache); + assert cache.size() == 4; + + cache.remove(u4); + System.out.println("cache = " + cache); + assert cache.size() == 4; + + cache.removeMarkedElements(); + System.out.println("cache = " + cache); + assert cache.size() == 4; + + System.out.println("sleeping for 1 sec"); + Util.sleep(1100); + cache.remove(u4); + System.out.println("cache = " + cache); + assert cache.size() == 1; + + } + + +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/blocks/LazyRemovalSetTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/blocks/LazyRemovalSetTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/blocks/LazyRemovalSetTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/blocks/LazyRemovalSetTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,177 @@ +package org.jgroups.blocks; + +import org.jgroups.Address; +import org.jgroups.Global; +import org.jgroups.util.UUID; +import org.jgroups.util.Util; +import org.testng.annotations.Test; + +import java.util.Arrays; +import java.util.List; + +/** + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL,sequential=false) +public class LazyRemovalSetTest { + + public static void testAdd() { + LazyRemovalSet cache=new LazyRemovalSet(); + UUID uuid=UUID.randomUUID(); + cache.add(uuid); + System.out.println("cache = " + cache); + assert 1 == cache.size(); + assert cache.contains(uuid); + + cache.remove(uuid); + System.out.println("cache = " + cache); + + assert cache.contains(uuid); + } + + public static void testRemoveAll() { + LazyRemovalSet cache=new LazyRemovalSet(10, 0); + cache.add("one", "two", "three", "four", "five", "two"); + System.out.println("cache = " + cache); + assert cache.size() == 5; + + List list=Arrays.asList("four", "two"); + System.out.println("removing " + list); + cache.removeAll(list); + System.out.println("cache = " + cache); + assert cache.size() == 5; + + cache.removeMarkedElements(); + System.out.println("cache = " + cache); + assert cache.size() == 3; + assert cache.contains("one"); + assert cache.contains("three"); + assert cache.contains("five"); + } + + public static void testRetainAll() { + LazyRemovalSet cache=new LazyRemovalSet(10, 0); + cache.add("one", "two", "three"); + System.out.println("cache = " + cache); + assert cache.size() == 3; + + List retain=Arrays.asList("two", "three"); + System.out.println("retaining " + retain); + cache.retainAll(retain); + System.out.println("cache = " + cache); + assert cache.size() == 3; + assert cache.contains("two"); + assert cache.contains("three"); + + cache.removeMarkedElements(); + System.out.println("cache = " + cache); + assert cache.size() == 2; + } + + + public static void testRemovalOnExceedingMaxSize() { + LazyRemovalSet cache=new LazyRemovalSet(2, 0); + cache.add("u1", "u2", "u3", "u4"); + assert cache.size() == 4; + + cache.remove("u3"); + System.out.println("cache = " + cache); + assert cache.size() == 3; + + cache.remove("u1"); + System.out.println("cache = " + cache); + assert cache.size() == 2; + + cache.remove("u4"); + System.out.println("cache = " + cache); + assert cache.size() == 2; + + cache.removeMarkedElements(); + System.out.println("cache = " + cache); + assert cache.size() == 1; + } + + + public static void testRemovalOnExceedingMaxSizeAndMaxTime() { + LazyRemovalSet cache=new LazyRemovalSet(2, 1000); + cache.add("u1", "u2", "u3", "u4"); + System.out.println("cache = " + cache); + assert cache.size() == 4; + + cache.remove("u3"); + System.out.println("cache = " + cache); + assert cache.size() == 4; + + cache.remove("u1"); + System.out.println("cache = " + cache); + assert cache.size() == 4; + + cache.remove("u4"); + System.out.println("cache = " + cache); + assert cache.size() == 4; + + cache.removeMarkedElements(); + System.out.println("cache = " + cache); + assert cache.size() == 4; + + System.out.println("sleeping for 1 sec"); + Util.sleep(1100); + cache.remove("u4"); + System.out.println("cache = " + cache); + assert cache.size() == 1; + } + + + public static void testCapacityExceeded() { + LazyRemovalSet cache=new LazyRemovalSet(5, 0); + for(int i=1; i <=10; i++) + cache.add(i); + System.out.println("cache = " + cache); + assert cache.size() == 10; + cache.retainAll(Arrays.asList(1,4,6,8)); + cache.add(11); + System.out.println("cache = " + cache); + assert cache.size() == 5; + } + + + public static void testContains() { + LazyRemovalSet
            cache=new LazyRemovalSet
            (5, 0); + Address a=Util.createRandomAddress("A"), + b=Util.createRandomAddress("B"), + c=Util.createRandomAddress("C"), + d=Util.createRandomAddress("D"); + cache.add(a,b,c,d); + System.out.println("cache = " + cache); + assert cache.size() == 4; + assert cache.contains(a); + assert cache.contains(b); + assert cache.contains(d); + assert cache.contains(d); + cache.retainAll(Arrays.asList(a,c)); + System.out.println("cache = " + cache); + assert cache.size() == 4; + cache.removeMarkedElements(); + System.out.println("cache = " + cache); + assert cache.size() == 2; + } + + + public static void testReAddition() { + LazyRemovalSet cache=new LazyRemovalSet(1, 1000); + cache.add("one", "two", "three"); + Util.sleep(1500); + System.out.println("cache = " + cache); + cache.add("two"); + System.out.println("cache = " + cache); + + cache.clear(false); + cache.removeMarkedElements(); + System.out.println("cache = " + cache); + assert cache.size() == 1; + assert cache.contains("two"); + + } + + +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/protocols/ENCRYPT14KeystoreTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/protocols/ENCRYPT14KeystoreTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/protocols/ENCRYPT14KeystoreTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/protocols/ENCRYPT14KeystoreTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -8,6 +8,7 @@ import org.jgroups.Event; import org.jgroups.Global; import org.jgroups.Message; +import org.jgroups.conf.ClassConfigurator; import org.jgroups.stack.Protocol; import org.testng.annotations.Test; @@ -16,7 +17,6 @@ import java.security.MessageDigest; import java.util.HashMap; import java.util.Map; -import java.util.Properties; /** * @author xenephon @@ -24,6 +24,7 @@ @Test(groups=Global.FUNCTIONAL, sequential=false) public class ENCRYPT14KeystoreTest { + static final short ENCRYPT_ID=ClassConfigurator.getProtocolId(ENCRYPT.class); public static void testInitWrongKeystoreProperties() { ENCRYPT encrypt=new ENCRYPT(); @@ -38,8 +39,6 @@ } public static void testInitKeystoreProperties() throws Exception { - - //javax. ENCRYPT encrypt=new ENCRYPT(); encrypt.keyStoreName = "defaultStore.keystore"; encrypt.init(); @@ -49,7 +48,6 @@ } public static void testMessageDownEncode() throws Exception { - ENCRYPT encrypt=new ENCRYPT(); encrypt.keyStoreName = "defaultStore.keystore"; encrypt.init(); @@ -81,8 +79,6 @@ public static void testMessageUpDecode() throws Exception { - - ENCRYPT encrypt=new ENCRYPT(); encrypt.keyStoreName = "defaultStore.keystore"; encrypt.init(); @@ -108,7 +104,7 @@ String symVersion=new String(digest.digest(), "UTF-8"); Message msg=new Message(null, null, encodedBytes); - msg.putHeader(ENCRYPT.EncryptHeader.KEY, new ENCRYPT.EncryptHeader(ENCRYPT.EncryptHeader.ENCRYPT, symVersion)); + msg.putHeader(ENCRYPT_ID, new ENCRYPT.EncryptHeader(ENCRYPT.EncryptHeader.ENCRYPT, symVersion)); Event event=new Event(Event.MSG, msg); encrypt.up(event); Message rcvdMsg=(Message)((Event)observer.getUpMessages().get("message0")).getArg(); @@ -119,15 +115,12 @@ } public static void testMessageUpWrongKey() throws Exception { - // initialise the encryption - - //javax. ENCRYPT encrypt=new ENCRYPT(); encrypt.keyStoreName = "defaultStore.keystore"; encrypt.init(); // use a second instance so we know we are not accidentally using internal key - ENCRYPT encrypt2=new ENCRYPT(); + ENCRYPT encrypt2=new ENCRYPT(); encrypt.keyStoreName = "defaultStore2.keystore"; encrypt2.init(); @@ -148,7 +141,7 @@ String symVersion=new String(digest.digest()); Message msg=new Message(null, null, encodedBytes); - msg.putHeader(ENCRYPT.EncryptHeader.KEY, new ENCRYPT.EncryptHeader(ENCRYPT.EncryptHeader.ENCRYPT, symVersion)); + msg.putHeader(ENCRYPT_ID, new ENCRYPT.EncryptHeader(ENCRYPT.EncryptHeader.ENCRYPT, symVersion)); Event event=new Event(Event.MSG, msg); encrypt.up(event); assert observer.getUpMessages().isEmpty(); @@ -156,8 +149,6 @@ } public static void testMessageUpNoEncryptHeader() throws Exception { - - //javax. ENCRYPT encrypt=new ENCRYPT(); encrypt.keyStoreName = "defaultStore.keystore"; encrypt.init(); @@ -184,8 +175,6 @@ } public static void testEventUpNoMessage() throws Exception { - - //javax. ENCRYPT encrypt=new ENCRYPT(); encrypt.keyStoreName = "defaultStore.keystore"; encrypt.init(); @@ -202,7 +191,6 @@ } public static void testMessageUpNoBuffer() throws Exception { - ENCRYPT encrypt=new ENCRYPT(); encrypt.keyStoreName = "defaultStore.keystore"; encrypt.init(); diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/protocols/ENCRYPTAsymmetricTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/protocols/ENCRYPTAsymmetricTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/protocols/ENCRYPTAsymmetricTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/protocols/ENCRYPTAsymmetricTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -8,11 +8,12 @@ import org.jgroups.*; -import org.jgroups.util.Util; +import org.jgroups.conf.ClassConfigurator; import org.jgroups.protocols.ENCRYPT.EncryptHeader; import org.jgroups.stack.Protocol; -import org.testng.annotations.Test; +import org.jgroups.util.Util; import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; import javax.crypto.Cipher; import java.io.*; @@ -20,7 +21,6 @@ import java.security.Security; import java.util.HashMap; import java.util.Map; -import java.util.Properties; import java.util.Vector; /** @@ -29,6 +29,8 @@ @Test(groups=Global.FUNCTIONAL, sequential=false) public class ENCRYPTAsymmetricTest { + static final short ENCRYPT_ID=ClassConfigurator.getProtocolId(ENCRYPT.class); + @BeforeClass public static void initProvider() { Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); @@ -65,7 +67,7 @@ ENCRYPT encrypt=new ENCRYPT(); encrypt.asymAlgorithm = "RSA"; - encrypt.asymProvider = "BC"; + // encrypt.asymProvider = "BC"; encrypt.init(); // test the default asymetric key @@ -81,25 +83,6 @@ } - /* public static void XtestInitRSABlockAsymProperties() throws Exception { - Properties props=new Properties(); - props.put("asym_algorithm", "RSA/ECB/OAEPPadding"); - ENCRYPT encrypt=new ENCRYPT(); - encrypt.setProperties(props); - encrypt.init(); - - // test the default asymetric key - Util.assertEquals("RSA/ECB/OAEPPadding", encrypt.getAsymAlgorithm()); - Util.assertEquals(512, encrypt.getAsymInit()); - Util.assertEquals("RSA", encrypt.getKpair().getPublic().getAlgorithm()); - //Strangely this returns differently from the default provider for RSA which is also BC! - Util.assertEquals("X509", encrypt.getKpair().getPublic().getFormat()); - Util.assertNotNull(encrypt.getKpair().getPublic().getEncoded()); - - //test the resulting ciphers - Util.assertNotNull(encrypt.getAsymCipher()); - - }*/ @Test(expectedExceptions=Exception.class) @@ -153,8 +136,7 @@ encrypt.keyServer=false; Message msg=new Message(); msg.setBuffer(cipher.doFinal("hello".getBytes())); - msg.putHeader(EncryptHeader.KEY, new EncryptHeader( - EncryptHeader.ENCRYPT, symVersion)); + msg.putHeader(ENCRYPT_ID, new EncryptHeader(EncryptHeader.ENCRYPT, symVersion)); Event evt=new Event(Event.MSG, msg); @@ -177,8 +159,7 @@ // send another encrypted message Message msg2=new Message(); msg2.setBuffer(cipher.doFinal("hello2".getBytes())); - msg2.putHeader(EncryptHeader.KEY, new EncryptHeader( - EncryptHeader.ENCRYPT, symVersion)); + msg2.putHeader(ENCRYPT_ID, new EncryptHeader(EncryptHeader.ENCRYPT, symVersion)); // we should have three messages now in our observer // that are decrypted @@ -242,8 +223,7 @@ Cipher cipher=server.getSymEncodingCipher(); Message msg=new Message(); msg.setBuffer(cipher.doFinal("hello".getBytes())); - msg.putHeader(EncryptHeader.KEY, new EncryptHeader( - EncryptHeader.ENCRYPT, symVersion)); + msg.putHeader(ENCRYPT_ID, new EncryptHeader(EncryptHeader.ENCRYPT, symVersion)); Event evt=new Event(Event.MSG, msg); @@ -262,7 +242,7 @@ Event sent=(Event)peerObserver.getDownMessages().get("message0"); - Util.assertEquals(((EncryptHeader)((Message)sent.getArg()).getHeader(EncryptHeader.KEY)).getType(), EncryptHeader.KEY_REQUEST); + Util.assertEquals(((EncryptHeader)((Message)sent.getArg()).getHeader(ENCRYPT_ID)).getType(), EncryptHeader.KEY_REQUEST); Util.assertEquals(new String(((Message)sent.getArg()).getBuffer()), new String(peer.getKpair().getPublic().getEncoded())); // send this event to server @@ -271,7 +251,7 @@ Event reply=(Event)serverObserver.getDownMessages().get("message1"); //assert that reply is the session key encrypted with peer's public key - Util.assertEquals(((EncryptHeader)((Message)reply.getArg()).getHeader(EncryptHeader.KEY)).getType(), EncryptHeader.SECRETKEY); + Util.assertEquals(((EncryptHeader)((Message)reply.getArg()).getHeader(ENCRYPT_ID)).getType(), EncryptHeader.SECRETKEY); assert !peer.getDesKey().equals(server.getDesKey()); @@ -284,8 +264,7 @@ // send another encrypted message to peer to test queue Message msg2=new Message(); msg2.setBuffer(cipher.doFinal("hello2".getBytes())); - msg2.putHeader(EncryptHeader.KEY, new EncryptHeader( - EncryptHeader.ENCRYPT, symVersion)); + msg2.putHeader(ENCRYPT_ID, new EncryptHeader(EncryptHeader.ENCRYPT, symVersion)); Event evt2=new Event(Event.MSG, msg2); @@ -383,7 +362,7 @@ Event sent=(Event)peerObserver.getDownMessages().get("message0"); // ensure type and that request contains peers pub key - Util.assertEquals(((EncryptHeader)((Message)sent.getArg()).getHeader(EncryptHeader.KEY)).getType(), EncryptHeader.KEY_REQUEST); + Util.assertEquals(((EncryptHeader)((Message)sent.getArg()).getHeader(ENCRYPT_ID)).getType(), EncryptHeader.KEY_REQUEST); Util.assertEquals(new String(((Message)sent.getArg()).getBuffer()), new String(peer.getKpair().getPublic().getEncoded())); //assume that server is no longer available and peer2 is new server @@ -404,7 +383,7 @@ Event reply=(Event)peer2Observer.getDownMessages().get("message1"); //assert that reply is the session key encrypted with peer's public key - Util.assertEquals(((EncryptHeader)((Message)reply.getArg()).getHeader(EncryptHeader.KEY)).getType(), EncryptHeader.SECRETKEY); + Util.assertEquals(((EncryptHeader)((Message)reply.getArg()).getHeader(ENCRYPT_ID)).getType(), EncryptHeader.SECRETKEY); assert !peer.getDesKey().equals(peer2.getDesKey()); diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/protocols/NAKACK_Delivery_Test.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/protocols/NAKACK_Delivery_Test.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/protocols/NAKACK_Delivery_Test.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/protocols/NAKACK_Delivery_Test.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,209 @@ +package org.jgroups.protocols; + +import org.jgroups.*; +import org.jgroups.conf.ClassConfigurator; +import org.jgroups.stack.Protocol; +import org.jgroups.stack.NakReceiverWindow; +import org.jgroups.protocols.pbcast.NAKACK; +import org.jgroups.protocols.pbcast.NakAckHeader; +import org.jgroups.util.*; +import org.jgroups.util.UUID; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.*; +import java.util.concurrent.*; + +/** + * Tests whether a mix of OOB and regular messages (with duplicates), sent my multiple threads, are delivery + * correctly. Correct delivery means: + *
              + *
            • All messages are received exactly once (no duplicates and no missing messages) + *
            • For regular messages only: all messages are received in the order in which they were sent (order of seqnos) + *
            + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL) +public class NAKACK_Delivery_Test { + private NAKACK nak; + private Address c1, c2; + static final short NAKACK_ID=ClassConfigurator.getProtocolId(NAKACK.class); + MyReceiver receiver=new MyReceiver(); + Executor pool; + final static int NUM_MSGS=50; + + @BeforeMethod + protected void setUp() throws Exception { + c1=Util.createRandomAddress("C1"); + c2=Util.createRandomAddress("C2"); + nak=new NAKACK(); + + TP transport=new TP() { + public boolean supportsMulticasting() {return false;} + public void sendMulticast(byte[] data, int offset, int length) throws Exception {} + public void sendUnicast(PhysicalAddress dest, byte[] data, int offset, int length) throws Exception {} + public String getInfo() {return null;} + public Object down(Event evt) {return null;} + protected PhysicalAddress getPhysicalAddress() {return null;} + public TimeScheduler getTimer() {return new DefaultTimeScheduler(1);} + }; + + transport.setId((short)100); + + nak.setDownProtocol(transport); + + receiver.init(c1, c2); + nak.setUpProtocol(receiver); + + nak.start(); + + Vector
            members=new Vector
            (2); + members.add(c1); members.add(c2); + View view=new View(c1, 1, members); + + // set the local address + nak.down(new Event(Event.SET_LOCAL_ADDRESS, c1)); + + // set a dummy digest + MutableDigest digest=new MutableDigest(2); + digest.add(c1, 0, 0, 0); + digest.add(c2, 0, 0, 0); + nak.down(new Event(Event.SET_DIGEST, digest)); + + // set dummy view + nak.down(new Event(Event.VIEW_CHANGE, view)); + + pool=new ThreadPoolExecutor(1, 100, 1000, TimeUnit.MILLISECONDS, new SynchronousQueue()); + // pool=new DirectExecutor(); + // if(pool instanceof ThreadPoolExecutor) + ((ThreadPoolExecutor)pool).setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + } + + + @AfterMethod + protected void tearDown() { + nak.stop(); + if(pool instanceof ThreadPoolExecutor) + ((ThreadPoolExecutor)pool).shutdownNow(); + } + + + /** + * Sends NUM_MSGS (regular or OOB) multicasts on c1 and c2, checks that both c1 and c2 received NUM_MSGS messages. + * This test doesn't use a transport, but injects messages directly into NAKACK. + */ + public void testSendingOfRandomMessages() { + List seqnos=generateRandomNumbers(1, NUM_MSGS); + seqnos.addAll(generateRandomNumbers(1, NUM_MSGS)); + seqnos.addAll(generateRandomNumbers(Math.min(15, NUM_MSGS / 2), NUM_MSGS / 2)); + seqnos.addAll(generateRandomNumbers(2, NUM_MSGS)); + seqnos.addAll(generateRandomNumbers(5, Math.max(5, NUM_MSGS - 10))); + + Set no_duplicates=new HashSet(seqnos); + + System.out.println("sending " + seqnos.size() + " msgs (including duplicates); size excluding duplicates=" + + no_duplicates.size()); + + // we need to add our own messages (nak is for C1), or else they will get discarded by NAKACK.handleMessage() + NakReceiverWindow win=nak.getWindow(c1); + for(int i=1; i <= NUM_MSGS; i++) + win.add(i, msg(c1, i, i, true)); + + for(int i: seqnos) { + boolean oob=Util.tossWeightedCoin(0.5); + pool.execute(new Sender(c2, i, i, oob)); + pool.execute(new Sender(c1, i, i, oob)); + } + + ConcurrentMap> msgs=receiver.getMsgs(); + Collection c1_list=msgs.get(c1); + Collection c2_list=msgs.get(c2); + + long end_time=System.currentTimeMillis() + 10000; + while(System.currentTimeMillis() < end_time) { + int size_c1=c1_list.size(); + int size_c2=c2_list.size(); + System.out.println("size C1 = " + size_c1 + ", size C2=" + size_c2); + if(size_c1 == NUM_MSGS && size_c2 == NUM_MSGS) + break; + Util.sleep(1000); + } + + assert c1_list.size() == NUM_MSGS : "[C1] expected " + NUM_MSGS + " messages, but got " + c1_list.size(); + assert c2_list.size() == NUM_MSGS : "[C2] expected " + NUM_MSGS + " messages, but got " + c2_list.size(); + } + + private static List generateRandomNumbers(int from, int to) { + List retval=new ArrayList(20); + for(int i=from; i <= to; i++) + retval.add(i); + Collections.shuffle(retval); + return retval; + } + + + private void send(Address sender, long seqno, int number, boolean oob) { + assert sender != null; + nak.up(new Event(Event.MSG, msg(sender, seqno, number, oob))); + } + + + private static Message msg(Address sender, long seqno, int number, boolean oob) { + Message msg=new Message(null, sender, number); + if(oob) + msg.setFlag(Message.OOB); + if(seqno != -1) + msg.putHeader(NAKACK_ID, NakAckHeader.createMessageHeader(seqno)); + return msg; + } + + + static class MyReceiver extends Protocol { + final ConcurrentMap> msgs=new ConcurrentHashMap>(); + + public ConcurrentMap> getMsgs() { + return msgs; + } + public void init(Address ... mbrs) { + for(Address mbr: mbrs) { + msgs.putIfAbsent(mbr, new ConcurrentLinkedQueue()); + } + } + + public Object up(Event evt) { + if(evt.getType() == Event.MSG) { + Message msg=(Message)evt.getArg(); + Address sender=msg.getSrc(); + Collection list=msgs.get(sender); + if(list == null) { + list=new ConcurrentLinkedQueue(); + Collection tmp=msgs.putIfAbsent(sender, list); + if(tmp != null) + list=tmp; + } + list.add(msg); + } + return null; + } + } + + class Sender implements Runnable { + final Address sender; + final long seqno; + final int number; + final boolean oob; + + public Sender(Address sender, long seqno, int number, boolean oob) { + this.sender=sender; + this.seqno=seqno; + this.number=number; + this.oob=oob; + } + + public void run() { + send(sender, seqno, number, oob); + } + } + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/protocols/NAKACK_SET_DIGEST_Test.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/protocols/NAKACK_SET_DIGEST_Test.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/protocols/NAKACK_SET_DIGEST_Test.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/protocols/NAKACK_SET_DIGEST_Test.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,84 @@ +package org.jgroups.protocols; + +import org.jgroups.*; +import org.jgroups.protocols.pbcast.NAKACK; +import org.jgroups.util.*; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Tests setting of digest NAKACK.down(SET_DIGEST), JIRA issue is https://jira.jboss.org/jira/browse/JGRP-1060 + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL) +public class NAKACK_SET_DIGEST_Test { + private NAKACK nak; + private MutableDigest d1, d2; + private Address a, b, c; + + private static final short TP_ID=101; + + @BeforeMethod + protected void setUp() throws Exception { + a=Util.createRandomAddress("A"); + b=Util.createRandomAddress("B"); + c=Util.createRandomAddress("C"); + nak=new NAKACK(); + d1=new MutableDigest(2); + d1.add(a, 0, 11, 11); + d1.add(b, 0, 30, 35); + + d2=new MutableDigest(3); + d2.add(a, 0, 10, 10); + d2.add(b, 0, 30, 30); + d2.add(c, 10, 50, 50); + + TP transport=new TP() { + public boolean supportsMulticasting() {return false;} + public void sendMulticast(byte[] data, int offset, int length) throws Exception {} + public void sendUnicast(PhysicalAddress dest, byte[] data, int offset, int length) throws Exception {} + public String getInfo() {return null;} + public Object down(Event evt) {return null;} + protected PhysicalAddress getPhysicalAddress() {return null;} + public TimeScheduler getTimer() {return new DefaultTimeScheduler(1);} + }; + transport.setId(TP_ID); + + nak.setDownProtocol(transport); + + nak.start(); +// View view=new View(a, 1, new Vector(Arrays.asList(new Address[]{a, b, c}))); +// nak.down(new Event(Event.VIEW_CHANGE, view)); + } + + @AfterMethod + protected void tearDown() { + nak.stop(); + } + + public void testSetDigest() throws TimeoutException { + System.out.println("d1: " + d1); + System.out.println("d2: " + d2); + + System.out.println("setting d2:"); + nak.down(new Event(Event.SET_DIGEST, d2)); + Digest digest=(Digest)nak.down(new Event(Event.GET_DIGEST)); + System.out.println("digest = " + digest); + assert digest.size() == 3; + assert digest.contains(a); + assert digest.contains(b); + assert digest.contains(c); + + System.out.println("setting d1:"); + nak.down(new Event(Event.SET_DIGEST, d1)); + digest=(Digest)nak.down(new Event(Event.GET_DIGEST)); + System.out.println("digest = " + digest); + assert digest.size() == 3; // https://jira.jboss.org/jira/browse/JGRP-1060 + assert digest.contains(a); + assert digest.contains(b); + assert digest.contains(c); + } + + +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/protocols/NAKACK_StressTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/protocols/NAKACK_StressTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/protocols/NAKACK_StressTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/protocols/NAKACK_StressTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,231 @@ +package org.jgroups.protocols; + +import org.jgroups.*; +import org.jgroups.conf.ClassConfigurator; +import org.jgroups.protocols.pbcast.NAKACK; +import org.jgroups.protocols.pbcast.NakAckHeader; +import org.jgroups.stack.Protocol; +import org.jgroups.util.*; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + + +/** + * Tests time for N threads to deliver M messages to NAKACK + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL, sequential=true) +public class NAKACK_StressTest { + static final int NUM_MSGS=1000000; + static final int NUM_THREADS=50; + + static final short NAKACK_ID=ClassConfigurator.getProtocolId(NAKACK.class); + + @DataProvider(name="createTimer") + Object[][] createTimer() { + return Util.createTimer(); + } + + @Test(dataProvider="createTimer") + public static void stressTest(TimeScheduler timer) { + start(NUM_THREADS, NUM_MSGS, false, timer); + } + + @Test(dataProvider="createTimer") + public static void stressTestOOB(TimeScheduler timer) { + start(NUM_THREADS, NUM_MSGS, true, timer); + } + + private static void start(final int num_threads, final int num_msgs, boolean oob, TimeScheduler timer) { + final NAKACK nak=new NAKACK(); + final AtomicInteger counter=new AtomicInteger(num_msgs); + final AtomicLong seqno=new AtomicLong(1); + final AtomicInteger delivered_msgs=new AtomicInteger(0); + final Lock lock=new ReentrantLock(); + final Condition all_msgs_delivered=lock.newCondition(); + final ConcurrentLinkedQueue delivered_msg_list=new ConcurrentLinkedQueue(); + final Address local_addr=Util.createRandomAddress("A"); + final Address sender=Util.createRandomAddress("B"); + + + if(timer == null) + timer=new TimeScheduler2(); + nak.setTimer(timer); + System.out.println("timer is a " + timer.getClass()); + + nak.setDownProtocol(new Protocol() {public Object down(Event evt) {return null;}}); + + nak.setUpProtocol(new Protocol() { + public Object up(Event evt) { + if(evt.getType() == Event.MSG) { + delivered_msgs.incrementAndGet(); + NakAckHeader hdr=(NakAckHeader)((Message)evt.getArg()).getHeader(NAKACK_ID); + if(hdr != null) + delivered_msg_list.add(hdr.getSeqno()); + + if(delivered_msgs.get() >= num_msgs) { + lock.lock(); + try { + all_msgs_delivered.signalAll(); + } + finally { + lock.unlock(); + } + } + } + return null; + } + }); + + nak.setDiscardDeliveredMsgs(true); + nak.down(new Event(Event.SET_LOCAL_ADDRESS, local_addr)); + nak.down(new Event(Event.BECOME_SERVER)); + View view=new View(local_addr, 1, Arrays.asList(local_addr, sender)); + nak.down(new Event(Event.VIEW_CHANGE, view)); + + MutableDigest digest=new MutableDigest(); + digest.add(local_addr, 0, 0, 0); + digest.add(sender, 0, 0, 0); + nak.down(new Event(Event.SET_DIGEST, digest)); + + final CountDownLatch latch=new CountDownLatch(1); + Sender[] adders=new Sender[num_threads]; + for(int i=0; i < adders.length; i++) { + adders[i]=new Sender(nak, latch, counter, seqno, oob, sender); + adders[i].start(); + } + + long start=System.currentTimeMillis(); + latch.countDown(); // starts all adders + + lock.lock(); + try { + while(delivered_msgs.get() < num_msgs) { + try { + all_msgs_delivered.await(1000, TimeUnit.MILLISECONDS); + System.out.println("received " + delivered_msgs.get() + " msgs"); + } + catch(InterruptedException e) { + e.printStackTrace(); + } + } + } + finally { + lock.unlock(); + } + + long time=System.currentTimeMillis() - start; + double requests_sec=num_msgs / (time / 1000.0); + System.out.println("\nTime: " + time + " ms, " + Util.format(requests_sec) + " requests / sec\n"); + System.out.println("Delivered messages: " + delivered_msg_list.size()); + if(delivered_msg_list.size() < 100) + System.out.println("Elements: " + delivered_msg_list); + + nak.stop(); + timer.stop(); + + List results=new ArrayList(delivered_msg_list); + + if(oob) + Collections.sort(results); + + assert results.size() == num_msgs : "expected " + num_msgs + ", but got " + results.size(); + + System.out.println("Checking results consistency"); + int i=1; + for(Long num: results) { + if(num.longValue() != i) { + assert i == num : "expected " + i + " but got " + num; + return; + } + i++; + } + System.out.println("OK"); + } + + private static Message createMessage(Address dest, Address src, long seqno, boolean oob) { + Message msg=new Message(dest, src, "hello world"); + NakAckHeader hdr=NakAckHeader.createMessageHeader(seqno) ; + msg.putHeader(NAKACK_ID, hdr); + if(oob) + msg.setFlag(Message.OOB); + return msg; + } + + + static class Sender extends Thread { + final NAKACK nak; + final CountDownLatch latch; + final AtomicInteger num_msgs; + final AtomicLong current_seqno; + final boolean oob; + final Address sender; + + public Sender(NAKACK nak, CountDownLatch latch, AtomicInteger num_msgs, AtomicLong current_seqno, + boolean oob, final Address sender) { + this.nak=nak; + this.latch=latch; + this.num_msgs=num_msgs; + this.current_seqno=current_seqno; + this.oob=oob; + this.sender=sender; + setName("Adder"); + } + + + public void run() { + try { + latch.await(); + } + catch(InterruptedException e) { + e.printStackTrace(); + return; + } + + while(num_msgs.getAndDecrement() > 0) { + long seqno=current_seqno.getAndIncrement(); + Message msg=createMessage(null, sender, seqno, oob); + nak.up(new Event(Event.MSG, msg)); + } + } + } + + + @Test(enabled=false) + public static void main(String[] args) { + int num_threads=10; + int num_msgs=1000000; + boolean oob=false; + + for(int i=0; i < args.length; i++) { + if(args[i].equals("-num_threads")) { + num_threads=Integer.parseInt(args[++i]); + continue; + } + if(args[i].equals("-num_msgs")) { + num_msgs=Integer.parseInt(args[++i]); + continue; + } + if(args[i].equals("-oob")) { + oob=Boolean.parseBoolean(args[++i]); + continue; + } + System.out.println("NAKACK_StressTest [-num_msgs msgs] [-num_threads threads] [-oob ]"); + return; + } + start(num_threads, num_msgs, oob, null); + } +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/protocols/UNICAST2_StressTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/protocols/UNICAST2_StressTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/protocols/UNICAST2_StressTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/protocols/UNICAST2_StressTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,244 @@ +package org.jgroups.protocols; + +import org.jgroups.Address; +import org.jgroups.Event; +import org.jgroups.Global; +import org.jgroups.Message; +import org.jgroups.conf.ClassConfigurator; +import org.jgroups.stack.Protocol; +import org.jgroups.util.TimeScheduler; +import org.jgroups.util.TimeScheduler2; +import org.jgroups.util.Util; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + + +/** + * Tests time for N threads to deliver M messages to UNICAST2 + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL, sequential=true) +public class UNICAST2_StressTest { + static final int NUM_MSGS=1000000; + static final int NUM_THREADS=50; + static final int MAX_MSG_BATCH_SIZE=50000; + + static final short UNICAST_ID=ClassConfigurator.getProtocolId(UNICAST2.class); + + @DataProvider(name="createTimer") + Object[][] createTimer() { + return Util.createTimer(); + } + + @Test(dataProvider="createTimer") + public static void stressTest(TimeScheduler timer) { + start(NUM_THREADS, NUM_MSGS, false, MAX_MSG_BATCH_SIZE, timer); + } + + @Test(dataProvider="createTimer") + public static void stressTestOOB(TimeScheduler timer) { + start(NUM_THREADS, NUM_MSGS, true, MAX_MSG_BATCH_SIZE, timer); + } + + private static void start(final int num_threads, final int num_msgs, boolean oob, int max_msg_batch_size, TimeScheduler timer) { + final UNICAST2 unicast=new UNICAST2(); + final AtomicInteger counter=new AtomicInteger(num_msgs); + final AtomicLong seqno=new AtomicLong(1); + final AtomicInteger delivered_msgs=new AtomicInteger(0); + final Lock lock=new ReentrantLock(); + final Condition all_msgs_delivered=lock.newCondition(); + final ConcurrentLinkedQueue delivered_msg_list=new ConcurrentLinkedQueue(); + final Address local_addr=Util.createRandomAddress(); + final Address sender=Util.createRandomAddress(); + + if(timer == null) + timer=new TimeScheduler2(); + unicast.setTimer(timer); + System.out.println("timer is a " + timer.getClass()); + + + unicast.setDownProtocol(new Protocol() { + public Object down(Event evt) {return null;} + }); + + unicast.setUpProtocol(new Protocol() { + public Object up(Event evt) { + if(evt.getType() == Event.MSG) { + delivered_msgs.incrementAndGet(); + UNICAST2.Unicast2Header hdr=(UNICAST2.Unicast2Header)((Message)evt.getArg()).getHeader(UNICAST_ID); + if(hdr != null) + delivered_msg_list.add(hdr.getSeqno()); + + if(delivered_msgs.get() >= num_msgs) { + lock.lock(); + try { + all_msgs_delivered.signalAll(); + } + finally { + lock.unlock(); + } + } + } + return null; + } + }); + + unicast.down(new Event(Event.SET_LOCAL_ADDRESS, local_addr)); + + unicast.setMaxMessageBatchSize(max_msg_batch_size); + unicast.setValue("max_bytes", 20000); + + // send the first message manually, to initialize the AckReceiverWindow tables + Message msg=createMessage(local_addr, sender, 1L, oob, true); + unicast.up(new Event(Event.MSG, msg)); + Util.sleep(500); + + + final CountDownLatch latch=new CountDownLatch(1); + Sender[] adders=new Sender[num_threads]; + for(int i=0; i < adders.length; i++) { + adders[i]=new Sender(unicast, latch, counter, seqno, oob, local_addr, sender); + adders[i].start(); + } + + long start=System.currentTimeMillis(); + latch.countDown(); // starts all adders + + lock.lock(); + try { + while(delivered_msgs.get() < num_msgs) { + try { + all_msgs_delivered.await(1000, TimeUnit.MILLISECONDS); + System.out.println("received " + delivered_msgs.get() + " msgs"); + } + catch(InterruptedException e) { + e.printStackTrace(); + } + } + } + finally { + lock.unlock(); + } + + long time=System.currentTimeMillis() - start; + double requests_sec=num_msgs / (time / 1000.0); + System.out.println("\nTime: " + time + " ms, " + Util.format(requests_sec) + " requests / sec\n"); + System.out.println("Delivered messages: " + delivered_msg_list.size()); + if(delivered_msg_list.size() < 100) + System.out.println("Elements: " + delivered_msg_list); + + unicast.stop(); + timer.stop(); + + List results=new ArrayList(delivered_msg_list); + + if(oob) + Collections.sort(results); + + assert results.size() == num_msgs : "expected " + num_msgs + ", but got " + results.size(); + + System.out.println("Checking results consistency"); + int i=1; + for(Long num: results) { + if(num.longValue() != i) { + assert i == num : "expected " + i + " but got " + num; + return; + } + i++; + } + System.out.println("OK"); + } + + private static Message createMessage(Address dest, Address src, long seqno, boolean oob, boolean first) { + Message msg=new Message(dest, src, "hello world"); + UNICAST2.Unicast2Header hdr=UNICAST2.Unicast2Header.createDataHeader(seqno, (short)1, first); + msg.putHeader(UNICAST_ID, hdr); + if(oob) + msg.setFlag(Message.OOB); + return msg; + } + + + static class Sender extends Thread { + final UNICAST2 unicast; + final CountDownLatch latch; + final AtomicInteger num_msgs; + final AtomicLong current_seqno; + final boolean oob; + final Address dest; + final Address sender; + + public Sender(UNICAST2 unicast, CountDownLatch latch, AtomicInteger num_msgs, AtomicLong current_seqno, + boolean oob, final Address dest, final Address sender) { + this.unicast=unicast; + this.latch=latch; + this.num_msgs=num_msgs; + this.current_seqno=current_seqno; + this.oob=oob; + this.dest=dest; + this.sender=sender; + setName("Adder"); + } + + + public void run() { + try { + latch.await(); + } + catch(InterruptedException e) { + e.printStackTrace(); + return; + } + + while(num_msgs.getAndDecrement() > 0) { + long seqno=current_seqno.getAndIncrement(); + Message msg=createMessage(dest, sender, seqno, oob, false); + unicast.up(new Event(Event.MSG, msg)); + } + } + } + + + @Test(enabled=false) + public static void main(String[] args) { + int num_threads=10; + int num_msgs=1000000; + int max=20000; + boolean oob=false; + + for(int i=0; i < args.length; i++) { + if(args[i].equals("-num_threads")) { + num_threads=Integer.parseInt(args[++i]); + continue; + } + if(args[i].equals("-num_msgs")) { + num_msgs=Integer.parseInt(args[++i]); + continue; + } + if(args[i].equals("-oob")) { + oob=Boolean.parseBoolean(args[++i]); + continue; + } + if(args[i].equals("-max")) { + max=Integer.parseInt(args[++i]); + continue; + } + System.out.println("UNICAST2_StressTest [-num_msgs msgs] [-num_threads threads] " + + "[-oob ] [-max ]"); + return; + } + start(num_threads, num_msgs, oob, max, null); + } +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/protocols/UNICAST_StressTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/protocols/UNICAST_StressTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/protocols/UNICAST_StressTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/protocols/UNICAST_StressTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,230 @@ +package org.jgroups.protocols; + +import org.jgroups.Address; +import org.jgroups.Event; +import org.jgroups.Global; +import org.jgroups.Message; +import org.jgroups.conf.ClassConfigurator; +import org.jgroups.stack.Protocol; +import org.jgroups.util.Util; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + + +/** + * Tests time for N threads to deliver M messages to UNICAST + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL, sequential=true) +public class UNICAST_StressTest { + static final int NUM_MSGS=1000000; + static final int NUM_THREADS=50; + static final int MAX_MSG_BATCH_SIZE=50000; + + static final short UNICAST_ID=ClassConfigurator.getProtocolId(UNICAST.class); + + @Test + public static void stressTest() { + start(NUM_THREADS, NUM_MSGS, false, MAX_MSG_BATCH_SIZE); + } + + @Test + public static void stressTestOOB() { + start(NUM_THREADS, NUM_MSGS, true, MAX_MSG_BATCH_SIZE); + } + + private static void start(final int num_threads, final int num_msgs, boolean oob, int max_msg_batch_size) { + final UNICAST unicast=new UNICAST(); + final AtomicInteger counter=new AtomicInteger(num_msgs); + final AtomicLong seqno=new AtomicLong(1); + final AtomicInteger delivered_msgs=new AtomicInteger(0); + final Lock lock=new ReentrantLock(); + final Condition all_msgs_delivered=lock.newCondition(); + final ConcurrentLinkedQueue delivered_msg_list=new ConcurrentLinkedQueue(); + final Address local_addr=Util.createRandomAddress("A"); + final Address sender=Util.createRandomAddress("B"); + + unicast.setDownProtocol(new Protocol() { + public Object down(Event evt) { + return null; + } + }); + + unicast.setUpProtocol(new Protocol() { + public Object up(Event evt) { + if(evt.getType() == Event.MSG) { + delivered_msgs.incrementAndGet(); + UNICAST.UnicastHeader hdr=(UNICAST.UnicastHeader)((Message)evt.getArg()).getHeader(UNICAST_ID); + if(hdr != null) + delivered_msg_list.add(hdr.getSeqno()); + + if(delivered_msgs.get() >= num_msgs) { + lock.lock(); + try { + all_msgs_delivered.signalAll(); + } + finally { + lock.unlock(); + } + } + } + return null; + } + }); + + unicast.down(new Event(Event.SET_LOCAL_ADDRESS, local_addr)); + unicast.setMaxMessageBatchSize(max_msg_batch_size); + + // send the first message manually, to initialize the AckReceiverWindow tables + Message msg=createMessage(local_addr, sender, 1L, oob, true); + unicast.up(new Event(Event.MSG, msg)); + Util.sleep(500); + + + final CountDownLatch latch=new CountDownLatch(1); + Adder[] adders=new Adder[num_threads]; + for(int i=0; i < adders.length; i++) { + adders[i]=new Adder(unicast, latch, counter, seqno, oob, local_addr, sender); + adders[i].start(); + } + + long start=System.currentTimeMillis(); + latch.countDown(); // starts all adders + + lock.lock(); + try { + while(delivered_msgs.get() < num_msgs) { + try { + all_msgs_delivered.await(1000, TimeUnit.MILLISECONDS); + System.out.println("received " + delivered_msgs.get() + " msgs"); + + // send a spurious message to trigger removal of pending messages in AckReceiverWindow + msg=createMessage(local_addr, sender, 1L, oob, false); + unicast.up(new Event(Event.MSG, msg)); + } + catch(InterruptedException e) { + e.printStackTrace(); + } + } + } + finally { + lock.unlock(); + } + + long time=System.currentTimeMillis() - start; + double requests_sec=num_msgs / (time / 1000.0); + System.out.println("\nTime: " + time + " ms, " + Util.format(requests_sec) + " requests / sec\n"); + System.out.println("Delivered messages: " + delivered_msg_list.size()); + if(delivered_msg_list.size() < 100) + System.out.println("Elements: " + delivered_msg_list); + + List results=new ArrayList(delivered_msg_list); + + if(oob) + Collections.sort(results); + + assert results.size() == num_msgs : "expected " + num_msgs + ", but got " + results.size(); + + System.out.println("Checking results consistency"); + int i=1; + for(Long num: results) { + if(num.longValue() != i) { + assert i == num : "expected " + i + " but got " + num; + return; + } + i++; + } + System.out.println("OK"); + } + + private static Message createMessage(Address dest, Address src, long seqno, boolean oob, boolean first) { + Message msg=new Message(dest, src, "hello world"); + UNICAST.UnicastHeader hdr=UNICAST.UnicastHeader.createDataHeader(seqno, (short)1, first); + msg.putHeader(UNICAST_ID, hdr); + if(oob) + msg.setFlag(Message.OOB); + return msg; + } + + + static class Adder extends Thread { + final UNICAST unicast; + final CountDownLatch latch; + final AtomicInteger num_msgs; + final AtomicLong current_seqno; + final boolean oob; + final Address dest; + final Address sender; + + public Adder(UNICAST unicast, CountDownLatch latch, AtomicInteger num_msgs, AtomicLong current_seqno, + boolean oob, final Address dest, final Address sender) { + this.unicast=unicast; + this.latch=latch; + this.num_msgs=num_msgs; + this.current_seqno=current_seqno; + this.oob=oob; + this.dest=dest; + this.sender=sender; + } + + + public void run() { + try { + latch.await(); + } + catch(InterruptedException e) { + e.printStackTrace(); + return; + } + + while(num_msgs.getAndDecrement() > 0) { + long seqno=current_seqno.getAndIncrement(); + Message msg=createMessage(dest, sender, seqno, oob, false); + unicast.up(new Event(Event.MSG, msg)); + } + } + } + + + @Test(enabled=false) + public static void main(String[] args) { + int num_threads=10; + int num_msgs=1000000; + int max=20000; + boolean oob=false; + + for(int i=0; i < args.length; i++) { + if(args[i].equals("-num_threads")) { + num_threads=Integer.parseInt(args[++i]); + continue; + } + if(args[i].equals("-num_msgs")) { + num_msgs=Integer.parseInt(args[++i]); + continue; + } + if(args[i].equals("-oob")) { + oob=Boolean.parseBoolean(args[++i]); + continue; + } + if(args[i].equals("-max")) { + max=Integer.parseInt(args[++i]); + continue; + } + System.out.println("UNICAST_StressTest [-num_msgs msgs] [-num_threads threads] " + + "[-oob ] [-max ]"); + return; + } + start(num_threads, num_msgs, oob, max); + } +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/AckCollectorTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/AckCollectorTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/AckCollectorTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/AckCollectorTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -5,7 +5,6 @@ import org.jgroups.Address; import org.jgroups.Global; import org.jgroups.TimeoutException; -import org.jgroups.stack.IpAddress; import org.jgroups.util.AckCollector; import org.jgroups.util.Util; import org.testng.Assert; @@ -79,7 +78,7 @@ } }.start(); try { - ac.waitForAllAcks(1000); + ac.waitForAllAcks(5000); assert true : "we should not get a timeout exception here"; } catch(TimeoutException e) { @@ -96,16 +95,16 @@ public void run() { ac.ack("one"); System.out.println("AckCollector: " + ac); - Util.sleep(100); + Util.sleep(200); ac.ack("two"); System.out.println("AckCollector: " + ac); - Util.sleep(100); + Util.sleep(200); ac.ack("three"); System.out.println("AckCollector: " + ac); - Util.sleep(100); + Util.sleep(200); ac.ack("four"); System.out.println("AckCollector: " + ac); - Util.sleep(100); + Util.sleep(200); ac.ack("five"); System.out.println("AckCollector: " + ac); } @@ -121,7 +120,7 @@ public void run() { Util.sleep(500); System.out.println("resetting AckCollector"); - ac.reset(null, new_list); + ac.reset(new_list); System.out.println("reset AckCollector: " + ac); } }.start(); @@ -139,7 +138,7 @@ public void run() { Util.sleep(500); System.out.println("resetting AckCollector"); - ac.reset(null, new_list); + ac.reset(new_list); System.out.println("reset AckCollector: " + ac); Util.sleep(100); ac.ack("six"); @@ -153,7 +152,7 @@ } }.start(); System.out.println("initial AckCollector: " + ac); - ac.waitForAllAcks(2000); + ac.waitForAllAcks(5000); System.out.println("new AckCollector: " + ac); } @@ -164,7 +163,7 @@ public static void testOneList() throws TimeoutException, UnknownHostException { List tmp=new ArrayList(); - Address addr=new IpAddress("127.0.0.1", 5555); + Address addr=Util.createRandomAddress(); tmp.add(addr); AckCollector coll=new AckCollector(null, tmp); coll.ack(addr); diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/AckMcastSenderWindowTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/AckMcastSenderWindowTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/AckMcastSenderWindowTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/AckMcastSenderWindowTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: AckMcastSenderWindowTest.java,v 1.5 2008/04/08 12:17:07 belaban Exp $ package org.jgroups.tests; diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/AckReceiverWindowStressTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/AckReceiverWindowStressTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/AckReceiverWindowStressTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/AckReceiverWindowStressTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,158 @@ +package org.jgroups.tests; + +import org.jgroups.stack.AckReceiverWindow; +import org.jgroups.Message; +import org.jgroups.Global; +import org.jgroups.util.Util; +import org.jgroups.util.Tuple; +import org.testng.annotations.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.List; + + +/** + * Tests time for N threads to insert and remove M messages into an AckReceiverWindow + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL, sequential=true) +public class AckReceiverWindowStressTest { + static final int NUM_MSGS=1000000; + static final int NUM_THREADS=50; + static final int SEGMENT_SIZE=50000; + + @Test + public static void stressTest() { + start(NUM_THREADS, NUM_MSGS, SEGMENT_SIZE); + } + + + private static void start(int num_threads, int num_msgs, int segment_size) { + final AckReceiverWindow win=new AckReceiverWindow(1, segment_size); + final AtomicInteger counter=new AtomicInteger(num_msgs); + final AtomicLong seqno=new AtomicLong(1); + final AtomicInteger removed_msgs=new AtomicInteger(0); + + final CountDownLatch latch=new CountDownLatch(1); + Adder[] adders=new Adder[num_threads]; + for(int i=0; i < adders.length; i++) { + adders[i]=new Adder(win, latch, counter, seqno, removed_msgs); + adders[i].start(); + } + + long start=System.currentTimeMillis(); + latch.countDown(); // starts all adders + + for(Adder adder: adders) { + try { + adder.join(); + } + catch(InterruptedException e) { + e.printStackTrace(); + } + } + + for(int i=0; i < 50; i++) { + if(removed_msgs.get() >= num_msgs) + break; + else { + System.out.println("removed: " + removed_msgs.get()); + Util.sleep(100); + List msgs=win.removeManyAsList(segment_size); + if(msgs != null && !msgs.isEmpty()) + removed_msgs.addAndGet(msgs.size()); + } + } + + long time=System.currentTimeMillis() - start; + double requests_sec=num_msgs / (time / 1000.0); + System.out.println("\nTime: " + time + " ms, " + Util.format(requests_sec) + " requests / sec\n"); + System.out.println("Total removed messages: " + removed_msgs); + assert removed_msgs.get() == num_msgs : + "removed messages (" + removed_msgs.get() + ") != num_msgs (" + num_msgs + ")"; + } + + + static class Adder extends Thread { + final AckReceiverWindow win; + final CountDownLatch latch; + final AtomicInteger num_msgs; + final AtomicLong current_seqno; + final AtomicInteger removed_msgs; + final static AtomicBoolean processing=new AtomicBoolean(false); + + public Adder(AckReceiverWindow win, CountDownLatch latch, AtomicInteger num_msgs, + AtomicLong current_seqno, AtomicInteger removed_msgs) { + this.win=win; + this.latch=latch; + this.num_msgs=num_msgs; + this.current_seqno=current_seqno; + this.removed_msgs=removed_msgs; + } + + + public void run() { + try { + latch.await(); + } + catch(InterruptedException e) { + e.printStackTrace(); + return; + } + + Message msg=new Message(false); + + while(num_msgs.getAndDecrement() > 0) { + long seqno=current_seqno.getAndIncrement(); + + int result=win.add2(seqno, msg); + if(result != 1) + System.err.println("seqno " + seqno + " not added correctly"); + + // simulates UNICAST: all threads call add2() concurrently, but only 1 thread removes messages + if(processing.compareAndSet(false, true)) { + try { + while(true) { + List msgs=win.removeManyAsList(20000); + if(msgs == null || msgs.isEmpty()) + break; + removed_msgs.addAndGet(msgs.size()); + } + } + finally { + processing.set(false); + } + } + } + } + } + + + @Test(enabled=false) + public static void main(String[] args) { + int num_threads=50; + int num_msgs=1000000; + int segment_size=20000; + + for(int i=0; i < args.length; i++) { + if(args[i].equals("-num_threads")) { + num_threads=Integer.parseInt(args[++i]); + continue; + } + if(args[i].equals("-num_msgs")) { + num_msgs=Integer.parseInt(args[++i]); + continue; + } + if(args[i].equals("-segment_size")) { + segment_size=Integer.parseInt(args[++i]); + continue; + } + System.out.println("AckReceiverWindowStressTest [-num_msgs msgs] [-num_threads threads] [-segment_size ]"); + return; + } + start(num_threads, num_msgs, segment_size); + } +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/AckReceiverWindowTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/AckReceiverWindowTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/AckReceiverWindowTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/AckReceiverWindowTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -3,22 +3,42 @@ import org.jgroups.Global; import org.jgroups.Message; +import org.jgroups.util.Util; +import org.jgroups.util.Tuple; import org.jgroups.stack.AckReceiverWindow; import org.testng.Assert; import org.testng.annotations.Test; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.AfterMethod; + +import java.util.concurrent.CountDownLatch; +import java.util.List; +import java.util.LinkedList; +import java.util.concurrent.atomic.AtomicInteger; /** * @author Bela Ban - * @version $Id: AckReceiverWindowTest.java,v 1.5 2008/06/10 09:03:53 belaban Exp $ */ -@Test(groups=Global.FUNCTIONAL,sequential=false) +@Test(groups=Global.FUNCTIONAL,sequential=true) public class AckReceiverWindowTest { + AckReceiverWindow win; + + @BeforeMethod + public void setUp() throws Exception { + win=new AckReceiverWindow(1); + } + + @AfterMethod + public void tearDown() throws Exception { + win.reset(); + } - public static void test1() { + public void testAdd() { Message m; - AckReceiverWindow win=new AckReceiverWindow(10); + win.reset(); // use a different window for this test method + win=new AckReceiverWindow(10); Assert.assertEquals(0, win.size()); win.add(9, msg()); Assert.assertEquals(0, win.size()); @@ -52,99 +72,221 @@ assert m == null; } + public void testAddExisting() { + win.add(1, msg()); + assert win.size() == 1; + win.add(1, msg()); + assert win.size() == 1; + win.add(2, msg()); + assert win.size() == 2; + } - public static void testDuplicates() { - boolean rc; - AckReceiverWindow win=new AckReceiverWindow(2); + public void testAddLowerThanNextToRemove() { + win.add(1, msg()); + win.add(2, msg()); + win.remove(); // next_to_remove is 2 + win.add(1, msg()); + assert win.size() == 1; + } + + public void testRemove() { + win.add(2, msg()); + Message msg=win.remove(); + assert msg == null; + assert win.size() == 1; + win.add(1, msg()); + assert win.size() == 2; + assert win.remove() != null; + assert win.size() == 1; + assert win.remove() != null; + assert win.size() == 0; + assert win.remove() == null; + } + + + + public void testDuplicates() { Assert.assertEquals(0, win.size()); - rc=win.add(9, msg()); - assert rc; - rc=win.add(1, msg()); - assert !(rc); - rc=win.add(2, msg()); - assert rc; - rc=win.add(2, msg()); - assert !(rc); - rc=win.add(0, msg()); - assert !(rc); - rc=win.add(3, msg()); - assert rc; - rc=win.add(4, msg()); - assert rc; - rc=win.add(4, msg()); - assert !(rc); + assert win.add(9, msg()); + assert win.add(1, msg()); + assert win.add(2, msg()); + assert !win.add(2, msg()); + assert !win.add(0, msg()); + assert win.add(3, msg()); + assert win.add(4, msg()); + assert !win.add(4, msg()); } - public static void testMessageReadyForRemoval() { + + + public static void testRemoveRegularMessages() { AckReceiverWindow win=new AckReceiverWindow(1); - System.out.println("win = " + win); - assert !win.hasMessagesToRemove(); - win.add(2, msg()); - System.out.println("win = " + win); - assert !win.hasMessagesToRemove(); - win.add(3, msg()); - System.out.println("win = " + win); - assert !win.hasMessagesToRemove(); win.add(1, msg()); System.out.println("win = " + win); - assert win.hasMessagesToRemove(); - win.remove(); System.out.println("win = " + win); - assert win.hasMessagesToRemove(); + assert win.size() == 0; + win.add(3, msg()); win.remove(); System.out.println("win = " + win); - assert win.hasMessagesToRemove(); + assert win.size() == 1; + win.add(2, msg(true)); win.remove(); System.out.println("win = " + win); - assert !win.hasMessagesToRemove(); + + assert win.size() == 1; } - public static void testRemoveOOBMessage() { + public static void testRemoveMany() { AckReceiverWindow win=new AckReceiverWindow(1); - System.out.println("win = " + win); + Tuple, Long> tuple=win.removeMany(100); + assert tuple == null; + win.add(2, msg()); - System.out.println("win = " + win); - assert win.removeOOBMessage() == null; - assert win.remove() == null; - win.add(1, msg(true)); - System.out.println("win = " + win); - assert win.removeOOBMessage() != null; - System.out.println("win = " + win); - assert win.removeOOBMessage() == null; - assert win.remove() != null; - assert win.remove() == null; - assert win.removeOOBMessage() == null; + win.add(4, msg()); + tuple=win.removeMany(100); + assert tuple == null; + + win.add(3, msg()); + win.add(1, msg()); + + tuple=win.removeMany(10); + List list=tuple.getVal1(); + assert list.size() == 4; + assert tuple.getVal2() == 4; } - public static void testRemoveRegularAndOOBMessages() { + + public static void testRemoveMany2() { AckReceiverWindow win=new AckReceiverWindow(1); - win.add(1, msg()); + for(int i=1; i <=50; i++) + win.add(i, msg()); System.out.println("win = " + win); - win.remove(); + List list=win.removeManyAsList(25); + System.out.println("win = " + win); - assert win.size() == 0; + assert list != null && list.size() == 25; + assert win.size() == 25; - win.add(3, msg()); - win.remove(); + list=win.removeManyAsList(30); System.out.println("win = " + win); - assert win.size() == 1; + assert list != null && list.size() == 25; + assert win.size() == 0; + } + + + public static void testSmallerThanNextToRemove() { + AckReceiverWindow win=new AckReceiverWindow(1); + Message msg; + for(long i=1; i <= 5; i++) + win.add(i, msg()); + System.out.println("win = " + win); + boolean added=win.add(3, msg()); + assert !added; + for(;;) { + msg=win.remove(); + if(msg == null) + break; + } + added=win.add(3, msg()); + assert !added; + } + + + public static void testConcurrentAdds() throws InterruptedException { + AckReceiverWindow win=new AckReceiverWindow(1); + final int NUM=100; + final int NUM_THREADS=10; + final CountDownLatch latch=new CountDownLatch(1); + + final Adder[] adders=new Adder[NUM_THREADS]; + for(int i=0; i < adders.length; i++) { + adders[i]=new Adder(1, NUM, 10, win, latch); + } + for(Adder adder: adders) + adder.start(); + + latch.countDown(); + + for(Adder adder: adders) + adder.join(); - win.add(2, msg(true)); - win.removeOOBMessage(); System.out.println("win = " + win); + assert win.size() == NUM; + } - assert win.size() == 1; + @Test(invocationCount=10) + public static void testConcurrentAddsAndRemoves() throws InterruptedException { + AckReceiverWindow win=new AckReceiverWindow(1); + final int NUM=100; + final int NUM_THREADS=10; + final CountDownLatch latch=new CountDownLatch(1); + + final Adder[] adders=new Adder[NUM_THREADS]; + for(int i=0; i < adders.length; i++) { + adders[i]=new Adder(1, NUM, 10, win, latch); + adders[i].start(); + } + + final AtomicInteger count=new AtomicInteger(NUM); + final Remover[] removers=new Remover[NUM_THREADS]; + for(int i=0; i < removers.length; i++) { + removers[i]=new Remover(win, latch, count); + removers[i].start(); + } + + latch.countDown(); + + for(Adder adder: adders) + adder.join(); + + int total=0; + int index=1; + for(Remover remover: removers) { + remover.join(); + List list=remover.getList(); + System.out.println("remover #" + index++ + ": " + list.size() + " msgs"); + total+=list.size(); + } + + System.out.println("total = " + total + "\n"); + if(total != NUM) { + for(Remover remover: removers) { + System.out.println(remover + ": " + print(remover.getList())); + } + } + assert total == NUM; } + private static String print(List list) { + StringBuilder sb=new StringBuilder(); + for(Message msg: list) { + if(msg == AckReceiverWindow.TOMBSTONE) + sb.append("T "); + else + sb.append(msg.getObject() + " "); + } + return sb.toString(); + } private static Message msg() { return msg(false); } + private static Message msg(long seqno) { + return msg(false, seqno); + } + + private static Message msg(boolean oob, long seqno) { + Message retval=new Message(null, null, seqno); + if(oob) + retval.setFlag(Message.OOB); + return retval; + } + private static Message msg(boolean oob) { Message retval=new Message(); if(oob) @@ -153,5 +295,72 @@ } + private static class Adder extends Thread { + private final long from, to, duplicates; + private final AckReceiverWindow win; + private final CountDownLatch latch; + + public Adder(long from, long to, long duplicates, AckReceiverWindow win, CountDownLatch latch) { + this.from=from; + this.to=to; + this.duplicates=duplicates; + this.win=win; + this.latch=latch; + setName("Adder"); + } + + public void run() { + try { + latch.await(); + } + catch(InterruptedException e) { + e.printStackTrace(); + } + for(long i=from; i <= to; i++) { + for(int j=0; j < duplicates; j++) { + win.add(i, msg(true, i)); + } + } + } + } + + private static class Remover extends Thread { + private final AckReceiverWindow win; + private final CountDownLatch latch; + private final List list=new LinkedList(); + private final AtomicInteger count; + + public Remover(AckReceiverWindow win, CountDownLatch latch, final AtomicInteger count) { + this.win=win; + this.latch=latch; + this.count=count; + setName("Remover"); + } + + public List getList() { + return list; + } + + public void run() { + try { + latch.await(); + } + catch(InterruptedException e) { + e.printStackTrace(); + return; + } + + while(count.get() > 0) { + Message msg=win.remove(); + if(msg != null) { + count.decrementAndGet(); + list.add(msg); + } + else { + Util.sleep(100); + } + } + } + } } diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/AckSenderWindowTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/AckSenderWindowTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/AckSenderWindowTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/AckSenderWindowTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -5,8 +5,10 @@ import org.jgroups.Message; import org.jgroups.stack.AckSenderWindow; import org.jgroups.stack.StaticInterval; -import org.jgroups.util.Util; +import org.jgroups.util.DefaultTimeScheduler; import org.jgroups.util.TimeScheduler; +import org.jgroups.util.Util; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.Map; @@ -16,26 +18,54 @@ /** * Test cases for AckSenderWindow * @author Bela Ban - * @version $Id: AckSenderWindowTest.java,v 1.6 2008/05/14 11:57:58 belaban Exp $ */ -@Test(groups=Global.FUNCTIONAL) +@Test(groups=Global.FUNCTIONAL,sequential=true) public class AckSenderWindowTest { - AckSenderWindow win=null; static final int NUM_MSGS=100; final static long[] xmit_timeouts={1000, 2000, 4000, 8000}; static final double PERCENTAGE_OFF=1.3; // how much can expected xmit_timeout and real timeout differ to still be okay ? final Map msgs=new ConcurrentHashMap(); // keys=seqnos (Long), values=Entries + @DataProvider(name="provider") + Object[][] provider() { + final TimeScheduler timer=new DefaultTimeScheduler(10); + return new Object[][] { + {timer, + new AckSenderWindow(new MyRetransmitCommand(), new StaticInterval(xmit_timeouts), timer)} + }; + } + + + + @Test(dataProvider="provider") + public void testSimpleAdd(TimeScheduler timer, AckSenderWindow win) throws InterruptedException { + try { + for(int i=1; i <=5; i++) + win.add(i, new Message()); + System.out.println("win = " + win); + assert win.size() == 5; + win.ack(1); + System.out.println("win = " + win); + assert win.size() == 4; + win.ack(4); + System.out.println("win = " + win); + assert win.size() == 1; + win.ack(44); + assert win.size() == 0; + } + finally { + timer.stop(); + win.reset(); + } + } + /** Tests whether retransmits are called at correct times for 1000 messages */ - @Test(groups=Global.FUNCTIONAL) - public void testRetransmits() throws InterruptedException { + @Test(dataProvider="provider") + public void testRetransmits(TimeScheduler timer, AckSenderWindow win) throws InterruptedException { int num_non_correct_entries=0; - TimeScheduler timer=new TimeScheduler(); - win=new AckSenderWindow(new MyRetransmitCommand(), new StaticInterval(xmit_timeouts), timer); try { - // 1. Send NUM_MSGS messages: System.out.println("-- sending " + NUM_MSGS + " messages:"); for(long i=0; i < NUM_MSGS; i++) { @@ -46,10 +76,10 @@ // 2. Wait for at least 4 xmits/msg: total of 1000 + 2000 + 4000 + 8000ms = 15000ms; wait for 20000ms System.out.println("-- waiting for all retransmits"); - long end_time=System.currentTimeMillis() + 20000L, curr, start=System.currentTimeMillis(); + long end_time=System.currentTimeMillis() + 20000L, start=System.currentTimeMillis(); Util.sleep(1000); - while((curr=System.currentTimeMillis()) < end_time) { + while(System.currentTimeMillis() < end_time) { // 3. Check whether all Entries have correct retransmission times num_non_correct_entries=checkEntries(false); if(num_non_correct_entries == 0) @@ -65,13 +95,107 @@ assert num_non_correct_entries == 0; } finally { + timer.stop(); win.reset(); + } + } + + + @Test(dataProvider="provider") + public void testLowest(TimeScheduler timer, AckSenderWindow win) { + try { + for(long i=1; i < 5; i++) + win.add(i, new Message()); + System.out.println("win = " + win + ", lowest=" + win.getLowest()); + assert win.getLowest() == Global.DEFAULT_FIRST_UNICAST_SEQNO; + + win.ack(3); + System.out.println("win = " + win + ", lowest=" + win.getLowest()); + assert win.getLowest() == 4; + + win.ack(4); + System.out.println("win = " + win + ", lowest=" + win.getLowest()); + assert win.getLowest() == 5; + + win.ack(2); + assert win.getLowest() == 5; + } + finally { timer.stop(); + win.reset(); } } - int checkEntries(boolean print) { + @Test(dataProvider="provider") + public void testGetLowestMessage(TimeScheduler timer, AckSenderWindow win) { + long[] seqnos={1,2,3,4,5}; + final Message[] messages=new Message[]{new Message(),new Message(),new Message(),new Message(),new Message()}; + + + try { + for(int i=0; i < seqnos.length; i++) { + win.add(seqnos[i], messages[i]); + } + System.out.println("win = " + win); + + Message msg=win.getLowestMessage(); + assert messages[0] == msg; + + win.ack(2); + msg=win.getLowestMessage(); + assert messages[2] == msg; + + win.ack(7); + msg=win.getLowestMessage(); + assert msg == null; + } + finally { + timer.stop(); + win.reset(); + } + } + + + @Test(dataProvider="provider") + public void testAdd(TimeScheduler timer, AckSenderWindow win) { + try { + for(int i=1; i <= 10; i++) + win.add(i, new Message()); + System.out.println("win = " + win); + assert win.size() == 10; + win.ack(7); + assert win.size() == 3; + } + finally { + timer.stop(); + win.reset(); + } + } + + + @Test(dataProvider="provider") + public void testAck(TimeScheduler timer, AckSenderWindow win) { + try { + for(int i=1; i <= 3; i++) + win.add(i, new Message()); + assert win.size() == 3; + win.ack(1); + assert win.size() == 2; + win.ack(2); + assert win.size() == 1; + win.ack(3); + assert win.size() == 0; + } + finally { + timer.stop(); + win.reset(); + } + } + + + + private int checkEntries(boolean print) { int retval=0; Entry entry; for(long i=0; i < NUM_MSGS; i++) { diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/AgeOutCacheTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/AgeOutCacheTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/AgeOutCacheTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/AgeOutCacheTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,108 @@ +package org.jgroups.tests; + +import org.jgroups.Global; +import org.jgroups.util.AgeOutCache; +import org.jgroups.util.TimeScheduler; +import org.jgroups.util.Util; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test cases for AgeOutCache + * + * @author Bela Ban + */ +@Test(groups = Global.FUNCTIONAL,sequential=true) +public class AgeOutCacheTest { + + @DataProvider(name="timerCreator") + protected Object[][] timerCreator() { + return Util.createTimer(); + } + + + @Test(dataProvider="timerCreator") + public void testExpiration(TimeScheduler timer) { + AgeOutCache cache=new AgeOutCache(timer, 500L, + new AgeOutCache.Handler() { + public void expired(Integer key) { + System.out.println(key + " expired"); + } + }); + for(int i = 1; i <= 5; i++) + cache.add(i); + System.out.println("cache:\n" + cache); + int size=cache.size(); + assert size == 5 : "size is " + size; + + for(int i=0; i < 30; i++) { + if(cache.size() == 0) + break; + Util.sleep(1000); + } + System.out.println("cache:\n" + cache); + size=cache.size(); + assert size == 0 : "size is " + size; + timer.stop(); + } + + + @Test(dataProvider="timerCreator") + public void testRemoveAndExpiration(TimeScheduler timer) { + AgeOutCache cache = new AgeOutCache(timer, 500L); + for (int i = 1; i <= 5; i++) + cache.add(i); + System.out.println("cache:\n" + cache); + cache.remove(3); + cache.remove(5); + cache.remove(6); // not existent + System.out.println("cache:\n" + cache); + assert cache.size() == 3 : "cache size should be 3 but is " + cache; + + for(int i=0; i < 10; i++) { + if(cache.size() == 0) + break; + Util.sleep(500); + } + + assert cache.size() == 0; + timer.stop(); + } + + + @Test(dataProvider="timerCreator") + public void testContains(TimeScheduler timer) { + AgeOutCache cache = new AgeOutCache(timer, 5000L); + for (int i = 1; i <= 5; i++) + cache.add(i); + System.out.println("cache:\n" + cache); + assert cache.contains(3); + cache.remove(3); + System.out.println("cache:\n" + cache); + assert !cache.contains(3); + cache.clear(); + assert cache.size() == 0; + assert !cache.contains(4); + timer.stop(); + } + + + @Test(dataProvider="timerCreator") + public void testGradualExpiration(TimeScheduler timer) { + AgeOutCache cache = new AgeOutCache(timer, 5000L, + new AgeOutCache.Handler() { + public void expired(Integer key) { + System.out.println(key + " expired"); + } + }); + for (int i = 1; i <= 10; i++) { + cache.add(i); + System.out.print("."); + Util.sleep(1000); + } + System.out.println("\ncache:\n" + cache); + int size = cache.size(); + assert size < 10 && size > 0 : " size was " + size + ", but should have been < 10 or > 0"; + timer.stop(); + } +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/BARRIERTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/BARRIERTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/BARRIERTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/BARRIERTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -5,24 +5,23 @@ import org.jgroups.protocols.BARRIER; import org.jgroups.protocols.PING; import org.jgroups.protocols.VIEW_SYNC; -import org.jgroups.stack.IpAddress; import org.jgroups.stack.Protocol; +import org.jgroups.util.UUID; import org.jgroups.util.Util; -import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.Vector; +import java.util.concurrent.atomic.AtomicInteger; /** * Tests the BARRIER protocol * @author Bela Ban - * @version $Id: BARRIERTest.java,v 1.4 2008/04/08 12:18:32 belaban Exp $ */ @Test(groups=Global.FUNCTIONAL, sequential=true) public class BARRIERTest { - IpAddress a1; + Address a1; Vector
            members; View v; Simulator s; @@ -32,7 +31,7 @@ @BeforeMethod public void setUp() throws Exception { - a1=new IpAddress(1111); + a1=UUID.randomUUID(); members=new Vector
            (); members.add(a1); v=new View(a1, 1, members); @@ -41,7 +40,8 @@ s.setView(v); s.addMember(a1); bottom_prot=new PING(); - Protocol[] stack=new Protocol[]{new VIEW_SYNC(), barrier_prot, bottom_prot}; + VIEW_SYNC view_sync=new VIEW_SYNC(); + Protocol[] stack=new Protocol[]{view_sync, barrier_prot, bottom_prot}; s.setProtocolStack(stack); s.start(); } @@ -53,11 +53,11 @@ public void testBlocking() { - assert !(barrier_prot.isClosed()); + assert !barrier_prot.isClosed(); s.send(new Event(Event.CLOSE_BARRIER)); assert barrier_prot.isClosed(); s.send(new Event(Event.OPEN_BARRIER)); - assert !(barrier_prot.isClosed()); + assert !barrier_prot.isClosed(); } @@ -73,15 +73,17 @@ }.start(); } - Util.sleep(500); + Util.sleep(2000); int num_in_flight_threads=barrier_prot.getNumberOfInFlightThreads(); - Assert.assertEquals(0, num_in_flight_threads); + assert num_in_flight_threads == 0; s.send(new Event(Event.OPEN_BARRIER)); - Util.sleep(500); + Util.sleep(2000); + num_in_flight_threads=barrier_prot.getNumberOfInFlightThreads(); - Assert.assertEquals(0, num_in_flight_threads); - Assert.assertEquals(5, receiver.getNumberOfReceivedMessages()); + assert num_in_flight_threads == 0; + int received_msgs=receiver.getNumberOfReceivedMessages(); + assert received_msgs == 5 : "expected " + 5 + " messages but got " + received_msgs; } @@ -108,18 +110,17 @@ static class MyReceiver implements Simulator.Receiver { - int num_mgs_received=0; + AtomicInteger num_mgs_received=new AtomicInteger(0); public void receive(Event evt) { if(evt.getType() == Event.MSG) { - num_mgs_received++; - if(num_mgs_received % 1000 == 0) - System.out.println("<== " + num_mgs_received); + if(num_mgs_received.incrementAndGet() % 1000 == 0) + System.out.println("<== " + num_mgs_received.get()); } } public int getNumberOfReceivedMessages() { - return num_mgs_received; + return num_mgs_received.get(); } } diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ConfiguratorTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ConfiguratorTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ConfiguratorTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ConfiguratorTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,7 +1,8 @@ package org.jgroups.tests; -import org.jgroups.JChannel; import org.jgroups.Global; +import org.jgroups.JChannel; +import org.jgroups.conf.ProtocolConfiguration; import org.jgroups.stack.Configurator; import org.jgroups.stack.Protocol; import org.jgroups.stack.ProtocolStack; @@ -10,17 +11,15 @@ import org.testng.annotations.Test; import java.util.List; -import java.util.Vector; /** * Tests ProtocolStack.insertProtocol() and removeProtocol() * @author Bela Ban - * @version $Id: ConfiguratorTest.java,v 1.9 2008/09/25 14:26:06 belaban Exp $ */ @Test(groups=Global.FUNCTIONAL,sequential=true) public class ConfiguratorTest { ProtocolStack stack; - static final String props="UDP(mcast_addr=225.1.2.3):PING:FD:pbcast.NAKACK(retransmit_timeouts=300,600):UNICAST:FC"; + static final String props="UDP:PING:FD:pbcast.NAKACK(retransmit_timeouts=300,600):UNICAST:FC"; final String[] names={"FC", "UNICAST", "NAKACK", "FD", "PING", "UDP"}; final String[] below={"FC", "UNICAST", "TRACE", "NAKACK", "FD", "PING", "UDP"}; final String[] above={"FC", "TRACE", "UNICAST", "NAKACK", "FD", "PING", "UDP"}; @@ -29,19 +28,18 @@ @BeforeMethod void setUp() throws Exception { - JChannel mock_channel=new JChannel() { - }; - stack=new ProtocolStack(mock_channel, props); + JChannel mock_channel=new JChannel() {}; + stack=new ProtocolStack(mock_channel); } public void testRemovalOfTop() throws Exception { - stack.setup(); + stack.setup(Configurator.parseConfigurations(props)); Protocol prot=stack.removeProtocol("FC"); assert prot != null; - Vector protocols=stack.getProtocols(); + List protocols=stack.getProtocols(); Assert.assertEquals(5, protocols.size()); - assert protocols.firstElement().getName().endsWith("UNICAST"); + assert protocols.get(0).getName().endsWith("UNICAST"); assert stack.getTopProtocol().getUpProtocol() != null; assert stack.getTopProtocol().getDownProtocol() != null; assert stack.getTopProtocol().getDownProtocol().getUpProtocol() != null; @@ -49,21 +47,21 @@ } public void testRemovalOfBottom() throws Exception { - stack.setup(); + stack.setup(Configurator.parseConfigurations(props)); Protocol prot=stack.removeProtocol("UDP"); assert prot != null; - Vector protocols=stack.getProtocols(); + List protocols=stack.getProtocols(); Assert.assertEquals(5, protocols.size()); - assert protocols.lastElement().getName().endsWith("PING"); + assert protocols.get(protocols.size() -1).getName().endsWith("PING"); } public void testAddingAboveTop() throws Exception{ - stack.setup(); + stack.setup(Configurator.parseConfigurations(props)); Protocol new_prot=(Protocol)Class.forName("org.jgroups.protocols.TRACE").newInstance(); stack.insertProtocol(new_prot, ProtocolStack.ABOVE, "FC"); - Vector protocols=stack.getProtocols(); + List protocols=stack.getProtocols(); Assert.assertEquals(7, protocols.size()); - assert protocols.firstElement().getName().endsWith("TRACE"); + assert protocols.get(0).getName().endsWith("TRACE"); assert stack.getTopProtocol().getUpProtocol() != null; assert stack.getTopProtocol().getDownProtocol() != null; assert stack.getTopProtocol().getDownProtocol().getUpProtocol() != null; @@ -72,7 +70,7 @@ @Test(expectedExceptions={IllegalArgumentException.class}) public void testAddingBelowBottom() throws Exception{ - stack.setup(); + stack.setup(Configurator.parseConfigurations(props)); Protocol new_prot=(Protocol)Class.forName("org.jgroups.protocols.TRACE").newInstance(); stack.insertProtocol(new_prot, ProtocolStack.BELOW, "UDP"); } @@ -80,7 +78,7 @@ public void testInsertion() throws Exception { - stack.setup(); + stack.setup(Configurator.parseConfigurations(props)); List protocols=stack.getProtocols(); assert protocols != null; Assert.assertEquals(6, protocols.size()); @@ -148,24 +146,22 @@ "discard_delivered_msgs=true):" + "UNICAST(loopback=false;timeout=300,600,1200,2400,3600):" + "pbcast.STABLE(desired_avg_gossip=50000;max_bytes=1000000;stability_delay=1000):" + - "VIEW_SYNC(avg_send_interval=60000):" + - "pbcast.GMS(print_local_addr=true;view_bundling=true;join_timeout=3000;" + - "shun=false):" + + "pbcast.GMS(print_local_addr=true;view_bundling=true;join_timeout=3000):" + "FC(max_block_time=10000;max_credits=5000000;min_threshold=0.25):" + "FRAG2(frag_size=60000):" + "pbcast.STREAMING_STATE_TRANSFER(use_reading_thread=true)"; - Vector ret=new Configurator().parseConfigurations(config); + List ret=Configurator.parseConfigurations(config); System.out.println("config:\n" + ret); - Assert.assertEquals(15, ret.size()); + Assert.assertEquals(14, ret.size()); config="UDP(mcast_addr=ff18:eb72:479f::2:3;mcast_port=2453):pbcast.FD:FRAG(frag_size=2292):FD_SIMPLE(s=22;d=33):MERGE2(a=22)"; - ret=new Configurator().parseConfigurations(config); + ret=Configurator.parseConfigurations(config); System.out.println("config:\n" + ret); Assert.assertEquals(5, ret.size()); config="com.mycomp.Class:B:pbcast.C:H(a=b;c=d;e=f)"; - ret=new Configurator().parseConfigurations(config); + ret=Configurator.parseConfigurations(config); System.out.println("config:\n" + ret); Assert.assertEquals(4, ret.size()); } diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ConnectionMapUnitTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ConnectionMapUnitTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ConnectionMapUnitTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ConnectionMapUnitTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,215 @@ + +package org.jgroups.tests; + + +import org.jgroups.Address; +import org.jgroups.Global; +import org.jgroups.blocks.TCPConnectionMap; +import org.jgroups.util.DefaultThreadFactory; +import org.jgroups.util.Util; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + + +/** + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL,sequential=true) +public class ConnectionMapUnitTest { + TCPConnectionMap ct1, ct2; + static final int port1=15555, port2=16666; + + + + @BeforeMethod + protected void setUp() throws Exception { + ct1=new TCPConnectionMap("TCPConnectionMap1", + new DefaultThreadFactory(Util.getGlobalThreadGroup(), "test", true), + null, null, null, null, port1, port1); + + ct1.setUseSendQueues(false); + ct1.start(); + ct2=new TCPConnectionMap("TCPConnectionMap2", + new DefaultThreadFactory(Util.getGlobalThreadGroup(), "test", true), + null, null, null, null, port2, port2); + ct2.setUseSendQueues(false); + ct2.start(); + } + + @AfterMethod + void tearDown() throws Exception { + if(ct1 != null) { + ct1.stop(); + ct1=null; + } + if(ct2 != null) { + ct2.stop(); + ct2=null; + } + } + + public void testSetup() { + Assert.assertNotSame(ct1.getLocalAddress(), ct2.getLocalAddress()); + } + + public void testSendToNullReceiver() throws Exception { + byte[] data=new byte[0]; + ct1.send(null, data, 0, data.length); + } + + public void testSendEmptyData() throws Exception { + byte[] data=new byte[0]; + Address myself=ct1.getLocalAddress(); + ct1.setReceiver(new TCPConnectionMap.Receiver() { + public void receive(Address sender, byte[] data, int offset, int length) {} + }); + ct1.send(myself, data, 0, data.length); + } + + public void testSendNullData() throws Exception { + Address myself=ct1.getLocalAddress(); + ct1.send(myself, null, 0, 0); + } + + + public void testSendToSelf() throws Exception { + long NUM=1000, total_time; + Address myself=ct1.getLocalAddress(); + MyReceiver r=new MyReceiver(ct1, NUM, false); + byte[] data=new byte[] {'b', 'e', 'l', 'a'}; + + ct1.setReceiver(r); + + for(int i=0; i < NUM; i++) { + ct1.send(myself, data, 0, 0); + } + log("sent " + NUM + " msgs"); + r.waitForCompletion(); + total_time=r.stop_time - r.start_time; + log("number expected=" + r.getNumExpected() + ", number received=" + r.getNumReceived() + + ", total time=" + total_time + " (" + (double)total_time / r.getNumReceived() + " ms/msg)"); + + Assert.assertEquals(r.getNumExpected(), r.getNumReceived()); + } + + public void testSendToOther() throws Exception { + long NUM=1000, total_time; + Address other=ct2.getLocalAddress(); + MyReceiver r=new MyReceiver(ct2, NUM, false); + byte[] data=new byte[] {'b', 'e', 'l', 'a'}; + + ct2.setReceiver(r); + + for(int i=0; i < NUM; i++) { + ct1.send(other, data, 0, 0); + } + log("sent " + NUM + " msgs"); + r.waitForCompletion(); + total_time=r.stop_time - r.start_time; + log("number expected=" + r.getNumExpected() + ", number received=" + r.getNumReceived() + + ", total time=" + total_time + " (" + (double)total_time / r.getNumReceived() + " ms/msg)"); + + Assert.assertEquals(r.getNumExpected(), r.getNumReceived()); + } + + + public void testSendToOtherGetResponse() throws Exception { + long NUM=1000, total_time; + Address other=ct2.getLocalAddress(); + MyReceiver r1=new MyReceiver(ct1, NUM, false); + MyReceiver r2=new MyReceiver(ct2, NUM, true); // send response + byte[] data=new byte[] {'b', 'e', 'l', 'a'}; + + ct1.setReceiver(r1); + ct2.setReceiver(r2); + + for(int i=0; i < NUM; i++) { + ct1.send(other, data, 0, 0); + } + log("sent " + NUM + " msgs"); + r1.waitForCompletion(); + total_time=r1.stop_time - r1.start_time; + log("number expected=" + r1.getNumExpected() + ", number received=" + r1.getNumReceived() + + ", total time=" + total_time + " (" + (double)total_time / r1.getNumReceived() + " ms/msg)"); + + Assert.assertEquals(r1.getNumExpected(), r1.getNumReceived()); + } + + + static void log(String msg) { + System.out.println("-- [" + Thread.currentThread() + "]: " + msg); + } + + + + + + static class MyReceiver implements TCPConnectionMap.Receiver { + long num_expected=0, num_received=0, start_time=0, stop_time=0; + boolean done=false, send_response=false; + long modulo; + TCPConnectionMap ct; + + MyReceiver(TCPConnectionMap ct, long num_expected, boolean send_response) { + this.ct=ct; + this.num_expected=num_expected; + this.send_response=send_response; + start_time=System.currentTimeMillis(); + modulo=num_expected / 10; + } + + + public long getNumReceived() { + return num_received; + } + + public long getNumExpected() { + return num_expected; + } + + + public void receive(Address sender, byte[] data, int offset, int length) { + num_received++; + if(num_received % modulo == 0) + log("received msg# " + num_received); + if(send_response) { + if(ct != null) { + try { + byte[] rsp=new byte[0]; + ct.send(sender, rsp, 0, 0); + } + catch(Exception e) { + e.printStackTrace(); + } + } + } + if(num_received >= num_expected) { + synchronized(this) { + if(!done) { + done=true; + stop_time=System.currentTimeMillis(); + notifyAll(); + } + } + } + } + + + public void waitForCompletion() { + synchronized(this) { + while(!done) { + try { + wait(); + } + catch(InterruptedException e) { + } + } + } + } + + + } + +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ConnectionTableUnitTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ConnectionTableUnitTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ConnectionTableUnitTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ConnectionTableUnitTest.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,213 +0,0 @@ - -package org.jgroups.tests; - - -import org.jgroups.Address; -import org.jgroups.Global; -import org.jgroups.blocks.ConnectionTable; -import org.jgroups.blocks.BasicConnectionTable; -import org.testng.Assert; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - - -/** - * @author Bela Ban - * @version $Id: ConnectionTableUnitTest.java,v 1.5 2008/05/20 12:49:07 belaban Exp $ - */ -@Test(groups=Global.FUNCTIONAL,sequential=true) -public class ConnectionTableUnitTest { - ConnectionTable ct1, ct2; - static final int port1=5555, port2=6666; - - - - - @BeforeMethod - protected void setUp() throws Exception { - ct1=new ConnectionTable(port1); - ct1.setUseSendQueues(false); - ct1.start(); - // log("address of ct1: " + ct1.getLocalAddress()); - ct2=new ConnectionTable(port2); - ct2.setUseSendQueues(false); - ct2.start(); - //log("address of ct2: " + ct2.getLocalAddress()); - } - - @AfterMethod - void tearDown() throws Exception { - if(ct1 != null) { - ct1.stop(); - ct1=null; - } - if(ct2 != null) { - ct2.stop(); - ct2=null; - } - } - - public void testSetup() { - Assert.assertNotSame(ct1.getLocalAddress(), ct2.getLocalAddress()); - } - - public void testSendToNullReceiver() throws Exception { - byte[] data=new byte[0]; - ct1.send(null, data, 0, data.length); - } - - public void testSendEmptyData() throws Exception { - byte[] data=new byte[0]; - Address myself=ct1.getLocalAddress(); - ct1.setReceiver(new BasicConnectionTable.Receiver() { - public void receive(Address sender, byte[] data, int offset, int length) {} - }); - ct1.send(myself, data, 0, data.length); - } - - public void testSendNullData() throws Exception { - Address myself=ct1.getLocalAddress(); - ct1.send(myself, null, 0, 0); - } - - - public void testSendToSelf() throws Exception { - long NUM=1000, total_time; - Address myself=ct1.getLocalAddress(); - MyReceiver r=new MyReceiver(ct1, NUM, false); - byte[] data=new byte[] {'b', 'e', 'l', 'a'}; - - ct1.setReceiver(r); - - for(int i=0; i < NUM; i++) { - ct1.send(myself, data, 0, 0); - } - log("sent " + NUM + " msgs"); - r.waitForCompletion(); - total_time=r.stop_time - r.start_time; - log("number expected=" + r.getNumExpected() + ", number received=" + r.getNumReceived() + - ", total time=" + total_time + " (" + (double)total_time / r.getNumReceived() + " ms/msg)"); - - Assert.assertEquals(r.getNumExpected(), r.getNumReceived()); - } - - public void testSendToOther() throws Exception { - long NUM=1000, total_time; - Address other=ct2.getLocalAddress(); - MyReceiver r=new MyReceiver(ct2, NUM, false); - byte[] data=new byte[] {'b', 'e', 'l', 'a'}; - - ct2.setReceiver(r); - - for(int i=0; i < NUM; i++) { - ct1.send(other, data, 0, 0); - } - log("sent " + NUM + " msgs"); - r.waitForCompletion(); - total_time=r.stop_time - r.start_time; - log("number expected=" + r.getNumExpected() + ", number received=" + r.getNumReceived() + - ", total time=" + total_time + " (" + (double)total_time / r.getNumReceived() + " ms/msg)"); - - Assert.assertEquals(r.getNumExpected(), r.getNumReceived()); - } - - - public void testSendToOtherGetResponse() throws Exception { - long NUM=1000, total_time; - Address other=ct2.getLocalAddress(); - MyReceiver r1=new MyReceiver(ct1, NUM, false); - MyReceiver r2=new MyReceiver(ct2, NUM, true); // send response - byte[] data=new byte[] {'b', 'e', 'l', 'a'}; - - ct1.setReceiver(r1); - ct2.setReceiver(r2); - - for(int i=0; i < NUM; i++) { - ct1.send(other, data, 0, 0); - } - log("sent " + NUM + " msgs"); - r1.waitForCompletion(); - total_time=r1.stop_time - r1.start_time; - log("number expected=" + r1.getNumExpected() + ", number received=" + r1.getNumReceived() + - ", total time=" + total_time + " (" + (double)total_time / r1.getNumReceived() + " ms/msg)"); - - Assert.assertEquals(r1.getNumExpected(), r1.getNumReceived()); - } - - - static void log(String msg) { - System.out.println("-- [" + Thread.currentThread() + "]: " + msg); - } - - - - - - static class MyReceiver implements ConnectionTable.Receiver { - long num_expected=0, num_received=0, start_time=0, stop_time=0; - boolean done=false, send_response=false; - long modulo; - ConnectionTable ct; - - MyReceiver(ConnectionTable ct, long num_expected, boolean send_response) { - this.ct=ct; - this.num_expected=num_expected; - this.send_response=send_response; - start_time=System.currentTimeMillis(); - modulo=num_expected / 10; - } - - - public long getNumReceived() { - return num_received; - } - - public long getNumExpected() { - return num_expected; - } - - - public void receive(Address sender, byte[] data, int offset, int length) { - num_received++; - if(num_received % modulo == 0) - log("received msg# " + num_received); - if(send_response) { - if(ct != null) { - try { - byte[] rsp=new byte[0]; - ct.send(sender, rsp, 0, 0); - } - catch(Exception e) { - e.printStackTrace(); - } - } - } - if(num_received >= num_expected) { - synchronized(this) { - if(!done) { - done=true; - stop_time=System.currentTimeMillis(); - notifyAll(); - } - } - } - } - - - public void waitForCompletion() { - synchronized(this) { - while(!done) { - try { - wait(); - } - catch(InterruptedException e) { - } - } - } - } - - - } - -} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/CreditMapTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/CreditMapTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/CreditMapTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/CreditMapTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,296 @@ +package org.jgroups.tests; + +import org.jgroups.Address; +import org.jgroups.Global; +import org.jgroups.util.CreditMap; +import org.jgroups.util.Util; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.List; +import java.util.concurrent.CyclicBarrier; + +/** + * Tests CreditMap + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL,sequential=true) +public class CreditMapTest { + static Address a=Util.createRandomAddress("A"); + static Address b=Util.createRandomAddress("B"); + static Address c=Util.createRandomAddress("C"); + static Address d=Util.createRandomAddress("D"); + static long MAX_CREDITS=1000; + + protected CreditMap map; + + @BeforeMethod + void create() { + map=new CreditMap(MAX_CREDITS); + } + + @AfterMethod + void destroy() { + map.clear(); + } + + private void addAll() { + map.putIfAbsent(a); map.putIfAbsent(b); map.putIfAbsent(c); map.putIfAbsent(d); + } + + private void replenishAll(long credits) { + map.replenish(a, credits); + map.replenish(b, credits); + map.replenish(c, credits); + map.replenish(d, credits); + } + + + + public void testSimpleDecrement() { + addAll(); + + System.out.println("map:\n" + map); + + boolean rc=map.decrement(200, 4000); + System.out.println("rc=" + rc + ", map:\n" + map); + assert rc; + assert map.getMinCredits() == MAX_CREDITS - 200; + assert map.getAccumulatedCredits() == 200; + + rc=map.decrement(150, 100); + System.out.println("\nrc=" + rc + ", map:\n" + map); + assert rc; + assert map.getMinCredits() == MAX_CREDITS - 200 - 150; + assert map.getAccumulatedCredits() == 200 + 150; + + rc=map.decrement(300, 100); + System.out.println("\nrc=" + rc + ", map:\n" + map); + assert rc; + assert map.getMinCredits() == MAX_CREDITS - 200 - 150 - 300; + assert map.getAccumulatedCredits() == 200 + 150 + 300; + + rc=map.decrement(500, 100); + System.out.println("\nrc=" + rc + ", map:\n" + map); + assert !rc; + assert map.getMinCredits() == MAX_CREDITS - 200 - 150 - 300; + assert map.getAccumulatedCredits() == 200 + 150 + 300; + } + + public void testDecrementAndReplenish() { + testSimpleDecrement(); + map.replenish(a, MAX_CREDITS); + System.out.println("\nmap:\n" + map); + assert map.getMinCredits() == MAX_CREDITS - 200 - 150 - 300; + assert map.getAccumulatedCredits() == 0; + + map.replenish(b, MAX_CREDITS); + map.replenish(c, MAX_CREDITS); + System.out.println("\nmap:\n" + map); + assert map.getMinCredits() == MAX_CREDITS - 200 - 150 - 300; + assert map.getAccumulatedCredits() == 0; + + map.replenish(d, MAX_CREDITS); + System.out.println("\nmap:\n" + map); + assert map.getMinCredits() == MAX_CREDITS; + assert map.getAccumulatedCredits() == 0; + } + + public void testDecrementAndReplenish2() { + map.putIfAbsent(a); + map.decrement(200, 100); + + map.putIfAbsent(b); + map.decrement(200, 100); + + map.putIfAbsent(c); + map.decrement(200, 100); + + map.putIfAbsent(d); + map.decrement(200, 100); + + // A: 200, B: 400, C: 600, D: 800 + System.out.println("map = " + map); + + assert map.getAccumulatedCredits() == 200; + assert map.getMinCredits() == 200; + + map.replenish(d, 100); + map.replenish(c, 100); + map.replenish(a, 100); + map.replenish(b, 100); + + assert map.getMinCredits() == 300; + } + + public void testBlockingDecrementAndReplenishment() throws Exception { + final CyclicBarrier barrier=new CyclicBarrier(2); + + Thread thread=new Thread() { + public void run() { + try { + barrier.await(); + Util.sleep(1000); + replenishAll(100); + } + catch(Exception e) { + e.printStackTrace(); + } + } + }; + thread.start(); + + addAll(); + boolean rc=map.decrement(800, 100); + assert rc; + System.out.println("map:\n" + map); + + barrier.await(); + rc=map.decrement(250, 5000); + assert rc; + System.out.println("map:\n" + map); + assert map.getMinCredits() == 50; + assert map.getAccumulatedCredits() == 250; + } + + + public void testBlockingDecrementAndReplenishment2() { + long[] credit_sizes={500, 100, 100, 500, 300}; + Decrementer[] decrementers=new Decrementer[credit_sizes.length]; + + addAll(); + boolean rc=map.decrement(800, 100); + assert rc; + + for(int i=0; i < credit_sizes.length; i++) + decrementers[i]=new Decrementer(map, credit_sizes[i], 20000, true); + + for(Decrementer decr: decrementers) + decr.start(); + + for(int i=0; i < 10; i++) { + if(countAliveThreads(decrementers) == 3) + break; + Util.sleep(500); + } + int alive=countAliveThreads(decrementers); + assert alive == 3; + + replenishAll(400); // the 300 credit decr will succeed now + for(int i=0; i < 10; i++) { + if(countAliveThreads(decrementers) == 2) + break; + Util.sleep(500); + } + assert countAliveThreads(decrementers) == 2; + + replenishAll(700); // one of the two 500 creds will succeed + for(int i=0; i < 10; i++) { + if(countAliveThreads(decrementers) == 1) + break; + Util.sleep(500); + } + assert countAliveThreads(decrementers) == 1; + + replenishAll(300); // the other one of the 500 creds will succeed + for(int i=0; i < 10; i++) { + if(countAliveThreads(decrementers) == 0) + break; + Util.sleep(500); + } + assert countAliveThreads(decrementers) == 0; + } + + public void testClear() { + addAll(); + boolean rc=map.decrement(800, 100); + assert rc; + + Decrementer decr1=new Decrementer(map, 300, 20000, false), decr2=new Decrementer(map, 500, 20000, false); + decr1.start(); + decr2.start(); + + Util.sleep(500); + map.clear(); + + for(int i=0; i < 20; i++) { + if(!decr1.isAlive() && !decr2.isAlive()) + break; + else + Util.sleep(1000); + } + + assert !decr1.isAlive(); + assert !decr2.isAlive(); + } + + + public void testGetMembersWithInsufficientCredits() { + addAll(); + boolean rc=map.decrement(800, 50); + assert rc; + List
            list=map.getMembersWithInsufficientCredits(100); + assert list.isEmpty(); + + list=map.getMembersWithInsufficientCredits(200); + assert list.isEmpty(); + + list=map.getMembersWithInsufficientCredits(250); + assert list.size() == 4; + assert list.contains(a) && list.contains(b) && list.contains(c) && list.contains(d); + + map.remove(b); map.remove(c); + list=map.getMembersWithInsufficientCredits(250); + assert list.size() == 2; + assert list.contains(a) && list.contains(d); + + map.decrement(100, 50); + map.putIfAbsent(b); map.putIfAbsent(c); + + // Now A and D have 100 credits, B and C 1000 + list=map.getMembersWithInsufficientCredits(800); + assert list.size() == 2; + assert list.contains(a) && list.contains(d); + + list=map.getMembersWithInsufficientCredits(100); + assert list.isEmpty(); + } + + + protected int countAliveThreads(Thread[] threads) { + int alive=0; + for(Thread thread: threads) + if(thread.isAlive()) + alive++; + return alive; + } + + + protected static class Decrementer extends Thread { + private final CreditMap map; + private final long amount; + private final long timeout; + protected final boolean loop; + + public Decrementer(CreditMap map, long amount, long timeout, boolean loop) { + this.map=map; + this.amount=amount; + this.timeout=timeout; + this.loop=loop; + } + + public void run() { + while(true) { + boolean rc=map.decrement(amount, timeout); + if(rc) { + System.out.println("[" + getId() + "] decremented " + amount + " credits"); + break; + } + if(!loop) + break; + } + } + } + +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/CustomProtocolTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/CustomProtocolTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/CustomProtocolTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/CustomProtocolTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,6 +1,5 @@ package org.jgroups.tests; -import org.testng.annotations.*; import org.jgroups.JChannel; import org.jgroups.Global; import org.jgroups.stack.Protocol; @@ -9,11 +8,10 @@ /** * Tests custom protocol. * Author: Lenny Phan - * Version: $Id: CustomProtocolTest.java,v 1.4 2008/04/08 08:29:41 belaban Exp $ */ public class CustomProtocolTest { - static final String PROTOCOL_STACK = "UDP(mcast_addr=228.1.2.3;mcast_port=45566;ip_ttl=32):" + + static final String PROTOCOL_STACK = "UDP(mcast_port=45566;ip_ttl=32):" + "org.jgroups.tests.CustomProtocolTest$MyProtocol:" + "PING(timeout=3000;num_initial_members=6):" + "FD(timeout=3000):" + @@ -23,20 +21,18 @@ "pbcast.STABLE(desired_avg_gossip=10000):" + "FRAG:" + "pbcast.GMS(join_timeout=5000;" + - "shun=true;print_local_addr=true)"; + "print_local_addr=true)"; @Test(groups=Global.FUNCTIONAL) public static void testMyProtocol() throws Exception { System.out.println("PROTOCOL_STACK: " + PROTOCOL_STACK); JChannel channel = new JChannel(PROTOCOL_STACK); + System.out.println("channel = " + channel); assert true; } public static class MyProtocol extends Protocol { - public String getName() { - return "MyProtocol"; - } } } diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/DigestTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/DigestTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/DigestTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/DigestTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -4,7 +4,6 @@ import org.jgroups.Address; import org.jgroups.Global; -import org.jgroups.stack.IpAddress; import org.jgroups.util.Digest; import org.jgroups.util.MutableDigest; import org.jgroups.util.Util; @@ -19,20 +18,19 @@ /** * @author Bela Ban - * @version $Id: DigestTest.java,v 1.4 2008/04/08 12:25:28 belaban Exp $ */ @Test(groups=Global.FUNCTIONAL, sequential=true) public class DigestTest { Digest d, d2; MutableDigest md; - IpAddress a1, a2, a3; + Address a1, a2, a3; @BeforeClass void beforeClass() throws Exception { - a1=new IpAddress(5555); - a2=new IpAddress(6666); - a3=new IpAddress(7777); + a1=Util.createRandomAddress(); + a2=Util.createRandomAddress(); + a3=Util.createRandomAddress(); } @BeforeMethod @@ -61,7 +59,6 @@ public void testDifference(){ - Map map=new HashMap(); map.put(a1, new Digest.Entry(4, 500, 501)); map.put(a2, new Digest.Entry(25, 26, 26)); @@ -86,7 +83,7 @@ map3.put(a1, new Digest.Entry(4, 500, 501)); map3.put(a2, new Digest.Entry(25, 26, 26)); map3.put(a3, new Digest.Entry(20, 37, 33)); - map3.put(new IpAddress(8888), new Digest.Entry(1, 2, 3)); + map3.put(Util.createRandomAddress(), new Digest.Entry(1, 2, 3)); Digest digest3 =new Digest(map3); diff = digest3.difference(digest); @@ -141,7 +138,6 @@ } - public void testMutability() { Digest md2=md; Assert.assertEquals(md, md2); @@ -216,7 +212,7 @@ Assert.assertEquals(3, md.size()); md.add(a1, 100, 200, 201); Assert.assertEquals(3, md.size()); - md.add(new IpAddress(14526), 1,2,3); + md.add(Util.createRandomAddress(), 1,2,3); Assert.assertEquals(4, md.size()); } @@ -230,10 +226,10 @@ public void testAddDigest2() { MutableDigest tmp=new MutableDigest(4); - tmp.add(new IpAddress(1111), 1,2,3); - tmp.add(new IpAddress(2222), 1,2,3); - tmp.add(new IpAddress(5555), 1,2,3); - tmp.add(new IpAddress(6666), 1,2,3); + tmp.add(Util.createRandomAddress(), 1,2,3); + tmp.add(Util.createRandomAddress(), 1,2,3); + tmp.add(a2, 1,2,3); + tmp.add(a3, 1,2,3); md.add(tmp); Assert.assertEquals(5, md.size()); } @@ -281,13 +277,13 @@ - public void testConstructor2() { + public static void testConstructor2() { Digest dd=new Digest(3); Assert.assertEquals(0, dd.size()); } - public void testConstructor3() { + public static void testConstructor3() { Digest dd=new MutableDigest(3); Assert.assertEquals(0, dd.size()); } @@ -418,7 +414,7 @@ public void testNonConflictingMerge() { MutableDigest cons_d=new MutableDigest(5); - IpAddress ip1=new IpAddress(1111), ip2=new IpAddress(2222); + Address ip1=Util.createRandomAddress(), ip2=Util.createRandomAddress(); cons_d.add(ip1, 1, 10, 10); cons_d.add(ip2, 2, 20, 20); diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ExponentialIntervalTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ExponentialIntervalTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ExponentialIntervalTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ExponentialIntervalTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -10,7 +10,6 @@ /** * @author Bela Ban - * @version $Id: ExponentialIntervalTest.java,v 1.4 2008/04/08 08:29:39 belaban Exp $ */ public class ExponentialIntervalTest { diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/FCTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/FCTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/FCTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/FCTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,11 +1,7 @@ -// $Id: FCTest.java,v 1.4 2008/05/23 10:45:56 belaban Exp $ package org.jgroups.tests; -import org.jgroups.Event; -import org.jgroups.Message; -import org.jgroups.View; -import org.jgroups.Global; +import org.jgroups.*; import org.jgroups.debug.Simulator; import org.jgroups.protocols.FC; import org.jgroups.protocols.FRAG2; @@ -16,7 +12,6 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import java.util.Properties; import java.util.Vector; @@ -36,7 +31,7 @@ @BeforeMethod void setUp() throws Exception { IpAddress a1=new IpAddress(1111); - Vector members=new Vector(); + Vector
            members=new Vector
            (); members.add(a1); View v=new View(a1, 1, members); s=new Simulator(); @@ -48,7 +43,7 @@ fc.setMaxCredits(10000); fc.setMaxBlockTime(1000); FRAG2 frag=new FRAG2(); - frag.setFragSize(60000); + frag.setFragSize(8000); Protocol[] stack=new Protocol[]{frag, fc}; s.setProtocolStack(stack); s.start(); diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/FIFOMessageQueueTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/FIFOMessageQueueTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/FIFOMessageQueueTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/FIFOMessageQueueTest.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,634 +0,0 @@ -package org.jgroups.tests; - -import org.jgroups.Address; -import org.jgroups.Global; -import org.jgroups.stack.IpAddress; -import org.jgroups.util.FIFOMessageQueue; -import org.jgroups.util.Util; -import org.testng.Assert; -import org.testng.annotations.Test; - -import java.util.Collections; -import java.util.LinkedList; -import java.util.concurrent.BrokenBarrierException; -import java.util.concurrent.CyclicBarrier; - -/** - * @author Bela Ban - * @version $Id: FIFOMessageQueueTest.java,v 1.3 2008/04/08 12:28:08 belaban Exp $ - */ -@Test(groups=Global.FUNCTIONAL) -public class FIFOMessageQueueTest { - private static final String s1="s1", s2="s2", s3="s3"; - private static final Address a1, a2; - - static { - a1=new IpAddress(5000); - a2=new IpAddress(6000); - } - - - - public static void testPollFromEmptyQueue() throws InterruptedException { - FIFOMessageQueue queue=new FIFOMessageQueue(); - Assert.assertEquals(0, queue.size()); - Integer ret=queue.poll(5); - assert ret == null; - Assert.assertEquals(queue.size(), 0, "queue.size() should be 0, but is " + queue.size()); - } - - - - public static void testPutTwoTakeTwo() throws InterruptedException { - FIFOMessageQueue queue=new FIFOMessageQueue(); - queue.put(a1, s1, 1); // 1 is available immediately - queue.put(a1, s1, 2); // 2 is queued - Integer ret=queue.poll(5); - assert ret != null; - queue.done(a1, s1); // 2 is made available (moved into 'queue') - queue.done(a1, s1); // done() by the first putter - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(0, queue.size()); - queue.put(a1, s1, 3); - Assert.assertEquals(1, queue.size()); - ret=queue.poll(5); // 3 should be available because queue for a1/s1 was empty - assert ret != null; - } - - - - public static void testTakeFollowedByPut() throws InterruptedException { - final FIFOMessageQueue queue=new FIFOMessageQueue(); - Assert.assertEquals(0, queue.size()); - - new Thread() { - - public void run() { - Util.sleep(1000); - try { - queue.put(a1, s1, 1); - } - catch(InterruptedException e) { - - } - } - }.start(); - - Integer ret=queue.take(); - assert ret != null; - Assert.assertEquals(1, ret.intValue()); - Assert.assertEquals(queue.size(), 0, "queue.size() should be 0, but is " + queue.size()); - } - - - public static void testMultipleTakersOnePutter() throws Exception { - final FIFOMessageQueue queue=new FIFOMessageQueue(); - final CyclicBarrier barrier=new CyclicBarrier(11); - for(int i=0; i < 10; i++) { - new Thread() { - public void run() { - try { - barrier.await(); - queue.take(); - - } - catch(Exception e) { - } - } - }.start(); - } - barrier.await(); - for(int i=0; i < 10; i++) { - queue.put(a1, s1, i); - queue.done(a1, s1); - } - Util.sleep(100); - Assert.assertEquals(0, queue.size()); - } - - - public static void testConcurrentPutsAndTakes() throws InterruptedException { - final FIFOMessageQueue queue=new FIFOMessageQueue(); - final int NUM=10000; - final int print=NUM / 10; - - Thread putter=new Thread() { - - public void run() { - setName("Putter"); - int cnt=0; - for(int i=0; i < NUM; i++) { - try { - queue.put(a1, s1, i); - cnt++; - if(cnt % print == 0) { - System.out.println("Putter: " + cnt); - } - queue.done(a1, s1); - } - catch(InterruptedException e) { - e.printStackTrace(); - } - } - } - }; - - Thread taker=new Thread() { - - public void run() { - setName("Taker"); - int cnt=0; - for(int i=0; i < NUM; i++) { - try { - queue.take(); - cnt++; - if(cnt % print == 0) { - System.out.println("Taker: " + cnt); - } - } - catch(InterruptedException e) { - e.printStackTrace(); - } - } - } - }; - - System.out.println("starting threads"); - taker.start(); - putter.start(); - - new Thread() { - - public void run() { - Util.sleep(3000); - System.out.println("queue:\n" + queue); - } - }.start(); - - putter.join(); - taker.join(); - - Assert.assertEquals(0, queue.size()); - } - - - - public static void testNullAddress() throws InterruptedException { - FIFOMessageQueue queue=new FIFOMessageQueue(); - queue.put(null, s1, 1); - queue.put(a1, s1, 2); - queue.put(a1, s1, 3); - queue.put(null, s1, 4); - System.out.println("queue:\n" + queue); - - Integer ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(1, ret.intValue()); - - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(2, ret.intValue()); - - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(4, ret.intValue()); - - ret=queue.poll(5); - assert ret == null; - - queue.done(a1, s1); - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(3, ret.intValue()); - - ret=queue.poll(5); - assert ret == null; - Assert.assertEquals(0, queue.size()); - } - - - - public static void testSimplePutAndTake() throws InterruptedException { - FIFOMessageQueue queue=new FIFOMessageQueue(); - queue.put(a1, s1, 1); - Assert.assertEquals(1, queue.size()); - int ret=queue.take(); - Assert.assertEquals(1, ret); - Assert.assertEquals(0, queue.size()); - } - - - public static void testSimplePutAndTakeMultipleSenders() throws InterruptedException { - FIFOMessageQueue queue=new FIFOMessageQueue(); - queue.put(a1, s1, 1); - queue.put(a2, s1, 2); - System.out.println("queue is:\n" + queue); - Assert.assertEquals(2, queue.size()); - int ret=queue.take(); - Assert.assertEquals(1, ret); - Assert.assertEquals(1, queue.size()); - ret=queue.take(); - Assert.assertEquals(2, ret); - Assert.assertEquals(0, queue.size()); - } - - - public static void testMultiplePutsAndTakes() throws InterruptedException { - FIFOMessageQueue queue=new FIFOMessageQueue(); - for(int i=1; i <= 5; i++) - queue.put(a1, s1, i); - System.out.println("queue is " + queue); - Assert.assertEquals(5, queue.size()); - for(int i=1; i <= 5; i++) { - int ret=queue.take(); - Assert.assertEquals(i, ret); - Assert.assertEquals(5 - i, queue.size()); - queue.done(a1, s1); - } - Assert.assertEquals(0, queue.size()); - } - - - /** - * Sender A sends M1 to S1 and M2 to S1. M2 should wait until M1 is done - */ - - public static void testSameSenderSameDestination() throws InterruptedException { - FIFOMessageQueue queue=new FIFOMessageQueue(); - queue.put(a1, s1, 1); - queue.put(a1, s1, 2); - queue.put(a1, s1, 3); - System.out.println("queue:\n" + queue); - - Assert.assertEquals(3, queue.size()); - int ret=queue.take(); - - Assert.assertEquals(1, ret); - Integer retval=queue.poll(100); - assert retval == null; - queue.done(a1, s1); - System.out.println("queue:\n" + queue); - ret=queue.take(); - Assert.assertEquals(2, ret); - queue.done(a1, s1); - System.out.println("queue:\n" + queue); - ret=queue.take(); - System.out.println("queue:\n" + queue); - Assert.assertEquals(3, ret); - } - - - - /** - * Sender A sends M1 to S1 and M2 to S2. M2 should get processed immediately and not have - * to wait for M1 to complete - */ - - public static void testSameSenderMultipleDestinations() throws InterruptedException { - FIFOMessageQueue queue=new FIFOMessageQueue(); - queue.put(a1, s1, 10); - queue.put(a1, s1, 11); - queue.put(a1, s1, 12); - - queue.put(a1, s2, 20); - queue.put(a1, s2, 21); - queue.put(a1, s2, 22); - - queue.put(a1, s3, 30); - queue.put(a1, s3, 31); - queue.put(a1, s3, 32); - System.out.println("queue:\n" + queue); - Integer ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(10, ret.intValue()); - - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(20, ret.intValue()); - - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(30, ret.intValue()); - - ret=queue.poll(5); - assert ret == null; - - queue.done(a1, s3); - queue.done(a1, s1); - queue.done(a1, s2); - - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(31, ret.intValue()); - - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(11, ret.intValue()); - - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(21, ret.intValue()); - - ret=queue.poll(5); - assert ret == null; - - Assert.assertEquals(3, queue.size()); - - ret=queue.poll(5); - assert ret == null; - - queue.done(a1, s1); - queue.done(a1, s3); - queue.done(a1, s2); - - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(12, ret.intValue()); - - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(32, ret.intValue()); - - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(22, ret.intValue()); - - ret=queue.poll(5); - assert ret == null; - - Assert.assertEquals(0, queue.size()); - } - - - /** - * Sender A sends M1 to S1 and sender B sends M2 to S1. M2 should get processed concurrently to M1 and - * should not have to wait for M1's completion - */ - - public static void testDifferentSendersSameDestination() throws InterruptedException { - FIFOMessageQueue queue=new FIFOMessageQueue(); - queue.put(a1, s1, 10); - queue.put(a2, s1, 20); - queue.put(a1, s1, 11); - queue.put(a2, s1, 21); - System.out.println("queue:\n" + queue); - Assert.assertEquals(4, queue.size()); - - Integer ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(10, ret.intValue()); - - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(20, ret.intValue()); - - queue.done(a1, s1); - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(11, ret.intValue()); - - queue.done(a2, s1); - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(21, ret.intValue()); - - ret=queue.poll(5); - assert ret == null; - Assert.assertEquals(0, queue.size()); - } - - - - /** - * Sender A sends M1 to S1 and sender B sends M2 to S2. M1 and M2 should get processed concurrently - */ - - public static void testDifferentSendersDifferentDestinations() throws Exception { - FIFOMessageQueue queue=new FIFOMessageQueue(); - queue.put(a1, s1, 1); - queue.put(a2, s2, 2); - queue.put(a1, s2, 3); - queue.put(a2, s1, 4); - System.out.println("queue:\n" + queue); - Assert.assertEquals(4, queue.size()); - - Integer ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(1, ret.intValue()); - - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(2, ret.intValue()); - - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(3, ret.intValue()); - - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(4, ret.intValue()); - - ret=queue.poll(5); - assert ret == null; - Assert.assertEquals(0, queue.size()); - - } - - - - - public static void testDifferentSendersDifferentDestinationsMultipleMessages() throws Exception { - FIFOMessageQueue queue=new FIFOMessageQueue(); - queue.put(a1, s1, 1); - queue.put(a2, s2, 2); - queue.put(a1, s2, 3); - queue.put(a2, s1, 4); - - queue.put(a1, s1, 5); - queue.put(a2, s2, 6); - queue.put(a1, s2, 7); - queue.put(a2, s1, 8); - - System.out.println("queue:\n" + queue); - Assert.assertEquals(8, queue.size()); - - Integer ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(1, ret.intValue()); - - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(2, ret.intValue()); - - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(3, ret.intValue()); - - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(4, ret.intValue()); - - - queue.done(a1, s1); - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(5, ret.intValue()); - - queue.done(a2, s2); - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(6, ret.intValue()); - - queue.done(a1, s2); - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(7, ret.intValue()); - - queue.done(a2, s1); - ret=queue.poll(5); - assert ret != null; - Assert.assertEquals(8, ret.intValue()); - } - - - - - public static void testOrdering() throws InterruptedException { - FIFOMessageQueue queue=new FIFOMessageQueue(); - for(int i=1; i <= 3; i++) - queue.put(a1, s1, i); - Assert.assertEquals(3, queue.size()); - - int ret=queue.take(); - Assert.assertEquals(1, ret); - Assert.assertEquals(2, queue.size()); - - queue.done(a1, s1); - queue.put(a1, s1, 4); - queue.put(a1, s1, 5); - System.out.println("queue: " + queue); - - for(int i=2; i <= 5; i++) { - ret=queue.take(); - Assert.assertEquals(i, ret); - Assert.assertEquals(5 - i, queue.size()); - queue.done(a1, s1); - } - Assert.assertEquals(0, queue.size()); - } - - - - public static void testOrderingMultipleThreads() throws BrokenBarrierException, InterruptedException { - FIFOMessageQueue queue=new FIFOMessageQueue(); - CyclicBarrier barrier=new CyclicBarrier(4); - int NUM=500; - Producer p1=new Producer(queue, "s1", 1, NUM, barrier); - Producer p2=new Producer(queue, "s2", 1001, NUM, barrier); - Producer p3=new Producer(queue, "s3", 2001, NUM, barrier); - - p1.start(); - p2.start(); - p3.start(); - Util.sleep(100); - barrier.await(); // starts all 3 threads - - p1.join(); - p2.join(); - p3.join(); - System.out.println("queue: " + queue.size() + " elements"); - Assert.assertEquals(NUM * 3, queue.size()); - } - - - public static void testOrderingMultipleThreadsWithTakes() throws BrokenBarrierException, InterruptedException { - testOrderingMultipleThreads(); - int ret; - LinkedList list=new LinkedList(); - FIFOMessageQueue queue=new FIFOMessageQueue(); - - int size=queue.size(); - for(int i=0; i < size; i++) { - ret=queue.take(); - list.add(ret); - queue.done(a1, "s1"); - queue.done(a1, "s2"); - queue.done(a1, "s3"); - } - - System.out.println("analyzing returned values for correct ordering"); - LinkedList one=new LinkedList(), two=new LinkedList(), three=new LinkedList(); - for(int val: list) { - if(val < 1000) { - one.add(val); - continue; - } - if(val > 1000 && val <= 2000) { - two.add(val); - continue; - } - if(val > 2000) { - three.add(val); - } - } - - int len=one.size(); - Assert.assertEquals(len, two.size()); - Assert.assertEquals(len, three.size()); - - - LinkedList sorted_one=new LinkedList(one); - Collections.sort(sorted_one); - Assert.assertEquals(sorted_one, one, "one: " + one + ", sorted: " + sorted_one); - - LinkedList sorted_two=new LinkedList(two); - Collections.sort(sorted_two); - Assert.assertEquals(sorted_two, two, "two: " + two + ", sorted: " + sorted_two); - - LinkedList sorted_three=new LinkedList(three); - Collections.sort(sorted_three); - Assert.assertEquals(sorted_three, three, "three: " + three + ", sorted: " + sorted_three); - - System.out.println("OK - all 3 collections are ordered"); - } - - - - private static class Producer extends Thread { - private FIFOMessageQueue queue; - private String key; - private int num_msgs; - private CyclicBarrier barrier; - private int start_num; - - private Producer(FIFOMessageQueue queue, String key, int start_num, int num_msgs, CyclicBarrier barrier) { - this.queue=queue; - this.key=key; - this.start_num=start_num; - this.num_msgs=num_msgs; - this.barrier=barrier; - } - - - public void run() { - try { - barrier.await(); - } - catch(Exception e) { - e.printStackTrace(); - } - for(int i=start_num; i <= num_msgs+start_num-1; i++) { - try { - // Util.sleepRandom(50); - queue.put(a1, key, i); - } - catch(InterruptedException e) { - e.printStackTrace(); - } - } - } - } - - -} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/FixedSizeBitSetTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/FixedSizeBitSetTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/FixedSizeBitSetTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/FixedSizeBitSetTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,124 @@ +package org.jgroups.tests; + +import org.jgroups.Global; +import org.jgroups.util.FixedSizeBitSet; +import org.jgroups.util.Util; +import org.testng.annotations.Test; + +import java.util.Set; +import java.util.TreeSet; + +/** + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL,sequential=false) +public class FixedSizeBitSetTest { + + public static void testConstructor() { + FixedSizeBitSet set=new FixedSizeBitSet(10); + assert set.cardinality() == 0; + assert set.size() == 10; + } + + @Test(expectedExceptions=IndexOutOfBoundsException.class) + public static void testSetWithIndexOutOfBounds() { + FixedSizeBitSet set=new FixedSizeBitSet(10); + set.set(0); + set.set(10); + } + + @Test(expectedExceptions=IndexOutOfBoundsException.class) + public static void testClearWithIndexOutOfBounds() { + FixedSizeBitSet set=new FixedSizeBitSet(10); + set.clear(10); + } + + @Test(expectedExceptions=IndexOutOfBoundsException.class) + public static void testGetWithIndexOutOfBounds() { + FixedSizeBitSet set=new FixedSizeBitSet(10); + set.get(10); + } + + + public static void testToString() { + FixedSizeBitSet set=new FixedSizeBitSet(10); + System.out.println("set = " + set); + set.set(0); + set.set(9); + System.out.println("set = " + set); + } + + + public static void testNextSetBit() { + FixedSizeBitSet set=new FixedSizeBitSet(64); + int index=set.nextSetBit(10); + assert index == -1 : "expected -1 but got " + index; + + index=set.nextSetBit(63); + assert index == -1 : "expected -1 but got " + index; + + set.set(62); + index=set.nextSetBit(62); + assert index == 62 : "expected 62 but got " + index; + + index=set.nextSetBit(63); + assert index == -1 : "expected -1 but got " + index; + + set.set(63); + index=set.nextSetBit(63); + assert index == 63 : "expected 63 but got " + index; + } + + public static void testNextSetBit2() { + FixedSizeBitSet set=new FixedSizeBitSet(64); + set.set(0); + int index=set.nextSetBit(0); + assert index == 0 : "expected 0 but got " + index; + } + + + + public static void testNextClearBit() { + FixedSizeBitSet set=new FixedSizeBitSet(64); + int index=set.nextClearBit(0); + assert index == 0 : "expected 0 but got " + index; + + set.set(62); set.set(63); + index=set.nextClearBit(62); + assert index == -1; + } + + + + public static void testNextSetAndClearBitOutOfRangeIndex() { + FixedSizeBitSet set=new FixedSizeBitSet(64); + for(int num: new int[]{64, 120}) { + int index=set.nextSetBit(num); + assert index == -1; + index=set.nextClearBit(num); + assert index == -1; + } + } + + + public static void testLargeSet() { + FixedSizeBitSet set=new FixedSizeBitSet(1500); + Set sorted_set=new TreeSet(); + for(int i=0; i < 500; i++) { + int num=(int)Util.random(1499); + sorted_set.add(num); + } + + for(int num: sorted_set) + set.set(num); + + int num_set=sorted_set.size(); + System.out.println("set " + num_set + " bits"); + assert set.cardinality() == num_set; + } + +} + + + + diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/FragTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/FragTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/FragTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/FragTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: FragTest.java,v 1.5 2008/10/20 10:21:22 belaban Exp $ package org.jgroups.tests; @@ -89,11 +88,6 @@ this.t=t; } - public String getName() { - return "FragReceiver"; - } - - public Object up(Event evt) { Message msg=null; Address sender; diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/HeadersTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/HeadersTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/HeadersTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/HeadersTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -5,19 +5,17 @@ import org.jgroups.util.Headers; import org.testng.annotations.Test; -import java.io.IOException; -import java.io.ObjectInput; -import java.io.ObjectOutput; +import java.io.*; import java.util.Map; /** * Tests the functionality of the Headers class * @author Bela Ban - * @version $Id: HeadersTest.java,v 1.4 2008/07/31 12:53:51 belaban Exp $ */ @Test(groups=Global.FUNCTIONAL,sequential=false) public class HeadersTest { - private static final String UDP="UDP", FRAG="FRAG", NAKACK="NAKACK"; + private static final short UDP_ID=1, FRAG_ID=2, NAKACK_ID=3; + private static final MyHeader h1=new MyHeader(), h2=new MyHeader(), h3=new MyHeader(); @@ -26,8 +24,10 @@ Headers hdrs=new Headers(5); System.out.println("hdrs = " + hdrs); assert hdrs.capacity() == 5 : "capacity must be 5 but was " + hdrs.capacity(); - Object[] data=hdrs.getRawData(); - assert data.length == hdrs.capacity() * 2; + short[] ids=hdrs.getRawIDs(); + assert ids.length == hdrs.capacity(); + Header[] headers=hdrs.getRawHeaders(); + assert headers.length == hdrs.capacity(); assert hdrs.size() == 0; } @@ -38,42 +38,59 @@ Headers hdrs=new Headers(old); System.out.println("hdrs = " + hdrs); assert hdrs.capacity() == 3 : "capacity must be 3 but was " + hdrs.capacity(); - Object[] data=hdrs.getRawData(); - assert data.length == hdrs.capacity() * 2; + + short[] ids=hdrs.getRawIDs(); + Header[] headers=hdrs.getRawHeaders(); + + assert ids.length == hdrs.capacity(); + assert headers.length == hdrs.capacity(); + assert hdrs.size() == 3; // make sure 'hdrs' is not changed when 'old' is modified, as 'hdrs' is a copy - old.putHeader("BLA", new MyHeader()); + old.putHeader((short)300, new MyHeader()); assert hdrs.capacity() == 3 : "capacity must be 3 but was " + hdrs.capacity(); - data=hdrs.getRawData(); - assert data.length == hdrs.capacity() * 2; + + assert ids.length == hdrs.capacity(); + assert headers.length == hdrs.capacity(); + assert hdrs.size() == 3; } public static void testGetRawData() { Headers hdrs=createHeaders(3); - Object[] data=hdrs.getRawData(); - assert data.length == 6; - assert data[0].equals(NAKACK); - assert data[1].equals(h1); - assert data[2].equals(FRAG); - assert data[3].equals(h2); - assert data[4].equals(UDP); - assert data[5].equals(h3); - assert data.length == hdrs.capacity() * 2; + + short[] ids=hdrs.getRawIDs(); + Header[] headers=hdrs.getRawHeaders(); + + assert ids.length == 3; + assert headers.length == 3; + + assert ids[0] == NAKACK_ID; + assert headers[0] == h1; + + assert ids[1] == FRAG_ID; + assert headers[1] == h2; + + assert ids[2] == UDP_ID; + assert headers[2] == h3; + + assert ids.length == hdrs.capacity(); + assert headers.length == hdrs.capacity(); + assert hdrs.size() == 3; } public static void testGetHeaders() { Headers hdrs=createHeaders(3); - Map map=hdrs.getHeaders(); + Map map=hdrs.getHeaders(); System.out.println("map = " + map); assert map != null && map.size() == 3; - assert map.get(NAKACK) == h1; - assert map.get(FRAG) == h2; - assert map.get(UDP) == h3; + assert map.get(NAKACK_ID) == h1; + assert map.get(FRAG_ID) == h2; + assert map.get(UDP_ID) == h3; } public static void testSize() { @@ -84,22 +101,22 @@ private static Headers createHeaders(int initial_capacity) { Headers hdrs=new Headers(initial_capacity); - hdrs.putHeader(NAKACK, h1); - hdrs.putHeader(FRAG, h2); - hdrs.putHeader(UDP, h3); + hdrs.putHeader(NAKACK_ID, h1); + hdrs.putHeader(FRAG_ID, h2); + hdrs.putHeader(UDP_ID, h3); return hdrs; } public static void testPutHeader() { Headers hdrs=createHeaders(3); - assert hdrs.getHeader(NAKACK) == h1; - hdrs.putHeader(NAKACK, new MyHeader()); + assert hdrs.getHeader(NAKACK_ID) == h1; + hdrs.putHeader(NAKACK_ID, new MyHeader()); assert hdrs.size() == 3; - assert hdrs.getHeader(NAKACK) != h1; + assert hdrs.getHeader(NAKACK_ID) != h1; assert hdrs.capacity() == 3; - hdrs.putHeader("NEW", new MyHeader()); + hdrs.putHeader((short)400, new MyHeader()); assert hdrs.size() == 4; assert hdrs.capacity() > 3; } @@ -107,13 +124,13 @@ public static void testPutHeaderIfAbsent() { Headers hdrs=createHeaders(3); - Header hdr=hdrs.putHeaderIfAbsent(FRAG, new MyHeader()); + Header hdr=hdrs.putHeaderIfAbsent(FRAG_ID, new MyHeader()); assert hdr == h2; - assert hdr == hdrs.getHeader(FRAG); + assert hdr == hdrs.getHeader(FRAG_ID); assert hdrs.size() == 3; assert hdrs.capacity() == 3; - hdr=hdrs.putHeaderIfAbsent("NEW", new MyHeader()); + hdr=hdrs.putHeaderIfAbsent((short)400, new MyHeader()); System.out.println("hdrs = " + hdrs); assert hdr == null; assert hdrs.size() == 4; @@ -122,8 +139,8 @@ public static void testGetHeader() { Headers hdrs=createHeaders(3); - assert null == hdrs.getHeader("NOTAVAILABLE"); - assert hdrs.getHeader(UDP) == h3; + assert null == hdrs.getHeader((short)400); + assert hdrs.getHeader(UDP_ID) == h3; } @@ -132,20 +149,19 @@ int capacity=hdrs.capacity(); System.out.println("hdrs = " + hdrs + ", capacity=" + capacity); - hdrs.putHeader("NEW", new MyHeader()); + hdrs.putHeader((short)400, new MyHeader()); System.out.println("hdrs = " + hdrs + ", capacity=" + hdrs.capacity()); assert hdrs.capacity() > capacity; capacity=hdrs.capacity(); - for(int i=0; i < 3; i++) - hdrs.putHeader("#" + i, new MyHeader()); + for(int i=10; i <= 13; i++) + hdrs.putHeader((short)i, new MyHeader()); System.out.println("hdrs = " + hdrs + ", capacity=" + hdrs.capacity()); assert hdrs.capacity() > capacity; } public static class MyHeader extends Header { - private static final long serialVersionUID=-7164974484154022976L; public MyHeader() { } @@ -154,10 +170,14 @@ return "MyHeader"; } - public void writeExternal(ObjectOutput out) throws IOException { + public void writeTo(DataOutputStream out) throws IOException { + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { } - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + public int size() { + return 0; } } } diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/InetAddressChecksTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/InetAddressChecksTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/InetAddressChecksTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/InetAddressChecksTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,153 @@ +package org.jgroups.tests; + +import org.jgroups.Event; +import org.jgroups.Global ; +import org.jgroups.conf.ProtocolConfiguration; +import org.jgroups.stack.Protocol; +import org.jgroups.stack.ProtocolStack; +import org.jgroups.stack.Configurator ; +import org.jgroups.stack.Configurator.InetAddressInfo; +import org.jgroups.annotations.Property; +import org.jgroups.util.Util ; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.*; +import java.net.InetAddress ; + +/** + * Tests checks made on InetAddress and related addresses in Configurator. + * @author Richard Achmatowicz + */ +@Test(groups=Global.FUNCTIONAL,sequential=true) +public class InetAddressChecksTest { + ProtocolStack stack = null; + Protocol protocol = null ; + static final String ipCheckNoConsistentProps="org.jgroups.tests.InetAddressChecksTest$IPCHECK(" + + "inetAddress1=127.0.0.1;inetAddress2=::1;inetAddress3=192.168.0.100;i=3)" ; + static final String ipCheckConsistentProps="org.jgroups.tests.InetAddressChecksTest$IPCHECK(" + + "inetAddress1=127.0.0.1;inetAddress2=127.0.0.1;inetAddress3=192.168.0.100;i=3)" ; + + List order = new LinkedList() ; + + @BeforeMethod + void setUp() { + stack=new ProtocolStack(); + } + + /* + * Checks IP version mechanism for inconsistent version processing + */ + @Test(expectedExceptions=RuntimeException.class) + public void testIPVersionCheckingNoConsistentVersion() throws Exception { + + Vector protocol_configs = new Vector() ; + Vector protocols = new Vector() ; + + // create the layer described by IPCHECK + protocol = Configurator.createProtocol(ipCheckNoConsistentProps, stack) ; + // process the defaults + protocol_configs.add(new ProtocolConfiguration(ipCheckNoConsistentProps)) ; + protocols.add(protocol) ; + + Map> inetAddressMap = null ; + try { + inetAddressMap = Configurator.createInetAddressMap(protocol_configs, protocols) ; + Collection addrs=Configurator.getAddresses(inetAddressMap); + Configurator.determineIpVersionFromAddresses(addrs) ; + } + catch(RuntimeException e) { + System.out.println("Expected exception received: " + e.getMessage()) ; + throw e ; + } + + // get the value which should have been assigned a default + InetAddress a = ((IPCHECK)protocol).getInetAddress1() ; + System.out.println("value of inetAddress1 = " + a) ; + + InetAddress b = ((IPCHECK)protocol).getInetAddress2() ; + System.out.println("value of inetAddress2 = " + b) ; + + InetAddress c = ((IPCHECK)protocol).getInetAddress3() ; + System.out.println("value of inetAddress3 = " + c) ; + + } + + /* + * Checks IP version mechanism for consistent version processing + */ + public void testIPVersionCheckingConsistentVersion() throws Exception { + + Vector protocol_configs = new Vector() ; + Vector protocols = new Vector() ; + + // create the layer described by IPCHECK + protocol = Configurator.createProtocol(ipCheckConsistentProps, stack) ; + // process the defaults + protocol_configs.add(new ProtocolConfiguration(ipCheckConsistentProps)) ; + protocols.add(protocol) ; + + Map> inetAddressMap = null ; + + inetAddressMap = Configurator.createInetAddressMap(protocol_configs, protocols) ; + Collection addrs=Configurator.getAddresses(inetAddressMap); + Configurator.determineIpVersionFromAddresses(addrs) ; + + // get the value which should have been assigned a default + InetAddress a = ((IPCHECK)protocol).getInetAddress1() ; + System.out.println("value of inetAddress1 = " + a) ; + + InetAddress b = ((IPCHECK)protocol).getInetAddress2() ; + System.out.println("value of inetAddress2 = " + b) ; + + InetAddress c = ((IPCHECK)protocol).getInetAddress3() ; + System.out.println("value of inetAddress3 = " + c) ; + + } + /* + * Checks which IP stacks are available on the platform + */ + public static void testWhichIPStacksAvailable() throws Exception { + + boolean isIPv4 = Util.isStackAvailable(true); + boolean isIPv6 = Util.isStackAvailable(false); + + System.out.println("isIPv4 = " + isIPv4); + System.out.println("isIPv6 = " + isIPv6); + } + + + public static class IPCHECK extends Protocol { + + @Property(name="inetAddress1") + InetAddress inetAddress1 ; + + public InetAddress getInetAddress1() { + return inetAddress1 ; + } + @Property(name="inetAddress2") + InetAddress inetAddress2 ; + public InetAddress getInetAddress2() { + return inetAddress2 ; + } + @Property(name="inetAddress3") + InetAddress inetAddress3 ; + public InetAddress getInetAddress3() { + return inetAddress3 ; + } + + @Property(description="wilma") + int i = 0 ; + + // do nothing + public Object down(Event evt) { + return down_prot.down(evt); + } + // do nothing + public Object up(Event evt) { + return up_prot.up(evt); + } + } + +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/InterruptTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/InterruptTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/InterruptTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/InterruptTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -15,7 +15,6 @@ /** * Tests Thread.interrupt() against InputStream.read(), Object.wait() and Thread.sleep() * @author Bela Ban Oct 5 2001 - * @version $Id: InterruptTest.java,v 1.4 2008/04/08 12:31:10 belaban Exp $ */ @Test(groups=Global.FUNCTIONAL) public class InterruptTest { diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/IpAddressTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/IpAddressTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/IpAddressTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/IpAddressTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,17 +1,19 @@ package org.jgroups.tests; +import org.jgroups.Address; +import org.jgroups.Global; import org.jgroups.stack.IpAddress; +import org.jgroups.util.StackType; import org.jgroups.util.Util; -import org.jgroups.Global; -import org.jgroups.Address; import org.testng.Assert; -import org.testng.annotations.BeforeMethod; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import java.io.*; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -20,16 +22,29 @@ IpAddress a, b, c, d, e, f, g, h, i, j, k; - @BeforeMethod + @BeforeClass public void setUp() throws Exception { - a=new IpAddress("localhost", 5555); - b=new IpAddress("localhost", 5555); + StackType type=Util.getIpStackType(); + if(type == StackType.IPv6) { + a=new IpAddress("::1", 5555); + b=new IpAddress("::1", 5555); + d=new IpAddress("::1", 5556); + e=new IpAddress("::1", 5555); + f=new IpAddress("2001:0db8:0000:0000:0000:002e:0370:2334", 80); + g=new IpAddress("2001:0db8:0000:0000:0000:002e:0370:2334", 8080); + h=new IpAddress("ff0e::3:4:5", 5555); + } + else { + a=new IpAddress("localhost", 5555); + b=new IpAddress("localhost", 5555); + d=new IpAddress("localhost", 5556); + e=new IpAddress("127.0.0.1", 5555); + f=new IpAddress("www.ibm.com", 80); + g=new IpAddress("www.ibm.com", 8080); + h=new IpAddress("224.0.0.1", 5555); + } + c=b; - d=new IpAddress("localhost", 5556); - e=new IpAddress("127.0.0.1", 5555); - f=new IpAddress("www.ibm.com", 80); - g=new IpAddress("www.ibm.com", 8080); - h=new IpAddress("224.0.0.1", 5555); } @@ -53,7 +68,9 @@ public static void testEqualityWithDnsRoundRobin() throws UnknownHostException { IpAddress x1, x2, x3; - InetAddress addr=InetAddress.getByName("127.0.0.1"); + StackType type=Util.getIpStackType(); + String tmp=type == StackType.IPv6? "::1" : "127.0.0.1"; + InetAddress addr=InetAddress.getByName(tmp); byte[] rawAddr=addr.getAddress(); InetAddress inet1=InetAddress.getByAddress("MyHost1", rawAddr); @@ -69,9 +86,7 @@ Assert.assertEquals(x3, x1); HashSet
            s=new HashSet
            (); - s.add(x1); - s.add(x2); - s.add(x3); + Collections.addAll(s, x1, x2, x3); System.out.println("s=" + s); Assert.assertEquals(1, s.size()); @@ -180,7 +195,7 @@ public static void testIPv6WithExternalization() throws IOException, ClassNotFoundException { - InetAddress tmp=Util.getFirstNonLoopbackIPv6Address(); + InetAddress tmp=Util.getNonLoopbackAddress(); IpAddress ip=new IpAddress(tmp, 5555); ByteArrayOutputStream bos=new ByteArrayOutputStream(); @@ -203,7 +218,7 @@ public static void testIPv6WithStreamable() throws IOException, ClassNotFoundException { - InetAddress tmp=Util.getFirstNonLoopbackIPv6Address(); + InetAddress tmp=Util.getNonLoopbackAddress(); IpAddress ip=new IpAddress(tmp, 5555); ByteArrayOutputStream bos=new ByteArrayOutputStream(); @@ -305,10 +320,10 @@ DataInputStream ois; IpAddress a2, b2, x, x2, y, y2; - x=new IpAddress(5555); + x=createStackConformantAddress(5555); x.setAdditionalData(new byte[]{'b','e','l','a'}); - y=new IpAddress(1111); + y=createStackConformantAddress(1111); y.setAdditionalData(new byte[]{'b','e','l','a'}); a.setAdditionalData(null); @@ -356,7 +371,7 @@ DataInputStream dis; IpAddress x, x2; - x=new IpAddress(65535); + x=createStackConformantAddress(65535); x.writeTo(oos); buf=bos.toByteArray(); @@ -424,6 +439,13 @@ } + private static IpAddress createStackConformantAddress(int port) throws UnknownHostException { + StackType type=Util.getIpStackType(); + if(type == StackType.IPv6) + return new IpAddress("::1", port); + else + return new IpAddress("127.0.0.1", port); + } } diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/MembershipTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/MembershipTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/MembershipTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/MembershipTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,47 +1,42 @@ -// $Id: MembershipTest.java,v 1.4 2008/04/08 12:36:46 belaban Exp $ package org.jgroups.tests; import org.jgroups.Address; -import org.jgroups.Membership; import org.jgroups.Global; -import org.jgroups.stack.IpAddress; +import org.jgroups.Membership; +import org.jgroups.util.UUID; +import org.jgroups.util.Util; import org.testng.Assert; -import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import java.util.Arrays; +import java.util.List; import java.util.Vector; +/** + * Author: Bela Ban + */ @Test(groups=Global.FUNCTIONAL,sequential=true) public class MembershipTest { Membership m1, m2; - Vector v1, v2; + List
            v1, v2; Address a1, a2, a3, a4, a5; @BeforeMethod public void setUp() { - a1=new IpAddress(5555); - a2=new IpAddress(6666); - a3=new IpAddress(6666); - a4=new IpAddress(7777); - a5=new IpAddress(8888); + a1=Util.createRandomAddress(); + a2=Util.createRandomAddress(); + a3=a2; + a4=Util.createRandomAddress(); + a5=Util.createRandomAddress(); m1=new Membership(); } - @AfterMethod - public void tearDown() { - - } - - public void testConstructor() { - v1=new Vector(); - v1.addElement(a1); - v1.addElement(a2); - v1.addElement(a3); + v1=Arrays.asList(a1, a2, a3); m2=new Membership(v1); assert m2.size() == 2; assert m2.contains(a1); @@ -51,13 +46,10 @@ public void testClone() { - v1=new Vector(); - v1.addElement(a1); - v1.addElement(a2); - v1.addElement(a3); + v1=Arrays.asList(a1, a2, a3); m2=new Membership(v1); m1=(Membership)m2.clone(); - Assert.assertEquals(m1.size(), m2.size()); + assert m1.size() == m2.size(); assert m1.contains(a1); assert m1.contains(a2); assert m2.contains(a1); @@ -67,13 +59,10 @@ public void testCopy() { - v1=new Vector(); - v1.addElement(a1); - v1.addElement(a2); - v1.addElement(a3); + v1=Arrays.asList(a1, a2, a3); m2=new Membership(v1); m1=m2.copy(); - Assert.assertEquals(m1.size(), m2.size()); + assert m1.size() == m2.size(); assert m1.contains(a1); assert m1.contains(a2); assert m2.contains(a1); @@ -83,9 +72,7 @@ public void testAdd() { - m1.add(a1); - m1.add(a2); - m1.add(a3); + m1.add(a1, a2, a3); assert m1.size() == 2; assert m1.contains(a1); assert m1.contains(a2); @@ -95,10 +82,7 @@ public void testAddVector() { - v1=new Vector(); - v1.addElement(a1); - v1.addElement(a2); - v1.addElement(a3); + v1=Arrays.asList(a1, a2, a3); m1.add(v1); assert m1.size() == 2; assert m1.contains(a1); @@ -107,15 +91,9 @@ public void testAddVectorDupl() { - v1=new Vector(); - v1.addElement(a1); - v1.addElement(a2); - v1.addElement(a3); - v1.addElement(a4); - v1.addElement(a5); + v1=Arrays.asList(a1, a2, a3, a4, a5); - m1.add(a5); - m1.add(a1); + m1.add(a5, a1); m1.add(v1); assert m1.size() == 4; assert m1.contains(a1); @@ -126,11 +104,7 @@ public void testRemove() { - m1.add(a1); - m1.add(a2); - m1.add(a3); - m1.add(a4); - m1.add(a5); + m1.add(a1, a2, a3, a4, a5); m1.remove(a2); assert m1.size() == 3; } @@ -145,13 +119,8 @@ public void testSet() { - v1=new Vector(); - v1.addElement(a1); - v1.addElement(a2); - m1.add(a1); - m1.add(a2); - m1.add(a4); - m1.add(a5); + v1=Arrays.asList(a1, a2); + m1.add(a1, a2, a4, a5); m1.set(v1); assert m1.size() == 2; assert m1.contains(a1); @@ -163,9 +132,7 @@ public void testSet2() { m1=new Membership(); m2=new Membership(); - m1.add(a1); - m1.add(a2); - m1.add(a4); + m1.add(a1, a2, a4); m2.add(a5); m2.set(m1); assert m2.size() == 3; @@ -178,17 +145,9 @@ public void testMerge() { - v1=new Vector(); - v2=new Vector(); - m1.add(a1); - m1.add(a2); - m1.add(a3); - m1.add(a4); - - - v1.addElement(a5); - v2.addElement(a2); - v2.addElement(a3); + v1=Arrays.asList(a5); + v2=Arrays.asList(a2, a3); + m1.add(a1, a2, a3, a4); m1.merge(v1, v2); assert m1.size() == 3; @@ -199,13 +158,8 @@ public void testSort() { - m1.add(a3); - m1.add(a4); - m1.add(a2); - m1.add(a1); - m1.add(a5); - m1.add(a2); // dupl - System.out.println("membership: " + m1); + m1.add(a3, a4, a2, a1, a5, a2); + System.out.println("membership:\n" + printUUIDs(m1)); Assert.assertEquals(4, m1.size()); Assert.assertEquals(a3, m1.elementAt(0)); Assert.assertEquals(a4, m1.elementAt(1)); @@ -214,12 +168,31 @@ m1.sort(); System.out.println("sorted: " + m1); Assert.assertEquals(4, m1.size()); - Assert.assertEquals(a1, m1.elementAt(0)); - Assert.assertEquals(a2, m1.elementAt(1)); - Assert.assertEquals(a4, m1.elementAt(2)); - Assert.assertEquals(a5, m1.elementAt(3)); + + Address addr0=m1.elementAt(0); + Address addr1=m1.elementAt(1); + Address addr2=m1.elementAt(2); + Address addr3=m1.elementAt(3); + + System.out.println("sorted membership:\n" + printUUIDs(m1)); + assert addr0.compareTo(addr1) < 0; + assert addr1.compareTo(addr2) < 0; + assert addr2.compareTo(addr3) < 0; } + private static String printUUIDs(Membership mbrs) { + StringBuilder sb=new StringBuilder(); + boolean first=true; + for(int i=0; i < mbrs.size(); i++) { + UUID mbr=(UUID)mbrs.elementAt(i); + if(first) + first=false; + else + sb.append(", "); + sb.append(mbr.toStringLong()); + } + return sb.toString(); + } } diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/MergerTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/MergerTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/MergerTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/MergerTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,137 @@ +package org.jgroups.tests; + +import org.jgroups.Address; +import org.jgroups.Global; +import org.jgroups.View; +import org.jgroups.protocols.pbcast.Merger; +import org.jgroups.util.Util; +import org.testng.annotations.Test; + +import java.util.*; + +/** + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL,sequential=false) +public class MergerTest { + private final static Address a=Util.createRandomAddress("A"), + b=Util.createRandomAddress("B"), + c=Util.createRandomAddress("C"), + d=Util.createRandomAddress("D"), + e=Util.createRandomAddress("E"), + f=Util.createRandomAddress("F"); + + + /** + * A: AB + * B: AB + * C: CD + * D: CD + */ + public void testSimpleMerge() { + Map map=new HashMap(); + map.put(a, makeView(a, a,b)); + map.put(b, makeView(a, a,b)); + map.put(c, makeView(c, c,d)); + map.put(d, makeView(c, c,d)); + System.out.println("map:\n" + print(map)); + + assert map.size() == 4; + Merger.sanitizeViews(map); + System.out.println("map after sanitization:\n" + print(map)); + + assert map.size() == 4; + assert map.get(a).size() == 2; + assert map.get(b).size() == 2; + assert map.get(c).size() == 2; + assert map.get(d).size() == 2; + } + + + /** + * A: ABC + * B: BC + * C: BC + */ + public void testOverlappingMerge() { + Map map=new HashMap(); + map.put(a, makeView(a, a,b,c)); + map.put(b, makeView(b, b,c)); + map.put(c, makeView(b, b,c)); + System.out.println("map:\n" + print(map)); + + assert map.size() == 3; + Merger.sanitizeViews(map); + System.out.println("map after sanitization:\n" + print(map)); + + assert map.size() == 3; + assert map.get(a).size() == 1; + assert map.get(b).size() == 2; + assert map.get(c).size() == 2; + } + + + /** + * A: AB + * B: B + */ + public void testOverlappingMerge2() { + Map map=new HashMap(); + map.put(a, makeView(a, a,b)); + map.put(b, makeView(b, b)); + System.out.println("map:\n" + print(map)); + + assert map.size() == 2; + Merger.sanitizeViews(map); + System.out.println("map after sanitization:\n" + print(map)); + + assert map.size() == 2; + assert map.get(a).size() == 1; + assert map.get(b).size() == 1; + } + + + /** + * A: AB + * B: BC + * C: CD + * D: DE + */ + public void testOverlappingMerge3() { + Map map=new HashMap(); + map.put(a, makeView(a, a,b)); + map.put(b, makeView(b, b,c)); + map.put(c, makeView(c, c,d)); + map.put(d, makeView(d, d,e)); + System.out.println("map:\n" + print(map)); + + assert map.size() == 4; + Merger.sanitizeViews(map); + System.out.println("map after sanitization:\n" + print(map)); + + assert map.size() == 4; + assert map.get(a).size() == 1; + assert map.get(b).size() == 1; + assert map.get(c).size() == 1; + assert map.get(d).size() == 2; + } + + + private static Collection makeList(T ... elements) { + return new ArrayList(Arrays.asList(elements)); + } + + private static View makeView(Address coord, Address ... members) { + return new View(coord, 1, new Vector
            (Arrays.asList(members))); + } + + static String print(Map map) { + StringBuilder sb=new StringBuilder(); + for(Map.Entry entry: map.entrySet()) { + sb.append(entry.getKey()).append(": ").append(entry.getValue().getMembers()).append("\n"); + } + return sb.toString(); + } + + +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/MessageSizeTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/MessageSizeTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/MessageSizeTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/MessageSizeTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,120 @@ + +package org.jgroups.tests; + +import org.jgroups.Global; +import org.jgroups.Message; +import org.jgroups.Address; +import org.jgroups.Version; +import org.jgroups.conf.ClassConfigurator; +import org.jgroups.protocols.PingHeader; +import org.jgroups.protocols.TpHeader; +import org.jgroups.protocols.UNICAST; +import org.jgroups.protocols.UDP; +import org.jgroups.protocols.pbcast.NakAckHeader; +import org.jgroups.protocols.pbcast.NAKACK; +import org.jgroups.util.*; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.DataOutputStream; + +/** + * Tests the size of marshalled messages (multicast, unicast) + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL) +public class MessageSizeTest { + private static final byte MULTICAST=2; + + private static final short UDP_ID=100; + private static final short UNICAST_ID=101; + private static final short NAKACK_ID=102; + + + private static final int MCAST_MAX_SIZE=84; + private static final int UNICAST_MAX_SIZE=102; + + + /** + * Tests size of a multicast message. + * Current record: 84 bytes (March 2010) + * Prev: 166, 109, 103, 84 + * @throws Exception + */ + public static void testMulticast() throws Exception { + Address src=Util.createRandomAddress(); + Message msg=createMessage(null, src); + Buffer buf=marshal(msg); + System.out.println("buf = " + buf); + + int len=buf.getLength(); + System.out.println("len = " + len); + + assert len <= MCAST_MAX_SIZE; + if(len < MCAST_MAX_SIZE) { + double percentage=compute(len, MCAST_MAX_SIZE); + System.out.println("multicast message (" + len + " bytes) is " + Util.format(percentage) + + "% smaller than previous max size (" + MCAST_MAX_SIZE + " bytes)"); + } + } + + /** + * Tests size of a unicast message. + * Current record: 102 (March 2010) + * Prev: 161, 127, 121, 102 + * @throws Exception + */ + public static void testUnicast() throws Exception { + Address dest=Util.createRandomAddress(); + Address src=Util.createRandomAddress(); + Message msg=createMessage(dest, src); + Buffer buf=marshal(msg); + System.out.println("buf = " + buf); + + int len=buf.getLength(); + System.out.println("len = " + len); + + assert len <= UNICAST_MAX_SIZE; + if(len < UNICAST_MAX_SIZE) { + double percentage=compute(len, UNICAST_MAX_SIZE); + System.out.println("multicast message (" + len + " bytes) is " + Util.format(percentage) + + "% smaller than previous max size (" + UNICAST_MAX_SIZE + " bytes)"); + } + } + + + private static double compute(int new_length, int old_length) { + if(new_length >= old_length) + return 0.0; + + return 100.0* (1.0 - (new_length / (double)old_length)); + } + + private static Buffer marshal(Message msg) throws Exception { + ExposedByteArrayOutputStream out_stream=new ExposedByteArrayOutputStream((int)(msg.size() + 50)); + ExposedDataOutputStream dos=new ExposedDataOutputStream(out_stream); + Address dest=msg.getDest(); + boolean multicast=dest == null || dest.isMulticastAddress(); + writeMessage(msg, dos, multicast); + return new Buffer(out_stream.getRawBuffer(), 0, out_stream.size()); + } + + protected static void writeMessage(Message msg, DataOutputStream dos, boolean multicast) throws Exception { + byte flags=0; + dos.writeShort(Version.version); // write the version + if(multicast) + flags+=MULTICAST; + dos.writeByte(flags); + msg.writeTo(dos); + } + + + static Message createMessage(Address dest, Address src) { + Message msg=new Message(dest, src, "hello world"); + msg.putHeader(NAKACK_ID, NakAckHeader.createMessageHeader(322649)); + msg.putHeader(UNICAST_ID, UNICAST.UnicastHeader.createDataHeader(465784, (short)23323, true)); + msg.putHeader(UDP_ID, new TpHeader("DrawDemo")); + return msg; + } + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/MessageTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/MessageTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/MessageTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/MessageTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,24 +1,37 @@ package org.jgroups.tests; -import org.jgroups.Message; import org.jgroups.Global; +import org.jgroups.Header; +import org.jgroups.Message; +import org.jgroups.conf.ClassConfigurator; import org.jgroups.protocols.PingHeader; import org.jgroups.protocols.TpHeader; +import org.jgroups.protocols.UDP; +import org.jgroups.protocols.PING; import org.jgroups.protocols.pbcast.NakAckHeader; -import org.jgroups.stack.IpAddress; +import org.jgroups.protocols.pbcast.NAKACK; import org.jgroups.util.Range; +import org.jgroups.util.UUID; import org.jgroups.util.Util; import org.testng.Assert; import org.testng.annotations.Test; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Map; + /** * @author Bela Ban - * @version $Id: MessageTest.java,v 1.11 2008/08/18 10:58:56 belaban Exp $ */ @Test(groups=Global.FUNCTIONAL) public class MessageTest { + static final short UDP_ID=101; + static final short PING_ID=102; + static final short NAKACK_ID=103; + public static void testFlags() { Message m1=new Message(); @@ -37,9 +50,9 @@ Message m1=new Message(); m1.setFlag(Message.OOB); assert m1.isFlagSet(Message.OOB); - Assert.assertEquals(Message.OOB, (m1.getFlags() & Message.OOB)); - assert !(m1.isFlagSet(Message.LOW_PRIO)); - Assert.assertNotSame((m1.getFlags() & Message.LOW_PRIO), Message.LOW_PRIO); + assert Message.OOB == (m1.getFlags() & Message.OOB); + assert !(m1.isFlagSet(Message.DONT_BUNDLE)); + Assert.assertNotSame((m1.getFlags() & Message.DONT_BUNDLE), Message.DONT_BUNDLE); } public static void testFlags3() { @@ -68,30 +81,30 @@ public static void testClearFlags2() { Message msg=new Message(); msg.setFlag(Message.OOB); - msg.setFlag(Message.HIGH_PRIO); - assert msg.isFlagSet(Message.LOW_PRIO) == false; + msg.setFlag(Message.NO_FC); + assert msg.isFlagSet(Message.DONT_BUNDLE) == false; assert msg.isFlagSet(Message.OOB); - assert msg.isFlagSet(Message.HIGH_PRIO); + assert msg.isFlagSet(Message.NO_FC); msg.clearFlag(Message.OOB); assert msg.isFlagSet(Message.OOB) == false; - msg.setFlag(Message.LOW_PRIO); - assert msg.isFlagSet(Message.LOW_PRIO); - assert msg.isFlagSet(Message.HIGH_PRIO); - msg.clearFlag(Message.HIGH_PRIO); - assert msg.isFlagSet(Message.HIGH_PRIO) == false; - msg.clearFlag(Message.HIGH_PRIO); - assert msg.isFlagSet(Message.HIGH_PRIO) == false; - msg.clearFlag(Message.LOW_PRIO); + msg.setFlag(Message.DONT_BUNDLE); + assert msg.isFlagSet(Message.DONT_BUNDLE); + assert msg.isFlagSet(Message.NO_FC); + msg.clearFlag(Message.NO_FC); + assert msg.isFlagSet(Message.NO_FC) == false; + msg.clearFlag(Message.NO_FC); + assert msg.isFlagSet(Message.NO_FC) == false; + msg.clearFlag(Message.DONT_BUNDLE); msg.clearFlag(Message.OOB); assert msg.getFlags() == 0; assert msg.isFlagSet(Message.OOB) == false; - assert msg.isFlagSet(Message.LOW_PRIO) == false; - assert msg.isFlagSet(Message.HIGH_PRIO) == false; - msg.setFlag(Message.LOW_PRIO); - assert msg.isFlagSet(Message.LOW_PRIO); - msg.setFlag(Message.LOW_PRIO); - assert msg.isFlagSet(Message.LOW_PRIO); + assert msg.isFlagSet(Message.DONT_BUNDLE) == false; + assert msg.isFlagSet(Message.NO_FC) == false; + msg.setFlag(Message.DONT_BUNDLE); + assert msg.isFlagSet(Message.DONT_BUNDLE); + msg.setFlag(Message.DONT_BUNDLE); + assert msg.isFlagSet(Message.DONT_BUNDLE); } @@ -211,6 +224,23 @@ Assert.assertEquals(3, m4.getBuffer().length); } + public static void testCopyHeaders() { + Message m1=new Message(null, null, "hello"); + for(short id: new short[]{1, 2, 10, Global.BLOCKS_START_ID, Global.BLOCKS_START_ID +10}) { + m1.putHeader(id, new DummyHeader(id)); + } + System.out.println("Headers for m1: " + m1.printHeaders()); + + Message m2=m1.copy(true, Global.BLOCKS_START_ID); + System.out.println("Headers for m2: " + m2.printHeaders()); + Map hdrs=m2.getHeaders(); + assert hdrs.size() == 2; + assert hdrs.containsKey(Global.BLOCKS_START_ID); + + short tmp=Global.BLOCKS_START_ID +10; + assert hdrs.containsKey(tmp); + } + public static void testComputeFragOffsets() { Range r; @@ -301,28 +331,28 @@ public static void testSizeMessageWithDest() throws Exception { - Message msg=new Message(new IpAddress("127.0.0.1", 3333), null, null); + Message msg=new Message(UUID.randomUUID(), null, null); _testSize(msg); } public static void testSizeMessageWithSrc() throws Exception { - Message msg=new Message(null, new IpAddress("127.0.0.1", 4444), null); + Message msg=new Message(null, UUID.randomUUID(), null); _testSize(msg); } public static void testSizeMessageWithDestAndSrc() throws Exception { - Message msg=new Message(new IpAddress("127.0.0.1", 3333), new IpAddress("127.0.0.1", 4444), null); + Message msg=new Message(UUID.randomUUID(), UUID.randomUUID(), null); _testSize(msg); } public static void testSizeMessageWithDestAndSrcAndFlags() throws Exception { - Message msg=new Message(new IpAddress("127.0.0.1", 3333), new IpAddress("127.0.0.1", 4444), null); + Message msg=new Message(UUID.randomUUID(), UUID.randomUUID(), null); msg.setFlag(Message.OOB); - msg.setFlag(Message.LOW_PRIO); + msg.setFlag(Message.DONT_BUNDLE); _testSize(msg); } @@ -346,7 +376,7 @@ public static void testSizeMessageWithAdditionalData() throws Exception { - IpAddress dest=new IpAddress("127.0.0.1", 5555); + UUID dest=UUID.randomUUID(); dest.setAdditionalData("bela".getBytes()); Message msg=new Message(dest, null, null); _testSize(msg); @@ -354,18 +384,18 @@ public static void testSizeMessageWithDestAndSrcAndHeaders() throws Exception { - Message msg=new Message(new IpAddress("127.0.0.1", 3333), new IpAddress("127.0.0.1", 4444), "bela".getBytes()); + Message msg=new Message(UUID.randomUUID(), UUID.randomUUID(), "bela".getBytes()); addHeaders(msg); _testSize(msg); } private static void addHeaders(Message msg) { TpHeader tp_hdr=new TpHeader("DemoChannel2"); - msg.putHeader("TP", tp_hdr); + msg.putHeader(UDP_ID, tp_hdr); PingHeader ping_hdr=new PingHeader(PingHeader.GET_MBRS_REQ, "demo-cluster"); - msg.putHeader("PING", ping_hdr); - NakAckHeader nak_hdr=new NakAckHeader(NakAckHeader.XMIT_REQ, 100, 104); - msg.putHeader("NAKACK", nak_hdr); + msg.putHeader(PING_ID, ping_hdr); + NakAckHeader nak_hdr=NakAckHeader.createXmitRequestHeader(100, 104, null); + msg.putHeader(NAKACK_ID, nak_hdr); } @@ -377,4 +407,30 @@ } + protected static class DummyHeader extends Header { + protected final short num; + + public DummyHeader(short num) { + this.num=num; + } + + public short getNum() { + return num; + } + + public int size() { + return 0; + } + + public void writeTo(DataOutputStream out) throws IOException { + } + + public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException { + } + + public String toString() { + return "DummyHeader(" + num + ")"; + } + } + } diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/MethodCallTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/MethodCallTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/MethodCallTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/MethodCallTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -18,7 +18,6 @@ /** * @author Bela Ban belaban@yahoo.com * @author Ovidiu Feodorov - * @version $Id: MethodCallTest.java,v 1.6 2008/05/20 15:16:44 belaban Exp $ **/ @Test(groups=Global.FUNCTIONAL) public class MethodCallTest { diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/NakackTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/NakackTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/NakackTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/NakackTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,156 +1,161 @@ -//$Id: NakackTest.java,v 1.7 2008/10/23 17:25:06 rachmatowicz Exp $ package org.jgroups.tests; -import org.jgroups.Global ; - -import org.testng.Assert; -import org.testng.annotations.Test; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.AfterMethod; - import org.jgroups.*; import org.jgroups.debug.Simulator; import org.jgroups.protocols.pbcast.NAKACK; -import org.jgroups.stack.IpAddress; import org.jgroups.stack.Protocol; +import org.jgroups.util.UUID; +import org.jgroups.util.MutableDigest; +import org.jgroups.util.Digest; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import java.util.Hashtable; import java.util.Vector; +import java.util.Arrays; /** * Tests the reliable FIFO (NAKACK) protocol - * + *

            * Two sender peers send 1000 messages to the group, where each message contains - * a long value, mirroring seqnos used. A receiver peer receives the messages + * a long value, mirroring seqnos used. A receiver peer receives the messages * from each sender and checks that seqnos are received in the correct order. - * - * This test makes use of Simulator to test the protocol in + *

            + * This test makes use of Simulator to test the protocol in * isolation from a JChannel. Each peer is wrapped in a Simulator instance and * the instances are linked together to form the group. - * - * An object all_msgs_recd is used to allow the main test thread to discover when + *

            + * An object all_msgs_recd is used to allow the main test thread to discover when * all sent messages have been received. - * + *

            * The test case passes if the expected number of messages is received, and messages * are received in order from each sender. This implies that: - * (i) all messages from each peer were received (reliable) and + * (i) all messages from each peer were received (reliable) and * (ii) all messages from each peer are received in order (FIFO) - * - * @author Richard Achmatowicz + * @author Richard Achmatowicz * @author Bela Ban */ @Test(groups=Global.FUNCTIONAL, sequential=true) public class NakackTest { - final static int NUM_PEERS = 3; - final static int NUM_MSGS = 1000; - final static int WAIT_TIMEOUT = 10; // secs - final static int MSGS_PER_STATUS_LINE = 100; + final static int NUM_PEERS=3; + final static int NUM_MSGS=1000; + final static int WAIT_TIMEOUT=10; // secs + final static int MSGS_PER_STATUS_LINE=100; // convey assertion failure from thread to main framework - static boolean notFIFO = false ; - static boolean allMsgsReceived = false ; + static boolean notFIFO=false; + static boolean allMsgsReceived=false; - IpAddress[] addresses = new IpAddress[NUM_PEERS] ; - Vector

            members = null ; + Address[] addresses=new Address[NUM_PEERS]; + Vector
            members=null; View view; - Simulator[] simulators = new Simulator[NUM_PEERS] ; - NAKACK[] layers = new NAKACK[NUM_PEERS] ; - Protocol[][] stacks = new Protocol[NUM_PEERS][] ; - Thread[] threads = new Thread[NUM_PEERS] ; + Simulator[] simulators=new Simulator[NUM_PEERS]; + NAKACK[] layers=new NAKACK[NUM_PEERS]; + Protocol[][] stacks=new Protocol[NUM_PEERS][]; + Thread[] threads=new Thread[NUM_PEERS]; //define senders and receivers - boolean[] isSender = new boolean[NUM_PEERS] ; + boolean[] isSender=new boolean[NUM_PEERS]; // used to wait for signal that all messages received - static Object all_msgs_recd = new Object() ; + static final Object all_msgs_recd=new Object(); /** - * Set up a number of simulator instances wrapping NAKACK + * Set up a number of simulator instances wrapping NAKACK */ @BeforeMethod public void setUp() throws Exception { - // define the senders and the receivers - isSender[0] = false ; - isSender[1] = true ; - isSender[2] = true ; - - // dummy IP addresses and ports - addresses[0] = new IpAddress(1111); - addresses[1] = new IpAddress(2222); - addresses[2] = new IpAddress(3333); - - // dummy set of members which works for all three simulators - members = new Vector
            (); - for (int i = 0 ; i < NUM_PEERS; i++) { - members.add(addresses[i]); - } - - // create a dummy View(creator, timestamp, member set) - view = new View(addresses[0], 1, members); - - // create new simulator instances - for (int i = 0; i < NUM_PEERS; i++) { - - // create the simulator instance - - // at this stage, the ProtocolAdapter should be created and the timer present - simulators[i] = new Simulator(); - simulators[i].setLocalAddress(addresses[i]); - simulators[i].setView(view); - - // set up the protocol under test - layers[i] = new NAKACK(); - - // set up its properties - layers[i].setUseMcastXmit(true); - - // our protocol stack under test consists of one protocol - stacks[i] = new Protocol[]{layers[i]}; - - // initalise the protocol stack - simulators[i].setProtocolStack(stacks[i]); - } - - // describe the configuration of the three simulators - for (int i = 0; i < NUM_PEERS; i++) { - for (int j = 0; j < NUM_PEERS; j++) { - if (i == j) - simulators[i].addMember(addresses[j]) ; - else - simulators[i].addMember(addresses[j], simulators[j]) ; - } - } - - // set up the receiver callbacks for each simulator - Simulator.Receiver[] receivers = new Simulator.Receiver[NUM_PEERS] ; - - // set up the sender and the receiver callbacks, according to whether - // the peer is a sender or a receiver - for (int i = 0; i < NUM_PEERS; i++) { - - if (isSender[i]) - receivers[i] = new SenderPeer(simulators[i]) ; - else - receivers[i] = new ReceiverPeer(simulators[i]) ; - - simulators[i].setReceiver(receivers[i]); - } - - // start the simulators - for (int i = 0; i < NUM_PEERS; i++) - simulators[i].start(); + // define the senders and the receivers + isSender[0]=false; + isSender[1]=true; + isSender[2]=true; + + // dummy IP addresses and ports + for(int i=0; i < addresses.length; i++) { + UUID uuid=UUID.randomUUID(); + UUID.add(uuid, "node-" + i); + addresses[i]=uuid; + } + + // dummy set of members which works for all three simulators + members=new Vector
            (); + members.addAll(Arrays.asList(addresses).subList(0, NUM_PEERS)); + + // create a dummy View(creator, timestamp, member set) + view=new View(addresses[0], 1, members); + + // create new simulator instances + for(int i=0; i < NUM_PEERS; i++) { + + // create the simulator instance + + // at this stage, the ProtocolAdapter should be created and the timer present + simulators[i]=new Simulator(); + simulators[i].setLocalAddress(addresses[i]); + simulators[i].setView(view); + + // set up the protocol under test + layers[i]=new NAKACK(); + + // set up its properties + layers[i].setUseMcastXmit(true); + + // our protocol stack under test consists of one protocol + stacks[i]=new Protocol[]{layers[i]}; + + // initalise the protocol stack + simulators[i].setProtocolStack(stacks[i]); + } + + // describe the configuration of the three simulators + for(int i=0; i < NUM_PEERS; i++) { + for(int j=0; j < NUM_PEERS; j++) { + if(i == j) + simulators[i].addMember(addresses[j]); + else + simulators[i].addMember(addresses[j], simulators[j]); + } + } + + // set up the receiver callbacks for each simulator + Simulator.Receiver[] receivers=new Simulator.Receiver[NUM_PEERS]; + + // set up the sender and the receiver callbacks, according to whether + // the peer is a sender or a receiver + for(int i=0; i < NUM_PEERS; i++) { + + if(isSender[i]) + receivers[i]=new SenderPeer(simulators[i]); + else + receivers[i]=new ReceiverPeer(simulators[i]); + + simulators[i].setReceiver(receivers[i]); + } + + // start the simulators + for(int i=0; i < NUM_PEERS; i++) + simulators[i].start(); + + MutableDigest digest=new MutableDigest(NUM_PEERS); + for(Address addr: addresses) + digest.add(new Digest(addr, 0, 0)); + for(int i=0; i < NUM_PEERS; i++) { + layers[i].down(new Event(Event.SET_DIGEST, digest)); + } } @AfterMethod public void tearDown() throws Exception { - // stop the simulators - for (int i = 0; i < NUM_PEERS; i++) - simulators[i].stop(); + // stop the simulators + for(int i=0; i < NUM_PEERS; i++) + simulators[i].stop(); } /** @@ -159,200 +164,200 @@ public void testReceptionOfAllMessages() { - // start the NAKACK peers and let them exchange messages - for (int i = 0; i < NUM_PEERS; i++) { + // start the NAKACK peers and let them exchange messages + for(int i=0; i < NUM_PEERS; i++) { - threads[i] = new MyNAKACKPeer(simulators[i], isSender[i]) ; - threads[i].start() ; - } - - // wait for the receiver peer to signal that it has received messages, or timeout - synchronized(all_msgs_recd) { - try { - all_msgs_recd.wait(WAIT_TIMEOUT * 1000) ; - } - catch(InterruptedException e) { - System.out.println("main thread interrupted") ; - } - } - - // wait for the threads to terminate - try { - for (int i = 0; i < NUM_PEERS; i++) { - threads[i].join() ; - } - } - catch(InterruptedException e) { - } - - // the test fails if: - // - a seqno is received out of order (not FIFO), or - // - not all messages are received in time allotted (allMsgsReceived) - Assert.assertTrue(allMsgsReceived, "Incorrect number of messages received by the receiver thread") ; - Assert.assertFalse(notFIFO, "Sequenece numbers for a peer not in correct order") ; + threads[i]=new MyNAKACKPeer(simulators[i], isSender[i]); + threads[i].start(); + } + + // wait for the receiver peer to signal that it has received messages, or timeout + synchronized(all_msgs_recd) { + try { + all_msgs_recd.wait(WAIT_TIMEOUT * 1000); + } + catch(InterruptedException e) { + System.out.println("main thread interrupted"); + } + } + + // wait for the threads to terminate + try { + for(int i=0; i < NUM_PEERS; i++) { + threads[i].join(); + } + } + catch(InterruptedException e) { + } + + // the test fails if: + // - a seqno is received out of order (not FIFO), or + // - not all messages are received in time allotted (allMsgsReceived) + Assert.assertTrue(allMsgsReceived, "Incorrect number of messages received by the receiver thread"); + Assert.assertFalse(notFIFO, "Sequenece numbers for a peer not in correct order"); } /** * This is called by the Simulator when a message comes back up the stack. * Used by message senders to simply display messages received from other peers. */ - class SenderPeer implements Simulator.Receiver { - Simulator simulator = null ; - int num_mgs_received=0; - - SenderPeer(Simulator s) { - this.simulator = s ; - } - - // keep track of how many messages were received - public void receive(Event evt) { - if(evt.getType() == Event.MSG) { - num_mgs_received++; - if(num_mgs_received % MSGS_PER_STATUS_LINE == 0) - System.out.println("<" + simulator.getLocalAddress() + ">:" + "<== " + num_mgs_received); - } - } - - public int getNumberOfReceivedMessages() { - return num_mgs_received; - } + static class SenderPeer implements Simulator.Receiver { + Simulator simulator=null; + int num_mgs_received=0; + + SenderPeer(Simulator s) { + this.simulator=s; + } + + // keep track of how many messages were received + public void receive(Event evt) { + if(evt.getType() == Event.MSG) { + num_mgs_received++; + if(num_mgs_received % MSGS_PER_STATUS_LINE == 0) + System.out.println("<" + simulator.getLocalAddress() + ">:" + "<== " + num_mgs_received); + } + } + + public int getNumberOfReceivedMessages() { + return num_mgs_received; + } } /** * This is called by the Simulator when a message comes back up the stack. * This method should do the following: - * - receive messages from senders + * - receive messages from senders * - check that sequence numbers for each sender are in order (with no gaps) * - terminate when correct number of messages have been received */ - class ReceiverPeer implements Simulator.Receiver { - Simulator simulator = null ; - int num_mgs_received=0; - long starting_seqno = 1 ; - long last_seqno = starting_seqno; - - Hashtable senders = new Hashtable() ; - Message msg ; - Address sender ; - Long s ; - long received_seqno ; - - ReceiverPeer(Simulator s) { - this.simulator = s ; - } - - public synchronized void receive(Event evt) { - - if (evt.getType() == Event.MSG) { - - // keep track of seqno ordering of messages received - msg=(Message)evt.getArg(); - sender=msg.getSrc(); - - // get the expected next seqno for this sender - s = (Long)senders.get(sender); - if(s == null) { - s = new Long(starting_seqno); - senders.put(sender, s); - } - last_seqno = s.longValue(); - - try { - s=(Long)msg.getObject(); - received_seqno=s.longValue(); - - num_mgs_received++; - - // 1. check if sequence numbers are in sequence - if(received_seqno == last_seqno) { - // correct - update with next expected seqno - senders.put(sender, new Long(last_seqno+1)); - } - else { - // error, terminate test - notFIFO = true ; - Assert.fail("FAIL: received msg #" + received_seqno + ", expected " + last_seqno); - } - - Address address = simulator.getLocalAddress() ; - - if(received_seqno % MSGS_PER_STATUS_LINE == 0 && received_seqno > 0) - System.out.println("<" + address + ">:" + "PASS: received msg #" + received_seqno + " from " + sender); - - - // condition to terminate the test - all messages received (whether in - // correct order or not) - if(num_mgs_received >= NakackTest.NUM_MSGS * (NUM_PEERS-1)) { - - // indicate that we have received the required number of messages - // to differentiate between timeout and notifyAll cases on monitor - allMsgsReceived = true ; - - // signal that all messages have been received - this will allow the receiver - // thread to terminate normally - synchronized(all_msgs_recd) { - all_msgs_recd.notifyAll() ; - } - } - } - catch(Exception ex) { - System.out.println(ex.toString()) ; - // log.error("NakackTest.CheckNoGaps.up()", ex); - } - } - } - - public int getNumberOfReceivedMessages() { - return num_mgs_received; - } + static class ReceiverPeer implements Simulator.Receiver { + Simulator simulator=null; + int num_mgs_received=0; + long starting_seqno=1; + long last_seqno=starting_seqno; + + Hashtable senders=new Hashtable(); + Message msg; + Address sender; + Long s; + long received_seqno; + + ReceiverPeer(Simulator s) { + this.simulator=s; + } + + public synchronized void receive(Event evt) { + + if(evt.getType() == Event.MSG) { + + // keep track of seqno ordering of messages received + msg=(Message)evt.getArg(); + sender=msg.getSrc(); + + // get the expected next seqno for this sender + s=senders.get(sender); + if(s == null) { + s=new Long(starting_seqno); + senders.put(sender, s); + } + last_seqno=s.longValue(); + + try { + s=(Long)msg.getObject(); + received_seqno=s.longValue(); + + num_mgs_received++; + + // 1. check if sequence numbers are in sequence + if(received_seqno == last_seqno) { + // correct - update with next expected seqno + senders.put(sender, new Long(last_seqno + 1)); + } + else { + // error, terminate test + notFIFO=true; + Assert.fail("FAIL: received msg #" + received_seqno + ", expected " + last_seqno); + } + + Address address=simulator.getLocalAddress(); + + if(received_seqno % MSGS_PER_STATUS_LINE == 0 && received_seqno > 0) + System.out.println("<" + address + ">:" + "PASS: received msg #" + received_seqno + " from " + sender); + + + // condition to terminate the test - all messages received (whether in + // correct order or not) + if(num_mgs_received >= NakackTest.NUM_MSGS * (NUM_PEERS - 1)) { + + // indicate that we have received the required number of messages + // to differentiate between timeout and notifyAll cases on monitor + allMsgsReceived=true; + + // signal that all messages have been received - this will allow the receiver + // thread to terminate normally + synchronized(all_msgs_recd) { + all_msgs_recd.notifyAll(); + } + } + } + catch(Exception ex) { + System.out.println(ex.toString()); + // log.error("NakackTest.CheckNoGaps.up()", ex); + } + } + } + + public int getNumberOfReceivedMessages() { + return num_mgs_received; + } } static class MyNAKACKPeer extends Thread { - Simulator s = null ; - boolean sender = false ; + Simulator s=null; + boolean sender=false; - public MyNAKACKPeer(Simulator s, boolean sender) { - this.s = s ; - this.sender = sender ; - } - - public void run() { - - // senders send NUM_MSGS messages to all peers, beginning with seqno 1 - if (sender) { - - Address address = s.getLocalAddress() ; - - // send a collection of dummy messages by mcast to the stack under test - for(int i=1; i <= NUM_MSGS; i++) { - - Message msg=new Message(null, address, new Long(i)); - Event evt=new Event(Event.MSG, msg); - - // call Simulator.send() to introduce the event into the stack under test - s.send(evt); - - // status indicator - if(i % MSGS_PER_STATUS_LINE == 0) - System.out.println("<" + address + ">:" + " ==> " + i); - } - } - - if (!sender) { - // wait for the receiver callback to signal that it has received messages, or timeout - // this just causes this thread to block until its receiver has finished - synchronized(all_msgs_recd) { - try { - all_msgs_recd.wait(WAIT_TIMEOUT * 1000) ; - } - catch(InterruptedException e) { - System.out.println("main thread interrupted") ; - } - } - } - } + public MyNAKACKPeer(Simulator s, boolean sender) { + this.s=s; + this.sender=sender; + } + + public void run() { + + // senders send NUM_MSGS messages to all peers, beginning with seqno 1 + if(sender) { + + Address address=s.getLocalAddress(); + + // send a collection of dummy messages by mcast to the stack under test + for(int i=1; i <= NUM_MSGS; i++) { + + Message msg=new Message(null, address, new Long(i)); + Event evt=new Event(Event.MSG, msg); + + // call Simulator.send() to introduce the event into the stack under test + s.send(evt); + + // status indicator + if(i % MSGS_PER_STATUS_LINE == 0) + System.out.println("<" + address + ">:" + " ==> " + i); + } + } + + if(!sender) { + // wait for the receiver callback to signal that it has received messages, or timeout + // this just causes this thread to block until its receiver has finished + synchronized(all_msgs_recd) { + try { + all_msgs_recd.wait(WAIT_TIMEOUT * 1000); + } + catch(InterruptedException e) { + System.out.println("main thread interrupted"); + } + } + } + } } } diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/NakReceiverWindowTest2.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/NakReceiverWindowTest2.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/NakReceiverWindowTest2.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/NakReceiverWindowTest2.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,251 @@ +package org.jgroups.tests; + +import org.jgroups.Address; +import org.jgroups.Global; +import org.jgroups.Message; +import org.jgroups.protocols.pbcast.NakAckHeader; +import org.jgroups.stack.NakReceiverWindow; +import org.jgroups.stack.Retransmitter; +import org.jgroups.util.DefaultTimeScheduler; +import org.jgroups.util.Util; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Stresses the NakReceiverWindow in isolation(https://jira.jboss.org/jira/browse/JGRP-1103) + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL, sequential=true) +public class NakReceiverWindowTest2 { + final static int NUM_THREADS=200; + final static int NUM_MSGS=5000; + + static final short NAKACK_ID=100; + + final Address self=Util.createRandomAddress(); + final Address sender=Util.createRandomAddress(); + final CyclicBarrier barrier=new CyclicBarrier(NUM_THREADS +1); + + NakReceiverWindow win; + + @BeforeMethod + void init() { + win=new NakReceiverWindow(self, new Retransmitter.RetransmitCommand() { + public void retransmit(long first_seqno, long last_seqno, Address sender) { + } + }, 0, 0, new DefaultTimeScheduler(2)); + } + + @AfterMethod + void cleanup() { + win.destroy(); + } + + + /** + * Has NUM_THREAD threads insert NUM_MSGS messages concurrently, checks whether messages are added only once + * @throws BrokenBarrierException + * @throws InterruptedException + */ + @Test(invocationCount=10) + public void testConcurrentInsertions() throws BrokenBarrierException, InterruptedException { + Sender[] senders=new Sender[NUM_THREADS]; + ConcurrentMap successful_adds=new ConcurrentHashMap(); + for(int i=1; i <= NUM_MSGS; i++) + successful_adds.put((long)i, new AtomicInteger(0)); + + for(int i=0; i < senders.length; i++) { + senders[i]=new Sender(NUM_MSGS, win, sender, barrier, successful_adds); + senders[i].start(); + } + + Util.sleep(2000); + System.out.println("Concurrently inserting " + NUM_MSGS + " messages with " + NUM_THREADS + " threads"); + barrier.await(); + + for(int i=0; i < senders.length; i++) + senders[i].join(20000); + System.out.println("OK: " + NUM_MSGS + " were added to the NakReceiverWindow concurrently by " + NUM_THREADS + " threads"); + + Set keys=successful_adds.keySet(); + + System.out.println("checking for missing or duplicate seqnos in " + keys.size() + " seqnos:"); + for(int i=1; i <= NUM_MSGS; i++) { + AtomicInteger val=successful_adds.get((long)i); + if(val.get() != 1) + System.err.println(i + " was not added exactly once (successful insertions=" + val.get() + ")"); + } + for(int i=1; i <= NUM_MSGS; i++) { + AtomicInteger val=successful_adds.get((long)i); + assert val != null : i + " is missing in " + successful_adds.keySet(); + assert val.get() == 1 : i + " was not added exactly once (successful insertions=" + val.get() + ")"; + } + + System.out.println("OK: " + keys.size() + " seqnos were added exactly once"); + } + + + @Test(invocationCount=5) + public void testConcurrentRandomInsertions() throws BrokenBarrierException, InterruptedException { + Sender[] senders=new RandomSender[NUM_THREADS]; + ConcurrentMap successful_adds=new ConcurrentHashMap(); + for(int i=1; i <= NUM_MSGS; i++) + successful_adds.put((long)i, new AtomicInteger(0)); + + for(int i=0; i < senders.length; i++) { + senders[i]=new RandomSender(NUM_MSGS, win, sender, barrier, successful_adds); + senders[i].start(); + } + + Util.sleep(2000); + System.out.println("Concurrently inserting " + NUM_MSGS + " messages with " + NUM_THREADS + " threads"); + barrier.await(); + + for(int i=0; i < senders.length; i++) + senders[i].join(20000); + System.out.println("OK: " + NUM_MSGS + " were added to the NakReceiverWindow concurrently by " + NUM_THREADS + " threads"); + + Set keys=successful_adds.keySet(); + + System.out.println("checking for missing or duplicate seqnos in " + keys.size() + " seqnos:"); + for(int i=1; i <= NUM_MSGS; i++) { + AtomicInteger val=successful_adds.get((long)i); + if(val.get() != 1) + System.err.println(i + " was not added exactly once (successful insertions=" + val.get() + ")"); + } + for(int i=1; i <= NUM_MSGS; i++) { + AtomicInteger val=successful_adds.get((long)i); + assert val != null : i + " is missing in " + successful_adds.keySet(); + assert val.get() == 1 : i + " was not added exactly once (successful insertions=" + val.get() + ")"; + } + + System.out.println("OK: " + keys.size() + " seqnos were added exactly once"); + } + + + @Test(invocationCount=5) + public void testConcurrentInsertionOfSameSeqno() throws BrokenBarrierException, InterruptedException { + Sender[] senders=new SameSeqnoSender[NUM_THREADS]; + ConcurrentMap successful_adds=new ConcurrentHashMap(); + for(int i=1; i <= NUM_MSGS; i++) + successful_adds.put((long)i, new AtomicInteger(0)); + + for(int i=0; i < senders.length; i++) { + senders[i]=new SameSeqnoSender(NUM_MSGS, win, sender, barrier, successful_adds); + senders[i].start(); + } + + Util.sleep(2000); + System.out.println("Concurrently inserting 1 message with " + NUM_THREADS + " threads"); + barrier.await(); + + for(int i=0; i < senders.length; i++) + senders[i].join(20000); + System.out.println("OK: 1 message was added to the NakReceiverWindow concurrently by " + NUM_THREADS + " threads"); + + Set keys=successful_adds.keySet(); + + System.out.println("checking for missing or duplicate seqnos in " + keys.size() + " seqnos:"); + AtomicInteger val=successful_adds.get(1L); + if(val.get() != 1) + System.err.println("1 was not added exactly once (successful insertions=" + val.get() + ")"); + assert val.get() == 1 : "1 was not added exactly once (successful insertions=" + val.get() + ")"; + + System.out.println("OK: 1 seqno was added exactly once"); + } + + + + static class Sender extends Thread { + final int num; + final NakReceiverWindow win; + final Address sender; + final CyclicBarrier barrier; + final ConcurrentMap map; + + public Sender(int num, NakReceiverWindow win, Address sender, CyclicBarrier barrier, ConcurrentMap map) { + this.num=num; + this.win=win; + this.sender=sender; + this.barrier=barrier; + this.map=map; + } + + public void run() { + waitForBarrier(); + + for(int i=1; i <= num; i++) + add(i); + } + + protected void add(long seqno) { + NakAckHeader hdr=NakAckHeader.createMessageHeader(seqno); + Message msg=new Message(null, sender, "hello"); + msg.putHeader(NAKACK_ID, hdr); + boolean added=win.add(seqno, msg); + + if(added) { + AtomicInteger val=map.get((long)seqno); + val.incrementAndGet(); + } + } + + protected void waitForBarrier() { + try { + barrier.await(); + } + catch(Exception e) { + e.printStackTrace(); + } + } + } + + static class RandomSender extends Sender { + + public RandomSender(int num, NakReceiverWindow win, Address sender, CyclicBarrier barrier, ConcurrentMap map) { + super(num, win, sender, barrier, map); + } + + public void run() { + final List seqnos; + seqnos=new ArrayList(num); + for(long i=1; i <= num; i++) + seqnos.add(i); + + // now randomize the seqnos: + Collections.shuffle(seqnos); + + waitForBarrier(); + for(long seqno: seqnos) + add(seqno); + } + } + + /** + * Inserts seqno 1 NUM_MSGS times + */ + static class SameSeqnoSender extends Sender { + + public SameSeqnoSender(int num, NakReceiverWindow win, Address sender, CyclicBarrier barrier, ConcurrentMap map) { + super(num, win, sender, barrier, map); + } + + public void run() { + waitForBarrier(); + for(int i=1; i <= num; i++) + add(1L); + } + } + +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/NakReceiverWindowTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/NakReceiverWindowTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/NakReceiverWindowTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/NakReceiverWindowTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: NakReceiverWindowTest.java,v 1.9 2008/09/23 14:50:29 belaban Exp $ package org.jgroups.tests; @@ -6,14 +5,12 @@ import org.jgroups.Address; import org.jgroups.Global; import org.jgroups.Message; -import org.jgroups.stack.IpAddress; import org.jgroups.stack.NakReceiverWindow; import org.jgroups.stack.Retransmitter; import org.jgroups.util.TimeScheduler; +import org.jgroups.util.Util; import org.testng.Assert; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.List; @@ -21,585 +18,739 @@ @Test(groups=Global.FUNCTIONAL) public class NakReceiverWindowTest { - private Address sender; - private MyRetransmitCommand cmd=new MyRetransmitCommand(); - private TimeScheduler timer; - - @BeforeMethod - void initTimer() { - timer=new TimeScheduler(); - } - - @AfterMethod - void destroyTimer() throws InterruptedException { - timer.stop(); - } - - - @BeforeClass - protected void setUp() throws Exception { - sender=new IpAddress("127.0.0.1", 5555); - } - - - public void test1() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 1, timer); - check(win, 0, 1, 1); - assert win.get(23) == null; - } - - - public void test2() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, timer); - check(win, 0, 100, 100); - } - - - public void test3() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - win.add(1, new Message()); - assert win.get(1) != null; - check(win, 0, 1, 0); - win.add(2, new Message()); - check(win, 0, 2, 0); - assert win.get(2) != null; - win.remove(); - check(win, 0, 2, 1); - win.remove(); - check(win, 0, 2, 2); - } - - - public void test4() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 1, timer); - win.add(2, new Message()); - check(win, 0, 2, 1); - } - - - public void test5() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, timer); - win.add(101, new Message()); - win.add(100, new Message()); - check(win, 0, 101, 100); - } - - - public void test6() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, timer); - win.add(101, new Message()); - System.out.println("win: " + win); - win.add(100, new Message()); - System.out.println("win: " + win); - check(win, 0, 101, 100); - win.remove(); - System.out.println("win: " + win); - check(win, 0, 101, 101); - while((win.remove()) != null); - check(win, 0, 101, 101); - } - - - - public void testLowerBounds() { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, 50, timer); - win.add(101, new Message()); - System.out.println("win: " + win); - win.add(100, new Message()); - System.out.println("win: " + win); - check(win, 50, 101, 100); - win.remove(); - System.out.println("win: " + win); - check(win, 50, 101, 101); - while((win.remove()) != null); - check(win, 50, 101, 101); - } - - - public void test7() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - win.add(1, new Message()); - win.add(2, new Message()); - win.add(3, new Message()); - win.add(4, new Message()); - check(win, 0, 4, 0); - System.out.println("Note that the subsequent warning is expected:"); - win.stable(4); // no-op because we haven't even removed 4 messages - check(win, 0, 4, 0); - while(win.remove() != null); - check(win, 0, 4, 4); - win.stable(4); - check(win, 4, 4, 4); - } - - - - public void testLowerBounds2() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, 50, timer); - win.add(100, new Message()); - win.add(101, new Message()); - win.add(102, new Message()); - win.add(103, new Message()); - System.out.println("win: " + win); - check(win, 50, 103, 100); - System.out.println("Note that the subsequent warning is expected:"); - win.stable(103); // no-op because we haven't even removed 4 messages - check(win, 50, 103, 100); - while(win.remove() != null); - check(win, 50, 103, 103); - win.stable(103); - System.out.println("win: " + win); - check(win, 103, 103, 103); - } - - - public void test8() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - win.add(1, new Message()); - win.add(2, new Message()); - win.add(3, new Message()); - win.add(4, new Message()); - win.add(6, new Message()); - check(win, 0, 6, 0); // haven't delivered a message yet - while(win.remove() != null); - check(win, 0, 6, 4); - win.add(5, new Message()); - check(win, 0, 6, 4); - win.remove(); - check(win, 0, 6, 5); - win.remove(); - check(win, 0, 6, 6); - win.stable(4); - check(win, 4, 6, 6); - win.stable(6); - check(win, 6, 6, 6); - } - - - - public void testAdd() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - check(win, 0, 0, 0); - win.add(0, new Message()); // discarded, next expected is 1 - check(win, 0, 0, 0); - win.add(1, new Message()); - check(win, 0, 1, 0); - win.add(2, new Message()); - win.add(3, new Message()); - win.add(4, new Message()); - check(win, 0, 4, 0); - win.add(6, new Message()); - check(win, 0, 6, 0); - win.add(5, new Message()); - check(win, 0, 6, 0); - while(win.remove() != null) ; - check(win, 0, 6, 6); - win.stable(4); - check(win, 4, 6, 6); - win.stable(6); - check(win, 6, 6, 6); - } - - - - public void test9() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - win.add(1, new Message()); - win.add(2, new Message()); - win.add(3, new Message()); - win.add(4, new Message()); - win.add(6, new Message()); - System.out.println("win: " + win); - while((win.remove()) != null) ; - win.stable(6); // 6 is ignore as it is >= highest delivered message - System.out.println("win: " + win); - assert win.get(2) != null; - check(win, 0, 6, 4); - win.add(5, new Message()); - check(win, 0, 6, 4); - while((win.remove()) != null) ; - check(win, 0, 6, 6); - win.stable(6); - check(win, 6, 6, 6); - } - - - - public void testHighestDelivered() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - win.add(1, new Message()); - win.add(2, new Message()); - win.add(3, new Message()); - win.add(4, new Message()); - check(win, 0, 4, 0); - win.add(10, new Message()); - check(win, 0, 10, 0); - System.out.println("win: " + win); - win.add(9, new Message()); - win.add(7, new Message()); - win.add(8, new Message()); - win.add(6, new Message()); - win.add(5, new Message()); - System.out.println("win: " + win); - check(win, 0, 10, 0); - while((win.remove()) != null) ; - check(win, 0, 10, 10); - win.stable(5); - System.out.println("win: " + win); - check(win, 5, 10, 10); - win.stable(10); - System.out.println("win: " + win); - check(win, 10, 10, 10); - } - - - - public void testMissingMessages() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - win.add(1, new Message()); - win.add(5, new Message()); - check(win, 0, 5, 0); - win.add(6, new Message()); - check(win, 0, 6, 0); - System.out.println("win: " + win); - } - - - - public void testMissingMessages2() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - win.add(1, new Message()); - win.add(5, new Message()); - check(win, 0, 5, 0); - win.add(8, new Message()); - check(win, 0, 8, 0); - win.add(9, new Message()); - check(win, 0, 9, 0); - System.out.println("win: " + win); - } - - - - public void testMissingMessages3() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - win.add(1, new Message()); - win.add(5, new Message()); - check(win, 0, 5, 0); - win.add(8, new Message()); - check(win, 0, 8, 0); - win.add(9, new Message()); - check(win, 0, 9, 0); - System.out.println("win: " + win); - win.add(2, new Message()); - check(win, 0, 9, 0); - win.add(3, new Message()); - win.add(4, new Message()); - check(win, 0, 9, 0); - win.add(7, new Message()); - check(win, 0, 9, 0); - win.add(6, new Message()); - check(win, 0, 9, 0); - win.add(10, new Message()); - check(win, 0, 10, 0); - win.add(11, new Message()); - check(win, 0, 11, 0); - System.out.println("win: " + win); - } - - - - public void testMissingMessages4() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, timer); - win.add(101, new Message()); - win.add(105, new Message()); - check(win, 0, 105, 100); - win.add(108, new Message()); - check(win, 0, 108, 100); - win.add(109, new Message()); - check(win, 0, 109, 100); - System.out.println("win: " + win); - win.add(102, new Message()); - check(win, 0, 109, 100); - win.add(103, new Message()); - win.add(104, new Message()); - check(win, 0, 109, 100); - win.add(107, new Message()); - check(win, 0, 109, 100); - win.add(106, new Message()); - check(win, 0, 109, 100); - win.add(110, new Message()); - check(win, 0, 110, 100); - win.add(110, new Message()); - check(win, 0, 110, 100); - System.out.println("win: " + win); - } - - - - public void testMissingMessages5() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, timer); - win.add(101, new Message()); - check(win, 0, 101, 100); - win.add(108, new Message()); - check(win, 0, 108, 100); - win.remove(); - win.add(109, new Message()); - check(win, 0, 109, 101); - System.out.println("win: " + win); - win.add(102, new Message()); - check(win, 0, 109, 101); - win.add(103, new Message()); - win.add(104, new Message()); - check(win, 0, 109, 101); - win.add(107, new Message()); - check(win, 0, 109, 101); - win.add(106, new Message()); - win.add(105, new Message()); - check(win, 0, 109, 101); - win.add(110, new Message()); - check(win, 0, 110, 101); - win.add(110, new Message()); - check(win, 0, 110, 101); - System.out.println("win: " + win); - } - - - public void test10() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - win.add(1, new Message()); - win.add(2, new Message()); - win.add(3, new Message()); - win.add(4, new Message()); - while((win.remove()) != null) ; - check(win, 0, 4, 4); - } - - - public void test10a() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - win.add(1, new Message()); - win.add(2, new Message()); - win.add(3, new Message()); - win.add(4, new Message()); - while((win.remove()) != null) ; - win.stable(4); - check(win, 4, 4, 4); - - } - - - public void test11() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - win.add(1, new Message()); - win.add(2, new Message()); - win.add(3, new Message()); - win.add(4, new Message()); - while((win.remove()) != null) ; - win.reset(); - check(win, 0, 0, 0); - } - - - - public void test12() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - - win.add(1, new Message(null, null, new Integer(1))); - win.add(2, new Message(null, null, new Integer(2))); - win.add(3, new Message(null, null, new Integer(3))); - - Assert.assertEquals(1, ((Integer)win.remove().getObject()).intValue()); - Assert.assertEquals(2, ((Integer)win.remove().getObject()).intValue()); - Assert.assertEquals(3, ((Integer)win.remove().getObject()).intValue()); - } - - - - public void test13() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - win.add(1, new Message()); - win.add(2, new Message()); - win.add(3, new Message()); - win.add(4, new Message()); - check(win, 0, 4, 0); - win.remove(); - win.remove(); - win.add(5, new Message()); - win.add(6, new Message()); - check(win, 0, 6, 2); - win.stable(2); - check(win, 2, 6, 2); - } - - - - - public void testAddOOBAtHead() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - boolean rc; - rc=win.add(0, oob()); - assert !(rc); - rc=win.add(1, oob()); - assert rc; - rc=win.add(1, oob()); - assert !(rc); - } - - - - public void testAddOOBAtTail() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - boolean rc; - rc=win.add(1, oob()); - assert rc; - rc=win.add(2, oob()); - assert rc; - rc=win.add(2, oob()); - assert !(rc); - } - - - - public void testAddOOBInTheMiddle() throws Exception { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - boolean rc; - rc=win.add(3, oob()); - assert rc; - rc=win.add(3, oob()); - assert !(rc); - rc=win.add(1, oob()); - assert rc; - rc=win.add(1, oob()); - assert !(rc); - rc=win.add(2, oob()); - assert rc; - rc=win.add(2, oob()); - assert !(rc); - } - - - public void testUpdateHighestSeen() { - add(1000); - add(2000); - add(3000); - add(4000); - add(5000); - add(10000); - add(15000); - add(20000); - add(30000); - } - - - public void test1000() { - add(1000); - } - - - public void test10000() { - add(10000); - } - - - public void testHasMessagesToRemove() { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - assert !win.hasMessagesToRemove(); - win.add(2, new Message()); - assert !win.hasMessagesToRemove(); - win.add(1, oob()); - assert win.hasMessagesToRemove(); - win.remove(); - - assert win.hasMessagesToRemove(); - win.remove(); - assert ! win.hasMessagesToRemove(); - } - - public void testRemoveOOBMessage() { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - System.out.println("win = " + win); - win.add(2, msg()); - System.out.println("win = " + win); - assert win.removeOOBMessage() == null; - assert win.remove() == null; - win.add(1, oob()); - System.out.println("win = " + win); - assert win.removeOOBMessage() != null; - System.out.println("win = " + win); - assert win.removeOOBMessage() == null; - assert win.remove() != null; - assert win.remove() == null; - assert win.removeOOBMessage() == null; - } - - public void testRemoveRegularAndOOBMessages() { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - win.add(1, msg()); - System.out.println("win = " + win); - win.remove(); - System.out.println("win = " + win); - assert win.getHighestDelivered() == 1; - - win.add(3, msg()); - win.remove(); - System.out.println("win = " + win); - assert win.getHighestDelivered() == 1; - - win.add(2, oob()); - win.removeOOBMessage(); - System.out.println("win = " + win); - - assert win.getHighestDelivered() == 2; - } - - public void testRemoveMany() { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - win.add(1, msg()); - win.add(2, msg()); - win.add(3, msg()); - win.add(5, msg()); - win.add(6, msg()); - System.out.println("win = " + win); - List msgs=win.removeMany(); - System.out.println("msgs = " + msgs); - assert msgs.size() == 3; - - win.add(4, msg()); - msgs=win.removeMany(); - System.out.println("msgs = " + msgs); - assert msgs.size() == 3; - } - - - public void testRetransmitter() { - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); - win.add(1, msg()); - win.add(2, msg()); - win.add(3, msg()); - win.add(5, msg()); - win.add(6, msg()); - System.out.println("win = " + win); - int num_pending_xmits=win.getPendingXmits(); - assert num_pending_xmits == 1; - - win.add(4, msg()); - num_pending_xmits=win.getPendingXmits(); - assert num_pending_xmits == 0; - } - - - - private void add(int num_msgs) { - long start, stop; - double time_per_msg; - NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 1, timer); - start=System.currentTimeMillis(); - for(int i=1; i < 1 + num_msgs; i++) { - win.add(i, new Message()); - } - stop=System.currentTimeMillis(); - time_per_msg=(stop-start) / (double)num_msgs; - System.out.println("-- time for " + num_msgs + " msgs: " + (stop-start) + ", " + time_per_msg + " ms/msg"); + private static final Address sender=Util.createRandomAddress(); + private static final MyRetransmitCommand cmd=new MyRetransmitCommand(); + + + @DataProvider(name="createTimer") + Object[][] createTimer() { + return Util.createTimer(); + } + + + @Test(dataProvider="createTimer") + public void test1(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 1, timer); + check(win, 0, 1, 1); + assert win.get(23) == null; + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void test2(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, timer); + check(win, 0, 100, 100); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void test3(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + win.add(1, new Message()); + assert win.get(1) != null; + check(win, 0, 1, 0); + win.add(2, new Message()); + check(win, 0, 2, 0); + assert win.get(2) != null; + win.remove(); + check(win, 0, 2, 1); + win.remove(); + check(win, 0, 2, 2); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void test4(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 1, timer); + win.add(2, new Message()); + check(win, 0, 2, 1); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void test5(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, timer); + win.add(101, new Message()); + win.add(100, new Message()); + check(win, 0, 101, 100); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void test6(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, timer); + win.add(101, new Message()); + System.out.println("win: " + win); + win.add(100, new Message()); + System.out.println("win: " + win); + check(win, 0, 101, 100); + win.remove(); + System.out.println("win: " + win); + check(win, 0, 101, 101); + while((win.remove()) != null); + check(win, 0, 101, 101); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void testLowerBounds(TimeScheduler timer) { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, 50, timer); + win.add(101, new Message()); + System.out.println("win: " + win); + win.add(100, new Message()); + System.out.println("win: " + win); + check(win, 50, 101, 100); + win.remove(); + System.out.println("win: " + win); + check(win, 50, 101, 101); + while((win.remove()) != null); + check(win, 50, 101, 101); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void test7(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + win.add(1, new Message()); + win.add(2, new Message()); + win.add(3, new Message()); + win.add(4, new Message()); + check(win, 0, 4, 0); + System.out.println("Note that the subsequent warning is expected:"); + win.stable(4); // no-op because we haven't even removed 4 messages + check(win, 0, 4, 0); + while(win.remove() != null); + check(win, 0, 4, 4); + win.stable(4); + check(win, 4, 4, 4); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void testLowerBounds2(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, 50, timer); + win.add(100, new Message()); + win.add(101, new Message()); + win.add(102, new Message()); + win.add(103, new Message()); + System.out.println("win: " + win); + check(win, 50, 103, 100); + System.out.println("Note that the subsequent warning is expected:"); + win.stable(103); // no-op because we haven't even removed 4 messages + check(win, 50, 103, 100); + while(win.remove() != null); + check(win, 50, 103, 103); + win.stable(103); + System.out.println("win: " + win); + check(win, 103, 103, 103); + } + finally { + timer.stop(); + } + } + + + + @Test(dataProvider="createTimer") + public void test8(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + win.add(1, new Message()); + win.add(2, new Message()); + win.add(3, new Message()); + win.add(4, new Message()); + win.add(6, new Message()); + check(win, 0, 6, 0); // haven't delivered a message yet + while(win.remove() != null); + check(win, 0, 6, 4); + win.add(5, new Message()); + check(win, 0, 6, 4); + win.remove(); + check(win, 0, 6, 5); + win.remove(); + check(win, 0, 6, 6); + win.stable(4); + check(win, 4, 6, 6); + win.stable(6); + check(win, 6, 6, 6); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void testAdd(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + check(win, 0, 0, 0); + win.add(0, new Message()); // discarded, next expected is 1 + check(win, 0, 0, 0); + win.add(1, new Message()); + check(win, 0, 1, 0); + win.add(2, new Message()); + win.add(3, new Message()); + win.add(4, new Message()); + check(win, 0, 4, 0); + win.add(6, new Message()); + check(win, 0, 6, 0); + win.add(5, new Message()); + check(win, 0, 6, 0); + while(win.remove() != null) ; + check(win, 0, 6, 6); + win.stable(4); + check(win, 4, 6, 6); + win.stable(6); + check(win, 6, 6, 6); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void test9(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + win.add(1, new Message()); + win.add(2, new Message()); + win.add(3, new Message()); + win.add(4, new Message()); + win.add(6, new Message()); + System.out.println("win: " + win); + while((win.remove()) != null) ; + win.stable(6); // 6 is ignore as it is >= highest delivered message + System.out.println("win: " + win); + assert win.get(2) != null; + check(win, 0, 6, 4); + win.add(5, new Message()); + check(win, 0, 6, 4); + while((win.remove()) != null) ; + check(win, 0, 6, 6); + win.stable(6); + check(win, 6, 6, 6); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void testHighestDelivered(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + win.add(1, new Message()); + win.add(2, new Message()); + win.add(3, new Message()); + win.add(4, new Message()); + check(win, 0, 4, 0); + win.add(10, new Message()); + check(win, 0, 10, 0); + System.out.println("win: " + win); + win.add(9, new Message()); + win.add(7, new Message()); + win.add(8, new Message()); + win.add(6, new Message()); + win.add(5, new Message()); + System.out.println("win: " + win); + check(win, 0, 10, 0); + while((win.remove()) != null) ; + check(win, 0, 10, 10); + win.stable(5); + System.out.println("win: " + win); + check(win, 5, 10, 10); + win.stable(10); + System.out.println("win: " + win); + check(win, 10, 10, 10); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void testMissingMessages(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + win.add(1, new Message()); + win.add(5, new Message()); + check(win, 0, 5, 0); + win.add(6, new Message()); + check(win, 0, 6, 0); + System.out.println("win: " + win); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void testMissingMessages2(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + win.add(1, new Message()); + win.add(5, new Message()); + check(win, 0, 5, 0); + win.add(8, new Message()); + check(win, 0, 8, 0); + win.add(9, new Message()); + check(win, 0, 9, 0); + System.out.println("win: " + win); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void testMissingMessages3(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + win.add(1, new Message()); + win.add(5, new Message()); + check(win, 0, 5, 0); + win.add(8, new Message()); + check(win, 0, 8, 0); + win.add(9, new Message()); + check(win, 0, 9, 0); + System.out.println("win: " + win); + win.add(2, new Message()); + check(win, 0, 9, 0); + win.add(3, new Message()); + win.add(4, new Message()); + check(win, 0, 9, 0); + win.add(7, new Message()); + check(win, 0, 9, 0); + win.add(6, new Message()); + check(win, 0, 9, 0); + win.add(10, new Message()); + check(win, 0, 10, 0); + win.add(11, new Message()); + check(win, 0, 11, 0); + System.out.println("win: " + win); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void testMissingMessages4(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, timer); + win.add(101, new Message()); + win.add(105, new Message()); + check(win, 0, 105, 100); + win.add(108, new Message()); + check(win, 0, 108, 100); + win.add(109, new Message()); + check(win, 0, 109, 100); + System.out.println("win: " + win); + win.add(102, new Message()); + check(win, 0, 109, 100); + win.add(103, new Message()); + win.add(104, new Message()); + check(win, 0, 109, 100); + win.add(107, new Message()); + check(win, 0, 109, 100); + win.add(106, new Message()); + check(win, 0, 109, 100); + win.add(110, new Message()); + check(win, 0, 110, 100); + win.add(110, new Message()); + check(win, 0, 110, 100); + System.out.println("win: " + win); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void testMissingMessages5(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 100, timer); + win.add(101, new Message()); + check(win, 0, 101, 100); + win.add(108, new Message()); + check(win, 0, 108, 100); + win.remove(); + win.add(109, new Message()); + check(win, 0, 109, 101); + System.out.println("win: " + win); + win.add(102, new Message()); + check(win, 0, 109, 101); + win.add(103, new Message()); + win.add(104, new Message()); + check(win, 0, 109, 101); + win.add(107, new Message()); + check(win, 0, 109, 101); + win.add(106, new Message()); + win.add(105, new Message()); + check(win, 0, 109, 101); + win.add(110, new Message()); + check(win, 0, 110, 101); + win.add(110, new Message()); + check(win, 0, 110, 101); + System.out.println("win: " + win); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void test10(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + win.add(1, new Message()); + win.add(2, new Message()); + win.add(3, new Message()); + win.add(4, new Message()); + while((win.remove()) != null) ; + check(win, 0, 4, 4); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void test10a(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + win.add(1, new Message()); + win.add(2, new Message()); + win.add(3, new Message()); + win.add(4, new Message()); + while((win.remove()) != null) ; + win.stable(4); + check(win, 4, 4, 4); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void test11(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + win.add(1, new Message()); + win.add(2, new Message()); + win.add(3, new Message()); + win.add(4, new Message()); + while((win.remove()) != null) ; + win.destroy(); + check(win, 0, 0, 0); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void test12(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + + win.add(1, new Message(null, null, new Integer(1))); + win.add(2, new Message(null, null, new Integer(2))); + win.add(3, new Message(null, null, new Integer(3))); + + Assert.assertEquals(1, ((Integer)win.remove().getObject()).intValue()); + Assert.assertEquals(2, ((Integer)win.remove().getObject()).intValue()); + Assert.assertEquals(3, ((Integer)win.remove().getObject()).intValue()); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void test13(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + win.add(1, new Message()); + win.add(2, new Message()); + win.add(3, new Message()); + win.add(4, new Message()); + check(win, 0, 4, 0); + win.remove(); + win.remove(); + win.add(5, new Message()); + win.add(6, new Message()); + check(win, 0, 6, 2); + win.stable(2); + check(win, 2, 6, 2); + } + finally { + timer.stop(); + } + } + + + + @Test(dataProvider="createTimer") + public void testAddOOBAtHead(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + boolean rc; + rc=win.add(0, oob()); + assert !(rc); + rc=win.add(1, oob()); + assert rc; + rc=win.add(1, oob()); + assert !(rc); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void testAddOOBAtTail(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + boolean rc; + rc=win.add(1, oob()); + assert rc; + rc=win.add(2, oob()); + assert rc; + rc=win.add(2, oob()); + assert !(rc); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void testAddOOBInTheMiddle(TimeScheduler timer) throws Exception { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + boolean rc; + rc=win.add(3, oob()); + assert rc; + rc=win.add(3, oob()); + assert !(rc); + rc=win.add(1, oob()); + assert rc; + rc=win.add(1, oob()); + assert !(rc); + rc=win.add(2, oob()); + assert rc; + rc=win.add(2, oob()); + assert !(rc); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void testUpdateHighestSeen(TimeScheduler timer) { + add(1000, timer); + add(2000, timer); + add(3000, timer); + add(4000, timer); + add(5000, timer); + add(10000, timer); + add(15000, timer); + add(20000, timer); + add(30000, timer); + } + + + @Test(dataProvider="createTimer") + public void test1000(TimeScheduler timer) { + add(1000, timer); + } + + + @Test(dataProvider="createTimer") + public void test10000(TimeScheduler timer) { + add(10000, timer); + } + + + + + + @Test(dataProvider="createTimer") + public void testRemoveRegularAndOOBMessages(TimeScheduler timer) { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + win.add(1, msg()); + System.out.println("win = " + win); + win.remove(); + System.out.println("win = " + win); + assert win.getHighestDelivered() == 1; + + win.add(3, msg()); + win.remove(); + System.out.println("win = " + win); + assert win.getHighestDelivered() == 1; + + win.add(2, oob()); + System.out.println("win = " + win); + + assert win.getHighestDelivered() == 1 : "highest_delivered should be 2, but is " + win.getHighestDelivered(); + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void testRemoveMany(TimeScheduler timer) { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + win.add(1, msg()); + win.add(2, msg()); + win.add(3, msg()); + win.add(5, msg()); + win.add(6, msg()); + System.out.println("win = " + win); + List msgs=win.removeMany(null); + System.out.println("msgs = " + msgs); + assert msgs.size() == 3; + + win.add(4, msg()); + msgs=win.removeMany(null); + System.out.println("msgs = " + msgs); + assert msgs.size() == 3; + } + finally { + timer.stop(); + } + } + + @Test(dataProvider="createTimer") + public void testRemoveMany2(TimeScheduler timer) { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 4, timer); + win.add(5, msg()); + win.add(6, msg()); + win.add(7, msg()); + win.add(9, msg()); + win.add(10, msg()); + System.out.println("win = " + win); + List msgs=win.removeMany(null); + System.out.println("msgs = " + msgs); + assert msgs.size() == 3; + + win.add(8, msg()); + msgs=win.removeMany(null); + System.out.println("msgs = " + msgs); + assert msgs.size() == 3; + } + finally { + timer.stop(); + } + } + + + + + @Test(dataProvider="createTimer") + public void testRetransmitter(TimeScheduler timer) { + try { + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 0, timer); + win.add(1, msg()); + win.add(2, msg()); + win.add(3, msg()); + win.add(5, msg()); + win.add(6, msg()); + System.out.println("win = " + win); + int num_pending_xmits=win.getPendingXmits(); + assert num_pending_xmits == 1; + + win.add(4, msg()); + num_pending_xmits=win.getPendingXmits(); + assert num_pending_xmits == 0; + } + finally { + timer.stop(); + } + } + + + + private static void add(int num_msgs, TimeScheduler timer) { + try { + long start, stop; + double time_per_msg; + NakReceiverWindow win=new NakReceiverWindow(sender, cmd, 1, timer); + start=System.currentTimeMillis(); + for(int i=1; i < 1 + num_msgs; i++) { + win.add(i, new Message()); + } + stop=System.currentTimeMillis(); + time_per_msg=(stop-start) / (double)num_msgs; + System.out.println("-- time for " + num_msgs + " msgs: " + (stop-start) + ", " + time_per_msg + " ms/msg"); + } + finally { + timer.stop(); + } } diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/OrderingTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/OrderingTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/OrderingTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/OrderingTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,242 @@ +package org.jgroups.tests; + +import org.jgroups.*; +import org.jgroups.protocols.*; +import org.jgroups.protocols.pbcast.GMS; +import org.jgroups.protocols.pbcast.NAKACK; +import org.jgroups.protocols.pbcast.STABLE; +import org.jgroups.stack.ProtocolStack; +import org.jgroups.util.Util; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Tests message ordering + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL,sequential=true) +public class OrderingTest { + protected static final int NUM_MSGS=200000; + protected static final int NUM_SENDERS=2; + protected static final int TOTAL_NUM_MSGS=NUM_MSGS * NUM_SENDERS; + + protected JChannel[] channels=new JChannel[NUM_SENDERS]; + protected MySender[] senders=new MySender[NUM_SENDERS]; + + + @BeforeMethod + void init() throws Exception { + System.out.println("creating " + NUM_SENDERS + " channels"); + for(int i=0; i < channels.length; i++) { + channels[i]=createChannel(); + channels[i].setReceiver(new MyReceiver()); + senders[i]=new MySender(channels[i]); + channels[i].connect("OrderingTest.testFIFOOrder"); + } + System.out.println("done"); + + System.out.println("\nwaiting for a cluster of " + NUM_SENDERS + " to form:"); + boolean done=true; + for(int i=0; i < 20; i++) { + for(JChannel ch: channels) { + if(ch.getView().size() != NUM_SENDERS) { + done=false; + break; + } + } + if(!done) + Util.sleep(1000); + else + break; + } + } + + @AfterMethod + void destroy() { + for(int i=channels.length-1; i >= 0; i--) { + Util.close(channels[i]); + } + } + + + protected static JChannel createChannel() throws Exception { + JChannel ch=new JChannel(false); + ProtocolStack stack=new ProtocolStack(); + ch.setProtocolStack(stack); + stack.addProtocol(new SHARED_LOOPBACK().setValue("oob_thread_pool_rejection_policy", "run") + .setValue("thread_pool_rejection_policy", "run") + .setValue("thread_pool_queue_max_size", 100000)) + .addProtocol(new PING()) + .addProtocol(new MERGE2()) + .addProtocol(new FD_SOCK()) + .addProtocol(new VERIFY_SUSPECT()) + .addProtocol(new BARRIER()) + .addProtocol(new NAKACK().setValue("use_mcast_xmit", false).setValue("discard_delivered_msgs", true)) + .addProtocol(new UNICAST2().setValue("stable_interval", 10000).setValue("max_bytes", 50000)) + .addProtocol(new STABLE().setValue("max_bytes", 50000)) + .addProtocol(new GMS().setValue("print_local_addr", false)) + .addProtocol(new UFC().setValue("max_credits", 2000000)) + .addProtocol(new MFC().setValue("max_credits", 2000000)) + .addProtocol(new FRAG2()); + stack.init(); + return ch; + } + + /*protected static JChannel createChannel() throws ChannelException { + return new JChannel("/home/bela/fast.xml"); + }*/ + + + + public void testFIFOOrdering() throws Exception { + assert channels[0].getView().size() == NUM_SENDERS : "view[0] is " + channels[0].getView().size(); + System.out.println("done, view is " + channels[0].getView()); + + System.out.println("\nstarting to send " + NUM_MSGS + " messages"); + for(int i=0; i < senders.length; i++) + senders[i].start(); + for(int i=0; i < senders.length; i++) { + MySender sender=senders[i]; + sender.join(); + } + System.out.println("senders done"); + + System.out.println("\nwaiting for message reception by all receivers:"); + boolean done; + for(int i=0; i < 50; i++) { + done=true; + for(JChannel ch: channels) { + MyReceiver receiver=(MyReceiver)ch.getReceiver(); + int received=receiver.getReceived(); + System.out.println(ch.getAddress() + ": " + received); + STABLE stable=(STABLE)ch.getProtocolStack().findProtocol(STABLE.class); + stable.runMessageGarbageCollection(); + if(received != TOTAL_NUM_MSGS) { + done=false; + break; + } + } + if(!done) + Util.sleep(1000); + else + break; + } + for(JChannel ch: channels) { + MyReceiver receiver=(MyReceiver)ch.getReceiver(); + System.out.println(ch.getAddress() + ": " + receiver.getReceived()); + } + + for(JChannel ch: channels) { + MyReceiver receiver=(MyReceiver)ch.getReceiver(); + assert receiver.getReceived() == TOTAL_NUM_MSGS : "receiver had " + receiver.getReceived() + + " messages (expected=" + TOTAL_NUM_MSGS + ")"; + } + System.out.println("done"); + + System.out.println("\nchecking message order"); + + for(JChannel ch: channels) { + MyReceiver receiver=(MyReceiver)ch.getReceiver(); + System.out.print(ch.getAddress() + ": "); + boolean ok=receiver.getNumberOfErrors() == 0; + System.out.println(ok? "OK" : "FAIL (" + receiver.getNumberOfErrors() + " errors)"); + assert ok : receiver.getNumberOfErrors() + " errors"; + } + + System.out.println("done"); + } + + + /* private static boolean checkOrder(ConcurrentMap> map, boolean print_incorrect_elements) { + boolean retval=true; + for(Map.Entry> entry: map.entrySet()) { + Address sender=entry.getKey(); + List list=entry.getValue(); + int curr=1; + for(Integer num: list) { + if(!num.equals(curr)) { + retval=false; + if(!print_incorrect_elements) + return false; + System.err.println("element " + num + " != " + curr); + } + curr++; + } + } + + return retval; + }*/ + + + protected static class MySender extends Thread { + protected final JChannel ch; + + public MySender(JChannel ch) { + this.ch=ch; + } + + public void run() { + for(int i=1; i <= NUM_MSGS; i++) { + try { + Message msg=new Message(null, null, new Integer(i)); + ch.send(msg); + if(i % 100000 == 0) + System.out.println(Thread.currentThread().getId() + ": " + i + " sent"); + } + catch(Exception e) { + e.printStackTrace(); + } + } + } + } + + protected static class MyReceiver extends ReceiverAdapter { + protected final ConcurrentMap map=new ConcurrentHashMap(); + final AtomicInteger received=new AtomicInteger(0); + protected int num_errors=0; + + public int getNumberOfErrors() { + return num_errors; + } + + public int getReceived() { + return received.intValue(); + } + + public void receive(Message msg) { + Integer num=(Integer)msg.getObject(); + Address sender=msg.getSrc(); + + Integer current_seqno=map.get(sender); + if(current_seqno == null) { + current_seqno=new Integer(1); + Integer tmp=map.putIfAbsent(sender, current_seqno); + if(tmp != null) + current_seqno=tmp; + } + + if(current_seqno.intValue() == num) + map.put(sender, current_seqno + 1); + else + num_errors++; + + if(received.incrementAndGet() % 100000 == 0) + System.out.println("received " + received); + } + } + + + /*public static void main(String[] args) throws Exception { + OrderingTest test=new OrderingTest(); + test.init(); + test.testFIFOOrdering(); + test.destroy(); + }*/ +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/PortManagerTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/PortManagerTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/PortManagerTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/PortManagerTest.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,93 +0,0 @@ -package org.jgroups.tests; - -import org.jgroups.Global; -import org.jgroups.util.PortsManager; -import org.jgroups.util.Util; -import org.testng.Assert; -import org.testng.annotations.Test; - -import java.util.LinkedList; -import java.util.List; - -/** - * @author Bela Ban - * @version $Id: PortManagerTest.java,v 1.6 2008/07/17 14:49:50 vlada Exp $ - */ -@Test(groups=Global.FUNCTIONAL) -public class PortManagerTest { - private final static int START_PORT=15550; - private static final String TEMP_DIR = System.getProperty("java.io.tmpdir", "/tmp"); - - - public static void testAddition() { - PortsManager pm=new PortsManager(30000,"testAddition.txt",TEMP_DIR); - pm.deleteFile(); - List ports=new LinkedList(); - - for(int i=0; i < 10; i++) { - int port=pm.getNextAvailablePort(START_PORT); - assert port > 0; - ports.add(port); - } - - System.out.println("ports: " + ports); - Assert.assertEquals(10, ports.size()); - } - - - public static void testNonDuplicateAddition() { - PortsManager pm=new PortsManager(30000,"testNonDuplicateAddition.txt",TEMP_DIR); - pm.deleteFile(); - - int port=pm.getNextAvailablePort(START_PORT); - System.out.println("port=" + port); - Assert.assertEquals(START_PORT, port); - - int port2=pm.getNextAvailablePort(START_PORT); - System.out.println("port2 = " + port2); - Assert.assertNotSame(port, port2); - } - - - public static void testExpiration() { - PortsManager pm=new PortsManager(800,"testExpiration.txt",TEMP_DIR); - pm.deleteFile(); - - int port=pm.getNextAvailablePort(START_PORT); - System.out.println("port = " + port); - Util.sleep(900); - int port2=pm.getNextAvailablePort(START_PORT); - System.out.println("port2 = " + port2); - Assert.assertEquals(port, port2); - - Util.sleep(900); - port=pm.getNextAvailablePort(START_PORT); - port2=pm.getNextAvailablePort(START_PORT); - System.out.println("port=" + port + ", port2=" + port2); - Assert.assertNotSame(port, port2); - } - - - public static void testRemove() { - PortsManager pm=new PortsManager(10000,"testRemove.txt",TEMP_DIR); - pm.deleteFile(); - int port=pm.getNextAvailablePort(START_PORT); - int old_port=port; - System.out.println("port = " + port); - Assert.assertEquals(START_PORT, port); - int port2=pm.getNextAvailablePort(START_PORT); - System.out.println("port2 = " + port2); - Assert.assertNotSame(port, port2); - pm.removePort(port); - port2=pm.getNextAvailablePort(START_PORT); - System.out.println("port2 = " + port2); - Assert.assertEquals(port, port2); - pm.removePort(port); - pm.removePort(port2); - port=pm.getNextAvailablePort(START_PORT); - System.out.println("port = " + port); - Assert.assertEquals(old_port, port); - } - - -} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ProgrammaticApiTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ProgrammaticApiTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ProgrammaticApiTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ProgrammaticApiTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,133 @@ +package org.jgroups.tests; + +import org.jgroups.*; +import org.jgroups.protocols.*; +import org.jgroups.protocols.pbcast.GMS; +import org.jgroups.protocols.pbcast.NAKACK; +import org.jgroups.protocols.pbcast.STABLE; +import org.jgroups.stack.Protocol; +import org.jgroups.stack.ProtocolStack; +import org.jgroups.util.Util; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.net.InetAddress; + +/** + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL,sequential=true) +public class ProgrammaticApiTest { + JChannel c1, c2; + + @BeforeMethod + void init() { + c1=new JChannel(false); c1.setName("A"); + c2=new JChannel(false); c2.setName("B"); + } + + @AfterMethod + void destroy() { + Util.close(c2, c1); + } + + public void testChannelCreation() throws Exception { + MyReceiver receiver=new MyReceiver(null); + c1.setReceiver(receiver); + ProtocolStack stack=new ProtocolStack(); + c1.setProtocolStack(stack); + stack.addProtocol(new SHARED_LOOPBACK()).addProtocol(new MockProtocol1()).addProtocol(new MockProtocol2()); + stack.init(); + c1.connect("demo"); + + Protocol transport=stack.getTransport(); + transport.up(new Event(Event.MSG, new Message(null, Util.createRandomAddress(), "hello world"))); + assert receiver.getNumMsgsReceived() == 1; + } + + + public void testSharedTransport() throws Exception { + ProtocolStack stack1=new ProtocolStack(), stack2=new ProtocolStack(); + c1.setProtocolStack(stack1); + c2.setProtocolStack(stack2); + + MyReceiver receiver1=new MyReceiver("A"), receiver2=new MyReceiver("B"); + + UDP shared_transport=(UDP)new UDP().setValue("singleton_name", "shared"); + + stack1.addProtocol(shared_transport).addProtocols(createProtocols()); + stack2.addProtocol(shared_transport).addProtocols(createProtocols()); + + stack1.init(); + stack2.init(); + + c1.setReceiver(receiver1); + c2.setReceiver(receiver2); + + c1.connect("cluster-one"); + c2.connect("cluster-two"); + + for(int i=0; i < 10; i++) + c1.send(new Message(null, null, "hello-" + i)); + + for(int i=0; i < 5; i++) + c2.send(new Message(null, null, "hello-" + i)); + + for(int i =0; i < 20; i++) { + if(receiver1.getNumMsgsReceived() == 10 && receiver2.getNumMsgsReceived() == 5) + break; + Util.sleep(500); + } + assert receiver1.getNumMsgsReceived() == 10 : "num msgs for A: " + receiver1.getNumMsgsReceived() + " (expected=10)"; + assert receiver2.getNumMsgsReceived() == 5 : "num msgs for B: " + receiver1.getNumMsgsReceived() + " (expected=5)"; + } + + + + protected static class MockProtocol1 extends Protocol { + + } + + protected static class MockProtocol2 extends Protocol { + + } + + static Protocol[] createProtocols() { + return new Protocol[] { + new PING(), + new MERGE2(), + new FD_SOCK(), + new FD_ALL().setValue("timeout", 12000).setValue("interval", 3000), + new VERIFY_SUSPECT(), + new BARRIER(), + new NAKACK(), + new UNICAST2(), + new STABLE(), + new GMS(), + new UFC(), + new MFC(), + new FRAG2() + }; + } + + + + static class MyReceiver extends ReceiverAdapter { + int num_msgs_received=0; + final String name; + + public MyReceiver(String name) { + this.name=name; + } + + public int getNumMsgsReceived() { + return num_msgs_received; + } + + public void receive(Message msg) { + System.out.println((name != null? "[" + name + "]" : "") + "<< " + msg.getObject()); + num_msgs_received++; + } + } +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/PromiseTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/PromiseTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/PromiseTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/PromiseTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,4 +1,3 @@ -// $Id: PromiseTest.java,v 1.4 2008/03/10 15:39:19 belaban Exp $ package org.jgroups.tests; diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/PropertyConvertersTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/PropertyConvertersTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/PropertyConvertersTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/PropertyConvertersTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -3,41 +3,34 @@ import org.jgroups.Global; import org.jgroups.conf.PropertyConverter; import org.jgroups.conf.PropertyConverters; -import org.jgroups.util.Util; +import org.jgroups.stack.Protocol; import org.testng.annotations.Test; -import java.net.InetAddress; import java.util.Arrays; -import java.util.Properties; import java.util.List; /** * @author Bela Ban - * @version $Id: PropertyConvertersTest.java,v 1.5 2008/07/08 14:36:33 vlada Exp $ */ @Test(groups=Global.FUNCTIONAL, sequential=false) public class PropertyConvertersTest { public static void testPrimitiveTypes() throws Exception { PropertyConverter conv=new PropertyConverters.Default(); - check(Boolean.TYPE, "true", true, conv); - check(Integer.TYPE, "322649", 322649, conv); - check(Long.TYPE, "322649", 322649L, conv); + check(null, Boolean.TYPE, "true", true, conv); + check(null, Integer.TYPE, "322649", 322649, conv); + check(null, Long.TYPE, "322649", 322649L, conv); } public static void testLongArray() throws Exception { PropertyConverter conv=new PropertyConverters.LongArray(); long[] array={1,2,3,4,5}; - checkArray(array.getClass(), "1,2,3,4,5", array, conv); + checkArray(null, array.getClass(), "1,2,3,4,5", array, conv); } - public static void testBindAddress() throws Exception { - PropertyConverter conv=new PropertyConverters.BindAddress(); - InetAddress addr=Util.getBindAddress(new Properties()); - check(InetAddress.class, addr.getHostAddress(),addr, conv); - } + /** Cannot really test list of eth0,eth1,lo, because the list differs from host to host * @@ -45,15 +38,15 @@ */ public static void testNetworkList() throws Exception { PropertyConverter conv=new PropertyConverters.NetworkInterfaceList(); - Object tmp=conv.convert(List.class, new Properties(), "lo"); + Object tmp=conv.convert(null, List.class, "bela", "lo", false); Object str=conv.toString(tmp); System.out.println("str = " + str); assert str.equals("lo"); } - private static void check(Class type, String prop, Object result, PropertyConverter converter) throws Exception { - Object tmp=converter.convert(type, new Properties(), prop); + private static void check(Protocol protocol, Class type, String prop, Object result, PropertyConverter converter) throws Exception { + Object tmp=converter.convert(protocol, type, "bela", prop, false); assert tmp.equals(result) : " conversion result: " + tmp + " (" + tmp.getClass() + ")" + ", expected result: " + result + " (" + result.getClass() + ")"; @@ -61,8 +54,8 @@ assert output.equals(prop) : "output=" + output + ", prop=" + prop; } - private static void checkArray(Class type, String prop, Object result, PropertyConverter converter) throws Exception { - Object tmp=converter.convert(type, null, prop); + private static void checkArray(Protocol protocol, Class type, String prop, Object result, PropertyConverter converter) throws Exception { + Object tmp=converter.convert(protocol, type, "bela", prop, false); assert Arrays.equals((long[])tmp, (long[])result) : " conversion result: " + tmp + " (" + tmp.getClass() + ")" + ", expected result: " + result + " (" + result.getClass() + ")"; diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ProtocolConfigurationTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ProtocolConfigurationTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ProtocolConfigurationTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ProtocolConfigurationTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,349 @@ +package org.jgroups.tests; + +import org.jgroups.Event; +import org.jgroups.Global; +import org.jgroups.conf.ProtocolConfiguration; +import org.jgroups.util.StackType; +import org.jgroups.annotations.Property; +import org.jgroups.conf.PropertyConverters; +import org.jgroups.stack.Configurator; +import org.jgroups.stack.IpAddress; +import org.jgroups.stack.Protocol; +import org.jgroups.stack.ProtocolStack; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.net.InetAddress; +import java.util.LinkedList; +import java.util.List; +import java.util.Vector; + +/** + * Tests the use of @Property dependency processing and default assignment. + * @author Richard Achmatowicz + */ +@Test(groups=Global.FUNCTIONAL,sequential=true) +public class ProtocolConfigurationTest { + ProtocolStack stack = null; + Protocol protocol = null ; + static final String orderProps="org.jgroups.tests.ProtocolConfigurationTest$ORDERING(a=1;b=2;c=3)"; + static final String refsProps="org.jgroups.tests.ProtocolConfigurationTest$REFS(a=1;b=2;c=3)"; + static final String defaultProps="org.jgroups.tests.ProtocolConfigurationTest$DEFAULTS(b=333)"; + static final String addressProps="org.jgroups.tests.ProtocolConfigurationTest$INETADDRESSES(" + + "inetAddressField=127.0.0.1;inet_address_method=192.168.0.100;" + + "ipAddressListField=127.0.0.1[8080],127.0.0.1[8081];" + + "ip_address_list_method=192.168.0.100[5678],192.168.0.101[2345];port_range=1)" ; + static final String configurableObjectsProps="org.jgroups.tests.ProtocolConfigurationTest$CONFIGOBJPROTOCOL(" + + "config_object_class=org.jgroups.tests.ProtocolConfigurationTest$ConfigurableObject;" + + "string_property=test)" ; + + List order = new LinkedList() ; + + @BeforeMethod + void setUp() { + stack=new ProtocolStack(); + } + + /* + * Checks that missing dependencies are flagged + */ + @Test(expectedExceptions=IllegalArgumentException.class) + public void testResolutionOfDependencies() throws Exception { + + // create the layer described by REFS + try { + protocol = Configurator.createProtocol(refsProps, stack) ; + } + catch(IllegalArgumentException e) { + System.out.println("exception thrown (expected): " + e.getMessage()); + // rethrow to make sure testNG does not fail the test + throw e ; + } + } + + /* + * Checks that dependency ordering works + */ + public void testDependencyOrdering() throws Exception { + // create a List describing correct Property ordering + List correctOrder = new LinkedList() ; + correctOrder.add("c") ; + correctOrder.add("b") ; + correctOrder.add("a") ; + + // create the layer described by ORDERING + protocol = Configurator.createProtocol(orderProps, stack) ; + + // check that the list elements are in the right order + List actualOrder = ((ORDERING)protocol).getList() ; + + assert actualOrder.equals(correctOrder) ; + } + + /* + * Checks assignment of defaults + */ + public void testDefaultAssignment() throws Exception { + + Vector protocol_configs = new Vector() ; + Vector protocols = new Vector() ; + + // create the layer described by DEFAULTS + protocol = Configurator.createProtocol(defaultProps, stack) ; + // process the defaults + protocol_configs.add(new ProtocolConfiguration(defaultProps)) ; + protocols.add(protocol) ; + Configurator.setDefaultValues(protocol_configs, protocols, StackType.IPv4) ; + + // get the value which should have been assigned a default + int a = ((DEFAULTS)protocol).getA() ; + System.out.println("value of a = " + a) ; + + // get the value which should not have been assigned a default + int b = ((DEFAULTS)protocol).getB() ; + System.out.println("value of b = " + b) ; + + // assert b == 333 ; + if (b != 333) { + throw new RuntimeException("default property value set when it should not have been") ; + } + + // get the value which should not have been assigned a default + InetAddress c = ((DEFAULTS)protocol).getC() ; + System.out.println("value of c = " + c) ; + assert c != null; + } + /* + * Checks InetAddress and IpAddress processing + */ + public void testAssignmentInetAddresses() throws Exception { + + Vector protocol_configs = new Vector() ; + Vector protocols = new Vector() ; + + // create the layer described by INETADDRESSES + protocol = Configurator.createProtocol(addressProps, stack) ; + + // get the value which should have been assigned a default + InetAddress a = ((INETADDRESSES)protocol).getInetAddressField() ; + System.out.println("value of inetAddressField = " + a) ; + + // get the value which should not have been assigned a default + InetAddress b = ((INETADDRESSES)protocol).getInetAddressMethod() ; + System.out.println("value of inetAddressMethod = " + b) ; + + // get the value which should have been assigned a default + List c = ((INETADDRESSES)protocol).getIpAddressListField() ; + System.out.println("value of ipAddressListField = " + c) ; + + // get the value which should not have been assigned a default + List d = ((INETADDRESSES)protocol).getIpAddressListMethod() ; + System.out.println("value of ipAddressListMethod = " + d) ; + + } + + /* + * Checks InetAddress and IpAddress processing + */ + public void testConfigurableObject() throws Exception { + + Vector protocol_configs = new Vector() ; + Vector protocols = new Vector() ; + + // create the layer described by INETADDRESSES + protocol = Configurator.createProtocol(configurableObjectsProps, stack) ; + + // process the defaults (want this eventually) + protocol_configs.add(new ProtocolConfiguration(configurableObjectsProps)) ; + protocols.add(protocol) ; + + // get the value which should have been assigned a default + List configObjs = ((CONFIGOBJPROTOCOL)protocol).getConfigurableObjects() ; + assert configObjs.size() == 1 ; + Object configObj = configObjs.get(0) ; + assert configObj instanceof ConfigurableObject ; + assert ((ConfigurableObject)configObj).getStringProp().equals("test") ; + + } + + + public static class ORDERING extends Protocol { + List list = new LinkedList() ; + + @Property(name="a", dependsUpon="b") + public void setA(int a) { + list.add("a") ; + } + @Property(name="b", dependsUpon="c") + public void setB(int b) { + list.add("b") ; + } + @Property(name="c") + public void setC(int c) { + list.add("c") ; + } + List getList() { + return list ; + } + public String getName() { + return name ; + } + // do nothing + public Object down(Event evt) { + return down_prot.down(evt); + } + // do nothing + public Object up(Event evt) { + return up_prot.up(evt); + } + } + public static class REFS extends Protocol { + + @Property(name="a", dependsUpon="b") + public void setA(int a) { + } + @Property(name="b", dependsUpon="d") + public void setB(int b) { + } + @Property(name="c") + public void setC(int c) { + } + public String getName() { + return name ; + } + // do nothing + public Object down(Event evt) { + return down_prot.down(evt); + } + // do nothing + public Object up(Event evt) { + return up_prot.up(evt); + } + } + public static class DEFAULTS extends Protocol { + int a ; + int b ; + InetAddress c ; + + @Property(name="a") + public void setA(int a) { + this.a = a ; + } + @Property(name="b") + public void setB(int b) { + this.b = b ; + } + @Property(name="c", defaultValueIPv4="192.168.1.10") + public void setC(InetAddress ia) { + this.c = ia ; + } + public int getA() { + return a ; + } + public int getB() { + return b ; + } + public InetAddress getC() { + return c ; + } + public String getName() { + return name ; + } + // do nothing + public Object down(Event evt) { + return down_prot.down(evt); + } + // do nothing + public Object up(Event evt) { + return up_prot.up(evt); + } + } + public static class INETADDRESSES extends Protocol { + InetAddress inetAddressMethod ; + + @Property(name="inetAddressField") + InetAddress inetAddressField ; + + public InetAddress getInetAddressField() { + return inetAddressField ; + } + @Property(name="inetAddressMethod") + public void setInetAddressMethod(InetAddress ia) { + this.inetAddressMethod = ia ; + } + public InetAddress getInetAddressMethod() { + return inetAddressMethod ; + } + + @Property(description="fred") + int port_range = 0 ; + + // List - uses InitialHosts converter + List ipAddressListMethod ; + + @Property(name="ipAddressListField", converter=PropertyConverters.InitialHosts.class) + List ipAddressListField ; + + public List getIpAddressListField() { + return ipAddressListField ; + } + @Property(name="ipAddressListMethod", converter=PropertyConverters.InitialHosts.class, dependsUpon="port_range") + public void setIpAddressListMethod(List ia) { + this.ipAddressListMethod = ia ; + } + public List getIpAddressListMethod() { + return ipAddressListMethod ; + } + + public String getName() { + return name ; + } + // do nothing + public Object down(Event evt) { + return down_prot.down(evt); + } + // do nothing + public Object up(Event evt) { + return up_prot.up(evt); + } + } + public static class CONFIGOBJPROTOCOL extends Protocol { + + private Object configObjInstance=null; + + @Property(name="config_object_class") + public void setConfigurableObjectClass(String class_name) throws Exception { + configObjInstance=Class.forName(class_name).newInstance(); + } + + protected List getConfigurableObjects() { + List retval=new LinkedList(); + if(configObjInstance != null) + retval.add(configObjInstance); + return retval; + } + + public String getName() { + return name ; + } + // do nothing + public Object down(Event evt) { + return down_prot.down(evt); + } + // do nothing + public Object up(Event evt) { + return up_prot.up(evt); + } + } + + public static class ConfigurableObject { + @Property(name="string_property") + String stringProp = null ; + public String getStringProp() { + return stringProp ; + } + public void setStringProp(String s) { + this.stringProp = s ; + } + } +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/QueueTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/QueueTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/QueueTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/QueueTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -11,11 +11,11 @@ import java.util.ArrayList; import java.util.LinkedList; +import java.util.List; /** * @author Bela Ban - * @version $Id: QueueTest.java,v 1.3 2008/04/15 15:16:36 belaban Exp $ */ @Test(groups=Global.FUNCTIONAL,sequential=false) public class QueueTest { @@ -33,11 +33,7 @@ assert queue.peek().equals("Q2"); assert queue.remove().equals("Q2"); - - queue.addAtHead("Q4"); queue.add("Q5"); - assert queue.peek().equals("Q4"); - assert queue.remove().equals("Q4"); queue.close(true); @@ -136,7 +132,7 @@ public static void testAddAll() throws QueueClosedException { final Queue queue=new Queue(); - ArrayList l=new ArrayList(); + List l=new ArrayList(); l.add("one"); l.add("two"); l.add("three"); @@ -590,7 +586,20 @@ System.out.println("-- adding element 100"); queue.add(new Long(100)); - Util.sleep(500); + long target_time=System.currentTimeMillis() + 10000L; + do { + int num=0; + for(int i=0; i < removers.length; i++) { + if(!removers[i].isAlive()) + num++; + } + if(num == 2) + break; + Util.sleep(500); + } + while(target_time > System.currentTimeMillis()); + + for(int i=0; i < removers.length; i++) { System.out.println("remover #" + i + " is " + (removers[i].isAlive() ? "alive" : "terminated")); if(!removers[i].isAlive()) { @@ -598,19 +607,19 @@ } } - assert num_dead == 2; + assert num_dead == 2 : "num_dead was " + num_dead + ", but expected 2"; + queue.close(false); } /** Multiple threads call remove(), one threads then adds an element. Only 1 thread should actually terminate * (the one that has the element) */ - public static void testBarrierWithTimeOut() throws QueueClosedException { final Queue queue=new Queue(); RemoveOneItemWithTimeout[] removers=new RemoveOneItemWithTimeout[10]; int num_dead=0; for(int i=0; i < removers.length; i++) { - removers[i]=new RemoveOneItemWithTimeout(i, 1000, queue); + removers[i]=new RemoveOneItemWithTimeout(i, 15000, queue); removers[i].start(); } @@ -619,20 +628,32 @@ System.out.println("-- adding element 100"); queue.add(new Long(100)); - Util.sleep(500); + long target_time=System.currentTimeMillis() + 10000L; + do { + int num_rsps=0; + for(int i=0; i < removers.length; i++) { + if(removers[i].getRetval() != null) + num_rsps++; + } + if(num_rsps == 2) + break; + Util.sleep(500); + } + while(target_time > System.currentTimeMillis()); + + Util.sleep(3000); for(int i=0; i < removers.length; i++) { - System.out.println("remover #" + i + " is " + (removers[i].isAlive()? "alive" : "terminated")); + System.out.println("remover #" + i + " is " + (removers[i].isAlive() ? "alive" : "terminated")); if(!removers[i].isAlive()) { num_dead++; } } - assert num_dead == 2; + assert num_dead == 2 : "num_dead should have been 2 but was " + num_dead; System.out.println("closing queue - causing all remaining threads to terminate"); - queue.close(false); // will cause all threads still blocking on peek() to return - + queue.close(false); // will cause all threads still blocking on remove() to return Util.sleep(500); num_dead=0; @@ -642,7 +663,7 @@ num_dead++; } } - assert num_dead == 10; + assert num_dead == 10 : "num_dead should have been 10 but was " + num_dead; } @@ -661,6 +682,7 @@ adders[i].start(); } + Util.sleep(500); while(num_items < (adders.length * items)) { queue.remove(); num_items++; @@ -675,7 +697,7 @@ } } - assert num_dead == 10; + assert num_dead == 10 : "num_dead should have been 10 but was " + num_dead; queue.close(false); // will cause all threads still blocking on peek() to return } @@ -833,9 +855,9 @@ static class RemoveOneItemWithTimeout extends Thread { Long retval=null; - int rank=0; - long timeout=0; - Queue queue; + final int rank; + final long timeout; + final Queue queue; RemoveOneItemWithTimeout(int rank, long timeout, Queue queue) { super("RemoveOneItem thread #" + rank); @@ -846,19 +868,15 @@ } public void run() { - boolean finished=false; - while(!finished) { - try { - retval=(Long)queue.remove(timeout); - // System.out.println("Thread #" + rank + " removed element (" + retval + ")"); - finished=true; - } - catch(QueueClosedException closed) { - System.err.println("Thread #" + rank + ": queue was closed"); - finished=true; - } - catch(TimeoutException e) { - } + try { + retval=(Long)queue.removeWait(timeout); + System.out.println("Thread #" + rank + ": retrieved " + retval); + } + catch(QueueClosedException closed) { + System.out.println("Thread #" + rank + ": queue was closed"); + } + catch(TimeoutException e) { + System.out.println("Thread #" + rank + ": timeout occurred"); } } diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ReentrantLockTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ReentrantLockTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ReentrantLockTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ReentrantLockTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -12,7 +12,6 @@ /** * Tests the ReentrantLock * @author Bela Ban - * @version $Id: ReentrantLockTest.java,v 1.4 2008/04/08 12:41:58 belaban Exp $ */ @Test(groups=Global.FUNCTIONAL,sequential=true) public class ReentrantLockTest { diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/RequestOptionsTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/RequestOptionsTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/RequestOptionsTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/RequestOptionsTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,55 @@ +package org.jgroups.tests; + +import org.jgroups.Global; +import org.jgroups.JChannel; +import org.jgroups.blocks.*; +import org.jgroups.blocks.mux.MuxRpcDispatcher; +import org.jgroups.blocks.mux.NoMuxHandlerRspFilter; +import org.jgroups.util.Util; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + + +@Test(groups=Global.FUNCTIONAL,sequential=true) +public class RequestOptionsTest { + protected JChannel channel; + protected RequestOptions reqOpt = new RequestOptions(Request.GET_ALL, 5000); + protected static final String simple_props="SHARED_LOOPBACK:PING(timeout=1000):" + + "pbcast.NAKACK(log_discard_msgs=false;log_not_found_msgs=false)" + + ":UNICAST:pbcast.STABLE(stability_delay=200):pbcast.GMS:MFC:UFC:FRAG2"; + + + @BeforeMethod + protected void start() throws Exception { + channel=new JChannel(simple_props); + } + + protected void stop() throws Exception { + Util.close(channel); + } + + /** + * Tests https://issues.jboss.org/browse/JGRP-1369 + */ + public void testRequestOptionsChaining() throws Exception { + MuxRpcDispatcher muxRpc = new MuxRpcDispatcher((short) 1, channel, null, null, new Server()); + channel.connect("group"); + for(int i=0; i < 20; i++) + muxRpc.callRemoteMethods(null, new MethodCall(Server.class.getMethod("foo", null)), reqOpt); + + RspFilter filter=reqOpt.getRspFilter(); + int count=count(filter); + System.out.println("count=" + count); + assert count == 1; + } + + protected static int count(RspFilter filter) { + if(filter instanceof NoMuxHandlerRspFilter) + return 1 + count(((NoMuxHandlerRspFilter)filter).getFilter()); + return 0; + } + + static public class Server { + public static void foo() {System.out.println("Entering foo"); } + } +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ResponseCollectorTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ResponseCollectorTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ResponseCollectorTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ResponseCollectorTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,128 @@ + +package org.jgroups.tests; + + +import org.jgroups.Address; +import org.jgroups.Global; +import org.jgroups.util.ResponseCollector; +import org.jgroups.util.Util; +import org.testng.annotations.Test; + +import java.util.Map; + + +/** + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL,sequential=false) +public class ResponseCollectorTest { + static final Address a=Util.createRandomAddress(), b=Util.createRandomAddress(), c=Util.createRandomAddress(); + + + public static void testAdd() { + ResponseCollector coll=new ResponseCollector(a, b, c); + coll.add(a, 1); + System.out.println("coll = " + coll); + assert coll.size() == 3; + assert !coll.hasAllResponses(); + coll.add(c, 3); + coll.add(b, 2); + System.out.println("coll = " + coll); + assert coll.size() == 3; + assert coll.hasAllResponses(); + } + + public static void testAddNonExistentKeys() { + ResponseCollector coll=new ResponseCollector(a, b); + coll.add(a, 1); + System.out.println("coll = " + coll); + assert coll.size() == 2; + assert !coll.hasAllResponses(); + coll.add(c, 3); // will get dropped + coll.add(b, 2); + System.out.println("coll = " + coll); + assert coll.size() == 2; + assert coll.hasAllResponses(); + } + + + public static void testWaitForAllResponses() { + final ResponseCollector coll=new ResponseCollector(a, b, c); + boolean rc=coll.waitForAllResponses(500); + assert !rc; + + new Thread() { + public void run() { + coll.add(a, 1); + Util.sleep(500); + coll.add(b, 2); + coll.add(c, 3); + } + }.start(); + + rc=coll.waitForAllResponses(5000); + System.out.println("coll = " + coll); + assert rc; + assert coll.hasAllResponses(); + } + + public static void testWaitForAllResponsesAndTimeout() { + final ResponseCollector coll=new ResponseCollector(a, b, c); + + new Thread() { + public void run() { + coll.add(a, 1); + Util.sleep(1000); + coll.add(b, 2); + Util.sleep(1000); + coll.add(c, 3); + } + }.start(); + + boolean rc=coll.waitForAllResponses(400); + System.out.println("coll = " + coll); + assert !rc; + assert !coll.hasAllResponses() : "collector had all responses (not expected)"; + } + + public static void testWaitForAllResponsesAndReset() { + final ResponseCollector coll=new ResponseCollector(a, b, c); + + new Thread() { + public void run() { + Util.sleep(1000); + coll.add(a, 1); + coll.reset(); + } + }.start(); + + boolean rc=coll.waitForAllResponses(5000); + System.out.println("coll = " + coll); + assert rc; + assert coll.hasAllResponses(); + } + + + public static void testWaitForAllResponsesAndGetResults() throws InterruptedException { + final ResponseCollector coll=new ResponseCollector(a, b, c); + + coll.add(a, 1); coll.add(b, 2); coll.add(c, 3); + Map results=coll.getResults(); + System.out.println("results = " + results); + + Thread thread=new Thread() { + public void run() { + coll.reset(); + } + }; thread.start(); + + thread.join(); + System.out.println("results = " + results); + assert coll.size() == 0; + } + + + + + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/RetransmitTableTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/RetransmitTableTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/RetransmitTableTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/RetransmitTableTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,261 @@ +package org.jgroups.tests; + +import org.jgroups.Global; +import org.jgroups.Message; +import org.jgroups.util.RetransmitTable; +import org.jgroups.util.Util; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.List; + +/** Tests {@link org.jgroups.util.RetransmitTable} + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL,sequential=false) +public class RetransmitTableTest { + static final Message MSG=new Message(null, null, "test"); + + public static void testCreation() { + RetransmitTable table=new RetransmitTable(3, 10, 0); + int size=table.size(); + assert size == 0; + assert table.get(15) == null; + } + + + public static void testAddition() { + RetransmitTable table=new RetransmitTable(3, 10, 0); + addAndGet(table, 0, "0"); + addAndGet(table, 1, "1"); + addAndGet(table, 5, "5"); + addAndGet(table, 9, "9"); + addAndGet(table, 10, "10"); + addAndGet(table, 11, "11"); + addAndGet(table, 19, "19"); + addAndGet(table, 20, "20"); + addAndGet(table, 29, "29"); + System.out.println("table: " + table.dump()); + assert table.size() == 9; + assert table.size() == table.computeSize(); + assert table.capacity() == 30; + } + + + public static void testAdditionWithOffset() { + RetransmitTable table=new RetransmitTable(3, 10, 100); + addAndGet(table, 100, "100"); + addAndGet(table, 101, "101"); + addAndGet(table, 105, "105"); + addAndGet(table, 109, "109"); + addAndGet(table, 110, "110"); + addAndGet(table, 111, "111"); + addAndGet(table, 119, "119"); + addAndGet(table, 120, "120"); + addAndGet(table, 129, "129"); + System.out.println("table: " + table.dump()); + assert table.size() == 9; + assert table.capacity() == 30; + } + + + public static void testDuplicateAddition() { + RetransmitTable table=new RetransmitTable(3, 10, 0); + addAndGet(table, 0, "0"); + addAndGet(table, 1, "1"); + addAndGet(table, 5, "5"); + addAndGet(table, 9, "9"); + addAndGet(table, 10, "10"); + + assert !table.put(5, new Message()); + assert table.get(5).getObject().equals("5"); + assert table.size() == 5; + } + + + public static void testDumpMatrix() { + RetransmitTable table=new RetransmitTable(3, 10, 1); + long[] seqnos={1,3,5,7,9,12,14,16,18,20,21,22,23,24}; + for(long seqno: seqnos) + table.put(seqno, MSG); + System.out.println("matrix:\n" + table.dumpMatrix()); + } + + + public static void testMassAddition() { + RetransmitTable table=new RetransmitTable(3, 10, 0); + final int NUM_MSGS=10005; + final Message MSG=new Message(null, null, "hello world"); + for(int i=0; i < NUM_MSGS; i++) + table.put(i, MSG); + System.out.println("table = " + table); + assert table.size() == NUM_MSGS; + assert table.capacity() == 10010; + } + + public static void testResize() { + RetransmitTable table=new RetransmitTable(3, 10, 0); + assert table.capacity() == 30; + addAndGet(table, 30, "30"); + addAndGet(table, 35, "35"); + assert table.capacity() == 40; + addAndGet(table, 500, "500"); + assert table.capacity() == 510; + + addAndGet(table, 515, "515"); + assert table.capacity() == 520; + } + + public void testResizeWithPurge() { + RetransmitTable table=new RetransmitTable(3, 10, 0); + for(long i=1; i <= 100; i++) + addAndGet(table, i, "hello-" + i); + System.out.println("table: " + table); + + // now remove 60 messages + for(long i=1; i <= 60; i++) { + Message msg=table.remove(i); + assert msg.getObject().equals("hello-" + i); + } + System.out.println("table after removal of seqno 60: " + table); + + table.purge(50); + System.out.println("now triggering a resize() by addition of seqno=120"); + addAndGet(table, 120, "120"); + + } + + + public void testResizeWithPurgeAndGetOfNonExistingElement() { + RetransmitTable table=new RetransmitTable(3, 10, 0); + for(long i=0; i < 50; i++) + addAndGet(table, i, "hello-" + i); + System.out.println("table: " + table); + + // now remove 15 messages + for(long i=0; i <= 15; i++) { + Message msg=table.remove(i); + assert msg.getObject().equals("hello-" + i); + } + System.out.println("table after removal of seqno 15: " + table); + + table.purge(15); + System.out.println("now triggering a resize() by addition of seqno=55"); + addAndGet(table, 55, "hello-55"); + + // now we have elements 40-49 in row 1 and 55 in row 2: + List list=new ArrayList(20); + for(int i=16; i < 50; i++) + list.add("hello-" + i); + list.add("hello-55"); + + for(long i=table.getOffset(); i < table.capacity() + table.getOffset(); i++) { + Message msg=table.get(i); + if(msg != null) { + String message=(String)msg.getObject(); + System.out.println(i + ": " + message); + list.remove(message); + } + } + + System.out.println("table:\n" + table.dumpMatrix()); + assert list.isEmpty() : " list: " + Util.print(list); + } + + + public void testResizeWithPurge2() { + RetransmitTable table=new RetransmitTable(3, 10, 0); + for(long i=0; i < 50; i++) + addAndGet(table, i, "hello-" + i); + System.out.println("table = " + table); + assert table.size() == 50; + assert table.capacity() == 50; + assert table.getHighestPurged() == 0; + assert table.getHighest() == 49; + + table.purge(43); + addAndGet(table, 52, "hello-52"); + assert table.get(43) == null; + + for(long i=44; i < 50; i++) { + Message msg=table.get(i); + assert msg != null && msg.getObject().equals("hello-" + i); + } + + assert table.get(50) == null; + assert table.get(51) == null; + Message msg=table.get(52); + assert msg != null && msg.getObject().equals("hello-52"); + assert table.get(53) == null; + } + + + public static void testMove() { + RetransmitTable table=new RetransmitTable(3, 10, 0); + for(long i=0; i < 50; i++) + addAndGet(table, i, "hello-" + i); + table.purge(49); + assert table.isEmpty(); + addAndGet(table, 50, "50"); + assert table.size() == 1; + assert table.capacity() == 50; + } + + + public static void testPurge() { + RetransmitTable table=new RetransmitTable(5, 10, 0); + for(long seqno=0; seqno < 25; seqno++) + table.put(seqno, MSG); + + long[] seqnos={30,31,32,37,38,39, 40,41,42,47,48,49}; + for(long seqno: seqnos) + table.put(seqno, MSG); + + System.out.println("table (before remove):\n" + table.dump()); + for(long seqno=0; seqno <= 22; seqno++) + table.remove(seqno); + + System.out.println("\ntable (after remove 22, before purge):\n" + table.dump()); + table.purge(22); + System.out.println("\ntable: (after purge 22):\n" + table.dump()); + assert table.size() == 2 + seqnos.length; + } + + + public void testCompact() { + RetransmitTable table=new RetransmitTable(3, 10, 0); + for(long i=0; i < 80; i++) + addAndGet(table, i, "hello-" + i); + assert table.size() == 80; + table.purge(59); + assert table.size() == 20; + table.compact(); + assert table.size() == 20; + assert table.capacity() == 40; + } + + + public void testCompactWithAutomaticPurging() { + RetransmitTable table=new RetransmitTable(3, 10, 0); + table.setAutomaticPurging(true); + for(long i=0; i < 80; i++) + addAndGet(table, i, "hello-" + i); + assert table.size() == 80; + for(long i=0; i <= 59; i++) + table.remove(i); + + assert table.size() == 20; + table.compact(); + assert table.size() == 20; + assert table.capacity() == 40; + } + + + protected static void addAndGet(RetransmitTable table, long seqno, String message) { + boolean added=table.put(seqno, new Message(null, null, message)); + assert added; + Message msg=table.get(seqno); + assert msg != null && msg.getObject().equals(message); + } + +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/RetransmitterTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/RetransmitterTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/RetransmitterTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/RetransmitterTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,28 +1,34 @@ -// $Id: RetransmitterTest.java,v 1.4 2008/05/15 10:49:14 belaban Exp $ package org.jgroups.tests; import org.jgroups.Address; import org.jgroups.Global; -import org.jgroups.util.TimeScheduler; +import org.jgroups.stack.DefaultRetransmitter; import org.jgroups.stack.Retransmitter; import org.jgroups.stack.StaticInterval; +import org.jgroups.util.DefaultTimeScheduler; +import org.jgroups.util.TimeScheduler; +import org.jgroups.util.Util; import org.testng.Assert; -import org.testng.annotations.Test; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.BeforeMethod; import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; -@Test(groups=Global.FUNCTIONAL) +@Test(groups=Global.FUNCTIONAL,sequential=true) public class RetransmitterTest { - private final Address sender=new org.jgroups.stack.IpAddress(5555); + private final Address sender=Util.createRandomAddress(); private TimeScheduler timer; + private Retransmitter xmitter; + @BeforeMethod void initTimer() { - timer=new TimeScheduler(); + timer=new DefaultTimeScheduler(); + xmitter=new DefaultRetransmitter(sender, new MyXmitter(), timer); + xmitter.setRetransmitTimeouts(new StaticInterval(1000,2000,4000,8000)); + xmitter.reset(); } @AfterMethod @@ -31,8 +37,6 @@ } public void testNoEntry() { - Retransmitter xmitter=new Retransmitter(sender, new MyXmitter(), timer); - xmitter.setRetransmitTimeouts(new StaticInterval(1000,2000,4000,8000)); int size=xmitter.size(); System.out.println("xmitter: " + xmitter); Assert.assertEquals(0, size); @@ -40,8 +44,6 @@ public void testSingleEntry() { - Retransmitter xmitter=new Retransmitter(sender, new MyXmitter(), timer); - xmitter.setRetransmitTimeouts(new StaticInterval(1000,2000,4000,8000)); xmitter.add(1, 1); int size=xmitter.size(); System.out.println("xmitter: " + xmitter); @@ -50,8 +52,6 @@ public void testEntry() { - Retransmitter xmitter=new Retransmitter(sender, new MyXmitter(), timer); - xmitter.setRetransmitTimeouts(new StaticInterval(1000,2000,4000,8000)); xmitter.add(1, 10); int size=xmitter.size(); System.out.println("xmitter: " + xmitter); @@ -60,8 +60,6 @@ public void testMultipleEntries() { - Retransmitter xmitter=new Retransmitter(sender, new MyXmitter(), timer); - xmitter.setRetransmitTimeouts(new StaticInterval(1000,2000,4000,8000)); xmitter.add(1, 10); int size=xmitter.size(); System.out.println("xmitter: " + xmitter); diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/RspListTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/RspListTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/RspListTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/RspListTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,9 +2,9 @@ import org.jgroups.Address; import org.jgroups.Global; -import org.jgroups.stack.IpAddress; import org.jgroups.util.Rsp; import org.jgroups.util.RspList; +import org.jgroups.util.Util; import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; @@ -22,11 +22,11 @@ @BeforeMethod public void setUp() throws Exception { rl=new RspList(); - a1=new IpAddress(1111); - a2=new IpAddress(2222); - a3=new IpAddress(3333); - a4=new IpAddress(4444); - a5=new IpAddress(5555); + a1=Util.createRandomAddress(); + a2=Util.createRandomAddress(); + a3=Util.createRandomAddress(); + a4=Util.createRandomAddress(); + a5=Util.createRandomAddress(); rsp1=new Rsp(a1); rsp2=new Rsp(a2, true); rsp3=new Rsp(a3, "hello world"); @@ -46,7 +46,7 @@ public void testConstructor() { - Collection c=new LinkedList(); + Collection c=new LinkedList(); c.add(rsp1); c.add(rsp2); c.add(rsp3); RspList tmp=new RspList(c); Assert.assertEquals(c.size(), tmp.size()); @@ -89,7 +89,7 @@ public void testPut() { Rsp rsp; - rsp=rl.put(new IpAddress(6666), new Rsp(new IpAddress(6666), true)); + rsp=rl.put(Util.createRandomAddress(), new Rsp(Util.createRandomAddress(), true)); assert rsp == null; rsp=rl.put(a2, rsp2); Assert.assertEquals(rsp, rsp2); @@ -99,7 +99,7 @@ public void testRemove() { Rsp rsp; - rsp=rl.remove(new IpAddress(6666)); + rsp=rl.remove(Util.createRandomAddress()); assert rsp == null; rsp=rl.remove(a2); Assert.assertEquals(rsp, rsp2); @@ -129,9 +129,10 @@ public void testAddRsp() { - rl.addRsp(new IpAddress(6666), new Integer(322649)); + Address tmp=Util.createRandomAddress(); + rl.addRsp(tmp, new Integer(322649)); Assert.assertEquals(6, rl.size()); - Rsp rsp=rl.get(new IpAddress(6666)); + Rsp rsp=rl.get(tmp); assert rsp != null; assert rsp.wasReceived(); assert !(rsp.wasSuspected()); @@ -171,11 +172,12 @@ public void testElementAt() { Rsp rsp; - Set s=new HashSet(); - for(int i=0; i < rl.size(); i++) { - rsp=(Rsp)rl.elementAt(i); - s.add(rsp.getSender()); - } + Set
            s=new HashSet
            (); + s.addAll(rl.keySet()); +// for(int i=0; i < rl.size(); i++) { +// rsp=(Rsp)rl.elementAt(i); +// s.add(rsp.getSender()); +// } System.out.println("-- set is " + s); Assert.assertEquals(rl.size(), s.size()); } diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/SeqnoComparatorTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/SeqnoComparatorTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/SeqnoComparatorTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/SeqnoComparatorTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,78 @@ +package org.jgroups.tests; + +import org.testng.annotations.Test; +import org.jgroups.Global; +import org.jgroups.util.SeqnoComparator; +import org.jgroups.util.Seqno; +import org.jgroups.util.SeqnoRange; + +/** + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL, sequential=false) +public class SeqnoComparatorTest { + + static final SeqnoComparator comp=new SeqnoComparator(); + + + public static void testTwoSeqnos() { + Seqno s1=new Seqno(10), s2=new Seqno(10); + assert comp.compare(s1, s2) == 0; + s1=new Seqno(9); + assert comp.compare(s1, s2) == -1; + s2=new Seqno(5); + assert comp.compare(s1, s2) == 1; + } + + + public static void compareDummyWithSeqnoRange() { + Seqno s1=new Seqno(10, true), s2=new SeqnoRange(1, 100); + assert comp.compare(s1, s2) == 0; + s1=new Seqno(1, true); + assert comp.compare(s1, s2) == 0; + s1=new Seqno(100, true); + assert comp.compare(s1, s2) == 0; + + s1=new Seqno(0, true); + assert comp.compare(s1, s2) == -1; + + s1=new Seqno(101, true); + assert comp.compare(s1, s2) == 1; + } + + public static void compareDummyWithSeqno() { + Seqno s1=new Seqno(10, true), s2=new Seqno(10); + assert comp.compare(s1, s2) == 0; + + s1=new Seqno(9, true); + assert comp.compare(s1, s2) == -1; + s1=new Seqno(11, true); + assert comp.compare(s1, s2) == 1; + } + + public static void compareSeqnoRangeWithDummy() { + Seqno s1=new SeqnoRange(1, 100), s2=new Seqno(10, true); + assert comp.compare(s1, s2) == 0; + s2=new Seqno(1, true); + assert comp.compare(s1, s2) == 0; + s2=new Seqno(100, true); + assert comp.compare(s1, s2) == 0; + + s2=new Seqno(0, true); + assert comp.compare(s1, s2) == 1; + + s2=new Seqno(101, true); + assert comp.compare(s1, s2) == -1; + } + + + public static void compareSeqnoWithDummy() { + Seqno s1=new Seqno(10), s2=new Seqno(10, true); + assert comp.compare(s1, s2) == 0; + + s2=new Seqno(9, true); + assert comp.compare(s1, s2) == 1; + s2=new Seqno(11, true); + assert comp.compare(s1, s2) == -1; + } +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/SeqnoTableTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/SeqnoTableTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/SeqnoTableTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/SeqnoTableTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -4,11 +4,11 @@ import org.jgroups.Address; import org.jgroups.Global; -import org.jgroups.stack.IpAddress; import org.jgroups.util.SeqnoTable; +import org.jgroups.util.Util; import org.testng.Assert; -import org.testng.annotations.Test; import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; import java.net.UnknownHostException; @@ -20,7 +20,7 @@ @BeforeClass private static void init() throws UnknownHostException { - MBR=new IpAddress("127.0.0.1", 5555); + MBR=Util.createRandomAddress(); } diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/SeqnoTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/SeqnoTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/SeqnoTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/SeqnoTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,377 @@ +package org.jgroups.tests; + +import org.jgroups.Global; +import org.jgroups.util.*; +import org.testng.annotations.Test; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL, sequential=false) +public class SeqnoTest { + + public static void testConstructor() { + SeqnoRange range=new SeqnoRange(10, 10); + System.out.println(print(range)); + assert range.size() == 1; + assert range.getLow() == 10; + assert range.getHigh() == 10; + assert range.contains(10); + assert !range.contains(11); + + range=new SeqnoRange(10, 15); + System.out.println(print(range)); + assert range.size() == 6; + assert range.getLow() == 10; + assert range.getHigh() == 15; + assert range.contains(10); + assert range.contains(14); + } + + public static void testSetAndGetWith1Seqno() { + Seqno range=new SeqnoRange(10, 10); + assert range.getNumberOfMissingMessages() == 1; + assert range.getNumberOfReceivedMessages() == 0; + + range.set(10); + assert range.getNumberOfMissingMessages() == 0; + assert range.getNumberOfReceivedMessages() == 1; + + assert range.get(10); + + range.clear(10); + assert !range.get(10); + assert range.getNumberOfMissingMessages() == 1; + assert range.getNumberOfReceivedMessages() == 0; + } + + public static void testSetAndGetWith5Seqnos() { + SeqnoRange range=new SeqnoRange(10, 15); + System.out.println("range=" + print(range)); + + assert range.size() == 6; + assert range.getNumberOfMissingMessages() == 6; + assert range.getNumberOfReceivedMessages() == 0; + + range.set(10); + assert range.getNumberOfMissingMessages() == 5; + assert range.getNumberOfReceivedMessages() == 1; + + assert range.get(10); + + range.set(13); + assert range.size() == 6; + assert range.getNumberOfMissingMessages() == 4; + assert range.getNumberOfReceivedMessages() == 2; + + range.set(13); + assert range.size() == 6; + assert range.getNumberOfMissingMessages() == 4; + assert range.getNumberOfReceivedMessages() == 2; + + System.out.println("range=" + print(range)); + + Collection xmits=range.getMessagesToRetransmit(); + Collection cleared_bits=range.getBits(false); + + System.out.println("xmits = " + xmits); + System.out.println("cleared_bits = " + cleared_bits); + + assert xmits.equals(cleared_bits); + } + + public static void testGetBits() { + SeqnoRange range=new SeqnoRange(1, 100); + System.out.println("range = " + range); + assert range.size() == 100; + + Collection bits=range.getBits(false); + assert bits.size() == 1; + Range tmp=bits.iterator().next(); + assert tmp.low == 1 && tmp.high == 100; + + range.set(1,2); + assert range.size() == 100; + + bits=range.getBits(true); + assert bits != null && bits.size() == 1; // 1 range: [1-2] + tmp=bits.iterator().next(); + assert tmp.low == 1 && tmp.high == 2; + + for(int i=1; i < 100; i++) + range.set(i); + + bits=range.getBits(false); + assert bits.size() == 1; + tmp=bits.iterator().next(); + assert tmp.low == 100 && tmp.high == 100; + + for(int i=1; i <= range.size(); i++) + range.clear(i); + + for(int i=2; i <= 99; i++) + range.set(i); + + bits=range.getBits(true); + assert bits.size() == 1; + tmp=bits.iterator().next(); + assert tmp.low == 2 && tmp.high == 99; + + bits=range.getBits(false); + assert bits.size() == 2; + + tmp=bits.iterator().next(); + assert tmp.low == 1 && tmp.high == 1; + + Iterator it=bits.iterator(); + it.next(); + tmp=it.next(); + assert tmp.low == 100 && tmp.high == 100; + } + + + public static void testSet() { + SeqnoRange range=new SeqnoRange(10, 15); + range.set(11, 12, 13, 14); + System.out.println("range=" + print(range)); + assert range.size() == 6; + assert range.getNumberOfReceivedMessages() == 4; + assert range.getNumberOfMissingMessages() == 2; + Collection xmits=range.getMessagesToRetransmit(); + assert xmits.size() == 2; + Iterator it=xmits.iterator(); + Range r=it.next(); + assert r.low == 10 && r.high == 10; + r=it.next(); + assert r.low == 15 && r.high == 15; + + + range=new SeqnoRange(10, 15); + range.set(10,11,12,13,14); + System.out.println("range=" + print(range)); + assert range.size() == 6; + assert range.getNumberOfReceivedMessages() == 5; + assert range.getNumberOfMissingMessages() == 1; + xmits=range.getMessagesToRetransmit(); + assert xmits.size() == 1; + it=xmits.iterator(); + r=it.next(); + assert r.low == 15 && r.high == 15; + + range=new SeqnoRange(10, 15); + range.set(11,12,13,14,15); + System.out.println("range=" + print(range)); + assert range.size() == 6; + assert range.getNumberOfReceivedMessages() == 5; + assert range.getNumberOfMissingMessages() == 1; + xmits=range.getMessagesToRetransmit(); + assert xmits.size() == 1; + it=xmits.iterator(); + r=it.next(); + assert r.low == 10 && r.high == 10; + + range=new SeqnoRange(10, 15); + range.set(10,11,12,13,14,15); + System.out.println("range=" + print(range)); + assert range.size() == 6; + assert range.getNumberOfReceivedMessages() == 6; + assert range.getNumberOfMissingMessages() == 0; + xmits=range.getMessagesToRetransmit(); + assert xmits.isEmpty(); + + range=new SeqnoRange(10, 15); + range.set(11,12,14,15); + System.out.println("range=" + print(range)); + assert range.size() == 6; + assert range.getNumberOfReceivedMessages() == 4; + assert range.getNumberOfMissingMessages() == 2; + xmits=range.getMessagesToRetransmit(); + assert xmits.size() == 2; + it=xmits.iterator(); + r=it.next(); + assert r.low == 10 && r.high == 10; + r=it.next(); + assert r.low == 13 && r.high == 13; + + range.set(13); + assert range.getNumberOfReceivedMessages() == 5; + assert range.getNumberOfMissingMessages() == 1; + xmits=range.getMessagesToRetransmit(); + it=xmits.iterator(); + r=it.next(); + assert r.low == 10 && r.high == 10; + + range.set(10); + System.out.println("range=" + print(range)); + assert range.getNumberOfReceivedMessages() == 6; + assert range.getNumberOfMissingMessages() == 0; + xmits=range.getMessagesToRetransmit(); + assert xmits.isEmpty(); + } + + @Test(expectedExceptions=IllegalArgumentException.class) + public static void testSetOfInvalidIndex() { + SeqnoRange range=new SeqnoRange(10, 10); + range.set(9); + } + + + public static void testCompareTo() { + TreeMap map=new TreeMap(new SeqnoComparator()); + + Seqno[] ranges=new Seqno[]{new SeqnoRange(900,905), new Seqno(222), new SeqnoRange(700,800), new SeqnoRange(23,200)}; + + for(Seqno range: ranges) + map.put(range, range); + + System.out.println("map = " + map.keySet()); + assert map.size() == ranges.length; + + for(long num: new long[]{0, 1, 201, 202, 223, 1000}) { + checkNull(map, num); + } + + checkInRange(map, 23, 23, 200); + checkInRange(map, 100, 23, 200); + checkInRange(map, 200, 23, 200); + checkInRange(map, 222, 222, 222); + checkInRange(map, 750, 700, 800); + checkInRange(map, 905, 900, 905); + } + + + public static void testCompareTo2() { + TreeMap map=new TreeMap(new SeqnoComparator()); + + Seqno[] ranges=new Seqno[]{new SeqnoRange(900,905), new Seqno(550), new Seqno(222), + new SeqnoRange(700,800), new Seqno(650), new SeqnoRange(23,200)}; + + for(Seqno range: ranges) + map.put(range, range); + + System.out.println("map = " + map.keySet()); + assert map.size() == 6; + + for(long num: new long[]{0, 1, 201, 202, 223, 1000}) { + checkNull(map, num); + } + + checkInRange(map, 550, 550, 550); + checkInRange(map, 650, 650, 650); + + checkInRange(map, 23, 23, 200); + checkInRange(map, 100, 23, 200); + checkInRange(map, 200, 23, 200); + checkInRange(map, 222, 222, 222); + checkInRange(map, 750, 700, 800); + checkInRange(map, 905, 900, 905); + } + + + + public static void testLargeRange() { + SeqnoRange range=new SeqnoRange(0, 1500); + + Set sorted_set=new TreeSet(); + for(int i=0; i < 500; i++) { + int num=(int)Util.random(1499); + sorted_set.add(num); + } + + for(int num: sorted_set) + range.set(num); + + int num_set=sorted_set.size(); + System.out.println("set " + num_set + " bits"); + assert range.getNumberOfReceivedMessages() == num_set; + Collection missing=range.getMessagesToRetransmit(); + System.out.println("missing = " + missing); + } + + + public static void testRemovalFromTreeMap() { + Map map=new TreeMap(new SeqnoComparator()); + + Seqno[] ranges=new Seqno[]{new SeqnoRange(900,905), new Seqno(222), new Seqno(500), + new SeqnoRange(700,800), new Seqno(801), new SeqnoRange(23,200)}; + + for(Seqno range: ranges) + map.put(range, range); + + System.out.println("map = " + map.keySet()); + assert map.size() == ranges.length; + + for(Seqno r: ranges) { + Seqno range=map.get(r); + assert range != null; + assert range == r; // must point to the same object in memory + } + + for(Seqno r: ranges) { + Seqno range=map.remove(r); + assert range != null; + assert range == r; + } + + assert map.isEmpty(); + } + + + public static void testRemovalFromHashMap() { + Map map=new ConcurrentHashMap(); + + Seqno[] ranges=new Seqno[]{new SeqnoRange(900,905), new Seqno(222), new SeqnoRange(700,800), + new SeqnoRange(23,200), new Seqno(201), new Seqno(205)}; + + for(Seqno range: ranges) + map.put(range, range); + + System.out.println("map = " + map.keySet()); + assert map.size() == ranges.length; + + for(Seqno r: ranges) { + Seqno range=map.get(r); + assert range != null; + assert range == r; // must point to the same object in memory + } + + for(Seqno r: ranges) { + Seqno range=map.remove(r); + assert range != null; + assert range == r; + } + + assert map.isEmpty(); + } + + + private static void checkInRange(Map map, long seqno, long from, long to) { + Seqno val=map.get(new Seqno(seqno, true)); + System.out.println("seqno=" + seqno + ", val = " + val); + assert val != null; + assert val.contains(seqno); + assert val.getLow() == from; + if(val instanceof SeqnoRange) + assert ((SeqnoRange)val).getHigh() == to; + } + + private static void checkNull(Map map, long seqno) { + Seqno val=map.get(new Seqno(seqno, true)); + assert val == null; + } + + + private static String print(Seqno seqno) { + StringBuilder sb=new StringBuilder(); + sb.append(seqno.toString()); + sb.append(", size= " + seqno.size()); + if(seqno instanceof SeqnoRange) { + sb.append(", received=" + ((SeqnoRange)seqno).printBits(true) + " (" + seqno.getNumberOfReceivedMessages() + ")"); + sb.append(", missing=" + ((SeqnoRange)seqno).printBits(false) + " (" + seqno.getNumberOfMissingMessages() + ")"); + } + return sb.toString(); + } +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/SetPropertyTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/SetPropertyTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/SetPropertyTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/SetPropertyTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -11,7 +11,6 @@ /** * @author Bela Ban - * @version $Id: SetPropertyTest.java,v 1.2 2008/06/03 14:37:01 belaban Exp $ */ @Test(groups=Global.FUNCTIONAL) public class SetPropertyTest { diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/SimulatorTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/SimulatorTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/SimulatorTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/SimulatorTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,5 +1,4 @@ -//$Id: SimulatorTest.java,v 1.1 2008/10/23 16:59:39 rachmatowicz Exp $ package org.jgroups.tests; diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/SizeTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/SizeTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/SizeTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/SizeTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,31 +2,26 @@ package org.jgroups.tests; -import org.testng.annotations.*; import org.jgroups.*; import org.jgroups.blocks.RequestCorrelator; import org.jgroups.mux.MuxHeader; import org.jgroups.mux.ServiceInfo; import org.jgroups.protocols.*; import org.jgroups.protocols.pbcast.*; +import org.jgroups.stack.GossipData; import org.jgroups.stack.IpAddress; -import org.jgroups.util.Digest; -import org.jgroups.util.MutableDigest; -import org.jgroups.util.Streamable; -import org.jgroups.util.Util; +import org.jgroups.util.*; +import org.jgroups.util.UUID; import org.testng.Assert; +import org.testng.annotations.Test; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; +import java.io.*; import java.util.*; /** * Tests whether method size() of a header and its serialized size correspond * @author Bela Ban - * @version $Id: SizeTest.java,v 1.11 2008/10/10 14:53:30 belaban Exp $ */ @Test(groups=Global.FUNCTIONAL) public class SizeTest { @@ -39,21 +34,135 @@ public static void testPingHeader() throws Exception { _testSize(new PingHeader(PingHeader.GET_MBRS_REQ, "bla")); - _testSize(new PingHeader(PingHeader.GET_MBRS_RSP, new PingRsp())); - _testSize(new PingHeader(PingHeader.GET_MBRS_RSP, (PingRsp)null)); + _testSize(new PingHeader(PingHeader.GET_MBRS_RSP, new PingData())); + _testSize(new PingHeader(PingHeader.GET_MBRS_RSP, (PingData)null)); _testSize(new PingHeader(PingHeader.GET_MBRS_RSP, (String)null)); - _testSize(new PingHeader(PingHeader.GET_MBRS_RSP, new PingRsp(new IpAddress(4444), null, true))); - IpAddress self=new IpAddress("127.0.0.1", 5555); - PingRsp rsp=new PingRsp(self, self, true); + _testSize(new PingHeader(PingHeader.GET_MBRS_RSP, new PingData(Util.createRandomAddress(), null, true))); + Address self=Util.createRandomAddress(); + PingData rsp=new PingData(self, Util.createView(self, 1, self), true); _testSize(new PingHeader(PingHeader.GET_MBRS_RSP, rsp)); + rsp=new PingData(self, Util.createView(self, 1, self, Util.createRandomAddress(), Util.createRandomAddress()), true); + _testSize(new PingHeader(PingHeader.GET_MBRS_RSP, rsp)); + } + + + public static void testPingData() throws Exception { + PingData data; + final Address own=org.jgroups.util.UUID.randomUUID(); + final Address coord=org.jgroups.util.UUID.randomUUID(); + final PhysicalAddress physical_addr_1=new IpAddress("127.0.0.1", 7500); + final PhysicalAddress physical_addr_2=new IpAddress("192.168.1.5", 6000); + final PhysicalAddress physical_addr_3=new IpAddress("192.134.2.1", 6655); + final Address self=Util.createRandomAddress(); + + data=new PingData(null, null, false); + _testSize(data); + + data=new PingData(own, Util.createView(coord, 22, coord, Util.createRandomAddress()), false); + _testSize(data); + + data=new PingData(null, null, false, "node-1", null); + _testSize(data); + + data=new PingData(own, Util.createView(coord, 22, coord), false, "node-1", null); + _testSize(data); + + data=new PingData(own, Util.createView(coord, 22, coord), false, "node-1", new ArrayList(7)); + _testSize(data); + + data=new PingData(null, null, false, "node-1", new ArrayList(7)); + _testSize(data); + + List list=new ArrayList(); + list.add(physical_addr_1); + list.add(physical_addr_2); + list.add(physical_addr_3); + data=new PingData(null, null, false, "node-1", list); + _testSize(data); + + list.clear(); + list.add(new IpAddress("127.0.0.1", 7500)); + data=new PingData(null, null, false, "node-1", list); + _testSize(data); + + View view=Util.createView(coord, 322649, coord, own, UUID.randomUUID()); + data.setView(view); + _testSize(data); + + data=new PingData(self, Util.createView(self, 1, self), true, "logical-name", null); + _testSize(data); + } + + public static void testGossipData() throws Exception { + GossipData data; + final Address own=org.jgroups.util.UUID.randomUUID(); + final Address coord=org.jgroups.util.UUID.randomUUID(); + UUID.add((UUID)own, "own"); + UUID.add((UUID)coord, "coord"); + + final PhysicalAddress physical_addr_1=new IpAddress("127.0.0.1", 7500); + final PhysicalAddress physical_addr_2=new IpAddress("192.168.1.5", 6000); + final PhysicalAddress physical_addr_3=new IpAddress("192.134.2.1", 6655); + + _testSize(new GossipData()); + + data=new GossipData((byte)1); + _testSize(data); + + data=new GossipData((byte)1, "DemoCluster", own, (List
            )null, null); + _testSize(data); + + data=new GossipData((byte)1, "DemoCluster", own, Arrays.asList(own, coord), null); + _testSize(data); + + data=new GossipData((byte)1, "DemoCluster", own, Arrays.asList(own, coord), + Arrays.asList(physical_addr_1, physical_addr_2, physical_addr_3)); + _testSize(data); + + List list=new ArrayList(); + list.add(physical_addr_1); + list.add(physical_addr_2); + list.add(physical_addr_3); + data=new GossipData((byte)1, "DemoCluster", own, Arrays.asList(own, coord), list); + _testSize(data); + + data=new GossipData((byte)1, "demo", own, "logical_name", null); + _testSize(data); + + data=new GossipData((byte)1, "demo", own, new byte[]{'b', 'e', 'l', 'a'}); + _testSize(data); + + byte[] buffer=new byte[10]; + buffer[2]='B'; + buffer[3]='e'; + buffer[4]='l'; + buffer[5]='a'; + data=new GossipData((byte)1, "demo", own, buffer, 2, 4); + _testSize(data); } + public static void testDigest() throws Exception { + Address addr=Util.createRandomAddress(); + MutableDigest mutableDigest=new MutableDigest(2); + mutableDigest.add(addr, 100, 200, 205); + mutableDigest.add(Util.createRandomAddress(), 102, 104, 105); + _testSize(mutableDigest); + + Digest digest=new Digest(); + _testSize(digest); + + digest=new Digest(10); + _testSize(digest); + + digest=new Digest(Util.createRandomAddress(), 10, 45, 50); + _testSize(digest); + } + public static void testNakackHeader() throws Exception { - _testSize(new NakAckHeader(NakAckHeader.MSG, 322649)); - _testSize(new NakAckHeader(NakAckHeader.XMIT_REQ, 100, 104, new IpAddress("127.0.0.1", 5655))); - _testSize(new NakAckHeader(NakAckHeader.XMIT_RSP, 100, 104, new IpAddress("127.0.0.1", 5655))); - _testSize(new NakAckHeader(NakAckHeader.XMIT_RSP, 322649)); + _testSize(NakAckHeader.createMessageHeader(322649)); + _testSize(NakAckHeader.createXmitRequestHeader(100, 104, Util.createRandomAddress())); + _testSize(NakAckHeader.createXmitResponseHeader()); } @@ -63,7 +172,7 @@ IpAddress a1=new IpAddress("127.0.0.1", 5555); IpAddress a2=new IpAddress("127.0.0.1", 6666); - Vector suspects=new Vector(); + Vector
            suspects=new Vector
            (); suspects.add(a1); suspects.add(a2); hdr=new FD.FdHeader(FD.FdHeader.SUSPECT, suspects, a1); @@ -75,14 +184,14 @@ sockhdr=new FD_SOCK.FdHeader(FD_SOCK.FdHeader.SUSPECT, new IpAddress("127.0.0.1", 5555)); _testSize(sockhdr); - Set tmp=new HashSet(); + Set
            tmp=new HashSet
            (); tmp.add(a1); tmp.add(a2); sockhdr=new FD_SOCK.FdHeader(FD_SOCK.FdHeader.SUSPECT, tmp); _testSize(sockhdr); - Hashtable cache=new Hashtable(); + Map cache=new Hashtable(); cache.put(a1, a2); cache.put(a2, a1); sockhdr=new FD_SOCK.FdHeader(FD_SOCK.FdHeader.SUSPECT, cache); @@ -122,15 +231,27 @@ public static void testUnicastHeader() throws Exception { - UNICAST.UnicastHeader hdr=new UNICAST.UnicastHeader(UNICAST.UnicastHeader.DATA, 322649); + UNICAST.UnicastHeader hdr=UNICAST.UnicastHeader.createDataHeader(322649, (short)127, false); + _testSize(hdr); + + hdr=UNICAST.UnicastHeader.createDataHeader(322649, Short.MAX_VALUE, false); + _testSize(hdr); + + hdr=UNICAST.UnicastHeader.createDataHeader(322649, (short)(Short.MAX_VALUE -10), true); + _testSize(hdr); + + hdr=UNICAST.UnicastHeader.createAckHeader(322649); + _testSize(hdr); + + hdr=UNICAST.UnicastHeader.createSendFirstSeqnoHeader(); _testSize(hdr); } public static void testStableHeader() throws Exception { org.jgroups.protocols.pbcast.STABLE.StableHeader hdr; - IpAddress addr=new IpAddress("127.0.0.1", 5555); - Map map=new HashMap(); + Address addr=UUID.randomUUID(); + Map map=new HashMap(); map.put(addr, new Digest.Entry(100, 200, 205)); Digest digest=new Digest(map); hdr=new STABLE.StableHeader(STABLE.StableHeader.STABLE_GOSSIP, digest); @@ -165,7 +286,7 @@ public static void testAddressVector() throws Exception { - Vector v=new Vector(); + Vector
            v=new Vector
            (); _testSize(v); v.add(new IpAddress(1111)); _testSize(v); @@ -186,17 +307,41 @@ } + public static void testMergeId() throws Exception { + MergeId id=MergeId.create(UUID.randomUUID()); + System.out.println("id = " + id); + _testSize(id); + + id=MergeId.create(UUID.randomUUID()); + System.out.println("id = " + id); + _testSize(id); + + Address addr=UUID.randomUUID(); + id=MergeId.create(addr); + System.out.println("id = " + id); + _testSize(id); + + id=MergeId.create(addr); + System.out.println("id = " + id); + _testSize(id); + + id=MergeId.create(addr); + System.out.println("id = " + id); + _testSize(id); + } + + public static void testView() throws Exception { View v=new View(); _testSize(v); - ViewId vid=new ViewId(new IpAddress(1111), 322649); - Vector mbrs=new Vector(); + ViewId vid=new ViewId(UUID.randomUUID(), 322649); + Vector
            mbrs=new Vector
            (); v=new View(vid, mbrs); _testSize(v); - mbrs.add(new IpAddress(3333)); + mbrs.add(UUID.randomUUID()); _testSize(v); - mbrs.add(new IpAddress(1111)); + mbrs.add(UUID.randomUUID()); _testSize(v); } @@ -206,15 +351,15 @@ v.addPayload("name", "Bela Ban"); _testSize(v); - ViewId vid=new ViewId(new IpAddress(1111), 322649); - Vector mbrs=new Vector(); + ViewId vid=new ViewId(UUID.randomUUID(), 322649); + Vector
            mbrs=new Vector
            (); v=new View(vid, mbrs); v.addPayload("id", 322649); v.addPayload("name", "Michelle"); _testSize(v); - mbrs.add(new IpAddress(3333)); + mbrs.add(UUID.randomUUID()); _testSize(v); - mbrs.add(new IpAddress(1111)); + mbrs.add(UUID.randomUUID()); _testSize(v); } @@ -223,31 +368,33 @@ View v=new MergeView(); _testSize(v); - ViewId vid=new ViewId(new IpAddress(1111), 322649); - Vector mbrs=new Vector(); + ViewId vid=new ViewId(UUID.randomUUID(), 322649); + Vector
            mbrs=new Vector
            (); v=new MergeView(vid, mbrs, null); _testSize(v); - mbrs.add(new IpAddress(3333)); + mbrs.add(UUID.randomUUID()); _testSize(v); - mbrs.add(new IpAddress(1111)); + mbrs.add(UUID.randomUUID()); _testSize(v); } public static void testMergeView2() throws Exception { - Vector m1, m2 , m3, all, subgroups; + Vector
            m1, m2 , m3, all; + Vector subgroups; Address a,b,c,d,e,f; View v1, v2, v3, view_all; - a=new IpAddress(1000); - b=new IpAddress(2000); - c=new IpAddress(3000); - d=new IpAddress(4000); - e=new IpAddress(5000); - f=new IpAddress(6000); + a=Util.createRandomAddress(); + b=Util.createRandomAddress(); + c=Util.createRandomAddress(); + d=Util.createRandomAddress(); + e=Util.createRandomAddress(); + f=Util.createRandomAddress(); - m1=new Vector(); m2=new Vector(); m3=new Vector(); all=new Vector(); subgroups=new Vector(); + m1=new Vector
            (); m2=new Vector
            (); m3=new Vector
            (); all=new Vector
            (); + subgroups=new Vector(); m1.add(a); m1.add(b); m1.add(c); m2.add(d); m3.add(e); m3.add(f); @@ -268,7 +415,8 @@ public static void testMergeView3() throws Exception { - Vector m1, m2 , m3, all, subgroups; + Vector
            m1, m2 , m3, all; + Vector subgroups; Address a,b,c,d,e,f; View v1, v2, v3, v4, view_all; @@ -279,14 +427,15 @@ e=new IpAddress(5000); f=new IpAddress(6000); - m1=new Vector(); m2=new Vector(); m3=new Vector(); all=new Vector(); subgroups=new Vector(); + m1=new Vector
            (); m2=new Vector
            (); m3=new Vector
            (); all=new Vector
            (); + subgroups=new Vector(); m1.add(a); m1.add(b); m1.add(c); m2.add(d); m3.add(e); m3.add(f); all.add(a); all.add(b); all.add(c); all.add(d); all.add(e); all.add(f); v1=new View(a, 1, m1); - v2=new MergeView(d, 2, m2, new Vector()); + v2=new MergeView(d, 2, m2, new Vector()); v3=new View(e, 3, m3); v4=new MergeView(e, 4, m3, null); subgroups.add(v1); @@ -301,31 +450,10 @@ - public static void testViewSyncHeader() throws Exception { - Address creator=new IpAddress("localhost", 12345); - Vector members=new Vector(); - members.add(new IpAddress(5555)); - members.add(creator); - View view=new View(creator, 322649, members); - VIEW_SYNC.ViewSyncHeader hdr=new VIEW_SYNC.ViewSyncHeader(VIEW_SYNC.ViewSyncHeader.VIEW_SYNC, view); - _testSize(hdr); - - view=new MergeView(); - hdr=new VIEW_SYNC.ViewSyncHeader(VIEW_SYNC.ViewSyncHeader.VIEW_SYNC, view); - _testSize(hdr); - - Vector subgroups=new Vector(); - subgroups.add(view); - view=new MergeView(creator, 322649, members, subgroups); - hdr=new VIEW_SYNC.ViewSyncHeader(VIEW_SYNC.ViewSyncHeader.VIEW_SYNC, view); - _testSize(hdr); - } - - public static void testJoinRsp() throws Exception { JoinRsp rsp; - Vector members=new Vector(); + Vector
            members=new Vector
            (); members.add(new IpAddress(1111)); members.add(new IpAddress(2222)); @@ -343,22 +471,25 @@ public static void testGmsHeader() throws Exception { - IpAddress addr=new IpAddress("127.0.0.1", 5555); + Address addr=UUID.randomUUID(); GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.JOIN_REQ, addr); _testSize(hdr); - Vector members=new Vector(); + Vector
            members=new Vector
            (); members.add(addr); members.add(addr); View v=new View(addr, 33, members); hdr=new GMS.GmsHeader(GMS.GmsHeader.JOIN_RSP, v); _testSize(hdr); + Collection
            mbrs=new ArrayList
            (); + Collections.addAll(mbrs, UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()); + hdr=new GMS.GmsHeader(GMS.GmsHeader.MERGE_REQ, mbrs); } public static void testFCHeader() throws Exception { - FC.FcHeader hdr=new FC.FcHeader(FC.FcHeader.REPLENISH); + FcHeader hdr=new FcHeader(FcHeader.REPLENISH); _testSize(hdr); } @@ -375,6 +506,30 @@ } + public static void testStompHeader() throws Exception { + STOMP.StompHeader hdr=STOMP.StompHeader.createHeader(STOMP.StompHeader.Type.MESSAGE, + "destination", "/topics/chat", + "sender", UUID.randomUUID().toString()); + _testSize(hdr); + + hdr=STOMP.StompHeader.createHeader(STOMP.StompHeader.Type.ENDPOINT, "endpoint", "192.168.1.5:8787"); + _testSize(hdr); + } + + public static void testRelayHeader() throws Exception { + RELAY.RelayHeader hdr=RELAY.RelayHeader.create(RELAY.RelayHeader.Type.FORWARD); + _testSize(hdr); + + hdr=RELAY.RelayHeader.createDisseminateHeader(Util.createRandomAddress("A")); + _testSize(hdr); + + Map uuid_cache=new HashMap(); + uuid_cache.put(Util.createRandomAddress("A"), "A"); + uuid_cache.put(Util.createRandomAddress("B"), "B"); + uuid_cache.put(Util.createRandomAddress("B"), "B"); + // hdr=RELAY.RelayHeader.create(RELAY.RelayHeader.Type.UUIDS); + // _testSize(hdr); + } public static void testStateHeader() throws Exception { IpAddress addr=new IpAddress("127.0.0.1", 5555); @@ -436,6 +591,7 @@ } + public static void testIpAddressWithAdditionalData() throws Exception { IpAddress addr=new IpAddress(5555, false); addr.setAdditionalData("bela".getBytes()); @@ -443,21 +599,104 @@ } + public static void testWriteAddress() throws IOException, IllegalAccessException, InstantiationException { + Address uuid=UUID.randomUUID(); + _testWriteAddress(uuid); + + ((UUID)uuid).setAdditionalData("Bela Ban".getBytes()); + _testWriteAddress(uuid); + + Address addr=new IpAddress(7500); + _testWriteAddress(addr); + + addr=new IpAddress("127.0.0.1", 5678); + _testWriteAddress(addr); + + ((IpAddress)addr).setAdditionalData("Bela Ban".getBytes()); + _testWriteAddress(addr); + } + + private static void _testWriteAddress(Address addr) throws IOException, InstantiationException, IllegalAccessException { + int len=Util.size(addr); + ByteArrayOutputStream output=new ByteArrayOutputStream(); + DataOutputStream out=new DataOutputStream(output); + Util.writeAddress(addr, out); + out.flush(); + byte[] buf=output.toByteArray(); + out.close(); + + System.out.println("\nlen=" + len + ", serialized length=" + buf.length); + assert len == buf.length; + DataInputStream in=new DataInputStream(new ByteArrayInputStream(buf)); + Address new_addr=Util.readAddress(in); + System.out.println("old addr=" + addr + "\nnew addr=" + new_addr); + assert addr.equals(new_addr); + } + + + + public static void testWriteAddresses() throws IOException, IllegalAccessException, InstantiationException { + List
            list=new ArrayList
            (); + for(int i=0; i < 3; i++) + list.add(UUID.randomUUID()); + _testWriteAddresses(list); + + list.clear(); + list.add(new IpAddress(7500)); + list.add(new IpAddress("192.168.1.5", 4444)); + list.add(new IpAddress("127.0.0.1", 5674)); + _testWriteAddresses(list); + } + + private static void _testWriteAddresses(List
            list) throws IOException, InstantiationException, IllegalAccessException { + long len=Util.size(list); + ByteArrayOutputStream output=new ByteArrayOutputStream(); + DataOutputStream out=new DataOutputStream(output); + Util.writeAddresses(list, out); + out.flush(); + byte[] buf=output.toByteArray(); + out.close(); + + System.out.println("\nlen=" + len + ", serialized length=" + buf.length); + assert len == buf.length; + DataInputStream in=new DataInputStream(new ByteArrayInputStream(buf)); + Collection new_list=Util.readAddresses(in, ArrayList.class); + System.out.println("old list=" + list + "\nnew list=" + new_list); + assert list.equals(new_list); + } + + + + public static void testUUID() throws Exception { + org.jgroups.util.UUID uuid=org.jgroups.util.UUID.randomUUID(); + System.out.println("uuid = " + uuid); + _testSize(uuid); + + uuid=org.jgroups.util.UUID.randomUUID(); + byte[] buf=Util.streamableToByteBuffer(uuid); + org.jgroups.util.UUID uuid2=(org.jgroups.util.UUID)Util.streamableFromByteBuffer(org.jgroups.util.UUID.class, buf); + System.out.println("uuid: " + uuid); + System.out.println("uuid2: " + uuid2); + assert uuid.equals(uuid2); + + int hash1=uuid.hashCode(), hash2=uuid2.hashCode(); + System.out.println("hash 1: " + hash1); + System.out.println("hash 2: " + hash2); + assert hash1 == hash2; + + uuid.setAdditionalData("bela ban".getBytes()); + _testSize(uuid); + } + + public static void testRequestCorrelatorHeader() throws Exception { RequestCorrelator.Header hdr; - hdr=new RequestCorrelator.Header(RequestCorrelator.Header.REQ, 322649, false, "HelloWorld"); - _testSize(hdr); - - hdr=new RequestCorrelator.Header(RequestCorrelator.Header.RSP, 322649, true, "bla"); - java.util.List l=new LinkedList(); - l.add(new IpAddress(1111)); - l.add(new IpAddress(2222)); - hdr.dest_mbrs=l; + hdr=new RequestCorrelator.Header(RequestCorrelator.Header.REQ, 322649, false, (short)1000); _testSize(hdr); - hdr=new RequestCorrelator.Header(RequestCorrelator.Header.RSP, 322649, true, "bla"); + hdr=new RequestCorrelator.Header(RequestCorrelator.Header.RSP, 322649, true, (short)356); ByteArrayOutputStream output=new ByteArrayOutputStream(); DataOutputStream out=new DataOutputStream(output); @@ -475,7 +714,29 @@ Assert.assertEquals(322649, hdr.id); assert hdr.rsp_expected; - Assert.assertEquals("bla", hdr.corrName); + Assert.assertEquals((short)356, hdr.corrId); + Assert.assertEquals(RequestCorrelator.Header.RSP, hdr.type); + + + hdr=new RequestCorrelator.Header(RequestCorrelator.Header.RSP, 322649, true, (short)356); + + output=new ByteArrayOutputStream(); + out=new DataOutputStream(output); + hdr.writeTo(out); + out.flush(); + + buf=output.toByteArray(); + out.close(); + + input=new ByteArrayInputStream(buf); + in=new DataInputStream(input); + + hdr=new RequestCorrelator.Header(); + hdr.readFrom(in); + + Assert.assertEquals(322649, hdr.id); + assert hdr.rsp_expected; + Assert.assertEquals(356, hdr.corrId); Assert.assertEquals(RequestCorrelator.Header.RSP, hdr.type); } @@ -502,36 +763,30 @@ } - private static void _testSize(Header hdr) throws Exception { - long size=hdr.size(); - byte[] serialized_form=Util.streamableToByteBuffer((Streamable)hdr); - System.out.println("size=" + size + ", serialized size=" + serialized_form.length); - Assert.assertEquals(serialized_form.length, size); + private static void _testSize(Digest digest) throws Exception { + long len=digest.serializedSize(); + byte[] serialized_form=Util.streamableToByteBuffer(digest); + System.out.println("digest = " + digest); + System.out.println("size=" + len + ", serialized size=" + serialized_form.length); + assert len == serialized_form.length; } - - private static void _testSize(VIEW_SYNC.ViewSyncHeader hdr) throws Exception { + private static void _testSize(Header hdr) throws Exception { long size=hdr.size(); byte[] serialized_form=Util.streamableToByteBuffer(hdr); System.out.println("size=" + size + ", serialized size=" + serialized_form.length); Assert.assertEquals(serialized_form.length, size); + } + - VIEW_SYNC.ViewSyncHeader hdr2=(VIEW_SYNC.ViewSyncHeader)Util.streamableFromByteBuffer(VIEW_SYNC.ViewSyncHeader.class, serialized_form); - int my_type=hdr.getType(), other_type=hdr2.getType(); - View my_view=hdr.getView(), other_view=hdr2.getView(); - System.out.println("my_type=" + my_type + ", other_type=" + other_type); - System.out.println("my_view=" + my_view + ", other_view=" + other_view); - Assert.assertEquals(my_type, other_type); - Assert.assertEquals(my_view, other_view); - } - private static void _testSize(Address addr) throws Exception { + private static void _testSize(Address addr) throws Exception { long size=addr.size(); byte[] serialized_form=Util.streamableToByteBuffer(addr); System.out.println("size=" + size + ", serialized size=" + serialized_form.length); - Assert.assertEquals(serialized_form.length, size); + Assert.assertEquals(serialized_form.length, size); } @@ -542,6 +797,13 @@ Assert.assertEquals(serialized_form.length, size); } + private static void _testSize(MergeId id) throws Exception { + long size=id.size(); + byte[] serialized_form=Util.streamableToByteBuffer(id); + System.out.println("size=" + size + ", serialized size=" + serialized_form.length); + assert serialized_form.length == size; + } + private static void _testSize(View v) throws Exception { long size=v.serializedSize(); byte[] serialized_form=Util.streamableToByteBuffer(v); @@ -549,7 +811,7 @@ Assert.assertEquals(serialized_form.length, size); } - private static void _testSize(Collection coll) throws Exception { + private static void _testSize(Collection
            coll) throws Exception { long size=Util.size(coll); byte[] serialized_form=Util.collectionToByteBuffer(coll); System.out.println("size=" + size + ", serialized size=" + serialized_form.length); @@ -563,6 +825,22 @@ Assert.assertEquals(serialized_form.length, size); } + private static void _testSize(PingData data) throws Exception { + System.out.println("\ndata: " + data); + long size=data.size(); + byte[] serialized_form=Util.streamableToByteBuffer(data); + System.out.println("size=" + size + ", serialized size=" + serialized_form.length); + assert serialized_form.length == size : "serialized length=" + serialized_form.length + ", size=" + size; + } + + private static void _testSize(GossipData data) throws Exception { + System.out.println("\ndata: " + data); + long size=data.size(); + byte[] serialized_form=Util.streamableToByteBuffer(data); + System.out.println("size=" + size + ", serialized size=" + serialized_form.length); + assert serialized_form.length == size : "serialized length=" + serialized_form.length + ", size=" + size; + } + private static void _testSize(ServiceInfo si) throws Exception { long size=si.size(); byte[] serialized_form=Util.streamableToByteBuffer(si); diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/StreamableTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/StreamableTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/StreamableTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/StreamableTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -2,14 +2,13 @@ package org.jgroups.tests; -import org.testng.annotations.*; import org.jgroups.*; -import org.jgroups.protocols.PingHeader; -import org.jgroups.protocols.PingRsp; -import org.jgroups.protocols.TpHeader; -import org.jgroups.stack.IpAddress; +import org.jgroups.conf.ClassConfigurator; +import org.jgroups.protocols.*; +import org.jgroups.util.UUID; import org.jgroups.util.Util; import org.testng.Assert; +import org.testng.annotations.Test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -21,6 +20,9 @@ @Test(groups=Global.FUNCTIONAL) public class StreamableTest { + static final short PING_ID=100; + static final short UDP_ID=101; + public static void testStreamable() throws Exception { byte[] buf={'b', 'e', 'l', 'a', 'b', 'a', 'n'}; byte[] tmp; @@ -102,39 +104,33 @@ public static void testNonNullAddress() throws Exception { - Address dest, src; - dest=new IpAddress("228.1.2.3", 5555); - src=new IpAddress("127.0.0.1", 6666); - Message msg=new Message(dest, src, "Hello world".getBytes()); - stream(msg); + stream(new Message(null, UUID.randomUUID(), "Hello world".getBytes())); } public static void testHeaders() throws Exception { - Address dest, src; - dest=new IpAddress("228.1.2.3", 5555); - src=new IpAddress("127.0.0.1", 6666); + Address dest=UUID.randomUUID(); + Address src=UUID.randomUUID(); Message msg=new Message(dest, src, "Hello world".getBytes()); - PingHeader hdr=new PingHeader(PingHeader.GET_MBRS_REQ, new PingRsp(src, src, true)); - msg.putHeader("ping-header", hdr); + PingHeader hdr=new PingHeader(PingHeader.GET_MBRS_REQ, new PingData(src, Util.createView(src, 1, src), true)); + msg.putHeader(PING_ID, hdr); TpHeader udp_hdr=new TpHeader("bla"); - msg.putHeader("udp-header", udp_hdr); + msg.putHeader(UDP_ID, udp_hdr); stream(msg); } public static void testAdditionalData() throws Exception { - IpAddress dest, src; - dest=new IpAddress("228.1.2.3", 5555); + UUID dest=UUID.randomUUID(); dest.setAdditionalData("foo".getBytes()); - src=new IpAddress("127.0.0.1", 6666); + UUID src=UUID.randomUUID(); src.setAdditionalData("foobar".getBytes()); Message msg=new Message(dest, src, "Hello world".getBytes()); - PingHeader hdr=new PingHeader(PingHeader.GET_MBRS_REQ, new PingRsp(src, src, false)); - msg.putHeader("ping-header", hdr); + PingHeader hdr=new PingHeader(PingHeader.GET_MBRS_REQ, new PingData(src, Util.createView(src, 1, src), false)); + msg.putHeader(PING_ID, hdr); TpHeader udp_hdr=new TpHeader("bla"); - msg.putHeader("udp-header", udp_hdr); + msg.putHeader(UDP_ID, udp_hdr); stream(msg); } @@ -146,12 +142,12 @@ Address a,b,c,d,e,f; View v1, v2, v3, v4, v5, view_all; - a=new IpAddress(1000); - b=new IpAddress(2000); - c=new IpAddress(3000); - d=new IpAddress(4000); - e=new IpAddress(5000); - f=new IpAddress(6000); + a=UUID.randomUUID(); + b=UUID.randomUUID(); + c=UUID.randomUUID(); + d=UUID.randomUUID(); + e=UUID.randomUUID(); + f=UUID.randomUUID(); tmp_m1=new Vector(); tmp_m2=new Vector(); m3=new Vector(); all=new Vector(); subgroups=new Vector(); tmp_m1.add(a); tmp_m1.add(b); tmp_m1.add(c); @@ -198,7 +194,7 @@ int length, bufLength; byte[] tmp; Message msg2; - Address src; + Address src, dest=msg.getDest(); int num_headers=getNumHeaders(msg); length=msg.getLength(); @@ -222,8 +218,7 @@ Assert.assertEquals(length, msg2.getLength()); Assert.assertEquals(bufLength, getBufLength(msg2)); - // assertTrue(match(dest, msg2.getDest())); - assert msg2.getDest() == null; + assert match(dest, msg2.getDest()); assert match(src, msg2.getSrc()); Assert.assertEquals(num_headers, getNumHeaders(msg2)); } diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/TimeSchedulerTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/TimeSchedulerTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/TimeSchedulerTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/TimeSchedulerTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,46 +1,44 @@ package org.jgroups.tests; -import org.jgroups.TimeoutException; import org.jgroups.Global; +import org.jgroups.TimeoutException; import org.jgroups.stack.Interval; import org.jgroups.stack.StaticInterval; +import org.jgroups.util.DefaultTimeScheduler; import org.jgroups.util.Promise; import org.jgroups.util.TimeScheduler; import org.jgroups.util.Util; import org.testng.Assert; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import java.util.Date; -import java.util.Map; -import java.util.List; -import java.util.LinkedList; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; -import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; /** - * - * Move to broken group since it is heavily dependent on sleeps which lead to transient failures - * depending on the load of a test machine. - * - * - * * Test cases for TimeScheduler * @author Bela Ban - * @version $Id: TimeSchedulerTest.java,v 1.10 2008/10/07 12:42:19 vlada Exp $ */ -@Test(groups="broken") +@Test(groups=Global.TIME_SENSITIVE,dataProvider="createTimer",sequential=true) public class TimeSchedulerTest { static final int NUM_MSGS=1000; static long[] xmit_timeouts={1000, 2000, 4000, 8000}; static double PERCENTAGE_OFF=0.3; // how much can expected xmit_timeout and real timeout differ to still be okay ? - public void testCancel() throws InterruptedException { - TimeScheduler timer=new TimeScheduler(); + @DataProvider(name="createTimer") + Object[][] createTimer() { + return Util.createTimer(); + } + + + @Test(dataProvider="createTimer") + public void testCancel(TimeScheduler timer) throws InterruptedException { for(int i=0; i < 10; i++) timer.scheduleWithDynamicInterval(new OneTimeTask(1000)); assert timer.size() == 10; @@ -49,10 +47,50 @@ } - public void testTaskCancellationBeforeTaskHasRun() throws InterruptedException { + /** + * Tests creating many tasks at the same time and then cancelling every second task. Asserts that not all tasks are cancelled; + * this was the case in an early implementation of {@link org.jgroups.util.TimeScheduler2}. + * @param timer + */ + @Test(dataProvider="createTimer") + public void testSchedulingTasksThenCancellingEverySecondTask(TimeScheduler timer) { + final int NUM=20; + + Future[] futures=new Future[NUM]; + + try { + for(int i=0; i < NUM; i++) { + futures[i]=timer.schedule(new MyRunnable(i), 1000, TimeUnit.MILLISECONDS); + if(i % 2 == 0) + futures[i].cancel(false); + } + + Util.sleep(3000); + + for(int i=0; i < NUM; i++) { + Future future=futures[i]; + System.out.println("[" + i + "] done=" + future.isDone() + ", cancelled=" + future.isCancelled()); + } + + for(int i=0; i < NUM; i++) { + Future future=futures[i]; + assert future.isDone(); + if(i % 2 == 0) + assert future.isCancelled(); + else + assert !future.isCancelled(); + } + } + finally { + timer.stop(); + } + } + + + @Test(dataProvider="createTimer") + public void testTaskCancellationBeforeTaskHasRun(TimeScheduler timer) throws InterruptedException { Future future; StressTask task=new StressTask(); - TimeScheduler timer=new TimeScheduler(); try { future=timer.scheduleWithDynamicInterval(task); assert timer.size() == 1; @@ -62,7 +100,10 @@ int num_executions=task.getNumExecutions(); System.out.println("number of task executions=" + num_executions); assert num_executions ==0 : "task should never have executed as it was cancelled before execution"; - timer.purge(); // removes cancelled tasks + if(timer instanceof DefaultTimeScheduler) + ((DefaultTimeScheduler)timer).purge(); // removes cancelled tasks + else + Util.sleep(1000); assert timer.size() == 0; } finally { @@ -70,11 +111,11 @@ } } - - public void testTaskCancellationAfterHasRun() throws InterruptedException { + + @Test(dataProvider="createTimer") + public void testTaskCancellationAfterHasRun(TimeScheduler timer) throws InterruptedException { Future future; StressTask task=new StressTask(); - TimeScheduler timer=new TimeScheduler(); try { future=timer.scheduleWithDynamicInterval(task); assert timer.size() == 1; @@ -86,9 +127,12 @@ int num_executions=task.getNumExecutions(); System.out.println("number of task executions=" + num_executions); - assert num_executions >= 1 : "task should have executed at least 1 time, as it was cancelled after 200ms"; - timer.purge(); // removes cancelled tasks - assert timer.size() == 0; + assert num_executions >= 1 : "task should have executed at least 1 time, as it was cancelled after 500ms"; + if(timer instanceof DefaultTimeScheduler) + ((DefaultTimeScheduler)timer).purge(); // removes cancelled tasks + else + Util.sleep(1000); + assert timer.size() == 0 : " timer size should be 0, but is " + size; } finally { timer.stop(); @@ -96,14 +140,13 @@ } - - public void testRepeatingTask() throws InterruptedException { + @Test(dataProvider="createTimer") + public void testRepeatingTask(TimeScheduler timer) throws InterruptedException { Future future; RepeatingTask task=new RepeatingTask(300); - TimeScheduler timer=new TimeScheduler(); try { - future=timer.scheduleWithDynamicInterval(task, false); - Util.sleep(3000); + future=timer.scheduleAtFixedRate(task, 0, 300, TimeUnit.MILLISECONDS); + Util.sleep(3200); System.out.println("<<< cancelling task"); future.cancel(true); @@ -119,22 +162,22 @@ } } - private String printExecutionTimes(RepeatingTask task) { + private static String printExecutionTimes(RepeatingTask task) { StringBuilder sb=new StringBuilder(); List times=task.getExecutionTimes(); - long base=times.get(0); + if(times.isEmpty()) + return "[]"; int cnt=1; for(Long time: times) { - sb.append("#" + cnt++ + ": ").append(time - base).append("\n"); + sb.append("#" + cnt++ + ": ").append(time).append("\n"); } return sb.toString(); } - - public void testStress() throws InterruptedException { + @Test(dataProvider="createTimer") + public void testStress(TimeScheduler timer) throws InterruptedException { StressTask t; - TimeScheduler timer=new TimeScheduler(); final int NUM_A=500, NUM_B=1000; int cnt=0, print=NUM_A * NUM_B / 10; try { @@ -166,13 +209,11 @@ } - - public void testDynamicTask() throws InterruptedException { + @Test(dataProvider="createTimer") + public void testDynamicTask(TimeScheduler timer) throws InterruptedException { TimeScheduler.Task task=new DynamicTask(); - TimeScheduler timer=new TimeScheduler(); try { - ScheduledFuture future=timer.scheduleWithDynamicInterval(task); - Assert.assertEquals(1, timer.getQueue().size()); + Future future=timer.scheduleWithDynamicInterval(task); assert !(future.isCancelled()); assert !(future.isDone()); @@ -191,12 +232,11 @@ } - - public void testDynamicTaskCancel() throws InterruptedException { - TimeScheduler timer=new TimeScheduler(); + @Test(dataProvider="createTimer") + public void testDynamicTaskCancel(TimeScheduler timer) throws InterruptedException { try { TimeScheduler.Task task=new DynamicTask(); - ScheduledFuture future=timer.scheduleWithDynamicInterval(task); + Future future=timer.scheduleWithDynamicInterval(task); assert !(future.isCancelled()); assert !(future.isDone()); @@ -205,13 +245,11 @@ assert !(future.isCancelled()); assert !(future.isDone()); - boolean success=future.cancel(true); - assert success; + assert future.cancel(true); assert future.isCancelled(); assert future.isDone(); - success=future.cancel(true); - assert success; + assert future.cancel(true) == false; } finally { timer.stop(); @@ -219,11 +257,11 @@ } - public void testIsDone() throws InterruptedException { - TimeScheduler timer=new TimeScheduler(); + @Test(dataProvider="createTimer") + public void testIsDone(TimeScheduler timer) throws InterruptedException { try { TimeScheduler.Task task=new DynamicTask(); - ScheduledFuture future=timer.scheduleWithDynamicInterval(task); + Future future=timer.scheduleWithDynamicInterval(task); assert !(future.isCancelled()); assert !(future.isDone()); @@ -241,25 +279,24 @@ } } - public void testIsDone2() throws InterruptedException { - TimeScheduler timer=new TimeScheduler(); + + @Test(dataProvider="createTimer") + public void testIsDone2(TimeScheduler timer) throws InterruptedException { try { TimeScheduler.Task task=new DynamicTask(new long[]{1000,2000,-1}); - ScheduledFuture future=timer.scheduleWithDynamicInterval(task); + Future future=timer.scheduleWithDynamicInterval(task); - assert !(future.isCancelled()); - assert !(future.isDone()); + assert !future.isCancelled(); + assert !future.isDone(); Thread.sleep(3500); - assert !(future.isCancelled()); + assert !future.isCancelled(); assert future.isDone(); - boolean success=future.cancel(true); - if(success) - assert future.isCancelled(); - else - assert !(future.isCancelled()); + assert !future.cancel(true); // cancel() fails because the task is done + assert future.isCancelled(); assert future.isDone(); + assert !future.cancel(true); } finally { timer.stop(); @@ -267,20 +304,17 @@ } - public void testIsDone3() throws InterruptedException { - TimeScheduler timer=new TimeScheduler(); + @Test(dataProvider="createTimer") + public void testIsDone3(TimeScheduler timer) throws InterruptedException { try { TimeScheduler.Task task=new DynamicTask(new long[]{-1}); - ScheduledFuture future=timer.scheduleWithDynamicInterval(task); + Future future=timer.scheduleWithDynamicInterval(task); Thread.sleep(100); - assert !(future.isCancelled()); + assert !future.isCancelled(); assert future.isDone(); - boolean success=future.cancel(true); - if(success) - assert future.isCancelled(); - else - assert !(future.isCancelled()); + assert !future.cancel(true); + assert future.isCancelled(); assert future.isDone(); } finally { @@ -289,8 +323,8 @@ } - public void testImmediateExecution() throws InterruptedException { - TimeScheduler timer=new TimeScheduler(); + @Test(dataProvider="createTimer") + public void testImmediateExecution(TimeScheduler timer) throws InterruptedException { try { Promise p=new Promise(); ImmediateTask task=new ImmediateTask(p); @@ -311,9 +345,9 @@ } - public void test2Tasks() throws InterruptedException { + @Test(dataProvider="createTimer") + public void test2Tasks(TimeScheduler timer) throws InterruptedException { int size; - TimeScheduler timer=new TimeScheduler(); try { System.out.println(System.currentTimeMillis() + ": adding task"); timer.schedule(new MyTask(), 500, TimeUnit.MILLISECONDS); @@ -353,48 +387,120 @@ - /** - * Tests whether retransmits are called at correct times for 1000 messages. A retransmit should not be - * more than 30% earlier or later than the scheduled retransmission time - */ - public void testRetransmits() throws InterruptedException { - Entry entry; - int num_non_correct_entries=0; - Map msgs=new ConcurrentHashMap(); // keys=seqnos (Long), values=Entries - TimeScheduler timer=new TimeScheduler(); - - try { - // 1. Add NUM_MSGS messages: - System.out.println("-- adding " + NUM_MSGS + " messages:"); - for(long i=0; i < NUM_MSGS; i++) { - entry=new Entry(i); - msgs.put(new Long(i), entry); - timer.scheduleWithDynamicInterval(entry); - } - System.out.println("-- done"); - - // 2. Wait for at least 4 xmits/msg: total of 1000 + 2000 + 4000 + 8000ms = 15000ms; wait for 20000ms - System.out.println("-- waiting for all retransmits"); - - // 3. Check whether all Entries have correct retransmission times - long end_time=System.currentTimeMillis() + 20000L, start=System.currentTimeMillis(); - while(System.currentTimeMillis() < end_time) { - num_non_correct_entries=check(msgs, false); - if(num_non_correct_entries == 0) - break; - Util.sleep(1000); - } - System.out.println("-- waited for " + (System.currentTimeMillis() - start) + " ms"); - - num_non_correct_entries=check(msgs, true); - if(num_non_correct_entries > 0) - System.err.println("Number of incorrect retransmission timeouts: " + num_non_correct_entries); - assert num_non_correct_entries == 0: "expected 0 incorrect entries but got " + num_non_correct_entries; - } - finally { - timer.stop(); - } - } + /** + * Tests whether retransmits are called at correct times for 1000 messages. A retransmit should not be + * more than 30% earlier or later than the scheduled retransmission time + */ + @Test(dataProvider="createTimer") + public void testRetransmits(TimeScheduler timer) throws InterruptedException { + Entry entry; + int num_non_correct_entries=0; + Map msgs=new ConcurrentHashMap(); // keys=seqnos (Long), values=Entries + + try { + // 1. Add NUM_MSGS messages: + System.out.println("-- adding " + NUM_MSGS + " messages:"); + for(long i=0; i < NUM_MSGS; i++) { + entry=new Entry(i); + msgs.put(new Long(i), entry); + timer.scheduleWithDynamicInterval(entry); + } + System.out.println("-- done"); + + // 2. Wait for at least 4 xmits/msg: total of 1000 + 2000 + 4000 + 8000ms = 15000ms; wait for 20000ms + System.out.println("-- waiting for all retransmits"); + + // 3. Check whether all Entries have correct retransmission times + long end_time=System.currentTimeMillis() + 20000L, start=System.currentTimeMillis(); + while(System.currentTimeMillis() < end_time) { + num_non_correct_entries=check(msgs, false); + if(num_non_correct_entries == 0) + break; + Util.sleep(1000); + } + System.out.println("-- waited for " + (System.currentTimeMillis() - start) + " ms"); + + num_non_correct_entries=check(msgs, true); + if(num_non_correct_entries > 0) + System.err.println("Number of incorrect retransmission timeouts: " + num_non_correct_entries); + assert num_non_correct_entries == 0: "expected 0 incorrect entries but got " + num_non_correct_entries; + } + finally { + timer.stop(); + } + } + + + /** + * Tests adding a task and - before it executes - adding tasks which are to be executed sooner + * @param timer + */ + @Test(dataProvider="createTimer") + public void testTasksPreemptingEachOther(TimeScheduler timer) { + final List results=new ArrayList(3); + + long execution_time=4000; + final long base=System.currentTimeMillis(); + + for(int num: new Integer[]{1,2,3}) { + final int cnt=num; + timer.schedule(new Runnable() { + public void run() { + results.add(cnt); + System.out.println("[" + (System.currentTimeMillis() - base) + "] " + cnt); + } + }, execution_time, TimeUnit.MILLISECONDS); + execution_time-=1300; + Util.sleep(300); + } + + for(int i=0; i < 10; i++) { + if(results.size() == 3) + break; + Util.sleep(500); + } + + System.out.println("results = " + results); + assert results.size() == 3: "results = " + results; + assert results.get(0) == 3: "results = " + results; + assert results.get(1) == 2: "results = " + results; + assert results.get(2) == 1: "results = " + results; + } + + + /** + * Tests the initial-delay argument of + * {@link TimeScheduler#scheduleWithFixedDelay(Runnable, long, long, java.util.concurrent.TimeUnit)} + */ + @Test(dataProvider="createTimer") + public void testSchedulerWithFixedDelay(TimeScheduler timer) { + final AtomicBoolean set=new AtomicBoolean(false); + + Future future=timer.scheduleWithFixedDelay(new Runnable() { + public void run() { + set.set(true); + } + }, 0, 3000, TimeUnit.MILLISECONDS); + + Util.sleep(500); + future.cancel(true); + + System.out.println("variable was set: " + set); + assert set.get(); + + future=timer.scheduleWithFixedDelay(new Runnable() { + public void run() { + set.set(true); + } + }, 300, 3000, TimeUnit.MILLISECONDS); + + Util.sleep(1000); + future.cancel(true); + + System.out.println("variable was set: " + set); + assert set.get(); + } + static int check(Map msgs, boolean print) { @@ -492,15 +598,15 @@ public void run() { if(base == 0) base=System.currentTimeMillis(); - System.out.println((num +1) + ": this is the repeating task (" + (System.currentTimeMillis() - base) + "ms after start)"); - execution_times.add(System.currentTimeMillis()); + long time=System.currentTimeMillis() - base; + System.out.println((num +1) + ": this is a repeating task (" + time + "ms after start)"); + execution_times.add(time); num++; } } static class StressTask implements TimeScheduler.Task { - boolean cancelled=false; int num_executions=0; public int getNumExecutions() { @@ -517,6 +623,18 @@ } + static class MyRunnable implements Runnable { + final int num; + + public MyRunnable(int num) { + this.num=num; + } + + public void run() { + System.out.println("[" + num + "] at " + System.currentTimeMillis()); + } + } + private static class Entry implements TimeScheduler.Task { long start_time=0; // time message was added @@ -524,7 +642,6 @@ long second_xmit=0; // time between first_xmit and second_xmit should be ca. 2000ms long third_xmit=0; // time between third_xmit and second_xmit should be ca. 4000ms long fourth_xmit=0; // time between third_xmit and second_xmit should be ca. 8000ms - boolean cancelled=false; Interval interval=new StaticInterval(xmit_timeouts); long seqno=0; diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/TupleTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/TupleTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/TupleTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/TupleTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -10,7 +10,6 @@ /** * @author Bela Ban - * @version $Id: TupleTest.java,v 1.3 2008/04/08 13:25:25 belaban Exp $ */ @Test(groups=Global.FUNCTIONAL) public class TupleTest { diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/UNICAST2_ConnectionTests.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/UNICAST2_ConnectionTests.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/UNICAST2_ConnectionTests.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/UNICAST2_ConnectionTests.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,218 @@ +package org.jgroups.tests; + +import org.jgroups.*; +import org.jgroups.protocols.UNICAST2; +import org.jgroups.stack.Protocol; +import org.jgroups.stack.ProtocolStack; +import org.jgroups.util.Util; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tests unilateral closings of UNICAST2 connections. The test scenarios are described in doc/design.UNICAST.new.txt. + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL,sequential=true) +public class UNICAST2_ConnectionTests { + private JChannel a, b; + private Address a_addr, b_addr; + private MyReceiver r1, r2; + private UNICAST2 u1, u2; + private static final String props="SHARED_LOOPBACK:UNICAST2(stable_interval=2000)"; + private static final String CLUSTER="UNICAST2_ConnectionTests"; + + + @BeforeMethod + void start() throws Exception { + r1=new MyReceiver("A"); + r2=new MyReceiver("B"); + a=new JChannel(props); + a.connect(CLUSTER); + a_addr=a.getAddress(); + a.setReceiver(r1); + u1=(UNICAST2)a.getProtocolStack().findProtocol(UNICAST2.class); + b=new JChannel(props); + b.connect(CLUSTER); + b_addr=b.getAddress(); + b.setReceiver(r2); + u2=(UNICAST2)b.getProtocolStack().findProtocol(UNICAST2.class); + System.out.println("A=" + a_addr + ", B=" + b_addr); + } + + + @AfterMethod void stop() {Util.close(b, a);} + + + /** + * Tests cases #1 and #2 of UNICAST.new.txt + * @throws Exception + */ + public void testRegularMessageReception() throws Exception { + sendAndCheck(a, b_addr, 100, r2); + sendAndCheck(b, a_addr, 50, r1); + } + + + /** + * Tests case #3 of UNICAST.new.txt + */ + public void testBothChannelsClosing() throws Exception { + sendToEachOtherAndCheck(10); + + // now close the connections to each other + System.out.println("==== Closing the connections on both sides"); + u1.removeConnection(b_addr); + u2.removeConnection(a_addr); + r1.clear(); r2.clear(); + + // causes new connection establishment + sendToEachOtherAndCheck(10); + } + + + /** + * Scenario #4 (A closes the connection unilaterally (B keeps it open), then reopens it and sends messages) + */ + public void testAClosingUnilaterally() throws Exception { + sendToEachOtherAndCheck(10); + + // now close connection on A unilaterally + System.out.println("==== Closing the connection on A"); + u1.removeConnection(b_addr); + + // then send messages from A to B + sendAndCheck(a, b_addr, 10, r2); + } + + /** + * Scenario #5 (B closes the connection unilaterally (A keeps it open), then A sends messages to B) + */ + public void testBClosingUnilaterally() throws Exception { + sendToEachOtherAndCheck(10); + + // now close connection on A unilaterally + System.out.println("==== Closing the connection on B"); + u2.removeConnection(a_addr); + + // then send messages from A to B + sendAndCheck(a, b_addr, 10, r2); + } + + + /** + * Scenario #6 (A closes the connection unilaterally (B keeps it open), then reopens it and sends messages, + * but loses the first message + */ + public void testAClosingUnilaterallyButLosingFirstMessage() throws Exception { + sendToEachOtherAndCheck(10); + + // now close connection on A unilaterally + System.out.println("==== Closing the connection on A"); + u1.removeConnection(b_addr); + + // add a Drop protocol to drop the first unicast message + Drop drop=new Drop(true); + a.getProtocolStack().insertProtocol(drop, ProtocolStack.BELOW, UNICAST2.class); + + // then send messages from A to B + sendAndCheck(a, b_addr, 10, r2); + } + + + + /** + * Send num unicasts on both channels and verify the other end received them + * @param num + * @throws Exception + */ + private void sendToEachOtherAndCheck(int num) throws Exception { + for(int i=1; i <= num; i++) { + a.send(b_addr, null, "m" + i); + b.send(a_addr, null, "m" + i); + } + List l1=r1.getMessages(); + List l2=r2.getMessages(); + for(int i=0; i < 10; i++) { + if(l1.size() == num && l2.size() == num) + break; + Util.sleep(500); + } + System.out.println("l1 = " + print(l1)); + System.out.println("l2 = " + print(l2)); + assert l1.size() == num; + assert l2.size() == num; + } + + private static void sendAndCheck(JChannel channel, Address dest, int num, MyReceiver receiver) throws Exception { + receiver.clear(); + for(int i=1; i <= num; i++) + channel.send(dest, null, "m" + i); + List list=receiver.getMessages(); + for(int i=0; i < 10; i++) { + if(list.size() == num) + break; + Util.sleep(500); + } + System.out.println("list = " + print(list)); + int size=list.size(); + assert size == num : "list has " + size + " elements"; + } + + + private static String print(List list) { + List tmp=new ArrayList(list.size()); + for(Message msg: list) + tmp.add((String)msg.getObject()); + return Util.printListWithDelimiter(tmp, " "); + } + + + private static class MyReceiver extends ReceiverAdapter { + final String name; + final List msgs=new ArrayList(20); + + public MyReceiver(String name) { + this.name=name; + } + + public void receive(Message msg) { + msgs.add(msg); + } + + public List getMessages() { return msgs; } + public void clear() {msgs.clear();} + public int size() {return msgs.size();} + + public String toString() { + return name; + } + } + + private static class Drop extends Protocol { + private volatile boolean drop_next=false; + + private Drop(boolean drop_next) { + this.drop_next=drop_next; + } + + public String getName() { + return "Drop"; + } + + public void dropNext() { + drop_next=true; + } + + public Object down(Event evt) { + if(drop_next && evt.getType() == Event.MSG) { + drop_next=false; + return null; + } + return super.down(evt); + } + } +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/UNICAST_ConnectionTests.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/UNICAST_ConnectionTests.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/UNICAST_ConnectionTests.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/UNICAST_ConnectionTests.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,218 @@ +package org.jgroups.tests; + +import org.jgroups.*; +import org.jgroups.stack.Protocol; +import org.jgroups.stack.ProtocolStack; +import org.jgroups.protocols.UNICAST; +import org.jgroups.util.Util; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.List; +import java.util.ArrayList; + +/** + * Tests unilateral closings of UNICAST connections. The test scenarios are described in doc/design.UNICAST.new.txt. + * @author Bela Ban + */ +@Test(groups=Global.FUNCTIONAL,sequential=true) +public class UNICAST_ConnectionTests { + private JChannel a, b; + private Address a_addr, b_addr; + private MyReceiver r1, r2; + private UNICAST u1, u2; + private static final String props="SHARED_LOOPBACK:UNICAST"; + private static final String CLUSTER="UNICAST_ConnectionTests"; + + + @BeforeMethod + void start() throws Exception { + r1=new MyReceiver("A"); + r2=new MyReceiver("B"); + a=new JChannel(props); + a.connect(CLUSTER); + a_addr=a.getAddress(); + a.setReceiver(r1); + u1=(UNICAST)a.getProtocolStack().findProtocol(UNICAST.class); + b=new JChannel(props); + b.connect(CLUSTER); + b_addr=b.getAddress(); + b.setReceiver(r2); + u2=(UNICAST)b.getProtocolStack().findProtocol(UNICAST.class); + System.out.println("A=" + a_addr + ", B=" + b_addr); + } + + + @AfterMethod void stop() {Util.close(b, a);} + + + /** + * Tests cases #1 and #2 of UNICAST.new.txt + * @throws Exception + */ + public void testRegularMessageReception() throws Exception { + sendAndCheck(a, b_addr, 100, r2); + sendAndCheck(b, a_addr, 50, r1); + } + + + /** + * Tests case #3 of UNICAST.new.txt + */ + public void testBothChannelsClosing() throws Exception { + sendToEachOtherAndCheck(10); + + // now close the connections to each other + System.out.println("==== Closing the connections on both sides"); + u1.removeConnection(b_addr); + u2.removeConnection(a_addr); + r1.clear(); r2.clear(); + + // causes new connection establishment + sendToEachOtherAndCheck(10); + } + + + /** + * Scenario #4 (A closes the connection unilaterally (B keeps it open), then reopens it and sends messages) + */ + public void testAClosingUnilaterally() throws Exception { + sendToEachOtherAndCheck(10); + + // now close connection on A unilaterally + System.out.println("==== Closing the connection on A"); + u1.removeConnection(b_addr); + + // then send messages from A to B + sendAndCheck(a, b_addr, 10, r2); + } + + /** + * Scenario #5 (B closes the connection unilaterally (A keeps it open), then A sends messages to B) + */ + public void testBClosingUnilaterally() throws Exception { + sendToEachOtherAndCheck(10); + + // now close connection on A unilaterally + System.out.println("==== Closing the connection on B"); + u2.removeConnection(a_addr); + + // then send messages from A to B + sendAndCheck(a, b_addr, 10, r2); + } + + + /** + * Scenario #6 (A closes the connection unilaterally (B keeps it open), then reopens it and sends messages, + * but loses the first message + */ + public void testAClosingUnilaterallyButLosingFirstMessage() throws Exception { + sendToEachOtherAndCheck(10); + + // now close connection on A unilaterally + System.out.println("==== Closing the connection on A"); + u1.removeConnection(b_addr); + + // add a Drop protocol to drop the first unicast message + Drop drop=new Drop(true); + a.getProtocolStack().insertProtocol(drop, ProtocolStack.BELOW, UNICAST.class); + + // then send messages from A to B + sendAndCheck(a, b_addr, 10, r2); + } + + + + /** + * Send num unicasts on both channels and verify the other end received them + * @param num + * @throws Exception + */ + private void sendToEachOtherAndCheck(int num) throws Exception { + for(int i=1; i <= num; i++) { + a.send(b_addr, null, "m" + i); + b.send(a_addr, null, "m" + i); + } + List l1=r1.getMessages(); + List l2=r2.getMessages(); + for(int i=0; i < 10; i++) { + if(l1.size() == num && l2.size() == num) + break; + Util.sleep(500); + } + System.out.println("l1 = " + print(l1)); + System.out.println("l2 = " + print(l2)); + assert l1.size() == num; + assert l2.size() == num; + } + + private static void sendAndCheck(JChannel channel, Address dest, int num, MyReceiver receiver) throws Exception { + receiver.clear(); + for(int i=1; i <= num; i++) + channel.send(dest, null, "m" + i); + List list=receiver.getMessages(); + for(int i=0; i < 10; i++) { + if(list.size() == num) + break; + Util.sleep(500); + } + System.out.println("list = " + print(list)); + int size=list.size(); + assert size == num : "list has " + size + " elements"; + } + + + private static String print(List list) { + List tmp=new ArrayList(list.size()); + for(Message msg: list) + tmp.add((String)msg.getObject()); + return Util.printListWithDelimiter(tmp, " "); + } + + + private static class MyReceiver extends ReceiverAdapter { + final String name; + final List msgs=new ArrayList(20); + + public MyReceiver(String name) { + this.name=name; + } + + public void receive(Message msg) { + msgs.add(msg); + } + + public List getMessages() { return msgs; } + public void clear() {msgs.clear();} + public int size() {return msgs.size();} + + public String toString() { + return name; + } + } + + private static class Drop extends Protocol { + private volatile boolean drop_next=false; + + private Drop(boolean drop_next) { + this.drop_next=drop_next; + } + + public String getName() { + return "Drop"; + } + + public void dropNext() { + drop_next=true; + } + + public Object down(Event evt) { + if(drop_next && evt.getType() == Event.MSG) { + drop_next=false; + return null; + } + return super.down(evt); + } + } +} diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/UNICAST_Test.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/UNICAST_Test.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/UNICAST_Test.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/UNICAST_Test.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,18 +1,17 @@ package org.jgroups.tests; -import org.jgroups.Event; -import org.jgroups.Global; -import org.jgroups.Message; -import org.jgroups.View; +import org.jgroups.*; import org.jgroups.debug.Simulator; import org.jgroups.protocols.DISCARD; import org.jgroups.protocols.UNICAST; -import org.jgroups.stack.IpAddress; +import org.jgroups.protocols.UNICAST2; import org.jgroups.stack.Protocol; +import org.jgroups.util.Util; import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; +import org.testng.annotations.DataProvider; import java.nio.ByteBuffer; import java.util.Vector; @@ -25,8 +24,8 @@ */ @Test(groups=Global.FUNCTIONAL,sequential=true) public class UNICAST_Test { - IpAddress a1, a2; - Vector members; + Address a1, a2; + Vector
            members; View v; Simulator simulator; @@ -40,28 +39,38 @@ simulator.stop(); } - - public void testReceptionOfAllMessages() throws Throwable { - UNICAST unicast=new UNICAST(); - unicast.setTimeout(new long[] {500,1000,2000,3000}); - Protocol[] stack=new Protocol[]{unicast}; + @Test(dataProvider="configProvider") + public void testReceptionOfAllMessages(Protocol prot) throws Throwable { + System.out.println("prot=" + prot.getClass().getSimpleName()); + Protocol[] stack=new Protocol[]{prot}; createStack(stack); _testReceptionOfAllMessages(); } - public void testReceptionOfAllMessagesWithDISCARD() throws Throwable { - UNICAST unicast=new UNICAST(); - unicast.setTimeout(new long[] {500,1000,2000,3000}); - + @Test(dataProvider="configProvider") + public void testReceptionOfAllMessagesWithDISCARD(Protocol prot) throws Throwable { + System.out.println("prot=" + prot.getClass().getSimpleName()); DISCARD discard=new DISCARD(); discard.setDownDiscardRate(0.1); // discard all down message with 10% probability - Protocol[] stack=new Protocol[]{unicast,discard}; + Protocol[] stack=new Protocol[]{prot, discard}; createStack(stack); _testReceptionOfAllMessages(); } + @DataProvider + public static Object[][] configProvider() { + Object[][] retval=new Object[][] { + {new UNICAST()}, + {new UNICAST2()} + }; + + ((UNICAST)retval[0][0]).setTimeout(new long[]{500,1000,2000,3000}); + ((UNICAST2)retval[1][0]).setTimeout(new long[]{500,1000,2000,3000}); + return retval; + } + private static byte[] createPayload(int size, int seqno) { @@ -115,7 +124,7 @@ private void _testReceptionOfAllMessages() throws Throwable { int num_received=0; - Receiver r=new Receiver(); + final Receiver r=new Receiver(); simulator.setReceiver(r); for(int i=1; i <= NUM_MSGS; i++) { Message msg=new Message(a1, null, createPayload(SIZE, i)); // unicast message @@ -139,8 +148,8 @@ } private void createStack(Protocol[] stack) throws Exception { - a1=new IpAddress(1111); - members=new Vector(); + a1=Util.createRandomAddress(); + members=new Vector
            (); members.add(a1); v=new View(a1, 1, members); simulator=new Simulator(); diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/UtilTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/UtilTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/UtilTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/UtilTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,20 +1,15 @@ -// $Id: UtilTest.java,v 1.10 2008/12/11 12:52:19 belaban Exp $ package org.jgroups.tests; import org.jgroups.*; -import org.jgroups.stack.IpAddress; import org.jgroups.util.Buffer; import org.jgroups.util.Util; +import org.jgroups.util.UUID; import org.testng.Assert; import org.testng.annotations.Test; import java.io.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; -import java.util.Vector; - +import java.util.*; @Test(groups=Global.FUNCTIONAL) @@ -100,6 +95,28 @@ assert result != null && result.equals("200"); } + public static void testFlags() { + final byte ONE = 1; + final byte FIVE = 16; + final byte SEVEN = 64; + + byte flags=0; + flags=Util.setFlag(flags, ONE); + assert Util.isFlagSet(flags, ONE); + + flags=0; + flags=Util.setFlag(flags, (byte)(ONE | SEVEN)); + assert Util.isFlagSet(flags, ONE); + assert Util.isFlagSet(flags, SEVEN); + assert !Util.isFlagSet(flags, FIVE); + flags=Util.clearFlags(flags, ONE); + assert !Util.isFlagSet(flags, ONE); + + flags=Util.setFlag(flags, FIVE); + assert Util.isFlagSet(flags, FIVE); + assert Util.isFlagSet(flags, SEVEN); + } + public static void testIgnoreBindAddress() { boolean retval; @@ -208,23 +225,82 @@ } + public static void testReadBytes() { + assert 10 == Util.readBytesInteger("10"); + assert 10 == Util.readBytesInteger("10 "); + assert 10 == Util.readBytesInteger(" 10"); + + assert 1000 == Util.readBytesInteger("1000"); + assert 1000 == Util.readBytesInteger("1kb"); + assert 1000 == Util.readBytesInteger("1 kb"); + assert 1000 == Util.readBytesInteger("1k"); + assert 1000 == Util.readBytesInteger("1KB"); + assert 1000 == Util.readBytesInteger("1 K"); + assert 1000 == Util.readBytesInteger("1K"); + + assert 1234 == Util.readBytesInteger("1.234K"); + + long M=1000*1000; + assert M == Util.readBytesLong("1M"); + assert M == Util.readBytesLong("1 M"); + assert M == Util.readBytesLong("1MB"); + assert M == Util.readBytesLong("1 mb"); + assert M == Util.readBytesLong("1m"); + assert M == Util.readBytesLong("1 m"); + + M=(long)(25.5 * 1000*1000); + assert M == Util.readBytesLong("25.5M"); + assert M == Util.readBytesLong("25.5m"); + assert M == Util.readBytesLong("25.5 MB"); + assert M == Util.readBytesLong("25.5 mB"); + assert M == Util.readBytesLong("25.5 m"); + assert M == Util.readBytesLong("25500K"); + + M=(long)(1.5 * 1000 * 1000 * 1000); + assert M == Util.readBytesLong("1.5GB"); + assert M == Util.readBytesLong("1.5gb"); + assert M == Util.readBytesLong("1.5g"); + assert M == Util.readBytesLong("1.5G"); + assert M == Util.readBytesLong("1500m"); + assert M == Util.readBytesLong("1500000K"); + assert M == Util.readBytesLong("1.5 gb"); + + double D=3.123456789; + assert D == Util.readBytesDouble("3.123456789"); + assert D * 10 == Util.readBytesDouble("31.23456789"); + assert D * 100 == Util.readBytesDouble("312.3456789"); + assert D * 1000 == Util.readBytesDouble("3123.456789"); + assert D * 1000 == Util.readBytesDouble("3.123456789K"); + assert D * 1000000 == Util.readBytesDouble("3.123456789M"); + assert D * 1000000 == Util.readBytesDouble("3123456.789"); + + } + + @SuppressWarnings("unchecked") public static void testObjectToFromByteBuffer() throws Exception { byte[] buf; - IpAddress addr=new IpAddress("localhost", 5000), addr2; + Address addr=Util.createRandomAddress(), addr2; List list=new ArrayList(), list2; list.add("Bela"); list.add("Jeannette"); buf=Util.objectToByteBuffer(addr); - addr2=(IpAddress)Util.objectFromByteBuffer(buf); + addr2=(Address)Util.objectFromByteBuffer(buf); System.out.println("addr=" + addr + ", addr2=" + addr2); Assert.assertEquals(addr, addr2); buf=Util.objectToByteBuffer(list); - list2=(List)Util.objectFromByteBuffer(buf); + list2=(List)Util.objectFromByteBuffer(buf); System.out.println("list=" + list + ", list2=" + list2); Assert.assertEquals(list, list2); + byte[] buffer=new byte[]{'B', 'e', 'l', 'a', ' ', 'B', 'a', 'n'}; + buf=Util.objectToByteBuffer(buffer); + + byte[] buffer2=(byte[])Util.objectFromByteBuffer(buf); + assert buffer2 != null && buffer.length == buffer2.length; + assert Arrays.equals(buffer, buffer2); + Object obj=null; buf=Util.objectToByteBuffer(obj); assert buf != null; @@ -255,9 +331,9 @@ public static void testMessageToByteBuffer() throws Exception { _testMessage(new Message()); _testMessage(new Message(null, null, "hello world")); - _testMessage(new Message(null, new IpAddress("localhost", 5000), null)); - _testMessage(new Message(null, new IpAddress("localhost", 5000), null)); - _testMessage(new Message(null, new IpAddress("localhost", 5000), "bela")); + _testMessage(new Message(null, Util.createRandomAddress(), null)); + _testMessage(new Message(null, Util.createRandomAddress(), null)); + _testMessage(new Message(null, Util.createRandomAddress(), "bela")); } private static void _testMessage(Message msg) throws Exception { @@ -316,7 +392,7 @@ public static void testWriteStreamable() throws IOException, IllegalAccessException, InstantiationException { Message m=new Message(null, null, "Hello"); ViewId vid=new ViewId(null, 12345); - ViewId vid2=new ViewId(new IpAddress("127.0.0.1", 5555), 35623); + ViewId vid2=new ViewId(Util.createRandomAddress(), 35623); ByteArrayOutputStream outstream=new ByteArrayOutputStream(); DataOutputStream dos=new DataOutputStream(outstream); Util.writeGenericStreamable(m, dos); @@ -353,11 +429,11 @@ public static void testWriteView() throws IOException, IllegalAccessException, InstantiationException { ViewId vid=new ViewId(null, 12345); - Vector members=new Vector(); + Vector
            members=new Vector
            (); View v; - IpAddress a1=new IpAddress("localhost", 1234); - IpAddress a2=new IpAddress("127.0.0.1", 4444); - IpAddress a4=new IpAddress("127.0.0.1", 7777); + Address a1=Util.createRandomAddress(); + Address a2=Util.createRandomAddress(); + Address a4=Util.createRandomAddress(); members.add(a1); members.add(a2); members.add(a4); @@ -395,9 +471,9 @@ } public static void writeAddress() throws IOException, IllegalAccessException, InstantiationException { - IpAddress a1=new IpAddress("localhost", 1234); - IpAddress a2=new IpAddress("127.0.0.1", 4444); - IpAddress a4=new IpAddress("127.0.0.1", 7777); + Address a1=Util.createRandomAddress(); + Address a2=Util.createRandomAddress(); + Address a4=Util.createRandomAddress(); ByteArrayOutputStream outstream=new ByteArrayOutputStream(); DataOutputStream dos=new DataOutputStream(outstream); @@ -415,7 +491,7 @@ } public static void writeNullAddress() throws IOException, IllegalAccessException, InstantiationException { - IpAddress a1=null; + Address a1=null; ByteArrayOutputStream outstream=new ByteArrayOutputStream(); DataOutputStream dos=new DataOutputStream(outstream); Util.writeAddress(a1, dos); @@ -462,8 +538,7 @@ public static void testLeftMembers() { - final Address a=new IpAddress(1000), b=new IpAddress(2000), c=new IpAddress(3000), - d=new IpAddress(4000), e=new IpAddress(5000); + final Address a=Util.createRandomAddress(), b=Util.createRandomAddress(), c=Util.createRandomAddress(), d=Util.createRandomAddress(); Vector
            v1=new Vector
            (); v1.add(a); @@ -486,8 +561,7 @@ } public static void testLeftMembers2() { - final Address a=new IpAddress(1000), b=new IpAddress(2000), c=new IpAddress(3000), - d=new IpAddress(4000), e=new IpAddress(5000); + final Address a=Util.createRandomAddress(), b=Util.createRandomAddress(), c=Util.createRandomAddress(), d=Util.createRandomAddress(); Vector
            v1=new Vector
            (); v1.add(a); @@ -509,8 +583,32 @@ assert left.isEmpty(); } + + public static void testNewMembers() { + final Address a=Util.createRandomAddress(), b=Util.createRandomAddress(), c=Util.createRandomAddress(), + d=Util.createRandomAddress(), e=Util.createRandomAddress(); + List
            old=new ArrayList
            (); + List
            new_list=new ArrayList
            (); + + old.add(a); old.add(b); old.add(c); + new_list.add(b); + new_list.add(a); + new_list.add(c); + new_list.add(d); + new_list.add(e); + + System.out.println("old: " + old); + System.out.println("new: " + new_list); + + List
            new_nodes=Util.newMembers(old, new_list); + System.out.println("new_nodes = " + new_nodes); + assert new_nodes.size() == 2 : "list should have d and e"; + assert new_nodes.contains(d); + assert new_nodes.contains(e); + } + public static void testPickRandomElement() { - Vector v=new Vector(); + Vector v=new Vector(); for(int i=0; i < 10; i++) { v.add(new Integer(i)); } @@ -522,8 +620,50 @@ } } + + public static void testPickNext() { + List list=new ArrayList(10); + for(int i=0; i < 10; i++) + list.add(i); + Integer num=Util.pickNext(list, 5); + System.out.println("number next to 5: " + num); + assert num != null; + assert num.equals(6); + + num=Util.pickNext(list, 9); + System.out.println("number next to 9: " + num); + assert num != null; + assert num.equals(0); + + num=Util.pickNext(list, 11); + assert num == null; + } + + + public static void testPickNextN() { + List list=Arrays.asList(1,2,3,4); + List result=Util.pickNext(list, 0, 1); + assert result.isEmpty(); + + result=Util.pickNext(list, 1, 1); + System.out.println("result = " + result); + assert result.size() == 1; + assert result.contains(2); + + result=Util.pickNext(list, 3, 2); + System.out.println("result = " + result); + assert result.size() == 2; + assert result.contains(4) && result.contains(1); + + result=Util.pickNext(list, 4, 5); + System.out.println("result = " + result); + assert result.size() == 3; + assert result.contains(1) && result.contains(2) && result.contains(3); + } + + public static void testAll() { - ArrayList l=new ArrayList(); + List l=new ArrayList(); l.add("one"); l.add("two"); l.add("one"); System.out.println("-- list is " + l); assert !(Util.all(l, "one")); @@ -615,7 +755,162 @@ } - + public static void testDetermineMergeParticipantsAndMergeCoords() { + Address a=Util.createRandomAddress(), b=Util.createRandomAddress(), c=Util.createRandomAddress(); + org.jgroups.util.UUID.add((UUID)a, "A"); + org.jgroups.util.UUID.add((UUID)b, "B"); + org.jgroups.util.UUID.add((UUID)c, "C"); + + View v1=Util.createView(b, 1, b, a, c); + View v2=Util.createView(b, 2, b, c); + View v3=Util.createView(b, 2, b, c); + + Map map=new HashMap(); + map.put(a, v1); map.put(b, v2); map.put(c, v3); + StringBuilder sb=new StringBuilder("map:\n"); + for(Map.Entry entry: map.entrySet()) + sb.append(entry.getKey() + ": " + entry.getValue() + "\n"); + System.out.println(sb); + + Collection
            merge_participants=Util.determineMergeParticipants(map); + System.out.println("merge_participants = " + merge_participants); + assert merge_participants.size() == 2; + assert merge_participants.contains(a) && merge_participants.contains(b); + + Collection
            merge_coords=Util.determineMergeCoords(map); + System.out.println("merge_coords = " + merge_coords); + assert merge_coords.size() == 1; + assert merge_coords.contains(b); + } + + + public static void testDetermineMergeParticipantsAndMergeCoords2() { + Address a=Util.createRandomAddress(), b=Util.createRandomAddress(), c=Util.createRandomAddress(), d=Util.createRandomAddress(); + org.jgroups.util.UUID.add((UUID)a, "A"); + org.jgroups.util.UUID.add((UUID)b, "B"); + org.jgroups.util.UUID.add((UUID)c, "C"); + org.jgroups.util.UUID.add((UUID)d, "D"); + + View v1=Util.createView(a, 1, a, b); + View v2=Util.createView(a, 1, a, b); + View v3=Util.createView(c, 2, c, d); + View v4=Util.createView(c, 2, c, d); + + Map map=new HashMap(); + map.put(a, v1); map.put(b, v2); map.put(c, v3); map.put(d, v4); + + StringBuilder sb=new StringBuilder("map:\n"); + for(Map.Entry entry: map.entrySet()) + sb.append(entry.getKey() + ": " + entry.getValue() + "\n"); + System.out.println(sb); + + Collection
            merge_participants=Util.determineMergeParticipants(map); + System.out.println("merge_participants = " + merge_participants); + assert merge_participants.size() == 2; + assert merge_participants.contains(a) && merge_participants.contains(c); + + Collection
            merge_coords=Util.determineMergeCoords(map); + System.out.println("merge_coords = " + merge_coords); + assert merge_coords.size() == 2; + assert merge_coords.contains(a) && merge_coords.contains(c); + } + + + public static void testDetermineMergeParticipantsAndMergeCoords3() { + Address a=Util.createRandomAddress(), b=Util.createRandomAddress(), c=Util.createRandomAddress(), d=Util.createRandomAddress(); + org.jgroups.util.UUID.add((UUID)a, "A"); + org.jgroups.util.UUID.add((UUID)b, "B"); + org.jgroups.util.UUID.add((UUID)c, "C"); + org.jgroups.util.UUID.add((UUID)d, "D"); + + View v1=Util.createView(a, 1, a, b, c, d); + View v2=Util.createView(a, 1, a, b, c, d); + View v3=Util.createView(a, 2, a, b, c, d); + View v4=Util.createView(a, 3, a, b, c, d); + + Map map=new HashMap(); + map.put(a, v1); map.put(b, v2); map.put(c, v3); map.put(d, v4); + + StringBuilder sb=new StringBuilder("map:\n"); + for(Map.Entry entry: map.entrySet()) + sb.append(entry.getKey() + ": " + entry.getValue() + "\n"); + System.out.println(sb); + + Collection
            merge_participants=Util.determineMergeParticipants(map); + System.out.println("merge_participants = " + merge_participants); + assert merge_participants.size() == 1; + assert merge_participants.contains(a); + + Collection
            merge_coords=Util.determineMergeCoords(map); + System.out.println("merge_coords = " + merge_coords); + assert merge_coords.size() == 1; + assert merge_coords.contains(a); + } + + public static void testDetermineMergeParticipantsAndMergeCoords4() { + Address a=Util.createRandomAddress(), b=Util.createRandomAddress(), c=Util.createRandomAddress(), d=Util.createRandomAddress(); + org.jgroups.util.UUID.add((UUID)a, "A"); + org.jgroups.util.UUID.add((UUID)b, "B"); + org.jgroups.util.UUID.add((UUID)c, "C"); + org.jgroups.util.UUID.add((UUID)d, "D"); + + View v1=Util.createView(a, 1, a, b); + View v2=Util.createView(c, 1, c, d); + + Map map=new HashMap(); + map.put(a, v1); map.put(b, v1); map.put(d, v2); + + StringBuilder sb=new StringBuilder("map:\n"); + for(Map.Entry entry: map.entrySet()) + sb.append(entry.getKey() + ": " + entry.getValue() + "\n"); + System.out.println(sb); + + Collection
            merge_participants=Util.determineMergeParticipants(map); + System.out.println("merge_participants = " + merge_participants); + assert merge_participants.size() == 3; + assert merge_participants.contains(a) && merge_participants.contains(c) && merge_participants.contains(d); + + Collection
            merge_coords=Util.determineMergeCoords(map); + System.out.println("merge_coords = " + merge_coords); + assert merge_coords.size() == 2; + assert merge_coords.contains(a) && merge_coords.contains(c); + } + + + public static void testAttributeNameToMethodName() { + _testAttributeNameToMethodName("my_name", "MyName"); + _testAttributeNameToMethodName("bela", "Bela"); + _testAttributeNameToMethodName("oob_max_input_size", "OobMaxInputSize"); + _testAttributeNameToMethodName("a_b_c", "ABC"); + _testAttributeNameToMethodName("aa_bb_cc", "AaBbCc"); + _testAttributeNameToMethodName("i", "I"); + _testAttributeNameToMethodName("tmp", "Tmp"); + _testAttributeNameToMethodName("inet_address_method", "InetAddressMethod"); + } + + public static void testMethodNameToAttributeName() { + _testMethodNameToAttributeName("setFoo", "foo"); + _testMethodNameToAttributeName("getFoo", "foo"); + _testMethodNameToAttributeName("isLogDiscardMessages", "log_discard_messages"); + _testMethodNameToAttributeName("setOOBMinPoolSize", "oob_min_pool_size"); + _testMethodNameToAttributeName("isOOBThreadPoolEnabled", "oob_thread_pool_enabled"); + _testMethodNameToAttributeName("OOBMinPoolSize", "oob_min_pool_size"); + _testMethodNameToAttributeName("inetAddressMethod", "inet_address_method"); + } + + private static void _testMethodNameToAttributeName(String input, String expected_output) { + String atttr_name=Util.methodNameToAttributeName(input); + System.out.println("method name=" + input + ", attrname=" + atttr_name + ", expected output=" + expected_output); + assert atttr_name.equals(expected_output) : + "method name=" + input + ", attrname=" + atttr_name + ", expected output=" + expected_output; + } + + private static void _testAttributeNameToMethodName(String input, String expected_output) { + String method_name=Util.attributeNameToMethodName(input); + System.out.println("attrname=" + input + ", method name=" + method_name + ", expected output=" + expected_output); + assert method_name.equals(expected_output) : + "attrname=" + input + ", method name=" + method_name + ", expected output=" + expected_output; + } } diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ViewIdTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ViewIdTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ViewIdTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ViewIdTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,23 +1,25 @@ -// $Id: ViewIdTest.java,v 1.2 2008/03/10 15:39:22 belaban Exp $ package org.jgroups.tests; -import org.jgroups.ViewId; import org.jgroups.Global; +import org.jgroups.ViewId; +import org.jgroups.util.Util; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import java.net.InetAddress; import java.net.UnknownHostException; +/** + * Author: Bela Ban + */ @Test(groups=Global.FUNCTIONAL) public class ViewIdTest { private ViewId v1, v2, v3; @BeforeClass public void setUp() throws UnknownHostException { - v1=new ViewId(new org.jgroups.stack.IpAddress(InetAddress.getByName("localhost"), 1000), 22); - v2=new ViewId(new org.jgroups.stack.IpAddress(InetAddress.getByName("localhost"), 1000), 21); + v1=new ViewId(Util.createRandomAddress(), 22); + v2=new ViewId(Util.createRandomAddress(), 21); v3=(ViewId)v1.clone(); } diff -Nru libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ViewTest.java libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ViewTest.java --- libjgroups-java-2.7.0.GA/tests/junit-functional/org/jgroups/tests/ViewTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/junit-functional/org/jgroups/tests/ViewTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,51 +1,42 @@ -// $Id: ViewTest.java,v 1.3 2008/03/10 15:39:22 belaban Exp $ package org.jgroups.tests; +import org.jgroups.Address; +import org.jgroups.Global; import org.jgroups.View; import org.jgroups.ViewId; -import org.jgroups.Global; -import org.jgroups.stack.IpAddress; -import org.testng.Assert; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.jgroups.util.Util; import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import java.util.Arrays; +import java.util.List; import java.util.Vector; @Test(groups=Global.FUNCTIONAL) public class ViewTest { - IpAddress a, b, c, d, e, f, g, h, i, j, k; + Address a, b, c, d, e, f, g, h, i, j, k; View view; - Vector members; + List
            members; - public ViewTest(String Name_) { - } + @BeforeClass public void setUp() throws Exception { - a=new IpAddress("localhost", 5555); - b=new IpAddress("localhost", 5555); + a=Util.createRandomAddress("A"); + b=a; c=b; - d=new IpAddress("localhost", 5556); - e=new IpAddress("127.0.0.1", 5555); - f=new IpAddress("127.0.0.1", 80); - g=new IpAddress("127.0.0.1", 8080); - h=new IpAddress("224.0.0.1", 5555); - i=new IpAddress("224.0.0.2", 5555); + d=Util.createRandomAddress("D"); + e=Util.createRandomAddress("E"); + f=Util.createRandomAddress("F"); + g=Util.createRandomAddress("G"); + h=Util.createRandomAddress("H"); + i=Util.createRandomAddress("I"); ViewId id=new ViewId(a, 34); - members=new java.util.Vector(); - members.addElement(a); - members.addElement(b); - members.addElement(d); - members.addElement(e); - members.addElement(f); - members.addElement(g); - members.addElement(h); + members=Arrays.asList(a, b, d, e, f, g, h); view=new View(id, members); } @@ -61,28 +52,33 @@ } public void testEqualsCreator() { - Assert.assertEquals(a, view.getCreator(), "Creator should be a:"); + assert a.equals(view.getCreator()) : "Creator should be a"; assert !view.getCreator().equals(d) : "Creator should not be d"; } public void testEquals() { - Assert.assertEquals(view, view); + assert view.equals(view); } public void testEquals2() { - View v1=new View(new ViewId(a, 12345), (Vector)members.clone()); - View v2=new View(a, 12345, (Vector)members.clone()); - Assert.assertEquals(v1, v2); - View v3=new View(a, 12543, (Vector)members.clone()); - assert !(v1.equals(v3)); + View v1=new View(new ViewId(a, 12345), new Vector
            (members)); + View v2=new View(a, 12345, new Vector
            (members)); + assert v1.equals(v2); + View v3=new View(a, 12543, new Vector
            (members)); + assert !v1.equals(v3); } - + public static void testEquals3() { - View v1, v2; - v1=new View(); - v2=new View(); - Assert.assertEquals(v1, v2); + View v1=new View(), v2=new View(); + assert v1.equals(v2); + } + + public void testCopy() { + View view2=view.copy(); + System.out.println("view = " + view); + System.out.println("view2 = " + view2); + assert view.equals(view2); } diff -Nru libjgroups-java-2.7.0.GA/tests/other/org/jgroups/tests/Bsh.java libjgroups-java-2.12.2.Final/tests/other/org/jgroups/tests/Bsh.java --- libjgroups-java-2.7.0.GA/tests/other/org/jgroups/tests/Bsh.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/other/org/jgroups/tests/Bsh.java 2011-10-18 11:22:35.000000000 +0000 @@ -1,16 +1,12 @@ -// $Id: Bsh.java,v 1.8 2005/05/30 16:15:11 belaban Exp $ package org.jgroups.tests; -import org.jgroups.JChannel; -import org.jgroups.Message; -import org.jgroups.protocols.BSH; -import org.jgroups.stack.IpAddress; +import org.jgroups.util.Util; -import java.io.BufferedReader; -import java.io.InputStreamReader; +import java.io.*; +import java.net.Socket; /** @@ -21,8 +17,6 @@ String host="localhost"; int port=0; long timeout=0; - String props=null; - JChannel ch; public void start(String[] args) throws Exception { @@ -30,11 +24,6 @@ for(int i=0; i < args.length; i++) { String tmp=args[i]; - if("-props".equals(tmp)) { - props=args[++i]; - continue; - } - if("-host".equals(tmp)) { host=args[++i]; continue; @@ -59,84 +48,48 @@ } void runClient() throws Exception { - IpAddress addr; - Message msg; - String line; - BufferedReader reader; - BSH.BshHeader hdr; - - ch=new JChannel(props); - ch.connect(null); // unicast channel - - addr=new IpAddress(host, port); - reader= new BufferedReader(new InputStreamReader(System.in)); + final Socket sock=new Socket(host, port); + final InputStream in=sock.getInputStream(); + final OutputStream out=sock.getOutputStream(); + final BufferedReader reader= new BufferedReader(new InputStreamReader(System.in)); + + new Thread() { + public void run() { + + byte[] buf=new byte[1024]; + while(!sock.isClosed()) { + try { + int num=in.read(buf, 0, buf.length); + String str=new String(buf, 0, num); + System.out.println(str); + } + catch(IOException e) { + e.printStackTrace(); + break; + } + } + } + }.start(); while(true) { System.out.print("> "); - line=reader.readLine(); + String line=reader.readLine(); if(line.startsWith("quit") || line.startsWith("exit")) { - ch.close(); + Util.close(sock); return; } - if(line.startsWith("get")) { - int i=1; - while(ch.getNumMessages() > 0) { - Object obj=ch.receive(1000); - System.out.println("#" + i++ + ": " + print(obj) + - ", obj=" + obj); - } - continue; - } - if(line.startsWith("destroyInterpreter")) { - msg=new Message(addr, null, line.getBytes()); - hdr=new BSH.BshHeader(BSH.BshHeader.REQ); - msg.putHeader("BSH", hdr); - sendAndReceive(msg, 1000); - continue; - } - - msg=new Message(addr, null, line.getBytes()); - hdr=new BSH.BshHeader(BSH.BshHeader.REQ); - msg.putHeader("BSH", hdr); - sendAndReceive(msg, timeout); + line=line + "\n"; + byte[] buf=line.getBytes(); + out.write(buf, 0, buf.length); + out.flush(); } } - Object print(Object obj) { - if(obj == null) - return null; - - if(obj instanceof Message) - return ((Message)obj).getObject(); - else - return obj; - } - - void sendAndReceive(Message msg, long timeout) { - Object obj, result; - try { - ch.send(msg); - obj=ch.receive(timeout); - if(obj == null || !(obj instanceof Message)) { - System.err.println("<-- " + obj); - } - else { - result=((Message)obj).getObject(); - System.out.println("<-- " + result); - } - - // System.out.println("** " + ch.getNumMessages() + " are waiting"); - } - catch(Throwable t) { - System.err.println("Bsh.sendAndReceive(): " + t); - } - } - void help() { - System.out.println("Bsh [-help] [-props ]" + - "[-host ] [-port ] [-timeout ]"); + static void help() { + System.out.println("Bsh [-help] [-host ] [-port ] [-timeout ]"); } diff -Nru libjgroups-java-2.7.0.GA/tests/other/org/jgroups/tests/BundlerTest.java libjgroups-java-2.12.2.Final/tests/other/org/jgroups/tests/BundlerTest.java --- libjgroups-java-2.7.0.GA/tests/other/org/jgroups/tests/BundlerTest.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/other/org/jgroups/tests/BundlerTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,168 @@ +package org.jgroups.tests; + + +import org.jgroups.Global; +import org.jgroups.JChannel; +import org.jgroups.Message; +import org.jgroups.ReceiverAdapter; +import org.jgroups.util.Util; +import org.testng.annotations.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Tests contention in TP.Bundler + * @author Bela Ban + */ +@Test(groups=Global.STACK_INDEPENDENT,sequential=true) +public class BundlerTest { + static final String props="SHARED_LOOPBACK(thread_pool.queue_max_size=5000;" + + "thread_pool.rejection_policy=run;thread_pool.min_threads=20;thread_pool.max_threads=20;" + + "oob_thread_pool.rejection_policy=run;enable_bundling=true)"; + static final int NUM_THREADS=200; + static final int NUM_MSGS=1000; + static final int SIZE=1000; // default size of a message in bytes + + + + public static void testSimpleMessageReception() throws Exception { + JChannel c1=new JChannel(props); + JChannel c2=new JChannel(props); + MyReceiver r1=new MyReceiver("c1"), r2=new MyReceiver("c2"); + c1.setReceiver(r1); + c2.setReceiver(r2); + c1.connect("testSimpleMessageReception"); + c2.connect("testSimpleMessageReception"); + + int NUM=100; + for(int i=1; i <= NUM; i++) { + c1.send(null, null, "bla"); + c2.send(null, null, "bla"); + } + + for(int i=0; i < 10; i++) { + if(r1.getNum() == NUM * 2 && r2.getNum() == NUM * 2) + break; + Util.sleep(500); + } + + System.out.println("c1 received " + r1.getNum() + " msgs"); + System.out.println("c2 received " + r2.getNum() + " msgs"); + + Util.close(c2, c1); + + assert r1.getNum() == NUM * 2: "expected " + NUM *2 + ", but got " + r1.getNum(); + assert r2.getNum() == NUM * 2: "expected " + NUM *2 + ", but got " + r2.getNum(); + } + + + /** + * Multiple threads (NUM_THREADS) send messages (NUM_MSGS) + * @throws Exception + */ + public static void testMessageReceptionUnderHighLoad() throws Exception { + CountDownLatch latch=new CountDownLatch(1); + JChannel c1=new JChannel(props); + JChannel c2=new JChannel(props); + MyReceiver r1=new MyReceiver("c1"), r2=new MyReceiver("c2"); + c1.setReceiver(r1); + c2.setReceiver(r2); + c1.connect("testSimpleMessageReception"); + c2.connect("testSimpleMessageReception"); + + System.out.println("starting to send messages"); + MySender[] c1_senders=new MySender[NUM_THREADS]; + for(int i=0; i < c1_senders.length; i++) { + c1_senders[i]=new MySender(c1, latch); + c1_senders[i].start(); + } + MySender[] c2_senders=new MySender[NUM_THREADS]; + for(int i=0; i < c2_senders.length; i++) { + c2_senders[i]=new MySender(c2, latch); + c2_senders[i].start(); + } + + Util.sleep(500); + long start=System.currentTimeMillis(); + latch.countDown(); // starts all threads + + long NUM_EXPECTED_MSGS=NUM_THREADS * NUM_MSGS * 2; + + for(int i=0; i < 1000; i++) { + if(r1.getNum() >= NUM_EXPECTED_MSGS && r2.getNum() >= NUM_EXPECTED_MSGS) + break; + Util.sleep(2000); + } + + System.out.println("c1 received " + r1.getNum() + " msgs"); + System.out.println("c2 received " + r2.getNum() + " msgs"); + + long diff=System.currentTimeMillis() - start; + Util.close(c2, c1); + + assert r1.getNum() == NUM_EXPECTED_MSGS : "expected " + NUM_EXPECTED_MSGS + ", but got " + r1.getNum(); + assert r2.getNum() == NUM_EXPECTED_MSGS : "expected " + NUM_EXPECTED_MSGS + ", but got " + r2.getNum(); + System.out.println("sending and receiving of " + NUM_EXPECTED_MSGS + " took " + diff + " ms"); + } + + + + + private static class MySender extends Thread { + private final JChannel ch; + private final CountDownLatch latch; + private final byte[] buf=new byte[SIZE]; + + public MySender(JChannel ch, CountDownLatch latch) { + this.ch=ch; + this.latch=latch; + } + + + public void run() { + try { + latch.await(); + } + catch(InterruptedException e) { + e.printStackTrace(); + } + for(int i=0; i < NUM_MSGS; i++) { + try { + Message msg=new Message(null, null, buf); + ch.send(msg); + } + catch(Exception e) { + e.printStackTrace(); + } + } + } + } + + + + private static class MyReceiver extends ReceiverAdapter { + final String name; + final AtomicInteger num=new AtomicInteger(0); + static final long MOD=NUM_MSGS * NUM_THREADS / 10; + + public MyReceiver(String name) { + this.name=name; + } + + public void receive(Message msg) { + int count=num.getAndIncrement(); + if(count > 0 && count % MOD == 0) { + System.out.println("[" + name + "] received " + count + " msgs"); + } + } + + public int getNum() { + return num.get(); + } + } + + + + +} \ No newline at end of file diff -Nru libjgroups-java-2.7.0.GA/tests/other/org/jgroups/tests/ChannelCallbackTest.java libjgroups-java-2.12.2.Final/tests/other/org/jgroups/tests/ChannelCallbackTest.java --- libjgroups-java-2.7.0.GA/tests/other/org/jgroups/tests/ChannelCallbackTest.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/other/org/jgroups/tests/ChannelCallbackTest.java 2011-10-18 11:22:35.000000000 +0000 @@ -5,7 +5,6 @@ /** * @author Bela Ban - * @version $Id: ChannelCallbackTest.java,v 1.1 2005/12/09 14:15:14 belaban Exp $ */ public class ChannelCallbackTest extends ReceiverAdapter implements ChannelListener { JChannel channel; @@ -58,11 +57,9 @@ } public void channelShunned() { - System.out.println("-- channel shunned"); } public void channelReconnected(Address addr) { - System.out.println("-- channel reconnected: " + addr); } } diff -Nru libjgroups-java-2.7.0.GA/tests/other/org/jgroups/tests/CheckGaps.java libjgroups-java-2.12.2.Final/tests/other/org/jgroups/tests/CheckGaps.java --- libjgroups-java-2.7.0.GA/tests/other/org/jgroups/tests/CheckGaps.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/other/org/jgroups/tests/CheckGaps.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,32 @@ +package org.jgroups.tests; + +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.Set; +import java.util.HashSet; + +/** Reads a sequence of numbers from stdin and verifies that they are in order + * @author Bela Ban + */ +public class CheckGaps { + + + public static void main(String[] args) { + SortedSet set=new TreeSet(); + + for(int i=0; i < args.length; i++) { + set.add(Integer.parseInt(args[i])); + } + + int low=set.first(), high=set.last(); + System.out.println("input has " + set.size() + " numbers, low=" + low + ", high=" + high); + + Set correct_set=new HashSet(); + for(int i=low; i < high; i++) { + correct_set.add(i); + } + + correct_set.removeAll(set); + System.out.println("missing seqnos: " + correct_set); + } +} diff -Nru libjgroups-java-2.7.0.GA/tests/other/org/jgroups/tests/CheckMonotonicallyIncreasingNumbers.java libjgroups-java-2.12.2.Final/tests/other/org/jgroups/tests/CheckMonotonicallyIncreasingNumbers.java --- libjgroups-java-2.7.0.GA/tests/other/org/jgroups/tests/CheckMonotonicallyIncreasingNumbers.java 2009-01-05 12:42:12.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/other/org/jgroups/tests/CheckMonotonicallyIncreasingNumbers.java 2011-10-18 11:22:35.000000000 +0000 @@ -11,7 +11,6 @@ * 4 * * @author Bela Ban - * @version $Id: CheckMonotonicallyIncreasingNumbers.java,v 1.2 2006/01/16 13:01:26 belaban Exp $ */ public class CheckMonotonicallyIncreasingNumbers { diff -Nru libjgroups-java-2.7.0.GA/tests/other/org/jgroups/tests/ConnectionMapDemo.java libjgroups-java-2.12.2.Final/tests/other/org/jgroups/tests/ConnectionMapDemo.java --- libjgroups-java-2.7.0.GA/tests/other/org/jgroups/tests/ConnectionMapDemo.java 1970-01-01 00:00:00.000000000 +0000 +++ libjgroups-java-2.12.2.Final/tests/other/org/jgroups/tests/ConnectionMapDemo.java 2011-10-18 11:22:35.000000000 +0000 @@ -0,0 +1,136 @@ + +package org.jgroups.tests; + +import org.jgroups.Address; +import org.jgroups.blocks.AbstractConnectionMap; +import org.jgroups.blocks.TCPConnectionMap; +import org.jgroups.stack.IpAddress; +import org.jgroups.util.DefaultThreadFactory; +import org.jgroups.util.Util; + +import java.io.BufferedReader; +import java.io.InputStreamReader; + + +public class ConnectionMapDemo implements TCPConnectionMap.Receiver, AbstractConnectionMap.ConnectionMapListener { + TCPConnectionMap ct=null; + String dst_host=null; + int dst_port=0; + + + public void receive(Address sender, byte[] data, int offset, int length) { + String s=new String(data, offset, length); + System.out.println("<-- " + s + " (from " + sender + ')'); + } + + + public void connectionOpened(Address peer_addr, TCPConnectionMap.TCPConnection conn) { + System.out.println("** Connection to " + peer_addr + " opened"); + } + + public void connectionClosed(Address peer_addr) { + System.out.println("** Connection to " + peer_addr + " closed"); + } + + + public void start(int local_port, String dst_host, int dst_port, + long reaper_interval, long conn_expire_time) throws Exception { + BufferedReader in; + String line; + Address dest; + byte[] data; + + if(reaper_interval > 0 || conn_expire_time > 0) + ct=new TCPConnectionMap("TCPConnectionMap", + new DefaultThreadFactory(Util.getGlobalThreadGroup(), "test", true), + this, null, null, local_port, local_port, reaper_interval, conn_expire_time); + else + ct=new TCPConnectionMap("TCPConnectionMap", + new DefaultThreadFactory(Util.getGlobalThreadGroup(), "test", true), + null, this, null, null, local_port, local_port); + ct.addConnectionMapListener(this); + ct.start(); + this.dst_host=dst_host; + this.dst_port=dst_port; + + in=new BufferedReader(new InputStreamReader(System.in)); + while(true) { + try { + System.out.print("> "); + System.out.flush(); + line=in.readLine(); + if(line.startsWith("quit".toLowerCase()) || + line.startsWith("exit".toLowerCase())) + break; + if(line.startsWith("conns")) { + System.out.println(ct); + continue; + } + dest=new IpAddress(dst_host, dst_port); + data=line.getBytes(); + ct.send(dest, data, 0, data.length); + } + catch(Exception e) { + System.err.println(e); + } + } + ct.stop(); + } + + + public static void main(String[] args) { + String host="localhost", tmp; + int port=6666, local_port=5555; + long reaper_interval=0; + long conn_expire_time=0; + + for(int i=0; i < args.length; i++) { + tmp=args[i]; + if("-local_port".equals(tmp)) { + local_port=Integer.parseInt(args[++i]); + continue; + } + if("-remote_host".equals(tmp)) { + host=args[++i]; + continue; + } + if("-remote_port".equals(tmp)) { + port=Integer.parseInt(args[++i]); + continue; + } + if("-reaper_interval".equals(tmp)) { + reaper_interval=Long.parseLong(args[++i]); + continue; + } + if("-conn_expire_time".equals(tmp)) { + conn_expire_time=Long.parseLong(args[++i]); + continue; + } + help(); + return; + } + + try { + if(reaper_interval > 0 || conn_expire_time > 0) { + if(reaper_interval <= 0) reaper_interval=60000; + if(conn_expire_time <= 0) conn_expire_time=300000; + new ConnectionMapDemo().start(local_port, host, port, reaper_interval, conn_expire_time); + } + else { + new ConnectionMapDemo().start(local_port, host, port, 0, 0); + } + } + catch(Exception ex) { + System.err.println("ConnectionMapDemo.main(): " + ex); + } + } + + + static void help() { + System.out.println("ConnectionMapDemo [-help] [-local_port ] [-remote_host ] " + + "[-remote_port ] [-reaper_interval ] " + + "[-conn_expire_time