diff --git a/conf/jg-magic-map.xml b/conf/jg-magic-map.xml index 03bf6520ba4..47c2b9aa5ab 100644 --- a/conf/jg-magic-map.xml +++ b/conf/jg-magic-map.xml @@ -62,6 +62,7 @@ + diff --git a/conf/jg-protocol-ids.xml b/conf/jg-protocol-ids.xml index f74342acc8e..83139140163 100644 --- a/conf/jg-protocol-ids.xml +++ b/conf/jg-protocol-ids.xml @@ -64,6 +64,7 @@ + diff --git a/src/org/jgroups/ObjectMessage.java b/src/org/jgroups/ObjectMessage.java index efb73588e7a..ea0d65e8659 100644 --- a/src/org/jgroups/ObjectMessage.java +++ b/src/org/jgroups/ObjectMessage.java @@ -73,7 +73,7 @@ public ObjectMessage(Address dest, SizeStreamable obj) { public ObjectMessage setArray(ByteArray buf) {throw new UnsupportedOperationException();} public boolean isWrapped() {return isFlagSet(Flag.SERIALIZED);} - // reusing SERIALIZABLE + // reusing SERIALIZED public ObjectMessage setWrapped(boolean b) { if(b) setFlag(Flag.SERIALIZED); else clearFlag(Flag.SERIALIZED); @@ -145,8 +145,11 @@ public void readPayload(DataInput in) throws IOException, ClassNotFoundException } @Override protected Message copyPayload(Message copy) { - if(obj != null) + if(obj != null) { ((ObjectMessage)copy).setObject(obj); + if(isFlagSet(Flag.SERIALIZED)) + copy.setFlag(Flag.SERIALIZED); + } return copy; } diff --git a/src/org/jgroups/conf/ClassConfigurator.java b/src/org/jgroups/conf/ClassConfigurator.java index 273a811ef39..18c61db33d2 100644 --- a/src/org/jgroups/conf/ClassConfigurator.java +++ b/src/org/jgroups/conf/ClassConfigurator.java @@ -30,7 +30,7 @@ public class ClassConfigurator { protected static final String ID = "id"; protected static final String NAME = "name"; protected static final String EXTERNAL = "external"; - private static final int MAX_MAGIC_VALUE=100; + private static final int MAX_MAGIC_VALUE=124; private static final int MAX_PROT_ID_VALUE=256; private static final short MIN_CUSTOM_MAGIC_NUMBER=1024; private static final short MIN_CUSTOM_PROTOCOL_ID=512; @@ -292,7 +292,7 @@ protected static void alreadyInProtocolsMap(short prot_id, String classname) { * 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 + * @return a list 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=Util.getResourceAsStream(name, ClassConfigurator.class); diff --git a/src/org/jgroups/protocols/NAKACK4.java b/src/org/jgroups/protocols/NAKACK4.java index 86f02e510fe..f0da9280bbb 100644 --- a/src/org/jgroups/protocols/NAKACK4.java +++ b/src/org/jgroups/protocols/NAKACK4.java @@ -19,7 +19,7 @@ import static org.jgroups.conf.AttributeType.SCALAR; /** - * New multicast protocols based on fixed-size xmit windows and message ACKs + * New multicast protocol based on fixed-size xmit windows and message ACKs
* Details: https://issues.redhat.com/browse/JGRP-2780 * @author Bela Ban * @since 5.4 diff --git a/src/org/jgroups/protocols/REVERSE2.java b/src/org/jgroups/protocols/REVERSE2.java new file mode 100644 index 00000000000..4061bd11873 --- /dev/null +++ b/src/org/jgroups/protocols/REVERSE2.java @@ -0,0 +1,66 @@ +package org.jgroups.protocols; + +import org.jgroups.Message; +import org.jgroups.annotations.MBean; +import org.jgroups.stack.Protocol; +import org.jgroups.util.MessageBatch; + +import java.util.Deque; +import java.util.Iterator; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.function.Predicate; + +/** + * @author Bela Ban + * @since x.y + */ +@MBean(description="Reverts messages based on a filter and delivers them when told") +public class REVERSE2 extends Protocol { + protected volatile Predicate filter; // if set and true: queue messages + protected final Deque queue=new ConcurrentLinkedDeque<>(); + + public Predicate filter() {return filter;} + public REVERSE2 filter(Predicate f) {this.filter=f; return this;} + public int size() {return queue.size();} + + /** Delivers queued messages */ + public int deliver() { + Message msg; int count=0; + while((msg=queue.pollLast()) != null) { + up_prot.up(msg); + count++; + } + return count; + } + + @Override + public Object up(Message msg) { + if(filter != null && filter.test(msg)) { + queue.add(msg); + return null; + } + return up_prot.up(msg); + } + + @Override + public void up(MessageBatch batch) { + if(filter == null) { + up_prot.up(batch); + return; + } + for(Iterator it=batch.iterator(); it.hasNext();) { + Message msg=it.next(); + if(filter.test(msg)) { + it.remove(); + queue.add(msg); + } + } + if(!batch.isEmpty()) + up_prot.up(batch); + } + + @Override + public String toString() { + return String.format("%d msgs", queue.size()); + } +} diff --git a/src/org/jgroups/protocols/ReliableUnicast.java b/src/org/jgroups/protocols/ReliableUnicast.java new file mode 100644 index 00000000000..704ebbb219c --- /dev/null +++ b/src/org/jgroups/protocols/ReliableUnicast.java @@ -0,0 +1,1602 @@ +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.conf.AttributeType; +import org.jgroups.protocols.relay.RELAY; +import org.jgroups.stack.Protocol; +import org.jgroups.stack.ProtocolStack; +import org.jgroups.util.*; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.LongAdder; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static org.jgroups.Message.Flag.*; +import static org.jgroups.Message.TransientFlag.*; +import static org.jgroups.conf.AttributeType.SCALAR; +import static org.jgroups.protocols.UnicastHeader.DATA; + + +/** + * Base class for reliable unicast protocols + * @author Bela Ban + * @since 5.4 + */ +@MBean(description="Reliable unicast layer") +public abstract class ReliableUnicast extends Protocol implements AgeOutCache.Handler
{ + protected static final long DEFAULT_FIRST_SEQNO=Global.DEFAULT_FIRST_UNICAST_SEQNO; + protected static final long DEFAULT_XMIT_INTERVAL=500; + + /* ------------------------------------------ Properties ------------------------------------------ */ + + @Property(description="Time (in milliseconds) after which an idle incoming or outgoing connection is closed. The " + + "connection will get re-established when used again. 0 disables connection reaping. Note that this creates " + + "lingering connection entries, which increases memory over time.",type=AttributeType.TIME) + protected long conn_expiry_timeout=(long) 60000 * 2; + + @Property(description="Time (in ms) until a connection marked to be closed will get removed. 0 disables this", + type=AttributeType.TIME) + protected long conn_close_timeout=60_000 * 4; // 4 mins == TIME_WAIT timeout (= 2 * MSL) + + // @Property(description="Max time (in ms) after which a connection to a non-member is closed") + protected long max_retransmit_time=60 * 1000L; + + @Property(description="Interval (in milliseconds) at which messages in the send windows are resent",type=AttributeType.TIME) + protected long xmit_interval=DEFAULT_XMIT_INTERVAL; + + @Property(description="When true, the sender retransmits messages until ack'ed and the receiver asks for missing " + + "messages. When false, this is not done, but ack'ing and stale connection testing is still done. " + + "https://issues.redhat.com/browse/JGRP-2676") + protected boolean xmits_enabled=true; + + @Property(description="If true, trashes warnings about retransmission messages not found in the xmit_table (used for testing)") + protected boolean log_not_found_msgs=true; + + @Property(description="Min time (in ms) to elapse for successive SEND_FIRST_SEQNO messages to be sent to the same sender", + type=AttributeType.TIME) + protected long sync_min_interval=2000; + + @Property(description="Max number of messages to ask for in a retransmit request. 0 disables this and uses " + + "the max bundle size in the transport") + protected int max_xmit_req_size; + + @Property(description="The max size of a message batch when delivering messages. 0 is unbounded") + protected int max_batch_size; + + @Property(description="Increment seqno and send a message atomically. Reduces retransmissions. " + + "Description in doc/design/NAKACK4.txt ('misc')") + protected boolean send_atomically; + + @Property(description="Reuses the same message batch for delivery of regular messages (only done by a single " + + "thread anyway). Not advisable for buffers that can grow infinitely (NAKACK3)") + protected boolean reuse_message_batches=true; + + @Property(description="If true, a unicast message to self is looped back up on the same thread. Note that this may " + + "cause problems (e.g. deadlocks) in some applications, so make sure that your code can handle this. " + + "Issue: https://issues.redhat.com/browse/JGRP-2547") + protected boolean loopback; + + protected static final int DEFAULT_INITIAL_CAPACITY=128; + protected static final int DEFAULT_INCREMENT=512; + + /* --------------------------------------------- JMX ---------------------------------------------- */ + + @ManagedAttribute(description="Number of message sent",type=SCALAR) + protected final LongAdder num_msgs_sent=new LongAdder(); + @ManagedAttribute(description="Number of message received",type=SCALAR) + protected final LongAdder num_msgs_received=new LongAdder(); + @ManagedAttribute(description="Number of acks sent",type=SCALAR) + protected final LongAdder num_acks_sent=new LongAdder(); + @ManagedAttribute(description="Number of acks received",type=SCALAR) + protected final LongAdder num_acks_received=new LongAdder(); + @ManagedAttribute(description="Number of retransmitted messages",type=SCALAR) + protected final LongAdder num_xmits=new LongAdder(); + + @ManagedAttribute(description="Number of retransmit requests received",type=SCALAR) + protected final LongAdder xmit_reqs_received=new LongAdder(); + + @ManagedAttribute(description="Number of retransmit requests sent",type=SCALAR) + protected final LongAdder xmit_reqs_sent=new LongAdder(); + + @ManagedAttribute(description="Number of retransmit responses sent",type=SCALAR) + protected final LongAdder xmit_rsps_sent=new LongAdder(); + + @ManagedAttribute(description="Average batch size of messages delivered to the application") + protected final AverageMinMax avg_delivery_batch_size=new AverageMinMax(); + + @ManagedAttribute(description="True if sending a message can block at the transport level") + protected boolean sends_can_block=true; + + @ManagedAttribute(description="tracing is enabled or disabled for the given log",writable=true) + protected boolean is_trace=log.isTraceEnabled(); + + @ManagedAttribute(description="Whether or not a RELAY protocol was found below in the stack") + protected boolean relay_present; + + /* --------------------------------------------- Fields ------------------------------------------------ */ + + + protected final Map send_table=Util.createConcurrentMap(); + protected final Map recv_table=Util.createConcurrentMap(); + /** To cache batches for sending messages up the stack (https://issues.redhat.com/browse/JGRP-2841) */ + protected final Map cached_batches=Util.createConcurrentMap(); + + protected final ReentrantLock recv_table_lock=new ReentrantLock(); + + /** Used by the retransmit task to keep the last retransmitted seqno per member (applicable only + * for received messages (ReceiverEntry)): https://issues.redhat.com/browse/JGRP-1539 */ + protected final Map xmit_task_map=new ConcurrentHashMap<>(); + + /** RetransmitTask running every xmit_interval ms */ + protected Future xmit_task; + + protected volatile List
members=new ArrayList<>(11); + + protected TimeScheduler timer; // used for retransmissions + + protected volatile boolean running; + + protected short last_conn_id; + + protected AgeOutCache
cache; + + protected TimeService time_service; // for aging out of receiver and send entries + + protected final AtomicInteger timestamper=new AtomicInteger(0); // timestamping of ACKs / SEND_FIRST-SEQNOs + + /** Keep track of when a SEND_FIRST_SEQNO message was sent to a given sender */ + protected ExpiryCache
last_sync_sent; + + @ManagedAttribute(description="Number of unicast messages to self looped back up",type=SCALAR) + protected final LongAdder num_loopbacks=new LongAdder(); + + // Queues messages until a {@link ReceiverEntry} has been created. Queued messages are then removed from + // the cache and added to the ReceiverEntry + protected final MessageCache msg_cache=new MessageCache(); + + protected static final Message DUMMY_OOB_MSG=new EmptyMessage().setFlag(OOB); + + protected final Predicate drop_oob_and_dont_loopback_msgs_filter=msg -> + msg != null && msg != DUMMY_OOB_MSG + && (!msg.isFlagSet(OOB) || msg.setFlagIfAbsent(Message.TransientFlag.OOB_DELIVERED)) + && !(msg.isFlagSet(DONT_LOOPBACK) && Objects.equals(local_addr, msg.getSrc())); + + protected static final Predicate remove_filter=m -> m != null + && (m.isFlagSet(DONT_LOOPBACK) || m == DUMMY_OOB_MSG || m.isFlagSet(OOB_DELIVERED)); + + protected static final BiConsumer BATCH_ACCUMULATOR=MessageBatch::add; + + protected abstract Buffer createBuffer(long initial_seqno); + protected Buffer.Options sendOptions() {return Buffer.Options.DEFAULT();} + protected abstract boolean needToSendAck(Entry e, int num_acks); + + public long getNumLoopbacks() {return num_loopbacks.sum();} + + @ManagedAttribute(description="Returns the number of outgoing (send) connections",type=SCALAR) + public int getNumSendConnections() { + return send_table.size(); + } + + @ManagedAttribute(description="Returns the number of incoming (receive) connections",type=SCALAR) + public int getNumReceiveConnections() { + return recv_table.size(); + } + + @ManagedAttribute(description="Returns the total number of outgoing (send) and incoming (receive) connections",type=SCALAR) + public int getNumConnections() { + return getNumReceiveConnections() + getNumSendConnections(); + } + + @ManagedAttribute(description="Next seqno issued by the timestamper",type=SCALAR) + public int getTimestamper() {return timestamper.get();} + + @Property(name="level", description="Sets the level") + public T setLevel(String level) { + T retval= super.setLevel(level); + is_trace=log.isTraceEnabled(); + return retval; + } + public long getXmitInterval() {return xmit_interval;} + public ReliableUnicast setXmitInterval(long i) {xmit_interval=i; return this;} + public boolean isXmitsEnabled() {return xmits_enabled;} + public ReliableUnicast setXmitsEnabled(boolean b) {xmits_enabled=b; return this;} + public long getConnExpiryTimeout() {return conn_expiry_timeout;} + public ReliableUnicast setConnExpiryTimeout(long c) {this.conn_expiry_timeout=c; return this;} + public long getConnCloseTimeout() {return conn_close_timeout;} + public ReliableUnicast setConnCloseTimeout(long c) {this.conn_close_timeout=c; return this;} + public boolean logNotFoundMsgs() {return log_not_found_msgs;} + public ReliableUnicast logNotFoundMsgs(boolean l) {this.log_not_found_msgs=l; return this;} + public long getSyncMinInterval() {return sync_min_interval;} + public ReliableUnicast setSyncMinInterval(long s) {this.sync_min_interval=s; return this;} + public int getMaxXmitReqSize() {return max_xmit_req_size;} + public ReliableUnicast setMaxXmitReqSize(int m) {this.max_xmit_req_size=m; return this;} + public boolean reuseMessageBatches() {return reuse_message_batches;} + public ReliableUnicast reuseMessageBatches(boolean b) {this.reuse_message_batches=b; return this;} + public boolean sendsCanBlock() {return sends_can_block;} + public ReliableUnicast sendsCanBlock(boolean s) {this.sends_can_block=s; return this;} + public boolean sendAtomically() {return send_atomically;} + public ReliableUnicast sendAtomically(boolean f) {send_atomically=f; return this;} + public boolean loopback() {return loopback;} + public ReliableUnicast loopback(boolean b) {this.loopback=b; return this;} + public ReliableUnicast timeService(TimeService ts) {this.time_service=ts; return this;} // testing only! + public ReliableUnicast lastSync(ExpiryCache
c) {this.last_sync_sent=c; return this;} // testing only! + + + @ManagedOperation + public String printConnections() { + StringBuilder sb=new StringBuilder(); + 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(); + } + + @ManagedOperation(description="Prints the cached batches (if reuse_message_batches is true)") + public String printCachedBatches() { + return "\n" + cached_batches.entrySet().stream().map(e -> String.format("%s: %s", e.getKey(), e.getValue())) + .collect(Collectors.joining("\n")); + } + + @ManagedOperation(description="Prints the cached batches (if reuse_message_batches is true)") + public ReliableUnicast clearCachedBatches() { + cached_batches.clear(); + return this; + } + + @ManagedOperation(description="Adjust the capacity of cached batches") + public ReliableUnicast trimCachedBatches() { + cached_batches.values().forEach(mb -> mb.array().trimTo(DEFAULT_INITIAL_CAPACITY)); + return this; + } + + /** Don't remove! https://issues.redhat.com/browse/JGRP-2814 */ + @ManagedAttribute(type=SCALAR) @Deprecated + public long getNumMessagesSent() {return num_msgs_sent.sum();} + + /** Don't remove! https://issues.redhat.com/browse/JGRP-2814 */ + @ManagedAttribute(type=SCALAR) @Deprecated + public long getNumMessagesReceived() {return num_msgs_received.sum();} + + + public long getNumAcksSent() {return num_acks_sent.sum();} + public long getNumAcksReceived() {return num_acks_received.sum();} + public long getNumXmits() {return num_xmits.sum();} + 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", + type=AttributeType.TIME) + public ReliableUnicast setMaxRetransmitTime(long max_retransmit_time) { + this.max_retransmit_time=max_retransmit_time; + if(cache != null && max_retransmit_time > 0) + cache.setTimeout(max_retransmit_time); + return this; + } + + @ManagedAttribute(description="Is the retransmit task running") + public boolean isXmitTaskRunning() {return xmit_task != null && !xmit_task.isDone();} + + @ManagedAttribute(type=SCALAR) + 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; + } + + /** Used for testing only */ + public boolean hasSendConnectionTo(Address dest) { + Entry entry=send_table.get(dest); + return entry != null && entry.state() == State.OPEN; + } + + /** The number of messages in all Entry.sent_msgs tables (haven't received an ACK yet) */ + @ManagedAttribute(type=SCALAR) + public int getNumUnackedMessages() { + return accumulate(Buffer::size, send_table.values()); + } + + @ManagedAttribute(description="Total number of undelivered messages in all receive windows",type=SCALAR) + public int getXmitTableUndeliveredMessages() { + return accumulate(Buffer::size, recv_table.values()); + } + + @ManagedAttribute(description="Total number of missing messages in all receive windows",type=SCALAR) + public int getXmitTableMissingMessages() { + return accumulate(Buffer::numMissing, recv_table.values()); + } + + @ManagedAttribute(description="Total number of deliverable messages in all receive windows",type=SCALAR) + public int getXmitTableDeliverableMessages() { + return accumulate(Buffer::getNumDeliverable, recv_table.values()); + } + + @ManagedOperation(description="Prints the contents of the receive windows for all members") + public String printReceiveWindowMessages() { + StringBuilder ret=new StringBuilder(local_addr + ":\n"); + for(Map.Entry entry: recv_table.entrySet()) { + Address addr=entry.getKey(); + Buffer buf=entry.getValue().buf; + ret.append(addr).append(": ").append(buf).append('\n'); + } + return ret.toString(); + } + + @ManagedOperation(description="Prints the contents of the send windows for all members") + public String printSendWindowMessages() { + StringBuilder ret=new StringBuilder(local_addr + ":\n"); + for(Map.Entry entry: send_table.entrySet()) { + Address addr=entry.getKey(); + Buffer buf=entry.getValue().buf; + ret.append(addr).append(": ").append(buf).append('\n'); + } + return ret.toString(); + } + + public void resetStats() { + avg_delivery_batch_size.clear(); + Stream.of(num_msgs_sent, num_msgs_received, num_acks_sent, num_acks_received, num_xmits, xmit_reqs_received, + xmit_reqs_sent, xmit_rsps_sent, num_loopbacks).forEach(LongAdder::reset); + send_table.values().stream().map(e -> e.buf).forEach(Buffer::resetStats); + recv_table.values().stream().map(e -> e.buf).forEach(Buffer::resetStats); + } + + public void init() throws Exception { + super.init(); + TP transport=getTransport(); + sends_can_block=transport instanceof TCP; // UDP and TCP_NIO2 won't block + time_service=transport.getTimeService(); + if(time_service == null) + throw new IllegalStateException("time service from transport is null"); + last_sync_sent=new ExpiryCache<>(sync_min_interval); + + // max bundle size (minus overhead) divided by times bits per long + // Example: for 8000 missing messages, SeqnoList has a serialized size of 1012 bytes, for 64000 messages, the + // serialized size is 8012 bytes. Therefore, for a serialized size of 64000 bytes, we can retransmit a max of + // 8 * 64000 = 512'000 seqnos + // see SeqnoListTest.testSerialization3() + int estimated_max_msgs_in_xmit_req=(transport.getBundler().getMaxSize() -50) * Global.LONG_SIZE; + int old_max_xmit_size=max_xmit_req_size; + if(max_xmit_req_size <= 0) + max_xmit_req_size=estimated_max_msgs_in_xmit_req; + else + max_xmit_req_size=Math.min(max_xmit_req_size, estimated_max_msgs_in_xmit_req); + if(old_max_xmit_size != max_xmit_req_size) + log.trace("%s: set max_xmit_req_size from %d to %d", local_addr, old_max_xmit_size, max_xmit_req_size); + + if(xmit_interval <= 0) { + log.warn("%s: xmit_interval (%d) has to be > 0; setting it to the default of %d", + local_addr, xmit_interval, DEFAULT_XMIT_INTERVAL); + xmit_interval=DEFAULT_XMIT_INTERVAL; + } + + if(xmits_enabled == false) { + // https://issues.redhat.com/browse/JGRP-2676 + RejectedExecutionHandler handler=transport.getThreadPool().getRejectedExecutionHandler(); + if(handler != null && !isCallerRunsHandler(handler)) { + log.warn("%s: xmits_enabled == false requires a CallerRunsPolicy in the thread pool; replacing %s", + local_addr, handler.getClass().getSimpleName()); + transport.getThreadPool().setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + } + Class cl=RED.class; + if(stack.findProtocol(cl) != null) { + String e=String.format("found %s: when retransmission is disabled (xmits_enabled=false), this can lead " + + "to message loss. Please remove %s or enable retransmission", + cl.getSimpleName(), cl.getSimpleName()); + throw new IllegalStateException(e); + } + } + relay_present=ProtocolStack.findProtocol(this.down_prot, true, RELAY.class) != null; + } + + public void start() throws Exception { + msg_cache.clear(); + 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); + running=true; + startRetransmitTask(); + } + + public void stop() { + sendPendingAcks(); + running=false; + stopRetransmitTask(); + xmit_task_map.clear(); + removeAllConnections(); + msg_cache.clear(); + } + + protected void handleUpEvent(Address sender, Message msg, UnicastHeader hdr) { + try { + switch(hdr.type) { + case DATA: // received regular message + throw new IllegalStateException("header of type DATA is not supposed to be handled by this method"); + case UnicastHeader.ACK: // received ACK for previously sent message + handleAckReceived(sender, hdr.seqno, hdr.conn_id, hdr.timestamp()); + break; + case UnicastHeader.SEND_FIRST_SEQNO: + handleResendingOfFirstMessage(sender, hdr.timestamp()); + break; + case UnicastHeader.XMIT_REQ: // received ACK for previously sent message + handleXmitRequest(sender, msg.getObject()); + break; + case UnicastHeader.CLOSE: + log.trace("%s <-- %s: CLOSE(conn-id=%s)", local_addr, sender, hdr.conn_id); + ReceiverEntry entry=recv_table.get(sender); + if(entry != null && entry.connId() == hdr.conn_id) { + recv_table.remove(sender, entry); + log.trace("%s: removed receive connection for %s", local_addr, sender); + } + break; + default: + log.error(Util.getMessage("TypeNotKnown"), local_addr, hdr.type); + break; + } + } + catch(Throwable t) { // we cannot let an exception terminate the processing of this batch + log.error(Util.getMessage("FailedHandlingEvent"), local_addr, t); + } + } + + public Object up(Message msg) { + Address dest=msg.dest(), sender=msg.src(); + if(dest == null || dest.isMulticast() || msg.isFlagSet(NO_RELIABILITY)) // only handle unicast messages + return up_prot.up(msg); // pass up + + UnicastHeader hdr=msg.getHeader(this.id); + if(hdr == null) + return up_prot.up(msg); + switch(hdr.type) { + case DATA: // received regular message + if(is_trace) + log.trace("%s <-- %s: DATA(#%d, conn_id=%d%s)", local_addr, sender, hdr.seqno, hdr.conn_id, hdr.first? ", first" : ""); + if(Objects.equals(local_addr, sender)) + handleDataReceivedFromSelf(sender, hdr.seqno, msg); + else + handleDataReceived(sender, hdr.seqno, hdr.conn_id, hdr.first, msg); + break; // we pass the deliverable message up in handleDataReceived() + default: + handleUpEvent(sender, msg, hdr); + break; + } + return null; + } + + public void up(MessageBatch batch) { + if(batch.dest() == null || batch.dest().isMulticast()) { // not a unicast batch + up_prot.up(batch); + return; + } + final Address sender=batch.sender(); + if(local_addr == null || local_addr.equals(sender)) { + Entry entry=local_addr != null? send_table.get(local_addr) : null; + if(entry != null) + handleBatchFromSelf(batch, entry); + return; + } + + int size=batch.size(); + short highest_conn_id=0; + long lowest_seqno=-1; // -1 means uninitialized (first: 1) + boolean first_seqno=false; + Map>> msgs=new ConcurrentHashMap<>(); + for(Iterator it=batch.iterator(); it.hasNext();) { + Message msg=it.next(); + UnicastHeader hdr; + if(msg == null || msg.isFlagSet(NO_RELIABILITY) || (hdr=msg.getHeader(id)) == null) + continue; + it.remove(); // remove the message from the batch, so it won't be passed up the stack + if(hdr.type != DATA) { + handleUpEvent(msg.getSrc(), msg, hdr); + continue; + } + highest_conn_id=max(hdr.conn_id, highest_conn_id); + if(lowest_seqno < 0) { + lowest_seqno=hdr.seqno; + first_seqno=hdr.first; + } + else { + if(lowest_seqno > hdr.seqno) { + lowest_seqno=hdr.seqno; + first_seqno=hdr.first; + } + } + List> list=msgs.computeIfAbsent(hdr.conn_id, k -> new FastArray<>(size)); + list.add(new LongTuple<>(hdr.seqno(), msg)); + } + if(msgs.isEmpty()) { + up_prot.up(batch); + return; + } + + List> list=msgs.get(highest_conn_id); + if(msgs.size() > 1) { // more than 1 conn_id (this should not normally be the case) + msgs.keySet().retainAll(List.of(highest_conn_id)); + Tuple tuple=getLowestSeqno(id, list); + lowest_seqno=tuple.getVal1(); + first_seqno=tuple.getVal2(); + } + + ReceiverEntry entry=getReceiverEntry(sender, lowest_seqno, first_seqno, highest_conn_id, batch.dest()); + if(entry == null) { + if(!list.isEmpty()) { + for(LongTuple tuple: list) { + msg_cache.add(sender, tuple.getVal2()); + log.trace("%s: cached %s#%d", local_addr, sender, tuple.getVal1()); + } + } + return; + } + boolean added_queued_msgs=false; + if(!msg_cache.isEmpty()) { // quick and dirty check + Collection queued_msgs=msg_cache.drain(sender); + if(queued_msgs != null) { + addQueuedMessages(sender, entry, queued_msgs); + added_queued_msgs=true; + } + } + + if(added_queued_msgs || (list != null && !list.isEmpty())) + handleBatchReceived(entry, sender, list, batch.mode() == MessageBatch.Mode.OOB, batch.dest()); + + if(!batch.isEmpty()) + up_prot.up(batch); + } + + protected void handleBatchFromSelf(MessageBatch batch, Entry entry) { + List> list=new ArrayList<>(batch.size()); + for(Iterator it=batch.iterator(); it.hasNext();) { + Message msg=it.next(); + UnicastHeader hdr; + if(msg == null || msg.isFlagSet(NO_RELIABILITY) || (hdr=msg.getHeader(id)) == null) + continue; + it.remove(); // remove the message from the batch, so it won't be passed up the stack + + if(hdr.type != DATA) { + handleUpEvent(msg.getSrc(), msg, hdr); + continue; + } + + if(entry.conn_id != hdr.conn_id) { + it.remove(); + continue; + } + list.add(new LongTuple<>(hdr.seqno(), msg)); + } + + if(!list.isEmpty()) { + if(is_trace) + log.trace("%s <-- %s: DATA(%s)", local_addr, batch.sender(), printMessageList(list)); + + int len=list.size(); + Buffer win=entry.buf; + update(entry, len); + + // OOB msg is passed up. When removed, we discard it. Affects ordering: https://issues.redhat.com/browse/JGRP-379 + if(batch.mode() == MessageBatch.Mode.OOB) { + MessageBatch oob_batch=new MessageBatch(local_addr, batch.sender(), batch.clusterName(), batch.multicast(), MessageBatch.Mode.OOB, len); + for(LongTuple tuple: list) { + long seq=tuple.getVal1(); + Message msg=win.get(seq); // we *have* to get the message, because loopback means we didn't add it to win ! + if(msg != null && msg.isFlagSet(OOB) && msg.setFlagIfAbsent(Message.TransientFlag.OOB_DELIVERED)) + oob_batch.add(msg); + } + deliverBatch(oob_batch, entry, batch.dest()); + } + removeAndDeliver(entry, batch.sender(), batch.clusterName(), batch.capacity()); + } + if(!batch.isEmpty()) + up_prot.up(batch); + } + + public Object down(Event evt) { + switch (evt.getType()) { + + case Event.VIEW_CHANGE: // remove connections to peers that are not members anymore ! + View view=evt.getArg(); + List
new_members=view.getMembers(); + Set
non_members=new HashSet<>(send_table.keySet()); + non_members.addAll(recv_table.keySet()); + members=new_members; + new_members.forEach(non_members::remove); + if(cache != null) + cache.removeAll(new_members); + + if(!non_members.isEmpty()) { + log.trace("%s: closing connections to non members %s", local_addr, non_members); + // remove all non-members, except those from remote sites: https://issues.redhat.com/browse/JGRP-2729 + non_members.stream().filter(this::isLocal).forEach(this::closeConnection); + } + if(!new_members.isEmpty()) { + for(Address mbr: new_members) { + Entry e=send_table.get(mbr); + if(e != null && e.state() == State.CLOSING) + e.state(State.OPEN); + e=recv_table.get(mbr); + if(e != null && e.state() == State.CLOSING) + e.state(State.OPEN); + } + } + xmit_task_map.keySet().retainAll(new_members); + last_sync_sent.removeExpiredElements(); + cached_batches.keySet().retainAll(new_members); + break; + } + + return down_prot.down(evt); // Pass on to the layer below us + } + + public Object down(Message msg) { + Address dst=msg.getDest(); + if(dst == null || dst.isMulticast() || msg.isFlagSet(NO_RELIABILITY)) // only handle unicast messages + return down_prot.down(msg); + + if(!running) { + log.trace("%s: discarded message as start() has not yet been called, message: %s", local_addr, msg); + return null; + } + + 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 + + // if the destination is the local site master, we change the it to be the local address. The reason is that + // the message will be looped back and the send-table entry (msg.dest) should match msg.src of the + // received message (https://issues.redhat.com/browse/JGRP-2729) + if(isLocalSiteMaster(dst)) + msg.dest(dst=local_addr); + + if(loopback && Objects.equals(local_addr, dst)) {// https://issues.redhat.com/browse/JGRP-2547 + if(msg.isFlagSet(DONT_LOOPBACK)) + return null; + num_loopbacks.increment(); + return up_prot.up(msg); + } + + SenderEntry entry=getSenderEntry(dst); + boolean dont_loopback_set=msg.isFlagSet(DONT_LOOPBACK) && dst.equals(local_addr); + send(msg, entry, dont_loopback_set); + num_msgs_sent.increment(); + return null; // the message was already sent down the stack in send() + } + + protected boolean isLocalSiteMaster(Address dest) { + // quick check to avoid the use of 'instanceof'; will be removed once https://bugs.openjdk.org/browse/JDK-8180450 + // has been fixed (in Java 22, should be backported to older versions) + if(relay_present && dest.isSiteMaster()) { + Object ret=down_prot.down(new Event(Event.IS_LOCAL_SITEMASTER, dest)); + return ret != null && (Boolean)ret; + } + return false; + } + + protected boolean isLocal(Address addr) { + // quick check to avoid the use of 'instanceof'; will be removed once https://bugs.openjdk.org/browse/JDK-8180450 + // has been fixed (in Java 22, should be backported to older versions) + if(relay_present && addr.isSiteAddress()) { + Object ret=down_prot.down(new Event(Event.IS_LOCAL, addr)); + return ret != null && (Boolean)ret; + } + return true; + } + + /** + * 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 be used ! + */ + public void closeConnection(Address mbr) { + closeSendConnection(mbr); + closeReceiveConnection(mbr); + } + + public void closeSendConnection(Address mbr) { + SenderEntry entry=send_table.get(mbr); + if(entry != null) + entry.state(State.CLOSING); + } + + public void closeReceiveConnection(Address mbr) { + ReceiverEntry entry=recv_table.get(mbr); + if(entry != null) + entry.state(State.CLOSING); + } + + public void removeSendConnection(Address mbr) { + SenderEntry entry=send_table.remove(mbr); + if(entry != null) { + entry.state(State.CLOSED); + if(members.contains(mbr)) + sendClose(mbr, entry.connId()); + } + } + + public void removeSendConnection(Predicate
pred) { + for(Iterator> it=send_table.entrySet().iterator(); it.hasNext();) { + Map.Entry e=it.next(); + Address addr=e.getKey(); + if(pred.test(addr)) { + e.getValue().state(State.CLOSED); + it.remove(); + } + } + } + + public void removeReceiveConnection(Address mbr) { + sendPendingAcks(); + ReceiverEntry entry=recv_table.remove(mbr); + if(entry != null) + entry.state(State.CLOSED); + } + + /** + * 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() { + send_table.clear(); + recv_table.clear(); + } + + /** Sends a retransmit request to the given sender */ + protected void retransmit(SeqnoList missing, Address sender, Address real_dest) { + Message xmit_msg=new ObjectMessage(sender, missing).setFlag(OOB, NO_FC) + .putHeader(id, UnicastHeader.createXmitReqHeader()); + if(!Objects.equals(local_addr, real_dest)) + xmit_msg.setSrc(real_dest); + if(is_trace) + log.trace("%s --> %s: XMIT_REQ(%s)", local_addr, sender, missing); + down_prot.down(xmit_msg); + xmit_reqs_sent.add(missing.size()); + } + + /** Called by the sender to resend messages for which no ACK has been received yet */ + protected void retransmit(Message msg) { + if(is_trace) { + UnicastHeader hdr=msg.getHeader(id); + long seqno=hdr != null? hdr.seqno : -1; + log.trace("%s --> %s: resending(#%d)", local_addr, msg.getDest(), seqno); + } + resend(msg); + num_xmits.increment(); + } + + /** Called by AgeOutCache, to removed expired connections */ + public void expired(Address key) { + if(key != null) { + log.debug("%s: removing expired connection to %s", local_addr, key); + closeConnection(key); + } + } + + /** + * Check whether the hashtable contains an entry e for {@code sender} (create if not). If + * e.received_msgs is null and {@code 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(final Address sender, long seqno, short conn_id, boolean first, final Message msg) { + ReceiverEntry entry=getReceiverEntry(sender, seqno, first, conn_id, msg.dest()); + if(entry == null) { + msg_cache.add(sender, msg); + log.trace("%s: cached %s#%d", local_addr, sender, seqno); + return; + } + if(!msg_cache.isEmpty()) { // quick and dirty check + Collection queued_msgs=msg_cache.drain(sender); + if(queued_msgs != null) + addQueuedMessages(sender, entry, queued_msgs); + } + addMessage(entry, sender, seqno, msg); + removeAndDeliver(entry, sender, null, 1); + } + + protected void addMessage(ReceiverEntry entry, Address sender, long seqno, Message msg) { + final Buffer win=entry.buf(); + update(entry, 1); + boolean oob=msg.isFlagSet(OOB), + added=win.add(seqno, oob? DUMMY_OOB_MSG : msg); // adding the same dummy OOB msg saves space (we won't remove it) + + // An OOB message is passed up immediately. Later, when remove() is called, we discard it. This affects ordering ! + // https://issues.redhat.com/browse/JGRP-377 + if(oob && added) + deliverMessage(msg, sender, seqno); + if(needToSendAck(entry, 1)) + sendAck(sender, entry, msg.dest()); + } + + protected void addQueuedMessages(final Address sender, final ReceiverEntry entry, Collection queued_msgs) { + for(Message msg: queued_msgs) { + UnicastHeader hdr=msg.getHeader(this.id); + if(hdr.conn_id != entry.conn_id) { + log.warn("%s: dropped queued message %s#%d as its conn_id (%d) did not match (entry.conn_id=%d)", + local_addr, sender, hdr.seqno, hdr.conn_id, entry.conn_id); + continue; + } + addMessage(entry, sender, hdr.seqno(), msg); + } + } + + /** Called when the sender of a message is the local member. In this case, we don't need to add the message + * to the table as the sender already did that */ + protected void handleDataReceivedFromSelf(final Address sender, long seqno, Message msg) { + Entry entry=send_table.get(sender); + if(entry == null || entry.state() == State.CLOSED) { + log.warn("%s: entry not found for %s; dropping message", local_addr, sender); + return; + } + + update(entry, 1); + final Buffer win=entry.buf; + + // An OOB message is passed up immediately. Later, when remove() is called, we discard it. + // This affects ordering ! JIRA: https://issues.redhat.com/browse/JGRP-377 + if(msg.isFlagSet(OOB)) { + msg=win.get(seqno); // we *have* to get a message, because loopback means we didn't add it to win ! + if(msg != null && msg.isFlagSet(OOB) && msg.setFlagIfAbsent(Message.TransientFlag.OOB_DELIVERED)) + deliverMessage(msg, sender, seqno); + } + removeAndDeliver(entry, sender, null, 1); // there might be more messages to deliver + } + + protected void handleBatchReceived(final ReceiverEntry entry, Address sender, List> msgs, + boolean oob, Address original_dest) { + if(is_trace) + log.trace("%s <-- %s: DATA(%s)", local_addr, sender, printMessageList(msgs)); + + int batch_size=msgs.size(); + Buffer buf=entry.buf; + + // adds all messages to the table, removing messages from 'msgs' which could not be added (already present) + boolean added=buf.add(msgs, oob, oob? DUMMY_OOB_MSG : null); + update(entry, batch_size); + entry.sendAck(); + + // OOB msg is passed up. When removed, we discard it. Affects ordering: https://issues.redhat.com/browse/JGRP-379 + if(added && oob) { + MessageBatch oob_batch=new MessageBatch(local_addr, sender, null, false, MessageBatch.Mode.OOB, msgs.size()); + for(LongTuple tuple: msgs) + oob_batch.add(tuple.getVal2()); + deliverBatch(oob_batch, entry, original_dest); + } + removeAndDeliver(entry, sender, null, msgs.size()); + } + + + /** + * Try to remove as many messages as possible from the table as pass them up. + * Prevents concurrent passing up of messages by different threads (https://issues.redhat.com/browse/JGRP-198); + * 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 in the + * order in which they were sent + */ + protected void removeAndDeliver(Entry entry, Address sender, AsciiString cluster, int min_size) { + Buffer buf=entry.buf(); + AtomicInteger adders=buf.getAdders(); + if(adders.getAndIncrement() != 0) + return; + + AsciiString cl=cluster != null? cluster : getTransport().getClusterNameAscii(); + int cap=Math.max(Math.max(Math.max(buf.size(), max_batch_size), min_size), DEFAULT_INITIAL_CAPACITY); + MessageBatch batch=reuse_message_batches && cl != null? + cached_batches.computeIfAbsent(sender, __ -> new MessageBatch(cap).dest(local_addr).sender(sender).cluster(cl) + .mcast(true).increment(DEFAULT_INCREMENT)) + : new MessageBatch(cap).dest(local_addr).sender(sender).cluster(cl).multicast(true).increment(DEFAULT_INCREMENT); + Supplier batch_creator=() -> batch; + MessageBatch mb=null; + do { + try { + batch.reset(); // sets index to 0: important as batch delivery may not remove messages from batch! + mb=buf.removeMany(true, max_batch_size, drop_oob_and_dont_loopback_msgs_filter, + batch_creator, BATCH_ACCUMULATOR); + } + catch(Throwable t) { + log.error("%s: failed removing messages from table for %s: %s", local_addr, sender, t); + } + if(!batch.isEmpty()) { + // batch is guaranteed to NOT contain any OOB messages as the drop_oob_msgs_filter above removed them + deliverBatch(batch, entry, null); // catches Throwable + } + } + while(mb != null || adders.decrementAndGet() != 0); + } + + protected String printMessageList(List> list) { + StringBuilder sb=new StringBuilder(); + int size=list.size(); + Message first=size > 0? list.get(0).getVal2() : null, second=size > 1? list.get(size-1).getVal2() : first; + UnicastHeader hdr; + if(first != null) { + hdr=first.getHeader(id); + if(hdr != null) + sb.append("#" + hdr.seqno); + } + if(second != null) { + hdr=second.getHeader(id); + if(hdr != null) + sb.append(" - #" + hdr.seqno); + } + return sb.toString(); + } + + protected ReceiverEntry getReceiverEntry(Address sender, long seqno, boolean first, short conn_id, Address real_dest) { + ReceiverEntry entry=recv_table.get(sender); + if(entry != null && entry.connId() == conn_id) + return entry; + return _getReceiverEntry(sender, seqno, first, conn_id, real_dest); + } + + // public for unit testing - don't use in app code! + public ReceiverEntry _getReceiverEntry(Address sender, long seqno, boolean first, short conn_id, Address real_dest) { + ReceiverEntry entry; + recv_table_lock.lock(); + try { + entry=recv_table.get(sender); + if(entry == null) { + if(first) + return createReceiverEntry(sender, seqno, conn_id, real_dest); + else { + recv_table_lock.unlock(); + sendRequestForFirstSeqno(sender, real_dest); // drops the message and returns (see below) + return null; + } + } + // entry != null + return compareConnIds(conn_id, entry.connId(), first, entry, sender, seqno, real_dest); + } + finally { + if(recv_table_lock.isHeldByCurrentThread()) + recv_table_lock.unlock(); + } + } + + protected ReceiverEntry compareConnIds(short other, short mine, boolean first, ReceiverEntry e, + Address sender, long seqno, Address real_dest) { + if(other == mine) + return e; + if(other < mine) + return null; + // other_conn_id > my_conn_id + if(first) { + log.trace("%s: other conn_id (%d) > mine (%d); creating new receiver window", local_addr, other, mine); + recv_table.remove(sender); + return createReceiverEntry(sender, seqno, other, real_dest); + } + else { + log.trace("%s: other conn_id (%d) > mine (%d) (!first); asking for first message", local_addr, other, mine); + recv_table_lock.unlock(); + sendRequestForFirstSeqno(sender, real_dest); // drops the message and returns (see below) + return null; + } + } + + protected SenderEntry getSenderEntry(Address dst) { + SenderEntry entry=send_table.get(dst); + if(entry == null || entry.state() == State.CLOSED) { + if(entry != null) + send_table.remove(dst, entry); + entry=send_table.computeIfAbsent(dst, k -> new SenderEntry(getNewConnectionId())); + log.trace("%s: created sender window for %s (conn-id=%s)", local_addr, dst, entry.connId()); + if(cache != null && !members.contains(dst)) + cache.add(dst); + } + if(entry.state() == State.CLOSING) + entry.state(State.OPEN); + return entry; + } + + protected ReceiverEntry createReceiverEntry(Address sender, long seqno, short conn_id, Address dest) { + ReceiverEntry entry=recv_table.computeIfAbsent(sender, k -> new ReceiverEntry(createBuffer(seqno-1), conn_id, dest)); + log.trace("%s: created receiver window for %s at seqno=#%d for conn-id=%d", local_addr, sender, seqno, conn_id); + return entry; + } + + /** Add the ACK to hashtable.sender.sent_msgs */ + protected void handleAckReceived(Address sender, long seqno, short conn_id, int timestamp) { + if(is_trace) + log.trace("%s <-- %s: ACK(#%d, conn-id=%d, ts=%d)", local_addr, sender, seqno, conn_id, timestamp); + SenderEntry entry=send_table.get(sender); + if(entry != null && entry.connId() != conn_id) { + log.trace("%s: my conn_id (%d) != received conn_id (%d); discarding ACK", local_addr, entry.connId(), conn_id); + return; + } + + Buffer win=entry != null? entry.buf : null; + if(win != null && entry.updateLastTimestamp(timestamp)) { + win.purge(seqno, true); // removes all messages <= seqno (forced purge) + num_acks_received.increment(); + } + } + + /** We need to resend the first message with our conn_id */ + protected void handleResendingOfFirstMessage(Address sender, int timestamp) { + log.trace("%s <-- %s: SEND_FIRST_SEQNO", local_addr, sender); + SenderEntry entry=send_table.get(sender); + Buffer win=entry != null? entry.buf : null; + if(win == null) { + log.warn(Util.getMessage("SenderNotFound"), local_addr, sender); + return; + } + + if(!entry.updateLastTimestamp(timestamp)) + return; + + Message rsp=win.get(win.low() +1); + if(rsp != null) { + // 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://issues.redhat.com/browse/JGRP-965) + Message copy=rsp.copy(true, true); + UnicastHeader hdr=copy.getHeader(this.id); + UnicastHeader newhdr=hdr.copy(); + newhdr.first=true; + copy.putHeader(this.id, newhdr).setFlag(DONT_BLOCK); + resend(copy); + } + } + + protected void handleXmitRequest(Address sender, SeqnoList missing) { + if(is_trace) + log.trace("%s <-- %s: XMIT(#%s)", local_addr, sender, missing); + + SenderEntry entry=send_table.get(sender); + xmit_reqs_received.add(missing.size()); + Buffer win=entry != null? entry.buf : null; + if(win == null) + return; + for(Long seqno: missing) { + Message msg=win.get(seqno); + if(msg == null) { + if(log.isWarnEnabled() && log_not_found_msgs && !local_addr.equals(sender) && seqno > win.low()) + log.warn(Util.getMessage("MessageNotFound"), local_addr, sender, seqno); + continue; + } + // This will change the original message, but that's fine as retransmissions will have DONT_BLOCK anyway + msg.setFlag(DONT_BLOCK); + resend(msg); + xmit_rsps_sent.increment(); + } + } + + protected void send(Message msg, SenderEntry entry, boolean dont_loopback_set) { + Buffer buf=entry.buf; + long seqno=entry.seqno.getAndIncrement(); + short send_conn_id=entry.connId(); + msg.putHeader(this.id,UnicastHeader.createDataHeader(seqno, send_conn_id,seqno == DEFAULT_FIRST_SEQNO)); + final Lock lock=send_atomically? buf.lock() : null; + if(lock != null) { + // As described in doc/design/NAKACK4 ("misc"): if we hold the lock while (1) getting the seqno for a message, + // (2) adding it to the send window and (3) sending it (so it is sent by the transport in that order). + // Messages should be received in order and therefore not require retransmissions. + // Passing the message down should not block with TransferQueueBundler (default), as drop_when_full==true + //noinspection LockAcquiredButNotSafelyReleased + lock.lock(); + } + try { + addToSendWindow(buf, seqno, msg, dont_loopback_set? remove_filter : null); + down_prot.down(msg); // if this fails, since msg is in sent_msgs, it can be retransmitted + if(conn_expiry_timeout > 0) + entry.update(); + if(dont_loopback_set) + buf.purge(buf.getHighestDeliverable()); + } + finally { + if(lock != null) + lock.unlock(); + } + if(is_trace) { + StringBuilder sb=new StringBuilder(); + sb.append(local_addr).append(" --> ").append(msg.dest()).append(": DATA(").append("#").append(seqno). + append(", conn_id=").append(send_conn_id); + if(seqno == DEFAULT_FIRST_SEQNO) sb.append(", first"); + sb.append(')'); + log.trace(sb); + } + } + + /** + * Adds the message to the send window. The loop tries to handle temporary OOMEs by retrying if add() failed. + */ + protected void addToSendWindow(Buffer win, long seq, Message msg, Predicate filter) { + long sleep=10; + do { + try { + win.add(seq, msg, filter, sendOptions()); + break; + } + catch(Throwable t) { + if(running) { + Util.sleep(sleep); + sleep=Math.min(5000, sleep*2); + } + } + } + while(running); + } + + + protected void resend(Message msg) { // needed for byteman ProtPerf script - don't remove! + down_prot.down(msg); + } + + protected void deliverMessage(final Message msg, final Address sender, final long seqno) { + if(is_trace) + log.trace("%s: delivering %s#%s", local_addr, sender, seqno); + try { + up_prot.up(msg); + } + catch(Throwable t) { + log.warn(Util.getMessage("FailedToDeliverMsg"), local_addr, msg.isFlagSet(OOB) ? + "OOB message" : "message", msg, t); + } + } + + protected void deliverBatch(MessageBatch batch, Entry entry, Address original_dest) { + try { + if(batch.isEmpty()) + return; + if(is_trace) { + Message first=batch.first(), last=batch.last(); + StringBuilder sb=new StringBuilder(local_addr + ": delivering"); + if(first != null && last != null) { + UnicastHeader hdr1=first.getHeader(id), hdr2=last.getHeader(id); + if(hdr1 != null && hdr2 != null) + sb.append(" #").append(hdr1.seqno).append(" - #").append(hdr2.seqno); + } + sb.append(" (" + batch.size()).append(" messages)"); + log.trace(sb); + } + if(needToSendAck(entry, batch.size())) + sendAck(batch.sender(), entry, original_dest); + up_prot.up(batch); + if(stats) + avg_delivery_batch_size.add(batch.size()); + } + catch(Throwable t) { + log.warn(Util.getMessage("FailedToDeliverMsg"), local_addr, "batch", batch, t); + } + } + + protected long getTimestamp() { + return time_service.timestamp(); + } + + public void startRetransmitTask() { + if(xmit_task == null || xmit_task.isDone()) + xmit_task=timer.scheduleWithFixedDelay(new RetransmitTask(), 0, xmit_interval, MILLISECONDS, sends_can_block); + } + + public void stopRetransmitTask() { + if(xmit_task != null) { + xmit_task.cancel(true); + xmit_task=null; + } + } + + protected static boolean isCallerRunsHandler(RejectedExecutionHandler h) { + return h instanceof ThreadPoolExecutor.CallerRunsPolicy || + (h instanceof ShutdownRejectedExecutionHandler + && ((ShutdownRejectedExecutionHandler)h).handler() instanceof ThreadPoolExecutor.CallerRunsPolicy); + } + + protected void sendAck(Address dst, Entry entry, Address real_dest) { // real_dest required by RELAY3 + if(!running) // if we are disconnected, then don't send any acks which throw exceptions on shutdown + return; + long seqno=entry.buf.highestDelivered(); + short conn_id=entry.connId(); + Message ack=new EmptyMessage(dst).setFlag(DONT_BLOCK).setFlag(NO_FC) + .putHeader(this.id, UnicastHeader.createAckHeader(seqno, conn_id, timestamper.incrementAndGet())); + if(real_dest != null && !Objects.equals(local_addr, real_dest)) + ack.setSrc(real_dest); + if(is_trace) + log.trace("%s --> %s: ACK(#%d)", local_addr, dst, seqno); + try { + down_prot.down(ack); + num_acks_sent.increment(); + } + catch(Throwable t) { + log.error(Util.getMessage("FailedSendingAck"), local_addr, seqno, dst, t); + } + } + + + protected synchronized short getNewConnectionId() { + 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; + } + + + protected void sendRequestForFirstSeqno(Address dest, Address original_dest) { + if(last_sync_sent.addIfAbsentOrExpired(dest)) { + Message msg=new EmptyMessage(dest).setFlag(OOB, NO_FC).setFlag(DONT_BLOCK) + .putHeader(this.id, UnicastHeader.createSendFirstSeqnoHeader(timestamper.incrementAndGet())); + if(!Objects.equals(local_addr, original_dest)) + msg.setSrc(original_dest); + log.trace("%s --> %s: SEND_FIRST_SEQNO", local_addr, dest); + down_prot.down(msg); + } + } + + public void sendClose(Address dest, short conn_id) { + Message msg=new EmptyMessage(dest).putHeader(id, UnicastHeader.createCloseHeader(conn_id)).setFlag(NO_FC); + log.trace("%s --> %s: CLOSE(conn-id=%d)", local_addr, dest, conn_id); + down_prot.down(msg); + } + + @ManagedOperation(description="Closes connections that have been idle for more than conn_expiry_timeout ms") + public void closeIdleConnections() { + // close expired connections in send_table + for(Map.Entry entry: send_table.entrySet()) { + SenderEntry val=entry.getValue(); + if(val.state() != State.OPEN) // only look at open connections + continue; + long age=val.age(); + if(age >= conn_expiry_timeout) { + log.debug("%s: closing expired connection for %s (%d ms old) in send_table", + local_addr, entry.getKey(), age); + closeSendConnection(entry.getKey()); + } + } + + // close expired connections in recv_table + for(Map.Entry entry: recv_table.entrySet()) { + ReceiverEntry val=entry.getValue(); + if(val.state() != State.OPEN) // only look at open connections + continue; + long age=val.age(); + if(age >= conn_expiry_timeout) { + log.debug("%s: closing expired connection for %s (%d ms old) in recv_table", + local_addr, entry.getKey(), age); + closeReceiveConnection(entry.getKey()); + } + } + } + + + @ManagedOperation(description="Removes connections that have been closed for more than conn_close_timeout ms") + public int removeExpiredConnections() { + int num_removed=0; + // remove expired connections from send_table + for(Map.Entry entry: send_table.entrySet()) { + SenderEntry val=entry.getValue(); + if(val.state() == State.OPEN) // only look at closing or closed connections + continue; + long age=val.age(); + if(age >= conn_close_timeout) { + log.debug("%s: removing expired connection for %s (%d ms old) from send_table", + local_addr, entry.getKey(), age); + removeSendConnection(entry.getKey()); + num_removed++; + } + } + + // remove expired connections from recv_table + for(Map.Entry entry: recv_table.entrySet()) { + ReceiverEntry val=entry.getValue(); + if(val.state() == State.OPEN) // only look at closing or closed connections + continue; + long age=val.age(); + if(age >= conn_close_timeout) { + log.debug("%s: removing expired connection for %s (%d ms old) from recv_table", + local_addr, entry.getKey(), age); + removeReceiveConnection(entry.getKey()); + num_removed++; + } + } + return num_removed; + } + + /** + * Removes send- and/or receive-connections whose state is not OPEN (CLOSING or CLOSED). + * @param remove_send_connections If true, send connections whose state is !OPEN are destroyed and removed + * @param remove_receive_connections If true, receive connections with state !OPEN are destroyed and removed + * @return The number of connections which were removed + */ + @ManagedOperation(description="Removes send- and/or receive-connections whose state is not OPEN (CLOSING or CLOSED)") + public int removeConnections(boolean remove_send_connections, boolean remove_receive_connections) { + int num_removed=0; + if(remove_send_connections) { + for(Map.Entry entry: send_table.entrySet()) { + SenderEntry val=entry.getValue(); + if(val.state() != State.OPEN) { // only look at closing or closed connections + log.debug("%s: removing connection for %s (%d ms old, state=%s) from send_table", + local_addr, entry.getKey(), val.age(), val.state()); + removeSendConnection(entry.getKey()); + num_removed++; + } + } + } + if(remove_receive_connections) { + for(Map.Entry entry: recv_table.entrySet()) { + ReceiverEntry val=entry.getValue(); + if(val.state() != State.OPEN) { // only look at closing or closed connections + log.debug("%s: removing expired connection for %s (%d ms old, state=%s) from recv_table", + local_addr, entry.getKey(), val.age(), val.state()); + removeReceiveConnection(entry.getKey()); + num_removed++; + } + } + } + return num_removed; + } + + @ManagedOperation(description="Triggers the retransmission task") + public void triggerXmit() { + // check for gaps in the received messages and ask senders to send them again + for(Map.Entry entry: recv_table.entrySet()) { + Address target=entry.getKey(); // target to send retransmit requests to + ReceiverEntry val=entry.getValue(); + Buffer win=val != null? val.buf : null; + + // receiver: send ack for received messages if needed + if(win != null && val.needToSendAck()) // sendAck() resets send_ack to false + sendAck(target, val, val.realDest()); + + if(xmits_enabled) { + // receiver: retransmit missing messages (getNumMissing() is fast) + SeqnoList missing; + if(win != null && win.numMissing() > 0 && (missing=win.getMissing(max_xmit_req_size)) != null) { + long highest=missing.getLast(); + Long prev_seqno=xmit_task_map.get(target); + if(prev_seqno == null) + xmit_task_map.put(target, highest); // no retransmission + else { + missing.removeHigherThan(prev_seqno); // we only retransmit the 'previous batch' + if(highest > prev_seqno) + xmit_task_map.put(target, highest); + if(!missing.isEmpty()) { + // remove msgs that are <= highest-delivered (https://issues.redhat.com/browse/JGRP-2574) + long highest_deliverable=win.getHighestDeliverable(), first=missing.getFirst(); + if(first < highest_deliverable) + missing.removeLowerThan(highest_deliverable + 1); + retransmit(missing, target, val.real_dest); + } + } + } + else if(!xmit_task_map.isEmpty()) + xmit_task_map.remove(target); // no current gaps for target + } + } + + if(xmits_enabled) { + // resend sent messages until ack'ed + // sender: only send the *highest sent* message if HA < HS and HA/HS didn't change from the prev run + for(SenderEntry val : send_table.values()) { + Buffer win=val != null? val.buf : null; + if(win != null) { + long highest_acked=win.highestDelivered(); // highest delivered == highest ack (sender win) + long highest_sent=win.high(); // we use table as a *sender* win, so it's highest *sent*... + + if(highest_acked < highest_sent && val.watermark[0] == highest_acked && val.watermark[1] == highest_sent) { + // highest acked and sent hasn't moved up - let's resend the HS + Message highest_sent_msg=win.get(highest_sent); + if(highest_sent_msg != null) + retransmit(highest_sent_msg); + } + else + val.watermark(highest_acked, highest_sent); + } + } + } + + // close idle connections + if(conn_expiry_timeout > 0) + closeIdleConnections(); + + if(conn_close_timeout > 0) + removeExpiredConnections(); + } + + + @ManagedOperation(description="Sends ACKs immediately for entries which are marked as pending (ACK hasn't been sent yet)") + public void sendPendingAcks() { + for(Map.Entry entry: recv_table.entrySet()) { + Address target=entry.getKey(); // target to send retransmit requests to + ReceiverEntry val=entry.getValue(); + Buffer win=val != null? val.buf : null; + + // receiver: send ack for received messages if needed + if(win != null && val.needToSendAck())// sendAck() resets send_ack to false + sendAck(target, val, val.realDest()); + } + } + + protected void update(Entry entry, int num_received) { + if(conn_expiry_timeout > 0) + entry.update(); + if(entry.state() == State.CLOSING) + entry.state(State.OPEN); + num_msgs_received.add(num_received); + } + + /** Compares 2 timestamps, handles numeric overflow */ + protected static int compare(int ts1, int ts2) { + int diff=ts1 - ts2; + return Integer.compare(diff, 0); + } + + @SafeVarargs + protected static int accumulate(ToIntFunction> func, Collection ... entries) { + return Stream.of(entries).flatMap(Collection::stream) + .map(entry -> entry.buf).filter(Objects::nonNull) + .mapToInt(func).sum(); + } + + protected static short max(short a, short b) {return (a >= b) ? a : b;} + + protected static Tuple getLowestSeqno(short prot_id, List> list) { + long lowest_seqno=-1; + boolean first=false; + for(LongTuple tuple: list) { + Message msg=tuple.getVal2(); + UnicastHeader hdr=msg.getHeader(prot_id); + if(lowest_seqno < 0) { + lowest_seqno=hdr.seqno; + first=hdr.first; + } + else { + if(lowest_seqno > hdr.seqno) { + lowest_seqno=hdr.seqno; + first=hdr.first; + } + } + } + return new Tuple<>(lowest_seqno, first); + } + + protected enum State {OPEN, CLOSING, CLOSED} + + + + + protected abstract class Entry { + protected final Buffer buf; // stores sent or received messages + protected final short conn_id; + protected final AtomicLong timestamp=new AtomicLong(0); // ns + protected volatile State state=State.OPEN; + protected final AtomicBoolean send_ack=new AtomicBoolean(); + protected final AtomicInteger acks_sent=new AtomicInteger(); + + protected Entry(short conn_id, Buffer buf) { + this.conn_id=conn_id; + this.buf=Objects.requireNonNull(buf); + update(); + } + + public Buffer buf() {return buf;} + public short connId() {return conn_id;} + protected void update() {timestamp.set(getTimestamp());} + protected State state() {return state;} + protected Entry state(State s) {if(this.state != s) {this.state=s; update();} return this;} + protected long age() {return MILLISECONDS.convert(getTimestamp() - timestamp.longValue(), NANOSECONDS);} + protected boolean needToSendAck() {return send_ack.compareAndSet(true, false);} + protected Entry sendAck() {send_ack.compareAndSet(false, true); return this;} + + /** Returns true if a real ACK should be sent. This is based on num_acks_sent being > ack_threshold */ + public boolean update(int num_acks, final IntBinaryOperator op) { + boolean should_send_ack=acks_sent.accumulateAndGet(num_acks, op) == 0; + if(should_send_ack) + return true; + sendAck(); + return false; + } + } + + protected final class SenderEntry extends Entry { + final AtomicLong seqno=new AtomicLong(DEFAULT_FIRST_SEQNO); // seqno for msgs sent by us + final long[] watermark={0,0}; // the highest acked and highest sent seqno + int last_timestamp; // to prevent out-of-order ACKs from a receiver + + public SenderEntry(short send_conn_id) { + super(send_conn_id, createBuffer(0)); + } + + long[] watermark() {return watermark;} + SenderEntry watermark(long ha, long hs) {watermark[0]=ha; watermark[1]=hs; return this;} + + /** Updates last_timestamp. Returns true of the update was in order (ts > last_timestamp) */ + private synchronized boolean updateLastTimestamp(int ts) { + if(last_timestamp == 0) { + last_timestamp=ts; + return true; + } + boolean success=compare(ts, last_timestamp) > 0; // ts has to be > last_timestamp + if(success) + last_timestamp=ts; + return success; + } + + public String toString() { + StringBuilder sb=new StringBuilder(); + if(buf != null) + sb.append(buf).append(", "); + sb.append("send_conn_id=" + conn_id).append(" (" + age()/1000 + " secs old) - " + state); + if(last_timestamp != 0) + sb.append(", last-ts: ").append(last_timestamp); + return sb.toString(); + } + } + + // public for unit testing + public final class ReceiverEntry extends Entry { + private final Address real_dest ; // if real_dest != local_addr (https://issues.redhat.com/browse/JGRP-2729) + + public ReceiverEntry(Buffer received_msgs, short recv_conn_id, Address real_dest) { + super(recv_conn_id, received_msgs); + this.real_dest=real_dest; + } + + Address realDest() {return real_dest;} + + public String toString() { + StringBuilder sb=new StringBuilder(); + if(buf != null) + sb.append(buf).append(", "); + sb.append("recv_conn_id=" + conn_id).append(" (" + age() / 1000 + " secs old) - " + state); + if(send_ack.get()) + sb.append(" [ack pending]"); + return sb.toString(); + } + } + + + /** + * Retransmitter task which periodically (every xmit_interval ms): + *
    + *
  • If any of the receiver windows have the ack flag set, clears the flag and sends an ack for the + * highest delivered seqno to the sender
  • + *
  • Checks all receiver windows for missing messages and asks senders for retransmission
  • + *
  • For all sender windows, checks if highest acked (HA) < highest sent (HS). If not, and HA/HS is the same + * as on the last retransmission run, send the highest sent message again
  • + *
+ */ + protected class RetransmitTask implements Runnable { + + public void run() { + triggerXmit(); + } + + public String toString() { + return ReliableUnicast.class.getSimpleName() + ": RetransmitTask (interval=" + xmit_interval + " ms)"; + } + } + +} diff --git a/src/org/jgroups/protocols/SHARED_LOOPBACK.java b/src/org/jgroups/protocols/SHARED_LOOPBACK.java index ed4331aa5d1..6af4135821b 100644 --- a/src/org/jgroups/protocols/SHARED_LOOPBACK.java +++ b/src/org/jgroups/protocols/SHARED_LOOPBACK.java @@ -184,6 +184,20 @@ public void destroy() { unregister(cluster_name, local_addr); } + public static void clear(AsciiString cluster) { + if(cluster == null) { + synchronized(routing_table) { + routing_table.clear(); + } + return; + } + Map map=routing_table.get(cluster); + if(map != null) { + map.clear(); + routing_table.remove(cluster); + } + } + protected void handleViewChange(View v) { curr_view=v; is_coord=Objects.equals(local_addr, v.getCoord()); diff --git a/src/org/jgroups/protocols/UNICAST4.java b/src/org/jgroups/protocols/UNICAST4.java new file mode 100644 index 00000000000..dfe1b38323d --- /dev/null +++ b/src/org/jgroups/protocols/UNICAST4.java @@ -0,0 +1,107 @@ +package org.jgroups.protocols; + +import org.jgroups.Message; +import org.jgroups.annotations.ManagedAttribute; +import org.jgroups.annotations.ManagedOperation; +import org.jgroups.annotations.Property; +import org.jgroups.util.AverageMinMax; +import org.jgroups.util.Buffer; +import org.jgroups.util.FixedBuffer; + +import java.util.function.IntBinaryOperator; + +import static org.jgroups.conf.AttributeType.SCALAR; + +/** + * New unicast protocol based on fixed-size xmit windows and message ACKs
+ * Details: https://issues.redhat.com/browse/JGRP-2843 + * @author Bela Ban + * @since 5.4 + */ +public class UNICAST4 extends ReliableUnicast { + protected static final Buffer.Options BLOCKING_SENDS=new Buffer.Options().block(true); + + @Property(description="Size of the send/receive buffers, in messages",writable=false) + protected int capacity=2048; + + @Property(description="Number of ACKs to skip before one is sent. For example, a value of 500 means that only " + + "every 500th ACk is sent; all others are dropped. If not set, defaulted to capacity/4",type=SCALAR) + protected int ack_threshold; + + protected final IntBinaryOperator add_acks=(current_acks_sent, acks_to_be_sent) -> { + if(current_acks_sent+acks_to_be_sent >= ack_threshold) + return 0; + else + return current_acks_sent + acks_to_be_sent; + }; + + @ManagedAttribute(description="Number of times sender threads were blocked on a full send window",type=SCALAR) + public long getNumBlockings() { + long total=0; + for(Entry e: send_table.values()) { + FixedBuffer buf=(FixedBuffer)e.buf(); + total+=buf.numBlockings(); + } + return total; + } + + @ManagedAttribute(description="Average time blocked") + public AverageMinMax getAvgTimeBlocked() { + AverageMinMax first=null; + for(Entry e: send_table.values()) { + FixedBuffer buf=(FixedBuffer)e.buf(); + AverageMinMax avg=buf.avgTimeBlocked(); + if(first == null) + first=avg; + else + first.merge(avg); + } + return first; + } + + /** + * Changes the capacity of all buffers, basically by creating new buffers and copying the messages from the + * old ones. + * This method is only supposed to be used by perf testing, so DON'T USE! + */ + @ManagedOperation + public void changeCapacity(int new_capacity) { + if(new_capacity == this.capacity) + return; + send_table.values().stream().map(Entry::buf) + .forEach(buf -> ((FixedBuffer)buf).changeCapacity(new_capacity)); + recv_table.values().stream().map(Entry::buf) + .forEach(buf -> ((FixedBuffer)buf).changeCapacity(new_capacity)); + this.capacity=new_capacity; + this.ack_threshold=capacity / 4; + } + + @Override + protected Buffer createBuffer(long seqno) { + return new FixedBuffer<>(capacity, seqno); + } + + @Override + public Buffer.Options sendOptions() {return BLOCKING_SENDS;} + public int capacity() {return capacity;} + public UNICAST4 capacity(int c) {capacity=c; return this;} + public int ackThreshold() {return ack_threshold;} + public UNICAST4 ackThreshold(int t) {ack_threshold=t; return this;} + + @Override + public void init() throws Exception { + super.init(); + if(ack_threshold <= 0) { + ack_threshold=capacity / 4; + log.info("defaulted ack_threshold to %d", ack_threshold); + } + } + + @Override + protected boolean needToSendAck(Entry e, int num_acks) { + return e.update(num_acks, add_acks); + } + + + +} diff --git a/src/org/jgroups/protocols/UnicastHeader.java b/src/org/jgroups/protocols/UnicastHeader.java new file mode 100644 index 00000000000..09b2469c05b --- /dev/null +++ b/src/org/jgroups/protocols/UnicastHeader.java @@ -0,0 +1,187 @@ +package org.jgroups.protocols; + +import org.jgroups.Global; +import org.jgroups.Header; +import org.jgroups.util.Bits; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.function.Supplier; + +/** + * Header for subclasses of {@link ReliableUnicast}, e.g. {@link UNICAST4}. + * @author Bela Ban + * @since 5.4 + */ +public class UnicastHeader extends Header { + public static final byte DATA = 0; + public static final byte ACK = 1; + public static final byte SEND_FIRST_SEQNO = 2; + public static final byte XMIT_REQ = 3; // SeqnoList of missing message is in the message's payload + public static final byte CLOSE = 4; + + byte type; + long seqno; // DATA and ACK + short conn_id; // DATA and CLOSE + boolean first; // DATA, *NEEDED*, e.g. B closes conn to A (but not B to A!), then A sends msgs + int timestamp; // SEND_FIRST_SEQNO and ACK + + + public UnicastHeader() {} // used for externalization + + protected UnicastHeader(byte type) { + this.type=type; + } + + protected UnicastHeader(byte type, long seqno) { + this.type=type; + this.seqno=seqno; + } + + protected 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 short getMagicId() {return 100;} + public Supplier create() {return UnicastHeader::new;} + + 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, short conn_id, int timestamp) { + return new UnicastHeader(ACK, seqno, conn_id, false).timestamp(timestamp); + } + + public static UnicastHeader createSendFirstSeqnoHeader(int timestamp) { + return new UnicastHeader(SEND_FIRST_SEQNO).timestamp(timestamp); + } + + public static UnicastHeader createXmitReqHeader() { + return new UnicastHeader(XMIT_REQ); + } + + public static UnicastHeader createCloseHeader(short conn_id) { + return new UnicastHeader(CLOSE, 0, conn_id, false); + } + + public byte type() {return type;} + public long seqno() {return seqno;} + public short connId() {return conn_id;} + public UnicastHeader connId(short c) {conn_id=c; return this;} // only for unit testing! + public boolean first() {return first;} + public int timestamp() {return timestamp;} + public UnicastHeader timestamp(int ts) {timestamp=ts; return this;} + + public String toString() { + StringBuilder sb=new StringBuilder(); + sb.append(type2Str(type)).append(", seqno=").append(seqno); + sb.append(", conn_id=").append(conn_id); + if(first) sb.append(", first"); + if(timestamp != 0) + sb.append(", ts=").append(timestamp); + 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"; + case XMIT_REQ: return "XMIT_REQ"; + case CLOSE: return "CLOSE"; + default: return ""; + } + } + + public final int serializedSize() { + int retval=Global.BYTE_SIZE; // type + switch(type) { + case DATA: + retval+=Bits.size(seqno) // seqno + + Global.SHORT_SIZE // conn_id + + Global.BYTE_SIZE; // first + break; + case ACK: + retval+=Bits.size(seqno) + + Global.SHORT_SIZE // conn_id + + Bits.size(timestamp); + break; + case SEND_FIRST_SEQNO: + retval+=Bits.size(timestamp); + break; + case XMIT_REQ: + break; + case CLOSE: + retval+=Global.SHORT_SIZE; // conn-id + break; + } + return retval; + } + + public UnicastHeader copy() { + return new UnicastHeader(type, seqno, conn_id, first); + } + + /** + * The following types and fields are serialized: + *
+     * | DATA | seqno | conn_id | first |
+     * | ACK  | seqno | timestamp |
+     * | SEND_FIRST_SEQNO | timestamp |
+     * | CLOSE | conn_id |
+     * 
+ */ + @Override + public void writeTo(DataOutput out) throws IOException { + out.writeByte(type); + switch(type) { + case DATA: + Bits.writeLongCompressed(seqno, out); + out.writeShort(conn_id); + out.writeBoolean(first); + break; + case ACK: + Bits.writeLongCompressed(seqno, out); + out.writeShort(conn_id); + Bits.writeIntCompressed(timestamp, out); + break; + case SEND_FIRST_SEQNO: + Bits.writeIntCompressed(timestamp, out); + break; + case XMIT_REQ: + break; + case CLOSE: + out.writeShort(conn_id); + break; + } + } + + @Override + public void readFrom(DataInput in) throws IOException { + type=in.readByte(); + switch(type) { + case DATA: + seqno=Bits.readLongCompressed(in); + conn_id=in.readShort(); + first=in.readBoolean(); + break; + case ACK: + seqno=Bits.readLongCompressed(in); + conn_id=in.readShort(); + timestamp=Bits.readIntCompressed(in); + break; + case SEND_FIRST_SEQNO: + timestamp=Bits.readIntCompressed(in); + break; + case XMIT_REQ: + break; + case CLOSE: + conn_id=in.readShort(); + break; + } + } +} diff --git a/src/org/jgroups/protocols/pbcast/CoordGmsImpl.java b/src/org/jgroups/protocols/pbcast/CoordGmsImpl.java index b4502b9321f..7c4b25d5358 100644 --- a/src/org/jgroups/protocols/pbcast/CoordGmsImpl.java +++ b/src/org/jgroups/protocols/pbcast/CoordGmsImpl.java @@ -93,8 +93,6 @@ public void handleMembershipChange(Collection requests) { for(Request req: requests) { switch(req.type) { case Request.JOIN: - new_mbrs.add(req.mbr); - break; case Request.JOIN_WITH_STATE_TRANSFER: new_mbrs.add(req.mbr); break; diff --git a/src/org/jgroups/util/Average.java b/src/org/jgroups/util/Average.java index 41960a0bb05..fff217a898c 100644 --- a/src/org/jgroups/util/Average.java +++ b/src/org/jgroups/util/Average.java @@ -53,8 +53,11 @@ public T add(long num) { public T merge(T other) { if(other == null) return (T)this; - for(int i=0; i < other.samples.length(); i++) - add(other.samples.get(i)); + for(int i=0; i < other.samples.length(); i++) { + Double el=other.samples.get(i); + if(el != null) + add(el); + } return (T)this; } @@ -91,18 +94,29 @@ public String toString(TimeUnit u) { @Override public void writeTo(DataOutput out) throws IOException { Bits.writeIntCompressed(samples.length(), out); - for(int i=0; i < samples.length(); i++) - Bits.writeDouble(samples.get(i), out); + for(int i=0; i < samples.length(); i++) { + Double sample=samples.get(i); + boolean not_null=sample != null; + out.writeBoolean(not_null); + if(not_null) + Bits.writeDouble(sample, out); + } + out.writeInt(index); Bits.writeDouble(total.sum(), out); + Bits.writeLongCompressed(count.sum(), out); } @Override public void readFrom(DataInput in) throws IOException { int len=Bits.readIntCompressed(in); samples=new AtomicReferenceArray<>(len); - for(int i=0; i < samples.length(); i++) - samples.set(i, Bits.readDouble(in)); + for(int i=0; i < samples.length(); i++) { + if(in.readBoolean()) + samples.set(i, Bits.readDouble(in)); + } + index=in.readInt(); total.add(Bits.readDouble(in)); + count.add(Bits.readLongCompressed(in)); } protected int nextIndex() { diff --git a/src/org/jgroups/util/Buffer.java b/src/org/jgroups/util/Buffer.java index 5070bf5d753..23020f6827b 100644 --- a/src/org/jgroups/util/Buffer.java +++ b/src/org/jgroups/util/Buffer.java @@ -87,6 +87,18 @@ public boolean add(long seqno, T element) { // used: MessageBatch received public abstract boolean add(MessageBatch batch, Function seqno_getter, boolean remove_from_batch, T const_value); + /** + * Adds elements from the list + * @param list The list of tuples of seqnos and elements. If remove_added_elements is true, if elements could + * not be added (e.g. because they were already present or the seqno was < HD), those + * elements will be removed from list + * @param remove_added_elements If true, elements that could not be added to the table are removed from list + * @param const_value If non-null, this value should be used rather than the values of the list tuples + * @return True if at least 1 element was added successfully, false otherwise. + */ + // used: MessageBatch received by UNICAST3/4 + public abstract boolean add(final List> list, boolean remove_added_elements, T const_value); + // used: retransmision etc public abstract T get(long seqno); @@ -250,7 +262,7 @@ public String dump() { @Override public String toString() { - return String.format("[%,d | %,d | %,d] (%,d elements, %,d missing)", low, hd, high, size, numMissing()); + return String.format("[%,d | %,d | %,d] (size: %,d, missing: %,d)", low, hd, high, size, numMissing()); } public static class Options { diff --git a/src/org/jgroups/util/DynamicBuffer.java b/src/org/jgroups/util/DynamicBuffer.java index 0cb72ea1408..caabb42c29b 100644 --- a/src/org/jgroups/util/DynamicBuffer.java +++ b/src/org/jgroups/util/DynamicBuffer.java @@ -197,6 +197,32 @@ public boolean add(MessageBatch batch, Function seqno_getter, boolean re } } + public boolean add(final List> list, boolean remove_added_elements, T const_value) { + if(list == null || list.isEmpty()) + return false; + boolean added=false; + // find the highest seqno (unfortunately, the list is not ordered by seqno) + long highest_seqno=findHighestSeqno(list); + lock.lock(); + try { + if(highest_seqno != -1 && computeRow(highest_seqno) >= matrix.length) + resize(highest_seqno); + + for(Iterator> it=list.iterator(); it.hasNext();) { + LongTuple tuple=it.next(); + long seqno=tuple.getVal1(); + T element=const_value != null? const_value : tuple.getVal2(); + if(add(seqno, element, null, null)) + added=true; + else if(remove_added_elements) + it.remove(); + } + return added; + } + finally { + lock.unlock(); + } + } /** * Returns an element at seqno diff --git a/src/org/jgroups/util/FixedBuffer.java b/src/org/jgroups/util/FixedBuffer.java index 2d30f4ace8a..9817021e65d 100644 --- a/src/org/jgroups/util/FixedBuffer.java +++ b/src/org/jgroups/util/FixedBuffer.java @@ -140,6 +140,27 @@ public boolean add(MessageBatch batch, Function seqno_getter, boolean re } } + public boolean add(final List> list, boolean remove_added_elements, T const_value) { + if(list == null || list.isEmpty()) + return false; + boolean added=false; + lock.lock(); + try { + for(Iterator> it=list.iterator(); it.hasNext();) { + LongTuple tuple=it.next(); + long seqno=tuple.getVal1(); + T element=const_value != null? const_value : tuple.getVal2(); + if(add(seqno, element, null, null)) + added=true; + else if(remove_added_elements) + it.remove(); + } + return added; + } + finally { + lock.unlock(); + } + } /** * Removes the next non-null element and advances hd @@ -356,13 +377,6 @@ protected int index(long seqno) { //return (int)((seqno - offset - 1) & (capacity() - 1)); } - protected int index(long seqno, int capacity) { - // return (int)((seqno-1) % capacity); // apparently slower than the computation below - - // apparently this is faster than mod for n^2 capacity - return (int)((seqno - offset - 1) & (capacity - 1)); - } - @GuardedBy("lock") protected boolean block(long seqno) { while(open && seqno - low > capacity()) { diff --git a/src/org/jgroups/util/MaxOneThreadPerSender.java b/src/org/jgroups/util/MaxOneThreadPerSender.java index f6a02b10426..5baa4a99bbb 100644 --- a/src/org/jgroups/util/MaxOneThreadPerSender.java +++ b/src/org/jgroups/util/MaxOneThreadPerSender.java @@ -7,7 +7,8 @@ import org.jgroups.protocols.TP; import java.util.List; -import java.util.concurrent.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.LongAdder; import java.util.concurrent.locks.Lock; @@ -148,7 +149,8 @@ protected Entry(Address sender, boolean mcast, AsciiString cluster_name) { this.sender=sender; this.cluster_name=cluster_name; int cap=max_buffer_size > 0? max_buffer_size : DEFAULT_INITIAL_CAPACITY; // initial capacity - batch=new MessageBatch(cap).dest(tp.getAddress()).sender(sender).clusterName(cluster_name).multicast(mcast); + batch=new MessageBatch(cap).dest(tp.getAddress()).sender(sender).clusterName(cluster_name) + .multicast(mcast).mode(MessageBatch.Mode.REG); // only regular messages are queued batch.array().increment(DEFAULT_INCREMENT); msg_queue=max_buffer_size > 0? new FastArray<>(max_buffer_size) : new FastArray<>(DEFAULT_INITIAL_CAPACITY); msg_queue.increment(DEFAULT_INCREMENT); @@ -228,7 +230,6 @@ protected boolean workAvailable() { } } - // unsynchronized on batch but who cares public String toString() { return String.format("msg_queue.size=%,d msg_queue.cap: %,d batch.cap=%,d queued msgs=%,d submitted batches=%,d", @@ -251,8 +252,17 @@ public void run() { while(entry.workAvailable() || entry.adders.decrementAndGet() != 0) { try { MessageBatch mb=entry.batch; - if(mb == null || mb.isEmpty() || (!mb.multicast() && tp.unicastDestMismatch(mb.dest()))) + if(mb.isEmpty()) continue; + if(!mb.multicast()) { + // due to an incorrect (e.g. late) view change, the cached batch's destination might be + // different from our local address. If this is the case, change the cached batch's dest address + if(tp.unicastDestMismatch(mb.dest())) { + Address dest=tp.addr(); + if(dest != null) + mb.dest(dest); + } + } tp.passBatchUp(mb, !loopback, !loopback); } catch(Throwable t) { diff --git a/src/org/jgroups/util/MessageBatch.java b/src/org/jgroups/util/MessageBatch.java index da91f76c218..9759a268514 100644 --- a/src/org/jgroups/util/MessageBatch.java +++ b/src/org/jgroups/util/MessageBatch.java @@ -93,6 +93,7 @@ public MessageBatch(Address dest, Address sender, AsciiString cluster_name, bool public int capacity() {return messages.capacity();} public long timestamp() {return timestamp;} public MessageBatch timestamp(long ts) {timestamp=ts; return this;} + public MessageBatch increment(int i) {messages.increment(i); return this;} /** Returns the underlying message array. This is only intended for testing ! */ @@ -294,7 +295,6 @@ public String toString() { if(sb.length() > 0) sb.append(", "); sb.append(size() + " messages [capacity=" + messages.capacity() + "]"); - return sb.toString(); } diff --git a/src/org/jgroups/util/MessageCache.java b/src/org/jgroups/util/MessageCache.java index bfed5879641..ea863ed334d 100644 --- a/src/org/jgroups/util/MessageCache.java +++ b/src/org/jgroups/util/MessageCache.java @@ -16,27 +16,21 @@ */ public class MessageCache { protected final Map> map=new ConcurrentHashMap<>(); - protected volatile boolean is_empty=true; public MessageCache add(Address sender, Message msg) { Queue list=map.computeIfAbsent(sender, addr -> new ConcurrentLinkedQueue<>()); list.add(msg); - is_empty=false; return this; } public Collection drain(Address sender) { if(sender == null) return null; - Queue queue=map.remove(sender); - if(map.isEmpty()) - is_empty=true; - return queue; + return map.remove(sender); } public MessageCache clear() { map.clear(); - is_empty=true; return this; } @@ -46,7 +40,7 @@ public int size() { } public boolean isEmpty() { - return is_empty; + return map.isEmpty(); } public String toString() { diff --git a/src/org/jgroups/util/MockTransport.java b/src/org/jgroups/util/MockTransport.java new file mode 100644 index 00000000000..fe1649607ac --- /dev/null +++ b/src/org/jgroups/util/MockTransport.java @@ -0,0 +1,31 @@ +package org.jgroups.util; + +import org.jgroups.Message; +import org.jgroups.PhysicalAddress; +import org.jgroups.protocols.NoBundler; +import org.jgroups.protocols.TP; + +/** + * A dummy implementation of {@link TP} + * @author Bela Ban + * @since 5.4 + */ +public class MockTransport extends TP { + + public void init() throws Exception { + super.init(); + setBundler(new NoBundler()); + } + public boolean supportsMulticasting() {return true;} + public void sendUnicast(PhysicalAddress dest, byte[] data, int offset, int length) throws Exception {} + public String getInfo() {return null;} + protected PhysicalAddress getPhysicalAddress() {return null;} + public MockTransport cluster(AsciiString s) {this.cluster_name=s; return this;} + + + public Object down(Message msg) { + return null; + } + + +} diff --git a/src/org/jgroups/util/Table.java b/src/org/jgroups/util/Table.java index 42f7085b187..f5417d03283 100644 --- a/src/org/jgroups/util/Table.java +++ b/src/org/jgroups/util/Table.java @@ -105,7 +105,7 @@ public Table(int num_rows, int elements_per_row, long offset, double resize_fact * @param num_rows the number of rows in the matrix * @param elements_per_row the number of elements per row * @param offset the seqno before the first seqno to be inserted. E.g. if 0 then the first seqno will be 1 - * @param resize_factor teh factor with which to increase the number of rows + * @param resize_factor the factor with which to increase the number of rows * @param max_compaction_time the max time in milliseconds after we attempt a compaction */ public Table(int num_rows, int elements_per_row, long offset, double resize_factor, long max_compaction_time) { diff --git a/src/org/jgroups/util/Util.java b/src/org/jgroups/util/Util.java index afe3b09c57d..7d7a328c5a0 100644 --- a/src/org/jgroups/util/Util.java +++ b/src/org/jgroups/util/Util.java @@ -7,6 +7,7 @@ import org.jgroups.blocks.cs.Connection; import org.jgroups.conf.ClassConfigurator; import org.jgroups.jmx.JmxConfigurator; +import org.jgroups.jmx.ResourceDMBean; import org.jgroups.logging.Log; import org.jgroups.protocols.*; import org.jgroups.protocols.pbcast.GMS; @@ -107,7 +108,9 @@ public class Util { private static final byte[] TYPE_BOOLEAN_TRUE={TYPE_BOOLEAN, 1}; private static final byte[] TYPE_BOOLEAN_FALSE={TYPE_BOOLEAN, 0}; - public static final Class[] getUnicastProtocols() {return new Class[]{UNICAST3.class};} + public static final Class[] getUnicastProtocols() { + return new Class[]{UNICAST3.class,UNICAST4.class}; + } public enum AddressScope {GLOBAL,SITE_LOCAL,LINK_LOCAL,LOOPBACK,NON_LOOPBACK} @@ -380,7 +383,13 @@ public static String printViews(JChannel ... channels) { return sb.toString(); } - + public static String print(MessageBatch batch, boolean print_headers) { + int count=1; + StringBuilder sb=new StringBuilder(String.format("%s:\n", batch.toString())); + for(Message msg: batch) + sb.append(String.format(" %d: %s%s\n", count++, msg, print_headers? String.format(", hdrs: %s", msg.printHeaders()) : "")); + return sb.toString(); + } /** * Waits until a list has the expected number of elements. Throws an exception if not met @@ -1237,7 +1246,7 @@ public static T streamableFromByteBuffer(Class T streamableFromByteBuffer(Class cl, ByteBuffer buffer) throws Exception { if(buffer == null) return null; DataInput in=new ByteBufferInputStream(buffer); - T retval=(T)cl.newInstance(); + T retval=(T)cl.getConstructor().newInstance(); retval.readFrom(in); return retval; } @@ -3009,7 +3018,7 @@ public static Collection
determineMergeParticipants(Map m View view=map.get(coord); Collection
mbrs=view != null? view.getMembers() : null; if(mbrs != null) - all_addrs.removeAll(mbrs); + mbrs.forEach(all_addrs::remove); } coords.addAll(all_addrs); return coords; @@ -3521,6 +3530,51 @@ public static Method findMethod(Class target_class, String method_name, Objec return retval; } + public static Method findMethod2(Class target_class, String method_name, Object[] args) throws Exception { + int len=args != null? args.length : 0; + Method retval=null; + Method[] methods=getAllMethods(target_class); + for(int i=0; i < methods.length; i++) { + Method m=methods[i]; + if(m.getName().equals(method_name)) { + Class[] parameter_types=m.getParameterTypes(); + if(parameter_types.length == len) { + boolean all_args_match=true; + // now check if actual and parameter types match: + for(int j=0; j < parameter_types.length; j++) { + Class parameter=parameter_types[j]; + Class actual=args[j] != null? args[j].getClass() : null; + if(actual != null && !isAssignableFrom(parameter, actual)) { + all_args_match=false; + break; + } + } + if(all_args_match) + return m; + } + } + } + return retval; + } + + public static boolean isAssignableFrom(Class left, Class right) { + if(left == null) + return false; + if(right == null) + return left == null || !left.isPrimitive(); + if(left == right) + return true; + // at this point, left and right are not null + if(left.isAssignableFrom(right)) + return true; + return ResourceDMBean.isNumber(left) && ResourceDMBean.isNumber(right); + } + + public static Object invoke(Object target, String method_name, Object... args) throws Exception { + Method method=Util.findMethod2(target.getClass(), method_name, args); + return method.invoke(target, args); + } + /** * The method walks up the class hierarchy and returns all methods of this class * and those inherited from superclasses and superinterfaces. @@ -4955,10 +5009,7 @@ public static String substituteVariable(String input, Properties p) { sb.append(ch); break; default: - if(cache != null) - cache.append(ch); - else - sb.append(ch); + Objects.requireNonNullElse(cache, sb).append(ch); break; } } diff --git a/tests/junit-functional/org/jgroups/protocols/UNICAST_ConnectionTests.java b/tests/junit-functional/org/jgroups/protocols/UNICAST_ConnectionTests.java index 6bd48ac9284..32863b28edd 100644 --- a/tests/junit-functional/org/jgroups/protocols/UNICAST_ConnectionTests.java +++ b/tests/junit-functional/org/jgroups/protocols/UNICAST_ConnectionTests.java @@ -3,6 +3,7 @@ import org.jgroups.*; import org.jgroups.stack.Protocol; import org.jgroups.stack.ProtocolStack; +import org.jgroups.util.MyReceiver; import org.jgroups.util.Util; import org.testng.annotations.AfterMethod; import org.testng.annotations.DataProvider; @@ -12,32 +13,32 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.CyclicBarrier; -import java.util.stream.Stream; /** * Tests unilateral closings of UNICAST connections. The test scenarios are described in doc/design/UNICAST2.txt. * Some of the tests may fail occasionally until https://issues.redhat.com/browse/JGRP-1594 is fixed * @author Bela Ban */ -@Test(groups=Global.FUNCTIONAL,singleThreaded=true) +@Test(groups=Global.FUNCTIONAL,singleThreaded=true,dataProvider="configProvider") public class UNICAST_ConnectionTests { - protected JChannel a, b; - protected Address a_addr, b_addr; - protected MyReceiver r1, r2; - protected Protocol u1, u2; + protected JChannel a, b; + protected Address a_addr, b_addr; + protected MyReceiver r1, r2; + protected Protocol u1, u2; protected static final String CLUSTER="UNICAST_ConnectionTests"; - + @DataProvider static Object[][] configProvider() { return new Object[][]{ - {UNICAST3.class} + {UNICAST3.class}, + {UNICAST4.class} }; } - protected void setup(Class unicast_class) throws Exception { - r1=new MyReceiver("A"); - r2=new MyReceiver("B"); + protected void setup(Class unicast_class) throws Exception { + r1=new MyReceiver().name("A"); + r2=new MyReceiver().name("B"); a=createChannel(unicast_class, "A"); a.connect(CLUSTER); a_addr=a.getAddress(); @@ -59,7 +60,7 @@ protected void setup(Class unicast_class) throws Exception { * @throws Exception */ @Test(dataProvider="configProvider") - public void testRegularMessageReception(Class unicast) throws Exception { + public void testRegularMessageReception(Class unicast) throws Exception { setup(unicast); sendAndCheck(a, b_addr, 100, r2); sendAndCheck(b,a_addr,50,r1); @@ -70,7 +71,7 @@ public void testRegularMessageReception(Class unicast) throw * Tests case #3 of UNICAST.new.txt */ @Test(dataProvider="configProvider") - public void testBothChannelsClosing(Class unicast) throws Exception { + public void testBothChannelsClosing(Class unicast) throws Exception { setup(unicast); sendToEachOtherAndCheck(10); @@ -78,7 +79,7 @@ public void testBothChannelsClosing(Class unicast) throws Ex System.out.println("==== Closing the connections on both sides"); removeConnection(u1, b_addr); removeConnection(u2, a_addr); - r1.clear(); r2.clear(); + r1.reset(); r2.reset(); // causes new connection establishment sendToEachOtherAndCheck(10); @@ -89,7 +90,7 @@ public void testBothChannelsClosing(Class unicast) throws Ex * Scenario #4 (A closes the connection unilaterally (B keeps it open), then reopens it and sends messages) */ @Test(dataProvider="configProvider") - public void testAClosingUnilaterally(Class unicast) throws Exception { + public void testAClosingUnilaterally(Class unicast) throws Exception { setup(unicast); sendToEachOtherAndCheck(10); @@ -105,7 +106,7 @@ public void testAClosingUnilaterally(Class unicast) throws E * Scenario #5 (B closes the connection unilaterally (A keeps it open), then A sends messages to B) */ @Test(dataProvider="configProvider") - public void testBClosingUnilaterally(Class unicast) throws Exception { + public void testBClosingUnilaterally(Class unicast) throws Exception { setup(unicast); sendToEachOtherAndCheck(10); @@ -117,11 +118,7 @@ public void testBClosingUnilaterally(Class unicast) throws E sendAndCheck(a, b_addr, 10, r2); } - @Test(dataProvider="configProvider") - public void testBRemovingUnilaterally(Class unicast) throws Exception { - if(!unicast.equals(UNICAST3.class)) - return; // only tested for UNICAST3 - + public void testBRemovingUnilaterally(Class unicast) throws Exception { setup(unicast); sendAndCheck(a, b_addr, 10, r2); @@ -133,13 +130,25 @@ public void testBRemovingUnilaterally(Class unicast) throws sendAndCheck(a, b_addr, 10, r2); } + public void testBRemovingUnilaterallyOOB(Class unicast) throws Exception { + setup(unicast); + sendAndCheck(a, b_addr, 10, r2); + + // now remove connection on A unilaterally + System.out.println("==== Removing the connection on B"); + removeConnection(u2, a_addr, true); + + // then send OOB messages from A to B + sendAndCheck(a, b_addr, true, 10, r2); + } + /** * Scenario #6 (A closes the connection unilaterally (B keeps it open), then reopens it and sends messages, * but loses the first message */ @Test(dataProvider="configProvider") - public void testAClosingUnilaterallyButLosingFirstMessage(Class unicast) throws Exception { + public void testAClosingUnilaterallyButLosingFirstMessage(Class unicast) throws Exception { setup(unicast); sendAndCheck(a, b_addr, 10, r2); @@ -157,7 +166,7 @@ public void testAClosingUnilaterallyButLosingFirstMessage(Class unicast) throws Exception { + public void testMultipleConcurrentResets(Class unicast) throws Exception { setup(unicast); sendAndCheck(a, b_addr, 1, r2); @@ -165,12 +174,10 @@ public void testMultipleConcurrentResets(Class unicast) thro System.out.println("==== Closing the connection on A"); removeConnection(u1, b_addr); - r2.clear(); - + r2.reset(); final Protocol ucast=b.getProtocolStack().findProtocol(Util.getUnicastProtocols()); int NUM=10; - final List msgs=new ArrayList<>(NUM); for(int i=1; i <= NUM; i++) { @@ -201,16 +208,17 @@ public void testMultipleConcurrentResets(Class unicast) thro for(Thread thread: threads) thread.join(); - List list=r2.getMessages(); + List list=r2.list(); System.out.println("list = " + print(list)); assert list.size() == 1 : "list must have 1 element but has " + list.size() + ": " + print(list); } @Test(dataProvider="configProvider") - public void testMessageToNonExistingMember(Class unicast) throws Exception { + public void testMessageToNonExistingMember(Class unicast) throws Exception { setup(unicast); - Stream.of(a,b).forEach(ch -> ((UNICAST3)ch.getProtocolStack().findProtocol(unicast)).setMaxRetransmitTime(5000)); + for(JChannel ch: List.of(a,b)) + Util.invoke(ch.stack().findProtocol(unicast), "setMaxRetransmitTime", 5000L); Address target=Util.createRandomAddress("FakeAddress"); a.send(target, "hello"); Protocol prot=a.getProtocolStack().findProtocol(unicast); @@ -224,10 +232,11 @@ public void testMessageToNonExistingMember(Class unicast) th assert !(Boolean)hasSendConnectionTo.invoke(prot, target); } - protected static Header createDataHeader(Protocol unicast, long seqno, short conn_id, boolean first) { if(unicast instanceof UNICAST3) return UnicastHeader3.createDataHeader(seqno, conn_id, first); + else if(unicast instanceof UNICAST4) + return UnicastHeader.createDataHeader(seqno, conn_id, first); throw new IllegalArgumentException("protocol " + unicast.getClass().getSimpleName() + " needs to be UNICAST3"); } @@ -242,8 +251,8 @@ protected void sendToEachOtherAndCheck(int num) throws Exception { a.send(b_addr, i); b.send(a_addr, i); } - List l1=r1.getMessages(); - List l2=r2.getMessages(); + List l1=r1.list(); + List l2=r2.list(); for(int i=0; i < 10; i++) { if(l1.size() == num && l2.size() == num) break; @@ -255,70 +264,45 @@ protected void sendToEachOtherAndCheck(int num) throws Exception { assert l2.size() == num; } - protected 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, i); - List list=receiver.getMessages(); - for(int i=0; i < 20; i++) { - if(list.size() == num) - break; - Util.sleep(500); + protected static void sendAndCheck(JChannel channel, Address dest, int num, MyReceiver r) throws Exception { + sendAndCheck(channel, dest, false, num, r); + } + + protected static void sendAndCheck(JChannel channel, Address dest, boolean oob, int num, MyReceiver r) throws Exception { + r.reset(); + for(int i=1; i <= num; i++) { + Message msg=new ObjectMessage(dest, i); + if(oob) + msg.setFlag(Message.Flag.OOB); + channel.send(msg); } - System.out.println("list = " + print(list)); + List list=r.list(); + Util.waitUntilTrue(10000, 500, () -> list.size() == num); + System.out.println("list = " + list); int size=list.size(); assert size == num : "list has " + size + " elements (expected " + num + "): " + list; } - protected static void removeConnection(Protocol prot, Address target) { + protected static void removeConnection(Protocol prot, Address target) throws Exception { removeConnection(prot, target, false); } - protected static void removeConnection(Protocol prot, Address target, boolean remove) { - if(prot instanceof UNICAST3) { - UNICAST3 unicast=(UNICAST3)prot; - if(remove) - unicast.removeReceiveConnection(target); - else - unicast.closeConnection(target); - } + protected static void removeConnection(Protocol prot, Address target, boolean remove) throws Exception { + if(remove) + Util.invoke(prot, "removeReceiveConnection", target); else - throw new IllegalArgumentException("prot (" + prot + ") needs to be UNICAST3"); + Util.invoke(prot, "closeConnection", target); } - protected static String print(List list) { return Util.printListWithDelimiter(list, " "); } - protected static JChannel createChannel(Class unicast_class, String name) throws Exception { Protocol unicast=unicast_class.getDeclaredConstructor().newInstance(); return new JChannel(new SHARED_LOOPBACK(), unicast).name(name); } - protected static class MyReceiver implements Receiver { - final String name; - final List msgs=new ArrayList<>(20); - - public MyReceiver(String name) { - this.name=name; - } - - public void receive(Message msg) { - synchronized(msgs) { - msgs.add(msg.getObject()); - } - } - - public List getMessages() {return msgs;} - public void clear() {msgs.clear();} - public int size() {return msgs.size();} - - public String toString() { - return name; - } - } protected static class Drop extends Protocol { protected volatile boolean drop_next=false; diff --git a/tests/junit-functional/org/jgroups/protocols/UNICAST_ContentionTest.java b/tests/junit-functional/org/jgroups/protocols/UNICAST_ContentionTest.java index a95c53edbbf..bd004c7f34c 100644 --- a/tests/junit-functional/org/jgroups/protocols/UNICAST_ContentionTest.java +++ b/tests/junit-functional/org/jgroups/protocols/UNICAST_ContentionTest.java @@ -30,14 +30,15 @@ protected void tearDown() throws Exception { @DataProvider static Object[][] provider() { return new Object[][] { - {UNICAST3.class} + {UNICAST3.class}, + {UNICAST4.class} }; } @Test(dataProvider="provider") - public void testSimpleMessageReception(Class unicast_class) throws Exception { + public void testSimpleMessageReception(Class unicast_class) throws Exception { a=create(unicast_class, "A"); b=create(unicast_class, "B"); MyReceiver r1=new MyReceiver("A"), r2=new MyReceiver("B"); @@ -56,25 +57,24 @@ public void testSimpleMessageReception(Class unicast_class) } for(int i=0; i < 10; i++) { - if(r1.getNum() == NUM * 2 && r2.getNum() == NUM * 2) + if(r1.num() == NUM * 2 && r2.num() == NUM * 2) break; Util.sleep(500); } - System.out.println("c1 received " + r1.getNum() + " msgs, " + getNumberOfRetransmissions(a) + " retransmissions"); - System.out.println("c2 received " + r2.getNum() + " msgs, " + getNumberOfRetransmissions(b) + " retransmissions"); + System.out.printf("%s: %,d msgs, %s xmits\n", "c1", r1.num(), + Util.invoke(a.stack().findProtocol(Util.getUnicastProtocols()), "getNumXmits")); + System.out.printf("%s: %,d msgs, %s xmits\n", "c2", r2.num(), + Util.invoke(b.stack().findProtocol(Util.getUnicastProtocols()), "getNumXmits")); - assert r1.getNum() == NUM * 2: "expected " + NUM *2 + ", but got " + r1.getNum(); - assert r2.getNum() == NUM * 2: "expected " + NUM *2 + ", but got " + r2.getNum(); + assert r1.num() == NUM * 2: "expected " + NUM *2 + ", but got " + r1.num(); + assert r2.num() == NUM * 2: "expected " + NUM *2 + ", but got " + r2.num(); } - /** - * Multiple threads (NUM_THREADS) send messages (NUM_MSGS) - * @throws Exception - */ + /** Multiple threads (NUM_THREADS) send messages (NUM_MSGS) */ @Test(dataProvider="provider") - public void testMessageReceptionUnderHighLoad(Class unicast_class) throws Exception { + public void testMessageReceptionUnderHighLoad(Class unicast_class) throws Exception { CountDownLatch latch=new CountDownLatch(1); a=create(unicast_class, "A"); b=create(unicast_class, "B"); @@ -107,31 +107,26 @@ public void testMessageReceptionUnderHighLoad(Class unicast_ long NUM_EXPECTED_MSGS=NUM_THREADS * NUM_MSGS; for(int i=0; i < 20; i++) { - if(r1.getNum() == NUM_EXPECTED_MSGS && r2.getNum() == NUM_EXPECTED_MSGS) + if(r1.num() == NUM_EXPECTED_MSGS && r2.num() == NUM_EXPECTED_MSGS) break; Util.sleep(2000); } - System.out.println("c1 received " + r1.getNum() + " msgs, " + getNumberOfRetransmissions(a) + " retransmissions"); - System.out.println("c2 received " + r2.getNum() + " msgs, " + getNumberOfRetransmissions(b) + " retransmissions"); + System.out.printf("%s: %,d msgs, %s xmits\n", "c1", r1.num(), + Util.invoke(a.stack().findProtocol(Util.getUnicastProtocols()), "getNumXmits")); + System.out.printf("%s: %,d msgs, %s xmits\n", "c2", r2.num(), + Util.invoke(b.stack().findProtocol(Util.getUnicastProtocols()), "getNumXmits")); - 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(); + assert r1.num() == NUM_EXPECTED_MSGS : "expected " + NUM_EXPECTED_MSGS + ", but got " + r1.num(); + assert r2.num() == NUM_EXPECTED_MSGS : "expected " + NUM_EXPECTED_MSGS + ", but got " + r2.num(); } - protected static JChannel create(Class unicast_class, String name) throws Exception { - return new JChannel(new SHARED_LOOPBACK(), - unicast_class.getDeclaredConstructor().newInstance().setXmitInterval(500)).name(name); + protected static JChannel create(Class unicast_class, String name) throws Exception { + Protocol p=unicast_class.getDeclaredConstructor().newInstance(); + Util.invoke(p, "setXmitInterval", 500L); + return new JChannel(new SHARED_LOOPBACK(), p).name(name); } - private static long getNumberOfRetransmissions(JChannel ch) { - Protocol prot=ch.getProtocolStack().findProtocol(Util.getUnicastProtocols()); - if(prot instanceof UNICAST3) - return ((UNICAST3)prot).getNumXmits(); - return 0; - } - - private static class MySender extends Thread { private final JChannel ch; @@ -178,11 +173,11 @@ public MyReceiver(String name) { public void receive(Message msg) { if(num.incrementAndGet() % MOD == 0) { - System.out.println("[" + name + "] received " + getNum() + " msgs"); + System.out.println("[" + name + "] received " + num() + " msgs"); } } - public int getNum() { + public int num() { return num.get(); } } diff --git a/tests/junit-functional/org/jgroups/protocols/UNICAST_DropFirstAndLastTest.java b/tests/junit-functional/org/jgroups/protocols/UNICAST_DropFirstAndLastTest.java index 7944ae5df31..6e3aa131ab7 100644 --- a/tests/junit-functional/org/jgroups/protocols/UNICAST_DropFirstAndLastTest.java +++ b/tests/junit-functional/org/jgroups/protocols/UNICAST_DropFirstAndLastTest.java @@ -14,7 +14,7 @@ import java.util.List; /** - * Tests UNICAST2. Created to test the last-message-dropped problem, see https://issues.redhat.com/browse/JGRP-1548. + * Tests UNICAST{3,4}. Created to test the last-message-dropped problem, see https://issues.redhat.com/browse/JGRP-1548. * @author Bela Ban * @since 3.3 */ @@ -24,7 +24,7 @@ public class UNICAST_DropFirstAndLastTest { protected MyReceiver rb; protected DISCARD discard; // on A - protected void setup(Class unicast_class) throws Exception { + protected void setup(Class unicast_class) throws Exception { a=createChannel(unicast_class, "A"); discard=a.getProtocolStack().findProtocol(DISCARD.class); assert discard != null; @@ -39,9 +39,10 @@ protected void setup(Class unicast_class) throws Exception { @DataProvider - static Object[][] configProvider() { + static Object[][] createUnicast() { return new Object[][]{ - {UNICAST3.class} + {UNICAST3.class}, + {UNICAST4.class} }; } @@ -50,13 +51,13 @@ static Object[][] configProvider() { * https://issues.redhat.com/browse/JGRP-1548 now needs to make sure message 5 is retransmitted to B * within a short time period, and we don't have to rely on the stable task to kick in. */ - @Test(dataProvider="configProvider") - public void testLastMessageDropped(Class unicast_class) throws Exception { + @Test(dataProvider="createUnicast") + public void testLastMessageDropped(Class unicast_class) throws Exception { setup(unicast_class); setLevel("trace", a, b); Address dest=b.getAddress(); for(int i=1; i <= 5; i++) { - Message msg=new BytesMessage(dest, i); + Message msg=new ObjectMessage(dest, i); if(i == 5) discard.dropDownUnicasts(1); // drops the next unicast a.send(msg); @@ -72,22 +73,21 @@ public void testLastMessageDropped(Class unicast_class) thro * https://issues.redhat.com/browse/JGRP-1563 now needs to make sure message 1 is retransmitted to B * within a short time period, and we don't have to rely on the stable task to kick in. */ - @Test(dataProvider="configProvider") - public void testFirstMessageDropped(Class unicast_class) throws Exception { + @Test(dataProvider="createUnicast") + public void testFirstMessageDropped(Class unicast_class) throws Exception { setup(unicast_class); - System.out.println("**** closing all connections ****"); // close all connections, so we can start from scratch and send message A1 to B for(JChannel ch: Arrays.asList(a,b)) { Protocol unicast=ch.getProtocolStack().findProtocol(Util.getUnicastProtocols()); - removeAllConnections(unicast); + Util.invoke(unicast, "removeAllConnections"); } setLevel("trace", a, b); System.out.println("--> A sending first message to B (dropped before it reaches B)"); discard.dropDownUnicasts(1); // drops the next unicast - a.send(new BytesMessage(b.getAddress(), 1)); + a.send(new ObjectMessage(b.getAddress(), 1)); List msgs=rb.list(); try { @@ -104,22 +104,23 @@ public void testFirstMessageDropped(Class unicast_class) thr } - protected static JChannel createChannel(Class unicast_class, String name) throws Exception { - UNICAST3 unicast=unicast_class.getDeclaredConstructor().newInstance(); + protected static JChannel createChannel(Class unicast_class, String name) throws Exception { + Protocol unicast=unicast_class.getDeclaredConstructor().newInstance(); + Util.invoke(unicast, "setXmitInterval", 500L); return new JChannel(new SHARED_LOOPBACK(), new SHARED_LOOPBACK_PING(), new NAKACK2().useMcastXmit(false), new DISCARD(), - unicast.setXmitInterval(500), + unicast, new GMS().printLocalAddress(false)) .name(name); } - protected void printConnectionTables(JChannel ... channels) { + protected static void printConnectionTables(JChannel... channels) throws Exception { System.out.println("**** CONNECTIONS:"); for(JChannel ch: channels) { - Protocol ucast=ch.getProtocolStack().findProtocol(Util.getUnicastProtocols()); - System.out.println(ch.getName() + ":\n" + printConnections(ucast) + "\n"); + Protocol p=ch.stack().findProtocol(Util.getUnicastProtocols()); + System.out.printf("%s:\n%s\n", ch.name(), Util.invoke(p, "printConnections")); } } @@ -128,22 +129,5 @@ protected static void setLevel(String level, JChannel... channels) { ch.getProtocolStack().findProtocol(Util.getUnicastProtocols()).level(level); } - protected String printConnections(Protocol prot) { - if(prot instanceof UNICAST3) { - UNICAST3 unicast=(UNICAST3)prot; - return unicast.printConnections(); - } - throw new IllegalArgumentException("prot (" + prot + ") needs to be UNICAST3"); - } - - protected static void removeAllConnections(Protocol prot) { - if(prot instanceof UNICAST3) { - UNICAST3 unicast=(UNICAST3)prot; - unicast.removeAllConnections(); - } - else - throw new IllegalArgumentException("prot (" + prot + ") needs to be UNICAST3"); - } - } diff --git a/tests/junit-functional/org/jgroups/protocols/UNICAST_DroppedAckTest.java b/tests/junit-functional/org/jgroups/protocols/UNICAST_DroppedAckTest.java index 67a66056c19..c456c51dc35 100644 --- a/tests/junit-functional/org/jgroups/protocols/UNICAST_DroppedAckTest.java +++ b/tests/junit-functional/org/jgroups/protocols/UNICAST_DroppedAckTest.java @@ -4,6 +4,7 @@ import org.jgroups.JChannel; import org.jgroups.protocols.pbcast.GMS; import org.jgroups.protocols.pbcast.NAKACK2; +import org.jgroups.stack.Protocol; import org.jgroups.util.Util; import org.testng.annotations.AfterMethod; import org.testng.annotations.DataProvider; @@ -15,11 +16,11 @@ * @author Bela Ban * @since 3.3 */ -@Test(groups=Global.FUNCTIONAL,singleThreaded=true) +@Test(groups=Global.FUNCTIONAL,singleThreaded=true,dataProvider="configProvider") public class UNICAST_DroppedAckTest { protected JChannel a, b; - protected void setup(Class unicast_class) throws Exception { + protected void setup(Class unicast_class) throws Exception { a=createChannel(unicast_class, "A"); b=createChannel(unicast_class, "B"); a.connect("UNICAST_DroppedAckTest"); @@ -32,39 +33,41 @@ protected void setup(Class unicast_class) throws Exception { @DataProvider static Object[][] configProvider() { return new Object[][]{ - {UNICAST3.class} + {UNICAST3.class}, + {UNICAST4.class} }; } - @Test(dataProvider="configProvider") - public void testNotEndlessXmits(Class unicast_class) throws Exception { + public void testNotEndlessXmits(Class unicast_class) throws Exception { setup(unicast_class); - - DISCARD discard_a=a.getProtocolStack().findProtocol(DISCARD.class); + DISCARD discard_a=a.stack().findProtocol(DISCARD.class); discard_a.dropDownUnicasts(5); // drops the next 5 ACKs for(int i=1; i <= 5; i++) b.send(a.getAddress(), i); - UNICAST3 ub=b.getProtocolStack().findProtocol(UNICAST3.class); + Protocol ub=b.getProtocolStack().findProtocol(Util.getUnicastProtocols()); for(int i=0; i < 10; i++) { - int num_unacked_msgs=ub.getNumUnackedMessages(); + int num_unacked_msgs=(int)Util.invoke(ub, "getNumUnackedMessages"); System.out.println("num_unacked_msgs=" + num_unacked_msgs); if(num_unacked_msgs == 0) break; Util.sleep(1000); } - assert ub.getNumUnackedMessages() == 0 : "num_unacked_msgs on B should be 0 but is " + ub.getNumUnackedMessages(); + assert 0 == (int)Util.invoke(ub, "getNumUnackedMessages") + : "num_unacked_msgs on B should be 0 but is " + Util.invoke(ub, "getNumUnackedMessages"); } - protected static JChannel createChannel(Class unicast_class, String name) throws Exception { + protected static JChannel createChannel(Class unicast_class, String name) throws Exception { + Protocol p=unicast_class.getDeclaredConstructor().newInstance(); + Util.invoke(p, "setXmitInterval", 500L); return new JChannel(new SHARED_LOOPBACK(), new SHARED_LOOPBACK_PING(), new MERGE3().setMaxInterval(3000).setMinInterval(1000), new NAKACK2(), new DISCARD(), - unicast_class.getDeclaredConstructor().newInstance().setXmitInterval(500), + p, new GMS()).name(name); } } diff --git a/tests/junit-functional/org/jgroups/protocols/UNICAST_MessagesToSelfTest.java b/tests/junit-functional/org/jgroups/protocols/UNICAST_MessagesToSelfTest.java index 9f4efa68ef2..ef37668b900 100644 --- a/tests/junit-functional/org/jgroups/protocols/UNICAST_MessagesToSelfTest.java +++ b/tests/junit-functional/org/jgroups/protocols/UNICAST_MessagesToSelfTest.java @@ -18,28 +18,26 @@ /** - * Tests the UNICAST{2,3} protocols with messages sent by member A to itself + * Tests the UNICAST{3,4} protocols with messages sent by member A to itself * @author Bela Ban */ -@Test(groups=Global.FUNCTIONAL,singleThreaded=true) +@Test(groups=Global.FUNCTIONAL,singleThreaded=true,dataProvider="createUnicast") public class UNICAST_MessagesToSelfTest { protected JChannel ch; protected Address a1; - - static final int SIZE=1000; // bytes - static final int NUM_MSGS=10000; - + static final int SIZE=1000; // bytes + static final int NUM_MSGS=10_000; @AfterMethod void tearDown() throws Exception {Util.close(ch);} @DataProvider - static Object[][] configProvider() { + static Object[][] createUnicast() { return new Object[][] { - {new UNICAST3()} + {new UNICAST3()}, + {new UNICAST4()} }; } - @Test(dataProvider="configProvider") public void testReceptionOfAllMessages(Protocol prot) throws Throwable { System.out.println("prot=" + prot.getClass().getSimpleName()); ch=createChannel(prot, null).name("A"); @@ -49,8 +47,6 @@ public void testReceptionOfAllMessages(Protocol prot) throws Throwable { _testReceptionOfAllMessages(); } - - @Test(dataProvider="configProvider") public void testReceptionOfAllMessagesWithDISCARD(Protocol prot) throws Throwable { System.out.println("prot=" + prot.getClass().getSimpleName()); DISCARD discard=new DISCARD(); @@ -62,8 +58,6 @@ public void testReceptionOfAllMessagesWithDISCARD(Protocol prot) throws Throwabl _testReceptionOfAllMessages(); } - - private static byte[] createPayload(int size, int seqno) { return ByteBuffer.allocate(size).putInt(seqno).array(); } diff --git a/tests/junit-functional/org/jgroups/protocols/UNICAST_OOB_Test.java b/tests/junit-functional/org/jgroups/protocols/UNICAST_OOB_Test.java index 36b303219d9..11140eac6ff 100644 --- a/tests/junit-functional/org/jgroups/protocols/UNICAST_OOB_Test.java +++ b/tests/junit-functional/org/jgroups/protocols/UNICAST_OOB_Test.java @@ -6,13 +6,12 @@ import org.jgroups.protocols.pbcast.NAKACK2; import org.jgroups.stack.Protocol; import org.jgroups.stack.ProtocolStack; +import org.jgroups.util.MyReceiver; import org.jgroups.util.Util; import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import java.util.Collections; -import java.util.LinkedList; import java.util.List; /** @@ -20,97 +19,127 @@ * https://issues.redhat.com/browse/JGRP-2327 * @author Bela Ban */ -@Test(groups=Global.FUNCTIONAL,singleThreaded=true) +@Test(groups=Global.FUNCTIONAL,singleThreaded=true,dataProvider="createUnicast") public class UNICAST_OOB_Test { protected JChannel a, b; - protected static final long XMIT_INTERVAL=500; - @BeforeMethod - void setup() throws Exception { - a=createChannel("A"); - b=createChannel("B"); + @DataProvider + static Object[][] createUnicast() { + return new Object[][]{ + {UNICAST3.class}, + {UNICAST4.class} + }; + } + + protected void setup(Class unicast_class) throws Exception { + a=createChannel("A", unicast_class); + b=createChannel("B", unicast_class); a.connect("UNICAST_OOB_Test"); b.connect("UNICAST_OOB_Test"); + Util.waitUntilAllChannelsHaveSameView(3000, 100, a,b); + System.out.printf("-- cluster formed: %s\n", b.view()); } @AfterMethod - void tearDown() throws Exception { - Util.close(b,a); - } + protected void tearDown() throws Exception {Util.close(b,a);} - public void testRegularMessages() throws Exception { + public void testRegularMessages(Class unicast_class) throws Exception { + setup(unicast_class); sendMessages(false); } - public void testOutOfBandMessages() throws Exception { + public void testOutOfBandMessages(Class unicast_class) throws Exception { + setup(unicast_class); sendMessages(true); } - /** Tests the case where B sends B1 and B2, but A receives B2 first (discards it) and requests retransmission of B1. JIRA: https://issues.redhat.com/browse/JGRP-2327 */ - public void testSecondMessageReceivedFirstRegular() throws Exception { + public void testSecondMessageReceivedFirstRegular(Class unicast_class) throws Exception { + setup(unicast_class); _testSecondMessageReceivedFirst(false, false); } - public void testSecondMessageReceivedFirstRegularBatched() throws Exception { - _testSecondMessageReceivedFirst(false, true); + public void testSecondMessageReceivedFirstRegularBatched(Class unicast_class) throws Exception { + setup(unicast_class); + _testSecondMessageReceivedFirst(false, true); } - public void testSecondMessageReceivedFirstOOB() throws Exception { + public void testSecondMessageReceivedFirstOOB(Class unicast_class) throws Exception { + setup(unicast_class); _testSecondMessageReceivedFirst(true, false); } - public void testSecondMessageReceivedFirstOOBBatched() throws Exception { + // @Test(invocationCount=100) + public void testSecondMessageReceivedFirstOOBBatched(Class unicast_class) throws Exception { + setup(unicast_class); _testSecondMessageReceivedFirst(true, true); } protected void _testSecondMessageReceivedFirst(boolean oob, boolean use_batches) throws Exception { Address dest=a.getAddress(), src=b.getAddress(); - UNICAST3 u_a=a.getProtocolStack().findProtocol(UNICAST3.class), u_b=b.getProtocolStack().findProtocol(UNICAST3.class); - u_a.removeReceiveConnection(src); - u_a.removeSendConnection(src); - u_b.removeReceiveConnection(dest); - u_b.removeSendConnection(dest); - System.out.println("=============== removed connection between A and B ==========="); + Protocol u_a=a.getProtocolStack().findProtocol(Util.getUnicastProtocols()), + u_b=b.getProtocolStack().findProtocol(Util.getUnicastProtocols()); + for(int i=0; i < 10; i++) { + Util.invoke(u_a, "removeReceiveConnection", src); + Util.invoke(u_a, "removeSendConnection", src); + Util.invoke(u_b, "removeReceiveConnection", dest); + Util.invoke(u_b, "removeSendConnection", dest); + int num_connections=(int)Util.invoke(u_a, "getNumConnections") + + (int)Util.invoke(u_b, "getNumConnections"); + if(num_connections == 0) + break; + Util.sleep(100); + } + System.out.println("=============== removed connections between A and B ==========="); - REVERSE reverse=new REVERSE().numMessagesToReverse(5) - .filter(msg -> msg.getDest() != null && src.equals(msg.getSrc()) && (msg.getFlags(false) == 0 || msg.isFlagSet(Message.Flag.OOB))); - a.getProtocolStack().insertProtocol(reverse, ProtocolStack.Position.BELOW, UNICAST3.class); + Protocol reverse=new REVERSE().numMessagesToReverse(5) + .filter(msg -> msg.getDest() != null && src.equals(msg.src()) && (msg.getFlags(false) == 0 || msg.isFlagSet(Message.Flag.OOB))); + // REVERSE2 reverse=new REVERSE2().filter(m -> m.dest() != null && m.isFlagSet(Message.Flag.OOB) && src.equals(m.src())); + a.getProtocolStack().insertProtocol(reverse, ProtocolStack.Position.BELOW, UNICAST3.class,UNICAST4.class); if(use_batches) { MAKE_BATCH mb=new MAKE_BATCH().unicasts(true).setAddress(dest); - a.getProtocolStack().insertProtocol(mb, ProtocolStack.Position.BELOW, UNICAST3.class); + a.getProtocolStack().insertProtocol(mb, ProtocolStack.Position.BELOW, UNICAST3.class,UNICAST4.class); mb.start(); } - MyReceiver r=new MyReceiver(); + MyReceiver r=new MyReceiver().name(a.getName()).verbose(true); a.setReceiver(r); - System.out.println("========== B sends messages 1-5 to A =========="); + System.out.printf("========== B sending %s messages 1-5 to A ==========\n", oob? "OOB" : "regular"); + //u_a.setLevel("trace"); u_b.setLevel("trace"); + long start=System.currentTimeMillis(); for(int i=1; i <= 5; i++) { - Message msg=new BytesMessage(dest, (long)i); + Message msg=new ObjectMessage(dest, (long)i); if(oob) msg.setFlag(Message.Flag.OOB); b.send(msg); + System.out.printf("-- %s: sent %s, hdrs: %s\n", b.address(), msg, msg.printHeaders()); } - Util.waitUntil(10000, 10, () -> r.size() == 5); + if(reverse instanceof REVERSE2) { + REVERSE2 rr=((REVERSE2)reverse); + Util.waitUntilTrue(2000, 100, () -> rr.size() == 5); + rr.filter(null); // from now on, all msgs are passed up + rr.deliver(); + } + + Util.waitUntil(5000, 100, () -> r.size() == 5, + () -> String.format("expected 5 messages but got %s", r.list())); long time=System.currentTimeMillis() - start; - System.out.printf("===== list: %s (in %d ms)\n", r.getSeqnos(), time); + System.out.printf("===== list: %s (in %d ms)\n", r.list(), time); long expected_time=XMIT_INTERVAL * 10; // increased because times might increase with the increase in parallel tests - assert time < XMIT_INTERVAL *2 : String.format("expected a time < %d ms, but got %d ms", expected_time, time); + assert time < expected_time : String.format("expected a time < %d ms, but got %d ms", expected_time, time); } - /** - * Check that 4 is received before 3 - */ + /** Check that 4 is received before 3 */ private void sendMessages(boolean oob) throws Exception { DISCARD_PAYLOAD discard=new DISCARD_PAYLOAD(); - MyReceiver receiver=new MyReceiver(); + MyReceiver receiver=new MyReceiver<>(); b.setReceiver(receiver); // the first channel will discard the unicast messages with seqno #3 two times, the let them pass down @@ -127,20 +156,12 @@ private void sendMessages(boolean oob) throws Exception { msg.setFlag(Message.Flag.OOB); System.out.println("-- sending message #" + i); a.send(msg); - Util.sleep(100); } // wait until retransmission of seqno #3 happens, so that 4 and 5 are received as well - long target_time=System.currentTimeMillis() + 30000; - do { - if(receiver.size() >= 5) - break; - Util.sleep(500); - } - while(target_time > System.currentTimeMillis()); + Util.waitUntilTrue(3000, 500, () -> receiver.size() >= 5); - - List seqnos=receiver.getSeqnos(); + List seqnos=receiver.list(); System.out.println("-- sequence numbers: " + seqnos); assert seqnos.size() == 5; @@ -161,40 +182,18 @@ private void sendMessages(boolean oob) throws Exception { } } - - protected static JChannel createChannel(String name) throws Exception { + protected static JChannel createChannel(String name, Class unicast_class) throws Exception { + Protocol p=unicast_class.getConstructor().newInstance(); + Util.invoke(p, "setXmitInterval", XMIT_INTERVAL); return new JChannel( - new SHARED_LOOPBACK(), + new SHARED_LOOPBACK(), // .bundler("nb"), new SHARED_LOOPBACK_PING(), new NAKACK2(), - new UNICAST3().setXmitInterval(XMIT_INTERVAL), - new GMS()) + p, + new GMS().printLocalAddress(false).setViewAckCollectionTimeout(100)) .name(name); } - public static class MyReceiver implements Receiver { - /** 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=msg.getObject(); - System.out.println(">> received " + num); - seqnos.add(num); - } - } - - public int size() {return seqnos.size();} - } - - } diff --git a/tests/junit-functional/org/jgroups/protocols/UNICAST_RetransmitTest.java b/tests/junit-functional/org/jgroups/protocols/UNICAST_RetransmitTest.java index edf747da09e..fb94ac5f197 100644 --- a/tests/junit-functional/org/jgroups/protocols/UNICAST_RetransmitTest.java +++ b/tests/junit-functional/org/jgroups/protocols/UNICAST_RetransmitTest.java @@ -1,14 +1,17 @@ package org.jgroups.protocols; -import org.jgroups.*; +import org.jgroups.Address; +import org.jgroups.Global; +import org.jgroups.JChannel; +import org.jgroups.Message; import org.jgroups.stack.Protocol; import org.jgroups.stack.ProtocolStack; +import org.jgroups.util.MyReceiver; import org.jgroups.util.Util; import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import java.util.ArrayList; import java.util.List; /** @@ -16,14 +19,21 @@ * @author Bela Ban * @since 3.6 */ -@Test(groups=Global.FUNCTIONAL,singleThreaded=true) +@Test(groups=Global.FUNCTIONAL,singleThreaded=true,dataProvider="createUnicast") public class UNICAST_RetransmitTest { protected JChannel a, b; protected static final int MAX_BUNDLE_SIZE=10000; protected static final int NUM_MSGS=50000; - @BeforeMethod - protected void setup() throws Exception { + @DataProvider + static Object[][] createUnicast() { + return new Object[][]{ + {UNICAST3.class}, + {UNICAST4.class} + }; + } + + protected void setup(Class unicast_class) throws Exception { a=new JChannel(Util.getTestStack()).name("A"); b=new JChannel(Util.getTestStack()).name("B"); change(a, b); @@ -37,17 +47,18 @@ protected void setup() throws Exception { /** - * Sends a number of messages, but discards every other message. The retransmission task in UNICAST3 is initially + * Sends a number of messages, but discards every other message. The retransmission task in UNICAST3/4 is initially * disabled. Then starts the retransmission task, which should generate an XMIT-REQ which is larger than * TP.max_bundle_size, leading to endless retransmissions. With JGRP-1868 resolved, the receiver should get * all messages. *

* https://issues.redhat.com/browse/JGRP-1868 */ - public void testLargeRetransmission() throws Exception { - MyReceiver receiver=new MyReceiver(); + public void testLargeRetransmission(Class unicast_class) throws Exception { + setup(unicast_class); + MyReceiver receiver=new MyReceiver<>(); b.setReceiver(receiver); - List list=receiver.getList(); + List list=receiver.list(); stopRetransmission(a, b); @@ -72,43 +83,28 @@ public void testLargeRetransmission() throws Exception { setLevel("warn", a,b); } - - - protected static void change(JChannel ... channels) { + protected static void change(JChannel ... channels) throws Exception { for(JChannel ch: channels) { TP transport=ch.getProtocolStack().getTransport(); transport.getBundler().setMaxSize(MAX_BUNDLE_SIZE); - UNICAST3 ucast=ch.getProtocolStack().findProtocol(UNICAST3.class); + Protocol ucast=ch.getProtocolStack().findProtocol(UNICAST3.class,UNICAST4.class); if(ucast == null) - throw new IllegalStateException("UNICAST3 not present in the stack"); - ucast.setMaxXmitReqSize(5000); + throw new IllegalStateException("UNICAST prototocol not found in the stack"); + Util.invoke(ucast, "setMaxXmitReqSize", 5000); } } - - protected static class MyReceiver implements Receiver { - protected final List list=new ArrayList<>(); - - public void receive(Message msg) { - Integer num=msg.getObject(); - list.add(num); - } - - public List getList() {return list;} - } - - - protected static void stopRetransmission(JChannel... channels) { + protected static void stopRetransmission(JChannel... channels) throws Exception { for(JChannel ch: channels) { - UNICAST3 ucast=ch.getProtocolStack().findProtocol(UNICAST3.class); - ucast.stopRetransmitTask(); + UNICAST3 ucast=ch.getProtocolStack().findProtocol(UNICAST3.class, UNICAST4.class); + Util.invoke(ucast, "stopRetransmitTask"); } } - protected static void startRetransmission(JChannel... channels) { + protected static void startRetransmission(JChannel... channels) throws Exception { for(JChannel ch: channels) { - UNICAST3 ucast=ch.getProtocolStack().findProtocol(UNICAST3.class); - ucast.startRetransmitTask(); + Protocol ucast=ch.getProtocolStack().findProtocol(UNICAST3.class, UNICAST4.class); + Util.invoke(ucast, "startRetransmitTask"); } } @@ -123,7 +119,7 @@ protected static void removeDiscardProtocol(JChannel ch) { protected static void setLevel(String level, JChannel ... channels) { for(JChannel ch: channels) { - Protocol prot=ch.getProtocolStack().findProtocol(UNICAST3.class); + Protocol prot=ch.getProtocolStack().findProtocol(UNICAST3.class,UNICAST4.class); prot.level(level); } } diff --git a/tests/junit-functional/org/jgroups/protocols/UNICAST3_Test.java b/tests/junit-functional/org/jgroups/protocols/UNICAST_Test.java similarity index 60% rename from tests/junit-functional/org/jgroups/protocols/UNICAST3_Test.java rename to tests/junit-functional/org/jgroups/protocols/UNICAST_Test.java index 0eede490b59..13d6589092e 100644 --- a/tests/junit-functional/org/jgroups/protocols/UNICAST3_Test.java +++ b/tests/junit-functional/org/jgroups/protocols/UNICAST_Test.java @@ -7,35 +7,39 @@ import org.jgroups.util.MessageBatch; import org.jgroups.util.Util; import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; /** * @author Bela Ban * @since 4.0 */ -@Test(groups=Global.FUNCTIONAL,singleThreaded=true) -public class UNICAST3_Test { +@Test(groups=Global.FUNCTIONAL,singleThreaded=true,dataProvider="createUnicast") +public class UNICAST_Test { protected JChannel a, b; - protected Address a_addr, b_addr; - protected MyReceiver receiver=new MyReceiver(); - protected UNICAST3 uni_a, uni_b; - protected DropUnicastAck drop_ack=new DropUnicastAck((short)499); + protected Protocol uni_a, uni_b; protected static final short UNICAST3_ID=ClassConfigurator.getProtocolId(UNICAST3.class); + protected static final short UNICAST4_ID=ClassConfigurator.getProtocolId(UNICAST4.class); protected static final int CONN_CLOSE_TIMEOUT=60_000; // change to a low value (e.g. 1000) to make this test fail - @BeforeMethod protected void setup() throws Exception { - a=create("A").connect(getClass().getSimpleName()); - b=create("B").connect(getClass().getSimpleName()); - a_addr=a.getAddress(); - b_addr=b.getAddress(); - uni_a=a.getProtocolStack().findProtocol(UNICAST3.class); - uni_b=b.getProtocolStack().findProtocol(UNICAST3.class); - b.getProtocolStack().insertProtocol(drop_ack, ProtocolStack.Position.BELOW, UNICAST3.class); + @DataProvider + static Object[][] createUnicast() { + return new Object[][]{ + {UNICAST3.class}, + {UNICAST4.class} + }; } - @AfterMethod protected void destroy() {Util.close(b, a);} + protected void setup(Class unicast_class) throws Exception { + a=create("A", unicast_class).connect(getClass().getSimpleName()); + b=create("B", unicast_class).connect(getClass().getSimpleName()); + uni_a=a.getProtocolStack().findProtocol(UNICAST3.class,UNICAST4.class); + uni_b=b.getProtocolStack().findProtocol(UNICAST3.class,UNICAST4.class); + DropUnicastAck drop_ack=new DropUnicastAck((short)499); + b.getProtocolStack().insertProtocol(drop_ack, ProtocolStack.Position.BELOW, UNICAST3.class,ReliableUnicast.class); + } + @AfterMethod protected void destroy() {Util.close(b, a);} /** - A and B exchanging unicast messages @@ -43,8 +47,8 @@ public class UNICAST3_Test { - B adds it to its table for A, and sends the ACK, then delivers the message - The ack for 499 from B to A is dropped - B excludes A (but A doesn't exclude B) and removes A's table - * This happens only if conn_close_timeout is small (default: 10s) - * If conn_close_timeout == 0, connections will not be removed + * This happens only if conn_close_timeout is small (default: 10s) + * If conn_close_timeout == 0, connections will not be removed - A retransmits 499 to B - B receives A:499, but asks for the first seqno - A has its highest seqno acked at 498, so resends 499 with first==true @@ -52,54 +56,48 @@ public class UNICAST3_Test { The issue is fixed by setting CONN_CLOSE_TIMEOUT to a highher value, or to 0 */ - public void testDuplicateMessageDelivery() throws Exception { + public void testDuplicateMessageDelivery(Class unicast_class) throws Exception { + setup(unicast_class); + Address a_addr=a.getAddress(); + Address b_addr=b.getAddress(); + MyReceiver receiver=new MyReceiver(); b.setReceiver(receiver); for(int i=1; i < 500; i++) a.send(b_addr, i); - for(int i=0; i < 10; i++) { - if(receiver.count >= 499) - break; - Util.sleep(50); - } + Util.waitUntilTrue(500, 50, () -> receiver.count >= 499); System.out.printf("B: received %d messages from A\n", receiver.count); assert receiver.count == 499; // remove A's receive window in B: System.out.printf("-- closing the receive-window for %s:\n", a_addr); // e.g. caused by an asymmetric network split: B excludes A, but not vice versa - uni_b.closeReceiveConnection(a_addr); - + Util.invoke(uni_b, "closeReceiveConnection", a_addr); uni_a.setLevel("trace"); uni_b.setLevel("trace"); - uni_b.setConnCloseTimeout(CONN_CLOSE_TIMEOUT); + Util.invoke(uni_b, "setConnCloseTimeout", CONN_CLOSE_TIMEOUT); // wait until B closes the receive window for A: for(int i=0; i < 10; i++) { - if(uni_b.getNumReceiveConnections() == 0) + int num_recv_conns=(int)Util.invoke(uni_b, "getNumReceiveConnections"); + if(num_recv_conns == 0) break; Util.sleep(500); } - // assert uni_b.getNumReceiveConnections() > 0; - // remove the DropUnicastAck protocol: System.out.printf("-- removing the %s protocol\n", DropUnicastAck.class.getSimpleName()); b.getProtocolStack().removeProtocol(DropUnicastAck.class); - for(int i=0; i < 20; i++) { - if(receiver.count >= 500) - break; - Util.sleep(100); - } + Util.waitUntilTrue(2000, 200, () -> receiver.count >= 500); System.out.printf("B: received %d messages from A\n", receiver.count); assert receiver.count == 499 : String.format("received %d messages, but should only have received 499", receiver.count); } - - protected static JChannel create(String name) throws Exception { - return new JChannel(new SHARED_LOOPBACK(), new SHARED_LOOPBACK_PING(), new UNICAST3()).name(name); + protected static JChannel create(String name, Class unicast_class) throws Exception { + Protocol ucast=unicast_class.getDeclaredConstructor().newInstance(); + return new JChannel(new SHARED_LOOPBACK(), new SHARED_LOOPBACK_PING(), ucast).name(name); } /** @@ -116,10 +114,20 @@ public DropUnicastAck(short start_drop_ack) { } public Object down(Message msg) { - UnicastHeader3 hdr=msg.getHeader(UNICAST3_ID); - if(hdr != null && hdr.type() == UnicastHeader3.ACK && hdr.seqno() == start_drop_ack) { - discarding=true; - hdr.seqno=start_drop_ack-1; // change 499 to 489, so A nevers gets the 499 ACK + UnicastHeader3 hdr3=msg.getHeader(UNICAST3_ID); + UnicastHeader hdr4=msg.getHeader(UNICAST4_ID); + + if(hdr3 != null) { + if(hdr3.type() == UnicastHeader3.ACK && hdr3.seqno() == start_drop_ack) { + discarding=true; + hdr3.seqno=start_drop_ack-1; // change 499 to 489, so A nevers gets the 499 ACK + } + } + else { + if(hdr4.type() == UnicastHeader.ACK && hdr4.seqno() == start_drop_ack) { + discarding=true; + hdr4.seqno=start_drop_ack-1; // change 499 to 489, so A nevers gets the 499 ACK + } } return down_prot.down(msg); } diff --git a/tests/junit-functional/org/jgroups/tests/AverageTest.java b/tests/junit-functional/org/jgroups/tests/AverageTest.java index 1f6d0fdc529..df85805ab4f 100644 --- a/tests/junit-functional/org/jgroups/tests/AverageTest.java +++ b/tests/junit-functional/org/jgroups/tests/AverageTest.java @@ -3,9 +3,11 @@ import org.jgroups.Global; import org.jgroups.util.Average; import org.jgroups.util.AverageMinMax; +import org.jgroups.util.ByteArray; import org.jgroups.util.Util; import org.testng.annotations.Test; +import java.io.IOException; import java.util.stream.IntStream; /** @@ -81,7 +83,7 @@ public void testMerge() { assert avg1.max() == 2; } - public void testMerger2() { + public void testMerge2() { AverageMinMax avg1=new AverageMinMax(10000), avg2=new AverageMinMax(10000); IntStream.rangeClosed(1, 10000).forEach(i -> avg2.add(2)); System.out.printf("avg1: %s, avg2: %s\n", avg1, avg2); @@ -93,10 +95,34 @@ public void testMerger2() { assert avg1.max() == 2; } + public void testMerge3() { + AverageMinMax avg1=new AverageMinMax(100), avg2=new AverageMinMax(200); + IntStream.rangeClosed(1, 100).forEach(i -> avg1.add(1)); + IntStream.rangeClosed(1, 200).forEach(i -> avg2.add(2)); + System.out.printf("avg1: %s, avg2: %s\n", avg1, avg2); + avg1.merge(avg2); + System.out.printf("merged avg1: %s\n", avg1); + assert avg1.count() == 300; + assert avg1.average() == 2.0; + assert avg1.min() == 1; + assert avg1.max() == 2; + } + public void testAverageWithNoElements() { Average avg=new AverageMinMax(); double av=avg.average(); assert av == 0.0; } + public void testSerialization() throws IOException, ClassNotFoundException { + Average avg=new Average(128); + for(int i=0; i < 100; i++) + avg.add(Util.random(128)); + ByteArray buf=Util.objectToBuffer(avg); + Average avg2=Util.objectFromBuffer(buf, null); + assert avg2 != null; + assert avg.count() == avg2.count(); + assert avg.average() == avg2.average(); + } + } diff --git a/tests/junit-functional/org/jgroups/tests/BufferTest.java b/tests/junit-functional/org/jgroups/tests/BufferTest.java index d9befaee8c0..92353cd4185 100644 --- a/tests/junit-functional/org/jgroups/tests/BufferTest.java +++ b/tests/junit-functional/org/jgroups/tests/BufferTest.java @@ -226,7 +226,7 @@ public void testAdditionMessageBatchWithOffset(Buffer type) { assertIndices(buf, 100, 100, 129); } - public void testAddListWithResizing(Buffer type) { + public void testAddBatchWithResizing(Buffer type) { if(type instanceof FixedBuffer) return; DynamicBuffer buf=new DynamicBuffer<>(3, 5, 0); @@ -252,7 +252,7 @@ public void testAddMessageBatchWithResizing(Buffer type) { } - public void testAddListWithResizingNegativeSeqnos(Buffer type) { + public void testAddBatchWithResizingNegativeSeqnos(Buffer type) { if(type instanceof FixedBuffer) return; long seqno=Long.MAX_VALUE-50; @@ -265,7 +265,7 @@ public void testAddListWithResizingNegativeSeqnos(Buffer type) { assert num_resizes == 1 : "number of resizings=" + num_resizes + " (expected 1)"; } - public void testAddListWithResizing2(Buffer type) { + public void testAddBatchWithResizing2(Buffer type) { if(type instanceof FixedBuffer) return; DynamicBuffer buf=new DynamicBuffer<>(3, 500, 0); @@ -410,7 +410,6 @@ public void testAddMissing(Buffer buf) { assert num == null; } - public void testDuplicateAddition(Buffer buf) { addAndGet(buf, 1, 5, 9, 10); assert !buf.add(5,5); @@ -517,6 +516,92 @@ public void testAddAndRemove4(Buffer type) { assert buf.highestDelivered() == 4; } + public void testAddListWithConstValue(Buffer buf) { + List> msgs=createList(1,2,3,4,5,6,7,8,9,10); + final int DUMMY=0; + boolean rc=buf.add(msgs, false, DUMMY); + System.out.println("buf = " + buf); + assert rc; + assert buf.size() == 10; + List list=buf.removeMany(true, 0, element -> element.hashCode() == Integer.hashCode(DUMMY)); + System.out.println("list = " + list); + assert list.size() == 10; + assert buf.isEmpty(); + for(int num: list) + assert num == DUMMY; + } + + public void testAddListWithResizingNegativeSeqnos(Buffer type) { + long seqno=Long.MAX_VALUE-50; + Buffer buf=type instanceof DynamicBuffer? new DynamicBuffer<>(3,5,seqno) : new FixedBuffer<>(100, seqno); + List> msgs=new ArrayList<>(); + for(int i=1; i < 100; i++) + msgs.add(new LongTuple<>((long)i+seqno,i)); + buf.add(msgs, false, null); + System.out.println("buf = " + buf); + if(type instanceof DynamicBuffer) { + int num_resizes=((DynamicBuffer)buf).getNumResizes(); + System.out.println("num_resizes = " + num_resizes); + assert num_resizes == 1 : "number of resizings=" + num_resizes + " (expected 1)"; + } + } + + public void testAddListWithRemoval2(Buffer buf) { + List> msgs=createList(1,2,3,4,5,6,7,8,9,10); + int size=msgs.size(); + boolean added=buf.add(msgs, false, null); + System.out.println("buf = " + buf); + assert added; + assert msgs.size() == size; + + added=buf.add(msgs, true, null); + System.out.println("buf = " + buf); + assert !added; + assert msgs.isEmpty(); + + msgs=createList(1,3,5,7); + size=msgs.size(); + added=buf.add(msgs, true, null); + System.out.println("buf = " + buf); + assert !added; + assert msgs.isEmpty(); + + msgs=createList(1,3,5,7,9,10,11,12,13,14,15); + size=msgs.size(); + added=buf.add(msgs, true, null); + System.out.println("buf = " + buf); + assert added; + assert msgs.size() == 5; + } + + public void testAddListWithResizing2(Buffer type) { + Buffer buf=type instanceof DynamicBuffer? new DynamicBuffer<>() : new FixedBuffer<>(100, 0); + List> msgs=new ArrayList<>(); + for(int i=1; i < 100; i++) + msgs.add(new LongTuple<>(i, i)); + buf.add(msgs, false, null); + System.out.println("buf = " + buf); + if(buf instanceof DynamicBuffer) { + int num_resizes=((DynamicBuffer)buf).getNumResizes(); + System.out.println("num_resizes = " + num_resizes); + assert num_resizes == 0 : "number of resizings=" + num_resizes + " (expected 0)"; + } + } + + + public void testAddListWithResizing(Buffer type) { + Buffer buf=type instanceof DynamicBuffer? new DynamicBuffer<>(3,5,0) : new FixedBuffer<>(100, 0); + List> msgs=new ArrayList<>(); + for(int i=1; i < 100; i++) + msgs.add(new LongTuple<>(i, i)); + buf.add(msgs, false, null); + System.out.println("buf = " + buf); + if(buf instanceof DynamicBuffer) { + int num_resizes=((DynamicBuffer)buf).getNumResizes(); + System.out.println("num_resizes = " + num_resizes); + assert num_resizes == 1 : "number of resizings=" + num_resizes + " (expected 1)"; + } + } public void testIndex(Buffer type) { Buffer buf=type instanceof DynamicBuffer? new DynamicBuffer<>(3, 10, 5) diff --git a/tests/junit-functional/org/jgroups/tests/ReliableUnicastTest.java b/tests/junit-functional/org/jgroups/tests/ReliableUnicastTest.java new file mode 100644 index 00000000000..213bfd89042 --- /dev/null +++ b/tests/junit-functional/org/jgroups/tests/ReliableUnicastTest.java @@ -0,0 +1,368 @@ +package org.jgroups.tests; + +import org.jgroups.Address; +import org.jgroups.Global; +import org.jgroups.Message; +import org.jgroups.ObjectMessage; +import org.jgroups.protocols.ReliableUnicast; +import org.jgroups.protocols.TP; +import org.jgroups.protocols.UNICAST4; +import org.jgroups.protocols.UnicastHeader; +import org.jgroups.stack.Protocol; +import org.jgroups.util.*; +import org.testng.annotations.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.LongAdder; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.jgroups.util.MessageBatch.Mode.OOB; +import static org.jgroups.util.MessageBatch.Mode.REG; + +/** + * Tests {@link ReliableUnicast}, ie. methods + * {@link ReliableUnicast#_getReceiverEntry(Address, long, boolean, short, Address)} and + * {@link ReliableUnicast#up(MessageBatch)} + * @author Bela Ban + * @since 5.4 + */ +@Test(groups=Global.FUNCTIONAL,singleThreaded=true,dataProvider="createUnicast") +public class ReliableUnicastTest { + protected ReliableUnicast unicast; + protected UpProtocol up_prot; + protected DownProtocol down_prot; + protected TP transport; + protected TimeScheduler timer; + protected static final Address DEST=Util.createRandomAddress("A"); + protected static final Address SRC=Util.createRandomAddress("B"); + protected static final AsciiString CLUSTER=new AsciiString("cluster"); + + @DataProvider + static Object[][] createUnicast() { + return new Object[][]{ + {UNICAST4.class} + }; + } + + @BeforeClass + protected void setupTimer() { + timer=new TimeScheduler3(); + } + + @AfterClass + protected void stopTimer() { + timer.stop(); + } + + protected void setup(Class unicast_cl) throws Exception { + unicast=unicast_cl.getConstructor().newInstance(); + unicast.addr(DEST); + up_prot=new UpProtocol(); + down_prot=new DownProtocol(); + transport=new MockTransport().cluster(CLUSTER).addr(DEST); + up_prot.setDownProtocol(unicast); + unicast.setUpProtocol(up_prot); + unicast.setDownProtocol(down_prot); + down_prot.setUpProtocol(unicast); + down_prot.setDownProtocol(transport); + transport.setUpProtocol(down_prot); + TimeService time_service=new TimeService(timer); + unicast.timeService(time_service); + unicast.lastSync(new ExpiryCache<>(5000)); + transport.init(); + } + + @AfterMethod + protected void destroy() { + unicast.stop(); + transport.stop(); + transport.destroy(); + } + + public void testGetReceiverEntryFirst(Class cl) throws Exception { + setup(cl); + ReliableUnicast.ReceiverEntry entry=unicast._getReceiverEntry(DEST, 1L, true, (short)0, null); + assert entry != null && entry.connId() == 0; + entry=unicast._getReceiverEntry(DEST, 1L, true, (short)0, null); + assert entry != null && entry.connId() == 0; + assert unicast.getNumReceiveConnections() == 1; + } + + public void testGetReceiverEntryNotFirst(Class cl) throws Exception { + setup(cl); + ReliableUnicast.ReceiverEntry entry=unicast._getReceiverEntry(DEST, 2L, false, (short)0, null); + assert entry == null; + assert down_prot.numSendFirstReqs() == 1; + } + + public void testGetReceiverEntryExists(Class cl) throws Exception { + setup(cl); + ReliableUnicast.ReceiverEntry entry=unicast._getReceiverEntry(DEST, 1L, true, (short)1, null); + ReliableUnicast.ReceiverEntry old=entry; + assert entry != null && entry.connId() == 1; + + // entry exists, but this conn-ID is smaller + entry=unicast._getReceiverEntry(DEST, 1L, true, (short)0, null); + assert entry == null; + + // entry exists and conn-IDs match + ReliableUnicast.ReceiverEntry e=unicast._getReceiverEntry(DEST, 2L, true, (short)1, null); + assert e != null && e == old; + + // entry exists, but is replaced by higher conn_id + entry=unicast._getReceiverEntry(DEST, 5L, true, (short)2, null); + assert entry.connId() == 2; + assert entry.buf().high() == 4; + + entry=unicast._getReceiverEntry(DEST, 10L, false, (short)3, null); + assert entry == null; + assert down_prot.numSendFirstReqs() == 1; + } + + public void testBatch(Class cl) throws Exception { + setup(cl); + testBatch(false); + } + + public void testBatchOOB(Class cl) throws Exception { + setup(cl); + testBatch(true); + } + + public void testBatchWithFirstMissing(Class cl) throws Exception { + setup(cl); + testBatchWithFirstMissing(false); + } + + public void testBatchWithFirstMissingOOB(Class cl) throws Exception { + setup(cl); + testBatchWithFirstMissing(true); + } + + public void testBatchWithFirstMissingAndExistingMessages(Class cl) throws Exception { + setup(cl); + testBatchWithFirstMissingAndExistingMessages(false); + } + + public void testBatchWithFirstMissingAndExistingMessagesOOB(Class cl) throws Exception { + setup(cl); + testBatchWithFirstMissingAndExistingMessages(true); + } + + public void testBatchWithFirstMissingAndEmptyBatch(Class cl) throws Exception { + setup(cl); + testBatchWithFirstMissingAndEmptyBatch(false); + } + + public void testBatchWithFirstMissingAndEmptyBatchOOB(Class cl) throws Exception { + setup(cl); + testBatchWithFirstMissingAndEmptyBatch(true); + } + + public void testBatchWithDifferentConnIds(Class cl) throws Exception { + setup(cl); + testBatchWithDifferentConnIds(false); + } + + public void testBatchWithDifferentConnIdsOOB(Class cl) throws Exception { + setup(cl); + testBatchWithDifferentConnIds(true); + } + + public void testBatchWithDifferentConnIds2(Class cl) throws Exception { + setup(cl); + testBatchWithDifferentConnIds2(false); + } + + public void testBatchWithDifferentConnIds2OOB(Class cl) throws Exception { + setup(cl); + testBatchWithDifferentConnIds2(true); + } + + protected void testBatch(boolean oob) throws Exception { + MessageBatch mb=create(DEST, SRC, oob, 1, 10, (short)0); + unicast.up(mb); + List list=up_prot.list(); + Util.waitUntilTrue(2000, 200, () -> list.size() == 10); + assert list.size() == 10; + List expected=IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toList()); + assert list.equals(expected); + } + + protected void testBatchWithFirstMissing(boolean oob) throws Exception { + MessageBatch mb=create(DEST, SRC, oob, 1, 10, (short)0); + mb.array().set(0, null); + unicast.up(mb); + List list=up_prot.list(); + Util.waitUntilTrue(1000, 200, () -> list.size() == 9); + assert list.isEmpty(); + // Now send the first seqno: + mb=create(DEST, SRC, oob, 11, 10, (short)0); + Message msg=new ObjectMessage(DEST, 1).src(SRC) + .putHeader(unicast.getId(), UnicastHeader.createDataHeader(1L, (short)0, true)); + if(oob) + msg.setFlag(Message.Flag.OOB); + mb.add(msg); + unicast.up(mb); + Util.waitUntil(2000, 200, () -> list.size() == 20); + List expected=IntStream.rangeClosed(1, 20).boxed().collect(Collectors.toList()); + if(oob) { + expected.remove(0); + expected.add(1); + } + assert list.equals(expected); + } + + protected void testBatchWithFirstMissingAndExistingMessages(boolean oob) throws Exception { + MessageBatch mb=create(DEST, SRC, oob, 1, 10, (short)0); + mb.array().set(0, null); + unicast.up(mb); + List list=up_prot.list(); + Util.waitUntilTrue(1000, 200, () -> list.size() == 9); + assert list.isEmpty(); + + // Now send the first seqno, but also existing messages 1-10 (and new messages 11-20) + mb=create(DEST, SRC, oob, 1, 20, (short)0); + unicast.up(mb); + Util.waitUntil(2000, 200, () -> list.size() == 20); + List expected=IntStream.rangeClosed(1, 20).boxed().collect(Collectors.toList()); + if(oob) { + expected.remove((Object)1); + expected.add(9, 1); + } + assert list.equals(expected); + } + + protected void testBatchWithFirstMissingAndEmptyBatch(boolean oob) throws Exception { + MessageBatch mb=create(DEST, SRC, oob, 1, 10, (short)0); + mb.array().set(0, null); + unicast.up(mb); + List list=up_prot.list(); + Util.waitUntilTrue(1000, 200, () -> list.size() == 9); + assert list.isEmpty(); + + // Now send the first seqno, but also existing messages 1-10 (and new messages 11-20) + mb=create(DEST, SRC, oob, 1, 1, (short)0); + unicast.up(mb); + Util.waitUntil(2000, 200, () -> list.size() == 10); + List expected=IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toList()); + if(oob) { + expected.remove((Object)1); + expected.add(9, 1); + } + assert list.equals(expected); + } + + protected void testBatchWithDifferentConnIds(boolean oob) throws TimeoutException { + MessageBatch mb=create(DEST, SRC, oob, 1, 20, (short)0); + List buf=mb.array(); + for(int i=0; i < buf.size(); i++) { + short conn_id=(short)Math.min(i, 10); + ((UnicastHeader)buf.get(i).getHeader(unicast.getId())).connId(conn_id); + } + List list=up_prot.list(); + unicast.up(mb); + Util.waitUntilTrue(1000, 200, () -> list.size() == 10); + assert list.isEmpty(); + + Message msg=new ObjectMessage(DEST, 10).src(SRC) + .putHeader(unicast.getId(), UnicastHeader.createDataHeader(10, (short)10, true)); + if(oob) + msg.setFlag(Message.Flag.OOB); + unicast.up(msg); + Util.waitUntil(2000, 200, () -> list.size() == 11); + List expected=IntStream.rangeClosed(10, 20).boxed().collect(Collectors.toList()); + if(oob) { + expected.remove(0); + expected.add(10); + } + assert list.equals(expected) : String.format("expected %s, but got: %s", expected, list); + } + + protected void testBatchWithDifferentConnIds2(boolean oob) throws TimeoutException { + MessageBatch mb=new MessageBatch(20).dest(DEST).sender(SRC).setMode(oob? OOB : REG); + short conn_id=5; + for(int i=20; i > 0; i--) { + if(i % 5 == 0) + conn_id--; + Message msg=new ObjectMessage(DEST, i).src(SRC) + .putHeader(unicast.getId(), UnicastHeader.createDataHeader(i, conn_id, false)); + if(oob) + msg.setFlag(Message.Flag.OOB); + mb.add(msg); + } + List list=up_prot.list(); + unicast.up(mb); + Util.waitUntilTrue(1000, 200, () -> list.size() == 5); + assert list.isEmpty(); + Message msg=new ObjectMessage(DEST, 16).src(SRC) + .putHeader(unicast.getId(), UnicastHeader.createDataHeader(16, (short)4, true)); + if(oob) + msg.setFlag(Message.Flag.OOB); + unicast.up(msg); + Util.waitUntilTrue(2000, 200, () -> list.size() == 5); + List expected=IntStream.rangeClosed(16, 20).boxed().collect(Collectors.toList()); + if(oob) + Collections.reverse(expected); + assert list.equals(expected) : String.format("expected %s, but got: %s", expected, list); + } + + protected MessageBatch create(Address dest, Address sender, boolean oob, int start_seqno, int num_msgs, short conn_id) { + MessageBatch mb=new MessageBatch(dest, sender, CLUSTER, false, oob? OOB : REG, 16); + for(int i=start_seqno; i < start_seqno+num_msgs; i++) { + Message msg=new ObjectMessage(dest, i).src(sender) + .putHeader(unicast.getId(), UnicastHeader.createDataHeader(i, conn_id, i == 1)); + if(oob) + msg.setFlag(Message.Flag.OOB); + mb.add(msg); + } + return mb; + } + + protected static class DownProtocol extends Protocol { + protected final LongAdder num_send_first_reqs=new LongAdder(); + + protected long numSendFirstReqs() {return num_send_first_reqs.sum();} + protected DownProtocol clear() {num_send_first_reqs.reset(); return this;} + + @Override + public Object down(Message msg) { + UnicastHeader hdr=msg.getHeader(up_prot.getId()); + if(hdr != null && hdr.type() == UnicastHeader.SEND_FIRST_SEQNO) + num_send_first_reqs.increment(); + return null; + } + } + + protected static class UpProtocol extends Protocol { + protected final List list=new ArrayList<>(); + protected boolean raw_msgs; + + protected List list() {return list;} + protected UpProtocol clear() {list.clear(); return this;} + public UpProtocol rawMsgs(boolean flag) {this.raw_msgs=flag; return this;} + + @Override + public Object up(Message msg) { + T obj=raw_msgs? (T)msg : (T)msg.getObject(); + synchronized(list) { + list.add(obj); + } + return null; + } + + @Override + public void up(MessageBatch batch) { + synchronized(list) { + for(Message m: batch) { + T obj=raw_msgs? (T)m : (T)m.getObject(); + list.add(obj); + } + } + } + } +} diff --git a/tests/junit-functional/org/jgroups/tests/UnicastUnitTest.java b/tests/junit-functional/org/jgroups/tests/UnicastUnitTest.java index 4e7c0fc40ca..7a5ce40b808 100644 --- a/tests/junit-functional/org/jgroups/tests/UnicastUnitTest.java +++ b/tests/junit-functional/org/jgroups/tests/UnicastUnitTest.java @@ -7,15 +7,18 @@ import org.jgroups.protocols.pbcast.STABLE; import org.jgroups.stack.Protocol; import org.jgroups.stack.ProtocolStack; +import org.jgroups.util.MyReceiver; import org.jgroups.util.ResourceManager; import org.jgroups.util.Util; import org.testng.annotations.AfterMethod; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -23,50 +26,55 @@ * Tests unicast functionality * @author Bela Ban */ -@Test(groups=Global.FUNCTIONAL,singleThreaded=true) +@Test(groups=Global.FUNCTIONAL,singleThreaded=true,dataProvider="create") public class UnicastUnitTest { protected JChannel a, b, c, d; + @DataProvider + static Object[][] create() { + return new Object[][]{ + {UNICAST3.class}, + {UNICAST4.class} + }; + } + @AfterMethod protected void tearDown() throws Exception {Util.closeReverse(a,b,c,d);} - public void testUnicastMessageInCallbackExistingMember() throws Throwable { + public void testUnicastMessageInCallbackExistingMember(Class cl) throws Throwable { String mcast_addr=ResourceManager.getNextMulticastAddress(); - a=create("A", false, mcast_addr); - b=create("B", false, mcast_addr); + a=create(cl, "A", false, mcast_addr); + b=create(cl, "B", false, mcast_addr); a.connect("UnicastUnitTest"); - MyReceiver receiver=new MyReceiver(a); + MyReceiver receiver=new MyReceiver<>(); a.setReceiver(receiver); b.connect("UnicastUnitTest"); a.setReceiver(null); // so the receiver doesn't get a view change - Throwable ex=receiver.getEx(); - if(ex != null) - throw ex; } /** Tests sending msgs from A to B */ // @Test(invocationCount=10) - public void testMessagesToOther() throws Exception { + public void testMessagesToOther(Class cl) throws Exception { String mcast_addr=ResourceManager.getNextMulticastAddress(); - a=create("A", false, mcast_addr); - b=create("B", false, mcast_addr); + a=create(cl, "A", false, mcast_addr); + b=create(cl, "B", false, mcast_addr); _testMessagesToOther(); } - public void testMessagesToOtherBatching() throws Exception { + public void testMessagesToOtherBatching(Class cl) throws Exception { String mcast_addr=ResourceManager.getNextMulticastAddress(); - a=create("A", true, mcast_addr); - b=create("B", true, mcast_addr); + a=create(cl, "A", true, mcast_addr); + b=create(cl, "B", true, mcast_addr); _testMessagesToOther(); } - public void testMessagesToEverybodyElse() throws Exception { - MyReceiver r1=new MyReceiver(), r2=new MyReceiver(), r3=new MyReceiver(), r4=new MyReceiver(); + public void testMessagesToEverybodyElse(Class cl) throws Exception { + MyReceiver r1=new MyReceiver<>(), r2=new MyReceiver<>(), r3=new MyReceiver<>(), r4=new MyReceiver<>(); String mcast_addr=ResourceManager.getNextMulticastAddress(); - a=create("A", false, mcast_addr); - b=create("B", false, mcast_addr); - c=create("C", false, mcast_addr); - d=create("D", false, mcast_addr); + a=create(cl, "A", false, mcast_addr); + b=create(cl, "B", false, mcast_addr); + c=create(cl, "C", false, mcast_addr); + d=create(cl, "D", false, mcast_addr); connect(a,b,c,d); a.setReceiver(r1); @@ -89,8 +97,7 @@ public void testMessagesToEverybodyElse() throws Exception { Util.sleep(500); } - Stream.of(r1,r2,r3,r4).forEach(r -> System.out.printf("%s\n", r.list)); - + Stream.of(r1,r2,r3,r4).forEach(r -> System.out.printf("%s\n", r.list())); List expected_list=Arrays.asList(1,2,3,4,5); System.out.print("Checking (per-sender) FIFO ordering of messages: "); Stream.of(r1,r2,r3,r4).forEach(r -> Stream.of(a, b, c, d).forEach(ch -> { @@ -103,10 +110,10 @@ public void testMessagesToEverybodyElse() throws Exception { System.out.println("OK"); } - public void testPartition() throws Exception { + public void testPartition(Class cl) throws Exception { String mcast_addr=ResourceManager.getNextMulticastAddress(); - a=create("A", false, mcast_addr); - b=create("B", false, mcast_addr); + a=create(cl, "A", false, mcast_addr); + b=create(cl, "B", false, mcast_addr); connect(a,b); System.out.println("-- Creating network partition"); Stream.of(a,b).forEach(ch -> { @@ -152,26 +159,28 @@ protected void _testMessagesToOther() throws Exception { b.setReceiver(receiver); send(a, msgs); checkReception(receiver, false, 1,2,3,4,5); + checkUnackedMessages(0, a); } // @Test(invocationCount=10) - public void testMessagesToSelf() throws Exception { + public void testMessagesToSelf(Class cl) throws Exception { String mcast_addr=ResourceManager.getNextMulticastAddress(); - a=create("A", false, mcast_addr); - b=create("B", false, mcast_addr); + a=create(cl, "A", false, mcast_addr); + b=create(cl, "B", false, mcast_addr); _testMessagesToSelf(); } - public void testMessagesToSelfBatching() throws Exception { + public void testMessagesToSelfBatching(Class cl) throws Exception { String mcast_addr=ResourceManager.getNextMulticastAddress(); - a=create("A", true, mcast_addr); - b=create("B", true, mcast_addr); + a=create(cl, "A", true, mcast_addr); + b=create(cl, "B", true, mcast_addr); _testMessagesToSelf(); } protected void _testMessagesToSelf() throws Exception { connect(a,b); + Util.waitUntilAllChannelsHaveSameView(3000, 100, a,b); Address dest=a.getAddress(); Message[] msgs={ msg(dest), @@ -189,24 +198,26 @@ protected void _testMessagesToSelf() throws Exception { a.setReceiver(receiver); send(a, msgs); checkReception(receiver, false, 1,2,3,5,8,9); + checkUnackedMessages(0, a); } - public void testMessagesToSelf2() throws Exception { + public void testMessagesToSelf2(Class cl) throws Exception { String mcast_addr=ResourceManager.getNextMulticastAddress(); - a=create("A", false, mcast_addr); - b=create("B", false, mcast_addr); + a=create(cl, "A", false, mcast_addr); + b=create(cl, "B", false, mcast_addr); _testMessagesToSelf2(); } - public void testMessagesToSelf2Batching() throws Exception { + public void testMessagesToSelf2Batching(Class cl) throws Exception { String mcast_addr=ResourceManager.getNextMulticastAddress(); - a=create("A", true, mcast_addr); - b=create("B", true, mcast_addr); + a=create(cl, "A", true, mcast_addr); + b=create(cl, "B", true, mcast_addr); _testMessagesToSelf2(); } protected void _testMessagesToSelf2() throws Exception { connect(a,b); + Util.waitUntilAllChannelsHaveSameView(3000, 100, a,b); Address dest=a.getAddress(); Message[] msgs={ msg(dest).setFlag(Message.Flag.OOB).setFlag(Message.TransientFlag.DONT_LOOPBACK), @@ -226,9 +237,9 @@ protected void _testMessagesToSelf2() throws Exception { a.setReceiver(receiver); send(a, msgs); checkReception(receiver, false, 2,5,6,10); + checkUnackedMessages(0, a); } - protected static void send(JChannel ch, Message... msgs) throws Exception { int cnt=1; for(Message msg: msgs) { @@ -240,11 +251,7 @@ protected static void send(JChannel ch, Message... msgs) throws Exception { protected static void checkReception(MyReceiver r, boolean check_order, int... num) { List received=r.list(); - for(int i=0; i < 10; i++) { - if(received.size() == num.length) - break; - Util.sleep(500); - } + Util.waitUntilTrue(3000, 500, () -> received.size() == num.length); List expected=new ArrayList<>(num.length); for(int n: num) expected.add(n); System.out.println("received=" + received + ", expected=" + expected); @@ -255,9 +262,17 @@ protected static void checkReception(MyReceiver r, boolean check_order, assert num[i] == received.get(i); } - protected static Message msg(Address dest) {return new BytesMessage(dest);} + protected static void checkUnackedMessages(int expected, JChannel ... channels) throws TimeoutException { + Util.waitUntil(3000, 100, + () -> Stream.of(channels).map(ch -> ch.stack().findProtocol(UNICAST3.class, UNICAST4.class)) + .map(rp -> rp instanceof UNICAST4? ((UNICAST4)rp).getNumUnackedMessages() + : ((UNICAST3)rp).getNumUnackedMessages()) + .allMatch(num -> num == expected)); + } + + protected static Message msg(Address dest) {return new ObjectMessage(dest);} - protected static JChannel create(String name, boolean use_batching, String mcast_addr) throws Exception { + protected static JChannel create(Class cl, String name, boolean use_batching, String mcast_addr) throws Exception { Protocol[] protocols={ new UDP().setMcastGroupAddr(InetAddress.getByName(mcast_addr)).setBindAddress(Util.getLoopback()), new LOCAL_PING(), @@ -265,7 +280,8 @@ protected static JChannel create(String name, boolean use_batching, String mcast new FD_ALL3().setTimeout(2000).setInterval(500), new NAKACK2(), new MAKE_BATCH().sleepTime(100).unicasts(use_batching), - new UNICAST3(), + //new UNBATCH(), + cl.getConstructor().newInstance(), new STABLE(), new GMS().setJoinTimeout(1000), new FRAG2().setFragSize(8000), @@ -279,41 +295,4 @@ protected static void connect(JChannel... channels) throws Exception { Util.waitUntilAllChannelsHaveSameView(10000, 1000, channels); } - - protected static class MyReceiver implements Receiver { - protected JChannel channel; - protected Throwable ex; - protected final List list=new ArrayList<>(); - - public MyReceiver() {this(null);} - public MyReceiver(JChannel ch) {this.channel=ch;} - public Throwable getEx() {return ex;} - public List list() {return list;} - public void clear() {list.clear();} - - public void receive(Message msg) { - T obj=msg.getObject(); - synchronized(list) { - list.add(obj); - } - } - - /* public void viewAccepted(View new_view) { - if(channel == null) return; - Address local_addr=channel.getAddress(); - assert local_addr != null; - System.out.println("[" + local_addr + "]: " + new_view); - List

members=new LinkedList<>(new_view.getMembers()); - assert 2 == members.size() : "members=" + members + ", local_addr=" + local_addr + ", view=" + new_view; - Address dest=members.get(0); - Message unicast_msg=new EmptyMessage(dest); - try { - channel.send(unicast_msg); - } - catch(Throwable e) { - ex=e; - throw new RuntimeException(e); - } - }*/ - } } \ No newline at end of file diff --git a/tests/junit-functional/org/jgroups/tests/UtilTest.java b/tests/junit-functional/org/jgroups/tests/UtilTest.java index a2091b43668..f24e6914127 100644 --- a/tests/junit-functional/org/jgroups/tests/UtilTest.java +++ b/tests/junit-functional/org/jgroups/tests/UtilTest.java @@ -700,17 +700,26 @@ public void testBufferToArray() { assert receiver.name.equals(hello); } + public void testIsAssignable() { + assert !Util.isAssignableFrom(null, Long.class); + assert !Util.isAssignableFrom(long.class, null); + assert Util.isAssignableFrom(Long.class, null); + assert Util.isAssignableFrom(Long.class, Integer.class); + assert Util.isAssignableFrom(Long.class, Long.class); + assert Util.isAssignableFrom(double.class, Integer.class); + assert Util.isAssignableFrom(Long.class, long.class); + assert Util.isAssignableFrom(long.class, Long.class); + } + private static void marshalString(int size) throws Exception { byte[] tmp=new byte[size]; - String str=new String(tmp, 0, tmp.length); + String str=new String(tmp); byte[] retval=Util.objectToByteBuffer(str); System.out.println("length=" + retval.length + " bytes"); String obj=Util.objectFromByteBuffer(retval); System.out.println("read " + obj.length() + " string"); } - - static void objectToByteBuffer(Object obj) throws Exception { byte[] buf=Util.objectToByteBuffer(obj); assert buf != null; diff --git a/tests/junit/org/jgroups/tests/UUIDCacheClearTest.java b/tests/junit/org/jgroups/tests/UUIDCacheClearTest.java index 7e6eff10dc6..5e2c705a1f5 100644 --- a/tests/junit/org/jgroups/tests/UUIDCacheClearTest.java +++ b/tests/junit/org/jgroups/tests/UUIDCacheClearTest.java @@ -3,7 +3,6 @@ import org.jgroups.*; import org.jgroups.blocks.LazyRemovalCache; -import org.jgroups.protocols.UNICAST3; import org.jgroups.stack.Protocol; import org.jgroups.util.Util; import org.testng.annotations.Test; @@ -30,7 +29,7 @@ public void testCacheClear() throws Exception { b=createChannel().name("B"); b.setReceiver(r2); boolean unicast_absent=Stream.of(a, b).map(JChannel::getProtocolStack) - .anyMatch(st -> st.findProtocol(UNICAST3.class) == null); + .anyMatch(st -> st.findProtocol(Util.getUnicastProtocols()) == null); makeUnique(a,b); a.connect("UUIDCacheClearTest"); @@ -64,10 +63,10 @@ public void testCacheClear() throws Exception { a.send(b.getAddress(), "one"); b.send(a.getAddress(), "two"); - // With UNICAST3 present, the above 2 messages will get dropped, as the cache doesn't have the dest addrs and - // discovery is *async*. However, retransmission ensures that they're retransmitted. With UNICAST3 *absent*, - // this won't happen, so we're waiting for async discovery to complete and then resend the messages. Kind of - // a kludge to make this test pass. + // With UNICAST{3,4} present, the above 2 messages will get dropped, as the cache doesn't have the dest + // addrs and discovery is *async*. However, retransmission ensures that they're retransmitted. With + // UNICAST{3,4} *absent*, this won't happen, so we're waiting for async discovery to complete and then + // resend the messages. Kind of a kludge to make this test pass. if(unicast_absent) { LazyRemovalCache cache_a, cache_b; cache_a=a.getProtocolStack().getTransport().getLogicalAddressCache(); diff --git a/tests/other/org/jgroups/tests/UnicastTest.java b/tests/other/org/jgroups/tests/UnicastTest.java index 22cf93cd4a5..a1729bcd2a7 100644 --- a/tests/other/org/jgroups/tests/UnicastTest.java +++ b/tests/other/org/jgroups/tests/UnicastTest.java @@ -306,7 +306,7 @@ public void receive(Message msg) { long time=System.currentTimeMillis() - start; double msgs_sec=(current_value.get() / (time / 1000.0)); double throughput=total_bytes.get() / (time / 1000.0); - System.out.printf("\nreceived %d messages in %d ms (%.2f msgs/sec), throughput=%s%n", + System.out.printf("\nreceived %,d messages in %d ms (%.2f msgs/sec), throughput=%s%n", current_value.get(), time, msgs_sec, Util.printBytes(throughput)); break; } diff --git a/tests/perf/org/jgroups/tests/perf/UPerf.java b/tests/perf/org/jgroups/tests/perf/UPerf.java index 9175a8d5d02..0f5b99c53ba 100644 --- a/tests/perf/org/jgroups/tests/perf/UPerf.java +++ b/tests/perf/org/jgroups/tests/perf/UPerf.java @@ -471,6 +471,8 @@ protected void printView() { } protected static String print(AverageMinMax avg, boolean details) { + if(avg == null) + return "n/a"; return details? String.format("min/avg/max = %,.2f/%,.2f/%,.2f us", avg.min() / 1000.0, avg.average() / 1000.0, avg.max() / 1000.0) : String.format("avg = %,.2f us", avg.average() / 1000.0);