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/conf/sym-encrypt.xml b/conf/sym-encrypt.xml index 2259e05d447..731c8863f07 100644 --- a/conf/sym-encrypt.xml +++ b/conf/sym-encrypt.xml @@ -23,9 +23,5 @@ - - diff --git a/doc/manual/api.adoc b/doc/manual/api.adoc index 10726893a1f..e77aac91752 100644 --- a/doc/manual/api.adoc +++ b/doc/manual/api.adoc @@ -269,48 +269,22 @@ to better performance. JGroups 5.0 comes with a number of message types (see the next sections). If none of them are a fit for the application's requirements, new message types can be defined and registered. To do this, the new message type needs to implement -`Message` (typically by subclassing `BaseMessage`) and registering it with the `MessageFactory` in the transport: +`Message` (typically by subclassing `BaseMessage`) and registering it with the `MessageFactory`: [source,java] ---- CustomMessage msg=new CustomMessage(...); -JChannel ch; -TP transport=ch.getProtocolStack().getTransport(); -MessageFactory mf=transport.getMessageFactory(); -mf.register((short)12345, CustomMessage::new) +MessageFactory.register((short)12345, CustomMessage::new) ---- A (unique) ID has to be assigned with the message type, and then it has to be registered with the message factory in the transport. This has to be done before sending an instance of the new message type. If the ID has already been registered before, or is taken, an exception will be thrown. -Note that the default implementation of `MessageFactory` requires all IDs to be greater than 32, so that there's room -for adding built-in message types. +`MessageFactory` requires all IDs to be greater than 32, so that there's room for adding built-in message types. NOTE: It is recommended to register all custom message types _before_ connecting the channel, so that potential errors are detected early. -[[CustomMessageFactory]] -==== Custom `MessageFactory` -`MessageFactory` is a simple interface: - -[source,java] ----- -public interface MessageFactory { - T create(short id); - void register(short type, Supplier generator); -} ----- -We saw the that the `register()` method is used to associate new message types with IDs <>. - -There is a `DefaultMessageFactory` which is set in the transport (`TP`). If more control over the creation of custom -messages is desired, a custom implementation of `MessageFactory` can be written and registered in the transport, using -`TP.setMessageFactory(MessageFactory mf)`. - -An example for why we might want to provide our own `MessageFactory` is that we have control over the creation of -messages; e.g. to create an `NioMessage` with a *direct* `ByteBuffer`, we may want to use a _pool_ of off-heap memory -rather than calling `ByteBuffer.allocateDirect()` for each message, which is slow. - - [[BytesMessage]] ==== BytesMessage This is the equivalent to the 4.x `Message`, and contains a byte array, offset and length. There are methods to get and @@ -365,8 +339,7 @@ The methods of `NioMessage` are: |========================== NOTE: The envisioned use case for `useDirectMemory()` is when we send an `NioMessage` with a direct `ByteBuffer`, but - don't need the `ByteBuffer` to be created in off-heap memory at the receiver, when on-heap will do. + - The alternative is to provide a custom <>. + don't need the `ByteBuffer` to be created in off-heap memory at the receiver, when on-heap will do. diff --git a/pom.xml b/pom.xml index 2bacf0bf9fc..c3001fc858b 100644 --- a/pom.xml +++ b/pom.xml @@ -427,7 +427,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 3.5.1 + 3.5.2 org.apache.maven.plugins diff --git a/src/org/jgroups/BatchMessage.java b/src/org/jgroups/BatchMessage.java index 85276c180f4..5a0977c0a14 100644 --- a/src/org/jgroups/BatchMessage.java +++ b/src/org/jgroups/BatchMessage.java @@ -1,4 +1,3 @@ - package org.jgroups; @@ -32,8 +31,6 @@ public class BatchMessage extends BaseMessage implements Iterable { protected Address orig_src; - protected static final MessageFactory mf=new DefaultMessageFactory(); - public BatchMessage() { } @@ -155,7 +152,7 @@ public void readPayload(DataInput in) throws IOException, ClassNotFoundException msgs=new Message[index]; // a bit of additional space should we add byte arrays for(int i=0; i < index; i++) { short type=in.readShort(); - msgs[i]=mf.create(type).setDest(dest()).setSrc(orig_src); + msgs[i]=MessageFactory.create(type).setDest(dest()).setSrc(orig_src); msgs[i].readFrom(in); } } diff --git a/src/org/jgroups/CompositeMessage.java b/src/org/jgroups/CompositeMessage.java index 0b9b7e59c30..3a960f9fd19 100644 --- a/src/org/jgroups/CompositeMessage.java +++ b/src/org/jgroups/CompositeMessage.java @@ -7,10 +7,7 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; -import java.util.Arrays; -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.Objects; +import java.util.*; import java.util.function.Supplier; /** @@ -31,8 +28,6 @@ public class CompositeMessage extends BaseMessage implements Iterable { protected boolean collapse; // send as a BytesMessage when true - protected static final MessageFactory mf=new DefaultMessageFactory(); - public CompositeMessage() { } @@ -46,6 +41,11 @@ public CompositeMessage(Address dest, Message ... messages) { add(messages); } + public CompositeMessage(Address dest, Collection messages) { + super(dest); + add(messages); + } + public Supplier create() {return CompositeMessage::new;} public short getType() {return collapse? Message.BYTES_MSG : Message.COMPOSITE_MSG;} @@ -86,6 +86,12 @@ public CompositeMessage add(Message ... messages) { return this; } + public CompositeMessage add(Collection messages) { + ensureCapacity(index + messages.size()); + for(Message msg: messages) + msgs[index++]=Objects.requireNonNull(ensureSameDest(msg)); + return this; + } public T get(int index) { return (T)msgs[index]; @@ -136,6 +142,7 @@ public void writePayload(DataOutput out) throws IOException { for(int i=0; i < index; i++) { Message msg=msgs[i]; out.writeShort(msg.getType()); + // msg.writeToNoAddrs(src(), out); msg.writeTo(out); } } @@ -147,8 +154,11 @@ public void readPayload(DataInput in) throws IOException, ClassNotFoundException msgs=new Message[index]; // a bit of additional space should we add byte arrays for(int i=0; i < index; i++) { short type=in.readShort(); - msgs[i]=mf.create(type); - msgs[i].readFrom(in); + Message msg=MessageFactory.create(type).setDest(getDest()); + if(msg.getSrc() == null) + msg.setSrc(src()); + msg.readFrom(in); + msgs[i]=msg; } } } diff --git a/src/org/jgroups/DefaultMessageFactory.java b/src/org/jgroups/DefaultMessageFactory.java deleted file mode 100644 index 6ca0f8747a0..00000000000 --- a/src/org/jgroups/DefaultMessageFactory.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.jgroups; - -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.function.Supplier; - -/** - * Default implementation of {@link MessageFactory}. Uses an array for message IDs less then 32, and a hashmap for - * types above 32 - * @author Bela Ban - * @since 5.0 - */ -public class DefaultMessageFactory implements MessageFactory { - protected static final byte MIN_TYPE=32; - protected final Supplier[] creators=new Supplier[MIN_TYPE]; - protected Map> map=new HashMap<>(); - - public DefaultMessageFactory() { - creators[Message.BYTES_MSG]=BytesMessage::new; - creators[Message.NIO_MSG]=NioMessage::new; - creators[Message.EMPTY_MSG]=EmptyMessage::new; - creators[Message.OBJ_MSG]=ObjectMessage::new; - creators[Message.LONG_MSG]=LongMessage::new; - creators[Message.COMPOSITE_MSG]=CompositeMessage::new; - creators[Message.FRAG_MSG]=FragmentedMessage::new; - creators[Message.EARLYBATCH_MSG]=BatchMessage::new; - } - - public T create(short type) { - Supplier creator=type < MIN_TYPE? creators[type] : map.get(type); - if(creator == null) - throw new IllegalArgumentException("no creator found for type " + type); - return (T)creator.get(); - } - - public T register(short type, Supplier generator) { - Objects.requireNonNull(generator, "the creator must be non-null"); - if(type < MIN_TYPE) - throw new IllegalArgumentException(String.format("type (%d) must be >= 32", type)); - if(map.containsKey(type)) - throw new IllegalArgumentException(String.format("type %d is already taken", type)); - map.put(type, generator); - return (T)this; - } -} diff --git a/src/org/jgroups/MessageFactory.java b/src/org/jgroups/MessageFactory.java index 1cb3fa65c4b..ebea18d50fe 100644 --- a/src/org/jgroups/MessageFactory.java +++ b/src/org/jgroups/MessageFactory.java @@ -1,21 +1,43 @@ package org.jgroups; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; import java.util.function.Supplier; /** + * Factory to create messages. Uses an array for message IDs less then 32, and a hashmap for + * types above 32 * @author Bela Ban * @since 5.0 */ -public interface MessageFactory { - - +public class MessageFactory { + protected static final byte MIN_TYPE=32; + protected static final Supplier[] creators=new Supplier[MIN_TYPE]; + protected static Map> map=new HashMap<>(); + static { + creators[Message.BYTES_MSG]=BytesMessage::new; + creators[Message.NIO_MSG]=NioMessage::new; + creators[Message.EMPTY_MSG]=EmptyMessage::new; + creators[Message.OBJ_MSG]=ObjectMessage::new; + creators[Message.LONG_MSG]=LongMessage::new; + creators[Message.COMPOSITE_MSG]=CompositeMessage::new; + creators[Message.FRAG_MSG]=FragmentedMessage::new; + creators[Message.EARLYBATCH_MSG]=BatchMessage::new; + } + /** * Creates a message based on the given ID - * @param id The ID + * @param type The ID * @param The type of the message * @return A message */ - T create(short id); + public static T create(short type) { + Supplier creator=type < MIN_TYPE? creators[type] : map.get(type); + if(creator == null) + throw new IllegalArgumentException("no creator found for type " + type); + return (T)creator.get(); + } /** * Registers a new creator of messages @@ -23,5 +45,12 @@ public interface MessageFactory { * needs to be available (ie., not taken by JGroups or other applications). * @param generator The creator of the payload associated with the given type */ - M register(short type, Supplier generator); + public static void register(short type, Supplier generator) { + Objects.requireNonNull(generator, "the creator must be non-null"); + if(type < MIN_TYPE) + throw new IllegalArgumentException(String.format("type (%d) must be >= 32", type)); + if(map.containsKey(type)) + throw new IllegalArgumentException(String.format("type %d is already taken", type)); + map.put(type, generator); + } } 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/COMPRESS.java b/src/org/jgroups/protocols/COMPRESS.java index b438bd78ca4..26a089d32a7 100644 --- a/src/org/jgroups/protocols/COMPRESS.java +++ b/src/org/jgroups/protocols/COMPRESS.java @@ -47,7 +47,6 @@ public class COMPRESS extends Protocol { protected BlockingQueue deflater_pool; protected BlockingQueue inflater_pool; - protected MessageFactory msg_factory; protected final LongAdder num_compressions=new LongAdder(), num_decompressions=new LongAdder(); @@ -77,7 +76,6 @@ public void init() throws Exception { inflater_pool=new ArrayBlockingQueue<>(pool_size); for(int i=0; i < pool_size; i++) inflater_pool.add(new Inflater()); - msg_factory=getTransport().getMessageFactory(); } public void destroy() { @@ -192,7 +190,7 @@ protected Message uncompress(Message msg, int original_size, boolean needs_deser inflater.inflate(uncompressed_payload); // we need to copy: https://issues.redhat.com/browse/JGRP-867 if(needs_deserialization) { - return messageFromByteArray(uncompressed_payload, msg_factory); + return messageFromByteArray(uncompressed_payload); } else return msg.copy(false, true).setArray(uncompressed_payload, 0, uncompressed_payload.length); @@ -221,9 +219,9 @@ protected static ByteArray messageToByteArray(Message msg) { } } - protected static Message messageFromByteArray(byte[] uncompressed_payload, MessageFactory msg_factory) { + protected static Message messageFromByteArray(byte[] uncompressed_payload) { try { - return Util.messageFromBuffer(uncompressed_payload, 0, uncompressed_payload.length, msg_factory); + return Util.messageFromBuffer(uncompressed_payload, 0, uncompressed_payload.length); } catch(Exception ex) { throw new RuntimeException("failed unmarshalling message", ex); diff --git a/src/org/jgroups/protocols/Encrypt.java b/src/org/jgroups/protocols/Encrypt.java index 75ba9109408..2d5d47c9bf9 100644 --- a/src/org/jgroups/protocols/Encrypt.java +++ b/src/org/jgroups/protocols/Encrypt.java @@ -70,8 +70,6 @@ public abstract class Encrypt extends Protocol { // SecureRandom instance for generating IV's protected SecureRandom secure_random = new SecureRandom(); - protected MessageFactory msg_factory; - /** * Sets the key store entry used to configure this protocol. @@ -96,7 +94,6 @@ public abstract class Encrypt extends Protocol { public SecureRandom secureRandom() {return this.secure_random;} /** Allows callers to replace secure_random with impl of their choice, e.g. for performance reasons. */ public > T secureRandom(SecureRandom sr) {this.secure_random = sr; return (T)this;} - public > T msgFactory(MessageFactory f) {this.msg_factory=f; return (T)this;} @ManagedAttribute public String version() {return Util.byteArrayToHexString(sym_version);} @@ -116,8 +113,6 @@ public void init() throws Exception { key_map=new BoundedHashMap<>(key_map_max_size); initSymCiphers(sym_algorithm, secret_key); TP transport=getTransport(); - if(transport != null) - msg_factory=transport.getMessageFactory(); } @@ -322,7 +317,7 @@ protected Message _decrypt(final Cipher cipher, Key key, Message msg, EncryptHea decrypted_msg=cipher.doFinal(msg.getArray(), msg.getOffset(), msg.getLength()); } if(hdr.needsDeserialization()) - return Util.messageFromBuffer(decrypted_msg, 0, decrypted_msg.length, msg_factory); + return Util.messageFromBuffer(decrypted_msg, 0, decrypted_msg.length); else return msg.setArray(decrypted_msg, 0, decrypted_msg.length); } diff --git a/src/org/jgroups/protocols/FRAG.java b/src/org/jgroups/protocols/FRAG.java index 8b8299434f9..d466700ab5e 100644 --- a/src/org/jgroups/protocols/FRAG.java +++ b/src/org/jgroups/protocols/FRAG.java @@ -42,7 +42,6 @@ public class FRAG extends Fragmentation { protected final FragmentationList fragment_list=new FragmentationList(); protected final AtomicInteger curr_id=new AtomicInteger(1); protected final List
members=new ArrayList<>(11); - protected MessageFactory msg_factory; protected final Predicate HAS_FRAG_HEADER=msg -> msg.getHeader(id) != null; @@ -58,7 +57,6 @@ public class FRAG extends Fragmentation { public void init() throws Exception { super.init(); - msg_factory=getTransport().getMessageFactory(); Map info=new HashMap<>(1); info.put("frag_size", frag_size); down_prot.down(new Event(Event.CONFIG, info)); @@ -229,7 +227,7 @@ private Message unfragment(Message msg, FragHeader hdr) { return null; try { - Message assembled_msg=Util.messageFromBuffer(buf, 0, buf.length, msg_factory); + Message assembled_msg=Util.messageFromBuffer(buf, 0, buf.length); assembled_msg.setSrc(sender); // needed ? YES, because fragments have a null src !! if(log.isTraceEnabled()) log.trace("assembled_msg is " + assembled_msg); num_received_msgs++; diff --git a/src/org/jgroups/protocols/FRAG2.java b/src/org/jgroups/protocols/FRAG2.java index c6d10532c64..fce77931cfc 100644 --- a/src/org/jgroups/protocols/FRAG2.java +++ b/src/org/jgroups/protocols/FRAG2.java @@ -46,7 +46,6 @@ public class FRAG2 extends Fragmentation { protected final AtomicLong curr_id=new AtomicLong(1); protected final List
members=new ArrayList<>(11); - protected MessageFactory msg_factory; protected final AverageMinMax avg_size_down=new AverageMinMax(); protected final AverageMinMax avg_size_up=new AverageMinMax(); @@ -75,7 +74,6 @@ public void init() throws Exception { throw new IllegalArgumentException("frag_size (" + frag_size + ") has to be < TP.max_bundle_size (" + max_bundle_size + ")"); } - msg_factory=transport.getMessageFactory(); Map info=new HashMap<>(1); info.put("frag_size", frag_size); down_prot.down(new Event(Event.CONFIG, info)); @@ -261,7 +259,7 @@ protected Message unfragment(Message msg, FragHeader hdr) { FragEntry entry=frag_table.get(hdr.id); if(entry == null) { - entry=new FragEntry(hdr.num_frags, hdr.needs_deserialization, msg_factory); + entry=new FragEntry(hdr.num_frags, hdr.needs_deserialization); FragEntry tmp=frag_table.putIfAbsent(hdr.id, entry); if(tmp != null) entry=tmp; @@ -314,7 +312,7 @@ protected Message assembleMessage(Message[] fragments, boolean needs_deserializa index+=length; } if(needs_deserialization) - retval=Util.messageFromBuffer(combined_buffer, 0, combined_buffer.length, msg_factory); + retval=Util.messageFromBuffer(combined_buffer, 0, combined_buffer.length); else retval.setArray(combined_buffer, 0, combined_buffer.length); return retval; @@ -332,7 +330,6 @@ protected static class FragEntry { protected final Message[] fragments; protected int number_of_frags_recvd; protected final boolean needs_deserialization; - protected final MessageFactory msg_factory; protected final Lock lock=new ReentrantLock(); @@ -340,10 +337,9 @@ protected static class FragEntry { * Creates a new entry * @param tot_frags the number of fragments to expect for this message */ - protected FragEntry(int tot_frags, boolean needs_deserialization, MessageFactory mf) { + protected FragEntry(int tot_frags, boolean needs_deserialization) { fragments=new Message[tot_frags]; this.needs_deserialization=needs_deserialization; - this.msg_factory=mf; } diff --git a/src/org/jgroups/protocols/FRAG3.java b/src/org/jgroups/protocols/FRAG3.java index 6def97ead56..f970dfb5443 100644 --- a/src/org/jgroups/protocols/FRAG3.java +++ b/src/org/jgroups/protocols/FRAG3.java @@ -45,8 +45,6 @@ public class FRAG3 extends Fragmentation { protected final List
members=new ArrayList<>(11); - protected MessageFactory msg_factory; - protected final AverageMinMax avg_size_down=new AverageMinMax(); protected final AverageMinMax avg_size_up=new AverageMinMax(); @@ -70,7 +68,6 @@ public void init() throws Exception { if(frag_size >= max_bundle_size) throw new IllegalArgumentException("frag_size (" + frag_size + ") has to be < TP.max_bundle_size (" + max_bundle_size + ")"); - msg_factory=transport.getMessageFactory(); Map info=new HashMap<>(1); info.put("frag_size", frag_size); down_prot.down(new Event(Event.CONFIG, info)); @@ -351,7 +348,7 @@ protected boolean isComplete() { * @return the complete message in one buffer */ protected Message assembleMessage() throws Exception { - return needs_deserialization? Util.messageFromBuffer(buffer, 0, buffer.length, msg_factory) + return needs_deserialization? Util.messageFromBuffer(buffer, 0, buffer.length) : msg.setArray(buffer, 0, buffer.length); } diff --git a/src/org/jgroups/protocols/FRAG4.java b/src/org/jgroups/protocols/FRAG4.java index 41952ebd04d..2f133728129 100644 --- a/src/org/jgroups/protocols/FRAG4.java +++ b/src/org/jgroups/protocols/FRAG4.java @@ -4,6 +4,7 @@ import org.jgroups.BytesMessage; import org.jgroups.FragmentedMessage; import org.jgroups.Message; +import org.jgroups.MessageFactory; import org.jgroups.util.ByteArrayDataInputStream; import org.jgroups.util.Range; import org.jgroups.util.Util; @@ -81,7 +82,7 @@ protected Message assembleMessage(Message[] fragments, boolean needs_deserializa m.getOffset(), m.getLength()))); DataInput in=new DataInputStream(seq); - Message retval=msg_factory.create(hdr.getOriginalType()); + Message retval=MessageFactory.create(hdr.getOriginalType()); retval.readFrom(in); return retval; } 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/SEQUENCER.java b/src/org/jgroups/protocols/SEQUENCER.java index bd796e6a74c..ab9bfca113b 100644 --- a/src/org/jgroups/protocols/SEQUENCER.java +++ b/src/org/jgroups/protocols/SEQUENCER.java @@ -66,8 +66,6 @@ public class SEQUENCER extends Protocol { /** Used for each resent message to wait until the message has been received */ protected final Promise ack_promise=new Promise<>(); - protected MessageFactory msg_factory; - @Property(description="Size of the set to store received seqnos (for duplicate checking)") @@ -106,7 +104,6 @@ public void resetStats() { public void init() throws Exception { super.init(); - msg_factory=getTransport().getMessageFactory(); } public void start() throws Exception { @@ -457,7 +454,7 @@ protected void broadcast(final Message msg, boolean copy, Address original_sende protected void unwrapAndDeliver(final Message msg, boolean flush_ack) { try { // Message msg_to_deliver=Util.streamableFromBuffer(BytesMessage::new, msg.getArray(), msg.getOffset(), msg.getLength()); - Message msg_to_deliver=Util.messageFromBuffer(msg.getArray(), msg.getOffset(), msg.getLength(), msg_factory); + Message msg_to_deliver=Util.messageFromBuffer(msg.getArray(), msg.getOffset(), msg.getLength()); SequencerHeader hdr=msg_to_deliver.getHeader(this.id); if(flush_ack) hdr.flush_ack=true; diff --git a/src/org/jgroups/protocols/SERIALIZE.java b/src/org/jgroups/protocols/SERIALIZE.java index ba58c4f45d6..a65805b5dd9 100644 --- a/src/org/jgroups/protocols/SERIALIZE.java +++ b/src/org/jgroups/protocols/SERIALIZE.java @@ -27,12 +27,6 @@ @MBean(description="Serializes entire message into the payload of another message") public class SERIALIZE extends Protocol { protected static final short GMS_ID=ClassConfigurator.getProtocolId(GMS.class); - protected MessageFactory mf; - - public void init() throws Exception { - super.init(); - mf=getTransport().getMessageFactory(); - } public Object down(Message msg) { if(msg.getSrc() == null) @@ -84,7 +78,7 @@ public void up(MessageBatch batch) { protected Message deserialize(Address sender, byte[] buf, int offset, int length) throws Exception { try { - Message msg=Util.messageFromBuffer(buf, offset, length, mf); + Message msg=Util.messageFromBuffer(buf, offset, length); if(msg.getDest() == null) msg.setDest(msg.getDest()); if(msg.getSrc() == null) 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/TP.java b/src/org/jgroups/protocols/TP.java index 3b1bdaaa934..e315f029cdc 100644 --- a/src/org/jgroups/protocols/TP.java +++ b/src/org/jgroups/protocols/TP.java @@ -163,11 +163,6 @@ public abstract class TP extends Protocol implements DiagnosticsHandler.ProbeHan "disables this.",type=AttributeType.TIME) protected long suppress_time_different_cluster_warnings=60000; - @Property(description="The fully qualified name of a MessageFactory implementation",exposeAsManagedAttribute=false) - protected String msg_factory_class; - - protected MessageFactory msg_factory=new DefaultMessageFactory(); - @Property(description="The type of bundler used (\"ring-buffer\", \"transfer-queue\" (default), \"sender-sends\" or " + "\"no-bundler\") or the fully qualified classname of a Bundler implementation") protected String bundler_type="transfer-queue"; @@ -177,9 +172,6 @@ public String getBundlerClass() { return bundler != null? bundler.getClass().getName() : "null"; } - public MessageFactory getMessageFactory() {return msg_factory;} - public T setMessageFactory(MessageFactory m) {msg_factory=m; return (T)this;} - public InetAddress getBindAddr() {return bind_addr;} public T setBindAddr(InetAddress b) {this.bind_addr=b; return (T)this;} @@ -222,20 +214,12 @@ public String getBundlerClass() { public long getSuppressTimeDifferentClusterWarnings() {return suppress_time_different_cluster_warnings;} public T setSuppressTimeDifferentClusterWarnings(long s) {this.suppress_time_different_cluster_warnings=s; return (T)this;} - public String getMsgFactoryClass() {return msg_factory_class;} - public T setMsgFactoryClass(String m) {this.msg_factory_class=m; return (T)this;} - public String getBundlerType() {return bundler_type;} public T setBundlerType(String b) {this.bundler_type=b; return (T)this;} @Property public T useVirtualThreads(boolean f) {use_vthreads=f; return (T)this;} - @ManagedAttribute - public String getMessageFactoryClass() { - return msg_factory != null? msg_factory.getClass().getName() : "n/a"; - } - @ManagedAttribute(description="Is the logical_addr_cache reaper task running") public boolean isLogicalAddressCacheReaperRunning() { return logical_addr_cache_reaper != null && !logical_addr_cache_reaper.isDone(); @@ -782,11 +766,6 @@ public String toString() { else msg_processing_policy.init(this); - if(msg_factory_class != null) { - Class clazz=(Class)Util.loadClass(msg_factory_class, getClass()); - msg_factory=clazz.getDeclaredConstructor().newInstance(); - } - if(bundler == null) { bundler=createBundler(bundler_type, getClass()); bundler.init(this); @@ -1245,7 +1224,7 @@ public void receive(Address sender, byte[] data, int offset, int length) { boolean is_message_list=(flags & LIST) == LIST, multicast=(flags & MULTICAST) == MULTICAST; ByteArrayDataInputStream in=new ByteArrayDataInputStream(data, offset, length); if(is_message_list) // used if message bundling is enabled - handleMessageBatch(in, multicast, msg_factory); + handleMessageBatch(in, multicast); else handleSingleMessage(in, multicast); } @@ -1264,15 +1243,15 @@ public void receive(Address sender, DataInput in, int ignoredLength) throws Exce boolean is_message_list=(flags & LIST) == LIST, multicast=(flags & MULTICAST) == MULTICAST; if(is_message_list) // used if message bundling is enabled - handleMessageBatch(in, multicast, msg_factory); + handleMessageBatch(in, multicast); else handleSingleMessage(in, multicast); } - protected void handleMessageBatch(DataInput in, boolean multicast, MessageFactory factory) { + protected void handleMessageBatch(DataInput in, boolean multicast) { try { - final MessageBatch[] batches=Util.readMessageBatch(in, multicast, factory); + final MessageBatch[] batches=Util.readMessageBatch(in, multicast); final MessageBatch regular=batches[0], oob=batches[1]; // we need to update the stats *before* processing the batches: protocols can remove msgs from the batch @@ -1290,7 +1269,7 @@ protected void handleMessageBatch(DataInput in, boolean multicast, MessageFactor protected void handleSingleMessage(DataInput in, boolean multicast) { try { short type=in.readShort(); - Message msg=msg_factory.create(type); // don't create headers, readFrom() will do this + Message msg=MessageFactory.create(type); // don't create headers, readFrom() will do this msg.readFrom(in); if(!multicast && unicastDestMismatch(msg.getDest())) 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/TLS.java b/src/org/jgroups/util/TLS.java index 7dd805a48b7..8a291af58ce 100644 --- a/src/org/jgroups/util/TLS.java +++ b/src/org/jgroups/util/TLS.java @@ -8,8 +8,6 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLServerSocket; -import java.io.File; -import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; @@ -120,11 +118,6 @@ public void init() throws Exception { truststore_type=keystore_type; truststore_password=keystore_password; } - if(keystore_path != null) { - File tmp=new File(keystore_path); - if(!tmp.exists()) - throw new FileNotFoundException(keystore_path); - } } public SSLContext createContext() { 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..1704f709e09 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; } @@ -1279,10 +1288,10 @@ public static ByteArray messageToBuffer(Message msg) throws Exception { } - public static Message messageFromBuffer(byte[] buf, int offset, int length, MessageFactory mf) throws Exception { + public static Message messageFromBuffer(byte[] buf, int offset, int length) throws Exception { ByteArrayDataInputStream in=new ByteArrayDataInputStream(buf, offset, length); short type=in.readShort(); - Message msg=mf.create(type); + Message msg=MessageFactory.create(type); msg.readFrom(in); return msg; } @@ -1298,12 +1307,12 @@ public static ByteArray messageToByteBuffer(Message msg) throws Exception { } - public static Message messageFromByteBuffer(byte[] buffer, int offset, int length, MessageFactory mf) throws Exception { + public static Message messageFromByteBuffer(byte[] buffer, int offset, int length) throws Exception { DataInput in=new ByteArrayDataInputStream(buffer,offset,length); if(!in.readBoolean()) return null; short type=in.readShort(); - Message msg=mf.create(type); + Message msg=MessageFactory.create(type); msg.readFrom(in); return msg; } @@ -1435,9 +1444,9 @@ public static void writeMessage(Message msg, DataOutput dos, boolean multicast) msg.writeTo(dos); } - public static Message readMessage(DataInput in, MessageFactory mf) throws IOException, ClassNotFoundException { + public static Message readMessage(DataInput in) throws IOException, ClassNotFoundException { short type=in.readShort(); - Message msg=mf.create(type); + Message msg=MessageFactory.create(type); msg.readFrom(in); return msg; } @@ -1512,7 +1521,7 @@ public static void writeMessageListHeader(Address dest, Address src, byte[] clus } - public static List readMessageList(DataInput in, short transport_id, MessageFactory mf) + public static List readMessageList(DataInput in, short transport_id) throws IOException, ClassNotFoundException { List list=new LinkedList<>(); Address dest=Util.readAddress(in); @@ -1527,7 +1536,7 @@ public static List readMessageList(DataInput in, short transport_id, Me for(int i=0; i < len; i++) { short type=in.readShort(); // skip the - Message msg=mf.create(type); + Message msg=MessageFactory.create(type); msg.readFrom(in); msg.setDest(dest); if(msg.getSrc() == null) @@ -1549,7 +1558,7 @@ public static List readMessageList(DataInput in, short transport_id, Me * * @return an array of 2 MessageBatches in the order above, the first batch is at index 0 */ - public static MessageBatch[] readMessageBatch(DataInput in, boolean multicast, MessageFactory factory) + public static MessageBatch[] readMessageBatch(DataInput in, boolean multicast) throws IOException, ClassNotFoundException { MessageBatch[] batches=new MessageBatch[2]; // [0]: reg, [1]: OOB Address dest=Util.readAddress(in); @@ -1562,7 +1571,7 @@ public static MessageBatch[] readMessageBatch(DataInput in, boolean multicast, M int len=in.readInt(); for(int i=0; i < len; i++) { short type=in.readShort(); - Message msg=factory.create(type).setDest(dest).setSrc(src); + Message msg=MessageFactory.create(type).setDest(dest).setSrc(src); msg.readFrom(in); boolean oob=msg.isFlagSet(Message.Flag.OOB); int index=0; @@ -1590,7 +1599,6 @@ public static void parse(InputStream input, BiConsumer msg_consum if(msg_consumer == null && batch_consumer == null) return; byte[] tmp=new byte[Global.INT_SIZE]; - MessageFactory mf=new DefaultMessageFactory(); try(DataInputStream dis=new DataInputStream(input)) { for(;;) { // for TCP, we send the length first; this needs to be skipped as it is not part of the JGroups payload @@ -1623,7 +1631,7 @@ public static void parse(InputStream input, BiConsumer msg_consum boolean is_message_list=(flags & LIST) == LIST; boolean multicast=(flags & MULTICAST) == MULTICAST; if(is_message_list) { // used if message bundling is enabled - final MessageBatch[] batches=Util.readMessageBatch(dis,multicast, mf); + final MessageBatch[] batches=Util.readMessageBatch(dis,multicast); for(MessageBatch batch: batches) { if(batch == null) continue; @@ -1636,7 +1644,7 @@ public static void parse(InputStream input, BiConsumer msg_consum } } else { - Message msg=Util.readMessage(dis, mf); + Message msg=Util.readMessage(dis); if(msg_consumer != null) msg_consumer.accept(version, msg); } @@ -3009,7 +3017,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 +3529,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 +5008,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/ENCRYPTKeystoreTest.java b/tests/junit-functional/org/jgroups/protocols/ENCRYPTKeystoreTest.java index 51be011dd71..67391dc6607 100644 --- a/tests/junit-functional/org/jgroups/protocols/ENCRYPTKeystoreTest.java +++ b/tests/junit-functional/org/jgroups/protocols/ENCRYPTKeystoreTest.java @@ -5,7 +5,6 @@ import org.jgroups.BytesMessage; -import org.jgroups.DefaultMessageFactory; import org.jgroups.Global; import org.jgroups.Message; import org.jgroups.conf.ClassConfigurator; @@ -33,7 +32,6 @@ public void testInitWrongKeystoreProperties() { SYM_ENCRYPT encrypt=new SYM_ENCRYPT().keystoreName("unkownKeystore.keystore"); try { encrypt.init(); - encrypt.msgFactory(new DefaultMessageFactory()); } catch(Exception e) { System.out.println("didn't find incorrect keystore (as expected): " + e.getMessage()); @@ -43,7 +41,6 @@ public void testInitWrongKeystoreProperties() { public void testInitKeystoreProperties() throws Exception { SYM_ENCRYPT encrypt=new SYM_ENCRYPT().keystoreName("defaultStore.keystore"); encrypt.init(); - encrypt.msgFactory(new DefaultMessageFactory()); } public void testMessageDownEncode() throws Exception { @@ -157,7 +154,6 @@ public void testEncryptEntireMessage() throws Exception { protected SYM_ENCRYPT create(String keystore) throws Exception { SYM_ENCRYPT encrypt=new SYM_ENCRYPT().keystoreName(keystore).symAlgorithm(symAlgorithm()).symIvLength(symIvLength()); encrypt.init(); - encrypt.msgFactory(new DefaultMessageFactory()); return encrypt; } diff --git a/tests/junit-functional/org/jgroups/protocols/EncryptTest.java b/tests/junit-functional/org/jgroups/protocols/EncryptTest.java index 6cf7a3edace..275bcf19595 100644 --- a/tests/junit-functional/org/jgroups/protocols/EncryptTest.java +++ b/tests/junit-functional/org/jgroups/protocols/EncryptTest.java @@ -129,7 +129,6 @@ public void testMessageSendingByRogueUsingEncryption() throws Exception { secretKey.setAccessible(true); Util.setField(secretKey, encrypt, secret_key); encrypt.init(); - encrypt.msgFactory(new DefaultMessageFactory()); short encrypt_id=ClassConfigurator.getProtocolId(SYM_ENCRYPT.class); byte[] iv = encrypt.makeIv(); 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/BatchMessageTest.java b/tests/junit-functional/org/jgroups/tests/BatchMessageTest.java index 49bc69649fc..b2d8cfd8692 100644 --- a/tests/junit-functional/org/jgroups/tests/BatchMessageTest.java +++ b/tests/junit-functional/org/jgroups/tests/BatchMessageTest.java @@ -22,8 +22,6 @@ public class BatchMessageTest extends MessageTestBase { protected static final Message M2=create(DEST, 1000, true, true); protected static final Message M3=new EmptyMessage(DEST); - protected static final MessageFactory MF=new DefaultMessageFactory(); - public void testCreation() { BatchMessage msg=new BatchMessage(DEST, SRC, new Message[]{M1,M2,M3}, 3); assert msg.getNumberOfMessages() == 3; 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/CompositeMessageTest.java b/tests/junit-functional/org/jgroups/tests/CompositeMessageTest.java index aba3d106ff7..3b034be00cc 100644 --- a/tests/junit-functional/org/jgroups/tests/CompositeMessageTest.java +++ b/tests/junit-functional/org/jgroups/tests/CompositeMessageTest.java @@ -21,8 +21,6 @@ public class CompositeMessageTest extends MessageTestBase { protected static final Message M2=create(DEST, 1000, true, true); protected static final Message M3=new EmptyMessage(DEST); - protected static final MessageFactory MF=new DefaultMessageFactory(); - public void testCreation() { CompositeMessage msg=new CompositeMessage(DEST, M1, M2); assert msg.getNumberOfMessages() == 2; @@ -62,7 +60,7 @@ public void testCollapse() throws Exception { CompositeMessage msg=new CompositeMessage(DEST, M1, M2, M3).collapse(true); int length=msg.getLength(); ByteArray buf=Util.messageToBuffer(msg); - Message msg2=Util.messageFromBuffer(buf.getArray(), buf.getOffset(), buf.getLength(), MF); + Message msg2=Util.messageFromBuffer(buf.getArray(), buf.getOffset(), buf.getLength()); assert msg2 instanceof BytesMessage; assert msg2.getLength() == length; } @@ -75,7 +73,7 @@ public void testCollapse2() throws Exception { .collapse(true); int length=msg.getLength(); ByteArray buf=Util.messageToBuffer(msg); - Message msg2=Util.messageFromBuffer(buf.getArray(), buf.getOffset(), buf.getLength(), MF); + Message msg2=Util.messageFromBuffer(buf.getArray(), buf.getOffset(), buf.getLength()); assert msg2 instanceof BytesMessage; assert msg2.getLength() == length; diff --git a/tests/junit-functional/org/jgroups/tests/FragmentedMessageTest.java b/tests/junit-functional/org/jgroups/tests/FragmentedMessageTest.java index 9df6db3bfe3..dfde70a9875 100644 --- a/tests/junit-functional/org/jgroups/tests/FragmentedMessageTest.java +++ b/tests/junit-functional/org/jgroups/tests/FragmentedMessageTest.java @@ -17,7 +17,6 @@ @Test(groups=Global.FUNCTIONAL) public class FragmentedMessageTest { protected static final int FRAG_SIZE=500; - protected final MessageFactory msg_factory=new DefaultMessageFactory(); protected final byte[] array=Util.generateArray(1200); protected final Address src=Util.createRandomAddress("X"), dest=Util.createRandomAddress("D"); @@ -109,7 +108,7 @@ protected void _testFragmentation(Message original_msg, Consumer verifi new SequenceInputStream(Util.enumerate(msgs, 0, msgs.length, m -> new ByteArrayDataInputStream(m.getArray(),m.getOffset(),m.getLength()))); DataInput input=new DataInputStream(seq); - Message new_msg=msg_factory.create(original_msg.getType()); + Message new_msg=MessageFactory.create(original_msg.getType()); new_msg.readFrom(input); assert original_msg.getLength() == new_msg.getLength(); verifier.accept(new_msg); diff --git a/tests/junit-functional/org/jgroups/tests/MessageBatchTest.java b/tests/junit-functional/org/jgroups/tests/MessageBatchTest.java index e8b538b1493..4e990eae3a1 100644 --- a/tests/junit-functional/org/jgroups/tests/MessageBatchTest.java +++ b/tests/junit-functional/org/jgroups/tests/MessageBatchTest.java @@ -378,7 +378,6 @@ public void testTotalSize() { public void testSize() throws Exception { - MessageFactory mf=new DefaultMessageFactory(); List msgs=createMessages(); ByteArrayOutputStream output=new ByteArrayOutputStream(); DataOutputStream out=new DataOutputStream(output); @@ -391,7 +390,7 @@ public void testSize() throws Exception { DataInputStream in=new DataInputStream(new ByteArrayInputStream(buf)); in.readShort(); // version in.readByte(); // flags - List list=Util.readMessageList(in, UDP_ID, mf); + List list=Util.readMessageList(in, UDP_ID); assert msgs.size() == list.size(); } diff --git a/tests/junit-functional/org/jgroups/tests/MessageFactoryTest.java b/tests/junit-functional/org/jgroups/tests/MessageFactoryTest.java index 48778989596..2154d9b5328 100644 --- a/tests/junit-functional/org/jgroups/tests/MessageFactoryTest.java +++ b/tests/junit-functional/org/jgroups/tests/MessageFactoryTest.java @@ -15,21 +15,20 @@ */ @Test(groups=Global.FUNCTIONAL) public class MessageFactoryTest { - protected final MessageFactory mf=new DefaultMessageFactory(); public void testRegistration() { for(int i=0; i < 32; i++) { try { - mf.register((short)i, MyMessageFactory::new); + MessageFactory.register((short)i, MyMessageFactory::new); } catch(IllegalArgumentException ex) { System.out.printf("received exception (as expected): %s\n", ex); } } - mf.register((short)32, MyMessageFactory::new); + MessageFactory.register((short)32, MyMessageFactory::new); try { - mf.register((short)32, MyMessageFactory::new); + MessageFactory.register((short)32, MyMessageFactory::new); } catch(IllegalArgumentException ex) { System.out.printf("received exception (as expected): %s\n", ex); diff --git a/tests/junit-functional/org/jgroups/tests/NioMessageTest.java b/tests/junit-functional/org/jgroups/tests/NioMessageTest.java index b790c786c8b..bd478e1bd1f 100644 --- a/tests/junit-functional/org/jgroups/tests/NioMessageTest.java +++ b/tests/junit-functional/org/jgroups/tests/NioMessageTest.java @@ -1,6 +1,5 @@ package org.jgroups.tests; -import org.jgroups.DefaultMessageFactory; import org.jgroups.Global; import org.jgroups.Message; import org.jgroups.NioMessage; @@ -236,7 +235,7 @@ public void testReadonly() throws Exception { ByteBuffer payload=ByteBuffer.allocate(4).putInt(322649).flip().asReadOnlyBuffer(); Message msg=new NioMessage(null, payload); ByteArray buf=Util.messageToBuffer(msg); - NioMessage msg2=(NioMessage)Util.messageFromBuffer(buf.getArray(), buf.getOffset(), buf.getLength(), new DefaultMessageFactory()); + NioMessage msg2=(NioMessage)Util.messageFromBuffer(buf.getArray(), buf.getOffset(), buf.getLength()); ByteBuffer buf2=msg2.getBuf(); assert payload.equals(buf2); diff --git a/tests/junit-functional/org/jgroups/tests/OrderingTest.java b/tests/junit-functional/org/jgroups/tests/OrderingTest.java index e5fce283d9f..f5c5464be89 100644 --- a/tests/junit-functional/org/jgroups/tests/OrderingTest.java +++ b/tests/junit-functional/org/jgroups/tests/OrderingTest.java @@ -60,10 +60,10 @@ protected static JChannel createChannel(int index) throws Exception { new SHUFFLE().setUp(false).setDown(false).setMaxSize(200), // reorders messages new NAKACK2().useMcastXmit(false).setDiscardDeliveredMsgs(true).setXmitInterval(200), new UNICAST3(), - new STABLE().setMaxBytes(50000).setDesiredAverageGossip(1000), + new STABLE().setMaxBytes(50_000).setDesiredAverageGossip(1000), new GMS().setJoinTimeout(500).printLocalAddress(false), - new UFC().setMaxCredits(2000000), - new MFC().setMaxCredits(2000000), + new UFC().setMaxCredits(2_000_000), + new MFC().setMaxCredits(2_000_000), new FRAG2()) .name(String.valueOf((char)('A' +index))); } @@ -114,20 +114,9 @@ protected void checkOrder(int expected_msgs) { } System.out.println("\n-- waiting for message reception by all receivers:"); - for(int i=0; i < 20; i++) { - boolean done=true; - for(JChannel ch: channels) { - MyReceiver receiver=(MyReceiver)ch.getReceiver(); - int received=receiver.getReceived(); - if(received != expected_msgs) { - done=false; - break; - } - } - if(done) - break; - Util.sleep(500); - } + Util.waitUntilTrue(10000, 500, + () -> Stream.of(channels).map(JChannel::getReceiver) + .allMatch(r -> ((MyReceiver)r).getReceived() == expected_msgs)); Stream.of(channels).forEach(ch -> System.out.printf("%s: %d\n", ch.getAddress(), ((MyReceiver)ch.getReceiver()).getReceived())); 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..cc9219c8dd3 100644 --- a/tests/junit-functional/org/jgroups/tests/UtilTest.java +++ b/tests/junit-functional/org/jgroups/tests/UtilTest.java @@ -505,17 +505,16 @@ public void testWriteAndReadString() throws IOException { } public void testMessageToByteBuffer() throws Exception { - MessageFactory mf=new DefaultMessageFactory(); - _testMessage(new EmptyMessage(), mf); - _testMessage(new BytesMessage(null, "hello world"), mf); - _testMessage(new EmptyMessage(null).setSrc(Util.createRandomAddress()), mf); - _testMessage(new EmptyMessage(null).setSrc(Util.createRandomAddress()), mf); - _testMessage(new BytesMessage(null, "bela").setSrc(Util.createRandomAddress()), mf); + _testMessage(new EmptyMessage()); + _testMessage(new BytesMessage(null, "hello world")); + _testMessage(new EmptyMessage(null).setSrc(Util.createRandomAddress())); + _testMessage(new EmptyMessage(null).setSrc(Util.createRandomAddress())); + _testMessage(new BytesMessage(null, "bela").setSrc(Util.createRandomAddress())); } - private static void _testMessage(Message msg, final MessageFactory mf) throws Exception { + private static void _testMessage(Message msg) throws Exception { ByteArray buf=Util.messageToByteBuffer(msg); - Message msg2=Util.messageFromByteBuffer(buf.getArray(), buf.getOffset(), buf.getLength(), mf); + Message msg2=Util.messageFromByteBuffer(buf.getArray(), buf.getOffset(), buf.getLength()); Assert.assertEquals(msg.getSrc(), msg2.getSrc()); Assert.assertEquals(msg.getDest(), msg2.getDest()); Assert.assertEquals(msg.getLength(), msg2.getLength()); @@ -700,17 +699,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);