From 7f76f0aa90c256d0e03cf0be54e583c04aa16b65 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Mon, 27 Jan 2020 22:23:38 +0200 Subject: [PATCH 01/62] Authentication Metadata Extension support (#731) * fixes tuples bytebufs Signed-off-by: Oleh Dokuka * provides first draft implementation of AuthMetadataFlyweight right now it supports encoding only Signed-off-by: Oleh Dokuka * provides full implementation of AuthMetadataFlyweight Signed-off-by: Oleh Dokuka * provides draft of AuthMetadata Signed-off-by: Oleh Dokuka * fixes allowed username max length Signed-off-by: Oleh Dokuka * removes eager release Signed-off-by: Oleh Dokuka * fixes formating Signed-off-by: Oleh Dokuka * provides minor fixes Signed-off-by: Oleh Dokuka --- .../rsocket/buffer/AbstractTupleByteBuf.java | 2 +- .../java/io/rsocket/buffer/Tuple2ByteBuf.java | 24 +- .../java/io/rsocket/buffer/Tuple3ByteBuf.java | 12 +- .../security/AuthMetadataFlyweight.java | 336 +++++++++++++ .../metadata/security/WellKnownAuthType.java | 121 +++++ .../java/io/rsocket/util/CharByteBufUtil.java | 208 ++++++++ .../security/AuthMetadataFlyweightTest.java | 470 ++++++++++++++++++ 7 files changed, 1151 insertions(+), 22 deletions(-) create mode 100644 rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java create mode 100644 rsocket-core/src/main/java/io/rsocket/metadata/security/WellKnownAuthType.java create mode 100644 rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java create mode 100644 rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java diff --git a/rsocket-core/src/main/java/io/rsocket/buffer/AbstractTupleByteBuf.java b/rsocket-core/src/main/java/io/rsocket/buffer/AbstractTupleByteBuf.java index fbac4b1a0..a80605877 100644 --- a/rsocket-core/src/main/java/io/rsocket/buffer/AbstractTupleByteBuf.java +++ b/rsocket-core/src/main/java/io/rsocket/buffer/AbstractTupleByteBuf.java @@ -550,7 +550,7 @@ public int writeCharSequence(CharSequence sequence, Charset charset) { @Override public ByteBuffer internalNioBuffer(int index, int length) { - throw new UnsupportedOperationException(); + return nioBuffer(index, length); } @Override diff --git a/rsocket-core/src/main/java/io/rsocket/buffer/Tuple2ByteBuf.java b/rsocket-core/src/main/java/io/rsocket/buffer/Tuple2ByteBuf.java index 66c68009a..ba6620cb0 100644 --- a/rsocket-core/src/main/java/io/rsocket/buffer/Tuple2ByteBuf.java +++ b/rsocket-core/src/main/java/io/rsocket/buffer/Tuple2ByteBuf.java @@ -134,6 +134,12 @@ public ByteBuffer[] _nioBuffers(int index, int length) { @Override public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + checkDstIndex(index, length, dstIndex, dst.capacity()); + if (length == 0) { + return this; + } + + // FIXME: check twice here long ri = calculateRelativeIndex(index); index = (int) (ri & Integer.MAX_VALUE); switch ((int) ((ri & MASK) >>> 32L)) { @@ -165,20 +171,22 @@ public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { @Override public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { ByteBuf dstBuf = Unpooled.wrappedBuffer(dst); - int min = Math.min(dst.length, capacity); - return getBytes(0, dstBuf, index, min); + return getBytes(index, dstBuf, dstIndex, length); } @Override public ByteBuf getBytes(int index, ByteBuffer dst) { ByteBuf dstBuf = Unpooled.wrappedBuffer(dst); - int min = Math.min(dst.limit(), capacity); - return getBytes(0, dstBuf, index, min); + return getBytes(index, dstBuf); } @Override public ByteBuf getBytes(int index, final OutputStream out, int length) throws IOException { checkIndex(index, length); + if (length == 0) { + return this; + } + long ri = calculateRelativeIndex(index); index = (int) (ri & Integer.MAX_VALUE); switch ((int) ((ri & MASK) >>> 32L)) { @@ -354,18 +362,12 @@ protected void deallocate() { @Override public String toString(Charset charset) { - StringBuilder builder = new StringBuilder(3); + StringBuilder builder = new StringBuilder(capacity); builder.append(one.toString(charset)); builder.append(two.toString(charset)); return builder.toString(); } - @Override - public String toString(int index, int length, Charset charset) { - // TODO - make this smarter - return toString(charset).substring(index, length); - } - @Override public String toString() { return "Tuple2ByteBuf{" diff --git a/rsocket-core/src/main/java/io/rsocket/buffer/Tuple3ByteBuf.java b/rsocket-core/src/main/java/io/rsocket/buffer/Tuple3ByteBuf.java index 1a0c1ec31..be593019f 100644 --- a/rsocket-core/src/main/java/io/rsocket/buffer/Tuple3ByteBuf.java +++ b/rsocket-core/src/main/java/io/rsocket/buffer/Tuple3ByteBuf.java @@ -235,15 +235,13 @@ public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { @Override public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { ByteBuf dstBuf = Unpooled.wrappedBuffer(dst); - int min = Math.min(dst.length, capacity); - return getBytes(0, dstBuf, index, min); + return getBytes(index, dstBuf, dstIndex, length); } @Override public ByteBuf getBytes(int index, ByteBuffer dst) { ByteBuf dstBuf = Unpooled.wrappedBuffer(dst); - int min = Math.min(dst.limit(), capacity); - return getBytes(0, dstBuf, index, min); + return getBytes(index, dstBuf); } @Override @@ -539,12 +537,6 @@ public String toString(Charset charset) { return builder.toString(); } - @Override - public String toString(int index, int length, Charset charset) { - // TODO - make this smarter - return toString(charset).substring(index, length); - } - @Override public String toString() { return "Tuple3ByteBuf{" diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java new file mode 100644 index 000000000..f0f5cf54e --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java @@ -0,0 +1,336 @@ +package io.rsocket.metadata.security; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.util.CharsetUtil; +import io.rsocket.buffer.TupleByteBuf; +import io.rsocket.util.CharByteBufUtil; + +public class AuthMetadataFlyweight { + + static final int STREAM_METADATA_KNOWN_MASK = 0x80; // 1000 0000 + static final byte STREAM_METADATA_LENGTH_MASK = 0x7F; // 0111 1111 + + static final int USERNAME_BYTES_LENGTH = 1; + static final int AUTH_TYPE_ID_LENGTH = 1; + + static final char[] EMPTY_CHARS_ARRAY = new char[0]; + + private AuthMetadataFlyweight() {} + + /** + * Encode a Authentication CompositeMetadata payload using custom authentication type + * + * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed. + * @param customAuthType the custom mime type to encode. + * @param metadata the metadata value to encode. + * @throws IllegalArgumentException in case of {@code customAuthType} is non US_ASCII string or + * empty string or its length is greater than 128 bytes + */ + public static ByteBuf encodeMetadata( + ByteBufAllocator allocator, String customAuthType, ByteBuf metadata) { + + int actualASCIILength = ByteBufUtil.utf8Bytes(customAuthType); + if (actualASCIILength != customAuthType.length()) { + throw new IllegalArgumentException("custom auth type must be US_ASCII characters only"); + } + if (actualASCIILength < 1 || actualASCIILength > 128) { + throw new IllegalArgumentException( + "custom auth type must have a strictly positive length that fits on 7 unsigned bits, ie 1-128"); + } + + int capacity = 1 + actualASCIILength; + ByteBuf headerBuffer = allocator.buffer(capacity, capacity); + // encoded length is one less than actual length, since 0 is never a valid length, which gives + // wider representation range + headerBuffer.writeByte(actualASCIILength - 1); + + ByteBufUtil.reserveAndWriteUtf8(headerBuffer, customAuthType, actualASCIILength); + + return TupleByteBuf.of(allocator, headerBuffer, metadata); + } + + /** + * Encode a Authentication CompositeMetadata payload using custom authentication type + * + * @param allocator the {@link ByteBufAllocator} to create intermediate buffers as needed. + * @param authType the well-known mime type to encode. + * @param metadata the metadata value to encode. + * @throws IllegalArgumentException in case of {@code authType} is {@link + * WellKnownAuthType#UNPARSEABLE_AUTH_TYPE} or {@link + * WellKnownAuthType#UNKNOWN_RESERVED_AUTH_TYPE} + */ + public static ByteBuf encodeMetadata( + ByteBufAllocator allocator, WellKnownAuthType authType, ByteBuf metadata) { + + if (authType == WellKnownAuthType.UNPARSEABLE_AUTH_TYPE + || authType == WellKnownAuthType.UNKNOWN_RESERVED_AUTH_TYPE) { + throw new IllegalArgumentException("only allowed AuthType should be used"); + } + + int capacity = AUTH_TYPE_ID_LENGTH; + ByteBuf headerBuffer = + allocator + .buffer(capacity, capacity) + .writeByte(authType.getIdentifier() | STREAM_METADATA_KNOWN_MASK); + + return TupleByteBuf.of(allocator, headerBuffer, metadata); + } + + /** + * Encode a Authentication CompositeMetadata payload using Simple Authentication format + * + * @throws IllegalArgumentException if the username length is greater than 255 + * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed. + * @param username the char sequence which represents user name. + * @param password the char sequence which represents user password. + */ + public static ByteBuf encodeSimpleMetadata( + ByteBufAllocator allocator, char[] username, char[] password) { + + int usernameLength = CharByteBufUtil.utf8Bytes(username); + if (usernameLength > 255) { + throw new IllegalArgumentException( + "Username should be shorter than or equal to 255 bytes length in UTF-8 encoding"); + } + + int passwordLength = CharByteBufUtil.utf8Bytes(password); + int capacity = AUTH_TYPE_ID_LENGTH + USERNAME_BYTES_LENGTH + usernameLength + passwordLength; + final ByteBuf buffer = + allocator + .buffer(capacity, capacity) + .writeByte(WellKnownAuthType.SIMPLE.getIdentifier() | STREAM_METADATA_KNOWN_MASK) + .writeByte(usernameLength); + + CharByteBufUtil.writeUtf8(buffer, username); + CharByteBufUtil.writeUtf8(buffer, password); + + return buffer; + } + + /** + * Encode a Authentication CompositeMetadata payload using Bearer Authentication format + * + * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed. + * @param token the char sequence which represents BEARER token. + */ + public static ByteBuf encodeBearerMetadata(ByteBufAllocator allocator, char[] token) { + + int tokenLength = CharByteBufUtil.utf8Bytes(token); + int capacity = AUTH_TYPE_ID_LENGTH + tokenLength; + final ByteBuf buffer = + allocator + .buffer(capacity, capacity) + .writeByte(WellKnownAuthType.BEARER.getIdentifier() | STREAM_METADATA_KNOWN_MASK); + + CharByteBufUtil.writeUtf8(buffer, token); + + return buffer; + } + + /** + * Encode a new Authentication Metadata payload information, first verifying if the passed {@link + * String} matches a {@link WellKnownAuthType} (in which case it will be encoded in a compressed + * fashion using the mime id of that type). + * + *

Prefer using {@link #encodeMetadata(ByteBufAllocator, String, ByteBuf)} if you already know + * that the mime type is not a {@link WellKnownAuthType}. + * + * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed. + * @param authType the mime type to encode, as a {@link String}. well known mime types are + * compressed. + * @param metadata the metadata value to encode. + * @see #encodeMetadata(ByteBufAllocator, WellKnownAuthType, ByteBuf) + * @see #encodeMetadata(ByteBufAllocator, String, ByteBuf) + */ + public static ByteBuf encodeMetadataWithCompression( + ByteBufAllocator allocator, String authType, ByteBuf metadata) { + WellKnownAuthType wkn = WellKnownAuthType.fromString(authType); + if (wkn == WellKnownAuthType.UNPARSEABLE_AUTH_TYPE) { + return AuthMetadataFlyweight.encodeMetadata(allocator, authType, metadata); + } else { + return AuthMetadataFlyweight.encodeMetadata(allocator, wkn, metadata); + } + } + + /** + * Get the first {@code byte} from a {@link ByteBuf} and check whether it is length or {@link + * WellKnownAuthType}. Assuming said buffer properly contains such a {@code byte} + * + * @param metadata byteBuf used to get information from + */ + public static boolean isWellKnownAuthType(ByteBuf metadata) { + byte lengthOrId = metadata.getByte(0); + return (lengthOrId & STREAM_METADATA_LENGTH_MASK) != lengthOrId; + } + + /** + * Read first byte from the given {@code metadata} and tries to convert it's value to {@link + * WellKnownAuthType}. + * + * @param metadata given metadata buffer to read from + * @return Return on of the know Auth types or {@link WellKnownAuthType#UNPARSEABLE_AUTH_TYPE} if + * field's value is length or unknown auth type + * @throws IllegalStateException if not enough readable bytes in the given {@link ByteBuf} + */ + public static WellKnownAuthType decodeWellKnownAuthType(ByteBuf metadata) { + if (metadata.readableBytes() < 1) { + throw new IllegalStateException( + "Unable to decode Well Know Auth type. Not enough readable bytes"); + } + byte lengthOrId = metadata.readByte(); + int normalizedId = (byte) (lengthOrId & STREAM_METADATA_LENGTH_MASK); + + if (normalizedId != lengthOrId) { + return WellKnownAuthType.fromIdentifier(normalizedId); + } + + return WellKnownAuthType.UNPARSEABLE_AUTH_TYPE; + } + + /** + * Read up to 129 bytes from the given metadata in order to get the custom Auth Type + * + * @param metadata + * @return + */ + public static CharSequence decodeCustomAuthType(ByteBuf metadata) { + if (metadata.readableBytes() < 2) { + throw new IllegalStateException( + "Unable to decode custom Auth type. Not enough readable bytes"); + } + + byte encodedLength = metadata.readByte(); + if (encodedLength < 0) { + throw new IllegalStateException( + "Unable to decode custom Auth type. Incorrect auth type length"); + } + + // encoded length is realLength - 1 in order to avoid intersection with 0x00 authtype + int realLength = encodedLength + 1; + if (metadata.readableBytes() < realLength) { + throw new IllegalArgumentException( + "Unable to decode custom Auth type. Malformed length or auth type string"); + } + + return metadata.readCharSequence(realLength, CharsetUtil.US_ASCII); + } + + /** + * Read all remaining {@code bytes} from the given {@link ByteBuf} and return sliced + * representation of a payload + * + * @param metadata metadata to get payload from. Please note, the {@code metadata#readIndex} + * should be set to the beginning of the payload bytes + * @return sliced {@link ByteBuf} or {@link Unpooled#EMPTY_BUFFER} if no bytes readable in the + * given one + */ + public static ByteBuf decodePayload(ByteBuf metadata) { + if (metadata.readableBytes() == 0) { + return Unpooled.EMPTY_BUFFER; + } + + return metadata.readSlice(metadata.readableBytes()); + } + + /** + * Read up to 257 {@code bytes} from the given {@link ByteBuf} where the first byte is username + * length and the subsequent number of bytes equal to decoded length + * + * @param simpleAuthMetadata the given metadata to read username from. Please note, the {@code + * simpleAuthMetadata#readIndex} should be set to the username length byte + * @return sliced {@link ByteBuf} or {@link Unpooled#EMPTY_BUFFER} if username length is zero + */ + public static ByteBuf decodeUsername(ByteBuf simpleAuthMetadata) { + short usernameLength = decodeUsernameLength(simpleAuthMetadata); + + if (usernameLength == 0) { + return Unpooled.EMPTY_BUFFER; + } + + return simpleAuthMetadata.readSlice(usernameLength); + } + + /** + * Read all the remaining {@code byte}s from the given {@link ByteBuf} which represents user's + * password + * + * @param simpleAuthMetadata the given metadata to read password from. Please note, the {@code + * simpleAuthMetadata#readIndex} should be set to the beginning of the password bytes + * @return sliced {@link ByteBuf} or {@link Unpooled#EMPTY_BUFFER} if password length is zero + */ + public static ByteBuf decodePassword(ByteBuf simpleAuthMetadata) { + if (simpleAuthMetadata.readableBytes() == 0) { + return Unpooled.EMPTY_BUFFER; + } + + return simpleAuthMetadata.readSlice(simpleAuthMetadata.readableBytes()); + } + /** + * Read up to 257 {@code bytes} from the given {@link ByteBuf} where the first byte is username + * length and the subsequent number of bytes equal to decoded length + * + * @param simpleAuthMetadata the given metadata to read username from. Please note, the {@code + * simpleAuthMetadata#readIndex} should be set to the username length byte + * @return {@code char[]} which represents UTF-8 username + */ + public static char[] decodeUsernameAsCharArray(ByteBuf simpleAuthMetadata) { + short usernameLength = decodeUsernameLength(simpleAuthMetadata); + + if (usernameLength == 0) { + return EMPTY_CHARS_ARRAY; + } + + return CharByteBufUtil.readUtf8(simpleAuthMetadata, usernameLength); + } + + /** + * Read all the remaining {@code byte}s from the given {@link ByteBuf} which represents user's + * password + * + * @param simpleAuthMetadata the given metadata to read username from. Please note, the {@code + * simpleAuthMetadata#readIndex} should be set to the beginning of the password bytes + * @return {@code char[]} which represents UTF-8 password + */ + public static char[] decodePasswordAsCharArray(ByteBuf simpleAuthMetadata) { + if (simpleAuthMetadata.readableBytes() == 0) { + return EMPTY_CHARS_ARRAY; + } + + return CharByteBufUtil.readUtf8(simpleAuthMetadata, simpleAuthMetadata.readableBytes()); + } + + /** + * Read all the remaining {@code bytes} from the given {@link ByteBuf} where the first byte is + * username length and the subsequent number of bytes equal to decoded length + * + * @param bearerAuthMetadata the given metadata to read username from. Please note, the {@code + * simpleAuthMetadata#readIndex} should be set to the beginning of the password bytes + * @return {@code char[]} which represents UTF-8 password + */ + public static char[] decodeBearerTokenAsCharArray(ByteBuf bearerAuthMetadata) { + if (bearerAuthMetadata.readableBytes() == 0) { + return EMPTY_CHARS_ARRAY; + } + + return CharByteBufUtil.readUtf8(bearerAuthMetadata, bearerAuthMetadata.readableBytes()); + } + + private static short decodeUsernameLength(ByteBuf simpleAuthMetadata) { + if (simpleAuthMetadata.readableBytes() < 1) { + throw new IllegalStateException( + "Unable to decode custom username. Not enough readable bytes"); + } + + short usernameLength = simpleAuthMetadata.readUnsignedByte(); + + if (simpleAuthMetadata.readableBytes() < usernameLength) { + throw new IllegalArgumentException( + "Unable to decode username. Malformed username length or content"); + } + + return usernameLength; + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/security/WellKnownAuthType.java b/rsocket-core/src/main/java/io/rsocket/metadata/security/WellKnownAuthType.java new file mode 100644 index 000000000..bd4b656b8 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/metadata/security/WellKnownAuthType.java @@ -0,0 +1,121 @@ +/* + * Copyright 2015-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.rsocket.metadata.security; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Enumeration of Well Known Auth Types, as defined in the eponymous extension. Such auth types are + * used in composite metadata (which can include routing and/or tracing metadata). Per + * specification, identifiers are between 0 and 127 (inclusive). + */ +public enum WellKnownAuthType { + UNPARSEABLE_AUTH_TYPE("UNPARSEABLE_AUTH_TYPE_DO_NOT_USE", (byte) -2), + UNKNOWN_RESERVED_AUTH_TYPE("UNKNOWN_YET_RESERVED_DO_NOT_USE", (byte) -1), + + SIMPLE("simple", (byte) 0x00), + BEARER("bearer", (byte) 0x01); + // ... reserved for future use ... + + static final WellKnownAuthType[] TYPES_BY_AUTH_ID; + static final Map TYPES_BY_AUTH_STRING; + + static { + // precompute an array of all valid auth ids, filling the blanks with the RESERVED enum + TYPES_BY_AUTH_ID = new WellKnownAuthType[128]; // 0-127 inclusive + Arrays.fill(TYPES_BY_AUTH_ID, UNKNOWN_RESERVED_AUTH_TYPE); + // also prepare a Map of the types by auth string + TYPES_BY_AUTH_STRING = new LinkedHashMap<>(128); + + for (WellKnownAuthType value : values()) { + if (value.getIdentifier() >= 0) { + TYPES_BY_AUTH_ID[value.getIdentifier()] = value; + TYPES_BY_AUTH_STRING.put(value.getString(), value); + } + } + } + + private final byte identifier; + private final String str; + + WellKnownAuthType(String str, byte identifier) { + this.str = str; + this.identifier = identifier; + } + + /** + * Find the {@link WellKnownAuthType} for the given identifier (as an {@code int}). Valid + * identifiers are defined to be integers between 0 and 127, inclusive. Identifiers outside of + * this range will produce the {@link #UNPARSEABLE_AUTH_TYPE}. Additionally, some identifiers in + * that range are still only reserved and don't have a type associated yet: this method returns + * the {@link #UNKNOWN_RESERVED_AUTH_TYPE} when passing such an identifier, which lets call sites + * potentially detect this and keep the original representation when transmitting the associated + * metadata buffer. + * + * @param id the looked up identifier + * @return the {@link WellKnownAuthType}, or {@link #UNKNOWN_RESERVED_AUTH_TYPE} if the id is out + * of the specification's range, or {@link #UNKNOWN_RESERVED_AUTH_TYPE} if the id is one that + * is merely reserved but unknown to this implementation. + */ + public static WellKnownAuthType fromIdentifier(int id) { + if (id < 0x00 || id > 0x7F) { + return UNPARSEABLE_AUTH_TYPE; + } + return TYPES_BY_AUTH_ID[id]; + } + + /** + * Find the {@link WellKnownAuthType} for the given {@link String} representation. If the + * representation is {@code null} or doesn't match a {@link WellKnownAuthType}, the {@link + * #UNPARSEABLE_AUTH_TYPE} is returned. + * + * @param authType the looked up auth type + * @return the matching {@link WellKnownAuthType}, or {@link #UNPARSEABLE_AUTH_TYPE} if none + * matches + */ + public static WellKnownAuthType fromString(String authType) { + if (authType == null) throw new IllegalArgumentException("type must be non-null"); + + // force UNPARSEABLE if by chance UNKNOWN_RESERVED_AUTH_TYPE's text has been used + if (authType.equals(UNKNOWN_RESERVED_AUTH_TYPE.str)) { + return UNPARSEABLE_AUTH_TYPE; + } + + return TYPES_BY_AUTH_STRING.getOrDefault(authType, UNPARSEABLE_AUTH_TYPE); + } + + /** @return the byte identifier of the auth type, guaranteed to be positive or zero. */ + public byte getIdentifier() { + return identifier; + } + + /** + * @return the auth type represented as a {@link String}, which is made of US_ASCII compatible + * characters only + */ + public String getString() { + return str; + } + + /** @see #getString() */ + @Override + public String toString() { + return str; + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java b/rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java new file mode 100644 index 000000000..e011d2a6f --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java @@ -0,0 +1,208 @@ +package io.rsocket.util; + +import static io.netty.util.internal.StringUtil.isSurrogate; + +import io.netty.buffer.ByteBuf; +import io.netty.util.CharsetUtil; +import io.netty.util.internal.MathUtil; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CoderResult; +import java.util.Arrays; + +public class CharByteBufUtil { + + private static final byte WRITE_UTF_UNKNOWN = (byte) '?'; + + private CharByteBufUtil() {} + + /** + * Returns the exact bytes length of UTF8 character sequence. + * + *

This method is producing the exact length according to {@link #writeUtf8(ByteBuf, char[])}. + */ + public static int utf8Bytes(final char[] seq) { + return utf8ByteCount(seq, 0, seq.length); + } + + /** + * This method is producing the exact length according to {@link #writeUtf8(ByteBuf, char[], int, + * int)}. + */ + public static int utf8Bytes(final char[] seq, int start, int end) { + return utf8ByteCount(checkCharSequenceBounds(seq, start, end), start, end); + } + + private static int utf8ByteCount(final char[] seq, int start, int end) { + int i = start; + // ASCII fast path + while (i < end && seq[i] < 0x80) { + ++i; + } + // !ASCII is packed in a separate method to let the ASCII case be smaller + return i < end ? (i - start) + utf8BytesNonAscii(seq, i, end) : i - start; + } + + private static int utf8BytesNonAscii(final char[] seq, final int start, final int end) { + int encodedLength = 0; + for (int i = start; i < end; i++) { + final char c = seq[i]; + // making it 100% branchless isn't rewarding due to the many bit operations necessary! + if (c < 0x800) { + // branchless version of: (c <= 127 ? 0:1) + 1 + encodedLength += ((0x7f - c) >>> 31) + 1; + } else if (isSurrogate(c)) { + if (!Character.isHighSurrogate(c)) { + encodedLength++; + // WRITE_UTF_UNKNOWN + continue; + } + final char c2; + try { + // Surrogate Pair consumes 2 characters. Optimistically try to get the next character to + // avoid + // duplicate bounds checking with charAt. + c2 = seq[++i]; + } catch (IndexOutOfBoundsException ignored) { + encodedLength++; + // WRITE_UTF_UNKNOWN + break; + } + if (!Character.isLowSurrogate(c2)) { + // WRITE_UTF_UNKNOWN + (Character.isHighSurrogate(c2) ? WRITE_UTF_UNKNOWN : c2) + encodedLength += 2; + continue; + } + // See http://www.unicode.org/versions/Unicode7.0.0/ch03.pdf#G2630. + encodedLength += 4; + } else { + encodedLength += 3; + } + } + return encodedLength; + } + + private static char[] checkCharSequenceBounds(char[] seq, int start, int end) { + if (MathUtil.isOutOfBounds(start, end - start, seq.length)) { + throw new IndexOutOfBoundsException( + "expected: 0 <= start(" + + start + + ") <= end (" + + end + + ") <= seq.length(" + + seq.length + + ')'); + } + return seq; + } + + /** + * Encode a {@link char[]} in UTF-8 and write it + * into {@link ByteBuf}. + * + *

This method returns the actual number of bytes written. + */ + public static int writeUtf8(ByteBuf buf, char[] seq) { + return writeUtf8(buf, seq, 0, seq.length); + } + + /** + * Equivalent to {@link #writeUtf8(ByteBuf, char[]) + * writeUtf8(buf, seq.subSequence(start, end), reserveBytes)} but avoids subsequence object + * allocation if possible. + * + * @return actual number of bytes written + */ + public static int writeUtf8(ByteBuf buf, char[] seq, int start, int end) { + return writeUtf8(buf, buf.writerIndex(), checkCharSequenceBounds(seq, start, end), start, end); + } + + // Fast-Path implementation + static int writeUtf8(ByteBuf buffer, int writerIndex, char[] seq, int start, int end) { + int oldWriterIndex = writerIndex; + + // We can use the _set methods as these not need to do any index checks and reference checks. + // This is possible as we called ensureWritable(...) before. + for (int i = start; i < end; i++) { + char c = seq[i]; + if (c < 0x80) { + buffer.setByte(writerIndex++, (byte) c); + } else if (c < 0x800) { + buffer.setByte(writerIndex++, (byte) (0xc0 | (c >> 6))); + buffer.setByte(writerIndex++, (byte) (0x80 | (c & 0x3f))); + } else if (isSurrogate(c)) { + if (!Character.isHighSurrogate(c)) { + buffer.setByte(writerIndex++, WRITE_UTF_UNKNOWN); + continue; + } + final char c2; + if (seq.length > ++i) { + // Surrogate Pair consumes 2 characters. Optimistically try to get the next character to + // avoid + // duplicate bounds checking with charAt. If an IndexOutOfBoundsException is thrown we + // will + // re-throw a more informative exception describing the problem. + c2 = seq[i]; + } else { + buffer.setByte(writerIndex++, WRITE_UTF_UNKNOWN); + break; + } + // Extra method to allow inlining the rest of writeUtf8 which is the most likely code path. + writerIndex = writeUtf8Surrogate(buffer, writerIndex, c, c2); + } else { + buffer.setByte(writerIndex++, (byte) (0xe0 | (c >> 12))); + buffer.setByte(writerIndex++, (byte) (0x80 | ((c >> 6) & 0x3f))); + buffer.setByte(writerIndex++, (byte) (0x80 | (c & 0x3f))); + } + } + buffer.writerIndex(writerIndex); + return writerIndex - oldWriterIndex; + } + + private static int writeUtf8Surrogate(ByteBuf buffer, int writerIndex, char c, char c2) { + if (!Character.isLowSurrogate(c2)) { + buffer.setByte(writerIndex++, WRITE_UTF_UNKNOWN); + buffer.setByte(writerIndex++, Character.isHighSurrogate(c2) ? WRITE_UTF_UNKNOWN : c2); + return writerIndex; + } + int codePoint = Character.toCodePoint(c, c2); + // See http://www.unicode.org/versions/Unicode7.0.0/ch03.pdf#G2630. + buffer.setByte(writerIndex++, (byte) (0xf0 | (codePoint >> 18))); + buffer.setByte(writerIndex++, (byte) (0x80 | ((codePoint >> 12) & 0x3f))); + buffer.setByte(writerIndex++, (byte) (0x80 | ((codePoint >> 6) & 0x3f))); + buffer.setByte(writerIndex++, (byte) (0x80 | (codePoint & 0x3f))); + return writerIndex; + } + + public static char[] readUtf8(ByteBuf byteBuf, int length) { + CharsetDecoder charsetDecoder = CharsetUtil.UTF_8.newDecoder(); + int en = (int) (length * (double) charsetDecoder.maxCharsPerByte()); + char[] ca = new char[en]; + + CharBuffer charBuffer = CharBuffer.wrap(ca); + ByteBuffer byteBuffer = byteBuf.internalNioBuffer(byteBuf.readerIndex(), length); + byteBuffer.mark(); + try { + CoderResult cr = charsetDecoder.decode(byteBuffer, charBuffer, true); + if (!cr.isUnderflow()) cr.throwException(); + cr = charsetDecoder.flush(charBuffer); + if (!cr.isUnderflow()) cr.throwException(); + + byteBuffer.reset(); + byteBuf.skipBytes(length); + + return safeTrim(charBuffer.array(), charBuffer.position()); + } catch (CharacterCodingException x) { + // Substitution is always enabled, + // so this shouldn't happen + throw new IllegalStateException("unable to decode char array from the given buffer", x); + } + } + + private static char[] safeTrim(char[] ca, int len) { + if (len == ca.length) return ca; + else return Arrays.copyOf(ca, len); + } +} diff --git a/rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java new file mode 100644 index 000000000..13d910e15 --- /dev/null +++ b/rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java @@ -0,0 +1,470 @@ +package io.rsocket.metadata.security; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; +import io.netty.util.CharsetUtil; +import io.netty.util.ReferenceCountUtil; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +class AuthMetadataFlyweightTest { + + public static final int AUTH_TYPE_ID_LENGTH = 1; + public static final int USER_NAME_BYTES_LENGTH = 1; + public static final String TEST_BEARER_TOKEN = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJpYXQxIjoxNTE2MjM5MDIyLCJpYXQyIjoxNTE2MjM5MDIyLCJpYXQzIjoxNTE2MjM5MDIyLCJpYXQ0IjoxNTE2MjM5MDIyfQ.ljYuH-GNyyhhLcx-rHMchRkGbNsR2_4aSxo8XjrYrSM"; + + @Test + void shouldCorrectlyEncodeData() { + String username = "test"; + String password = "tset1234"; + + int usernameLength = username.length(); + int passwordLength = password.length(); + + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeSimpleMetadata( + ByteBufAllocator.DEFAULT, username.toCharArray(), password.toCharArray()); + + byteBuf.markReaderIndex(); + checkSimpleAuthMetadataEncoding( + username, password, usernameLength, passwordLength, byteBuf.retain()); + byteBuf.resetReaderIndex(); + checkSimpleAuthMetadataEncodingUsingDecoders( + username, password, usernameLength, passwordLength, byteBuf); + } + + @Test + void shouldCorrectlyEncodeData1() { + String username = "𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎"; + String password = "tset1234"; + + int usernameLength = username.getBytes(CharsetUtil.UTF_8).length; + int passwordLength = password.length(); + + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeSimpleMetadata( + ByteBufAllocator.DEFAULT, username.toCharArray(), password.toCharArray()); + + byteBuf.markReaderIndex(); + checkSimpleAuthMetadataEncoding( + username, password, usernameLength, passwordLength, byteBuf.retain()); + byteBuf.resetReaderIndex(); + checkSimpleAuthMetadataEncodingUsingDecoders( + username, password, usernameLength, passwordLength, byteBuf); + } + + @Test + void shouldCorrectlyEncodeData2() { + String username = "𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎1234567#4? "; + String password = "tset1234"; + + int usernameLength = username.getBytes(CharsetUtil.UTF_8).length; + int passwordLength = password.length(); + + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeSimpleMetadata( + ByteBufAllocator.DEFAULT, username.toCharArray(), password.toCharArray()); + + byteBuf.markReaderIndex(); + checkSimpleAuthMetadataEncoding( + username, password, usernameLength, passwordLength, byteBuf.retain()); + byteBuf.resetReaderIndex(); + checkSimpleAuthMetadataEncodingUsingDecoders( + username, password, usernameLength, passwordLength, byteBuf); + } + + private static void checkSimpleAuthMetadataEncoding( + String username, String password, int usernameLength, int passwordLength, ByteBuf byteBuf) { + Assertions.assertThat(byteBuf.capacity()) + .isEqualTo(AUTH_TYPE_ID_LENGTH + USER_NAME_BYTES_LENGTH + usernameLength + passwordLength); + + Assertions.assertThat(byteBuf.readUnsignedByte() & ~0x80) + .isEqualTo(WellKnownAuthType.SIMPLE.getIdentifier()); + Assertions.assertThat(byteBuf.readUnsignedByte()).isEqualTo((short) usernameLength); + + Assertions.assertThat(byteBuf.readCharSequence(usernameLength, CharsetUtil.UTF_8)) + .isEqualTo(username); + Assertions.assertThat(byteBuf.readCharSequence(passwordLength, CharsetUtil.UTF_8)) + .isEqualTo(password); + + ReferenceCountUtil.release(byteBuf); + } + + private static void checkSimpleAuthMetadataEncodingUsingDecoders( + String username, String password, int usernameLength, int passwordLength, ByteBuf byteBuf) { + Assertions.assertThat(byteBuf.capacity()) + .isEqualTo(AUTH_TYPE_ID_LENGTH + USER_NAME_BYTES_LENGTH + usernameLength + passwordLength); + + Assertions.assertThat(AuthMetadataFlyweight.decodeWellKnownAuthType(byteBuf)) + .isEqualTo(WellKnownAuthType.SIMPLE); + byteBuf.markReaderIndex(); + Assertions.assertThat(AuthMetadataFlyweight.decodeUsername(byteBuf).toString(CharsetUtil.UTF_8)) + .isEqualTo(username); + Assertions.assertThat(AuthMetadataFlyweight.decodePassword(byteBuf).toString(CharsetUtil.UTF_8)) + .isEqualTo(password); + byteBuf.resetReaderIndex(); + + Assertions.assertThat(new String(AuthMetadataFlyweight.decodeUsernameAsCharArray(byteBuf))) + .isEqualTo(username); + Assertions.assertThat(new String(AuthMetadataFlyweight.decodePasswordAsCharArray(byteBuf))) + .isEqualTo(password); + + ReferenceCountUtil.release(byteBuf); + } + + @Test + void shouldThrowExceptionIfUsernameLengthExitsAllowedBounds() { + String username = + "𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎𠸏𠹷𠺝𠺢𠻗𠻹𠻺𠼭𠼮𠽌𠾴𠾼𠿪𡁜𡁯𡁵𡁶𡁻𡃁𡃉𡇙𢃇𢞵𢫕𢭃𢯊𢱑𢱕𢳂𢴈𢵌𢵧𢺳𣲷𤓓𤶸𤷪𥄫𦉘𦟌𦧲𦧺𧨾𨅝𨈇𨋢𨳊𨳍𨳒𩶘𠜎𠜱𠝹"; + String password = "tset1234"; + + Assertions.assertThatThrownBy( + () -> + AuthMetadataFlyweight.encodeSimpleMetadata( + ByteBufAllocator.DEFAULT, username.toCharArray(), password.toCharArray())) + .hasMessage( + "Username should be shorter than or equal to 255 bytes length in UTF-8 encoding"); + } + + @Test + void shouldEncodeBearerMetadata() { + String testToken = TEST_BEARER_TOKEN; + + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeBearerMetadata( + ByteBufAllocator.DEFAULT, testToken.toCharArray()); + + byteBuf.markReaderIndex(); + checkBearerAuthMetadataEncoding(testToken, byteBuf); + byteBuf.resetReaderIndex(); + checkBearerAuthMetadataEncodingUsingDecoders(testToken, byteBuf); + } + + private static void checkBearerAuthMetadataEncoding(String testToken, ByteBuf byteBuf) { + Assertions.assertThat(byteBuf.capacity()) + .isEqualTo(testToken.getBytes(CharsetUtil.UTF_8).length + AUTH_TYPE_ID_LENGTH); + Assertions.assertThat( + byteBuf.readUnsignedByte() & ~AuthMetadataFlyweight.STREAM_METADATA_KNOWN_MASK) + .isEqualTo(WellKnownAuthType.BEARER.getIdentifier()); + Assertions.assertThat(byteBuf.readSlice(byteBuf.capacity() - 1).toString(CharsetUtil.UTF_8)) + .isEqualTo(testToken); + } + + private static void checkBearerAuthMetadataEncodingUsingDecoders( + String testToken, ByteBuf byteBuf) { + Assertions.assertThat(byteBuf.capacity()) + .isEqualTo(testToken.getBytes(CharsetUtil.UTF_8).length + AUTH_TYPE_ID_LENGTH); + Assertions.assertThat(AuthMetadataFlyweight.isWellKnownAuthType(byteBuf)).isTrue(); + Assertions.assertThat(AuthMetadataFlyweight.decodeWellKnownAuthType(byteBuf)) + .isEqualTo(WellKnownAuthType.BEARER); + byteBuf.markReaderIndex(); + Assertions.assertThat(new String(AuthMetadataFlyweight.decodeBearerTokenAsCharArray(byteBuf))) + .isEqualTo(testToken); + byteBuf.resetReaderIndex(); + Assertions.assertThat( + AuthMetadataFlyweight.decodePayload(byteBuf).toString(CharsetUtil.UTF_8).toString()) + .isEqualTo(testToken); + } + + @Test + void shouldEncodeCustomAuth() { + String payloadAsAText = "testsecuritybuffer"; + ByteBuf testSecurityPayload = + Unpooled.wrappedBuffer(payloadAsAText.getBytes(CharsetUtil.UTF_8)); + + String customAuthType = "myownauthtype"; + ByteBuf buffer = + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, customAuthType, testSecurityPayload); + + checkCustomAuthMetadataEncoding(testSecurityPayload, customAuthType, buffer); + } + + private static void checkCustomAuthMetadataEncoding( + ByteBuf testSecurityPayload, String customAuthType, ByteBuf buffer) { + Assertions.assertThat(buffer.capacity()) + .isEqualTo(1 + customAuthType.length() + testSecurityPayload.capacity()); + Assertions.assertThat(buffer.readUnsignedByte()) + .isEqualTo((short) (customAuthType.length() - 1)); + Assertions.assertThat( + buffer.readCharSequence(customAuthType.length(), CharsetUtil.US_ASCII).toString()) + .isEqualTo(customAuthType); + Assertions.assertThat(buffer.readSlice(testSecurityPayload.capacity())) + .isEqualTo(testSecurityPayload); + + ReferenceCountUtil.release(buffer); + } + + @Test + void shouldThrowOnNonASCIIChars() { + ByteBuf testSecurityPayload = ByteBufAllocator.DEFAULT.buffer(); + String customAuthType = "1234567#4? 𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎"; + + Assertions.assertThatThrownBy( + () -> + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, customAuthType, testSecurityPayload)) + .hasMessage("custom auth type must be US_ASCII characters only"); + } + + @Test + void shouldThrowOnOutOfAllowedSizeType() { + ByteBuf testSecurityPayload = ByteBufAllocator.DEFAULT.buffer(); + // 130 chars + String customAuthType = + "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; + + Assertions.assertThatThrownBy( + () -> + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, customAuthType, testSecurityPayload)) + .hasMessage( + "custom auth type must have a strictly positive length that fits on 7 unsigned bits, ie 1-128"); + } + + @Test + void shouldThrowOnOutOfAllowedSizeType1() { + ByteBuf testSecurityPayload = ByteBufAllocator.DEFAULT.buffer(); + String customAuthType = ""; + + Assertions.assertThatThrownBy( + () -> + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, customAuthType, testSecurityPayload)) + .hasMessage( + "custom auth type must have a strictly positive length that fits on 7 unsigned bits, ie 1-128"); + } + + @Test + void shouldEncodeUsingWellKnownAuthType() { + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, + WellKnownAuthType.SIMPLE, + ByteBufAllocator.DEFAULT.buffer(3, 3).writeByte(1).writeByte('u').writeByte('p')); + + checkSimpleAuthMetadataEncoding("u", "p", 1, 1, byteBuf); + } + + @Test + void shouldEncodeUsingWellKnownAuthType1() { + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, + WellKnownAuthType.SIMPLE, + ByteBufAllocator.DEFAULT.buffer().writeByte(1).writeByte('u').writeByte('p')); + + checkSimpleAuthMetadataEncoding("u", "p", 1, 1, byteBuf); + } + + @Test + void shouldEncodeUsingWellKnownAuthType2() { + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, + WellKnownAuthType.BEARER, + Unpooled.copiedBuffer(TEST_BEARER_TOKEN, CharsetUtil.UTF_8)); + + byteBuf.markReaderIndex(); + checkBearerAuthMetadataEncoding(TEST_BEARER_TOKEN, byteBuf); + byteBuf.resetReaderIndex(); + checkBearerAuthMetadataEncodingUsingDecoders(TEST_BEARER_TOKEN, byteBuf); + } + + @Test + void shouldThrowIfWellKnownAuthTypeIsUnsupportedOrUnknown() { + ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(); + + Assertions.assertThatThrownBy( + () -> + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, WellKnownAuthType.UNPARSEABLE_AUTH_TYPE, buffer)) + .hasMessage("only allowed AuthType should be used"); + + Assertions.assertThatThrownBy( + () -> + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, WellKnownAuthType.UNPARSEABLE_AUTH_TYPE, buffer)) + .hasMessage("only allowed AuthType should be used"); + + buffer.release(); + } + + @Test + void shouldCompressMetadata() { + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeMetadataWithCompression( + ByteBufAllocator.DEFAULT, + "simple", + ByteBufAllocator.DEFAULT.buffer().writeByte(1).writeByte('u').writeByte('p')); + + checkSimpleAuthMetadataEncoding("u", "p", 1, 1, byteBuf); + } + + @Test + void shouldCompressMetadata1() { + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeMetadataWithCompression( + ByteBufAllocator.DEFAULT, + "bearer", + Unpooled.copiedBuffer(TEST_BEARER_TOKEN, CharsetUtil.UTF_8)); + + byteBuf.markReaderIndex(); + checkBearerAuthMetadataEncoding(TEST_BEARER_TOKEN, byteBuf); + byteBuf.resetReaderIndex(); + checkBearerAuthMetadataEncodingUsingDecoders(TEST_BEARER_TOKEN, byteBuf); + } + + @Test + void shouldNotCompressMetadata() { + ByteBuf testMetadataPayload = + Unpooled.wrappedBuffer(TEST_BEARER_TOKEN.getBytes(CharsetUtil.UTF_8)); + String customAuthType = "testauthtype"; + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeMetadataWithCompression( + ByteBufAllocator.DEFAULT, customAuthType, testMetadataPayload); + + checkCustomAuthMetadataEncoding(testMetadataPayload, customAuthType, byteBuf); + } + + @Test + void shouldConfirmWellKnownAuthType() { + ByteBuf metadata = + AuthMetadataFlyweight.encodeMetadataWithCompression( + ByteBufAllocator.DEFAULT, "simple", Unpooled.EMPTY_BUFFER); + + int initialReaderIndex = metadata.readerIndex(); + + Assertions.assertThat(AuthMetadataFlyweight.isWellKnownAuthType(metadata)).isTrue(); + Assertions.assertThat(metadata.readerIndex()).isEqualTo(initialReaderIndex); + + ReferenceCountUtil.release(metadata); + } + + @Test + void shouldConfirmGivenMetadataIsNotAWellKnownAuthType() { + ByteBuf metadata = + AuthMetadataFlyweight.encodeMetadataWithCompression( + ByteBufAllocator.DEFAULT, "simple/afafgafadf", Unpooled.EMPTY_BUFFER); + + int initialReaderIndex = metadata.readerIndex(); + + Assertions.assertThat(AuthMetadataFlyweight.isWellKnownAuthType(metadata)).isFalse(); + Assertions.assertThat(metadata.readerIndex()).isEqualTo(initialReaderIndex); + + ReferenceCountUtil.release(metadata); + } + + @Test + void shouldReadSimpleWellKnownAuthType() { + ByteBuf metadata = + AuthMetadataFlyweight.encodeMetadataWithCompression( + ByteBufAllocator.DEFAULT, "simple", Unpooled.EMPTY_BUFFER); + WellKnownAuthType expectedType = WellKnownAuthType.SIMPLE; + checkDecodeWellKnowAuthTypeCorrectly(metadata, expectedType); + } + + @Test + void shouldReadSimpleWellKnownAuthType1() { + ByteBuf metadata = + AuthMetadataFlyweight.encodeMetadataWithCompression( + ByteBufAllocator.DEFAULT, "bearer", Unpooled.EMPTY_BUFFER); + WellKnownAuthType expectedType = WellKnownAuthType.BEARER; + checkDecodeWellKnowAuthTypeCorrectly(metadata, expectedType); + } + + @Test + void shouldReadSimpleWellKnownAuthType2() { + ByteBuf metadata = + ByteBufAllocator.DEFAULT + .buffer() + .writeByte(3 | AuthMetadataFlyweight.STREAM_METADATA_KNOWN_MASK); + WellKnownAuthType expectedType = WellKnownAuthType.UNKNOWN_RESERVED_AUTH_TYPE; + checkDecodeWellKnowAuthTypeCorrectly(metadata, expectedType); + } + + @Test + void shouldNotReadSimpleWellKnownAuthTypeIfEncodedLength() { + ByteBuf metadata = ByteBufAllocator.DEFAULT.buffer().writeByte(3); + WellKnownAuthType expectedType = WellKnownAuthType.UNPARSEABLE_AUTH_TYPE; + checkDecodeWellKnowAuthTypeCorrectly(metadata, expectedType); + } + + @Test + void shouldNotReadSimpleWellKnownAuthTypeIfEncodedLength1() { + ByteBuf metadata = + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, "testmetadataauthtype", Unpooled.EMPTY_BUFFER); + WellKnownAuthType expectedType = WellKnownAuthType.UNPARSEABLE_AUTH_TYPE; + checkDecodeWellKnowAuthTypeCorrectly(metadata, expectedType); + } + + @Test + void shouldThrowExceptionIsNotEnoughReadableBytes() { + Assertions.assertThatThrownBy( + () -> AuthMetadataFlyweight.decodeWellKnownAuthType(Unpooled.EMPTY_BUFFER)) + .hasMessage("Unable to decode Well Know Auth type. Not enough readable bytes"); + } + + private static void checkDecodeWellKnowAuthTypeCorrectly( + ByteBuf metadata, WellKnownAuthType expectedType) { + int initialReaderIndex = metadata.readerIndex(); + + WellKnownAuthType wellKnownAuthType = AuthMetadataFlyweight.decodeWellKnownAuthType(metadata); + + Assertions.assertThat(wellKnownAuthType).isEqualTo(expectedType); + Assertions.assertThat(metadata.readerIndex()) + .isNotEqualTo(initialReaderIndex) + .isEqualTo(initialReaderIndex + 1); + + ReferenceCountUtil.release(metadata); + } + + @Test + void shouldReadCustomEncodedAuthType() { + String testAuthType = "TestAuthType"; + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, testAuthType, Unpooled.EMPTY_BUFFER); + checkDecodeCustomAuthTypeCorrectly(testAuthType, byteBuf); + } + + @Test + void shouldThrowExceptionOnEmptyMetadata() { + Assertions.assertThatThrownBy( + () -> AuthMetadataFlyweight.decodeCustomAuthType(Unpooled.EMPTY_BUFFER)) + .hasMessage("Unable to decode custom Auth type. Not enough readable bytes"); + } + + @Test + void shouldThrowExceptionOnMalformedMetadata_wellknowninstead() { + Assertions.assertThatThrownBy( + () -> + AuthMetadataFlyweight.decodeCustomAuthType( + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, + WellKnownAuthType.BEARER, + Unpooled.copiedBuffer(new byte[] {'a', 'b'})))) + .hasMessage("Unable to decode custom Auth type. Incorrect auth type length"); + } + + @Test + void shouldThrowExceptionOnMalformedMetadata_length() { + Assertions.assertThatThrownBy( + () -> + AuthMetadataFlyweight.decodeCustomAuthType( + ByteBufAllocator.DEFAULT.buffer().writeByte(127).writeChar('a').writeChar('b'))) + .hasMessage("Unable to decode custom Auth type. Malformed length or auth type string"); + } + + private static void checkDecodeCustomAuthTypeCorrectly(String testAuthType, ByteBuf byteBuf) { + int initialReaderIndex = byteBuf.readerIndex(); + + Assertions.assertThat(AuthMetadataFlyweight.decodeCustomAuthType(byteBuf).toString()) + .isEqualTo(testAuthType); + Assertions.assertThat(byteBuf.readerIndex()) + .isEqualTo(initialReaderIndex + testAuthType.length() + 1); + } +} From 3f5b30448777b631af0de96aff9639096639a0fc Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Tue, 4 Feb 2020 18:37:33 +0100 Subject: [PATCH 02/62] bumps version Signed-off-by: Oleh Dokuka --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 13a89e30c..3018f4792 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,5 +11,5 @@ # See the License for the specific language governing permissions and # limitations under the License. # -version=1.0.0-RC6 -perfBaselineVersion=1.0.0-RC5 +version=1.0.0-RC7 +perfBaselineVersion=1.0.0-RC6 From f689f54913f09484aaf2038936faa62333b73dd5 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Sat, 22 Feb 2020 22:31:41 +0200 Subject: [PATCH 03/62] Provides Prioritised delivering of Zero Streams Frames (#718) * prioritization Signed-off-by: Oleh Dokuka * provides prioritized sending for zero-stream frames; refactors benchmarks Signed-off-by: Oleh Dokuka * provides minor cleanups Signed-off-by: Oleh Dokuka Signed-off-by: Oleh Dokuka * more fixes Signed-off-by: Oleh Dokuka * fixes format Signed-off-by: Oleh Dokuka * fixes Signed-off-by: Oleh Dokuka --- build.gradle | 5 +- .../java/io/rsocket/RSocketRequester.java | 4 +- .../java/io/rsocket/RSocketResponder.java | 2 +- .../rsocket/internal/UnboundedProcessor.java | 70 +++++++++++++------ .../io/rsocket/internal/UnicastMonoEmpty.java | 6 +- .../jctools/queues/SupportsIterator.java | 20 ------ .../internal/UnboundedProcessorTest.java | 31 ++++++++ 7 files changed, 88 insertions(+), 50 deletions(-) delete mode 100644 rsocket-core/src/main/java/io/rsocket/internal/jctools/queues/SupportsIterator.java diff --git a/build.gradle b/build.gradle index e834e21bd..7f7a3dea2 100644 --- a/build.gradle +++ b/build.gradle @@ -88,8 +88,11 @@ subprojects { repositories { mavenCentral() - if (version.endsWith('BUILD-SNAPSHOT') || project.hasProperty('platformVersion')) { + if (version.endsWith('SNAPSHOT') || project.hasProperty('platformVersion')) { maven { url 'http://repo.spring.io/libs-snapshot' } + maven { + url 'https://oss.jfrog.org/artifactory/oss-snapshot-local' + } } } diff --git a/rsocket-core/src/main/java/io/rsocket/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/RSocketRequester.java index 5590a9df0..ed13e9f6e 100644 --- a/rsocket-core/src/main/java/io/rsocket/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/RSocketRequester.java @@ -124,7 +124,7 @@ class RSocketRequester implements RSocket { new ClientKeepAliveSupport(allocator, keepAliveTickPeriod, keepAliveAckTimeout); this.keepAliveFramesAcceptor = keepAliveHandler.start( - keepAliveSupport, sendProcessor::onNext, this::tryTerminateOnKeepAlive); + keepAliveSupport, sendProcessor::onNextPrioritized, this::tryTerminateOnKeepAlive); } else { keepAliveFramesAcceptor = null; } @@ -411,7 +411,7 @@ private Mono handleMetadataPush(Payload payload) { MetadataPushFrameFlyweight.encode(allocator, payload.sliceMetadata().retain()); payload.release(); - sendProcessor.onNext(metadataPushFrame); + sendProcessor.onNextPrioritized(metadataPushFrame); }); } diff --git a/rsocket-core/src/main/java/io/rsocket/RSocketResponder.java b/rsocket-core/src/main/java/io/rsocket/RSocketResponder.java index 490b00967..53ced9763 100644 --- a/rsocket-core/src/main/java/io/rsocket/RSocketResponder.java +++ b/rsocket-core/src/main/java/io/rsocket/RSocketResponder.java @@ -85,7 +85,7 @@ class RSocketResponder implements ResponderRSocket { .subscribe(null, this::handleSendProcessorError); Disposable receiveDisposable = connection.receive().subscribe(this::handleFrame, errorConsumer); - Disposable sendLeaseDisposable = leaseHandler.send(sendProcessor::onNext); + Disposable sendLeaseDisposable = leaseHandler.send(sendProcessor::onNextPrioritized); this.connection .onClose() diff --git a/rsocket-core/src/main/java/io/rsocket/internal/UnboundedProcessor.java b/rsocket-core/src/main/java/io/rsocket/internal/UnboundedProcessor.java index dfcc13a64..fe664e843 100644 --- a/rsocket-core/src/main/java/io/rsocket/internal/UnboundedProcessor.java +++ b/rsocket-core/src/main/java/io/rsocket/internal/UnboundedProcessor.java @@ -56,6 +56,7 @@ public final class UnboundedProcessor extends FluxProcessor AtomicLongFieldUpdater.newUpdater(UnboundedProcessor.class, "requested"); final Queue queue; + final Queue priorityQueue; volatile boolean done; Throwable error; volatile CoreSubscriber actual; @@ -67,11 +68,12 @@ public final class UnboundedProcessor extends FluxProcessor public UnboundedProcessor() { this.queue = new MpscUnboundedArrayQueue<>(Queues.SMALL_BUFFER_SIZE); + this.priorityQueue = new MpscUnboundedArrayQueue<>(Queues.SMALL_BUFFER_SIZE); } @Override public int getBufferSize() { - return Queues.capacity(this.queue); + return Integer.MAX_VALUE; } @Override @@ -84,6 +86,7 @@ void drainRegular(Subscriber a) { int missed = 1; final Queue q = queue; + final Queue pq = priorityQueue; for (; ; ) { @@ -93,10 +96,18 @@ void drainRegular(Subscriber a) { while (r != e) { boolean d = done; - T t = q.poll(); - boolean empty = t == null; + T t; + boolean empty; + + if (!pq.isEmpty()) { + t = pq.poll(); + empty = false; + } else { + t = q.poll(); + empty = t == null; + } - if (checkTerminated(d, empty, a, q)) { + if (checkTerminated(d, empty, a)) { return; } @@ -110,7 +121,7 @@ void drainRegular(Subscriber a) { } if (r == e) { - if (checkTerminated(done, q.isEmpty(), a, q)) { + if (checkTerminated(done, q.isEmpty() && pq.isEmpty(), a)) { return; } } @@ -129,12 +140,10 @@ void drainRegular(Subscriber a) { void drainFused(Subscriber a) { int missed = 1; - final Queue q = queue; - for (; ; ) { if (cancelled) { - q.clear(); + clear(); actual = null; return; } @@ -188,14 +197,9 @@ public void drain() { } } - boolean checkTerminated(boolean d, boolean empty, Subscriber a, Queue q) { + boolean checkTerminated(boolean d, boolean empty, Subscriber a) { if (cancelled) { - while (!q.isEmpty()) { - T t = q.poll(); - if (t != null) { - release(t); - } - } + clear(); actual = null; return true; } @@ -237,6 +241,23 @@ public Context currentContext() { return actual != null ? actual.currentContext() : Context.empty(); } + public void onNextPrioritized(T t) { + if (done || cancelled) { + Operators.onNextDropped(t, currentContext()); + release(t); + return; + } + + if (!priorityQueue.offer(t)) { + Throwable ex = + Operators.onOperatorError(null, Exceptions.failWithOverflow(), t, currentContext()); + onError(Operators.onOperatorError(null, ex, t, currentContext())); + release(t); + return; + } + drain(); + } + @Override public void onNext(T t) { if (done || cancelled) { @@ -319,25 +340,24 @@ public void cancel() { } } - @Override - public T peek() { - return queue.peek(); - } - @Override @Nullable public T poll() { + Queue pq = this.priorityQueue; + if (!pq.isEmpty()) { + return pq.poll(); + } return queue.poll(); } @Override public int size() { - return queue.size(); + return priorityQueue.size() + queue.size(); } @Override public boolean isEmpty() { - return queue.isEmpty(); + return priorityQueue.isEmpty() && queue.isEmpty(); } @Override @@ -348,6 +368,12 @@ public void clear() { release(t); } } + while (!priorityQueue.isEmpty()) { + T t = priorityQueue.poll(); + if (t != null) { + release(t); + } + } } @Override diff --git a/rsocket-core/src/main/java/io/rsocket/internal/UnicastMonoEmpty.java b/rsocket-core/src/main/java/io/rsocket/internal/UnicastMonoEmpty.java index eb8a1aa11..64a7d4422 100644 --- a/rsocket-core/src/main/java/io/rsocket/internal/UnicastMonoEmpty.java +++ b/rsocket-core/src/main/java/io/rsocket/internal/UnicastMonoEmpty.java @@ -9,10 +9,8 @@ import reactor.util.annotation.Nullable; /** - * Represents an empty publisher which only calls onSubscribe and onComplete. - * - *

This Publisher is effectively stateless and only a single instance exists. Use the {@link - * #instance()} method to obtain a properly type-parametrized view of it. + * Represents an empty publisher which only calls onSubscribe and onComplete and allows only a + * single subscriber. * * @see Reactive-Streams-Commons */ diff --git a/rsocket-core/src/main/java/io/rsocket/internal/jctools/queues/SupportsIterator.java b/rsocket-core/src/main/java/io/rsocket/internal/jctools/queues/SupportsIterator.java deleted file mode 100644 index 50d2a326f..000000000 --- a/rsocket-core/src/main/java/io/rsocket/internal/jctools/queues/SupportsIterator.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.rsocket.internal.jctools.queues; - -import io.rsocket.internal.jctools.util.InternalAPI; - -/** Tagging interface to help testing */ -@InternalAPI -public interface SupportsIterator {} diff --git a/rsocket-core/src/test/java/io/rsocket/internal/UnboundedProcessorTest.java b/rsocket-core/src/test/java/io/rsocket/internal/UnboundedProcessorTest.java index 0dc7d9090..7bf975543 100644 --- a/rsocket-core/src/test/java/io/rsocket/internal/UnboundedProcessorTest.java +++ b/rsocket-core/src/test/java/io/rsocket/internal/UnboundedProcessorTest.java @@ -17,6 +17,7 @@ package io.rsocket.internal; import io.rsocket.Payload; +import io.rsocket.util.ByteBufPayload; import io.rsocket.util.EmptyPayload; import java.util.concurrent.CountDownLatch; import org.junit.Assert; @@ -82,6 +83,36 @@ public void testOnNextAfterSubscribe_1000() throws Exception { testOnNextAfterSubscribeN(1000); } + @Test + public void testPrioritizedSending() { + UnboundedProcessor processor = new UnboundedProcessor<>(); + + for (int i = 0; i < 1000; i++) { + processor.onNext(EmptyPayload.INSTANCE); + } + + processor.onNextPrioritized(ByteBufPayload.create("test")); + + Payload closestPayload = processor.next().block(); + + Assert.assertEquals(closestPayload.getDataUtf8(), "test"); + } + + @Test + public void testPrioritizedFused() { + UnboundedProcessor processor = new UnboundedProcessor<>(); + + for (int i = 0; i < 1000; i++) { + processor.onNext(EmptyPayload.INSTANCE); + } + + processor.onNextPrioritized(ByteBufPayload.create("test")); + + Payload closestPayload = processor.poll(); + + Assert.assertEquals(closestPayload.getDataUtf8(), "test"); + } + public void testOnNextAfterSubscribeN(int n) throws Exception { CountDownLatch latch = new CountDownLatch(n); UnboundedProcessor processor = new UnboundedProcessor<>(); From c66dfb8b6d9d1a51655cde43dcf56666fa251162 Mon Sep 17 00:00:00 2001 From: Jacky Chan Date: Tue, 25 Feb 2020 03:02:21 -0800 Subject: [PATCH 04/62] add MESSAGE_RSOCKET_MIMETYPE and MESSAGE_RSOCKET_ACCEPT_MIMETYPES (#744) Signed-off-by: linux_china --- .../src/main/java/io/rsocket/metadata/WellKnownMimeType.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/WellKnownMimeType.java b/rsocket-core/src/main/java/io/rsocket/metadata/WellKnownMimeType.java index 2743f604d..e78e87629 100644 --- a/rsocket-core/src/main/java/io/rsocket/metadata/WellKnownMimeType.java +++ b/rsocket-core/src/main/java/io/rsocket/metadata/WellKnownMimeType.java @@ -72,7 +72,8 @@ public enum WellKnownMimeType { APPLICATION_CLOUDEVENTS_JSON("application/cloudevents+json", (byte) 0x28), // ... reserved for future use ... - + MESSAGE_RSOCKET_MIMETYPE("message/x.rsocket.mime-type.v0", (byte) 0x7A), + MESSAGE_RSOCKET_ACCEPT_MIMETYPES("message/x.rsocket.accept-mime-types.v0", (byte) 0x7B), MESSAGE_RSOCKET_AUTHENTICATION("message/x.rsocket.authentication.v0", (byte) 0x7C), MESSAGE_RSOCKET_TRACING_ZIPKIN("message/x.rsocket.tracing-zipkin.v0", (byte) 0x7D), MESSAGE_RSOCKET_ROUTING("message/x.rsocket.routing.v0", (byte) 0x7E), From 2f71f730c39eec4a05ac01cd679d0fb341cf0a6a Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Sun, 8 Mar 2020 21:41:40 +0200 Subject: [PATCH 05/62] disables gradle modules feature (#751) Signed-off-by: Oleh Dokuka --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index 7f7a3dea2..80b16619d 100644 --- a/build.gradle +++ b/build.gradle @@ -96,6 +96,10 @@ subprojects { } } + tasks.withType(GenerateModuleMetadata) { + enabled = false + } + plugins.withType(JavaPlugin) { compileJava { sourceCompatibility = 1.8 From 0cc46d0e23b2d6746efdfc7d25f64f98fb3bea77 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Sun, 22 Mar 2020 15:28:35 +0200 Subject: [PATCH 06/62] provides rollback to CompositeByteBuf usage (#750) * provides rollback to CompositeByteBuf usage Signed-off-by: Oleh Dokuka * fixes test Signed-off-by: Oleh Dokuka --- .../frame/DataAndMetadataFlyweight.java | 7 +- .../rsocket/frame/FrameLengthFlyweight.java | 3 +- .../security/AuthMetadataFlyweight.java | 5 +- .../java/io/rsocket/util/CharByteBufUtil.java | 5 +- .../java/io/rsocket/test/TransportTest.java | 76 +++++++++++++++++- .../main/resources/words.shakespeare.txt.gz | Bin 0 -> 81824 bytes .../netty/TcpSecureTransportTest.java | 55 +++++++++++++ 7 files changed, 138 insertions(+), 13 deletions(-) create mode 100644 rsocket-test/src/main/resources/words.shakespeare.txt.gz create mode 100644 rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpSecureTransportTest.java diff --git a/rsocket-core/src/main/java/io/rsocket/frame/DataAndMetadataFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/DataAndMetadataFlyweight.java index e4b16fec7..d910fe92f 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/DataAndMetadataFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/DataAndMetadataFlyweight.java @@ -3,7 +3,6 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; -import io.rsocket.buffer.TupleByteBuf; class DataAndMetadataFlyweight { public static final int FRAME_LENGTH_MASK = 0xFFFFFF; @@ -33,11 +32,11 @@ private static int decodeLength(final ByteBuf byteBuf) { static ByteBuf encodeOnlyMetadata( ByteBufAllocator allocator, final ByteBuf header, ByteBuf metadata) { - return TupleByteBuf.of(allocator, header, metadata); + return allocator.compositeBuffer(2).addComponents(true, header, metadata); } static ByteBuf encodeOnlyData(ByteBufAllocator allocator, final ByteBuf header, ByteBuf data) { - return TupleByteBuf.of(allocator, header, data); + return allocator.compositeBuffer(2).addComponents(true, header, data); } static ByteBuf encode( @@ -45,7 +44,7 @@ static ByteBuf encode( int length = metadata.readableBytes(); encodeLength(header, length); - return TupleByteBuf.of(allocator, header, metadata, data); + return allocator.compositeBuffer(3).addComponents(true, header, metadata, data); } static ByteBuf metadataWithoutMarking(ByteBuf byteBuf, boolean hasMetadata) { diff --git a/rsocket-core/src/main/java/io/rsocket/frame/FrameLengthFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/FrameLengthFlyweight.java index 6011263fa..622160061 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/FrameLengthFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/FrameLengthFlyweight.java @@ -2,7 +2,6 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; -import io.rsocket.buffer.TupleByteBuf; /** * Some transports like TCP aren't framed, and require a length. This is used by DuplexConnections @@ -35,7 +34,7 @@ private static int decodeLength(final ByteBuf byteBuf) { public static ByteBuf encode(ByteBufAllocator allocator, int length, ByteBuf frame) { ByteBuf buffer = allocator.buffer(); encodeLength(buffer, length); - return TupleByteBuf.of(allocator, buffer, frame); + return allocator.compositeBuffer(2).addComponents(true, buffer, frame); } public static int length(ByteBuf byteBuf) { diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java index f0f5cf54e..27bf4d1da 100644 --- a/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java @@ -5,7 +5,6 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.util.CharsetUtil; -import io.rsocket.buffer.TupleByteBuf; import io.rsocket.util.CharByteBufUtil; public class AuthMetadataFlyweight { @@ -49,7 +48,7 @@ public static ByteBuf encodeMetadata( ByteBufUtil.reserveAndWriteUtf8(headerBuffer, customAuthType, actualASCIILength); - return TupleByteBuf.of(allocator, headerBuffer, metadata); + return allocator.compositeBuffer(2).addComponents(true, headerBuffer, metadata); } /** @@ -76,7 +75,7 @@ public static ByteBuf encodeMetadata( .buffer(capacity, capacity) .writeByte(authType.getIdentifier() | STREAM_METADATA_KNOWN_MASK); - return TupleByteBuf.of(allocator, headerBuffer, metadata); + return allocator.compositeBuffer(2).addComponents(true, headerBuffer, metadata); } /** diff --git a/rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java b/rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java index e011d2a6f..6f2aa7150 100644 --- a/rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java +++ b/rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java @@ -182,7 +182,10 @@ public static char[] readUtf8(ByteBuf byteBuf, int length) { char[] ca = new char[en]; CharBuffer charBuffer = CharBuffer.wrap(ca); - ByteBuffer byteBuffer = byteBuf.internalNioBuffer(byteBuf.readerIndex(), length); + ByteBuffer byteBuffer = + byteBuf.nioBufferCount() == 1 + ? byteBuf.internalNioBuffer(byteBuf.readerIndex(), length) + : byteBuf.nioBuffer(byteBuf.readerIndex(), length); byteBuffer.mark(); try { CoderResult cr = charsetDecoder.decode(byteBuffer, charBuffer, true); diff --git a/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java b/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java index fc6301d7d..6721acf2c 100644 --- a/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java +++ b/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java @@ -23,10 +23,14 @@ import io.rsocket.transport.ClientTransport; import io.rsocket.transport.ServerTransport; import io.rsocket.util.DefaultPayload; +import java.io.BufferedReader; +import java.io.InputStreamReader; import java.time.Duration; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.zip.GZIPInputStream; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -38,6 +42,25 @@ public interface TransportTest { + String MOCK_DATA = "test-data"; + String MOCK_METADATA = "metadata"; + String LARGE_DATA = read("words.shakespeare.txt.gz"); + Payload LARGE_PAYLOAD = DefaultPayload.create(LARGE_DATA, LARGE_DATA); + + static String read(String resourceName) { + + try (BufferedReader br = + new BufferedReader( + new InputStreamReader( + new GZIPInputStream( + TransportTest.class.getClassLoader().getResourceAsStream(resourceName))))) { + + return br.lines().map(String::toLowerCase).collect(Collectors.joining("\n\r")); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + @AfterEach default void close() { getTransportPair().dispose(); @@ -54,12 +77,12 @@ default Payload createTestPayload(int metadataPresent) { metadata1 = ""; break; default: - metadata1 = "metadata"; + metadata1 = MOCK_METADATA; break; } String metadata = metadata1; - return DefaultPayload.create("test-data", metadata); + return DefaultPayload.create(MOCK_DATA, metadata); } @DisplayName("makes 10 fireAndForget requests") @@ -73,6 +96,17 @@ default void fireAndForget10() { .verify(getTimeout()); } + @DisplayName("makes 10 fireAndForget with Large Payload in Requests") + @Test + default void largePayloadFireAndForget10() { + Flux.range(1, 10) + .flatMap(i -> getClient().fireAndForget(LARGE_PAYLOAD)) + .as(StepVerifier::create) + .expectNextCount(0) + .expectComplete() + .verify(getTimeout()); + } + default RSocket getClient() { return getTransportPair().getClient(); } @@ -92,6 +126,17 @@ default void metadataPush10() { .verify(getTimeout()); } + @DisplayName("makes 10 metadataPush with Large Metadata in requests") + @Test + default void largePayloadMetadataPush10() { + Flux.range(1, 10) + .flatMap(i -> getClient().metadataPush(DefaultPayload.create("", LARGE_DATA))) + .as(StepVerifier::create) + .expectNextCount(0) + .expectComplete() + .verify(getTimeout()); + } + @DisplayName("makes 1 requestChannel request with 0 payloads") @Test default void requestChannel0() { @@ -127,6 +172,19 @@ default void requestChannel200_000() { .verify(getTimeout()); } + @DisplayName("makes 1 requestChannel request with 2,000 large payloads") + @Test + default void largePayloadRequestChannel200() { + Flux payloads = Flux.range(0, 200).map(__ -> LARGE_PAYLOAD); + + getClient() + .requestChannel(payloads) + .as(StepVerifier::create) + .expectNextCount(200) + .expectComplete() + .verify(getTimeout()); + } + @DisplayName("makes 1 requestChannel request with 20,000 payloads") @Test default void requestChannel20_000() { @@ -223,6 +281,17 @@ default void requestResponse100() { .verify(getTimeout()); } + @DisplayName("makes 100 requestResponse requests") + @Test + default void largePayloadRequestResponse100() { + Flux.range(1, 100) + .flatMap(i -> getClient().requestResponse(LARGE_PAYLOAD).map(Payload::getDataUtf8)) + .as(StepVerifier::create) + .expectNextCount(100) + .expectComplete() + .verify(getTimeout()); + } + @DisplayName("makes 10,000 requestResponse requests") @Test default void requestResponse10_000() { @@ -283,7 +352,7 @@ default void assertPayload(Payload p) { } default void assertChannelPayload(Payload p) { - if (!"test-data".equals(p.getDataUtf8()) || !"metadata".equals(p.getMetadataUtf8())) { + if (!MOCK_DATA.equals(p.getDataUtf8()) || !MOCK_METADATA.equals(p.getMetadataUtf8())) { throw new IllegalStateException("Unexpected payload"); } } @@ -312,6 +381,7 @@ public TransportPair( client = RSocketFactory.connect() + .keepAlive(Duration.ZERO, Duration.ZERO, 1) .transport(clientTransportSupplier.apply(address, server)) .start() .doOnError(Throwable::printStackTrace) diff --git a/rsocket-test/src/main/resources/words.shakespeare.txt.gz b/rsocket-test/src/main/resources/words.shakespeare.txt.gz new file mode 100644 index 0000000000000000000000000000000000000000..422a4b331007ff9d1f575ebcaf12e1da47d1bb94 GIT binary patch literal 81824 zcmV)eK&HPRiwFo?;N4sR19xw7WOFWaXklw*b8uy0a%C=bcys`4z1w!0YGdHK^Y&P$!Z>#T=&*maGw;qt1f>Ov`drX0C5U)<)LC5BB z5+W%CNJmx3JdSQXegWpon}?Gf1T^_h@7CkE zKWiuM4Q#5U=^rTa>;gmT%J zcyU{$t?&s0Bzb^k3eWQ(7qiD{*et4M(RwL~xlVD+Qw~eA(A5lvK<9bpvzyOz?~l=G zyKsMQb1n*4L>dM;m&H+8o-8>!s-UB^u1PMnjycdhr$809H?$zsOyju{hJ!Fq2>+Ba z6LdmbrD?oNOpVaxHWY1WSWv46{-7_97B$QqBs5Ctsq1@0zxnq3^0NCuJGYp|%PFB5 zehuTTPw1gn5Bx#jJ_?>3$jIC{bkmH!#VQ!ecFfTJd$w*7IdBph??WUo-<5{8kaaoH z66ms=bfxp>Y!Gy`*F)0xh?uUu*N9>Bz`Sy5%u z^{(k%UYxrNMkJK1z%@T~Vh?JyP-rdh9?nE7B@RzqK*_5psR!@lb9=TngdxJoqf(9S zK`1VH(nqTsZx#=q7aT!;ef~iSZbytHFqt(XQ$6~eDTr(4A{D*X0&*rLigjdxjVh;6 z#Wd<6k6r-`S^Y*YJoF1In&j>i4_m$l&#EyhZg6gsNvkR&Sl5_h`~JLob*XW2SRp*u3rFNl8qYI^a^#CodnGY~n)mb99w!ZrK`M!H!w58I=06dQ1tWP!o z+3c@#ADJ^ju@qR{`I5=`oyifSZtB7hSi4XSiY|GAI+b^);)c(aKvEm1$h)G5>78sm zaK#(MHEbHY#-^l=Bg%5ub}01(2oZQpbpG4U7KsAgWwoUAo8pN%>HOkuVH z6v5|btWQai3BxWsglM5Un#|=Z!Q0Nl!yc6;NNDzw$;(00 z@6XTNFfF+|o~BAvkU>LRs!~43fypDfEY}FJOFg(AW^-9Boebw4zCl<;Q~ObRJN!zpcLSzQ1C# zMN9o-^9y-H=Z9{OGXOUROskIU3TToeB0R<8F*jF*aLhiVKBCpGtP8YdM~3?@sK5 zi?GKgBCAJl>jP841kdc{`zG(vbG@wI*Vgly1sWvGLcl6QAgQ=WTM97tmDJp?Vc@_NplVV)TSNJ2RFuzKrs zM$ps9m1Cc*E}5g3-RlN@exEKE+9QGKZ);&_DKESKS-oy;IIX&BoiQdl47ZT-WQS_#zn=6%AqVzWX#sA9-5aIObj9{`d1ffP_ zZLlIGAsxN-RO;&F&yJ0s zjGxe%X?$+MXT*9raaTQv_4lYNmy>+=7sQrR(;~&frl(CowVyEMfC*KxVZoBdRFy8;@}Q>c zMBDB-`wW?!vu$IVf&3?_c5?~c{Gem*7H)b>{$REOy6yQ$&el60EG^Q_SdpR!T(KK8 zqm`K)<;RGZjWKvk8Li#`bBBhz%;!#X&U1&dxA6&uYF(M7`1|wj^#^RQpFq>NXP$!w zO*}}H^T;U8WC%i&wIC5PlVhfy8<=O`l{Ca-xSYCkb2!n%g^9ZG#S4I;UHVQlNU}Q- z>9|bTN;5KBowka4zT3LWLa_p};<0J#cYdI;-Fn|t+lNKdLb-{_NtoL~RZTIpfeUY+I$`rCV z+AB~cVFXVYD7W<3xrFBlvlTELo8qO6DX`J5kps3`Am+M;V@L0N9!Hj*H9^@iU%xR+ zR&jcVLd0&%@Ygt-3!4NC7@EzXC3<5Tudh3bf6HXj4ApYM)OvvHPc_-gcNeZQ~5s^P$;YxCW@SJo1TNC@AqEj0y~sRsbuSp***vhX}${cD7Dn z_4s)H?d=t7dDGzqrK8)uZN7!wrC|sb*Xd4;jj+8t`~BP7i@TH76MY$5k4_A_f6v@r zAxnGN3TD~Egn_Dcf|mGU4foAuCCC&?jz0@L3JZyQLc8t#+! zEHCzlmCK5zadg8lRmX!_02eq)*v>{|zwf?V1c>dDhQZeDbV0+K&815WjScc*^D1+2 z78@OG{6sk9@HY12k)=U)2U_?3cOVsILPZxAk=$6VBDb1&;JeI)Hnx_-63n2N8IW9y z6c8X~bbXF4FT{Oh_T523(<`p*ydCS#z*RMF?e)qB^-46tCZ> zM=pzrZ#=*ZfAry2 zwJn`CHe=g>VBzJ4+g90T_j;q@jJbD?8D*u#xi4n&9y4NrB{kd7neiyh*jrzVedP!G zzWKI({_*YY_Xh{qB!twahT9xnMnU zCpsT&nw4=5%Z!F5qS2X>+$t-uML`_V4QA{Ms^Bm|1dPy*g-s3Q4w&g;PU5YER#K5f z=d%j-IBQk-{@c&*yVsAZkyDtRRY68;L#z;E!Ta;>g&cu#1IGjw6<=CpJxr|y0I1tp zuQ^~jjU2D6&p(|L=Q)+CHrS*k+pvf&wR~=9;O(Hp`**x2l#;nRH%Dwq7(u9K<~vNA z8$j2|VSr7!N2-}3Z5$fd2&rdatFAkNj{?9#cM_W7ERbFp0X3)+STbexcLI}GINE$8 zAfXu>wU)K_)o<81Hn}?v)B|01m&TM}Qf~mcaVRwJbcx6_hDh!wXU=(70!a!{L>jv5 zuB>!Gb_rkst)n~il#Iu4Np=s$_(Y(&$%J3npz3|>zCUl@-q}Lp6=l|2$vmB~&VB#* z_VXRe?!an8L@_&|#*f<={j?rB2jlxDqc%_B1Y1a4HDy+OS+MqBJ1aV{ z_dlNP$smm;*q=@BicPT0JT#nSHs!#GU{+pG`3p~c(rn6?u`i%DG#yQ5V!s&Nm(u&iM_axS zujV|(=0w(g2AmtZyWpSw8leyen%T2;K^upu4IMB+Y^i}JhgPWAkuf+ecnh!+NYdg? zl!J8DK+%WenB9KZftD8h+qUwTp%eDQaSf~LV)X8FDI*Mk>r^W0x`=d* zgF$yV6bX$SkuJ{4sk=bX0c)WrCdVPY;7VE6F}xtqbbB7a3)U`lv5!E>5t7YB=Y2-{ z<~0Oo=UUB`jDoy8%C$6BV1&%z4b#@65}fI9q=UbK*HeAxoM} zRgjz;V#fI1_2-X|_ve+_AP)2{oK72KX#;#O%{jaVa~e* zI|j5+4Ku|&$>j6nA45?V=vgF7s;bP1i2AyelT0gCur+tI}JsPi|6(Vg$ zsI}yq*$Hq$MnLgyf$Z;C)~}nx0qa%Zso_fl(i!fsXtuf-WKprp+v8Nl8V1-Ndsm_YR`Tk=14qr+hHY8|bR!efj~V053KL4+hILN<8JzT>8OD#EL>n4>5k?toX^Dn&CHR&tSfCrV}7eo6>d=g)b!OK)rS4TMQ z57;i}js^_Ah5*SOpBK~ICsa#AJjR}C#nV&Uvv3KLijc)i^tf@J0YLjR{G7=oGZm#d zJIr)2of?}pS@FJnd|Ulky}RyL)%{=7;e^dv|9f=f_+F4kmmDqrHE?p6I_Yg>CMh)K z^RCnmi3S6pd}IL+M^;r%GS>o0vK4U+i@zRhJ#8vHUW2)(iL4}%;Tq$w0a z&(q+kWhEc}J+nd(l@2g>0*Y#5WVF(I{r`TxK@E>Kiz_H^piOVrtKUDKe|)S$<4%4t zNGo~$8%6H|#06xyCqCpJGHYK^F3XP)+RlzX1|BVm-sxo1z8RhAhoA%Bsn z8_DedfqF3ctH-erMaO5EzsPZrXpfyQGZ^R4aD!tFhR>i9vi)i~f3^`8_DQCk1TfA) z0z+pXO_QI|QsWkkkdI6TPQ6i;dIuL7L}sT9P~y2UuF}F0iFRCAl9tUnGc7XobWy=g z;sVIg)>=2?h1I2?WJ@AzY4(bz=wTH-tP&4PePl`EEcJ5216(`0Oy{Pvp0^y3TC%T7 zWO+`9nW}*zj%JKsNt`isH0kY&#SNg20azY7G@~CsR(KN$viqQgH38IjQ#`Nmmeq6< z8j6`5wH#O0z}~l^*Idj6<$GeKUk7oGtW;o=%EB7B9*cU~b?tGH>Z#EORmKjepcC@Q zPF{gbQR)+`7R}Yi?guno-G;#0&w2oT*iVNXz*7RoHOL=8ezQrWxhBq5P6#cCwWB50g>5TuJab>fT)i zc^~x1?&H8_XsbD!G_8&D1tv|T4tOUZ)7Zq>d(UIg!v{f9aj{T#6UXF463J1{kM8V? z2I-lG!ah?&4+CjN*qN-AnH=?$ld6*5o(h;8B4xBIf2X?lH>zvLJ>RFUX@XA#^RT8# zjyl%Bbs3bob@^ymaV0Wb!~%5WGJ=%_$PRSRHob-3a~IgDo&rjyXX5ef*NTVE%v zD}xTx9D{jJ`1Q;6aazV`D?dm_tF!|YOKZKpnV*SMKy$@i?DcMIPiPC9?u=U9_Ex#q z0+VduVH>l#C?jJkHQ`T)CVkR?I-U5kVx3a|EA5#|)+N>?rjG@oQb_Dr2=t=PB=Jks zy-g^3nE?#rQ`6RjEC4V`hgk%it&=Fj{CZ(6ki@STNT+$AGIvQTlC)5SiHC%Mj+oZc zG@j5{V3J$fcfajEK6dYtJ?f(j_5=h;Y=9ue^~GiB8U69-L3E6?8^kr`Bvb7PR&{P& zWJhUeZVn6uJDF<%BxYos#MR!9ndkl2K}^GdW~`zJDUhy2TvI|5UOb`BpktP1#B^%< zPlrOhCWkhV^~6{=q8OcohMv^N$5gXUG>!CIwjeSV0sUjtq z0x+g51(Qy$MrLPJHVo7)okleYA>jvj;y!N9ev^MW%Z8TxP9ua*{x;nUN+yP zVHmy6Di~?mR0)!3{fP;F>W^ImsY^0j!6&Rbw%QmadVW;Cc67N7MH`w@NqF*9;0uDa zV2_r$4#V;^QY8ydL$Ch4$FX)g8wVzo3)AH1>qR^Qo~Q>Fx);C8)Ho1f`OwmHwvl+4 zeJzlrkd1WJrmL3ThCqd03Zoao7#U{w>B&VSs@>h$KZ-UsvbO-x^I0i6kKSNlIq(Uq zdmVfVGw{HJ5Q$(_WkvPg$=A`2d+({(qKAaZ;`#yGj@My0wdK<8b+>)q{3|pQeZ?2u zp_~=sr+roYMlY)c{2?b1}tbId)b%fw?!j z*Mu_-^I0S00R4#E{PFf;)Bb(B@GXiXQ*gm+rhT&IwhF^ZXwt&kYD5)R+QP+^Mw0z%>vn| zsyc#>t__Ttwg<@9Eu<=?Ni!}YQz;S}c!T>%d7QMxm?k`@wZJ5Mh^TQwmld_Xn3-rf zdfd}#K3{+xif>~4lOhGMAvuBOoE#YO_{FL=;g%&p;f6xu``)!cQdN6GvQ~7KtFv%a zL`0X}&&b7Yn)F1ph!sexYSqlTu1&tKtrN6q;-ql8X^JX@+)dsvjQ-8iA?|J5>uK^c zi?t1Y7_I+!#!!7l&TiakCeZ(_iD}P`Z)Sj;j}jnxy}IFy$65dhdyob+PouKLWWf>9 zW!7;rj|Y@no5oe@H3btfzI7)!Z`wSqpI5J&l_VRn@g3nB#=g^A3+qi- zkkD8_s25#}qjUfkwWG_0iu)5UKhvdW`z{>N5(@gqqw5d=?5wARCz}Zmf-FF*4gBEc>Ip8)MKW({o(1A-Si)-naTY_}YTEwXVUj%h*K|J2P_tzX8zWwD; zr-mkw#Ey&%6EPC)GvHbu6m;M0X}HNSjTD%eiO?js(Ge5;O5I5v<3hNLA_+uP3XqdV zc+fN6=+ah+>Gk@m*F`suQY4wJfI2RkdhQU=ByRV=c<8w=;+!Q=CYfmcj{A7Mv##%XKzyUF*N%_Cq+dy9 zE0E4T;fq9p6xv`TgyM@PyQQ8xM>NSphakn7$P}Ed21IFfLJhr~`98PvYIbfvj*)z~2PmZ5l zQi3T2BaGP4qh$vI_>$@FnEXyI_=0Nf=rS!hg(c7)pBW;ykfq+!W)=BUPv*Af^e5kVVq^sf8jLF(Pb3{Dcvv$ zlc?(1IC+&1^78ic^#c|+C~stPc~hDBq^rjzL2H_O_VsJ^_%KnXoNdx0nIfswwCEpZ zx@4u%x}cv`);MFamieOe=%PDec9f67+v?2EcZgmyc!*^!Fp1u>Oes7_wwGBE`$L$$ zr4T0(&qMJ>JbP`@ftSi2EET}ScZL{KN9<(5&=sRJ(XPhEhqqCK>4+DOCn6;zVDwH3 z$Zm9uPHI$_#5y}RPRk9i0oRr7$36aKFixxX7<-YdJzlJs%FjUH;#1vwx?Fp@=l5@B z9;NOAsO(giy>jb<)b;3jYQGpiYA)-o8DHFB3$h>K?8w&y1QZZvKO=haCJ@;DS-Z?{ELw!B+XhS6YwVr4N@K5&kAO>;k<^*UMT3Z@L@ zfpiZjAsBD3D~QM!dQmet$##UCq(0+gI*~z`ghgi0B{?@Zc?l3qf5HPF_`thv9FbHf zroq*5KzYV~#9AOpp$#a~VWbBfTNno%kr?r5BP)Teeb!t6nL!`pm+JhezS!{0*Lz?z z&rdq&T*|iUInE+oYCKFj{YvwkTD6~ zK0e;QyV6S;6cNt{d`-*tp!|AnwW1zzzjR5y3d1@Z3-M5nJ>Rh zc^!lrfAqnqbA=>-uU1*ru_wT=lU%9SXq8o6IK0b}Qs!c94T2W@_VeA^Ezv63?wvMK z&1Tk)nbl)vDXIR>EPU)XbE6zXHLJ(%z%vM5M{Ya|;q^p=x9Z9|stjHg<6==d=tD<$ z$oNg{lCjtsk)XesV`PW_Xxwjq?(lr>YH)Y>gYov~kEbxfU46fp(sFNiWdQnRwDn}y{>=0|0x&ruDd~uFig(XbF7C% zijznHS^-URch_x-bEcOir&7Bz(bpv2N@yivd~~Fo z9b9z(WsJ~{Y;DGN@hDU#*aooR_l67I@$;m2R%a_#YWELfJT9bIli z(S{~h5<#RAjuy=ntOgzF_AU8f;YI!8LZzh&_YSjKD>Y0>QX!`(tfLm!7*Oxofe{txac` z$IRJErE;SnaDaDB7&Ii47uQXc`{ckR;7ue=a>Z-N$8LmeQ%G5p*4(*6W)=5)@vPon zTV!kBDbtmRgUj*(Qo>Gw8i`PutuqHh$Gvu=Ve$(gW8OV^gsh7y>W=r$2o=Y0R3+*B zO=FPI6tN;3N$Q)q8IXBW4ki=IOD7!LAjJvG$Zh~-W8^-YciPS)B+3k{m&gM`5) zK9^CQ+30=QsG()_&WgX~0BB_<$FN1O$O(A%8F=PIdo9>|73P}-zE|i2ixUj!qjyro zlgAajI@B9)=l5VG{JT4X=7o-XPKh(OA!w2-;v3E+OTiFtMP_nnh05)WufNvNw{r(e|ukgk9tQirZ2pQ$P zpTDHRmM;u2bO!`kuz?8in;`3-n}69l6h8|BTZRj0>4>$8m;;Mj0Hl4k)@iU&U9>j5*U0LT*W4RCP@n>dlP=WZ4*E}K~1j7p%1S}X3 z%l*&y-@ZXQvK;Y2Gvv8?k~j+Be^g<2+PU}^^XXH;s*vyxkAtGIn~1wuP~Sq598%kBb{ z%Q;a-#}1yL*C3jjs~zOKr&NjwsvUH2$adCi|dH$3#QiaL+jJzY~TCA@fU1kEvxI`6YaweTpSO;+pt$GEd zK4D_ulk*7D$Y<|ilHZ*vd4Am4kb{{}jahGKF}lnjWK3M zju9s3RI@(=tT18h=U+yC0cCKjOh{WU3;_otb^8ePhDc-lN|nx}&;K`|Qy z-eKC~p=ni^SO9&JSEI-L)gahiOyrYGn*ci1$Ex0oLq+~a2wJ}TO*cKD)55>6qB(G@NNHDtc9_}yT3 zvspcVe0$yf_+d($=UB(=h&U0vIrs_7uSBDz3Cq6OGZlTq-1Tv1q|-aB4!Fw8`q9m| z6~gHKwhLqU3_4drU!OD>G|STbJ+D`zE*+Q0yFPv=b`hAih48< zMm{r8hIUmtuPW1f%V&1pcOwDc${vV^z~UAdTsN!l>-XpFZ#(Mls$FL!_MFIiQoQh? zBx{yP0N&6eoM*ghH(M3Zc|3Ql-!@2X!bqwQ-+$k!N}Mms*7<^Pk#5xX0SjSbR&^LK z#vE`29H}8l3bD!x4z$P$PuSKZZ8NdZ^!3}u5i--rox8w^%dG=HecObokFz(cSACp~ zyHDumi#3=a$y30|@&R9>?RlMWP$tV6bbMF@Nl5_>`|odYUKTrL6{q7)Y0E~ z@%(|fhKb-K8(1j^<`kl}YmQSJ-0S$xEu;f}!;G;^9^|zNApRQFm*K%)fbrV|r1W~h zhw_U4qHpFwGEW#*#$-CDOVqs!pYVlcdtr%ORI-~O)D+6d(dE?P>HPlf`3EFieFE|M zW%mK6e)pDGFLZJ2=>bSt{mW*drp4iLQ!4t<6!UzVCt?8o*J=sq2Y|lCc0cg5ia~0;Fq4vWIt`7N+fS66 z%kYcmfTxf#p%hTf{xUbby!p^@A6kGfuPS3&Gv=YJ7S0}&^lWvC70O5{fwH30HaTSJ zx`D%45PllCdZ|`ybE0y8Zq!IIp2OSB;>{?u%w#EiH9&x@vG?GpAQRtDfpAl66C^b5 z3Umm)o6Mdvc|`9F(>SwXgHn&~`2FSi$IFgtHhSJ;4@d;hv@%GNuOljr^5B>HRK|!| zwRP>*#?j?ANO7X3l`k|BUyzeIOb&GJeRsAIhBE?@BF276C^@^FLoT5RAE9tr*~pQJ z%s*c@7Dw=WHe7SltZH2d$`I^`J%ys1jxykO0?h_SOK!Y8^wh~o0*~?xSV-KUAIp? zM@;-|744qiB(Y7K$YBb16|t?d<97qUOb|rLns^OE5V=>{R0V9PII8;*)5Oqt&!22M z_@4B}5pvIh(;%Y#u><$0&79c>1K3zVe4HSK4hgmHkagdZiOnpsOW%|;nQng}%O?>W zkhq-a(3Zd(n&ixi7B<2nRGOU}ltJ8f+?9oe6*yy7Aj#@KzNmQQMS{bvnfvgJ=V|61 zW9-N7#~K4HcHlEhV&cx#rkTQh&#ebj;K_bqRy9#%$ST{5yD$BQ;sIX>_6{1Sz#RPw6>wFrIl7{&&DPaZ ziJ^K2lP-_Nb!_Ta{A30Tlnt2e*V^siAPf3P6NBi~nd4?-nqH#Ujx(ytE ziC5rmXdZM8ExA>JqMd3$GN?LrrOmWFZOL9+=0jn1%d z9vPY_e|<{xTuyS6Y8-*xo0YjuJ-kaw10R`z<#I#l1}1OdjkiJ>mU%6#{}`vJ}$ zi7@H;``asAGaO z+zCCe7}``;b?rj)11$?L@OFNRPg^&RF1u6E-VfF-V@%koo{$w3|`PXg}&)$`C+E|qGZWOpIOk*OHTFEoML7?gUA!Tj1Zch5#pPF$BR50BX zl|=OJ)SrX;`v>gP*^4%F_wSoc`>j)h(=)YaaZmr*%ZXol(HavY`1+0?(xg?S{~U<~ z@r0a}{Ie1>q{HZhbM?mC4Jw@CPGmkD2$I=irwKoe^xn(PWh( zA{r;q%=tWLHrNi2mgA=|L- zitm$?GU;OYk|}{E$Efb2_S;=p;+b6qzxgtqZ*CaL&571P$aBW;#HyGIJ5c=sJA<-X zz=PsW;b_zp4bX?oLLWJk8#R-|0SuZb+fnVD2Q2_f%Gk46jhdzaSo~wJ=Z+su5y36% z8-b+k7`E(}jd-}2V~rOwmE3rdq%qtu95R3lDl(2md#}YGZP^G+Qmlo~lyJ~5!3p5f zn>|7>Edv@V!AEwR2mYWhRbaYAm9)w|{E6dZuuz_x2%<)!C%5_b{`}+P?RWgR(t{q= zioc4*q1{=#(7aFvd@p0qedW{4X3YZ*2JkZDeV9S!a)2Nyg_`?(Vl2F1(%rcWTB$ts zbk>WjRGox|RnDl~GgvE)=cfZ!G`u>fGShc_!J?W9!20~zR#tK{sc8xFij`OxWCG&dc7Y-{uU`nrOSqlw_qsYMYi z?On!>T$h>N%;!O99Sbe_7>JV-)(#^$X+u0a95lrs|HLJ;9`bxXYlTd%+vgu8-P7$d zuYPx~%f>g4!lO<552-vzTJHZ-l%#)uvXIL~rUyY%#0Z>lZupx-BLe!l#r7>_v`{Lr zfvcY?GPu!Dl5ZaBjX_!A_qgtCLz7%L$%x)%!BB{;69s}pQX85F|BO@^vWLU@A5i0V z&+EtD~H|N_uVV@d$c9+f*)rG zzgVZ$Q2u@O@xDSBmn5?l6afai9^dez!#>^}&MGOeB=&`1VZQ9%|0RvGj3ZYEd|0}9 zv3|4X&DH|wao%AA6X%J|#eebguF$xPLdC=kpu>DYw`n$K?kW+r31@T z%#9IMQ76>MH-nQ3^Pc#K73AM@qm-i!;2mSnc~I~3Afkegir-B50ziti^uieK z3i|E|zxSRT{Z=#xL@cDtfbW@gi&L6@rlLI>PHNuxI{hbeVGw7tp(6!iZvL4Rn zQasgs@UzUsv%Zf2*K6ysIr>ZY5u+juFgnR4IiN1^Q&yU+wDAe+L7%W5@Chpnu&V&Ez0Q zT)h}xFdtE4fq59w;K4XO8hPcDMR;d7iPkjgSgfIbM==PkVd%}lp8gVsz47xi1r#FI zzXXWZeV{=`dmN9Q|AfFo>1^miJ9NJ^tr>AH(4lo)EKr9=TMe+xxmi2%ms$=uhi-<| z(vc~#Ss}LTMt%%M$ONT_YjpJr@P^9J>+2*VSYSX&p(UedeQA5pC$<~GAwIB$TrQYR zjCdP^GwEPgoKo7nz3l#Y#%p(*!5?7qJRJ_3-6p{esK{!_9Hf2rmk%$l%hn6 zK%>&&G>M>jI;o-@^-f5vxMTWdPCW5^*8PmpC@l=m&-RR|!?>nee#Q;F8-XN_OTmg$ zhJ5yWbirJK-z`d&smK)ZW{d<7pV0d@y#?fVeROHGl zS*}dsgL(;qMsa+413k00>-T;9`qPad?m(r;&iw8BGk;9hY|f2LHb?Iezy^eD3fO^4dJ7W(k73JRD}{! zsvc`(?L(*$QzUViTv5?aJW5!6dM^2+>>!2A6acgw!j`ICx!A#x|8n z*L#RVAO6BkxVR-%bf;<~=TYZb{x_4iS)B_}vfc-87On>FQk23xEcpdjqULE<8b%x~ zHM}$&9)vEdyf4k*{=t+mXH{1g<%3I|BBXS82ObwH%QZ>oT>8*exeY}d8jm1*9dPEl z5~=c|oSO$ueW@5B>U^JA4XKox(^{FGde47J{>b^}lF-uw&qxS8iZ~JsI+XfoY^4)D zgXq1|$r*pOX`za*Q!jAoB7D6e4H6nvQc=|m@<>S^KY$#BV3ELpWNmt8v^lTe?M{pTS zW%JNh;?>M)XceOWy75BOqY!S+U8Ep$S8pR0rJcuf=V=yI7)syNdB=X4GG<)h3+Dj4@CSW) zv~b^9y6lqo?x0~4Rz7-FN^S8lO9@#y{L}|_o616`n){hz;1Z2o*2xz6m?Hp{&}Y}y z^mVO21Tj6YPc3JJF1Mj*Llf;9JXaPZlGWiviKEN4_En*T6rt<9iNLqAh(ME(CUpAFN zmq&jJAnyG?s2t8wF=pLcDC({5vvw?>^HKwr!2w)_cJz+_=pFyDcKpYO-u{@9APb7L zox8N^?_aAxNXat@I)#&T^he_KLG5X(f)bYJh&<08pg-up5?iyl|C;Mcr}E-LIPl>4`<6lYu${TCszT$Pf-&q=4vfRo zI(YOv*WxIfdcs9i?c7<_m7qcN6iJv2&KNdtukZYp)3a2X0Y6ord?a;IYly*yAH9HD z=%Q^OcyT@s(PBzEM0e`6U3ipd(TY^)a=06O1z4$TOXxg=f7iww{K3MXxGh~Z5g}=- zN-jHX5jY7=6QOp?z~J0l;e+%KyCqbQ3N&)yywm{P_0fYo4k1?`npp5Rqcs}DuSRbc z>RL@DNfqwcN7BI}sbifTk*e&q_ak&!y;6ytpMh_p=h`Fl*ToNo?1lYi|@${PIk`r~88cc`w5JWRDHxIei0PvS$Q z^YKz+sFiC<{X^N>DsEnctaV$ks`I>w`%|5C8P{f^RqM&&{W9TmK0HJ-dm=$XBmXn| zQGle-`pP|MsHeav-ubMc=W`0V4MiK8Qi=ONNBx}7OF-wSi=>hG_)+rB!WrDu99``! zblhC7l-kbSbwkJ=|5TU5!xTk2HD_(^ZXWo9zJ0V{7AK!tPd>GtG>3K;Q7zNyDQ405 zIqqnI&LdBY^k8?bE~jubRZT1f{5QUX%~2p#c(hR+owU^`bU9U4Gf?L)lP`OR$$Mzu zupl-;QiK^D)3kKe@mt@O&NaN4PlAs@TvMjNrEIK)cwa1fuT)7BtGeQJ)(pZ~)%Ewv zeQ2w^>Zb=sQ`J|;i>9rrw(4?arP|gsMd@Vi#W@L$hr-_=e{dKDVFP$qU4vHl@k=g? zRGj-huhZ$D(!KI{JkI-S<3w4mzi(az@4-!&!pU_ZDxMr`eldTCO>PO;V0F8b1}j zS!igd^gtt?NWJIW^L%e=Io_M481E6?-G_c-5ra2@YpwrQXVj2M7L`1DeY}p#Lh(8( zc5S%P85Dd`JARQd7~DiA3%KFCVb;n~6>;>M7aJL>T6|T?Bdj<(q!9@xhDIJ3GPC$F zc+rm0<&>rwU8-(U@Ou;t)UD&_%&4Pd1io2@MrQ*qe9D)(4ke;$Q1~Wh%or!lvk-Dm zN#YG8+@FXFSUN~-Wm6kCozr8=dit}|Ok%wlD z+LV;9v(3%h3xBLt+72On(O?eFqt7k5%jbsDA)-KDEk%57nDrVbYkxfZt_8LMi`0%h zyE5iM(mb)4h8u5cvEcrD_3{FHzLjS}u+!zJas2K5?RE9>jI-R(dPKUhLP*6W%3CBf z)sAzKV9*sQiln($-;lFj+Y(t>$W;48Ik~8G(W)dMlyFd_DHm+^sTD5b^dQynA^E;^ z&fU@HBSx>v6p4Zz_cb$ltbS%rYlJSVRyhk@t~wPcXpK$$UOC%frJ?nLaO&E{7lT#t zsw>QeS5nvliuc-#<&3J&7RvrkKO4LWeACYdLyzC?@~sp80IQQb9WzEP#t;fiL^;*= z;#64M3~xGNGEF+ILZk~e%^AnJX_B+lfRihfbrHJfW;M>kZ*(diraR(smYK1c$t(&X z>&^Z=&^X{AuA|EgeBcKOjxH~ROTS7NZS^X`c=<}|CtpX+KJEbr9~LVAhlTr3iYHQR z=wDSn%RDUE>T6&=FQ4PPPO`h~@;zjpXmyI?jth0| zZLCf_U=PcKVn`P~v*)kslw~-Sy z-`TGV!5Q#jP>>~nN%Tn-Fb5VV#T~fpCCw=n&9|TLAJTXjQ%&qt(>gt*Zo+Tkg}U6` zWeLk)98QB(oqLEJO}YxDGU~i6CY=|YvzO__%>mjJ%}Q`%3lxR(nYn1ALTK`&0I@($ zzukdr^4%nnB8H3uSck4`wVL{0eTVGoUj_!DMi2U`L|>I+pO6B256riLukL#C0H+9N z@(vOAHp~lg@1Qy+3juJ$zq)#Yq!dK6uA6=(eiKBR;))Woc35uA>rFYhY0hy&L%x+> z4uJ5CR}p_7bb5^lABYAq+_C+`UiO!1egWC$%v7!arSFx9Ik1SRc%7I=H6Sqs?D;?pFT-6wzT+evnUf&_72{4I7e z>W@dlmzPE2X~kSMQ&V)5!-K(-#8w4oBSbek>BVR`GYQr2Ov#;Yx^b$ zG-sNaJqjeXF19WgVfdcHwxR2l|eVs zob?G08CxSSTZj22Q>qI*r;E6!Hu1RN2AX(@_68nD#4zu%;3iJQtxly|9gj(6xKs9M+P!} ziSO0DXwAKGr0l_8XrNATF`P%%gb{ZfnNTDYae39eM>vV7@;eEQJL)|k4Fq7inktX- zjeDH<2sH~ANJ^phU~e>-k6SCA9K%K6Ku3ePvQ9kso#4VLu!wj*M5rl}We;=4oE&BV z1DpBF*+h55mWU4(%}`UGIpWyz=prw;7mPWa*#*Kp_&wo5mJi6}HcHjZ+B>`Lq!1Jx zKi(iPU>n>pEAV^P{Fb$R$Aw`Ki7xj%qabcx@!iP0?+(_HkW4V4KYa1LzN5>?x|ll| zBq-{Q_4*B!H8sXH?e55YGt=Ln|P zJ!wKFnvCB3w#&KaEa{2gn9lm9an{8*Sc1%h>WHJu3~KOWPBXS=4zsl=`#0SCXL!c= zBX2b{Bz#ZK$XT%2Qd+ukA`7q-H1AKo@6Fc zhV3GbJ$kP`$4!TNyRIQpk(1DB#Zd@jVc@oyo>S4)lF=EwQPn`f|pP6A(Rhama#9Iliq#{Y~Jn}MXTb&ny!+KIC{elvZ!E;ZM zzjP-Y0%eOvvH-4Dv6aR158ClDar!Q|ST+bu;%u1!OKY~QbVQ_hA-EB5W+yRg;{!PI znd#$pfh6S$EVV&$CB>OgPaFA1N_c~}!Nto_1TIpjDHzs z{*V0uo@LPd0|qQ!@c&H1O~D^D>4Nl+XPE&&SWqvuYvG6SDMF7?Q7TS)lu!3s{nA># z-Fx`=06YW*;bBA|9?JdDb9B77ypHXD=6Y8z8Escq18wzy~v25moi`v(v+ z1laNio(08x=@$RC!3v<&MQJ$*?45*Wa#mflxzJg56@g1vAw3sp`$Pi@{1WuQr0dHk zp=|CJUe5^3L?ts3@2s(`Zf)Le|5$Zx4a$KzKI6{K7Pnb%VYK|A;1+8XGX^LJGWO!z zAg;NKa7GTs)ZiVmIg|>YruGfkg$BcxVWZPl0BNKz_(9&boTqMzHZ4 zh>W(ntC|opN@HMP@^DlrdWrz_y4%XCj`0%DD~S;y06FKrmhTlBdgQ zKw3B>0ieEZiLk|~S%+?6NXzPdyW&SgEf#TG7%jgr*wSqM!rhi18*FK|`Urh1X!Xs( zb^&HAe}R9IrNKb~era{RtxP)kV)FBshx`@ReI<8-*JLLxe};qR$SRe?y{>+1b+y*E zLguY?Ep{1UC|wgDR@N_3LG~~v1i;c}`Eyzao}^b^gl(hV-w_T_Q2fW_7;>V~((IM` zi9y*+4Mt+yob|g*+aRH#!vO)pBxhK$ElEbW;0B&;;Ogh^-=1Gh_6;T@W~~1B)12P$ zKhRJnZU_Kwkdk}P-;CM@X))*D!f2aczt~0zl3GVL6eA4K+}PjF2s%F)VCXvpCVhf` zlA*c~fp7h=PP^d}ineR1})z z^mX9Rq!E>*1FK8Y^{$R|h&KuXl+U&@>Es7n7F$P`H6$eeN@5$tHMx=+z(S^AEVTH1 zV*3xq#R4n`zUe2EKmU{h6t6=Kas7@!q951-<&`->WXtd@3iiD_-tunhFQ~rAl$|dx zwlDhPB`1z;5ZNp#bSgC_En7#I+aTe>Gdmubvk{Y5!^}st4HB9*m?tbJw+kBun-e~y z-v+TYy*|tf(pFZ2DZn;9(`y+p9VY#HYs=paH1kY@^kQ+C0mdRu7d_Z?VGG6PSDR#0 zn2S$Ny$G!%S^}|%C?TJmlMv)^W>4{nn*#;X^F~17%%}4idw`$^6aqn#s&Fvr1-=o%iYz7$iEC ztw2)im>r8-yK7I8YT~CVBH$dM58af4c=ZC4eEJQ~RVh@51oed{(bqDSqa$(R0BWe^{wu9N8eYj&16^FuMqi z(+`-;fOQb#o;||z@890m>=Ys&%EMm`3{|MVbf{sQ`i8%bX{z394qnfR!##jQEAC{QWPzfDpEPBS58rnm?;0Y`9gXA2;5*jUGv0pp{Uc>KtlDRt zh~B$3h3Ox>eGoi}Aw7oR?c=Oo4#2t4Bav^6Yw$O)&hc7tK_T)n4Qd7h*R6@~lC}a# z6GbY|TtcLQ;_mhJ`3I#tg+JkbWPXNGSpjZn^WR6i#PNkMt^M9Ha6gwJx?U7nd0reUC>Kt_VjVQUwlo<)9j%Z%DU}E~lzV zRj2%tmar|ZiGU_4_>0EdAfah13s+U8BVgMH{-Af)ci9uzM>JNTKdh^IdtJdrVjBVY zoN$PbdX0+TSm_s@*BL0scSebMqv=&}bM)U^!$62rAKmE0xeCBTl2 zTIg~ciZ(Pk2wNXmMB0^zvzt61kI+yHwn`T*hlO$(eU6Clt}0Qx4JO^)HFwFA8asYE zMm9i~JshySuoBeM9n`gM?VkZ{ANYeFj_{cH@?iT2w9(9K(#%UlF-$3f0^^82`Q$#n z>;TLD^~}QmX%i{XIXJ}Ztm@iD*)H0}QzVCV&c`Kw9-=A0q&N55Akr+J7!bd)*gCrG z>2xY*y1`v7^J$f0GNEDWDaqUO0hf&XniulHw9^4Qt)iujATQCS7rj zyO|u=GMu#OW&)Ntx4qw=ZnWiFdn33XwZ+j1GvN4dZUPcIj&=WJ+C$#jEV0wR`2&nl zwpc!MPG$ycbLT(e6r?39Fwcpxb=XsH0}DbMXjq^Byh4B9HJ1iQDM09hclz=0SIb{O zfr(P+4ksAhMOzcNWqtd+`4@ED`h^A$!Qc+%#IrW>wT`K$JFX^f@hm$8+j8$+m#Uiv zd2B^d^E<==dptM}a5_oSvj1UO>-|WV;3tX69k*S(G#lrQeF81iD=iDZS2{UkJ?M1L zAJFpMCuiJF2-c;t2nxqh3H z3!Wx<8+^U$tT?+1r>bF^kDAsAJ)oeWRj_o0q-yNzeANoX_qaxIIVBFq;65`)Dl8`| z6*2}f^F%}2oh?38?Q?8}1%pTQi|AT{ghpvIK6^=tVq#J${WIjVJHPNLp80M?NH;JL z1}iqYZ1vXasCoBLtDahg!>|U2ft77=M}wXVy3UR-hXpCn>h3eHzuWzQW`OiccK_8- z`1AjwQC{$WQ4Hu!*zI-vCSog)#Pr375zbf|Iqm^*I?YVO|KisOmwk^DY2=r41Vil+ z_?)T3ufouzjvCe^G)<^V{BkKN3!T_HQF2SuVw7o)ve(W_htCACab#rRb-oI47%)X~ z!g%nij2rl*IAiYN=QFLb#YEtELMF#WePi(-SKn49ohE->*{6YhUcn`q&h%sMTe6Z| zrIsD6#YG)>kr>1?%OBm`I=URr6%_CEaa0Q9?w*B~;3E2go^54S*H)@JThMd6w)1*l zqBmb?`@rRdSB(dWu!4u)osOOlT{0X@mbw#jTUph)H7{taOeI=*)pJEI0Uz&g|Nm%I zxP#v}NE-J8NSM?G1&tV7=e}WUm}9A#%wFAx^|&?p^GOq z(o)aFlXFWgNN6IctA0K#xQ5J9PoIZ=aiKtUz;#<$)p@w&#c7%<>BGBsK0)PlE{c6- z2U1$j%N<=-_^n?M;8Z58<*aJY=gL_ghp;#Z#e0^=d16o ze0E3Fxou@tM~9zt_F<_UM{kNG4DXon(qbDVG;LM2Rp(hGFJbSQBK1z`ICrW=BB5!k zs;#1fgLfwV z7>iG;>O167?E^{dmyF8bZg zZDmz=_P7?QxU(;*am02TBs9uS(5h9(vRql|LaIcGNjX$8tUIeZ)$ht(?}dyF6mO}Q z-W$J2Xxhp+^#}D__DvmaF1?;S(dF7QR5fj7 zYP#5g_MT+fj9z$gK_SgkIP`F4pL>!-c%D@L^n=fE|2=E{DWW=&Q}3N?9`_8l@C7vR z>|LF+s%sZzyJ+(REtPvuUU}&6VzH76>K^@elI;Vh-X{^IyVNqW`H~lin1>Ah=%R)_ z_Ie;9p=m2C)l%-~rj>h=(9r$FGnK+3qHcYPL0vZ;ZG%WN3CrX*zL*yC@fDZFD6e8o z%qSnkvdA6)xtI7Nz^3aR*z*%zt_2Cf!|`8Wb6T84H#c>Bz3oK1CJPU}+D6dOdRT+D zgT%CMUigDvRjo?p(XJ*(dx=3p<6fOET2D;fa9nt)La;9E`QzGc{7}g@NN6Ggecl^1 z9wl#ONe{GLcum4wrb@yi&h2jIsI7n=rsB!;7$iIFeBAD&?w8h^ZzjN8* z$4XlMz=)aH+uS<5Z9d-KU0GZ!N13HC9%dy1Q$;iRpJ~7u$`%F~9HH+UEj9Yz^9MP% zZ@;Z~@9%%Y=96uC#CG7Q?LhqS&lW~IcE^c;?9e2qYJz4WgY&-G^O*Ja?Payzy}YcR zc#ka%wi96wmpvT4K!u<1X#J-<4E2*@tuO6;T_213vDdj(#}>bD(ej5aw)_zZTt3nA zha9>CRR1Ih;NiWBI14=(b;%*+N|bBt65@+KF-6tsMt1};@p#7D_va6u&Ol#_e{9C# z=94`asc)~Vm-XuT74}}g#{o$k4!(zk&-}I{Q^8mVI*~lc0S+26Hg21o{wl>1h*KqvqHvzxIg6ayYD4kGK2xn@M|J~}lG#=iWnIXDU zWg|7`2<#kzor`35I7yQ0%oNbwk@nmKCn%;;WQL@8ec|WCoiM1v;FT6U4ZJ(W7v)}x zTB0Y?9-&| z=ga+`zOAFl`cy*L27ynGuDY<#nhky!!6pyyHpaZUL`N7LU?NI_N#g~AndFLh9QFPu z^M*CNRfZcn(Ty-IKN6?Obm;0dp+e9bq-~JU$SdRbkoEbPh->ZyW{8h@^j2k^9K&Xb za_OB%72|l-qhp6ev-R)1+xUvR)#uw=!T+P{UANoDl`Y)=ek*q(SxRKPkCXITz5mG; zB{8N*H7|CQr$05nS)kl|pEHJ3b5`L(5ClLF1OaJ7W$@J%@vTABmfu7$xxNZ0mLl>t z9T1j^dE)dY)eg;N$Pd1yxLQp4E-(n*DfttnYNAwyq-ji&&U)#Sm^ulDWs1cqIlB0A zygc*7-(U7!2il|jO!0fx-$JK2y~wlVAn@mPiMz?0s(&u{hconDW0g2I)Rf1kZ{t;@nmA&NX^r}!2KfW$dwUczB-3L4j_)yZC z7v0MPGt~xud&KgAVX%{CbBg33)uj47<<#h$mXdQ)_=5T~ayJc{VKJG~=V8f# r zFGJ|p4WJs>*LducUA2K%%7h$1hwKc~F#k2@Vh zmKWC0-!a|G)4Pz>&Elrg?v)FPiXIU%E)K}m2^KLTrNCSLi9(z5syWk#UyH56_qO+q z^bogC8&nlmp<1X#qV+g8NFGk#$uE%pR&23#g8CK_C*+UE#)9cu<(KT@NTPv_I!8%( z)f!6@#-n2xkN-^S(_Co=#`S&s>xOY(5BW;!V*16EB+(C!bI7xbM)k@rK&EkUHig@w zh^VF8Gt6k(m7gR4%2dbB&KZj)kc8Km~At9!?w4JW<#JDR&E}ipN>vdJXBb2dv(O_VW z53x8}V)ZiV9}W?XcG+F%J^x~NP|^B)cl&gscY`uiH-1YfNngFQ^Wv@T;6UGqvSa#VXh zS6UdXDIA2HhB?i9f4{Uyw64XYXQmPpJZC)ytq-J}2}}t)qY_o^86yuEIyH#ZL6IFC z&mryvi-R6f7k7(z-mcG((LqCh=#XXm3K{DH2y?nv02pR+3W9(rL`skDRaeHH1+Q-TVjX9|J?qu_`bT) z4S|nT79kW*G^N@FmA8V)bo@a+R5U`ov!89Kb=yHkZ3*meZx%>>fD?C}h1%H7-@-w= zyjJ=>Z+)+Ur6D>Q`BHviu%^?547NvNGKWGN2EAHHb|{Ptg(2f*REcP@MNWb1&N9bXh0p@e44dB+idOF#Q^!6bikKoX#m7PSL(EPG|1syh~9_A)>01h^~V~p&n|bij6Y>4Gt!mG-Rj^1-grZ3apWS zNS`Egq)?*h*kQ3c`MZ`yA{!7m0+#16;y$|YoO2-#F%CRl%$=)As*^v>F;cM$8^ugdO)W$P`Dy7k0XhjpTylDB1g~3{y3-hBol&($z_Qg+9WMQ zA!^{-vR^%nj{<|`ewmAz&9;y38G#iNs{lQR&J}nUGn;L@E3H2Aez+=`pt8~}l=%*e zna#G{m6i-c%Q$lRJ2CC~o>s4rr5SIFRuSaFErn!6xu%IoZ~e_hheZ?&96!jLYGfhB zG88uTS7xJwVRlj2IcYn0(_4QI9vF8}q(gM?u8|8-+=D-S{w6jle}CsfU_ZZ-Np`ZD`9I|x&|LM;9JsDhJ-FB%Ts#gGxJ9D% zazPHI%pJC!TAV|bPMSn7^~T$2iroc znR*%Kh@_clD1~m74!u`qG8+pP%Ue}&HV_YEUWw_HD=a3>^A^pUNqd-hP-^SLX`A3R zB`fdMR+X*VlF-il)O`yHPgibQLLcU*K7d*nDvfaJDZ3DbXnf>`8NiFg)OW=&`^eG< zClUU-Te;b2V6|#b)gXI*h!0T$+wPeIgNnC{4%c%*_0b<5|G0h9$M02})3Jl}8Qkxr zMD$)|VF>MATx%n}fO}B z9BVy9M5tq*6@!l-4XCfm!=c^M)`7c1xQg+_Ug=Ih&{hs^D~E|z43OsbZ^rlS=)7X# zJ;A90ycc?53af3BIJED8PIkH7&38Ag=-#`{D%62#9rC_6&OMG~C5~~MjYHv7xK!o? zNXat%K_lX0zvWVzIND>+3#G-(*lMOqn1j|Y&c+~lq}r!j^OgLf5LtLgFcPteEg4uV zTQ3aO?30DAwS3c7AI3piZXRQ~rScIwjh{@7cXM8zQlXT96 zo-vyw7n5>7(vnjl-)k<)TRixwdkIz;+U|rwm^w*-Db}~zI(=(_jcyd)$`gGswE~O8 z)H7tcf^W2#Z@MmTW-ih3+^I*6_T4!iB=Rhx?`{_7Xbcz}NHUnI+4^&}_@+kdT(dss z!l+w{*^s0jPU)yn=UVI5O(!#K;q9Pzl9hHd*Eu9}VFwknn85O)qw#H5N%fO{#vP5d-4M{bj5j zQSe{jnf5q{P%2@Au%SQLd$CqwueP** zp`FqUi1CK4FnoXO=kHw>J@X*KLbmjt5yO~-|GHgOd`PuGR{1qmRX(Pw%C}UNzl92T zKk6gEAQ#EKrn#t-0)0<#(0!)+utm5j>eaV-F^lpjA;t+EdSW>6+RUidsMjVR33W0J z^#?l31d1>m84>O2O7xA!at$&zT7lZY;ZHfrn}lXoyB6y<+Mm?dQw!63mbgAUVh}Al zJ2#7`$EYX$d0X#pyvIjnLKJ5$PP6Fsj#0xsi>E)=4|l8EdpR+|LAL}-Ss(Y1UV5+Y zo?$R5qMkPci9T`OnhPvBp1bqC8U-4Wo)H8jTnG*0=EHUSxeU*MVXM#*{!T+e6aId+IcpLRnG4NgENWE9v7V`WaH!p;S{q<_V zPe;9ORPP1bLU8eg9J{mS*?BYeHHJw;_WhJ&%T?JCs1U`#tVNM`E_Wa!qJSe4!-IxN z3t~7j^k?KUz%rEfs!|I!ys#|l;@hX&O|+@f)t{06u!t)Wy6zXi4rj(CjLJe-n>YxM zdLJ-&Ga;>x@ItGUMEB+^_2LQKQfH=B4QSH_wjBe|m?4#jUE!l`QsFns+n_5aiQ*wF z&z>d~$wCA%tco!srjYOxClJRQU-Z`()mzyr{h6t{NK9RMFP0v-+05ko z;^|@i{qJSZFMj;FdU|9r z)I~JTXBzBa-Fa(>sNPiJ7jIPe0QCd@x_$U22fxTL#@TA1Oizdmq9guVJfM>8Z_EK; zP~DO@a^Ad<3H1xaUyCQyX6uC%79aQryQAuPCA=u4;?ukZ63*R1WIUGe7mu}946487 z$LECrIL-Q=xUH3FR`=k*LgOTOPFVv}aQD#fg~H(KzC>qdJeNWjef( z++t>FQ~!Hrv#rGB+f>jQG_xw0wQY;*bEy?;TBWTTK`2f4bd?JB^e1)8t3H5i! zkkZCoIVZ36;PBC%PE+fJ@IGo8rq3af!U!^Xi_n=7VWG07bb~9#IdDq>c8TdvZ=n;> zGv7_BU)n3)F&85{Vo+I!h2$}dp;I&RB5keLqVIzhnNE^$cn;Lww?*7g^PPC7Ir)`x9| zax5=45lH-g&-W{xbdN>`hngwUj&te@JIywEIrG?Hu;zY7p_f9lB3=0h`;ZDnK1o{o z;^s0^m~BH^>?gm?EY!&|n{CY{`J9~6K`eA~WDSlqRp+@OIlBd6?K@zo3fkT~35zSe zT6Z6K@qH?ZGu<{Hl=PGCIiu!R^av>Z=enP>QJu7f!P+j-0s2gQ!#GpmwYs01jtdRq zyQAs?)^@2TW?bdN$D~x3U}L`IoB8aAPs?D1FxMk7btl~KAODSR($8f=ZJ{)^P+wTF zjDKp}?3}&ZZ?J##p*N`;rv1JtO(3)Vwd&BxyR=>@HI<@o!-5v@)cdOOj^Iy4#Vx1DbFAE z)R>72d)wwbky)`rb*c*bf+kDbFq7gv$BB}cAdK?-n&`EK%xiYhlfcC@&n{7OG#YZL zo*7Rv;_%EvtP#Ubf>XTYlWt(WOF$hIo0+HVTu9<2x`F%c&2sy!XM^SKAM4wPFz9}X z90Yy+BJj&}7K>g=c)e&=iME1ct(+1P#f#UfVFl^R`|9Ls(gHkM==>9PHTI>eYvN#P z7!0VnMwFR?>?*_#8C5*sfJw(&8muYQ;n0lJr_uft9VCRg3d&M#lofddGGi+%<>&1yp%k3`>Xv1iA;MmOMUw%X%vCq@2dmp*? zf%|gYz8toOSmx+6?fMjq)MBJDYk@jn)z-h|W5(z(c z-Oh|0HKJ#p7KV+K4_X%tbQ=8q_40P}?fdO=Ia>(gveV=XAC_8QN_e&PB{I0C1gShE zDHhib6MPJ3yvHk%2iOQbw*^Y!a~>&L5Q;+__Bza{e42A#IV z?ZX|a>lUxR?2aU6qHdj2#{HHSpk-$1Zf3-)dJ1=Xt#xw%8PD-o{weEvH(=V1 zK4Adk*ju-Ky)FXHTW_yiq(0rKftu1qaZFMyc=1{ECxpYEj*O1&Su-nSPkHQ_7HM>} z9)~DoOJO!A*f{wz@uwOs6I0Jd9PXHMN3Q*r7KEb=Zn*}%q+S}V&1U&_xFK%e`^c`_Z{Hs`%LNV!tYgW$`lCxEoGUacd&dg4gYMu1oX|%Oow*~(k^{XY z8Pba#x)W-5N^K6&k>g{1$YW-0bu#nVKF2Yq7{NH(G0%3Ks3en`s;XC6ojx>M%iABD zPmd3OJgq<9DGPLmN4l-lEh`Tiw3!((He+f!t7)L|(N_oto_E8p_};h?h9t=#Ss_NE z-@1gUPsq}c^pZ5gPuB=0m+-QRrfLRiISPdidv?VMl_y6tnIZJD@dIBkjC>kaQxLd=1i^jr?ocsy>; zy!gE~aM)|TvVC=AfCnc{uUI1Yxr+nn80bc@Jp$vRNfk_uC=FVwQ^sJ;m&+Zs$v@vczAH9r4h&Ts(bVKiocimYWXJEARR!2wTwCE^q`cmYS5c z*8)D25uXB}=cn zmPk06Q_UoCd9G*}~cH)e_GXRBwl0mTt2lbVwRIvLnF7Cpl(4QLl@ z3ZeY>E4x6|3S@BQ3|E{vo&ee0x>2q@op+g-`T!X)!*#{gCNW+k=^Kj|fLR*YMhMv! zM-Et-`=|#BJYX%4%G$?5Uo)@svG?-8OM^8ZUxXM+IynE9M38KMaJ8?s(11WEKxyf# zXXYpDYnK)uFFMX$|2h*)PMNJI1wX;Ij1-A+&iyIRR&xcGNO+UkNcu^Ark#{80UA>? z^@6W#{cW{)x?en`tK+E*{^(5X)MY*n$8*Oi4Q+Q)qDVycG-`SnHRG5!T90+9Cu~+P zdKc2+skwjFW1d^~PiyKO!AH#Wl?`aUwcgs0ekfP&wJxvt`<;%>a{c{({&TCh340BD zE+zJSroXR$h5AcK{J(5-biW+SFQ3vc_j>AIK(jp{m`)?2Ha1|8RAq1Y@=|b-JXMxE zLz^%d6Li$ZYbq@Ng{t_=&F%e~SJF$(ZRQF!n!hT^{a6ddhv+j&!_m^Qd%#kJ5Tjxl zVGRk)>~HAeHGVQ!CZ_Jh`(feAhT<(#H0Dg8q;)l0^L*x;pXuyOGT5>*JiX;9|D_WS z``d)ScLeko!Z}IMi_>>;+*R}_^x2$lGWu-p(*mP{dT+hG8qZ~u>|m@A3S+RgOC&Jo zUX@d*d;;nfE|GBZ&WK+8a&A>#*vcakjV4Jik#HX^FIym&r^@Bj%(4zP>JLZ+`{eu zx}{;>!Y)aMD3{2q_82cWWj^3CQF?phxq-`O;thVB-jBs*qvpGrTv8^yPMp(JQa*Wf zgC!XwhbdJ02387Qz+++#u5&^qs{hx7u z@dir!Zb!GE6Ro%(brnsb#;%qOV0DV-a8LwlQoFc7@!xl~{tbdC>9FXlhnqFJGxflu8JZT>-Oj7yi*0$%Hq#FYpPx?sa##xh+%88m4;m~ zQ|tnd+kR)}qjbzXTPCL7TnfvBcIsRrS+Qg(A;!BMNOvh{KV)Aw?u&;!+UU~JjV>PD zsA-WyGN=B0c3RJ8rwC`|O#C08#5L7;X|TpM=%2UB3&2{HD&Ck%yi81;n@Msm>=OZEp~OJsigwKSm7c@l9y)E|E>4bprkFu9nJn?2F& zlr5i8o%&se;%e8k65XD)=J$GAyOa=PjOtNUFUG1WSb`2!33Tkzv+uEDuiPWW&a~T|-+39V@d9XOvn@}=EqWovGR&qt z^k^KbjO<^eq;zEbXgM=%M}5tO?da8;u(}ORBFkGIokb;*IAz7WF}2YVydr#Z+@|`bbB3Sf{{-WfEpVG~ z+7d}thLbPd$uAj#l%|Cnc|T8b`@pXbeWtyy3J~?6>FCSE)Z1cci$y~X!F(g_2hB@g zCR$Gq5Lu`s%R;bpd4Tt9vrUR6GjVv&mTX{(`Ld9G=jxXw&xezIeEPn!<;9=7VsA>t z9gqE$zSkujOg1gpGH2SD575bmWEaBJt=3H7fa`}po^HR{mECp3+W9zrUX`+t@t%d( zC8u^e#%A(or9sqSQ70z`F%4*xIlnaN=DIr3U7BbWLYB)> z=wQdWI8WJZOdhr}re(BbIz$o&;Q~w~hA{o`;`?S{d$wC~`dz$$&_-O}8w5vu&xZJ& zrdJ(R1|+&)9_Y_mKKrsZ_I_R%d#`W$b=wd0gJKdp@j1-CXJA572NQZq2`sL2V(5As zkA&~6?5q*M9sR&Fw*hyJmQOrpFY|lD>xbtbK0E*SN9MCOI8?o@k_?V*{jK1q+v1`% z)UI@xc{h7~w2g-VKJi^5;VL&m*L$(1ZfBAKnv2opNV!+P5R8}b#)ceI86L+@#*u2$ zZ>OClcaNN9wMP>XMddbCz{nO&};z^t5>8pG> zR5$)ke~HX*^{wdzv5`nC7Pf+1@W44&ejW?VQ?JsKd33s4AL6$U_q%9AqDr=iPsW5K zGeE3|_El;pE+ece%pc%Kxc0Y8W)PC)wSfx3Mb9BfIG5FEeJD7Z_$>XfA}r^pYg6IJ zw|kz&mwqDGrFAy?R@;}mde5vB%cu42lP!ZyfL38PC?3BCF<7e#$@t2e7D@BHxs^)> zCF%JVB{6li4$CXOTJ3|Y=H5OBUxW- zsIjkekgu=bI?t)ocuYuD8U}y*PVsjKK2-@)55tzq8PAleZj@k&r^kD#l!VUH(1b3? zCPOlkoCFImM?~;Jspy8HD2IW4Plv-Dm@2B#Eh$gMDr|nZ9_2!5?p&Cw*@k(!v3l zE(H+A(=!rN_o#&V)k0PWYcxV^4J7u{biHP9NU~NcsK$lcLoGS#X z4|Wz;p#B-*0@yz@bR6V`wDcT`Jvr|T);MY*w@;rJ4~wVSX_(!Hv?cz=p)P^eB~a(s z4Vq*>ZDA}R6h7~sZvR@yry7m`)-XyWYTXYHnp;U>?A^gXYI>ET(agm8*pEJ8Em5O#M9lQF0$yDohcm(jcE}4bxU%KCwuh|}Fjj~i zQn8N2L$h{gux2kuC*p2qha7l3Xzho(N}^;g(Dw3h(~^HDka+b0Sf zG>mbGUVn%x9;5D3?xQ0bhtss1gJ<0c2$*=#mk?X2wHD_)XwKK6I_Y2Q>ghA@T!E{s zv~{R_6TC}IU2R>Jw?z|fs8@P5D~Ciob`mn>;H`TYcZH;-gQ$Hm95k(LMD4cHy6Kky z3zNPM;8EMLCeI^j^3m-)_e_4w_(Eh>7#;$yIvh^~$w zw@gVgv-Zuge!k(QbbpiYrVY4M2wW-*BmvM7^!}uvFHo%&mhX@U?d~8EX+R!og*`Og zm>>scVBKgbe}$0>Rs9iyjy@43soRoJ& zCjA}L@5*v#kZr0EJ0ynjrY!0R>Q-iSR8XHBG)|}0Jo}vyPp!Hzt>#JHA%njo$nr$# zEiycGH+ONf>r_oTKG_qjm}Bu?W}})QZiVR zHR@!igSbWthM`~u%a^z_!^lh6L=3)Nc4at9>>_#UN@yj|e(6wlSRI`%S67)S$tvu3 z-F_d{;c>Z)+Nzlye#do(Jm`tyj>ifwa(!p8#*v%daMBBwndunKA(FEd@$(Y|Pf;hE zZDLK*^o&_VUq_?)7=82gM*8n9*D0`3c0c^j?M4|+W~Ruw`G=Vf@QfS6sgh53U1nG0 zm+t3@(#g%ckEGYls&iI$yLZiWHqj#}q%DXS^#hS`@9lTcG>e0#Cuo|AQmknO(~HQC zTbafL4tz&*hxAV42frD;GoVoe{3i4+-(cSH9#Ymgi(Sp3P2E?e>UV>yNH_5Pzfn;Q z7HlacQlAsj`=Fspz1Y0-|A@deQUoU59yQ_W!1O9f9q&zF5ZoE8`BnnuY~FGQ5HmkH zLAyQ!iah?cc+&dy#i{ro(vOhl-A%hj{|=1sc|-O)WbVX1Z^K@QN`WXtb$2t%E!yv{ zKxULZ+V2V}7DtnBu~?ASGDE5RlWWo%dxF-jrOhfncY2G{^9|#{^ipgbA-XobFrGc{ zknp%e-X)^H9O*=fRtZxED~*eIHWdUtNAZhC+=tJ9J}vAzsG+}`*=!?a8eR6At2-pz zr_nOu7m{bs2#g`Lb7hI`?C8!~>gVp?SR5$eGYgxZWF>vL;Y>sVX(^A)JxYa~8!OA5 zArB<~W@2Ltbh#k^)j4j>?N6=I-C$kO)a zb%V?5+=r6yzEdC~`zbkV* zx(VsonZ_^}>F@XK8gpPx(mN!aVr8^Gq(g@BfJ1oESNa8sR|#Nx?}683$UIV29xX&| z@kLO#56Rd{Pq$C5#4P!=(9oJX4ZIJ7*0S;)7Ah zE85yP-reJqGQ86MaG`2{xP$h*xsy0(jNlIOwD=pfT$k!{!oDvBJw+I_M6PXubI2sswR1{x3on1$Bd$q*&~z22O5)x-C4dm&^6{m0Z*!)h8H< zI5|Lz*jeVm;v9K(DVw``TIx`OvnX@SGskqbV5XN6ze9Qv@!m+fjObZ<^8)9bhPy&+ zX|h0-Yfnd8vPsAWai&=DsnVq`Q12P6Am!Da@w-GXKzfHf@b=;knG2F0`so>ADLWhU z8Fq<_Kswvf`N)fs ziD9~l+1UX1z=_Y<1S6hD=VhJ4v4z+|R@CLwacDEMgo^{=v#rRW*3XF(^>y;`nmq#A zAVg34pGcsd_a`NV8J~BC(9Yfa&P->=<&IvY5+4u;|MB#oHdSw14-;3-SBs%9-klxj zrFu&J-K6wta9A+VyS%8%U1%*Ys#>d?c!XJPL+aJqmWd3j4?OI3<2A3Ybb#BEtLi`_ zRxsVwSvYQA^nZ;=Lx_bZ@oGfVX$ciX4vIlD*;mNpwt5Mw4|{?ig7V)gT?f6{04o&+ z5#1t%4GB5OSnJl)IIL zmwsDqeT0T6qa3Ux#Lj#`lB~VJ-n+iRcdY=mKuW(^GQy}X6zG+NSXWG%eGhgx23V`o z{BHAmYO`5;7_Vk#8|mgGMlNwfr(Kvw8wta8WV5Rkuhv3p6kv6Gzgc`wO9?7Z`Hm%T#uU6T`S#gU1`WF|vmRU@~`h`b~D;>=FhEeSGL!jitRZ+)c z^-+-vm&=<^7>9xbo@wH-l$4(IzPAwjPPu(I>Xl<5&1I&oi&MLM@9VL|hO^4sq3DZ)L8*l^iQ82 ze>{D^;a0VBtH8TdLwer%AUmfzZl^QJ)~(MjxEN;y9Lo$-f0A<4P_G0Zwj3Gj4WIj_ za)P;A9ksvPYD)p_U!4%W4>0tXB*b}=#}mv&zCUGxPkGIf$dcGHa5L0E3ULnW80i4QcP*+HJleTh%;W(%(m(i(I&@}cwvx&2Mb2ckZ zSt&cVj1{yp-x*rl+SDAi*1|DRC5F)@&L91*ZohqBKit{w;$MhezTJ4y`QuiH)dC7g zj_3sxjOpq_jxWb779^8Yj?_~p)lb>0#MH?jH>ug8jua0ke{@c85@GWy2FI;65|xMU zeFTM>#3r>`OyfM}giYuF#0QdrE$1ZQKojYnTBmesGv3KJ?uoCU5|z3nM?J-_B#u0v zuaIy%>a^b67`-{hw`_{uwDMspEQaXOS*8XTuLI0x-3ut3t47FCTlp zNHz!`iIon7d8CHuv~a=7V2u(oGuw(!-!GsDlv7Ru~_l_N`c^8H01VTtP&i+NA__?y^?$_ z;m6`54>FecGIg0rJ+<7V%?sF%&6UlmR&T{%O;eQ3#_Op?O<5U^8p&Bn9K8(DF$Zyk zTtX*=y^a&~oQN0uWxR@4_Hd}nBFYg6+nP={8DNADyI_(*SAa^LDq#v2V>BD!y+ zGqdeb&;|!lfaj|1ln}NeURA7`x>|hF_ZUa5#`_|p7J3Zh_s6Hybby!XIUWZ1e(mV5 z(}81mn8{!!z`5EbxRirea)IS#qb^rem%VeI{#;e>$p8WBL6@PXb~A6-K&IBbCQBst zTD77kJkJS$u_5L$AIkbqLRaSmtk@N>jt1B(mcQ-^Ilw+A=f7H`H zj(u&ZHOEDwoGu=o{+17pr?b~JR7%;Bb!D)ojtFE(yTbI(Qg{?wEuQ{ZsQ0JscASO% zGkz^!Cf=+k!Pz!4VpnB@dVC?a&w39uv%yp^Jn&;C+w%p(GYQuU z)moHl^>0c)u@Jv_mgky;7H|r>JxYKy=59b7y()8}?PHgquN|S&xg(~U?>|-|wG+)# zot7q6p?|tx-&)hNG5X_!8VqfKmp~sJMnL;}aJWwV5k>}g&eP86?VQez#cqzd_e~-m zh|o+sJg;WdrUwitnNs^{CI_y;#94-%e%%~t?%sto9`8Pg9(b#Fc~<7fzkcps{AT2+ zwopmqrV$T$D?RA>JL;o`yH*lnG&@E$J7#W-ojN3Zv|>Q99J^>T{eO;f=xtWQsK%;$&KVt{ zw=BYZR42Zp7oa;oH~kQF!3&Y8=0Ic%60Xesh>@#Z^Gx4gvn*CcR5mD=IuH1QsxY}A z#wzj1!Wb)L@CpId9(GldOF_FhUU#Vi8#}wUbWO8We9hK855g0}>T#p_UUsJZ&deb@ zOGnT-293WNtodjfYb(dobM=wsG*()HT|?qQx;^s*bkN6TG^LAg@^qHKqoQFKWP?sA z&UeMrLyk0cC$i$?58w;IRKL)AC9%mtSig)JtypzREL*lRSmSVz*+giOOM10#E%}Rf zjf6Q{gSb;7HcYG+_<`%fwzVuRgD8&=k59K+i8Cx+w9=gkA2w?QdYqz6Fz&2P@Z_dY z25T(Cy1LTYdPz#%f%wGAkP=LBs@Cbaol2^1XCW#BPl}00rp_EaLFQFtkHGdSZ+_|t z#B@eDqI0^OaTyVIaa)imT|T0*^MAK>%{}WB&t^L$J;Feb8x?Y7YbxZ3l~Z=NaRw&R zuR5la2*x^c;8BwW9>a5A|5gt|HF)J}+_%pUjjq`eQ_Y8Q{FpL_449rz3C{% zamV-NNs^XJv0QA~B~S)M(xK}Uwk2@K6?2mp@|hP6ogYL6zECE7rf8)~D@ml44o!BP+Q8t|sp}=D>IIA&1VA zm;}}`wq)37u78niWhQ^!uvU|wVSe}#X4?6sgpiY%BI@`bC#(uTMM5UIs4Fiarb{y& z@@2zxzzcqK$5?5YZ1v-AC4=05s+>&CD+1aee8lM7sVGlWn~9GA3@QeNV4>s{a`Y>5 z!!9usB-$qyUFE;LL&tKl4#cVbxL?Q~T@^>}f*$0QAv(VD5SjV}A2)XvFdG!z`Kb9Q z5#^sN%}KF^$mC7?LPz;eOnoSZnW$T$rOj4F_>)Dm3vOh#z;6MjoPjT5A0xs?p$bmk zQ+kz%?gRNI@NZO~K~5guU_5u_QC-QCQ)6OFRV-@kCT8^qGze#-rtKeHwu~cmVw|L0fs4?1ib)XNr;h@ zfr2~~=WHV;jrOlD&+6=5c71f!F3OS%8S}S3yMb>jm6yw@MTnlYS1*M%DP8@Q6yA1U z&hY?Q>k-^c?*|Hti*o6;ScE~()=3m_7<3!IFn#`IdsE-6Z`Rr*Ho9Qz+fS?Y{bT3{ zDR+-I_49VTS-))c(5pSa*Dn=a)acIX{}R{JAm__X+%30=LGpC7Ru_ByIZl(_U(}_z zCm^Ln;I)Jp6}9F~D0kYmM7`@8g!Z*p3QWUcf3x0)4AY^lk(wEvcmcI~D1pq=7%>~9 z`4i&x%+eNT9C;G|y17|L0^-r({YFo%>qdDWGTJ9SudW-pHkH04XS+~lFnY7ZI=}qp z;WdQX6u@h5Cyg>!SkyXE_MIxkA@ig5E8P{&2}UX|dSFac=mOuP z8S^LBANU7*@m{5es_45Us#Y_T@8@_*3|Zx#KtcOSp*8drh34c1;S+aBeAIfJk9pE| zKk~S~QdwKadD3dG0+r&Z82VmhS8I!yWDc2Zwy9u!;I-Ovo7*hCAC*dUidBy;S;mYnvhZy|xpanM>4F8LpaB=Yq2?#1`t) z%x0UgKc-E!`1y7S!S>veAWhQ4wpL)Ua12PL(PMHX6dY7X06HSOtGrJWOAf2 zpJ~RSa=A`o(^zUvH)iR`*{H$#>d=d#e(?FY@{W5Bsz$Ck?w4hR5lnuPj#6 zuKs6ofJ2+WS>zdd2@a9@K5?FCo`E_}D}K>ZM!8}uT{5-ZMmHnbbY<}r>~Blz_Jzeb zgzbFW3FYiFo|$ay;xQ&~w=4jXyeY)a?CzPkQ!FNFaRX~Uq>+5rUL!2Aw*J4&g>=>~ z@L`~W7S^OSP1)FJR>oRFOnZlw6FNFcM@L-ebuuH0+9j=JW8t;InnP$8>_lCh@DbhX z5_>auBj8m!7dPjNNm_=PjLB$AW`^x-Uz*dIxGSW%6u%Rjd%lDtm!M{oY> zVhEbId~0q8*B|(7A5G`Be^x#^%e zGc8e&%Er{l0Ku#~31Tz=g$>YOmKBk!&)YIRUEe#983M-?B&*xb6=+xQwfBxM$E;jF zDE3_V2YX{*hhjdspqk|24sAjy`!gR>sS@+HliX3~wKOT=`?=?mN~U9Y;oF%)Xa7Uq z_mb9EhoNgGU$Y%MRph~jvZd>`IF1DzO*HGp$qU&9nVz!;A5>3I25Y-icd4H;$0ncD z`pOKM{rGTo8E{@FA5sptGFShr#S1uRrjxd;P&e2s?2H z0Icm&P0SO_(S;;B2&>f?vuGsaIt0(I#Ju=HP)2WBe|eioDh_$aI^^AI{uXq7rMCLh zjP(cp!S2X&6ZR_|@UPFn>egR>Bxa>Ex4$b-JL$_$L)L|a#nT20!Bc7dnXe+xVCvbz zz>D`f>l~Ddk!&cmuYVPat)5dP=1aoMIl=t$CKSfN*NIUl{+UYQhpOx+p~5)Ze|FjJ zZ-16$0Izg_b7TGKVF$1`I_{rSX*~g)pf;8Z5;y7knMlRX8O6*8E7~O;GL_3e zM1s!+vo|zcJFY`@?bVw5XnLPx@GH<_K;Zl-w>h5JbKyw?Al)NfTDE!w{l zTd+Bp7EZ-Cq*$B}g%)dGp_zZMUn3i4FU2-zl(-XhAW*oLAHi)e{=m&`RLMr)SUjJjvqpev%MZ>+QAskEYW&w)0_tQlk= z;y!PXpVuC>G=0(z%b|&};sdH?<{!=zNM>OljCt5|UyWB*8s>kg zvunvBc>j@qh-|n9Piy+@OYpv%SDmyzm1{3H`4ppBE8{;|{9P|37ZYCh3jx$M8RO@+@zBOlor8q0I8 ziEj;Kk6$y7@IkKDy!668NtV67^ttDK7=m&y%Og&7pWe2_AkdXCO3%daKIY()LZ`iM}>(b=PbQb+0^dcASg%n@1<#>Wmw7rUf__S}d6HXx>P+$e#aUcDnDYAs{8J`b#g5*-`; z!=U|7*4p`m?oIEfrQYw@*L^L=^*7kjk_Q4v6_IM!8wtiado=+PVgppJaZ~IfYbgpS zZ{;jPJ4BbbFN~gkP~EBuKZSQ!pHSxhruMq&*h)`SnT9zIs^9iGp77V-ZvR+pQfs!U zZ*Ieav!pz~Y20 zG&=&nnG!QlHWH)%8W$*vas0lA+_E5M;sA+@uBjchzvHa^8Ryzi=SQ4T@J?Y1AtrlS zMRq+Nx0-vQR#z2aWd8a(;jf$eO|`3jiyHRdo1Zs7x7wBZr{)!YLEL`&in?uCDbpKRCqWy_+fU--=a%kBN*oA^ca?_jNh zwlpW80*00C?Z5B^(wFU7AFzJeYM+W~jQsm@`~Un{JS@I{zqNI+)-q6y~1jr zm_Cp-nSXBeN$*Pq87=MiP5aUZzUwcK7yX08$%gl8+jPUls3aw(uKQNfOL3FuIPlNh z)%0hjM(-!f@rDl5>vpY=h1x>7&275Ep;eEG?=mY;Pg3tIZ; z%j4}su;=){yjGEQ@xUEb_Gc&egV2$5$MB1ITs@!;=n{Bgn0POIZY#PM() zS@Za9@#jj~aoUEPd`|B7|I`G)FIuTjFYdlP{&@O)yMDl|QBjcm085CVYRn6*W;zg8 zlDN}LoWk)<&t6{)KQ%q{C(}TGc5%fzMStPMFS>^qI44hi52P;WL+dZoPXFy-S%Hxm ztVHw$`P0i00raVVdR?L~25Yu*K7n}qQb@7jH{ZE_`M?xL>!c*?68nOKi6d8upJ)*z zmH0*p)pi13er(paa8=n*7T;4-6EtZfQ%M~&9DjThGz5senNDW}Wh&4|JDrulwnFI0 zt-Ai9I2LxI4&$q;TUa~U9)q3Phl9a2iOhz$SC>bQpKNr2RJ6TPFeuMbR62l6&RUPO zeMwBcpUF_zQ2eB2-hVAMGIwCHJz-NN>=GTUu`8XJpRDKebMnVPLODjKFG}9)_H^_0 z_WSMA`r-EGt2$6$8Ta@~XF8Ev5`C4JmwWjd&y;Fuh}1&*U#k&sqb`t| z&sQ}7uUZ@Yl~uuC^?{&7WVT>9>K-_=^bz4|x$o5C7<6&!W~X&SUe$Jgm3Z~$Hb86W z4azp5av}G<`TB3nEaDdM6&XD8q1K)lT4_i3#;-M~W4AjP2WWRb0BhhF?8T|cxwnuy z18``_#Xc`+l#H*16ia2=XKc)$e&sv9SCzn5pN51+sa~1We8M73eJBP(9elupt(chM z{5281O1-k~`y;=viLdl3+k)~iRtV^5r;!yBUR(7G zkrAIEG`>g-jJpVuhj+7!yn*O8t7N~n8skJ(iKM2jDnt|@B%JgXf@QEipdlOlzRUg& zqXe{?DE;a!9={r_sXP>73tvHfo#~KfU6$5ca(ylMu)bS;|0X-J66Z(&F9L3&(%aTP z>FyaBx%U+b=g@&Zy(Cx$>x99?*>xiV>*$1cYBaSMuL6X4c>E8l3U%u>1ivPh-Fv?# zrrul%8%)xxH~0NIGuc}77ETi19VUV_H=CPy{@TRvHso`Z%Z4X3q^eyQt4^|Jxq)WO zqDPzCKcXj-TKG!R&!qTNk~cH6ZOGu?imoTrpnBd7xM*hDG1yCmP#QvRD{YX|uq<#G zR9G98>rD|}$g}YxZh%g3OLx=YB)rgg3X3jHLD+%apko*tJsU~1GF3_flSVd%#x;Ni z{2Q&tvO(N_{u}kVCQZ8w-*y$E(0T=L+MvW<;kPVcqh(?mp41wxV-oW89Ihum-jPy_JdcPVEBUEz{Hj~Bxd1eokP#p4m>SRV zn=75ImqO`TeBdRXJb)k7Hvg9ue@0}wYgxFBrbH0KS^Ny!I+0{$t8KU|mCA=tw=A3Q zy%Tclgxom+pkTGiPGV%xT;P1pI}Ai0$!rRd<;1nu-C~1m^yDi6We=|n&%wI<4|kiz z-{>P*(jKPb0a7duI~7zLTgV4nCL5k9E&S>!uT&_VN^$!t!TObnc=a+z%~rX1u5~Uc z8|TOwhXGMpVjg_l^Skk%DN0`;UwUs0jh8Xop%gQ1H*H9* zSgE}ID*}^7uQpqM5Wl&7`mU~a!$Mw2orL@int7<_h|QHY+L&pxC3A_NdupSlzBXG9 zG`NiVQM&Q=E*pb1dDf6llWTJEu6QHSY#oxxDH}t>GFy$;?a@AJa-a@!V+L@i^WJD3 zt&N0O)~t9M(oytr+y)6Rj)x=1oLGGUcONN;(;p;*kM^PJu(|!AooKCT#Wx!^kKfZq zke1V&s3TKZ{s2SoYcuiPQ#kfpX+3uf!GN*KLupr24kq*4HnJ_ey;)ecE-i1hhC(;D zf33AtN8^r$fxLXvled8j{2pQ6g?g6V7_8y_J*zNii^|P=3sYj=oyJC8K?dI=E|Sr7Bx5zBR9Q)*MpUT+H&^`f}9X-v_}Glxth~P+9H_wjC9I;Pi%!gpBqg`-!CE zO_@)@^U7C-48xLFU~d@gIgO6;ts1>PSd;5oZ4#U6#YYp+&QxCD(->tavC%O54R4V9 zs-V0nhxkWTQmx=o7m0KAdgHBhnu_B%ZW39ME!BvSbszOvqN$XyL-vqFE`S;=1DYOq zf>`kCTZ9mVKaeg!->|9eHCV%A`Lb>E|3TBMl3M4sHZ${zLQPfE_Q#{Pa|NV`z;p;b zC^dBaHd_0Nm9Mm`kklQF7}~5oZPuPPYfs+SjrK5Lh9B(rz*1_3aK5G)7G45?%B@Js z!{dK%n544d&Z~Y7@r-Kns;Z)7@tA=lZkmmesk}Ab6t+TyHF$Hf-<;$(*OoWc?>93a z^?iq+2*2;LC4Juwnb>!Av=;wa#}!`RG@kpUZBB1C5)GSVH}&%~m$ZJ)RpwzhumfQt zNf1?i21%P)TU}W(Mq(2~@AYmb^h-25wWrg8G_oR|3cS$*>pbR!0A}3I_N6@U+YI`w?Q_w?E3y*Ik>*NW!3qx zvT0+(V8#a7IIMp8R0NdH()~m2IX4Fd8T7MP8S7n35 z`s)I9XUWlCtCHtpjI~_pV16MyDR|{9UbAmstBh=FmlPgP>(>(5yWZ&raVlRkiFv6T zUuvQU)?m(bgmyhJ#HR~o_9Le$hM;X3dpS>er79dxbc@ylC7d3J6o58 zmaK7w=CE4XfoKn8Ghoe89Ncx^%*-~2pK2~do|B#xLpU7y+G^NYFm`0PlR2ur(9O6A zHL3zGcSq6XUZAlK%_Zf_tW6Qm6IJn_iT2?io>PIU=yd&I3Nbu$HHI_7N!9(uY4;;r z&3eomgSFZ0(=QAgRcD|g@Kop5?jFPPnrQm+(^DM0Ibec4XvB77u%_g!{|ZIj%$j!P z4XB5-dt_oxPWiztbkFsHX*7iLj*dAP4I5N_TmswYb>^8C(UmJ4@(B(D6we zKpHwazX1*@e`gE4>0=6YC>a?cm8^mlZBaUyjFhZOJ z=c@g!GWE20_@76)C~-a8)=AUAmri5jbD>KvIRGg~fT}q-e+Fkyo}+sH9Vuf-Y-7-t zq~am83Yf-<*b$&%Z7!XFhO8vSxaR!vuzqJVB4^E zZAAB`dz)rBY!HusEl_vpJ984Lj=h{W*xV)RN~;SY>+zUA)9dBN+o;O+WNv@Gqp*^E z1UL^jt_EvF^&Yl&A^Gvn;onQue0L1*sm$KOSpppYUl zS+oGXhQ}s0kKP!psV+}1FRVDx=y7O+_@Pm`$$fmZ2Q8x~4+D11jgVP$vqER9rO?;6 z4@FnM7K5Hu!?NedFJ9tJF3hEnMM0+*A14kXo?g@?p42{u;7WnYBN3gfS6?LQuc$p| z&{sZ*r-jc$pSW%dnK*?>>)*)FkIlvjxk&pv*iL_4s%ARqZ^3~497R?%wP)BAnwer{IP>k2M5NK88MC^7|n=xtS5?%7C z*G-slk*SXsP(q9nIQ4GZCPv1m!SiMsEjgu?f5JBzIgAXAYI+AbEKJw>VBH;($$M6? zYEWg!2uI5cf#gGvn-gOYlWyu7OPqd%=~sRXw)ww|n18A3w@LUberx?LLjE^?g0%(m zF9|t_$p3!{@ZSs}7s5DDhH@~6BcMZO&^av54xPbE_>3T9a1N}%H^WdelK62e=L^~II7gW-m%90@x%+(doB9KUYR;xS+n5-A5y7=ubRf9Fej~r zIVj@$$0toro|k7z;#ib~oiMs!_egkUrLv|Y>|VBKfo`y;+(P|6@uc^%_eE&dv@DUU z_l6T&tl!^$-ux}D@r8SXvyXE7LaRmPcjS5{@9-e2j}Iux#!;!2Cz!vo1LN$fa>7>F68N=eX zzN%QhRJHaH6hlsksh=xa3-B11Lx%g|Hq*LGrBf(~@>@tIU>HC2r%wwyt|x6CfXmUGxO(0V8pm|-)f4IRRu=rM5+N(! zr9d)z6W%QvOCKe&p64z82G z*MhY2y}mVX6Vmu#vrnx)xcrz9Nu3_U--!vsy{%j}a3}^xG1l}X`e>5rU_=M{9&!8h z71g=mw#CL6#%aqvs3#vrr&%950lz3R-xvR-#PG@Z{YPvQI=s1w)ePAa zcW5z3Ywgk}ko>7spdY3FxCh(w=%45t%}lRWnw6ecgZEqVN^2|BRH8UHm7(DkLG};6 zlMk;hc~^9SCoIh!MsmQ-Itn5y`uK+xxfAXeYN-x?iySc_L(@}3P!I-c(2J^5)B8R4!zP2gr?MLdR18Z z+Hzze<}kmxW2$;g&HY8!STX97Tzt>HbV~Gvo80Bx(ePZjEnjf|T_JRY-(=Nqa^*K; z{(8y^_iFpXDbU#up{4^EKqTqaJF63$2ufZgql!wLs{F|EDWiEq3tF?|@Ax;AooA(r|j7g#L3DQq@zLOU*AfN$$$~&nxvo^nHF@Qm*@~ zO(*%&2f_v-^Vx8o6=kS;MR2vNSNBb-$`?YN2MIBU;}!RPqIBm)l?klIEIxbN1_UduTewzG1~HaVkuc zBf7!5ogZOlhE2)TvxI~gMG-)wmNa>qO`c}Mc{EdG_hR!S`eGGtw+)I{uZlZiS4zK_ z7J41bX%UkYQ&q@N&17e$6F!}_$CQRe?)6{juq~Y*V3(+rwcGQt!yom)l|b#8#L4TB z+#61wpmoZLSSMy)glwr>9%ZWsEFfpP->u)w9LZr`ykJEJD{ZHkF+;J}z6F zXJ()I#jFR|?VBu3PK#@0Bp#DE#_IOr`|LD_7#(prK&>I$`$CGP@iFUcwxZ|GHBMF) z+r_Aa>wrHru&;2o69?zU!7BIf*Wr=&Fdv0Wz`l%I@2zu0Hjzs%{ z$f9R?AXRqUyPw%?8?HutQeH;i6Qk!8Jr3&BNFg4xv1U$$L5Jb`M3xkLH$X)OZ~gW3eeZ-TazqwE|j=nmxG!`tGG0;=ZGi%zB5ODY@ zz25ojvHL>8($iC|cMdR28o-HUeE#<1KFXME+Me*!z--9lOJyaUGr=0(SxMo2O(I4s4RQM(Kw}z zpV&pzQX5Hy$%#)!k)y^kow&CJVVX#EFcLMh8a$Ae-cQN zr5@f)!l|Fa$h>EW1$0E%=J0A@9@4-pXl5F+wDVz6DDXF|;J@$yf= z1~u|MV8uVHfjoIMQt|DMMMN9Oy!IHU)DHN0|G4@65mt1_0<5XWSgq$NY65!3EuJR1 zDwd5C;lV{Yv={FP{Z&BaVE^+EbXjsYO9G#vSs!5OE3-A<_LyL48TkiK(kA69 z(tPKtobtvQXP~ER?sV0bptqHT?+?D7cwO15lOKLG{u(4StKEgS#mdvj<1bnZ_YME< zvSJDTMgP^4qs@9c6JsMN+@srAMEGR5GF4t_({h|w!U3;2Yp)ye9MKn*WEU++>?T%k z9J#gDg{SskIIk$E(z{QGc)ioRmhMkjpl1w)Oa6pqALE_|?i;av6&x(T19~-DRuROPK&GpEIm0^LS7Vh^wy#Ow$p)kM$s>F*bxkV` zEqk4nTD!7et5YoXspP*)QuXJKhlF6uIt0K79UDhS6>5p-vWWooIOsQo^RJnmL$SoIl`MnH+LkMKMoj^;$E5;53i|@$jg}ei(C7 z^jML0W|wP*%n;Jv@(ZBuCcK0ayp2qhA`K03fog<_^|SIyZQ0>_n%LzrikI?1gth& z1i>wWq3?bXu&ez+np3SU+@q@Fzi;cq#UFJ4Aydi0CuMMootoh8|7_`KalMbR<8Dx- z8V_=MBbzwIgmta2iR~ec$Bt`fD&x!0iu&dRO!~rgJFhFNIuxLd_rBvRx5TGRnCux7 z|8P91j;Bc%?NsV`Zo6yhXpRSzywW6RefVP~5jMu$vJ=NHey=HuC7Bmir3o}hLO!wQ z!DKn6t={OC8r`OoKOe2O>Fo`5v)_LEhPvTuBmhRl$*;q3p6+0vh`PefOsBKG3hqa= zy{>FMolQiZ4@~HM(#v}saK&Af#PqHWln$M38Z~Z20XG^qkkuN%XC16BA^Q@ze0rsW@@2yB1CV07#a7?*>;70JTG=6^j4zX4dUbSI#x@eVPh2D6B1^UP3{T)y40!ghg3mK~p zB!0z?a_40@c79e9MKJU|NT(f=UzeeW#JL`VY&kWs4CjSwlF!(~+>f53x0NVnaGe83 z&QmW|R#xiPTW|QUxasRq<8vHT{2JuUIW0OoBc>65j1I8PFgpBYjHym$<(BjN>JAY}?rEojm7=fY2q1&n3(a-2n9 zA`|-{Y6~-8&FMOM{iy{Xi(bet{_YM$378!{*IyBI@vs(gPk0keHb{!ME{sjQFs@ zhK-x&rx)1ZxMi;rj|JkuOD8~!+62LN2k31!M#~r+nhti<$&K|aCq0aAj3bxsRgoa6 zr|*Sx=$jQUSYq|S-R?#%Y{!mvr%3SyUf_(#4ZMMv7~fz=7d%GSy57J%yu+wZDlFu$ z3~xs27~%^paGIgWWSw|tsn+Vo2pI|IMk$5nTZyEMoY@zx;8+1<)!1$4~X#w5KRv_ zbcPZbq?Y!l=#5Yqg>0!o`{>>e&_u$KyLTM$1faFza|V-05A*1V~UMal%JX z1ur&YhM0%>1XJtpPPtw=+;5-e5zkX*3raKCs^eAPzA7gg zEc(o<$QL=h^gSiCFLg3%@H`B6^VIy!t4i`aECl1gV!S-BC+FSe(=7N)#9XzWY0RGHri?R%7gs zM~o(ktdBZ4;Nl081e@A`HZZ^tGQRI%(A>HgCN9MuYfx$<-#mZ&zI}OYVDSm{CmHV? zAn?~Q$c|y+60UGMq4U}pEwf#SGGeqzP6}*fVq?sQ*jJNy!Z(SdUR9p(X}}j=9%75l zjnO-7Qq*PKf1u2JdK}15|2+q+8-XO3z}cmDJ~CJWH^JG!0IOrREH-2R-%-1LR~X>| z=E!{`D_@Y5bnIjz_^HwfC`08jO-u!DhcK`s=%`{DouRR|ZTt>3qI9g}2-E;+69gJK zW!7ktV|l|Sh%{YjI%|~q#Jq0=l!_$V)j^-hZ2e4`X&9IQOapBMIQa}7Ya>8+lK5Bz zV5Sq_kPGOFK5(t^3ib}^6$>)cGiJ_95Y$_P>2nrRX)s$kCWd|PY48xWAZ;-AsHPW` z?Mkt>XG2?l4>2b8xcEqY17pOdNOXm&PUBSXn`mRTvt4@DRr;hsI?|Z&k_H#wM*ky5 zZWZ6tP%o+N{%_lte?V4U0a%WiUk!}h$#`=&59euS8dt9EJ?N0DX3gw|--L2o#TwO* z?>}FEd)mJ|Zhrq?wvLH|j%tHd2ZBQEZfrlS4_1e2qtrXzt&q4@3Y(4UgQI*vJ_s-# z6~Y?r2yIA1{EboajhG2su{&a}X_Hu)v*lLK5aSlBx5ybHFUq719yWp7AK$SPh4oAU zdqV-FcZO$AYN>82vGi!OvQ@_`U}g=>*Ls`7A!Mz?_=`MhrfMUo(nteAE!=ZtW3EU z6KQy?R_ogX%TygrP#^Yj6!n&d@7c}Nf2hjjSFzjo5qtg1TV zO<3TH7hcfSbi?2^!;MKEwlot~2-jOgI^wwDx0^}5aJE6}?Wonsc-xg(H-~D51}hij z3M<#QwddGwpua(NBdg^BK)$v{$Tc;`9&fM^4vr%;(D`~inJCa-Ali&g-3WO+NqSOI3onjLA5 zuyO>Id5b11vZ2%~x4kmUqgJ*?$=Pxaxr8G88+(8io62>UD3ys&@3D3_V_>K7-Ij^sA#mTFZ7q4Ne{YEsUB57+_YQWGC&rwlzXV#Y{f91*FiH zJhLnIIj2IVHdqOIp*4Kgn>#9E_x%ay{NTkjJ2<0dCdf-F6OW6)lDlPt~K26Ke-AQ#>FNW$*D?7J>;IGbup z*K)NDlAK14W%$P$1)9+NyhMC&-8;!-=f`m6!tNw5w+`G8o5pc!3P z);`=-`SiG{H^Mw>!^j}jD(kkFrw_(Iao^f)MXbQzVmOUJo97L}gwHx~K4pAs0A8J7 zTBVK`m*MRr)qo1#WYUCCn@PPOO77ntw$1+eTjk~OfTv5Of`AXj%~btGJ4Ph*x3xwD zlBmr%2`S&CG=ec{sl926;;d)K*k9tMF)D&|@a7&Z?#MNtF)36m# z(v#CxlpyW1{iJ>J(=y3v z4G#aG`v(mrV1VJJ(bg!Lw~m$JJ$-Egb&$TrKz*5y_UCWj;%Sr?5>N%U$rD zItDQ-sbjF;x0@$gy*n}Q_y9AAQuU5QVn9wh{e?6c#*o)|-L5a*e$ync#Hz?3*5?@= zfEqRk8?8xLdjHAb%9y^Kw-c^r0pVFTB%RBA@|mOLm^lZ@Jil5@39rw>ZblHgF3`4} z3cGyhxrVfNL6C(qrEnQU)8C}r$Cqr%QBVbwlp;}N@;Dz1pWRAkhWQ$;jK zDbC3ea_+}O&ad1`fGlze5=F)4E>>?O=R0!|k+avk^eqiMXr^mzWA$ILrGcLHiZSiK zpEghwh;)bMdu4{WP6_91VzUis(Lo?nR}OuzT=!5x&PHR{kcMDQm+z1sfI z->{LWBuy!&Ke=;qZEH1h7>Uac9(E7Y0WX>!c8B8)Te3{(&UY{-egZIY`F}OF3X$YS zs0o>HRMrm0q*Jm8vw_||!yO+2CmxOkvsM%;zj{tM-emXqZM(0}=99L8s))e&Zh@ds z+BPx56_-QRVx&Y9A)k|E{qKGk=G2em}IwG0aUeqk}=0U7ugwy8<`x=bUBN6tt9(l;TtIwy33&dNi1=na0bFU8&qrh zU<2YAZ_lZjJA*8^tY#8HWUPM{^}yu$Mbg9-w>z2>-_z~xW`yyMA)S$4@WoV8yaSDz z%$}YGrFKx$80xbYN1v6RX($@5Gy}?H;1lOYD=BJ(1CR3r(prT$cnK01tD2R;Pci@> z16!sDJfjXrjuVYkVm-p{D-E`!GHB(Ng2pEm#Rp1iM7q|@52Yvi}*?otL4#mOr zX<0jvQI0)}c^r9qJ3}dG*rRjFGQ!kZ;+5exq{X5;e2~^z|Dka3QY0a2yd~MvrdMj+ z%Km~%{yf-4mdJQ-2zJ7smXop=@G%@t+^`ZnQH4(zCaedV4)CDQuXYvRx&bfjq2lN5 zyCBrm{>#|O&50QzN>Llluo9`TT6HBX5mDJrs^fgXCd-M856MB0+&X@MT+>EDF<{p$ zBLjH*kf|8-1u==+ZpL+;Y1y*o36i={v-m*l8yiD*E6FSSW&dE(F%2v)x>txDJEp7z zI~jOjI(26B^^Upv>ELT3yF1~v_=vqUp(`JTUVC`czE@Yuuk=aK*XB<$%Boaeky-5q zPs4(nfa#Abe0QhRDnUca26;_tAlNW;?%^kK@2HyC?aRv((_5eQFj8I_J>)bSC>E!o zs5XjfgNdL!jwN$y=#5>gv5!b-T#vB_G_FAH&-DK2da-5?}%1nt6AOISEhb6W1YrM z0LjgG9;4`T->N;%N|v;649136bXAoP-9ZZ+^1f4wJN$y1iSX0~+67w^C#?wlbZBLA zW2fjSf$H^yl#^kUnx#)fwd@|7<{3vQJ%#q}Z&bh-u><+EHgU78>A<$ZjWb_p42C+**0^?Y&F5GQ#hld&06%l zTiL2BcV*B|*`CwLU~_sKc%S~U-+p`HjhZ`k?F5q4lE5^`(dJ!nvS1uC^N7ox(K6l5 zY?8|xuK9)cj%vc^GSI;O9lOV$FW6IX(!qy2fh6W{;oWcHwYwBH*`?S(5Nu3fX67_RVL5NfpI=T3{b;6tV*j(3GrP|}kHwiBE1%sBB8 z8>SRP5iouQUXTJYbRIjS<$Mb!CvT~x6X3*C10i)soNSQJ54)$w=IOVWZPRRDVb5^z zUTNx%%KwocvI-F-9$7l(MA^V*fJ`{{&78VcX=;@u053dfux}tjZ2J>7CY`iT)=n^a zyR1puWmRpigTJTW_gko-$M^=ughTCjFu<6$3b@XFV*SLk>N#t$>sUEmwmnA?J0oOH ze^2}pYKOxE89a~LG_vI3hyMeIe^^v8b-ZzPTm@DEw?#b2Rs$;=t7ElljKg+e94L3} zw<6FX0_m79 zvUiOMzP(KE2>Iwn3B@F!h%%g!!O-dmwjj_>OS*7gn#` ze&`Og*Ew00c`JW11e>s8awm|~T5K&N`x73wyNM4Wgf~nwiqavPE9~-pt!(1t{C`Si^IB$?tvZY7c5RDXWY`q&Zu04mk>P?io$H!$b4j8-4jwWmW zF%ki9IRJ%}TB&R2x)VrJLP5qLsva*Y6myRIYft=-uZaIFzIgtlE#Jg$h~YoFI`Q0B z4cVO!@98Eos@ux3_8_RPB&-PhRe41x(FMb6b90w);r<;U=pvr>O?^mtvV2gh6bnZx4H6YbNzosOv7AL$ zKeO8n-zo2mkSAR5PKmigwPa_tc3i_{!M=#QAfahjRlDlSs#|?VI+jDUHdDG_k#UjQ zPH5-)9D5gJ(&?pYxbCCo`LI~AZ!$$^T!q}YtD?@{6Sp!J!!Sw|NQp-d21aWy>$vNpnppoK${g&mrrHv z@hZ-oSgs6d#m(=EKQk$i1o`8DJqJe-i)@FlVex)V&`2dHQKt!JdmOhBs&#~Th zKjN9;qdV4YeSiAxNxT3$d^xx+aH)v`ULPjxse|~w~qZFvYCLFTm znbfd-dfjfQ=_kt1J4=|bHtT})Bm>S0f?GPSsNmBX1Ai0bp|X-DOcH$&8&m^YP znD8P3Euq)zvR>vJk3yU<5wYWJL^TeW#fEgmTjEJ498WsoSWt+3L@`d_?Aiw6(_zT=({Yxgy%X+eX_E(wzXW07-oPd zHzrfBOv+WgT6Z+39&TWL;wuXRoM4efwK&VeiDiA_n)|BFss7G9uzbSGhKXnY?s~q~ z5>Q011Cxsurk=&g2MMm$Fse5keERbiKn5*u2_TjF_tErK%8}NYBbG}IC^XA)kX_$V`9RXF$@jmJ~-be9e zoL)$39!+1fRp7}flW&V!$hZ@%%NZx^OtynD>CoN^wC0A*6YrzEhxUAQWk6$(bcinM$kl&e+(h0v8GGjFt!5yz^aW&k~78Km)O-cbiWw~{z&^sDp@qC4z2Q@(#Vd;!)~xBfe!(AB+^#_9jqLPhSbO^I zz5tJG^bUVJ0VU*P&@2Ak7yP7_FZhRe!#{9itDQhnYb;jV6>9|K2EcfYCOKLMvCJZN z+-qYRGE5vBDXZ5SG|Ur7O5t~2!{-)wV%V&AW%iz* znf)`^naP|x@m9b9XFYwJNF~$m>Jay+#zl5ByD-Fs{q%5PZPDr`UpJ-frsQxL`OZjL zjqdiYz3}f;)}BHdJWXwctYF{@vrU60j|q&It4wH;hmNW6-k2h@ytNA7gqk8*4)q~as=_b@fdUztmE=XwT|Jdo>$jYSaiIn(GjzCd2 zivimo!qi4VVD99CDOaO=s$ii-T@$o3$WEQtpcUmrrM*`{u@mHR2|JpTHk8{5B*}@6 z(V7O#hYb4iSjb}YH^{qz^CLr&D}1v&;Xb?L%~tnXniG#3ZYe1OE#R%VoKV^XNwTg4 zD|^klU-5!c3?slIa&^jF(LVC!GdOISra#8Wx`WHu31!9n2dA zT8*r}g(qb1jFcS>Un36qew3aVC!Wm)UzVt;`meoyQb$gp=j0we;dtB~Ka#2f3Nv+Q z7=^T!D~)aK&md|rT}hU8jWXe2KVIM5$Yi&D-qayV;({oelRpb5vZR7gBaciAUlr(s zae$q0-a{bC)*6EZk>>7&4LWI!{LaYjVJ9uE+$G-Q1ciu+Cyt-+A^Hvm*c*CKA)VdH znJMCMGYvfr&HsYj2pFwkFPBb$M-m|)Kvr6Uv$}@?cj^3|*-p+UY(9g+I^$Wi!bIbMVC(2t$$8y9Avx6CB>zG94y;<`>^!Lg z&zNVWhPC5_jNS|kjAqZ*fkNbqjc-(WqxaIwwZwxwrj$@C7$m}&aHKm779ghiV1RW7 zbkY^k|KYXIj_-cpKAnjf!<#he1nb>)rxUCvd^hx;13jr7OcR_I+#p0GZKQ_|Rgk~epdlW`F6 z##adr#O@i;%&A#nDCR(?*vkt*G9z(>>L9Qlrk2qD2cFa+u{yN41UpyGScH=k*27gX zANqMEi?Ti|nB~M|vcJ$O!KCGL@geO3t~-F5ni<=wnfbtbDM@<5r~zbxJR!{x-qcWO8Bvy z*=QT=Fu_(^+%=ckhrod<I0whz|{L? zAii7OfnX=&iJ$Sz#8xJPTF~GZdf>c`cVU9=zf0<=C=}_M{#B8f3P!M3{MEx<^%0tg z(stz)b(J;VPYo+!SzO~aJs2XoGB1Bt_Gd*5ovU%EeO5krl|se>>^GwqLx^`PX;+r- z?Cx%5%xgY$Z(OWactSaa;jPWVV7VP|&lzs&NRjiLEZQz%qV-GH^rSOwLWg-sJ0s z?_JWTX`w5Z=*`K#IoO+QVTZy%Bz)r82_!La5(W}0X-E_fm{$0389vPOQ75Zc{?@g6 zpWUZ)Hhk`O<$1Epk0Day~D3;j`Cb3T&a*s7a0EHtx3jN(q&QP zY(yRR0|;iHm8fOaY`p1lY^7~*^~rZ`_pxIFg#PUi#f4+51D(+IBx`VPK;+W1FyFbt z>!LzBoCRaY{E`lct6CsQYiRHEt}W)_GAmPL^G8q@?z>yVdw*J19p>Bxk{p0vsprlg z@S4pl!TmXR|5;Env+_crTbWx%w5j#9z%RW0BDg5ezMM%IEZ0KkZv0Ywqk7*Up((3M z9)I<8#C;@sg^K0h5moSFfsTom+fcNtM$K^IN07T9(zrx*Pj%bB3tw#%NKV)Z`oRriIh z!K+tZDYSibltrnLTes#?oN+g5j~PK0HMrtQ96ns^?xy{5m5R}F2}O_yayj2?6MS)N zybHAPt^?#)z`=l-gE=aDUMrg;{hiTrw=&1f)0lxDoxJ0PS;mW7N|bE;iHED+yC`d)GaO_hFfR z9YF2&&yTO-y3W+SGUQ;wV-VtWLB(ygIR#ng> zmx_HvFXNqNk%JD3&ofUS5G0|3J@@Xoa3~q% zge6bD`ucO`3fXp-)jEIzNqs*)$YQ1Z9t|HP|MxzG2UO@gFnV6WI>TtvBX^bQn99^J}3hjB>Rd@ZyhgZq2;^gi;g8ksybWgUAww4 zDJw=~!vQaggM`NMnXX#)P~v5EP^>%Oa}a{8^5H)IOg=GfWhJPl_P`f~y$qUF+i$=9 zeBrzrTrS!?MYNN{+=bi*DPdErak;cTCRcVrLZgZmAMUi6shyMQ2DH+L`eXSCdgq799b0XC{jA<1BQJ1omjxB3kX8Bb{YTt`|ay<^YnXIm9Sbp>oPyb zL_;v>Fw2~8qH)EVtvZFn0Xe{NPp(W5cj@HWBx2ChH_+Ghc^OWw7u6NFzs)QACAN%# zy5P)KUAt1}YUS25+Vl>q^e#@-MB@-?Aaj1To|jlw72Euz1WtNqEu!`~@iXgP5NWiU zB4`O3?$lJKkM1ojD-B%azm2;yc7nUVNVa2Rnfmd+T!9Sk zHxXbtTEzcNOH0X^SGR8#E;qd^y>g2I+w z5kgLyrW2v(ug{X{FVB)rua;D=EL_$ucvfO-;wOGd7gUZX_YuWyrMR&G9W~y&L_?5p z3zuJL!nNG&p74E?HB0$IRc&Q*_gipKjuY(fd2*WhwS>{K5;5PqFwm64nQm-i2W&UztNb+~XY^s$ngW`z7+eIOhv5G9GKY6G)PsLPe=)Ho9QL4x0R8 zp=hs;q766@1kZrDv+5HYqP-dLcq5{sAV=3pg43m`!n!7-Wt56p4#ot5q6Po_jqh1^ z3pR168U6X~>E-1K@+X9UR8L+qVzgXH(AN?WO{-T=3%eAg=)K#b=1S8T3%+lJG3o3tLKe&f`&ykBN{DaZ z(nf;3Xc^%$paX^}K{EwdWCBAq!FTHz6D(f+wv4KWQPnU;EsWa0Ttowtl~Vl)1Ml<} zW`4rl4NO>06Yi-3Nz$^#7$KucAOjgEU?5g}R)@P^kkH)i%F1v}h^mA#{m~DH`wPby ziv}1=j^T;er8DEOZQ%!BG|5{*W@N7E?ER5Mipb3nPH>jb&Nyk?=?Vjlp2N{fQcp{M z87Yv|8tF4uMSuk0YFk~v9+AT*^0A? zA}foUs);nS6?WQl-|XF;-nHe6S}`zJDy&O)!+mvO8)XZ1`(*3fEpDU*%d|(#PQfoC zLJTurc+fQ%ZPr?3w2V)$gBQIH-WeftwfFMt7Z$eYaIndTg9)qW+JJAT^FiM;-}2#% zRr*3&lLc83DF<7K-N;9lTh1R;%pZqS!oE-cT^|0qRP)ecztkFir7~c5s9hCJB)5_i z)<=WX3-O;c$!&}45>QKCP@7IAwSW32P4ZSim!J0d<*;E(ayIBH5W5Agp61TDI-2F^ zj0meqjy?zC%th2yMg7>yOoVw21M~*>%4f#*IR7+ScKo1&(kYr(`o#rCg{CC}x@)k> z;6J}`>YWlC?g*G=*5LnXwCuROBG*)2XF};=(mz&0R6vM`5iy}bz$`K~Z%#^A-3WIu ze?C2Jq@n#V$Yeq~d}#1bfh4A?^EU?nT$yx(;D}2&ol7$?cirwT3`1ASOKVJTj-erji zcC|FHez>p_dn(#C+FY13?7CT8F?70wDn^LitX!}bdhLQnB48NfVLD*dCTJZh)RbW| z{J)C^oItJ^C&Ba*9YKJf>KLD3Re6Goi5_?{^lEXvn_HhhWzq#>BV!Vni6O#N{W|2iB9Ja>ORQ(7IWzNVgHa1N31Nu@|GI1R=F5BYQfHAFg|G@y;)y4WPjk0 zy@g@snow+7#{7%S#M(>c!#g;foepOX3E{X781^tS&vMq@vRQ9xvDAfzEKdFkE9>oT z^_70%$v5K=$b5kN+GX!yWWE0)WI>sQv)n1Y=+pC|cQ$LQ*~0)krfVRbjP^Jx>N{fT zt5)B@ygg{`&7P(g-s}ZleiO@U_D0Fje|9ulnpv7s-C>iwdatXD|05@#_m8i?{oFq_ zusPG9$2wGnft>@>D9XH@C&g7U+aV%2Bm8seZiJa$PzuC1o#jAQ2bp`LWm;e{r?w)e z*$QN*n$)sut{~dO0ArnupQ@AbGaL*ZoeVchuIrIaN?QnLZ2HG`NK|xXZGB@=YH+hb zse{~Y*o*G(R9YfKp5+wfHz6S~9(PX^;ff#s?FEtwMYcc|<}}m52-*m3MqpHNP`wvG zat<>CC(Pk`j;5$pUm z;G3WQPtR zBN4yk@K?$*59*VDC6?zeHnHj-CYH4AX~rTp;cNv%!UDEEjI8f^0BS%WNG(6+VS4>p z&;BnceW4_NBd5;+bRbhub#zLTE+-niT#S&Ntcg;F6;s8|iJMWuEUUaw*ah_{yjvW-#^w zNlrUxIG_jaj*vTC&(#9~5}Fx7j}ZGWTzXdLn>(=m%CO)Ni7tcp7==1%hmb^_x<#+=xpt&}zShC!8}HP(A1;^-q|pEP)> zbng8-uKQq2pNi&F?O?yMRVP<8`EuGQMXzRrP4Zky=`{AgG^F_=&^6A#*$X5&9(;82 zHs6_oOy~EM|H5@4zv0`DvAwhI3s*d6?MZc~a)Eufx;y)#RR0uGzSFYOer-kA5qmF? z^wp*dr9;s%9%4f55VAK~wx?xni=gT#?b2SebA@KNH(K_ELhkS`nmsjL37d`H{|6~% zYD=*U0F=Y^eUOZo5$_BD?;rC0jRTj)c81mn4=5|hEe?rbAKI#HbVe4|#nR}`a0rkj zk099^wa;MEF@|pNb=Y=Zs8*d}T~UrfaM$7l4W*;9IT)Lf4yH0H%?q1vhtITgN5>s& zuVWY9ebP`WI$j#xCoKjVy4*WAZ}o7UyPkLTmG2gG44IeQt}AZ6q}EKA*gLP0z0tB_ zc#ngiO^e8%4_bF?h&uIB+)D5+Cb2oZYdmj++~K}1bR>+NF)t@3ncLP7(M=L0wH8~; zMZPqU50bkRy`Q)r%D%&<>-(p_KW?bwC3eyc4d4z1v(uLBcAZI_k z?^hsoL!qA!CrW*GP*kX5YEt zjIHZvY@;TE35kvy0Ph8ol!`9;OfWfA0Hd6bvn97W(NMB1OsrY$BG}QvdONo}rJ6H1 z-Q@0KivB>mmH@j^{gRoSVWVAQyxSqSUC&1+p=AYiqxvSUb~Z()#`l$bfh2{d&w))! z*xmia%}U?-?v4DGMm3RmkrE`eM)rk%(YX&oP0__R;a<+m#jN|n?O1|c7Z?8h>l6sQ z^X!9!MqYX+GAB)Bn1sfzK-ICVTzTgD=5A%K2rjhS8!2DhhiiGw-e|cEQdTmXISb2a zH1Jcey(wLB(SVU5d6+vb>m&P0sqGAQ6kQ{zQL*YJl98_U66kJONP0eD=tYLH4T1(? zVht-jUUZNZJ#d@Z;cQ^-=yk{5Xt@mnAB?)ZEpSDU)LLvU7onmq^)@y5q9(tSc4f#w zu+T9Jq-wbVLVV%i6>hQarIbSqx?w0^N9!lV!>JHozIZZ*gyNLu6X z%WR0u_O4G7<))8{)y}0A)1amz!safz+B*C+D1SuDZ7A*}LeRVpiK4E|QLF1I(e)H> zK>(%)OJX=)blGd!;ofLj{wF`s?e8?SO6q-YR0|5QS20a$ZA4k5(UUT~F{xY~*UYP6 zEzeoq#agOdqUZvfJuu->600)h7@)7bSO}#`^DiWvL^OWvGzpE9nAg#!cv+cpqC`o{ zBEbN7FObw4i`8_Ck9yzYem3(37{MaaGb=SbqMWz-c}>dfZ@SD_0@zqm$Z6(TUCZpA z1?X|g>^^387c=)BKF!g=%zQ(Juh&s!Ux3c>RK=jVoD!%}f)Fx}PUTg}6Y2H>NlJ_l z=oEA0X3`aF5aH!8@+tYE!OBI06#%RB;!%qjJ#=y>XF~w*$Rc*-4yZEDKU_L{$IG$3 z(Q+G#HZ*Fiq%PQt4r>XrhE>9EXd<~}gQ8aD5#=l&V{E)~1%zdzRJy}6YlAAOS%UzQ zc_ofa&j3QpcI<}?uq4#Adg(bd2Iv~TXcqI*yB2UGqRfu%qBeQ)9QDOB(-&W$mza&d z#ANg(=AyZ7bT5$98l#lcFw?;hk`f>yO$0UY@)S~z{v@c?N~}B8{mOE8)f|vtYFuOD zha{WaT_UurreF8npY?wm4+EYyp8nW8zWly@;CAZRd}{yEaq{{@1J(T z7=B;3o8MOrd{i}_l={}wa+Tm+V$T(?f&wQ*%F3+?zc1Z$@W3U}H0e;@I`f$V8Y@?p z(xer3Y3Gxrh825{F0k@#-!moeOvpQjMm;7it3eKDCmDQ9;RbPgna_X!{zIBhp_}2# z1!uQo5;~TZFzZOty#PD%(>bQeVLHf^*ATfp054R~9{0T(Z0=eA%oa1{B?0KJ{5F!5 zQQ+mwel@T7>b5HNVA7|V27RPCbew@QLWVivk19d?tp<}fu^=bCci#u0Mq%;rkIao& zVX_TPwql4OzyiG<7kOa64Vs0DLe4tYgO!(ga4e&NOn<>$2PWU{gIJ~|0!AmI{;t1h zC0^DW0j(I?8=n95v~Q@KW!fQY?NZA=;hk^dJKKciBQU_2GGmp;2Oi!GqiY43Ua}o# zJCqhB1Cwr#)&2-P(13#@byObZhLd50V!RO*ys*k0jTS0w_`cu%_O$;&U7?PiF`$vT z{q1=J4J~Q6V38aeL}Dj^&Y?0M`I@mBHS>I=%nkf98dt)V8wQ$i4fXe@dgW|k2d79A zqdzgU6F!g8S3P^0=&>nwsmt1>4xke8a3W#pTTa|% zdCwEnX(mGv66l~q@?W2};EK)j*J-?=gXQ(ahhlz&ma(Hw=26Kok@kc}f^{^Bi(_z1 z@g4>kQwSJtB538wS7PFo#~TK|EV~h?QI4=nJi6a)^Z4?N?$??_urBPLpn1dB3j82y z&xs37TU7IWU|C~fA6D)Bg0H8|xBcVh$N%@BkLLGb7FagWB00zjGJ*?HJ6`Q^>iz!v z<0}-(-i%G#_X0`kCA`^HMLYq`NWwyw%L$XB2p2nJ?rD;<#RH2ihnuWfdKDcoo~-tI zzVZiV!oir03dWCL{`Pcl%zhXHsJ{6Mizaz1p_Mcb>51*n=K1AiyP>Y2X)cV4 z<)nEE9E<*-DHF}VVtHJ;Vr-nQ+yl~xVn=dr7+%vP$9L=`OtX-(mN}78qp7wU{ZhM- z`mZR1xdy_1IdY~AP~eP#RNyUOInt=XEqX7@4_H{%i%vxb(z+HA3^32pfypX-D1@%m z0QbhMZ@DVOD>ifn=n6Q~flpvyV1eb{Xt|K!Rt`{}@HtFg>Ls$A5XaBI|MmxDJ{>Gr ztFWM}g8{}-{NiV~ipnw{C2;`SeqrDoN7Dq)GUWsXrcP$zl*@kMra{&29HEP}D=Wb` z-d)ZZ04zLPc5nFky8XGgvI|>!tM|9)NCmPr()GxY#!)HPAzpi9 z#%Hzrh1piuws_^;5-abx(QC#h5Br6UhF>}W6p&v7R~&*MCERv!j|<^o7z62>mNOZP zsBkPzP_rhLQ`SY9Bm7xfxIqBuJ*$C1*xm?PlM`QvuCJdzULI?=nf-YM5!t$R59OhG zD2Imzzaxz!j;7QBp)|__7fv=Xz>c_{LEeT$mt0NRXnk)j&BCR;)y&${3rr3GknmAM zcV}Oex!n1k;=8|<*YuNfBa8)(5H`Z#6-y{HvF4x=NXkZfs>NMRUa95q4wvs8SxfV@ z;9&u*I&1`xGKEZDqiF_MJL|{36KZcRU_PWjh)_Op zjaMbhxq1M@cY=i{_abwwbJh6VTk3T&&%C5&r2(^(Z!DFW`K#vp;mr*JLhmq&YY6Nn z=kK?Wb%EF0O?$Ylq==0Mlw+_3h5h5_e)GKfi4vc0XgJkG3|OGm&?IjKVI}B!)nGFh zmF@C&qaSMDL1~QYL@(;Jz5u;nc)9}pgXYjtL6;p_OOskdYyVHQ_6jp=1r*&3I+&Zf?VEyeJ=_%k zjk5$@*u8!RMz7dqS!;!|3H;76K%y%9H69mPE#q_Pr&d69m=mmWwO>#LwM`QT* zh;t30WEzcJvrlu9si1B!IVKa@=@~Q01)qe&;I(YD%nWF^gUus$-Y11AAhmC|3I0RO}N&{$THU zc}wr8c$nm@lccY7sh}H{c#6w0>2k!bJ#ZX=Skq*a9fVn;@-!#5lAW)L9Hk-F=XO~GsVl>P2D5_ zHCPctGGV6LwfHq_;Y-$rW?{{Dcupa70$JQ( zX1L5`Xk7wH3NvdrZ?l!m;fk$oWd&b{!}#F!kF~e05CeBXiSLS<^}C|tkBJIiP#3LY zD_ptO*gRreLKMx@Z;#E7=f_R8f5WS%Mj+|H6Q+X)>%tllyk+gnC3X|e2nKKR^J>Ls z(q?7S4MFH=reRyD7VHmD^pQg&kR+e(9!Tb!T(Cj$d?cE!pb%)I1$HJF?yjup69fT@ zLMS;4RX--BL$ct5&brp1p)0>$XlRnF)pQD>v;R-pRRTK@3qJ!);_{%i!4Fi&H&Ttb1scuDhu))Nq^i-H5YCny1b4c5mrMWO#i1U4cV1e)iSSBTzR z$Z%&e4a!W~l@+tNYI!csKyM_WDXR*uuur_A=Z^7r(ylC=xxr3|Bb#GqCpIjz_D&Ix zsEQQ3a|o1vkXfivYOvKgx1>`8;&b%vRO;z!1 z^*NApC8Dy!YeX(VRHzpRdht`3SP3io!IL*luS#qL-d)`*6dQpg1*ry?FPM#OjFx@b zkgI{zIJj_WjG-UL4V_<=-2&lU@4X+5Y={na@R zp+|;RQ?mow4W0P|`;Z-L;lcy{bhv%fsowA%;~2McjW2X6((>t#ADGO1#~a}C zaloI4FdGX<+sSMN($#1B#_;{|_wB!}WCg5Dy6|u#-eCXE9bT&VKFg#)51$asOl+hWhEmd^#!44sK2~$+>KgOgR#Me|&jSo*+9s(BT$L zVa@bI>oe$??C7Y@%*(kGZzZ&n-VX}(%(a0{kkBZivu^@m0ka(2xAQiB%A})-(N3Ls zD~Nz572l-dn-uKiU~((7GtvzJ5<|_Mcq>8f*f>9U4hSFu`=lGDEKlTv#l+8S;Jn}R;8+jtA2l(K;CQb5Iz*y=f^8rg()dv=ud_?)+vj$AX) z90|Di2@FWnO2Rf2D+wQJe8Hf*IM$2Y&<8czhY^GpES0W9iNPd)!=eVVV&Lh=pv(kj z#b7Exc99?5`+Luk+(-AxyZ8r(X&)S!eP}EOxDiy4MCqMOzbFBrL)`H`aHD6yQ+*&~ zMnpoxG0gv)D^yB%J#`sdlr%=jxu>26tMS^YCV25VZ-6cc_3Tr$dXA{{_wOu}UVTl^ zGUz=nHKD~w0~O7?{s&KoZ3L2LA(Nu_^g!97hMN#LC_3|_8%9y%r|&oXJn%Y>`V1WH5Pdp@-EtUfj4eg=~Q zQ^Cbm72qLtG~_3Be);*4SWbyb79(frKClp}p$R_HoMpjuW()WFe-zjg;L3B?iwd%nq5S;#L{6 z#f?Ccf+BG|Zs`=@`4w+5CYhzqb%PHCF3ct!6HioECz zbk+2D+9xpSAW95a5wZZXdV#F#9KhV=UcDYr0iGwqC{N?3AwtC+wJ}0=;1@@K=^XyW z!K>5Ey*o|r6)oVEYa-3v3FA=B<)i|0i7rQ(5>Wgw$)y@Usbj*m9LlKBm)7k}tCEbZ ziQSAeJV3zOoQ5X3+>T>7r4uwuwn{ZUwY){c?GIG*Z9sp~eE;KR`|TeOrGIGZ1ABkG z)q&34#N;Ee2GPIak0lm8_bmw=O)!9c{-%c%YKq`S7{gwV`Pdb)fl;(0q5v83c8Ep0 zA(mc4UBcN8`H829nxXbbEU_(O^Mznp66X{fPAqbVZGAshdVDZUlL8IYFPo+AO-+C5 zdKAIw1N?)GA)GXLFhVYr3i(Vvi`%_Ecq(4jU3?S3r)muheyuhTI(i&?P9X)M2Ai0U z^H*L%P%NWXGc+rc?qEqR1+1Wv+eHGye>fWy#?V=6jV>EdqarVKE)|QT zG2uSpWfl6ae39r^yQvi1BO2Z)s^MgPIys*Vo{ei{v8vbMLPvqDoC{R}R1^Lh1NKN$ z{Xg%)I~u5_%od!n4`w4M_?Exv879-#L=pi!&`ea}g0U3*e%FXEu^Oz<;8HWLG{a$7 zY76Mzd-emKTQOF7;Vd9gZOIHi_ytt+{BL~1Q~KJh>%ZdUP(_LvoByABRa-TjdTET1 zKPXl6`G0nW?58Yd%?>pZ&mADMV3xVtCNdBxj=` znS~V3aB?P7*bKVoHkZJWAu)DTsE#YS^%}*rg*3o1f16IQfTp&u590dT>r7~beVnefXjF<<_ zy8vc}q2+}}%W^?>*P(TZGJT?)ZxvmwsZj#40i=QuQ^4k5^dDUEzKV`O0z3Cr=)MS! zTmqvHTD6!vxSLt3d=turOYsILw7ALL%|&x_IxwL>a1+7`Eyn``UsKT}XR-6?o+dd1 zBNh`p2n3;qS6>inie&pxsEQchrm9jqZo|+RtK2(H8bJtY;mS4Eo9^#V`_LT?$JO`ky2+W>$Dlsy5+onkwPOKNTk$9ZmS;LTi}pWd?pIQsL5#Oe%;1 zK$&4p)SzcBPb&`T>MSn&nyN8Y`9y~N2SG_)xJJ7%N@iG2?V=5aOb{KP83qjF z2M*u|Zu5^OZbFSiCvo!Nr2b$209M7BUCPX)v&-F$}?g_jzXJ5{meL3S} zXPT`*I;A7I4)Cp4H4_6RNRprANlCIHT&j9WiA<%wqiT$n6$+Sw!Gr0Uw<8FwoAICr z&XEh}X_6PfXD743h8d`EgE`UQl}gM&O!UayaEgCaMUR?rt}w`CL4q(Tpr@$Dk(ZDOePFyj;#(w|bnAn7NDwJ1X7ls!4<$V`shY@5a%L(P zn3;xED3^4^|0oT65142s>8O*?3O>dx>8;m0;Y$2r1sUww1>gWrHgb$a6Ti4_1O=0a zNpH|neI#yRfYI(QlmfLEVNugQC1D-hp~vN;Yunk(oA|q zi{qk3TAW8q$|9H_P0YhJhr_{IQ-?*@cp^lQM9UtyOOUVLTktk_de^Q2r5sEud2Mw$ zg7IMyD@^fZynWGX_$q51QyLN8j;+UBP68^?3_@U?*fcEolF%~z87 zet7%(v~Qk%NOQpeUb=rh4d zxUP(O8nt>QF-jO)ypZM@oJ~?mauJ9?=>em)aJ$(&{XnseRMHDQxB)OOM#fW+8v!M@ zM0Wk^`PuOsS|i&cNH|vU#GQdR0St9b6hqLW*?5eA()8h{PBET@TJNV-q6PPMi4TR>eVRxj41Oxy7%6LX1vaJdW(9qqS{H`P_5B1{I2NsnkF~@V6*4Q4 zPh$G&xB}bTYT|XcaN=$0RZ8YaNSGMXHxOur#wpz3hG!Iw*6;*O&6Q0<=!z=_X5}LRIV&c+=X5tg_ z#AOOJ$uTxGR+$boDJ5)SJN6vEgP>Upi)=8+p7>G6gb$;tnHhfW#9M)ESR+Qu$nL~f z?J&avv)hfZSg~43!RtiGl+?y&>1dQvJ(_|=M$rvJB7BTqMEy>r{ATq|JJ3`Y49MTlIwVgLlZ=raShUi$oFp( zxBRZaIq!*2LsL8rO?nKP_!z`Ph#Q*Zj!PX&6NH)~nDt_<;STjo;wXt8alkt9{GdZ- zs|{wfCf+Xvt+5FCkscXxSMNQ4K4^D1nymmm@}%e4iQDfq0!h@eRC{^gWvbTEt3>+~ zUer#U=bK`Zf71Nk)DNPMp!222QsU6vm+M zFNkt5wix(vlT^3+q1V@DCUnlUa^?hPkzgB+41!3brZ%jp;lO?J$bHh#eM;V0 zlu%)}{m0WrO1slS@hQ2>NqoNmKpr?=f=S1?$I~QNuH&{&!aVBvY+?gSQHRMoWhgm6 ziQCH)>2sN7Iz-O0OgJG6<|vqH;vOV`bdyQfIMbyONK#-rtfitR&xB0Gy6@ASvcrhI zpUC8uMK9Rxxnz=37Z|S8V8FE2ZHg_P#EL_)bKP}JLLz%oB0n7C6! zBPckJ6$YGP%o0YQs2C}X;zfd-%2^g?v_Q+s0>?6#KbFL`1kNOwz!%^HLfknC4u+r55JJ)YQV5l>qW;2^%3g7C8v#NQ-Ut{-2#WWK zC1PmZ)W?f@4b z;w2W#^M{B7>Afam*1M${+iTOL@Y%_vpfVVgtb0k||)n&jw2X3YArE1YBg zZ^lQ$4NY>nV$zjY^#3%;9rNsLdj>j4XdKt9Y~*DQS3{N0Z!EYU;!yYc&nM zvzztKZtj%ujt6DJl^c8KjIgih+*fo=+xcuKy4)2xD<|>6Db^7<1-SFlBvFLuH8I=;vIe>PW+RZ) z8tLbx4ON?9Y;-CWt{|XEt{DA#3#8|9MWikSEiQ7e5(BAOOI3=P;8UjaHAcQj9E7sO z`>OX3nsxu5v4mvi{i@HR=G}_LL`ltAS4{755y&zH$}LU4KDh;x>Z4cOx`xu*+o9z; zU{dUh!;``rnC}`Bq^PhI6(&;5!cmbEs_6<_Z&g*Tca{NW=x0u((WnX z+!w!inYmwJg)0s90h)K6Ije-7zu7Es#IO5jQbn8(S_Tj#G^#i8>p}iGe9IcYdDr#( zp6kuZV(<0FNZFyy;YZm4860?I2vo??Q`Yb)d%&a9a2um#%+0LKx-~Q|W^00krmU*f zm2<3BZ9ziQt}L%Ce6B?~O*pTHOm-L}pYKeO&KhXXoUdtq>>qz@>EtT&*kv~v3(C^f z8cE8qc|^~fdFE3M!Q$EAOn3fjY4#=%GY=(e1d^m3Pnb;(5*l9KE4^z~LKY$dNbXl9 zi1E#^(XzdJBaW3>7vWuLbrrXl>uTx#t1@jHiDTTg1Q}#Dr~)fdTd722DsD0@U<$lU zrjqy0pddflJo?YhQVw;oewAkv6<_Zz*94jsqL3mB_=trj`Tt+lwQV=98_Re9m9w{# z@rw62%cjiuyvZUhF{TKH0L9UK{pqUehMcve?&`*c0EjzraUbW%XhN=ZJF2ka5M`i? z!g=Xk>YQg+=a3c5ON7olxl2+EvC=_Sl1DAcVsdLyz)}>jR0~t`rgFiRH%h0Z@L>A_ z+|YDo>qI6=?`%DK2_(pYJe=#T4qI(J#w;ul8pW7Uj@`CwxUPgXp8_kZrXRq0I8A`i zC~CrtoS@e?ii%>gY6%+l+_H~APD{Mr?A5vC?6RBl(}x2+rIX@Hd(wExU)ibRQiS45 z5sEKGcnv^JmQ0V%9!p&Z<3LgKV`yG0f^HV2TdCMXTIc@LBVmZang_;h<{@%8v<+GW z`+D4y4jdI-`TQ$C&y?%0yNt~i>j$oTc@>k?x!AevWt%v=nad38`oW6|U!A)wZydRf zpA`gin9`?nuS0wc`BiSOHyGMWU$vKB+9FM`?7Vomn2X5v@oYcxX#I%8bSkR4?xy@^ zGvBm1v~&1cx9uAol&6TU;<*I(fMx&48Yl-W{PqJpXrNhP_YLP8_5fHfyZB=A@TR?4R-gr$Ndhv;-vl?o41XX!$2BgV;Oay)vH@a6vvOl1+2V}0zdV+l z30~QYU$qQKOcI0QAhXobGkqae(P9I(>ZmX=NuFy0X2+fLK7bNFkaV&$Bv4Jz z{isMfoN;!&;qnbK9-ndit&p1^Ye3V1vo?;xX7W4c;69C3f$#qQ_vfd7(u^|$0T8q! zrp<1Y%P?@9G&=OoU1qin6gozQP)`?RJ_(q^nZ9)j1IWcdSV%PetLwtR+y>VOt$qT8 z^Sr7?a(d~1n`-1ZjVzP1E?@mLS_v{U5qu#l70zso0kK(I)%>@<_SZXZD5sqVYL?Dv2q|p zzs2#J5J3nj0+FLypRwvI-;jdDn6q2D&Yl8XQM&rlZ*r~uTD{k5d{nJI&+=KjtH1{@ z#De99vctnodx!@1CWNiQeRpS_6C4XBC6|p6T3WFPy^M<0b4* z^i9rza=`GM_e%=6)7*#Lg`x{huE3wK&0ZC`uBMCDe>qUCG~+s6R|8n3p%bCZV1vzjwQD>dK`jKZmxjy^Krge#az=B@DtC&fjJ?jK=Mowr6q^J-g zNDi`2Uj$|J$X&g-0jr`#b-3C#?ELA2cL8XAPAlK5fExnRbmE~R;xyuguFjQ~gdW&g z`!N7kS>yFKuK_~SRTVw;OLSj>P)ohM;M2~!T>7gsPfA1nBtwZV1VWP?iK{2F=zg!) z0Mdj%!-f82*6zA_@YT7?45JX+>3Rd9YqJ36aGXOP4r*ruRb8%dbr`2gt-i`*)dxi9 z{-o0TTFQep*U)Q7Rn7lgeA2#q_WH#($$>!$?e%<%9N=H`c%ZGKKz-nc4^l$h?sC1*Ea z*au&2J@COI&Fs2tP(V z3*`Rkfg*!A_LG~^E}v#&(gp;G;U5vAhD_!P#~6?PQtYter6J|Am!chj#Ww&Jzcr+& z6)PNs7)_HXOwQ*14BnG;q_lR{Czb{u7AVrXo-cO)7Zy@TNsCr4pnZP1hh!zqQmxtJ zX$&ntxD@}~QgEnR!J=#sDJDK?`tQnMP0STWaR@vCx>sdC(*GU$SUi%F+&7Y}y+B4JFkXPV6HVjWpz7>m6* z2Q9jgkc<|*1Qn%|ALd``0sh6$?+535^>ZHsz=Yc}8Znb5r}SryA}YXevjcAS7bMpG z4z~?p#mjX#Qd#EDs|`FT@45jrWA{Js~YEpovG$VdGcKIaR2i9 zpDguXl~H8 z=g&as9{&2*!)p_AmQ?`PtjR+(>d|BGlC%l)D-M>cBedWZbvLLu$zh>yMcDIVg>J>& z7I(2z^5lMeF1n-wTdHZGyNT8=N6J_W$t!yvOA< zCVw!({Rbn~Q-uikpGB1FVS(N#ah4#HOxtG?LHTs{GytkE@J6u981f_(VkAFiA5^lGCSTVr*DT388IW;_uHFhDV}{Gk3eH6p zBc%c2izSi`DKXR6PrwscoTH=?IOjpQ0cA!gbR zEI0!T4(nj`JWlGIpFHyqEJOnf5zbgb6B&S-aQYRlziuw&&zB^YwE?A>lsI&>vB=BC zJCa0UAbaY601S!@0AHw~D?OG?K3L?V7o%}jYrCsaP3XE5!!H0Pmb27@hGk3-?HXGvh71vIVPg4P&ZD>_brn zWRPOllu#Bg&RF$02uQ?}{p-w_;ZV*}& zogXg`Kc8PI=9333pThY@CmZm|V$rWPH5+K3fD z2UUCtE8TDBmXdS@Li`wL5xTXe7d zfm&fLBy*NV*wDD}6h$UV%&k-qsj4ub8>91o22qFSoJZ+mKPm&ZbR?%3OM0!@h zSLNoFRUMfmPrEhhC*3gII4`I5{2?k~HX~4C`mC?bjdPbj(pbLZ9Ky|t9_Jsa%{lhj zH9vN^AQn=_Cv7vQ8JusNU_0>}ffygZ`R195U>9{2Twl+42iHZd-1`e!In(k!nZ|h; z?wa8Pe?$WmJxeatXoiEIj?_H<@#F804^MyIaZ`X6{==N}e1qos8t0O;IA2)+Y49cn z-bP%o8nXc@T##9~95X|um|!)NwFahu6+VLxKnY-LF&r6cP~t3sCa6DsLuSD`nu3n( zz{g?~%kpOgOFy`sprh_8deJ-?HZC_HKzs=HvT)@SaY8sf4?%sznjt35#O1$O$3rYb?WzKQs9-&u$%^79%t+s5%BdauMnXJK{{7g%LjG_<_SE zY=A(aKyyBjBT;#(a3f|C)+sdu)}D|KNZjWB=OfYl~M+9j}kbk4u3K8E3OZ~$JnBZ-zZ+1n553R`vjQX z24$7ggd|4j(igzjou_JIyl}!!LrRPpHldkWC0VVUW(>H~%x+Pd{b}=AuVp?b@%b)x zd4Qu16O%+IBK+`>nA3URWkX8*p>L+&aF!5CMo_%yMJ^nmH_lz&2dFlfSVD{|p#n>o z^d!xfGyw|xtO>IWd;3gq9GM4S$Xm#3V6Z+C0}dFYKl?>a4`U{zd2d7j{gH z2y&xc1ArZ))L_G?X;gyXIZ7F4k{Zn?Ej%X&?2ELBssJxIG>5bkTe5gCK1Zk*V(7p% z7g@! zOOMreO+{5#Ur6@#cg;6k_5KpCz@Z!3pyPP%uM3c(}RE>%%teTEc=V+ z-HX0U8|ay%FUnZF%))uUxB)+;1j;vMuG(Azdhi2E>u%uo<^JvAhL z@5;SL*dd0dOGTw(mX-S1%aPTW6&20~G@28r!+#(BFS_})aqhC$TEy|VtPEs;jg*6> zqG~wYpdMaCLtog~`2@hiRYk`9iCX4Bj1%DGZ!~25{y?P12)JgcK$W|24ivzjEuW~&;W%?0 zV*o{z-+?U^USA)cXrvmTXLH047N8WYiwIJeHJlFGlf=KQ%zl+`AO)HNeC=rEnKz}A+ z4iDV$GU#)z9Y44!g;qua!d z@#AnGke*}UKuW9-bF;WHDlHmvfs;1P>Wpp*BaT%+AV4R=lQ0vX$2)_G7WaBpO(O1c z7m6-4OMo0kxoU7oYvAHYRjGQvJl~0wx(jw67Y^1qtA@$6Q7kh8jMbGj$*R}8vaD%8 zk#XEp-H4e)EX0j&0J>fRXdz{|)`7P80#?kakaC}eifzMI9zuiAUHoMr%1%hY?>owVjy`4r139MS_wZCx@25a@6J9h7& z6s%fPv3p`!(8o`0`;1+m;*0ZqS2uhbSTVCjrWFwDgn!5B7}njaxQ(dkUfBs>mBy;V zSIyI}{8o>hGB#?&cTF*gtL_;Jh(}^bwJx6|mU`=gR9U`yVSHL6%M=kDa7yF#nCSS- z84z&>3k!sTd@$=7IItdZz~D@7nZr!1US#;iDbKRTr-{zAIqPCwL;qbhw8oAibtu+(qWIYq`Vh*dr?RTvc}FYhBUhL|H4z#|T3NQoIzjZ(1Y9Y%OdT%~T5i*d>I zJ~w>M0^wl}&BoIK%7*W24XNSr6WO z(Qdr5r?`81x1Qtcoi4&Ky6i>Guig1`>3Ku8)!BGIV)mrzZZ{ca)v^;a>2OHrbLu#9 zbdksSy0qd18AA3j0RFrBieCtoV-n0jci~Q(iV%ruZ)4X!9*Fz4z_8LD!1_;ncQVfl zZ+QeP2%}MAPAnO+f%lG-ST?aL$e5LDP);NUh)0%k3vr1>V8UFHW$?C71_C}KbCsD1 zmo&H??kRB&dmor30{f!*$KE~r*e{|VE@obz0)z+eoKr4Lgpb~mUFrMLe|oV2#v!lH z@iiB4#9{@y#^N$E+1V8JkD(JqC(fQV$d(Q$H83hI$*TsbjaI7j2ahRrgPUSj&3IMd zTbr26INjZ0V&eg{$WdnQ`QXZb=WcZqm4taW2cFs=i{C*^=nTgZ0}S2hDx9YQzIwL% zP)}+XAT&8t9>H}OWqs+CuIu8_;~rO7Ubu*74a7v#R;U2(08ri@_Os`?1Lie$#R4nPTNALu5LMM zB)^-KjQIBHORW97y!8PE5B%gocuc{WA&9DzV+t3i{p9{@l{?{i2xFkdwG;0)C!DPk zub#~qW9s{VslL@yL2nWq?EXjkAG7>TtQZHR5te=Mul|OX8_X3}pM}E!ALEEF`*Dp# z-SkF)a2$6DNBqP5-MutZw($T%G;m1j}^zAE^j0emKfHed2^)vf{mv zPwY@q+JEj4$-UTp?toOa>sFxB5I^4(G-4K)Lv-h36goVt* z{fR4)M}c5&Fe&$-;wD3@{K?$^IYf^TpJ#TYhRvaJ4@VU{F-cXevJT_{Hbjr`{TpoY zaHZHSPh50ku<+_lPQ`5RK9e`?*8k>7L9&mR(y{ieFtkb=>$((wF&GV7oOcyf9bFyO z_8hfzaA#mq_fbH0aFYF6abbh)AT*wKN1g9t082@YC57mi+&TN6wdZ+E+zhk}V4B6R z6WQG$wK}t(kO%PqsdOFJ{h}l|uKk&8Q0h2Xr;~csC*|9dw$TNkRc{Qy#y$|I>Ijdn zo9E$%_=*C#&eH)nOpX8iasRJ>y;JXf!;?lFT72$YKKJ-my>l+P&`WC&Rq+I>Iz68E zS%8nTo2tKVK$+GUEhIJc;Ge6$OhHo1d`i4ML8bPjTWfX#^mAQ2XAKAS$H@`E;p{`L zo%LL;R<6FF0kG=fsG3B*34U}mR*;v}=y=J%XH}zUTv&()efx5td%davtN%&S!u)4t zRKOV1uYS_V@T=#j?+fFWotPxW9Y_uazY1t~gX`qYD$kQ0*aq8(Bs=4Szb4ipUz3(K z3L#&MuM=-X6YIzau}x_y!2DDp&a>Gwg-358b`|+qH@nFPUSLU9ee@?FtHS};kU0Dz z`$DpNWWe-j0cg(tGSE@xTi`{ZVCo8tSU@GFfTg`xej`^^f!m{YVv@3pGEFi~UoN?~ ziLaVmig=!PFh-n~r9c`I$0>!ZCeBz)0rn-Pp~4w^Q^nZuI0y}+ULmxLcYV&70N8zJ z3Ii4-xd$q}%X{o+Ms_YJ$=pE!6W58$o_ZNCL7 z!!g_tt7m;?3wBN%QJuYHo;^X&N>Ed*pPw2(5&84h%=s*!-@oNE0i4ERMZ1csjtS`4 z3(wT^WVS^wEhORLc>2&)S&+P!IVDA9O3H$h&qz()+Rv)XXBDA&zwD39`i}b0TE#XB zcC{BWYssyE`UtanRP+Ag1_;dDTS4_#GWSPnl7>|R9Sww_vliY<2F`rK>XC{(9k9$z z7TDm-|6EnkFD)&xHxuCsJl$0Lqa0(^iE=DkVo#f%9PX0c<1i)Jz%7KYZLYdyJnfW9El z(^gYbuLm-B!%W4@Knz$_4v;F7&y(Fxk*;ND`sl8-RT70RR#FvHqgu#fpISf3#5-Ki-C0M0cygkdxTv_8< zV@s&j%gEm|TK=BVBq*H7PF=u-SwHt$j0n^#vt-}Sz9Vu^IB3l(V;ScAg$mT`@zmuq z9QrEB_fkSEqd!Jd47o>Jm`^NQy8y141m@7pH%%t+QMUl!o1?0e%fzuv=1v&On><>E z=tCM_KRyW1PlD3^H9OO! zUY*6ga3wN(ESG)E?p%7l2r$*F7i`b;FkDy%?tP1F;RccjgXr@2->>(a+vA zqZqhUP#=@L+-@n6F4!%;+#oA9VRx`_d$@v?c&OkmfN6Me{yJTe0E*!?aio7$m3-AO zd%oLQLeBgyYEazezH0ia)0YHcfK&1w)2xXlGERaL@ySH6Jfd>hiAkbMZsy_k{~d@< zoW(Jw`(U)DdmDGoUGBBE*(D{14(KG)^OuDBSWFUZS(}Vr88{gS3pkB#kTDNqT4!Hk zw9c$=c4Csu+JbcpcRMoet&|EzSS`V?sQQAc0=#JIi>FMHNK!RD;s?&~>v+pm<50Z_)mfl;Wm*_6mdQDt zmwj-7XqZD#Vl77|7<64wA zIgz&0bQ-7yX@^}J@dm=ypS=D7q()c=ILdB+<#c- z`R*-1XfhCvrvXb~7LPZOi%!^!G54Eu$fKTwm|oCEzJUN))Gk^a0LFr3z(Q){2*lPn zHX>bc_TAfFD*!LzofJ4@=I-tO>EUPd`17Ia)*LVqU=0;)y*%E#yvkcro)Z1$++_>X zTScz(0FG886)JI+q8RnsD+FNUnKv;>sxZ)QMrvCNdgtvZ+30>_N8{U#DGA|Vf_*lz zZ(@?@o*-9H0+q1k-e~fg0HN{5`z=6d$$m;A>wLiDgiTj59XNpeHS3N800^l1W;w4zhFW z=|!*Q4Rfx@H1{tr*unuhQF?|Qy1Xh|i(jx9Yjmbm0D{*U&t=GRiJX#^i!ue6!x$y| zZNGM0E(vtR!9{QPyMO$+|7qq}SWD`uyK(^uxq{+=8n=L4yPj2ISNY03t$7sjMckdn znRyqTnP=>RyQ$3KFNEdareW)Iu{dH_{swZfo|6%4LU@A2rN_BJd~=SjfeL_iGubNy z!7KExAw)PvZP26n;?AZw5JMRK80K=-w}+=4c2Qrjx)fec9p?vcT}vW`Z?aJBgHIJ? z+PRV)dr{Rg6;+*ELZf7+1EfKIW1;HX!_UV*9;n281USQl`Vf@)&;e2wsqm(xh@zqq z6O@)k7(uJR{(NKk^Gc@D{?wA8(w|Ce30x#5h-If zJffWVO$!KqEgf#dG7H^0QPewOXwb=c14YZyvupA8@bZrbJdqn_$WRW9bLxnIo})MU zCU&VG9EhB;gagpR*|H1M87n*Ba5Bjr_Vf8CBy_>ar#-aKKkp^=nQ>$}*43=@@uQQ2fe=phJ3x*sQzlQA6Td~0)=dCCATC9&FBi|}A9dp1h zrp&Ki8m8ATR8!F5FaQ6lH$k0-noouukWNSrz2MlQ=++ znt%kvz76MefixsFJdhwiy*YPTE#QEAcbpA8kSRf13< zC=S@ZKR)7*w}Cbc1PD3%#yq{SbpjMk1Ln6Oj3jJ2#Fq-LfwBNHy>plOI*EpxB_%px z4hOEeS)25h0#)7U51p=VDn9Iwi3Z47K+}FWA{P#pIO<+K{`>=#*E0+ceVAFRLhBuG z)j;DhOd?0-&518=Vz4G$(Z^R~^)uTV2eeYyO&pTdRG2HU2R_@7!{-g|>imI&?106N zD9N>&CxSvhEYSC%P{t5s-XLU}B=#W4;5VxXP^I3iJZsruupty)?c3waKOUbR;WY+T z&Y0Z=)bvsCiq7#>o_t4Zm}b|ABV`v{`1~d&Dc{u@l(zt(VN|kmTjsUw;;@?enFR{9 zspPb;MAQatPRYRbAgd+PIG|gR*P7G};6{8CdGJ~x4@^*e40t2Gm`5Z%DN?L>7sA&@ zy$#rbJRtfQ$_y8F+?Bg7(EjQXqTl%YAq_yxQNJ~3AYgjymMe^Lr;`A@0YGmxA;%Dm)(O z?(O+W=L5l`^+N+kLcBZU=U{ZTo^j}nIokb&Q*T^@EFu+p2&S8S3vI>YwPK-qlhRF4 z?Urz}oxi7qi<(GbfFxvZJS|0Mv|JJ|jn4vlAV4k^xC+m~5tkQEy}prN{KOCOC3tq= zTrU8pd*I{NTSZmZ-IU!;n=5n=I(!iYASv$0W{x*-y2VaNQrJ;*f}_LUM63spt}Jt8 zW@o{NPvqHk#8xgW4MlB8hNLCPKUWVcifvxSQo+q-Sw-SO&@p1i+nK{k%+ zw>@rRGg?RxqSiQq3pta*U)*@Yn}Ii05iRM)wuv;POqOAR4vzB5xQ(T@xHA~Q%9WVB zkQfrZ;qm-i5cIe=pE_urZ=5;chBXj;e3xhy7h;6mv*F_47G%@i!JBRlCMzd$_tWD& zFy3ECim#riBNWxN+QyG_q`f4$;RCO{gn#o>_FLy0&)IKD zapMc%NF>W>a9T3j!YTV?Ogt{%*?7L9QR~{n3^BEbHxDm&xHM)%Jplr>n51tYJ5qog zSr!zQDQQna{}}tpT?`D)M1nv}g-ABpptjB_^Bqk74kmvGQ(@f5Bq;r|#VEYG+OlF^ZpVqpwh=a855K`$8Pg`QGoj9D3)=1cbwn1Tz} zTIalvrv=%xe~XOIIeUQ4^yVzb43AQD@x&yj9Y%JUNo0lLrf-W9THIx~Q&X4_GtmKm zSD3?X4$YN73AFSol#Azxh4;ldrwh6#VknHE^9d2?ZuE)q7x>X*6u1x{zb z7}8&~^-G!Umn`EUSFR`}rwChDFN)VK3LcS4wgt+9gfIFn2oTalOi~tK#PCv$tBeC& z$>>`fV8aX$2oI|QKtRa`_hHn>EFp4Q#77`8dPmMr=`Dy~aTPaQ#l2SXnNd6kcLP0T z86sp%=+-%9emT^|S1l>wUVV3(?dS) zHoDmZUi#8B{PYuY)zbW48hhT4D^P!`!3i*Ca-XjZcDmx>A2A^l2Mp? z06@Fm6+?`MTn++}&CQBV6Fq2W+g0l7@=pAfHrGtM|aenMi>wJsTe+WEk6M~6;1^TZo2)~QBAl%B|_6$?> zBQUwvf^gQQRlu`8O$kiYo_YV*BAk|fMCYY_cy0cAetCGpI0v>>mGGT;=4U!BD??j{-Z21(WU_k%@>Ir*c7LWkYDC zG0YM}{Ox-54M8*6nRFIKCIF|=1lLA>Jp811=&3mLF4chH-07B-SOIqcO^r;28&^xU zq}%PVe!(n*eRjFH?B*X=$;P)jFWFYK`6>OabC*jJN}n=GpDw%2{G<|;p@tbY=Q}M?X-SD?OV{WUgnd3} zmXgI!fNjYMH~zMyhL=%PURjs-I2;{K0cNc2B+O!5vQiNr<{it^z*+Xzxy$lA`wFx6 z25;INIET43MN}!MbNdCtrxF3BO_u&jPaJsxsgXW$(AbJma-|;QFMsft??X!Cyw9)w z@yJSxChA+l43|zr<3arGl>l-r|9c1s53_j`)X3_I86?qdtTn2Lp-NcT(2ALK21b>V z5()AHs9~1Pq{L3R!q?wCaGH*Rvon?$2ceOf$3Xo~UPjQ80w+-yJQfI`g>JaxSYgvv zgjP%v0#Be9F<@`ahSf1G2oRpl-Rz)<`Ow)=xFsc~UmZ_TB|l*YGZ9>JJU@KG!%uj~ zR@_&)Zr*MWv@>$BC??6nH2QPL?vtY%14w!?NY!4ZmbfS7I3bqE3WVhmc4^8oHt0JD zIqD$e?$}+ooQ=2F{P^_rc>h=M*SlC();hnfC~GVFzOoEjtj0ujZhk!P{_*2)@3g^m z)l35~6xeWjc^kyXcBtV|yAN!Z26i1-dLDoN@x$8hhZkxwMT6R)WtE_PM4KPV(K?4b z>L5YDv(jVIo1OdxPTh|gHB7mX+87BWfFdi*B0J26ekB+0AE<3>{ClP4l)b13n> zO&Y8s30kGY%)qmbA2=m8sCw7iVL8Tec-sJwaZ^VtCW%^jhJuSVgzU{~F<~nP$+Ky8 zLlyvZEm`)pSfmx(v|eP>Vw6^F^OYl;uNr9|Uw=F_KPW3-`!GMand34@)6aK2g$aaV zLGFXdu{kO6Vd2E4UliLqr>x{Cv`*V-$y;yGjbR5pWrEOzE8wXbXV>-NqL&N4Gc^cg zOo-wO(uCynq#DRTVqS9)8d)0jKxbi&E>Y_ovJQKwD4wJ<8@9nmBmU8(bsoz>Z%An} z`L?U}26@z#FBxsYrjLH1x@dR{pm$Q-euYLE;_3yh&Dx2bJhHqwedzG2o> zH;-)kT(kLCn$231g!&&1WFlv#Np<7!hNBd=BrTN2M(bgwFaKgC02t0RE@b+mk5!;x%MDEbsw3O`ko$P5P()we^z}-v#?^4=o&zQc0~W{jBN5* z*n~42)=UowLL=)127C%TpVs8JX!aG4)P1nlGoVQV2bqXrO&y0@07?bp-V;c;PN2O} z_6>pfy}NyW{`2ke=^->DpQA9ywSup=gRXw!As4NfB*wvVOUv$K(#6i?GQb-I_qUZs zjRI3bkd}itH_tDBsI}oW&MjxhjHnFo66ZE4FQH!470$*GndOT}L0vRS@7!e>C~e3Y zG$J0wPTb3s5Bib|+8P<0u@fG{J9QVpDV|~JOeC4FuMGeVt@9z8Zc4h<75pmdK}LN} zO1zMHFu{@G+8cCjHJ;_S;PMUT8&=|F4IUD1=4wfa<(>j6T}_v3DR~!Q36N#MBN7vI zvDmx~@NLaG;aHI~w=0{_AxQ8EGv0n~s*c@jNs09=1B`&UuJUe4<38`!Ic4P!2cgMA z9xPt^-HJg1y)b`RYrzxvjdHt<^QD69h@RuP@QufXx1_`f2TuwIEuctcQewVS2S_=@7F^t=Lx2lm?;|q`ivc zqH8Bx=ak*5hIZmzfW?8^De_+v^=*LADDN!3LaPn1six&ttwo$KIKbwk&PC@)I`0Bx z+7x(se8vTUPwy~feE1=o=Nh#jI@hQrC0?A#k~}L46tXy zwaz!*r`3`Y(=V^n!S$8|m`PGZH>pO}{aeNu*2GMrO@BY2V zHZRMjcmMvwevkNzkiLPKtv>+X9!Z=U!;gOuvjLk=_It!%g!ByrW&rTziM~9^7gIt< zl{97*e$#XLPVebItkb?6gbSF^jU2fvIg>j&luLIix7WM(J1$_-;-xEQY2QJBEN!p& z`uFY&eXrK#yrPE*f>gX7M)u|{G2hKezce!K`6?2f6m}5cVurU)kVde*e9qs>=?w&g06vUax&ItB@=R z9AUka68A3kAuqx6UEM$wz*SjJOiDciifu*jAlg}U<<@#o%eBVng>}Z>6YN^G{tj|g zXY5_qyu=n=d>zxV+2WO_m+~;pKcmF5$8|yvL$^C!04rEmo Gj{^We%n*YB literal 0 HcmV?d00001 diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpSecureTransportTest.java b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpSecureTransportTest.java new file mode 100644 index 000000000..b77de6d4e --- /dev/null +++ b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpSecureTransportTest.java @@ -0,0 +1,55 @@ +package io.rsocket.transport.netty; + +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import io.netty.handler.ssl.util.SelfSignedCertificate; +import io.rsocket.test.TransportTest; +import io.rsocket.transport.netty.client.TcpClientTransport; +import io.rsocket.transport.netty.server.TcpServerTransport; +import java.net.InetSocketAddress; +import java.security.cert.CertificateException; +import java.time.Duration; +import reactor.core.Exceptions; +import reactor.netty.tcp.TcpClient; +import reactor.netty.tcp.TcpServer; + +public class TcpSecureTransportTest implements TransportTest { + private final TransportPair transportPair = + new TransportPair<>( + () -> new InetSocketAddress("localhost", 0), + (address, server) -> + TcpClientTransport.create( + TcpClient.create() + .addressSupplier(server::address) + .secure( + ssl -> + ssl.sslContext( + SslContextBuilder.forClient() + .trustManager(InsecureTrustManagerFactory.INSTANCE)))), + address -> { + try { + SelfSignedCertificate ssc = new SelfSignedCertificate(); + TcpServer server = + TcpServer.create() + .addressSupplier(() -> address) + .secure( + ssl -> + ssl.sslContext( + SslContextBuilder.forServer( + ssc.certificate(), ssc.privateKey()))); + return TcpServerTransport.create(server); + } catch (CertificateException e) { + throw Exceptions.propagate(e); + } + }); + + @Override + public Duration getTimeout() { + return Duration.ofMinutes(10); + } + + @Override + public TransportPair getTransportPair() { + return transportPair; + } +} From d1c36ba7d9e61c3d9d3c5d79b03e9a4ba424ffee Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Tue, 24 Mar 2020 14:25:05 +0200 Subject: [PATCH 07/62] updates to latest reactor / netty versions Signed-off-by: Oleh Dokuka --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 80b16619d..262015af6 100644 --- a/build.gradle +++ b/build.gradle @@ -27,11 +27,11 @@ subprojects { apply plugin: 'io.spring.dependency-management' apply plugin: 'com.github.sherter.google-java-format' - ext['reactor-bom.version'] = 'Dysprosium-RELEASE' + ext['reactor-bom.version'] = 'Dysprosium-SR6' ext['logback.version'] = '1.2.3' ext['findbugs.version'] = '3.0.2' - ext['netty-bom.version'] = '4.1.37.Final' - ext['netty-boringssl.version'] = '2.0.25.Final' + ext['netty-bom.version'] = '4.1.48.Final' + ext['netty-boringssl.version'] = '2.0.30.Final' ext['hdrhistogram.version'] = '2.1.10' ext['mockito.version'] = '3.2.0' ext['slf4j.version'] = '1.7.25' From da18ea1e0797032bc6e29dcf58fe5c5d60ecc595 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Wed, 25 Mar 2020 13:47:54 +0200 Subject: [PATCH 08/62] Reconnectable and Shared Mono feature (#759) * provides a reconnectable Mono feature Signed-off-by: Oleh Dokuka * fixes interface naming Signed-off-by: Oleh Dokuka * makes usage of retry only in case whenFactory is set up Signed-off-by: Oleh Dokuka * refactors to convenient Retry Signed-off-by: Oleh Dokuka * removes noargs reconnect() method Signed-off-by: Oleh Dokuka * fixes tests Signed-off-by: Oleh Dokuka * Update rsocket-core/src/main/java/io/rsocket/RSocketFactory.java Co-Authored-By: Rossen Stoyanchev * fixes docs Signed-off-by: Oleh Dokuka * provide test events logging Signed-off-by: Oleh Dokuka * fixes test Signed-off-by: Oleh Dokuka Co-authored-by: Rossen Stoyanchev --- build.gradle | 4 + .../main/java/io/rsocket/RSocketFactory.java | 107 ++- .../main/java/io/rsocket/ReconnectMono.java | 477 ++++++++++ .../java/io/rsocket/RSocketReconnectTest.java | 165 ++++ .../java/io/rsocket/ReconnectMonoTests.java | 868 ++++++++++++++++++ 5 files changed, 1617 insertions(+), 4 deletions(-) create mode 100644 rsocket-core/src/main/java/io/rsocket/ReconnectMono.java create mode 100644 rsocket-core/src/test/java/io/rsocket/RSocketReconnectTest.java create mode 100644 rsocket-core/src/test/java/io/rsocket/ReconnectMonoTests.java diff --git a/build.gradle b/build.gradle index 262015af6..7e5eb9823 100644 --- a/build.gradle +++ b/build.gradle @@ -119,6 +119,10 @@ subprojects { test { useJUnitPlatform() + testLogging { + events "started", "passed", "skipped", "failed" + } + systemProperty "io.netty.leakDetection.level", "ADVANCED" } diff --git a/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java b/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java index 44f64e550..6570ed7a3 100644 --- a/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java +++ b/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java @@ -44,10 +44,13 @@ import io.rsocket.util.MultiSubscriberRSocket; import java.time.Duration; import java.util.Objects; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; +import reactor.core.Disposable; import reactor.core.publisher.Mono; +import reactor.util.retry.Retry; /** Factory for creating RSocket clients and servers. */ public class RSocketFactory { @@ -95,6 +98,9 @@ default Start transport(ServerTransport transport) { public static class ClientRSocketFactory implements ClientTransportAcceptor { private static final String CLIENT_TAG = "client"; + private static final BiConsumer INVALIDATE_FUNCTION = + (r, i) -> r.onClose().subscribe(null, null, i::invalidate); + private SocketAcceptor acceptor = (setup, sendingSocket) -> Mono.just(new AbstractRSocket() {}); private Consumer errorConsumer = Throwable::printStackTrace; @@ -125,6 +131,8 @@ public static class ClientRSocketFactory implements ClientTransportAcceptor { private boolean multiSubscriberRequester = true; private boolean leaseEnabled; private Supplier> leasesSupplier = Leases::new; + private boolean reconnectEnabled; + private Retry retrySpec; private ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; @@ -230,6 +238,86 @@ public ClientRSocketFactory singleSubscriberRequester() { return this; } + /** + * Enables a reconnectable, shared instance of {@code Mono} so every subscriber will + * observe the same RSocket instance up on connection establishment.
+ * For example: + * + *

{@code
+     * Mono sharedRSocketMono =
+     *   RSocketFactory
+     *                .connect()
+     *                .singleSubscriberRequester()
+     *                .reconnect(Retry.fixedDelay(3, Duration.ofSeconds(1)))
+     *                .transport(transport)
+     *                .start();
+     *
+     *  RSocket r1 = sharedRSocketMono.block();
+     *  RSocket r2 = sharedRSocketMono.block();
+     *
+     *  assert r1 == r2;
+     *
+     * }
+ * + * Apart of the shared behavior, if the connection is lost, the same {@code Mono} + * instance will transparently re-establish the connection for subsequent subscribers.
+ * For example: + * + *
{@code
+     * Mono sharedRSocketMono =
+     *   RSocketFactory
+     *                .connect()
+     *                .singleSubscriberRequester()
+     *                .reconnect(Retry.fixedDelay(3, Duration.ofSeconds(1)))
+     *                .transport(transport)
+     *                .start();
+     *
+     *  RSocket r1 = sharedRSocketMono.block();
+     *  RSocket r2 = sharedRSocketMono.block();
+     *
+     *  assert r1 == r2;
+     *
+     *  r1.dispose()
+     *
+     *  assert r2.isDisposed()
+     *
+     *  RSocket r3 = sharedRSocketMono.block();
+     *  RSocket r4 = sharedRSocketMono.block();
+     *
+     *
+     *  assert r1 != r3;
+     *  assert r4 == r3;
+     *
+     * }
+ * + * Note, having reconnect() enabled does not eliminate the need to accompany each + * individual request with the corresponding retry logic.
+ * For example: + * + *
{@code
+     * Mono sharedRSocketMono =
+     *   RSocketFactory
+     *                .connect()
+     *                .singleSubscriberRequester()
+     *                .reconnect(Retry.fixedDelay(3, Duration.ofSeconds(1)))
+     *                .transport(transport)
+     *                .start();
+     *
+     *  sharedRSocket.flatMap(rSocket -> rSocket.requestResponse(...))
+     *               .retryWhen(ownRetry)
+     *               .subscribe()
+     *
+     * }
+ * + * @param retrySpec a retry factory applied for {@link Mono#retryWhen(Retry)} + * @return a shared instance of {@code Mono}. + */ + public ClientRSocketFactory reconnect(Retry retrySpec) { + this.retrySpec = Objects.requireNonNull(retrySpec); + this.reconnectEnabled = true; + return this; + } + public ClientRSocketFactory resume() { this.resumeEnabled = true; return this; @@ -392,6 +480,15 @@ public Mono start() { .sendOne(setupFrame) .thenReturn(wrappedRSocketRequester); }); + }) + .as( + source -> { + if (reconnectEnabled) { + return new ReconnectMono<>( + source.retryWhen(retrySpec), Disposable::dispose, INVALIDATE_FUNCTION); + } else { + return source; + } }); } @@ -422,7 +519,7 @@ private ClientSetup clientSetup(DuplexConnection startConnection) { } private Mono newConnection() { - return transportClient.get().connect(mtu); + return Mono.fromSupplier(transportClient).flatMap(t -> t.connect(mtu)); } } } @@ -698,9 +795,11 @@ public Mono start() { @Override public Mono get() { - return transportServer - .get() - .start(duplexConnection -> acceptor(serverSetup, duplexConnection), mtu) + return Mono.fromSupplier(transportServer) + .flatMap( + transport -> + transport.start( + duplexConnection -> acceptor(serverSetup, duplexConnection), mtu)) .doOnNext(c -> c.onClose().doFinally(v -> serverSetup.dispose()).subscribe()); } }); diff --git a/rsocket-core/src/main/java/io/rsocket/ReconnectMono.java b/rsocket-core/src/main/java/io/rsocket/ReconnectMono.java new file mode 100644 index 000000000..b2c36908d --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/ReconnectMono.java @@ -0,0 +1,477 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.rsocket; + +import java.time.Duration; +import java.util.concurrent.CancellationException; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.core.publisher.Mono; +import reactor.core.publisher.Operators; +import reactor.core.publisher.Operators.MonoSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +final class ReconnectMono extends Mono implements Invalidatable, Disposable, Scannable { + + final Mono source; + final BiConsumer onValueReceived; + final Consumer onValueExpired; + final ReconnectMainSubscriber mainSubscriber; + + volatile int wip; + + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(ReconnectMono.class, "wip"); + + volatile ReconnectInner[] subscribers; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater SUBSCRIBERS = + AtomicReferenceFieldUpdater.newUpdater( + ReconnectMono.class, ReconnectInner[].class, "subscribers"); + + @SuppressWarnings("rawtypes") + static final ReconnectInner[] EMPTY_UNSUBSCRIBED = new ReconnectInner[0]; + + @SuppressWarnings("rawtypes") + static final ReconnectInner[] EMPTY_SUBSCRIBED = new ReconnectInner[0]; + + @SuppressWarnings("rawtypes") + static final ReconnectInner[] READY = new ReconnectInner[0]; + + @SuppressWarnings("rawtypes") + static final ReconnectInner[] TERMINATED = new ReconnectInner[0]; + + static final int ADDED_STATE = 0; + static final int READY_STATE = 1; + static final int TERMINATED_STATE = 2; + + T value; + Throwable t; + + ReconnectMono( + Mono source, + Consumer onValueExpired, + BiConsumer onValueReceived) { + this.source = source; + this.onValueExpired = onValueExpired; + this.onValueReceived = onValueReceived; + this.mainSubscriber = new ReconnectMainSubscriber<>(this); + + SUBSCRIBERS.lazySet(this, EMPTY_UNSUBSCRIBED); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return source; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + + final boolean isDisposed = isDisposed(); + if (key == Attr.TERMINATED) return isDisposed; + if (key == Attr.ERROR) return t; + + return null; + } + + @Override + public void dispose() { + this.terminate(new CancellationException("ReconnectMono has already been disposed")); + } + + @Override + public boolean isDisposed() { + return this.subscribers == TERMINATED; + } + + @Override + @SuppressWarnings("uncheked") + public void subscribe(CoreSubscriber actual) { + final ReconnectInner inner = new ReconnectInner<>(actual, this); + actual.onSubscribe(inner); + + final int state = this.add(inner); + + if (state == READY_STATE) { + inner.complete(this.value); + } else if (state == TERMINATED_STATE) { + inner.onError(this.t); + } + } + + /** + * Block the calling thread indefinitely, waiting for the completion of this {@code + * ReconnectMono}. If the {@link ReconnectMono} is completed with an error a RuntimeException that + * wraps the error is thrown. + * + * @return the value of this {@code ReconnectMono} + */ + @Override + @Nullable + public T block() { + return block(null); + } + + /** + * Block the calling thread for the specified time, waiting for the completion of this {@code + * ReconnectMono}. If the {@link ReconnectMono} is completed with an error a RuntimeException that + * wraps the error is thrown. + * + * @param timeout the timeout value as a {@link Duration} + * @return the value of this {@code ReconnectMono} or {@code null} if the timeout is reached and + * the {@code ReconnectMono} has not completed + */ + @Override + @Nullable + @SuppressWarnings("uncheked") + public T block(@Nullable Duration timeout) { + try { + ReconnectInner[] subscribers = this.subscribers; + if (subscribers == READY) { + return this.value; + } + + if (subscribers == TERMINATED) { + RuntimeException re = Exceptions.propagate(this.t); + re = Exceptions.addSuppressed(re, new Exception("ReconnectMono terminated with an error")); + throw re; + } + + // connect once + if (subscribers == EMPTY_UNSUBSCRIBED + && SUBSCRIBERS.compareAndSet(this, EMPTY_UNSUBSCRIBED, EMPTY_SUBSCRIBED)) { + this.source.subscribe(this.mainSubscriber); + } + + long delay; + if (null == timeout) { + delay = 0L; + } else { + delay = System.nanoTime() + timeout.toNanos(); + } + for (; ; ) { + ReconnectInner[] inners = this.subscribers; + + if (inners == READY) { + return this.value; + } + if (inners == TERMINATED) { + RuntimeException re = Exceptions.propagate(this.t); + re = + Exceptions.addSuppressed(re, new Exception("ReconnectMono terminated with an error")); + throw re; + } + if (timeout != null && delay < System.nanoTime()) { + throw new IllegalStateException("Timeout on Mono blocking read"); + } + + Thread.sleep(1); + } + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + + throw new IllegalStateException("Thread Interruption on Mono blocking read"); + } + } + + @SuppressWarnings("unchecked") + void terminate(Throwable t) { + if (isDisposed()) { + return; + } + + // writes happens before volatile write + this.t = t; + + final ReconnectInner[] subscribers = SUBSCRIBERS.getAndSet(this, TERMINATED); + if (subscribers == TERMINATED) { + Operators.onErrorDropped(t, Context.empty()); + return; + } + + this.mainSubscriber.dispose(); + + this.doFinally(); + + for (CoreSubscriber consumer : subscribers) { + consumer.onError(t); + } + } + + void complete() { + ReconnectInner[] subscribers = this.subscribers; + if (subscribers == TERMINATED) { + return; + } + + final T value = this.value; + + for (; ; ) { + // ensures TERMINATE is going to be replaced with READY + if (SUBSCRIBERS.compareAndSet(this, subscribers, READY)) { + break; + } + + subscribers = this.subscribers; + + if (subscribers == TERMINATED) { + this.doFinally(); + return; + } + } + + this.onValueReceived.accept(value, this); + + for (ReconnectInner consumer : subscribers) { + consumer.complete(value); + } + } + + void doFinally() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + int m = 1; + T value; + + for (; ; ) { + value = this.value; + + if (value != null && isDisposed()) { + this.value = null; + this.onValueExpired.accept(value); + return; + } + + m = WIP.addAndGet(this, -m); + if (m == 0) { + return; + } + } + } + + // Check RSocket is not good + @Override + public void invalidate() { + if (this.subscribers == TERMINATED) { + return; + } + + final ReconnectInner[] subscribers = this.subscribers; + + if (subscribers == READY && SUBSCRIBERS.compareAndSet(this, READY, EMPTY_UNSUBSCRIBED)) { + final T value = this.value; + this.value = null; + + if (value != null) { + this.onValueExpired.accept(value); + } + } + } + + int add(ReconnectInner ps) { + for (; ; ) { + ReconnectInner[] a = this.subscribers; + + if (a == TERMINATED) { + return TERMINATED_STATE; + } + + if (a == READY) { + return READY_STATE; + } + + int n = a.length; + @SuppressWarnings("unchecked") + ReconnectInner[] b = new ReconnectInner[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = ps; + + if (SUBSCRIBERS.compareAndSet(this, a, b)) { + if (a == EMPTY_UNSUBSCRIBED) { + this.source.subscribe(this.mainSubscriber); + } + return ADDED_STATE; + } + } + } + + @SuppressWarnings("unchecked") + void remove(ReconnectInner ps) { + for (; ; ) { + ReconnectInner[] a = this.subscribers; + int n = a.length; + if (n == 0) { + return; + } + + int j = -1; + for (int i = 0; i < n; i++) { + if (a[i] == ps) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + + ReconnectInner[] b; + + if (n == 1) { + b = EMPTY_SUBSCRIBED; + } else { + b = new ReconnectInner[n - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, n - j - 1); + } + if (SUBSCRIBERS.compareAndSet(this, a, b)) { + return; + } + } + } + + static final class ReconnectMainSubscriber implements CoreSubscriber { + + final ReconnectMono parent; + + volatile Subscription s; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater( + ReconnectMainSubscriber.class, Subscription.class, "s"); + + ReconnectMainSubscriber(ReconnectMono parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onComplete() { + final Subscription s = this.s; + final ReconnectMono p = this.parent; + final T value = p.value; + + if (s == Operators.cancelledSubscription() || !S.compareAndSet(this, s, null)) { + p.doFinally(); + return; + } + + if (value == null) { + p.terminate(new IllegalStateException("Unexpected Completion of the Upstream")); + } else { + p.complete(); + } + } + + @Override + public void onError(Throwable t) { + final Subscription s = this.s; + final ReconnectMono p = this.parent; + + if (s == Operators.cancelledSubscription() + || S.getAndSet(this, Operators.cancelledSubscription()) + == Operators.cancelledSubscription()) { + p.doFinally(); + Operators.onErrorDropped(t, Context.empty()); + return; + } + + // terminate upstream which means retryBackoff has exhausted + p.terminate(t); + } + + @Override + public void onNext(T value) { + if (this.s == Operators.cancelledSubscription()) { + this.parent.onValueExpired.accept(value); + return; + } + + final ReconnectMono p = this.parent; + + p.value = value; + // volatile write and check on racing + p.doFinally(); + } + + void dispose() { + Operators.terminate(S, this); + } + } + + static final class ReconnectInner extends MonoSubscriber { + final ReconnectMono parent; + + ReconnectInner(CoreSubscriber actual, ReconnectMono parent) { + super(actual); + this.parent = parent; + } + + @Override + public void cancel() { + if (!isCancelled()) { + super.cancel(); + this.parent.remove(this); + } + } + + @Override + public void onComplete() { + if (!isCancelled()) { + this.actual.onComplete(); + } + } + + @Override + public void onError(Throwable t) { + if (isCancelled()) { + Operators.onErrorDropped(t, currentContext()); + } else { + this.actual.onError(t); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return this.parent; + return super.scanUnsafe(key); + } + } +} + +interface Invalidatable { + + void invalidate(); +} diff --git a/rsocket-core/src/test/java/io/rsocket/RSocketReconnectTest.java b/rsocket-core/src/test/java/io/rsocket/RSocketReconnectTest.java new file mode 100644 index 000000000..b5ca4858e --- /dev/null +++ b/rsocket-core/src/test/java/io/rsocket/RSocketReconnectTest.java @@ -0,0 +1,165 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.rsocket; + +import static org.junit.Assert.assertEquals; + +import io.rsocket.test.util.TestClientTransport; +import io.rsocket.transport.ClientTransport; +import java.io.UncheckedIOException; +import java.time.Duration; +import java.util.Iterator; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.function.Consumer; +import java.util.function.Supplier; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import reactor.core.Exceptions; +import reactor.core.publisher.Mono; +import reactor.util.retry.Retry; + +public class RSocketReconnectTest { + + private Queue retries = new ConcurrentLinkedQueue<>(); + + @Test + public void shouldBeASharedReconnectableInstanceOfRSocketMono() { + TestClientTransport[] testClientTransport = + new TestClientTransport[] {new TestClientTransport()}; + Mono rSocketMono = + RSocketFactory.connect() + .singleSubscriberRequester() + .reconnect(Retry.indefinitely()) + .transport( + () -> { + return testClientTransport[0]; + }) + .start(); + + RSocket rSocket1 = rSocketMono.block(); + RSocket rSocket2 = rSocketMono.block(); + + Assertions.assertThat(rSocket1).isEqualTo(rSocket2); + + testClientTransport[0].testConnection().dispose(); + testClientTransport[0] = new TestClientTransport(); + + RSocket rSocket3 = rSocketMono.block(); + RSocket rSocket4 = rSocketMono.block(); + + Assertions.assertThat(rSocket3).isEqualTo(rSocket4).isNotEqualTo(rSocket2); + } + + @Test + @SuppressWarnings({"rawtype", "unchecked"}) + public void shouldBeRetrieableConnectionSharedReconnectableInstanceOfRSocketMono() { + Supplier mockTransportSupplier = Mockito.mock(Supplier.class); + Mockito.when(mockTransportSupplier.get()) + .thenThrow(UncheckedIOException.class) + .thenThrow(UncheckedIOException.class) + .thenThrow(UncheckedIOException.class) + .thenThrow(UncheckedIOException.class) + .thenReturn(new TestClientTransport()); + Mono rSocketMono = + RSocketFactory.connect() + .singleSubscriberRequester() + .reconnect( + Retry.backoff(4, Duration.ofMillis(100)) + .maxBackoff(Duration.ofMillis(500)) + .doAfterRetry(onRetry())) + .transport(mockTransportSupplier) + .start(); + + RSocket rSocket1 = rSocketMono.block(); + RSocket rSocket2 = rSocketMono.block(); + + Assertions.assertThat(rSocket1).isEqualTo(rSocket2); + assertRetries( + UncheckedIOException.class, + UncheckedIOException.class, + UncheckedIOException.class, + UncheckedIOException.class); + } + + @Test + @SuppressWarnings({"rawtype", "unchecked"}) + public void shouldBeExaustedRetrieableConnectionSharedReconnectableInstanceOfRSocketMono() { + Supplier mockTransportSupplier = Mockito.mock(Supplier.class); + Mockito.when(mockTransportSupplier.get()) + .thenThrow(UncheckedIOException.class) + .thenThrow(UncheckedIOException.class) + .thenThrow(UncheckedIOException.class) + .thenThrow(UncheckedIOException.class) + .thenThrow(UncheckedIOException.class) + .thenReturn(new TestClientTransport()); + Mono rSocketMono = + RSocketFactory.connect() + .singleSubscriberRequester() + .reconnect( + Retry.backoff(4, Duration.ofMillis(100)) + .maxBackoff(Duration.ofMillis(500)) + .doAfterRetry(onRetry())) + .transport(mockTransportSupplier) + .start(); + + Assertions.assertThatThrownBy(rSocketMono::block) + .matches(Exceptions::isRetryExhausted) + .hasCauseInstanceOf(UncheckedIOException.class); + + Assertions.assertThatThrownBy(rSocketMono::block) + .matches(Exceptions::isRetryExhausted) + .hasCauseInstanceOf(UncheckedIOException.class); + + assertRetries( + UncheckedIOException.class, + UncheckedIOException.class, + UncheckedIOException.class, + UncheckedIOException.class); + } + + @Test + public void shouldBeNotBeASharedReconnectableInstanceOfRSocketMono() { + + Mono rSocketMono = + RSocketFactory.connect() + .singleSubscriberRequester() + .transport(new TestClientTransport()) + .start(); + + RSocket rSocket1 = rSocketMono.block(); + RSocket rSocket2 = rSocketMono.block(); + + Assertions.assertThat(rSocket1).isNotEqualTo(rSocket2); + } + + @SafeVarargs + private final void assertRetries(Class... exceptions) { + assertEquals(exceptions.length, retries.size()); + int index = 0; + for (Iterator it = retries.iterator(); it.hasNext(); ) { + Retry.RetrySignal retryContext = it.next(); + assertEquals(index, retryContext.totalRetries()); + assertEquals(exceptions[index], retryContext.failure().getClass()); + index++; + } + } + + Consumer onRetry() { + return context -> retries.add(context); + } +} diff --git a/rsocket-core/src/test/java/io/rsocket/ReconnectMonoTests.java b/rsocket-core/src/test/java/io/rsocket/ReconnectMonoTests.java new file mode 100644 index 000000000..fb7707509 --- /dev/null +++ b/rsocket-core/src/test/java/io/rsocket/ReconnectMonoTests.java @@ -0,0 +1,868 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.rsocket; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeoutException; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.mockito.Mockito; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.core.publisher.Hooks; +import reactor.core.publisher.Mono; +import reactor.core.publisher.MonoProcessor; +import reactor.core.scheduler.Schedulers; +import reactor.test.StepVerifier; +import reactor.test.publisher.TestPublisher; +import reactor.test.util.RaceTestUtils; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; +import reactor.util.retry.Retry; + +public class ReconnectMonoTests { + + private Queue retries = new ConcurrentLinkedQueue<>(); + private Queue> received = new ConcurrentLinkedQueue<>(); + private Queue expired = new ConcurrentLinkedQueue<>(); + + @Test + public void shouldExpireValueOnRacingDisposeAndNext() { + Hooks.onErrorDropped(t -> {}); + Hooks.onNextDropped(System.out::println); + for (int i = 0; i < 100000; i++) { + final int index = i; + final CoreSubscriber[] monoSubscribers = new CoreSubscriber[1]; + Subscription mockSubscription = Mockito.mock(Subscription.class); + final Mono stringMono = + new Mono() { + @Override + public void subscribe(CoreSubscriber actual) { + actual.onSubscribe(mockSubscription); + monoSubscribers[0] = actual; + } + }; + + final ReconnectMono reconnectMono = + stringMono + .doOnDiscard(Object.class, System.out::println) + .as(source -> new ReconnectMono<>(source, onExpire(), onValue())); + + final MonoProcessor processor = reconnectMono.subscribeWith(MonoProcessor.create()); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).isEmpty(); + + RaceTestUtils.race(() -> monoSubscribers[0].onNext("value" + index), reconnectMono::dispose); + + Assertions.assertThat(processor.isTerminated()).isTrue(); + Mockito.verify(mockSubscription).cancel(); + + if (processor.isError()) { + Assertions.assertThat(processor.getError()) + .isInstanceOf(CancellationException.class) + .hasMessage("ReconnectMono has already been disposed"); + + Assertions.assertThat(expired).containsOnly("value" + i); + } else { + Assertions.assertThat(processor.peek()).isEqualTo("value" + i); + } + + expired.clear(); + received.clear(); + } + } + + @Test + public void shouldNotifyAllTheSubscribersUnderRacingBetweenSubscribeAndComplete() { + Hooks.onErrorDropped(t -> {}); + for (int i = 0; i < 100000; i++) { + final TestPublisher cold = + TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW); + + final ReconnectMono reconnectMono = + cold.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue())); + + final MonoProcessor processor = reconnectMono.subscribeWith(MonoProcessor.create()); + final MonoProcessor racerProcessor = MonoProcessor.create(); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).isEmpty(); + + cold.next("value" + i); + + RaceTestUtils.race(cold::complete, () -> reconnectMono.subscribe(racerProcessor)); + + Assertions.assertThat(processor.isTerminated()).isTrue(); + + Assertions.assertThat(processor.peek()).isEqualTo("value" + i); + Assertions.assertThat(racerProcessor.peek()).isEqualTo("value" + i); + + Assertions.assertThat(reconnectMono.subscribers).isEqualTo(ReconnectMono.READY); + + Assertions.assertThat( + reconnectMono.add(new ReconnectMono.ReconnectInner<>(processor, reconnectMono))) + .isEqualTo(ReconnectMono.READY_STATE); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received) + .hasSize(1) + .containsOnly(Tuples.of("value" + i, reconnectMono)); + + received.clear(); + } + } + + @Test + public void shouldEstablishValueOnceInCaseOfRacingBetweenSubscribers() { + for (int i = 0; i < 100000; i++) { + final TestPublisher cold = TestPublisher.createCold(); + cold.next("value" + i); + + final ReconnectMono reconnectMono = + cold.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue())); + + final MonoProcessor processor = MonoProcessor.create(); + final MonoProcessor racerProcessor = MonoProcessor.create(); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).isEmpty(); + + Assertions.assertThat(cold.subscribeCount()).isZero(); + + RaceTestUtils.race( + () -> reconnectMono.subscribe(processor), () -> reconnectMono.subscribe(racerProcessor)); + + Assertions.assertThat(processor.isTerminated()).isTrue(); + Assertions.assertThat(racerProcessor.isTerminated()).isTrue(); + + Assertions.assertThat(processor.peek()).isEqualTo("value" + i); + Assertions.assertThat(racerProcessor.peek()).isEqualTo("value" + i); + + Assertions.assertThat(reconnectMono.subscribers).isEqualTo(ReconnectMono.READY); + + Assertions.assertThat(cold.subscribeCount()).isOne(); + + Assertions.assertThat( + reconnectMono.add(new ReconnectMono.ReconnectInner<>(processor, reconnectMono))) + .isEqualTo(ReconnectMono.READY_STATE); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received) + .hasSize(1) + .containsOnly(Tuples.of("value" + i, reconnectMono)); + + received.clear(); + } + } + + @Test + public void shouldEstablishValueOnceInCaseOfRacingBetweenSubscribeAndBlock() { + Duration timeout = Duration.ofMillis(100); + for (int i = 0; i < 100000; i++) { + final TestPublisher cold = TestPublisher.createCold(); + cold.next("value" + i); + + final ReconnectMono reconnectMono = + cold.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue())); + + final MonoProcessor processor = MonoProcessor.create(); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).isEmpty(); + + Assertions.assertThat(cold.subscribeCount()).isZero(); + + String[] values = new String[1]; + + RaceTestUtils.race( + () -> values[0] = reconnectMono.block(timeout), () -> reconnectMono.subscribe(processor)); + + Assertions.assertThat(processor.isTerminated()).isTrue(); + + Assertions.assertThat(processor.peek()).isEqualTo("value" + i); + Assertions.assertThat(values).containsExactly("value" + i); + + Assertions.assertThat(reconnectMono.subscribers).isEqualTo(ReconnectMono.READY); + + Assertions.assertThat(cold.subscribeCount()).isOne(); + + Assertions.assertThat( + reconnectMono.add(new ReconnectMono.ReconnectInner<>(processor, reconnectMono))) + .isEqualTo(ReconnectMono.READY_STATE); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received) + .hasSize(1) + .containsOnly(Tuples.of("value" + i, reconnectMono)); + + received.clear(); + } + } + + @Test + public void shouldEstablishValueOnceInCaseOfRacingBetweenBlocks() { + Duration timeout = Duration.ofMillis(100); + for (int i = 0; i < 100000; i++) { + final TestPublisher cold = TestPublisher.createCold(); + cold.next("value" + i); + + final ReconnectMono reconnectMono = + cold.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue())); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).isEmpty(); + + Assertions.assertThat(cold.subscribeCount()).isZero(); + + String[] values1 = new String[1]; + String[] values2 = new String[1]; + + RaceTestUtils.race( + () -> values1[0] = reconnectMono.block(timeout), + () -> values2[0] = reconnectMono.block(timeout)); + + Assertions.assertThat(values2).containsExactly("value" + i); + Assertions.assertThat(values1).containsExactly("value" + i); + + Assertions.assertThat(reconnectMono.subscribers).isEqualTo(ReconnectMono.READY); + + Assertions.assertThat(cold.subscribeCount()).isOne(); + + Assertions.assertThat( + reconnectMono.add( + new ReconnectMono.ReconnectInner<>(MonoProcessor.create(), reconnectMono))) + .isEqualTo(ReconnectMono.READY_STATE); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received) + .hasSize(1) + .containsOnly(Tuples.of("value" + i, reconnectMono)); + + received.clear(); + } + } + + @Test + public void shouldExpireValueOnRacingDisposeAndNoValueComplete() { + Hooks.onErrorDropped(t -> {}); + for (int i = 0; i < 100000; i++) { + final TestPublisher cold = + TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW); + + final ReconnectMono reconnectMono = + cold.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue())); + + final MonoProcessor processor = reconnectMono.subscribeWith(MonoProcessor.create()); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).isEmpty(); + + RaceTestUtils.race(cold::complete, reconnectMono::dispose); + + Assertions.assertThat(processor.isTerminated()).isTrue(); + + Throwable error = processor.getError(); + + if (error instanceof CancellationException) { + Assertions.assertThat(error) + .isInstanceOf(CancellationException.class) + .hasMessage("ReconnectMono has already been disposed"); + } else { + Assertions.assertThat(error) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Unexpected Completion of the Upstream"); + } + + Assertions.assertThat(expired).isEmpty(); + + expired.clear(); + received.clear(); + } + } + + @Test + public void shouldExpireValueOnRacingDisposeAndComplete() { + Hooks.onErrorDropped(t -> {}); + for (int i = 0; i < 100000; i++) { + final TestPublisher cold = + TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW); + + final ReconnectMono reconnectMono = + cold.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue())); + + final MonoProcessor processor = reconnectMono.subscribeWith(MonoProcessor.create()); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).isEmpty(); + + cold.next("value" + i); + + RaceTestUtils.race(cold::complete, reconnectMono::dispose); + + Assertions.assertThat(processor.isTerminated()).isTrue(); + + if (processor.isError()) { + Assertions.assertThat(processor.getError()) + .isInstanceOf(CancellationException.class) + .hasMessage("ReconnectMono has already been disposed"); + } else { + Assertions.assertThat(received) + .hasSize(1) + .containsOnly(Tuples.of("value" + i, reconnectMono)); + Assertions.assertThat(processor.peek()).isEqualTo("value" + i); + } + + Assertions.assertThat(expired).hasSize(1).containsOnly("value" + i); + + expired.clear(); + received.clear(); + } + } + + @Test + public void shouldExpireValueOnRacingDisposeAndError() { + Hooks.onErrorDropped(t -> {}); + RuntimeException runtimeException = new RuntimeException("test"); + for (int i = 0; i < 100000; i++) { + final TestPublisher cold = + TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW); + + final ReconnectMono reconnectMono = + cold.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue())); + + final MonoProcessor processor = reconnectMono.subscribeWith(MonoProcessor.create()); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).isEmpty(); + + cold.next("value" + i); + + RaceTestUtils.race(() -> cold.error(runtimeException), reconnectMono::dispose); + + Assertions.assertThat(processor.isTerminated()).isTrue(); + + if (processor.isError()) { + if (processor.getError() instanceof CancellationException) { + Assertions.assertThat(processor.getError()) + .isInstanceOf(CancellationException.class) + .hasMessage("ReconnectMono has already been disposed"); + } else { + Assertions.assertThat(processor.getError()) + .isInstanceOf(RuntimeException.class) + .hasMessage("test"); + } + } else { + Assertions.assertThat(received) + .hasSize(1) + .containsOnly(Tuples.of("value" + i, reconnectMono)); + Assertions.assertThat(processor.peek()).isEqualTo("value" + i); + } + + Assertions.assertThat(expired).hasSize(1).containsOnly("value" + i); + + expired.clear(); + received.clear(); + } + } + + @Test + public void shouldExpireValueOnRacingDisposeAndErrorWithNoBackoff() { + Hooks.onErrorDropped(t -> {}); + RuntimeException runtimeException = new RuntimeException("test"); + for (int i = 0; i < 100000; i++) { + final TestPublisher cold = + TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW); + + final ReconnectMono reconnectMono = + cold.mono() + .retryWhen(Retry.max(1).filter(t -> t instanceof Exception)) + .as(source -> new ReconnectMono<>(source, onExpire(), onValue())); + + final MonoProcessor processor = reconnectMono.subscribeWith(MonoProcessor.create()); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).isEmpty(); + + cold.next("value" + i); + + RaceTestUtils.race(() -> cold.error(runtimeException), reconnectMono::dispose); + + Assertions.assertThat(processor.isTerminated()).isTrue(); + + if (processor.isError()) { + + if (processor.getError() instanceof CancellationException) { + Assertions.assertThat(processor.getError()) + .isInstanceOf(CancellationException.class) + .hasMessage("ReconnectMono has already been disposed"); + } else { + Assertions.assertThat(processor.getError()) + .matches(t -> Exceptions.isRetryExhausted(t)) + .hasCause(runtimeException); + } + + Assertions.assertThat(expired).hasSize(1).containsOnly("value" + i); + } else { + Assertions.assertThat(received) + .hasSize(1) + .containsOnly(Tuples.of("value" + i, reconnectMono)); + Assertions.assertThat(processor.peek()).isEqualTo("value" + i); + } + + expired.clear(); + received.clear(); + } + } + + @Test + public void shouldThrowOnBlocking() { + final TestPublisher publisher = + TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW); + + final ReconnectMono reconnectMono = + publisher.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue())); + + Assertions.assertThatThrownBy(() -> reconnectMono.block(Duration.ofMillis(100))) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Timeout on Mono blocking read"); + } + + @Test + public void shouldThrowOnBlockingIfHasAlreadyTerminated() { + final TestPublisher publisher = + TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW); + + final ReconnectMono reconnectMono = + publisher.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue())); + + publisher.error(new RuntimeException("test")); + + Assertions.assertThatThrownBy(() -> reconnectMono.block(Duration.ofMillis(100))) + .isInstanceOf(RuntimeException.class) + .hasMessage("test") + .hasSuppressedException(new Exception("ReconnectMono terminated with an error")); + } + + @Test + public void shouldBeScannable() { + final TestPublisher publisher = + TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW); + + final Mono parent = publisher.mono(); + final ReconnectMono reconnectMono = + parent.as(source -> new ReconnectMono<>(source, onExpire(), onValue())); + + final Scannable scannableOfReconnect = Scannable.from(reconnectMono); + + Assertions.assertThat( + (List) + scannableOfReconnect.parents().map(s -> s.getClass()).collect(Collectors.toList())) + .hasSize(1) + .containsExactly(publisher.mono().getClass()); + Assertions.assertThat(scannableOfReconnect.scanUnsafe(Scannable.Attr.TERMINATED)) + .isEqualTo(false); + Assertions.assertThat(scannableOfReconnect.scanUnsafe(Scannable.Attr.ERROR)).isNull(); + + final MonoProcessor processor = reconnectMono.subscribeWith(MonoProcessor.create()); + + final Scannable scannableOfMonoProcessor = Scannable.from(processor); + + Assertions.assertThat( + (List) + scannableOfMonoProcessor + .parents() + .map(s -> s.getClass()) + .collect(Collectors.toList())) + .hasSize(3) + .containsExactly( + ReconnectMono.ReconnectInner.class, ReconnectMono.class, publisher.mono().getClass()); + + reconnectMono.dispose(); + + Assertions.assertThat(scannableOfReconnect.scanUnsafe(Scannable.Attr.TERMINATED)) + .isEqualTo(true); + Assertions.assertThat(scannableOfReconnect.scanUnsafe(Scannable.Attr.ERROR)) + .isInstanceOf(CancellationException.class); + } + + @Test + public void shouldNotExpiredIfNotCompleted() { + final TestPublisher publisher = + TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW); + + final ReconnectMono reconnectMono = + publisher.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue())); + + MonoProcessor processor = MonoProcessor.create(); + + reconnectMono.subscribe(processor); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).isEmpty(); + Assertions.assertThat(processor.isTerminated()).isFalse(); + + publisher.next("test"); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).isEmpty(); + Assertions.assertThat(processor.isTerminated()).isFalse(); + + reconnectMono.invalidate(); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).isEmpty(); + Assertions.assertThat(processor.isTerminated()).isFalse(); + publisher.assertSubscribers(1); + Assertions.assertThat(publisher.subscribeCount()).isEqualTo(1); + + publisher.complete(); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).hasSize(1); + Assertions.assertThat(processor.isTerminated()).isTrue(); + + publisher.assertSubscribers(0); + Assertions.assertThat(publisher.subscribeCount()).isEqualTo(1); + } + + @Test + public void shouldNotEmitUntilCompletion() { + final TestPublisher publisher = + TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW); + + final ReconnectMono reconnectMono = + publisher.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue())); + + MonoProcessor processor = MonoProcessor.create(); + + reconnectMono.subscribe(processor); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).isEmpty(); + Assertions.assertThat(processor.isTerminated()).isFalse(); + + publisher.next("test"); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).isEmpty(); + Assertions.assertThat(processor.isTerminated()).isFalse(); + + publisher.complete(); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).hasSize(1); + Assertions.assertThat(processor.isTerminated()).isTrue(); + Assertions.assertThat(processor.peek()).isEqualTo("test"); + } + + @Test + public void shouldBePossibleToRemoveThemSelvesFromTheList_CancellationTest() { + final TestPublisher publisher = + TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW); + // given + final int minBackoff = 1; + final int maxBackoff = 5; + final int timeout = 10; + + final ReconnectMono reconnectMono = + publisher.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue())); + + MonoProcessor processor = MonoProcessor.create(); + + reconnectMono.subscribe(processor); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).isEmpty(); + Assertions.assertThat(processor.isTerminated()).isFalse(); + + publisher.next("test"); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).isEmpty(); + Assertions.assertThat(processor.isTerminated()).isFalse(); + + processor.cancel(); + + Assertions.assertThat(reconnectMono.subscribers).isEqualTo(ReconnectMono.EMPTY_SUBSCRIBED); + + publisher.complete(); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).hasSize(1); + Assertions.assertThat(processor.isTerminated()).isFalse(); + Assertions.assertThat(processor.peek()).isNull(); + } + + @Test + public void shouldExpireValueOnDispose() { + final TestPublisher publisher = TestPublisher.create(); + // given + final int minBackoff = 1; + final int maxBackoff = 5; + final int timeout = 10; + + final ReconnectMono reconnectMono = + publisher.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue())); + + StepVerifier.create(reconnectMono) + .expectSubscription() + .then(() -> publisher.next("value")) + .expectNext("value") + .expectComplete() + .verify(Duration.ofSeconds(timeout)); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).hasSize(1); + + reconnectMono.dispose(); + + Assertions.assertThat(expired).hasSize(1); + Assertions.assertThat(received).hasSize(1); + Assertions.assertThat(reconnectMono.isDisposed()).isTrue(); + + StepVerifier.create(reconnectMono.subscribeOn(Schedulers.elastic())) + .expectSubscription() + .expectError(CancellationException.class) + .verify(Duration.ofSeconds(timeout)); + } + + @Test + public void shouldNotifyAllTheSubscribers() { + final TestPublisher publisher = TestPublisher.create(); + // given + final int minBackoff = 1; + final int maxBackoff = 5; + final int timeout = 10; + + final ReconnectMono reconnectMono = + publisher.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue())); + + final MonoProcessor sub1 = MonoProcessor.create(); + final MonoProcessor sub2 = MonoProcessor.create(); + final MonoProcessor sub3 = MonoProcessor.create(); + final MonoProcessor sub4 = MonoProcessor.create(); + + reconnectMono.subscribe(sub1); + reconnectMono.subscribe(sub2); + reconnectMono.subscribe(sub3); + reconnectMono.subscribe(sub4); + + Assertions.assertThat(reconnectMono.subscribers).hasSize(4); + + final ArrayList> processors = new ArrayList<>(200); + + for (int i = 0; i < 100; i++) { + final MonoProcessor subA = MonoProcessor.create(); + final MonoProcessor subB = MonoProcessor.create(); + processors.add(subA); + processors.add(subB); + RaceTestUtils.race(() -> reconnectMono.subscribe(subA), () -> reconnectMono.subscribe(subB)); + } + + Assertions.assertThat(reconnectMono.subscribers).hasSize(204); + + sub1.dispose(); + + Assertions.assertThat(reconnectMono.subscribers).hasSize(203); + + publisher.next("value"); + + Assertions.assertThatThrownBy(sub1::peek).isInstanceOf(CancellationException.class); + Assertions.assertThat(sub2.peek()).isEqualTo("value"); + Assertions.assertThat(sub3.peek()).isEqualTo("value"); + Assertions.assertThat(sub4.peek()).isEqualTo("value"); + + for (MonoProcessor sub : processors) { + Assertions.assertThat(sub.peek()).isEqualTo("value"); + Assertions.assertThat(sub.isTerminated()).isTrue(); + } + + Assertions.assertThat(publisher.subscribeCount()).isEqualTo(1); + } + + @Test + public void shouldExpireValueExactlyOnce() { + for (int i = 0; i < 1000; i++) { + final TestPublisher cold = TestPublisher.createCold(); + cold.next("value"); + // given + final int minBackoff = 1; + final int maxBackoff = 5; + final int timeout = 10; + + final ReconnectMono reconnectMono = + cold.mono().as(source -> new ReconnectMono<>(source, onExpire(), onValue())); + + StepVerifier.create(reconnectMono.subscribeOn(Schedulers.elastic())) + .expectSubscription() + .expectNext("value") + .expectComplete() + .verify(Duration.ofSeconds(timeout)); + + Assertions.assertThat(expired).isEmpty(); + Assertions.assertThat(received).hasSize(1).containsOnly(Tuples.of("value", reconnectMono)); + RaceTestUtils.race(reconnectMono::invalidate, reconnectMono::invalidate); + + Assertions.assertThat(expired).hasSize(1).containsOnly("value"); + Assertions.assertThat(received).hasSize(1).containsOnly(Tuples.of("value", reconnectMono)); + + StepVerifier.create(reconnectMono.subscribeOn(Schedulers.elastic())) + .expectSubscription() + .expectNext("value") + .expectComplete() + .verify(Duration.ofSeconds(timeout)); + + Assertions.assertThat(expired).hasSize(1).containsOnly("value"); + Assertions.assertThat(received) + .hasSize(2) + .containsOnly(Tuples.of("value", reconnectMono), Tuples.of("value", reconnectMono)); + + Assertions.assertThat(cold.subscribeCount()).isEqualTo(2); + + expired.clear(); + received.clear(); + } + } + + @Test + public void shouldTimeoutRetryWithVirtualTime() { + // given + final int minBackoff = 1; + final int maxBackoff = 5; + final int timeout = 10; + + // then + StepVerifier.withVirtualTime( + () -> + Mono.error(new RuntimeException("Something went wrong")) + .retryWhen( + Retry.backoff(Long.MAX_VALUE, Duration.ofSeconds(minBackoff)) + .doAfterRetry(onRetry()) + .maxBackoff(Duration.ofSeconds(maxBackoff))) + .timeout(Duration.ofSeconds(timeout)) + .as(m -> new ReconnectMono<>(m, onExpire(), onValue())) + .subscribeOn(Schedulers.elastic())) + .expectSubscription() + .thenAwait(Duration.ofSeconds(timeout)) + .expectError(TimeoutException.class) + .verify(Duration.ofSeconds(timeout)); + + Assertions.assertThat(received).isEmpty(); + Assertions.assertThat(expired).isEmpty(); + } + + @Test + public void monoRetryNoBackoff() { + Mono mono = + Mono.error(new IOException()) + .retryWhen(Retry.max(2).doAfterRetry(onRetry())) + .as(m -> new ReconnectMono<>(m, onExpire(), onValue())); + + StepVerifier.create(mono).verifyErrorMatches(Exceptions::isRetryExhausted); + assertRetries(IOException.class, IOException.class); + + Assertions.assertThat(received).isEmpty(); + Assertions.assertThat(expired).isEmpty(); + } + + @Test + public void monoRetryFixedBackoff() { + Mono mono = + Mono.error(new IOException()) + .retryWhen(Retry.fixedDelay(1, Duration.ofMillis(500)).doAfterRetry(onRetry())) + .as(m -> new ReconnectMono<>(m, onExpire(), onValue())); + + StepVerifier.withVirtualTime(() -> mono) + .expectSubscription() + .expectNoEvent(Duration.ofMillis(300)) + .thenAwait(Duration.ofMillis(300)) + .verifyErrorMatches(Exceptions::isRetryExhausted); + + assertRetries(IOException.class); + + Assertions.assertThat(received).isEmpty(); + Assertions.assertThat(expired).isEmpty(); + } + + @Test + public void monoRetryExponentialBackoff() { + Mono mono = + Mono.error(new IOException()) + .retryWhen( + Retry.backoff(4, Duration.ofMillis(100)) + .maxBackoff(Duration.ofMillis(500)) + .jitter(0.0d) + .doAfterRetry(onRetry())) + .as(m -> new ReconnectMono<>(m, onExpire(), onValue())); + + StepVerifier.withVirtualTime(() -> mono) + .expectSubscription() + .thenAwait(Duration.ofMillis(100)) + .thenAwait(Duration.ofMillis(200)) + .thenAwait(Duration.ofMillis(400)) + .thenAwait(Duration.ofMillis(500)) + .verifyErrorMatches(Exceptions::isRetryExhausted); + + assertRetries(IOException.class, IOException.class, IOException.class, IOException.class); + + Assertions.assertThat(received).isEmpty(); + Assertions.assertThat(expired).isEmpty(); + } + + Consumer onRetry() { + return context -> retries.add(context); + } + + BiConsumer onValue() { + return (v, __) -> received.add(Tuples.of(v, __)); + } + + Consumer onExpire() { + return (v) -> expired.add(v); + } + + @SafeVarargs + private final void assertRetries(Class... exceptions) { + assertEquals(exceptions.length, retries.size()); + int index = 0; + for (Iterator it = retries.iterator(); it.hasNext(); ) { + Retry.RetrySignal retryContext = it.next(); + assertEquals(index, retryContext.totalRetries()); + assertEquals(exceptions[index], retryContext.failure().getClass()); + index++; + } + } + + static boolean isRetryExhausted(Throwable e, Class cause) { + return Exceptions.isRetryExhausted(e) && cause.isInstance(e.getCause()); + } +} From a8df31622d2136f612b5fdc4866652118f77650f Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Fri, 27 Mar 2020 12:22:38 +0200 Subject: [PATCH 09/62] provides support for the JDK 14 based builds (#763) Signed-off-by: Oleh Dokuka --- .travis.yml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a40bdf55e..4722957c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,7 @@ matrix: - jdk: openjdk8 - jdk: openjdk11 env: SKIP_RELEASE=true - - jdk: openjdk13 + - jdk: openjdk14 env: SKIP_RELEASE=true env: diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 94920145f..a4b442974 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From c76fdb037dbbee2409fb4f115c40c73953f21ab5 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Sun, 5 Apr 2020 18:02:24 +0300 Subject: [PATCH 10/62] eliminates deprecations for websocket transport Signed-off-by: Oleh Dokuka --- .../client/WebsocketClientTransport.java | 1 - .../netty/server/WebsocketRouteTransport.java | 134 +----------------- .../server/WebsocketServerTransport.java | 8 +- 3 files changed, 10 insertions(+), 133 deletions(-) diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java index 5049119a5..62bbd9b99 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java @@ -43,7 +43,6 @@ */ public final class WebsocketClientTransport implements ClientTransport, TransportHeaderAware { - private static final int DEFAULT_FRAME_SIZE = 65536; private static final String DEFAULT_PATH = "/"; private final HttpClient client; diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketRouteTransport.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketRouteTransport.java index 30aa0fa96..60a34c9b1 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketRouteTransport.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketRouteTransport.java @@ -19,26 +19,20 @@ import static io.rsocket.frame.FrameLengthFlyweight.FRAME_LENGTH_MASK; import io.netty.buffer.ByteBufAllocator; -import io.netty.handler.codec.http.HttpMethod; import io.rsocket.Closeable; import io.rsocket.DuplexConnection; import io.rsocket.fragmentation.FragmentationDuplexConnection; import io.rsocket.transport.ServerTransport; import io.rsocket.transport.netty.WebsocketDuplexConnection; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.function.BiFunction; import java.util.function.Consumer; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; import reactor.netty.Connection; import reactor.netty.http.server.HttpServer; import reactor.netty.http.server.HttpServerRoutes; +import reactor.netty.http.server.WebsocketServerSpec; import reactor.netty.http.websocket.WebsocketInbound; import reactor.netty.http.websocket.WebsocketOutbound; @@ -48,7 +42,7 @@ */ public final class WebsocketRouteTransport extends BaseWebsocketServerTransport { - private final UriPathTemplate template; + private final String path; private final Consumer routesBuilder; @@ -65,7 +59,7 @@ public WebsocketRouteTransport( HttpServer server, Consumer routesBuilder, String path) { this.server = serverConfigurer.apply(Objects.requireNonNull(server, "server must not be null")); this.routesBuilder = Objects.requireNonNull(routesBuilder, "routesBuilder must not be null"); - this.template = new UriPathTemplate(Objects.requireNonNull(path, "path must not be null")); + this.path = Objects.requireNonNull(path, "path must not be null"); } @Override @@ -77,10 +71,9 @@ public Mono start(ConnectionAcceptor acceptor, int mtu) { routes -> { routesBuilder.accept(routes); routes.ws( - hsr -> hsr.method().equals(HttpMethod.GET) && template.matches(hsr.uri()), + path, newHandler(acceptor, mtu), - null, - FRAME_LENGTH_MASK); + WebsocketServerSpec.builder().maxFramePayloadLength(FRAME_LENGTH_MASK).build()); }) .bind() .map(CloseableChannel::new); @@ -118,121 +111,4 @@ public static BiFunction> n return acceptor.apply(connection).then(out.neverComplete()); }; } - - static final class UriPathTemplate { - - private static final Pattern FULL_SPLAT_PATTERN = Pattern.compile("[\\*][\\*]"); - private static final String FULL_SPLAT_REPLACEMENT = ".*"; - - private static final Pattern NAME_SPLAT_PATTERN = Pattern.compile("\\{([^/]+?)\\}[\\*][\\*]"); - private static final String NAME_SPLAT_REPLACEMENT = "(?<%NAME%>.*)"; - - private static final Pattern NAME_PATTERN = Pattern.compile("\\{([^/]+?)\\}"); - private static final String NAME_REPLACEMENT = "(?<%NAME%>[^\\/]*)"; - - private final List pathVariables = new ArrayList<>(); - private final HashMap matchers = new HashMap<>(); - private final HashMap> vars = new HashMap<>(); - - private final Pattern uriPattern; - - static String filterQueryParams(String uri) { - int hasQuery = uri.lastIndexOf("?"); - if (hasQuery != -1) { - return uri.substring(0, hasQuery); - } else { - return uri; - } - } - - /** - * Creates a new {@code UriPathTemplate} from the given {@code uriPattern}. - * - * @param uriPattern The pattern to be used by the template - */ - UriPathTemplate(String uriPattern) { - String s = "^" + filterQueryParams(uriPattern); - - Matcher m = NAME_SPLAT_PATTERN.matcher(s); - while (m.find()) { - for (int i = 1; i <= m.groupCount(); i++) { - String name = m.group(i); - pathVariables.add(name); - s = m.replaceFirst(NAME_SPLAT_REPLACEMENT.replaceAll("%NAME%", name)); - m.reset(s); - } - } - - m = NAME_PATTERN.matcher(s); - while (m.find()) { - for (int i = 1; i <= m.groupCount(); i++) { - String name = m.group(i); - pathVariables.add(name); - s = m.replaceFirst(NAME_REPLACEMENT.replaceAll("%NAME%", name)); - m.reset(s); - } - } - - m = FULL_SPLAT_PATTERN.matcher(s); - while (m.find()) { - s = m.replaceAll(FULL_SPLAT_REPLACEMENT); - m.reset(s); - } - - this.uriPattern = Pattern.compile(s + "$"); - } - - /** - * Tests the given {@code uri} against this template, returning {@code true} if the uri matches - * the template, {@code false} otherwise. - * - * @param uri The uri to match - * @return {@code true} if there's a match, {@code false} otherwise - */ - public boolean matches(String uri) { - return matcher(uri).matches(); - } - - /** - * Matches the template against the given {@code uri} returning a map of path parameters - * extracted from the uri, keyed by the names in the template. If the uri does not match, or - * there are no path parameters, an empty map is returned. - * - * @param uri The uri to match - * @return the path parameters from the uri. Never {@code null}. - */ - final Map match(String uri) { - Map pathParameters = vars.get(uri); - if (null != pathParameters) { - return pathParameters; - } - - pathParameters = new HashMap<>(); - Matcher m = matcher(uri); - if (m.matches()) { - int i = 1; - for (String name : pathVariables) { - String val = m.group(i++); - pathParameters.put(name, val); - } - } - synchronized (vars) { - vars.put(uri, pathParameters); - } - - return pathParameters; - } - - private Matcher matcher(String uri) { - uri = filterQueryParams(uri); - Matcher m = matchers.get(uri); - if (null == m) { - m = uriPattern.matcher(uri); - synchronized (matchers) { - matchers.put(uri, m); - } - } - return m; - } - } } diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketServerTransport.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketServerTransport.java index 948d6f573..13caa6345 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketServerTransport.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketServerTransport.java @@ -35,6 +35,7 @@ import reactor.core.publisher.Mono; import reactor.netty.Connection; import reactor.netty.http.server.HttpServer; +import reactor.netty.http.server.WebsocketServerSpec; /** * An implementation of {@link ServerTransport} that connects to a {@link ClientTransport} via a @@ -122,8 +123,6 @@ public Mono start(ConnectionAcceptor acceptor, int mtu) { (request, response) -> { transportHeaders.get().forEach(response::addHeader); return response.sendWebsocket( - null, - FRAME_LENGTH_MASK, (in, out) -> { DuplexConnection connection = new WebsocketDuplexConnection((Connection) in); @@ -133,7 +132,10 @@ public Mono start(ConnectionAcceptor acceptor, int mtu) { connection, ByteBufAllocator.DEFAULT, mtu, false, "server"); } return acceptor.apply(connection).then(out.neverComplete()); - }); + }, + WebsocketServerSpec.builder() + .maxFramePayloadLength(FRAME_LENGTH_MASK) + .build()); }) .bind() .map(CloseableChannel::new); From 29cd7bc77adc9a8b454a48333dc128eefa315746 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 9 Apr 2020 12:36:05 +0100 Subject: [PATCH 11/62] Reduce top-level package dependencies (#770) * Reduce top-level package dependencies This commit extracts interfaces for ClientRSocketFactory, ServerRSocketFactory, and ConnectionSetupPayload, and moves the implementations into a sub-package with no references to it from the top-level package. This removes a large package cycle with io.rsocket at the center of everything since it currently contains both high level abstractions that (accessed by everything), as well as core protocol implementations classes (accessing everything). The refactoring is functionally neutral and backwards compatible. Signed-off-by: Rossen Stoyanchev * Remove deprecated methods See gh-770 Signed-off-by: Rossen Stoyanchev --- .../io/rsocket/ConnectionSetupPayload.java | 142 +--- .../main/java/io/rsocket/RSocketFactory.java | 782 +++--------------- .../core/DefaultClientRSocketFactory.java | 430 ++++++++++ .../core/DefaultConnectionSetupPayload.java | 128 +++ .../core/DefaultServerRSocketFactory.java | 379 +++++++++ .../rsocket/{ => core}/RSocketRequester.java | 5 +- .../rsocket/{ => core}/RSocketResponder.java | 6 +- .../io/rsocket/{ => core}/ReconnectMono.java | 2 +- .../rsocket/{ => core}/StreamIdSupplier.java | 2 +- .../java/io/rsocket/core/package-info.java | 24 + .../plugins/SocketAcceptorInterceptor.java | 2 +- .../{ => core}/AbstractSocketRule.java | 3 +- .../ConnectionSetupPayloadTest.java | 10 +- .../io/rsocket/{ => core}/KeepAliveTest.java | 3 +- .../rsocket/{ => core}/RSocketLeaseTest.java | 3 +- .../{ => core}/RSocketReconnectTest.java | 4 +- .../RSocketRequesterSubscribersTest.java | 3 +- .../RSocketRequesterTerminationTest.java | 6 +- .../{ => core}/RSocketRequesterTest.java | 3 +- .../{ => core}/RSocketResponderTest.java | 5 +- .../io/rsocket/{ => core}/RSocketTest.java | 5 +- .../{ => core}/ReconnectMonoTests.java | 2 +- .../{ => core}/SetupRejectionTest.java | 3 +- .../{ => core}/StreamIdSupplierTest.java | 2 +- .../io/rsocket/{ => core}/TestingStuff.java | 4 +- 25 files changed, 1141 insertions(+), 817 deletions(-) create mode 100644 rsocket-core/src/main/java/io/rsocket/core/DefaultClientRSocketFactory.java create mode 100644 rsocket-core/src/main/java/io/rsocket/core/DefaultConnectionSetupPayload.java create mode 100644 rsocket-core/src/main/java/io/rsocket/core/DefaultServerRSocketFactory.java rename rsocket-core/src/main/java/io/rsocket/{ => core}/RSocketRequester.java (99%) rename rsocket-core/src/main/java/io/rsocket/{ => core}/RSocketResponder.java (99%) rename rsocket-core/src/main/java/io/rsocket/{ => core}/ReconnectMono.java (99%) rename rsocket-core/src/main/java/io/rsocket/{ => core}/StreamIdSupplier.java (98%) create mode 100644 rsocket-core/src/main/java/io/rsocket/core/package-info.java rename rsocket-core/src/test/java/io/rsocket/{ => core}/AbstractSocketRule.java (97%) rename rsocket-core/src/test/java/io/rsocket/{ => core}/ConnectionSetupPayloadTest.java (89%) rename rsocket-core/src/test/java/io/rsocket/{ => core}/KeepAliveTest.java (99%) rename rsocket-core/src/test/java/io/rsocket/{ => core}/RSocketLeaseTest.java (99%) rename rsocket-core/src/test/java/io/rsocket/{ => core}/RSocketReconnectTest.java (98%) rename rsocket-core/src/test/java/io/rsocket/{ => core}/RSocketRequesterSubscribersTest.java (98%) rename rsocket-core/src/test/java/io/rsocket/{ => core}/RSocketRequesterTerminationTest.java (93%) rename rsocket-core/src/test/java/io/rsocket/{ => core}/RSocketRequesterTest.java (99%) rename rsocket-core/src/test/java/io/rsocket/{ => core}/RSocketResponderTest.java (98%) rename rsocket-core/src/test/java/io/rsocket/{ => core}/RSocketTest.java (98%) rename rsocket-core/src/test/java/io/rsocket/{ => core}/ReconnectMonoTests.java (99%) rename rsocket-core/src/test/java/io/rsocket/{ => core}/SetupRejectionTest.java (99%) rename rsocket-core/src/test/java/io/rsocket/{ => core}/StreamIdSupplierTest.java (99%) rename rsocket-core/src/test/java/io/rsocket/{ => core}/TestingStuff.java (97%) diff --git a/rsocket-core/src/main/java/io/rsocket/ConnectionSetupPayload.java b/rsocket-core/src/main/java/io/rsocket/ConnectionSetupPayload.java index 8762e0489..47cb0cf11 100644 --- a/rsocket-core/src/main/java/io/rsocket/ConnectionSetupPayload.java +++ b/rsocket-core/src/main/java/io/rsocket/ConnectionSetupPayload.java @@ -1,11 +1,11 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,147 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package io.rsocket; import io.netty.buffer.ByteBuf; -import io.netty.util.AbstractReferenceCounted; -import io.rsocket.frame.FrameHeaderFlyweight; -import io.rsocket.frame.SetupFrameFlyweight; +import io.netty.util.ReferenceCounted; import javax.annotation.Nullable; /** - * Exposed to server for determination of ResponderRSocket based on mime types and SETUP - * metadata/data + * Exposes information from the {@code SETUP} frame to a server, as well as to client responders. */ -public abstract class ConnectionSetupPayload extends AbstractReferenceCounted implements Payload { - - public static ConnectionSetupPayload create(final ByteBuf setupFrame) { - return new DefaultConnectionSetupPayload(setupFrame); - } +public interface ConnectionSetupPayload extends ReferenceCounted, Payload { - public abstract int keepAliveInterval(); + String metadataMimeType(); - public abstract int keepAliveMaxLifetime(); + String dataMimeType(); - public abstract String metadataMimeType(); + int keepAliveInterval(); - public abstract String dataMimeType(); + int keepAliveMaxLifetime(); - public abstract int getFlags(); + int getFlags(); - public abstract boolean willClientHonorLease(); + boolean willClientHonorLease(); - public abstract boolean isResumeEnabled(); + boolean isResumeEnabled(); @Nullable - public abstract ByteBuf resumeToken(); - - @Override - public ConnectionSetupPayload retain() { - super.retain(); - return this; - } - - @Override - public ConnectionSetupPayload retain(int increment) { - super.retain(increment); - return this; - } - - @Override - public abstract ConnectionSetupPayload touch(); - - @Override - public abstract ConnectionSetupPayload touch(Object hint); - - private static final class DefaultConnectionSetupPayload extends ConnectionSetupPayload { - private final ByteBuf setupFrame; - - public DefaultConnectionSetupPayload(ByteBuf setupFrame) { - this.setupFrame = setupFrame; - } - - @Override - public boolean hasMetadata() { - return FrameHeaderFlyweight.hasMetadata(setupFrame); - } - - @Override - public int keepAliveInterval() { - return SetupFrameFlyweight.keepAliveInterval(setupFrame); - } - - @Override - public int keepAliveMaxLifetime() { - return SetupFrameFlyweight.keepAliveMaxLifetime(setupFrame); - } - - @Override - public String metadataMimeType() { - return SetupFrameFlyweight.metadataMimeType(setupFrame); - } - - @Override - public String dataMimeType() { - return SetupFrameFlyweight.dataMimeType(setupFrame); - } - - @Override - public int getFlags() { - return FrameHeaderFlyweight.flags(setupFrame); - } - - @Override - public boolean willClientHonorLease() { - return SetupFrameFlyweight.honorLease(setupFrame); - } - - @Override - public boolean isResumeEnabled() { - return SetupFrameFlyweight.resumeEnabled(setupFrame); - } - - @Override - public ByteBuf resumeToken() { - return SetupFrameFlyweight.resumeToken(setupFrame); - } - - @Override - public ConnectionSetupPayload touch() { - setupFrame.touch(); - return this; - } - - @Override - public ConnectionSetupPayload touch(Object hint) { - setupFrame.touch(hint); - return this; - } - - @Override - protected void deallocate() { - setupFrame.release(); - } - - @Override - public ByteBuf sliceMetadata() { - return SetupFrameFlyweight.metadata(setupFrame); - } - - @Override - public ByteBuf sliceData() { - return SetupFrameFlyweight.data(setupFrame); - } - - @Override - public ByteBuf data() { - return sliceData(); - } - - @Override - public ByteBuf metadata() { - return sliceMetadata(); - } - } + ByteBuf resumeToken(); } diff --git a/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java b/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java index 6570ed7a3..0d83d06dc 100644 --- a/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java +++ b/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java @@ -1,11 +1,11 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,230 +13,107 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package io.rsocket; -import static io.rsocket.internal.ClientSetup.DefaultClientSetup; -import static io.rsocket.internal.ClientSetup.ResumableClientSetup; - import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; -import io.rsocket.exceptions.InvalidSetupException; -import io.rsocket.exceptions.RejectedSetupException; -import io.rsocket.frame.FrameHeaderFlyweight; -import io.rsocket.frame.ResumeFrameFlyweight; -import io.rsocket.frame.SetupFrameFlyweight; import io.rsocket.frame.decoder.PayloadDecoder; -import io.rsocket.internal.ClientServerInputMultiplexer; -import io.rsocket.internal.ClientSetup; -import io.rsocket.internal.ServerSetup; -import io.rsocket.keepalive.KeepAliveHandler; import io.rsocket.lease.LeaseStats; import io.rsocket.lease.Leases; -import io.rsocket.lease.RequesterLeaseHandler; -import io.rsocket.lease.ResponderLeaseHandler; -import io.rsocket.plugins.*; -import io.rsocket.resume.*; +import io.rsocket.plugins.DuplexConnectionInterceptor; +import io.rsocket.plugins.RSocketInterceptor; +import io.rsocket.plugins.SocketAcceptorInterceptor; +import io.rsocket.resume.ResumableFramesStore; +import io.rsocket.resume.ResumeStrategy; import io.rsocket.transport.ClientTransport; import io.rsocket.transport.ServerTransport; -import io.rsocket.util.ConnectionUtils; -import io.rsocket.util.EmptyPayload; -import io.rsocket.util.MultiSubscriberRSocket; +import java.lang.reflect.Constructor; import java.time.Duration; -import java.util.Objects; -import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; -import reactor.core.Disposable; import reactor.core.publisher.Mono; import reactor.util.retry.Retry; -/** Factory for creating RSocket clients and servers. */ -public class RSocketFactory { +/** + * Main entry point to create RSocket clients or servers as follows: + * + *
    + *
  • {@link ClientRSocketFactory} to connect as a client. Use {@link #connect()} for a default + * instance. + *
  • {@link ServerRSocketFactory} to start a server. Use {@link #receive()} for a default + * instance. + *
+ */ +public final class RSocketFactory { + + private static final Constructor clientFactoryConstructor = + getConstructorFor("io.rsocket.core.DefaultClientRSocketFactory"); + + private static final Constructor serverFactoryConstructor = + getConstructorFor("io.rsocket.core.DefaultServerRSocketFactory"); + /** - * Creates a factory that establishes client connections to other RSockets. + * Create a {@link ClientRSocketFactory} to connect to a remote RSocket endpoint. A shortcut for + * creating {@link io.rsocket.core.DefaultClientRSocketFactory}. * - * @return a client factory + * @return the {@code ClientRSocketFactory} instance */ public static ClientRSocketFactory connect() { - return new ClientRSocketFactory(); + try { + // Avoid explicit dependency and a package cycle + return (ClientRSocketFactory) clientFactoryConstructor.newInstance(); + } catch (Exception ex) { + throw new IllegalStateException("Failed to create ClientRSocketFactory", ex); + } } /** - * Creates a factory that receives server connections from client RSockets. + * Create a {@link ServerRSocketFactory} to accept connections from RSocket clients. A shortcut + * for creating {@link io.rsocket.core.DefaultServerRSocketFactory}. * - * @return a server factory. + * @return the {@code ClientRSocketFactory} instance */ public static ServerRSocketFactory receive() { - return new ServerRSocketFactory(); - } - - public interface Start { - Mono start(); - } - - public interface ClientTransportAcceptor { - Start transport(Supplier transport); - - default Start transport(ClientTransport transport) { - return transport(() -> transport); + try { + // Avoid explicit dependency and a package cycle + return (ServerRSocketFactory) serverFactoryConstructor.newInstance(); + } catch (Exception ex) { + throw new IllegalStateException("Failed to create ServerRSocketFactory", ex); } } + /** Factory to create and configure an RSocket client, and connect to a server. */ + public interface ClientRSocketFactory extends ClientTransportAcceptor { - public interface ServerTransportAcceptor { + ClientRSocketFactory byteBufAllocator(ByteBufAllocator allocator); - ServerTransport.ConnectionAcceptor toConnectionAcceptor(); + ClientRSocketFactory addConnectionPlugin(DuplexConnectionInterceptor interceptor); - Start transport(Supplier> transport); + ClientRSocketFactory addRequesterPlugin(RSocketInterceptor interceptor); - default Start transport(ServerTransport transport) { - return transport(() -> transport); - } - } + ClientRSocketFactory addResponderPlugin(RSocketInterceptor interceptor); - public static class ClientRSocketFactory implements ClientTransportAcceptor { - private static final String CLIENT_TAG = "client"; + ClientRSocketFactory addSocketAcceptorPlugin(SocketAcceptorInterceptor interceptor); - private static final BiConsumer INVALIDATE_FUNCTION = - (r, i) -> r.onClose().subscribe(null, null, i::invalidate); + ClientRSocketFactory keepAlive(Duration tickPeriod, Duration ackTimeout, int missedAcks); - private SocketAcceptor acceptor = (setup, sendingSocket) -> Mono.just(new AbstractRSocket() {}); + ClientRSocketFactory keepAliveTickPeriod(Duration tickPeriod); - private Consumer errorConsumer = Throwable::printStackTrace; - private int mtu = 0; - private PluginRegistry plugins = new PluginRegistry(Plugins.defaultPlugins()); + ClientRSocketFactory keepAliveAckTimeout(Duration ackTimeout); - private Payload setupPayload = EmptyPayload.INSTANCE; - private PayloadDecoder payloadDecoder = PayloadDecoder.DEFAULT; + ClientRSocketFactory keepAliveMissedAcks(int missedAcks); - private Duration tickPeriod = Duration.ofSeconds(20); - private Duration ackTimeout = Duration.ofSeconds(30); - private int missedAcks = 3; + ClientRSocketFactory mimeType(String metadataMimeType, String dataMimeType); - private String metadataMimeType = "application/binary"; - private String dataMimeType = "application/binary"; + ClientRSocketFactory dataMimeType(String dataMimeType); - private boolean resumeEnabled; - private boolean resumeCleanupStoreOnKeepAlive; - private Supplier resumeTokenSupplier = ResumeFrameFlyweight::generateResumeToken; - private Function resumeStoreFactory = - token -> new InMemoryResumableFramesStore(CLIENT_TAG, 100_000); - private Duration resumeSessionDuration = Duration.ofMinutes(2); - private Duration resumeStreamTimeout = Duration.ofSeconds(10); - private Supplier resumeStrategySupplier = - () -> - new ExponentialBackoffResumeStrategy(Duration.ofSeconds(1), Duration.ofSeconds(16), 2); + ClientRSocketFactory metadataMimeType(String metadataMimeType); - private boolean multiSubscriberRequester = true; - private boolean leaseEnabled; - private Supplier> leasesSupplier = Leases::new; - private boolean reconnectEnabled; - private Retry retrySpec; + ClientRSocketFactory lease(Supplier> leasesSupplier); - private ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; + ClientRSocketFactory lease(); - public ClientRSocketFactory byteBufAllocator(ByteBufAllocator allocator) { - Objects.requireNonNull(allocator); - this.allocator = allocator; - return this; - } - - public ClientRSocketFactory addConnectionPlugin(DuplexConnectionInterceptor interceptor) { - plugins.addConnectionPlugin(interceptor); - return this; - } - /** Deprecated. Use {@link #addRequesterPlugin(RSocketInterceptor)} instead */ - @Deprecated - public ClientRSocketFactory addClientPlugin(RSocketInterceptor interceptor) { - return addRequesterPlugin(interceptor); - } - - public ClientRSocketFactory addRequesterPlugin(RSocketInterceptor interceptor) { - plugins.addRequesterPlugin(interceptor); - return this; - } - - /** Deprecated. Use {@link #addResponderPlugin(RSocketInterceptor)} instead */ - @Deprecated - public ClientRSocketFactory addServerPlugin(RSocketInterceptor interceptor) { - return addResponderPlugin(interceptor); - } - - public ClientRSocketFactory addResponderPlugin(RSocketInterceptor interceptor) { - plugins.addResponderPlugin(interceptor); - return this; - } - - public ClientRSocketFactory addSocketAcceptorPlugin(SocketAcceptorInterceptor interceptor) { - plugins.addSocketAcceptorPlugin(interceptor); - return this; - } - - /** - * Deprecated as Keep-Alive is not optional according to spec - * - * @return this ClientRSocketFactory - */ - @Deprecated - public ClientRSocketFactory keepAlive() { - return this; - } - - public ClientRSocketFactory keepAlive( - Duration tickPeriod, Duration ackTimeout, int missedAcks) { - this.tickPeriod = tickPeriod; - this.ackTimeout = ackTimeout; - this.missedAcks = missedAcks; - return this; - } - - public ClientRSocketFactory keepAliveTickPeriod(Duration tickPeriod) { - this.tickPeriod = tickPeriod; - return this; - } - - public ClientRSocketFactory keepAliveAckTimeout(Duration ackTimeout) { - this.ackTimeout = ackTimeout; - return this; - } - - public ClientRSocketFactory keepAliveMissedAcks(int missedAcks) { - this.missedAcks = missedAcks; - return this; - } - - public ClientRSocketFactory mimeType(String metadataMimeType, String dataMimeType) { - this.dataMimeType = dataMimeType; - this.metadataMimeType = metadataMimeType; - return this; - } - - public ClientRSocketFactory dataMimeType(String dataMimeType) { - this.dataMimeType = dataMimeType; - return this; - } - - public ClientRSocketFactory metadataMimeType(String metadataMimeType) { - this.metadataMimeType = metadataMimeType; - return this; - } - - public ClientRSocketFactory lease(Supplier> leasesSupplier) { - this.leaseEnabled = true; - this.leasesSupplier = Objects.requireNonNull(leasesSupplier); - return this; - } - - public ClientRSocketFactory lease() { - this.leaseEnabled = true; - return this; - } - - public ClientRSocketFactory singleSubscriberRequester() { - this.multiSubscriberRequester = false; - return this; - } + ClientRSocketFactory singleSubscriberRequester(); /** * Enables a reconnectable, shared instance of {@code Mono} so every subscriber will @@ -312,531 +189,108 @@ public ClientRSocketFactory singleSubscriberRequester() { * @param retrySpec a retry factory applied for {@link Mono#retryWhen(Retry)} * @return a shared instance of {@code Mono}. */ - public ClientRSocketFactory reconnect(Retry retrySpec) { - this.retrySpec = Objects.requireNonNull(retrySpec); - this.reconnectEnabled = true; - return this; - } + ClientRSocketFactory reconnect(Retry retrySpec); - public ClientRSocketFactory resume() { - this.resumeEnabled = true; - return this; - } + ClientRSocketFactory resume(); - public ClientRSocketFactory resumeToken(Supplier resumeTokenSupplier) { - this.resumeTokenSupplier = Objects.requireNonNull(resumeTokenSupplier); - return this; - } + ClientRSocketFactory resumeToken(Supplier resumeTokenSupplier); - public ClientRSocketFactory resumeStore( - Function resumeStoreFactory) { - this.resumeStoreFactory = resumeStoreFactory; - return this; - } + ClientRSocketFactory resumeStore( + Function resumeStoreFactory); - public ClientRSocketFactory resumeSessionDuration(Duration sessionDuration) { - this.resumeSessionDuration = Objects.requireNonNull(sessionDuration); - return this; - } + ClientRSocketFactory resumeSessionDuration(Duration sessionDuration); - public ClientRSocketFactory resumeStreamTimeout(Duration resumeStreamTimeout) { - this.resumeStreamTimeout = Objects.requireNonNull(resumeStreamTimeout); - return this; - } + ClientRSocketFactory resumeStreamTimeout(Duration resumeStreamTimeout); - public ClientRSocketFactory resumeStrategy(Supplier resumeStrategy) { - this.resumeStrategySupplier = Objects.requireNonNull(resumeStrategy); - return this; - } + ClientRSocketFactory resumeStrategy(Supplier resumeStrategy); - public ClientRSocketFactory resumeCleanupOnKeepAlive() { - resumeCleanupStoreOnKeepAlive = true; - return this; - } + ClientRSocketFactory resumeCleanupOnKeepAlive(); @Override - public Start transport(Supplier transportClient) { - return new StartClient(transportClient); - } + Start transport(Supplier transportClient); - public ClientTransportAcceptor acceptor(Function acceptor) { - return acceptor(() -> acceptor); - } + ClientTransportAcceptor acceptor(Function acceptor); - public ClientTransportAcceptor acceptor(Supplier> acceptor) { - return acceptor((setup, sendingSocket) -> Mono.just(acceptor.get().apply(sendingSocket))); - } + ClientTransportAcceptor acceptor(Supplier> acceptor); - public ClientTransportAcceptor acceptor(SocketAcceptor acceptor) { - this.acceptor = acceptor; - return StartClient::new; - } + ClientTransportAcceptor acceptor(SocketAcceptor acceptor); - public ClientRSocketFactory fragment(int mtu) { - this.mtu = mtu; - return this; - } - - public ClientRSocketFactory errorConsumer(Consumer errorConsumer) { - this.errorConsumer = errorConsumer; - return this; - } + ClientRSocketFactory fragment(int mtu); - public ClientRSocketFactory setupPayload(Payload payload) { - this.setupPayload = payload; - return this; - } + ClientRSocketFactory errorConsumer(Consumer errorConsumer); - public ClientRSocketFactory frameDecoder(PayloadDecoder payloadDecoder) { - this.payloadDecoder = payloadDecoder; - return this; - } + ClientRSocketFactory setupPayload(Payload payload); - private class StartClient implements Start { - private final Supplier transportClient; - - StartClient(Supplier transportClient) { - this.transportClient = transportClient; - } - - @Override - public Mono start() { - return newConnection() - .flatMap( - connection -> { - ClientSetup clientSetup = clientSetup(connection); - ByteBuf resumeToken = clientSetup.resumeToken(); - KeepAliveHandler keepAliveHandler = clientSetup.keepAliveHandler(); - DuplexConnection wrappedConnection = clientSetup.connection(); - - ClientServerInputMultiplexer multiplexer = - new ClientServerInputMultiplexer(wrappedConnection, plugins, true); - - boolean isLeaseEnabled = leaseEnabled; - Leases leases = leasesSupplier.get(); - RequesterLeaseHandler requesterLeaseHandler = - isLeaseEnabled - ? new RequesterLeaseHandler.Impl(CLIENT_TAG, leases.receiver()) - : RequesterLeaseHandler.None; - - RSocket rSocketRequester = - new RSocketRequester( - allocator, - multiplexer.asClientConnection(), - payloadDecoder, - errorConsumer, - StreamIdSupplier.clientSupplier(), - keepAliveTickPeriod(), - keepAliveTimeout(), - keepAliveHandler, - requesterLeaseHandler); - - if (multiSubscriberRequester) { - rSocketRequester = new MultiSubscriberRSocket(rSocketRequester); - } - - RSocket wrappedRSocketRequester = plugins.applyRequester(rSocketRequester); - - ByteBuf setupFrame = - SetupFrameFlyweight.encode( - allocator, - isLeaseEnabled, - keepAliveTickPeriod(), - keepAliveTimeout(), - resumeToken, - metadataMimeType, - dataMimeType, - setupPayload); - - ConnectionSetupPayload setup = ConnectionSetupPayload.create(setupFrame); - - return plugins - .applySocketAcceptorInterceptor(acceptor) - .accept(setup, wrappedRSocketRequester) - .flatMap( - rSocketHandler -> { - RSocket wrappedRSocketHandler = plugins.applyResponder(rSocketHandler); - - ResponderLeaseHandler responderLeaseHandler = - isLeaseEnabled - ? new ResponderLeaseHandler.Impl<>( - CLIENT_TAG, - allocator, - leases.sender(), - errorConsumer, - leases.stats()) - : ResponderLeaseHandler.None; - - RSocket rSocketResponder = - new RSocketResponder( - allocator, - multiplexer.asServerConnection(), - wrappedRSocketHandler, - payloadDecoder, - errorConsumer, - responderLeaseHandler); - - return wrappedConnection - .sendOne(setupFrame) - .thenReturn(wrappedRSocketRequester); - }); - }) - .as( - source -> { - if (reconnectEnabled) { - return new ReconnectMono<>( - source.retryWhen(retrySpec), Disposable::dispose, INVALIDATE_FUNCTION); - } else { - return source; - } - }); - } - - private int keepAliveTickPeriod() { - return (int) tickPeriod.toMillis(); - } - - private int keepAliveTimeout() { - return (int) (ackTimeout.toMillis() + tickPeriod.toMillis() * missedAcks); - } - - private ClientSetup clientSetup(DuplexConnection startConnection) { - if (resumeEnabled) { - ByteBuf resumeToken = resumeTokenSupplier.get(); - return new ResumableClientSetup( - allocator, - startConnection, - newConnection(), - resumeToken, - resumeStoreFactory.apply(resumeToken), - resumeSessionDuration, - resumeStreamTimeout, - resumeStrategySupplier, - resumeCleanupStoreOnKeepAlive); - } else { - return new DefaultClientSetup(startConnection); - } - } - - private Mono newConnection() { - return Mono.fromSupplier(transportClient).flatMap(t -> t.connect(mtu)); - } - } + ClientRSocketFactory frameDecoder(PayloadDecoder payloadDecoder); } - public static class ServerRSocketFactory { - private static final String SERVER_TAG = "server"; + /** Factory to create, configure, and start an RSocket server. */ + public interface ServerRSocketFactory { + ServerRSocketFactory byteBufAllocator(ByteBufAllocator allocator); - private SocketAcceptor acceptor; - private PayloadDecoder payloadDecoder = PayloadDecoder.DEFAULT; - private Consumer errorConsumer = Throwable::printStackTrace; - private int mtu = 0; - private PluginRegistry plugins = new PluginRegistry(Plugins.defaultPlugins()); + ServerRSocketFactory addConnectionPlugin(DuplexConnectionInterceptor interceptor); - private boolean resumeSupported; - private Duration resumeSessionDuration = Duration.ofSeconds(120); - private Duration resumeStreamTimeout = Duration.ofSeconds(10); - private Function resumeStoreFactory = - token -> new InMemoryResumableFramesStore(SERVER_TAG, 100_000); + ServerRSocketFactory addRequesterPlugin(RSocketInterceptor interceptor); - private boolean multiSubscriberRequester = true; - private boolean leaseEnabled; - private Supplier> leasesSupplier = Leases::new; + ServerRSocketFactory addResponderPlugin(RSocketInterceptor interceptor); - private ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; - private boolean resumeCleanupStoreOnKeepAlive; + ServerRSocketFactory addSocketAcceptorPlugin(SocketAcceptorInterceptor interceptor); - private ServerRSocketFactory() {} + ServerTransportAcceptor acceptor(SocketAcceptor acceptor); - public ServerRSocketFactory byteBufAllocator(ByteBufAllocator allocator) { - Objects.requireNonNull(allocator); - this.allocator = allocator; - return this; - } + ServerRSocketFactory frameDecoder(PayloadDecoder payloadDecoder); - public ServerRSocketFactory addConnectionPlugin(DuplexConnectionInterceptor interceptor) { - plugins.addConnectionPlugin(interceptor); - return this; - } - /** Deprecated. Use {@link #addRequesterPlugin(RSocketInterceptor)} instead */ - @Deprecated - public ServerRSocketFactory addClientPlugin(RSocketInterceptor interceptor) { - return addRequesterPlugin(interceptor); - } + ServerRSocketFactory fragment(int mtu); - public ServerRSocketFactory addRequesterPlugin(RSocketInterceptor interceptor) { - plugins.addRequesterPlugin(interceptor); - return this; - } + ServerRSocketFactory errorConsumer(Consumer errorConsumer); - /** Deprecated. Use {@link #addResponderPlugin(RSocketInterceptor)} instead */ - @Deprecated - public ServerRSocketFactory addServerPlugin(RSocketInterceptor interceptor) { - return addResponderPlugin(interceptor); - } + ServerRSocketFactory lease(Supplier> leasesSupplier); - public ServerRSocketFactory addResponderPlugin(RSocketInterceptor interceptor) { - plugins.addResponderPlugin(interceptor); - return this; - } + ServerRSocketFactory lease(); - public ServerRSocketFactory addSocketAcceptorPlugin(SocketAcceptorInterceptor interceptor) { - plugins.addSocketAcceptorPlugin(interceptor); - return this; - } + ServerRSocketFactory singleSubscriberRequester(); - public ServerTransportAcceptor acceptor(SocketAcceptor acceptor) { - this.acceptor = acceptor; - return new ServerStart<>(); - } + ServerRSocketFactory resume(); - public ServerRSocketFactory frameDecoder(PayloadDecoder payloadDecoder) { - this.payloadDecoder = payloadDecoder; - return this; - } + ServerRSocketFactory resumeStore( + Function resumeStoreFactory); - public ServerRSocketFactory fragment(int mtu) { - this.mtu = mtu; - return this; - } + ServerRSocketFactory resumeSessionDuration(Duration sessionDuration); - public ServerRSocketFactory errorConsumer(Consumer errorConsumer) { - this.errorConsumer = errorConsumer; - return this; - } + ServerRSocketFactory resumeStreamTimeout(Duration resumeStreamTimeout); - public ServerRSocketFactory lease(Supplier> leasesSupplier) { - this.leaseEnabled = true; - this.leasesSupplier = Objects.requireNonNull(leasesSupplier); - return this; - } + ServerRSocketFactory resumeCleanupOnKeepAlive(); + } - public ServerRSocketFactory lease() { - this.leaseEnabled = true; - return this; - } + public interface ClientTransportAcceptor { + Start transport(Supplier transport); - public ServerRSocketFactory singleSubscriberRequester() { - this.multiSubscriberRequester = false; - return this; + default Start transport(ClientTransport transport) { + return transport(() -> transport); } + } - public ServerRSocketFactory resume() { - this.resumeSupported = true; - return this; - } + public interface ServerTransportAcceptor { - public ServerRSocketFactory resumeStore( - Function resumeStoreFactory) { - this.resumeStoreFactory = resumeStoreFactory; - return this; - } + ServerTransport.ConnectionAcceptor toConnectionAcceptor(); - public ServerRSocketFactory resumeSessionDuration(Duration sessionDuration) { - this.resumeSessionDuration = Objects.requireNonNull(sessionDuration); - return this; - } + Start transport(Supplier> transport); - public ServerRSocketFactory resumeStreamTimeout(Duration resumeStreamTimeout) { - this.resumeStreamTimeout = Objects.requireNonNull(resumeStreamTimeout); - return this; + default Start transport(ServerTransport transport) { + return transport(() -> transport); } + } - public ServerRSocketFactory resumeCleanupOnKeepAlive() { - resumeCleanupStoreOnKeepAlive = true; - return this; - } + public interface Start { + Mono start(); + } - private class ServerStart implements Start, ServerTransportAcceptor { - private Supplier> transportServer; - - @Override - public ServerTransport.ConnectionAcceptor toConnectionAcceptor() { - return new ServerTransport.ConnectionAcceptor() { - private final ServerSetup serverSetup = serverSetup(); - - @Override - public Mono apply(DuplexConnection connection) { - return acceptor(serverSetup, connection); - } - }; - } - - @Override - @SuppressWarnings("unchecked") - public Start transport(Supplier> transport) { - this.transportServer = (Supplier) transport; - return (Start) this::start; - } - - private Mono acceptor(ServerSetup serverSetup, DuplexConnection connection) { - ClientServerInputMultiplexer multiplexer = - new ClientServerInputMultiplexer(connection, plugins, false); - - return multiplexer - .asSetupConnection() - .receive() - .next() - .flatMap(startFrame -> accept(serverSetup, startFrame, multiplexer)); - } - - private Mono acceptResume( - ServerSetup serverSetup, ByteBuf resumeFrame, ClientServerInputMultiplexer multiplexer) { - return serverSetup.acceptRSocketResume(resumeFrame, multiplexer); - } - - private Mono accept( - ServerSetup serverSetup, ByteBuf startFrame, ClientServerInputMultiplexer multiplexer) { - switch (FrameHeaderFlyweight.frameType(startFrame)) { - case SETUP: - return acceptSetup(serverSetup, startFrame, multiplexer); - case RESUME: - return acceptResume(serverSetup, startFrame, multiplexer); - default: - return acceptUnknown(startFrame, multiplexer); - } - } - - private Mono acceptSetup( - ServerSetup serverSetup, ByteBuf setupFrame, ClientServerInputMultiplexer multiplexer) { - - if (!SetupFrameFlyweight.isSupportedVersion(setupFrame)) { - return sendError( - multiplexer, - new InvalidSetupException( - "Unsupported version: " - + SetupFrameFlyweight.humanReadableVersion(setupFrame))) - .doFinally( - signalType -> { - setupFrame.release(); - multiplexer.dispose(); - }); - } - - boolean isLeaseEnabled = leaseEnabled; - - if (SetupFrameFlyweight.honorLease(setupFrame) && !isLeaseEnabled) { - return sendError(multiplexer, new InvalidSetupException("lease is not supported")) - .doFinally( - signalType -> { - setupFrame.release(); - multiplexer.dispose(); - }); - } - - return serverSetup.acceptRSocketSetup( - setupFrame, - multiplexer, - (keepAliveHandler, wrappedMultiplexer) -> { - ConnectionSetupPayload setupPayload = ConnectionSetupPayload.create(setupFrame); - - Leases leases = leasesSupplier.get(); - RequesterLeaseHandler requesterLeaseHandler = - isLeaseEnabled - ? new RequesterLeaseHandler.Impl(SERVER_TAG, leases.receiver()) - : RequesterLeaseHandler.None; - - RSocket rSocketRequester = - new RSocketRequester( - allocator, - wrappedMultiplexer.asServerConnection(), - payloadDecoder, - errorConsumer, - StreamIdSupplier.serverSupplier(), - setupPayload.keepAliveInterval(), - setupPayload.keepAliveMaxLifetime(), - keepAliveHandler, - requesterLeaseHandler); - - if (multiSubscriberRequester) { - rSocketRequester = new MultiSubscriberRSocket(rSocketRequester); - } - RSocket wrappedRSocketRequester = plugins.applyRequester(rSocketRequester); - - return plugins - .applySocketAcceptorInterceptor(acceptor) - .accept(setupPayload, wrappedRSocketRequester) - .onErrorResume( - err -> sendError(multiplexer, rejectedSetupError(err)).then(Mono.error(err))) - .doOnNext( - rSocketHandler -> { - RSocket wrappedRSocketHandler = plugins.applyResponder(rSocketHandler); - - ResponderLeaseHandler responderLeaseHandler = - isLeaseEnabled - ? new ResponderLeaseHandler.Impl<>( - SERVER_TAG, - allocator, - leases.sender(), - errorConsumer, - leases.stats()) - : ResponderLeaseHandler.None; - - RSocket rSocketResponder = - new RSocketResponder( - allocator, - wrappedMultiplexer.asClientConnection(), - wrappedRSocketHandler, - payloadDecoder, - errorConsumer, - responderLeaseHandler); - }) - .doFinally(signalType -> setupPayload.release()) - .then(); - }); - } - - @Override - public Mono start() { - return Mono.defer( - new Supplier>() { - - ServerSetup serverSetup = serverSetup(); - - @Override - public Mono get() { - return Mono.fromSupplier(transportServer) - .flatMap( - transport -> - transport.start( - duplexConnection -> acceptor(serverSetup, duplexConnection), mtu)) - .doOnNext(c -> c.onClose().doFinally(v -> serverSetup.dispose()).subscribe()); - } - }); - } - - private ServerSetup serverSetup() { - return resumeSupported - ? new ServerSetup.ResumableServerSetup( - allocator, - new SessionManager(), - resumeSessionDuration, - resumeStreamTimeout, - resumeStoreFactory, - resumeCleanupStoreOnKeepAlive) - : new ServerSetup.DefaultServerSetup(allocator); - } - - private Mono acceptUnknown(ByteBuf frame, ClientServerInputMultiplexer multiplexer) { - return sendError( - multiplexer, - new InvalidSetupException( - "invalid setup frame: " + FrameHeaderFlyweight.frameType(frame))) - .doFinally( - signalType -> { - frame.release(); - multiplexer.dispose(); - }); - } - - private Mono sendError(ClientServerInputMultiplexer multiplexer, Exception exception) { - return ConnectionUtils.sendError(allocator, multiplexer, exception); - } - - private Exception rejectedSetupError(Throwable err) { - String msg = err.getMessage(); - return new RejectedSetupException(msg == null ? "rejected by server acceptor" : msg); - } + private static Constructor getConstructorFor(String className) { + try { + Class clazz = Class.forName(className); + return clazz.getDeclaredConstructor(); + } catch (Throwable ex) { + throw new IllegalStateException("No " + className); } } } diff --git a/rsocket-core/src/main/java/io/rsocket/core/DefaultClientRSocketFactory.java b/rsocket-core/src/main/java/io/rsocket/core/DefaultClientRSocketFactory.java new file mode 100644 index 000000000..ce43cd1fd --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/core/DefaultClientRSocketFactory.java @@ -0,0 +1,430 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.rsocket.core; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.rsocket.AbstractRSocket; +import io.rsocket.ConnectionSetupPayload; +import io.rsocket.DuplexConnection; +import io.rsocket.Payload; +import io.rsocket.RSocket; +import io.rsocket.RSocketFactory; +import io.rsocket.SocketAcceptor; +import io.rsocket.frame.ResumeFrameFlyweight; +import io.rsocket.frame.SetupFrameFlyweight; +import io.rsocket.frame.decoder.PayloadDecoder; +import io.rsocket.internal.ClientServerInputMultiplexer; +import io.rsocket.internal.ClientSetup; +import io.rsocket.keepalive.KeepAliveHandler; +import io.rsocket.lease.LeaseStats; +import io.rsocket.lease.Leases; +import io.rsocket.lease.RequesterLeaseHandler; +import io.rsocket.lease.ResponderLeaseHandler; +import io.rsocket.plugins.DuplexConnectionInterceptor; +import io.rsocket.plugins.PluginRegistry; +import io.rsocket.plugins.Plugins; +import io.rsocket.plugins.RSocketInterceptor; +import io.rsocket.plugins.SocketAcceptorInterceptor; +import io.rsocket.resume.ExponentialBackoffResumeStrategy; +import io.rsocket.resume.InMemoryResumableFramesStore; +import io.rsocket.resume.ResumableFramesStore; +import io.rsocket.resume.ResumeStrategy; +import io.rsocket.transport.ClientTransport; +import io.rsocket.util.EmptyPayload; +import io.rsocket.util.MultiSubscriberRSocket; +import java.time.Duration; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import reactor.core.Disposable; +import reactor.core.publisher.Mono; +import reactor.util.retry.Retry; + +/** + * Default implementation of {@link RSocketFactory.ClientRSocketFactory} that can be instantiated + * directly or through the shortcut {@link RSocketFactory#connect()}. + */ +public class DefaultClientRSocketFactory implements RSocketFactory.ClientRSocketFactory { + private static final String CLIENT_TAG = "client"; + + private static final BiConsumer INVALIDATE_FUNCTION = + (r, i) -> r.onClose().subscribe(null, null, i::invalidate); + + private SocketAcceptor acceptor = (setup, sendingSocket) -> Mono.just(new AbstractRSocket() {}); + + private Consumer errorConsumer = Throwable::printStackTrace; + private int mtu = 0; + private PluginRegistry plugins = new PluginRegistry(Plugins.defaultPlugins()); + + private Payload setupPayload = EmptyPayload.INSTANCE; + private PayloadDecoder payloadDecoder = PayloadDecoder.DEFAULT; + + private Duration tickPeriod = Duration.ofSeconds(20); + private Duration ackTimeout = Duration.ofSeconds(30); + private int missedAcks = 3; + + private String metadataMimeType = "application/binary"; + private String dataMimeType = "application/binary"; + + private boolean resumeEnabled; + private boolean resumeCleanupStoreOnKeepAlive; + private Supplier resumeTokenSupplier = ResumeFrameFlyweight::generateResumeToken; + private Function resumeStoreFactory = + token -> new InMemoryResumableFramesStore(CLIENT_TAG, 100_000); + private Duration resumeSessionDuration = Duration.ofMinutes(2); + private Duration resumeStreamTimeout = Duration.ofSeconds(10); + private Supplier resumeStrategySupplier = + () -> new ExponentialBackoffResumeStrategy(Duration.ofSeconds(1), Duration.ofSeconds(16), 2); + + private boolean multiSubscriberRequester = true; + private boolean leaseEnabled; + private Supplier> leasesSupplier = Leases::new; + private boolean reconnectEnabled; + private Retry retrySpec; + + private ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; + + @Override + public RSocketFactory.ClientRSocketFactory byteBufAllocator(ByteBufAllocator allocator) { + Objects.requireNonNull(allocator); + this.allocator = allocator; + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory addConnectionPlugin( + DuplexConnectionInterceptor interceptor) { + plugins.addConnectionPlugin(interceptor); + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory addRequesterPlugin(RSocketInterceptor interceptor) { + plugins.addRequesterPlugin(interceptor); + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory addResponderPlugin(RSocketInterceptor interceptor) { + plugins.addResponderPlugin(interceptor); + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory addSocketAcceptorPlugin( + SocketAcceptorInterceptor interceptor) { + plugins.addSocketAcceptorPlugin(interceptor); + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory keepAlive( + Duration tickPeriod, Duration ackTimeout, int missedAcks) { + this.tickPeriod = tickPeriod; + this.ackTimeout = ackTimeout; + this.missedAcks = missedAcks; + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory keepAliveTickPeriod(Duration tickPeriod) { + this.tickPeriod = tickPeriod; + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory keepAliveAckTimeout(Duration ackTimeout) { + this.ackTimeout = ackTimeout; + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory keepAliveMissedAcks(int missedAcks) { + this.missedAcks = missedAcks; + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory mimeType( + String metadataMimeType, String dataMimeType) { + this.dataMimeType = dataMimeType; + this.metadataMimeType = metadataMimeType; + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory dataMimeType(String dataMimeType) { + this.dataMimeType = dataMimeType; + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory metadataMimeType(String metadataMimeType) { + this.metadataMimeType = metadataMimeType; + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory lease( + Supplier> leasesSupplier) { + this.leaseEnabled = true; + this.leasesSupplier = Objects.requireNonNull(leasesSupplier); + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory lease() { + this.leaseEnabled = true; + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory singleSubscriberRequester() { + this.multiSubscriberRequester = false; + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory reconnect(Retry retrySpec) { + this.retrySpec = Objects.requireNonNull(retrySpec); + this.reconnectEnabled = true; + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory resume() { + this.resumeEnabled = true; + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory resumeToken(Supplier resumeTokenSupplier) { + this.resumeTokenSupplier = Objects.requireNonNull(resumeTokenSupplier); + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory resumeStore( + Function resumeStoreFactory) { + this.resumeStoreFactory = resumeStoreFactory; + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory resumeSessionDuration(Duration sessionDuration) { + this.resumeSessionDuration = Objects.requireNonNull(sessionDuration); + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory resumeStreamTimeout(Duration resumeStreamTimeout) { + this.resumeStreamTimeout = Objects.requireNonNull(resumeStreamTimeout); + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory resumeStrategy( + Supplier resumeStrategy) { + this.resumeStrategySupplier = Objects.requireNonNull(resumeStrategy); + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory resumeCleanupOnKeepAlive() { + resumeCleanupStoreOnKeepAlive = true; + return this; + } + + @Override + public RSocketFactory.Start transport(Supplier transportClient) { + return new StartClient(transportClient); + } + + @Override + public RSocketFactory.ClientTransportAcceptor acceptor(Function acceptor) { + return acceptor(() -> acceptor); + } + + @Override + public RSocketFactory.ClientTransportAcceptor acceptor( + Supplier> acceptor) { + return acceptor((setup, sendingSocket) -> Mono.just(acceptor.get().apply(sendingSocket))); + } + + @Override + public RSocketFactory.ClientTransportAcceptor acceptor(SocketAcceptor acceptor) { + this.acceptor = acceptor; + return StartClient::new; + } + + @Override + public RSocketFactory.ClientRSocketFactory fragment(int mtu) { + this.mtu = mtu; + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory errorConsumer(Consumer errorConsumer) { + this.errorConsumer = errorConsumer; + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory setupPayload(Payload payload) { + this.setupPayload = payload; + return this; + } + + @Override + public RSocketFactory.ClientRSocketFactory frameDecoder(PayloadDecoder payloadDecoder) { + this.payloadDecoder = payloadDecoder; + return this; + } + + private class StartClient implements RSocketFactory.Start { + private final Supplier transportClient; + + StartClient(Supplier transportClient) { + this.transportClient = transportClient; + } + + @Override + public Mono start() { + return newConnection() + .flatMap( + connection -> { + ClientSetup clientSetup = clientSetup(connection); + ByteBuf resumeToken = clientSetup.resumeToken(); + KeepAliveHandler keepAliveHandler = clientSetup.keepAliveHandler(); + DuplexConnection wrappedConnection = clientSetup.connection(); + + ClientServerInputMultiplexer multiplexer = + new ClientServerInputMultiplexer(wrappedConnection, plugins, true); + + boolean isLeaseEnabled = leaseEnabled; + Leases leases = leasesSupplier.get(); + RequesterLeaseHandler requesterLeaseHandler = + isLeaseEnabled + ? new RequesterLeaseHandler.Impl(CLIENT_TAG, leases.receiver()) + : RequesterLeaseHandler.None; + + RSocket rSocketRequester = + new RSocketRequester( + allocator, + multiplexer.asClientConnection(), + payloadDecoder, + errorConsumer, + StreamIdSupplier.clientSupplier(), + keepAliveTickPeriod(), + keepAliveTimeout(), + keepAliveHandler, + requesterLeaseHandler); + + if (multiSubscriberRequester) { + rSocketRequester = new MultiSubscriberRSocket(rSocketRequester); + } + + RSocket wrappedRSocketRequester = plugins.applyRequester(rSocketRequester); + + ByteBuf setupFrame = + SetupFrameFlyweight.encode( + allocator, + isLeaseEnabled, + keepAliveTickPeriod(), + keepAliveTimeout(), + resumeToken, + metadataMimeType, + dataMimeType, + setupPayload); + + ConnectionSetupPayload setup = new DefaultConnectionSetupPayload(setupFrame); + + return plugins + .applySocketAcceptorInterceptor(acceptor) + .accept(setup, wrappedRSocketRequester) + .flatMap( + rSocketHandler -> { + RSocket wrappedRSocketHandler = plugins.applyResponder(rSocketHandler); + + ResponderLeaseHandler responderLeaseHandler = + isLeaseEnabled + ? new ResponderLeaseHandler.Impl<>( + CLIENT_TAG, + allocator, + leases.sender(), + errorConsumer, + leases.stats()) + : ResponderLeaseHandler.None; + + RSocket rSocketResponder = + new RSocketResponder( + allocator, + multiplexer.asServerConnection(), + wrappedRSocketHandler, + payloadDecoder, + errorConsumer, + responderLeaseHandler); + + return wrappedConnection + .sendOne(setupFrame) + .thenReturn(wrappedRSocketRequester); + }); + }) + .as( + source -> { + if (reconnectEnabled) { + return new ReconnectMono<>( + source.retryWhen(retrySpec), Disposable::dispose, INVALIDATE_FUNCTION); + } else { + return source; + } + }); + } + + private int keepAliveTickPeriod() { + return (int) tickPeriod.toMillis(); + } + + private int keepAliveTimeout() { + return (int) (ackTimeout.toMillis() + tickPeriod.toMillis() * missedAcks); + } + + private ClientSetup clientSetup(DuplexConnection startConnection) { + if (resumeEnabled) { + ByteBuf resumeToken = resumeTokenSupplier.get(); + return new ClientSetup.ResumableClientSetup( + allocator, + startConnection, + newConnection(), + resumeToken, + resumeStoreFactory.apply(resumeToken), + resumeSessionDuration, + resumeStreamTimeout, + resumeStrategySupplier, + resumeCleanupStoreOnKeepAlive); + } else { + return new ClientSetup.DefaultClientSetup(startConnection); + } + } + + private Mono newConnection() { + return Mono.fromSupplier(transportClient).flatMap(t -> t.connect(mtu)); + } + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/core/DefaultConnectionSetupPayload.java b/rsocket-core/src/main/java/io/rsocket/core/DefaultConnectionSetupPayload.java new file mode 100644 index 000000000..23eeac160 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/core/DefaultConnectionSetupPayload.java @@ -0,0 +1,128 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.rsocket.core; + +import io.netty.buffer.ByteBuf; +import io.netty.util.AbstractReferenceCounted; +import io.rsocket.ConnectionSetupPayload; +import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.SetupFrameFlyweight; + +/** Default implementation of {@link ConnectionSetupPayload}. */ +class DefaultConnectionSetupPayload extends AbstractReferenceCounted + implements ConnectionSetupPayload { + + private final ByteBuf setupFrame; + + public DefaultConnectionSetupPayload(ByteBuf setupFrame) { + this.setupFrame = setupFrame; + } + + @Override + public ConnectionSetupPayload retain() { + super.retain(); + return this; + } + + @Override + public ConnectionSetupPayload retain(int increment) { + super.retain(increment); + return this; + } + + @Override + public boolean hasMetadata() { + return FrameHeaderFlyweight.hasMetadata(setupFrame); + } + + @Override + public int keepAliveInterval() { + return SetupFrameFlyweight.keepAliveInterval(setupFrame); + } + + @Override + public int keepAliveMaxLifetime() { + return SetupFrameFlyweight.keepAliveMaxLifetime(setupFrame); + } + + @Override + public String metadataMimeType() { + return SetupFrameFlyweight.metadataMimeType(setupFrame); + } + + @Override + public String dataMimeType() { + return SetupFrameFlyweight.dataMimeType(setupFrame); + } + + @Override + public int getFlags() { + return FrameHeaderFlyweight.flags(setupFrame); + } + + @Override + public boolean willClientHonorLease() { + return SetupFrameFlyweight.honorLease(setupFrame); + } + + @Override + public boolean isResumeEnabled() { + return SetupFrameFlyweight.resumeEnabled(setupFrame); + } + + @Override + public ByteBuf resumeToken() { + return SetupFrameFlyweight.resumeToken(setupFrame); + } + + @Override + public ConnectionSetupPayload touch() { + setupFrame.touch(); + return this; + } + + @Override + public ConnectionSetupPayload touch(Object hint) { + setupFrame.touch(hint); + return this; + } + + @Override + protected void deallocate() { + setupFrame.release(); + } + + @Override + public ByteBuf sliceMetadata() { + return SetupFrameFlyweight.metadata(setupFrame); + } + + @Override + public ByteBuf sliceData() { + return SetupFrameFlyweight.data(setupFrame); + } + + @Override + public ByteBuf data() { + return sliceData(); + } + + @Override + public ByteBuf metadata() { + return sliceMetadata(); + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/core/DefaultServerRSocketFactory.java b/rsocket-core/src/main/java/io/rsocket/core/DefaultServerRSocketFactory.java new file mode 100644 index 000000000..f2acb9af0 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/core/DefaultServerRSocketFactory.java @@ -0,0 +1,379 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.rsocket.core; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.rsocket.Closeable; +import io.rsocket.ConnectionSetupPayload; +import io.rsocket.DuplexConnection; +import io.rsocket.RSocket; +import io.rsocket.RSocketFactory; +import io.rsocket.SocketAcceptor; +import io.rsocket.exceptions.InvalidSetupException; +import io.rsocket.exceptions.RejectedSetupException; +import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.SetupFrameFlyweight; +import io.rsocket.frame.decoder.PayloadDecoder; +import io.rsocket.internal.ClientServerInputMultiplexer; +import io.rsocket.internal.ServerSetup; +import io.rsocket.lease.Leases; +import io.rsocket.lease.RequesterLeaseHandler; +import io.rsocket.lease.ResponderLeaseHandler; +import io.rsocket.plugins.DuplexConnectionInterceptor; +import io.rsocket.plugins.PluginRegistry; +import io.rsocket.plugins.Plugins; +import io.rsocket.plugins.RSocketInterceptor; +import io.rsocket.plugins.SocketAcceptorInterceptor; +import io.rsocket.resume.InMemoryResumableFramesStore; +import io.rsocket.resume.ResumableFramesStore; +import io.rsocket.resume.SessionManager; +import io.rsocket.transport.ServerTransport; +import io.rsocket.util.ConnectionUtils; +import io.rsocket.util.MultiSubscriberRSocket; +import java.time.Duration; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import reactor.core.publisher.Mono; + +/** + * Default implementation of {@link RSocketFactory.ServerRSocketFactory} that can be instantiated + * directly or through the shortcut {@link RSocketFactory#receive()}. + */ +public class DefaultServerRSocketFactory implements RSocketFactory.ServerRSocketFactory { + private static final String SERVER_TAG = "server"; + + private SocketAcceptor acceptor; + private PayloadDecoder payloadDecoder = PayloadDecoder.DEFAULT; + private Consumer errorConsumer = Throwable::printStackTrace; + private int mtu = 0; + private PluginRegistry plugins = new PluginRegistry(Plugins.defaultPlugins()); + + private boolean resumeSupported; + private Duration resumeSessionDuration = Duration.ofSeconds(120); + private Duration resumeStreamTimeout = Duration.ofSeconds(10); + private Function resumeStoreFactory = + token -> new InMemoryResumableFramesStore(SERVER_TAG, 100_000); + + private boolean multiSubscriberRequester = true; + private boolean leaseEnabled; + private Supplier> leasesSupplier = Leases::new; + + private ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; + private boolean resumeCleanupStoreOnKeepAlive; + + @Override + public RSocketFactory.ServerRSocketFactory byteBufAllocator(ByteBufAllocator allocator) { + Objects.requireNonNull(allocator); + this.allocator = allocator; + return this; + } + + @Override + public RSocketFactory.ServerRSocketFactory addConnectionPlugin( + DuplexConnectionInterceptor interceptor) { + plugins.addConnectionPlugin(interceptor); + return this; + } + + @Override + public RSocketFactory.ServerRSocketFactory addRequesterPlugin(RSocketInterceptor interceptor) { + plugins.addRequesterPlugin(interceptor); + return this; + } + + @Override + public RSocketFactory.ServerRSocketFactory addResponderPlugin(RSocketInterceptor interceptor) { + plugins.addResponderPlugin(interceptor); + return this; + } + + @Override + public RSocketFactory.ServerRSocketFactory addSocketAcceptorPlugin( + SocketAcceptorInterceptor interceptor) { + plugins.addSocketAcceptorPlugin(interceptor); + return this; + } + + @Override + public RSocketFactory.ServerTransportAcceptor acceptor(SocketAcceptor acceptor) { + this.acceptor = acceptor; + return new ServerStart<>(); + } + + @Override + public RSocketFactory.ServerRSocketFactory frameDecoder(PayloadDecoder payloadDecoder) { + this.payloadDecoder = payloadDecoder; + return this; + } + + @Override + public RSocketFactory.ServerRSocketFactory fragment(int mtu) { + this.mtu = mtu; + return this; + } + + @Override + public RSocketFactory.ServerRSocketFactory errorConsumer(Consumer errorConsumer) { + this.errorConsumer = errorConsumer; + return this; + } + + @Override + public RSocketFactory.ServerRSocketFactory lease(Supplier> leasesSupplier) { + this.leaseEnabled = true; + this.leasesSupplier = Objects.requireNonNull(leasesSupplier); + return this; + } + + @Override + public RSocketFactory.ServerRSocketFactory lease() { + this.leaseEnabled = true; + return this; + } + + @Override + public RSocketFactory.ServerRSocketFactory singleSubscriberRequester() { + this.multiSubscriberRequester = false; + return this; + } + + @Override + public RSocketFactory.ServerRSocketFactory resume() { + this.resumeSupported = true; + return this; + } + + @Override + public RSocketFactory.ServerRSocketFactory resumeStore( + Function resumeStoreFactory) { + this.resumeStoreFactory = resumeStoreFactory; + return this; + } + + @Override + public RSocketFactory.ServerRSocketFactory resumeSessionDuration(Duration sessionDuration) { + this.resumeSessionDuration = Objects.requireNonNull(sessionDuration); + return this; + } + + @Override + public RSocketFactory.ServerRSocketFactory resumeStreamTimeout(Duration resumeStreamTimeout) { + this.resumeStreamTimeout = Objects.requireNonNull(resumeStreamTimeout); + return this; + } + + @Override + public RSocketFactory.ServerRSocketFactory resumeCleanupOnKeepAlive() { + resumeCleanupStoreOnKeepAlive = true; + return this; + } + + private class ServerStart + implements RSocketFactory.Start, RSocketFactory.ServerTransportAcceptor { + private Supplier> transportServer; + + @Override + public ServerTransport.ConnectionAcceptor toConnectionAcceptor() { + return new ServerTransport.ConnectionAcceptor() { + private final ServerSetup serverSetup = serverSetup(); + + @Override + public Mono apply(DuplexConnection connection) { + return acceptor(serverSetup, connection); + } + }; + } + + @Override + @SuppressWarnings("unchecked") + public RSocketFactory.Start transport( + Supplier> transport) { + this.transportServer = (Supplier) transport; + return (RSocketFactory.Start) this::start; + } + + private Mono acceptor(ServerSetup serverSetup, DuplexConnection connection) { + ClientServerInputMultiplexer multiplexer = + new ClientServerInputMultiplexer(connection, plugins, false); + + return multiplexer + .asSetupConnection() + .receive() + .next() + .flatMap(startFrame -> accept(serverSetup, startFrame, multiplexer)); + } + + private Mono acceptResume( + ServerSetup serverSetup, ByteBuf resumeFrame, ClientServerInputMultiplexer multiplexer) { + return serverSetup.acceptRSocketResume(resumeFrame, multiplexer); + } + + private Mono accept( + ServerSetup serverSetup, ByteBuf startFrame, ClientServerInputMultiplexer multiplexer) { + switch (FrameHeaderFlyweight.frameType(startFrame)) { + case SETUP: + return acceptSetup(serverSetup, startFrame, multiplexer); + case RESUME: + return acceptResume(serverSetup, startFrame, multiplexer); + default: + return acceptUnknown(startFrame, multiplexer); + } + } + + private Mono acceptSetup( + ServerSetup serverSetup, ByteBuf setupFrame, ClientServerInputMultiplexer multiplexer) { + + if (!SetupFrameFlyweight.isSupportedVersion(setupFrame)) { + return sendError( + multiplexer, + new InvalidSetupException( + "Unsupported version: " + SetupFrameFlyweight.humanReadableVersion(setupFrame))) + .doFinally( + signalType -> { + setupFrame.release(); + multiplexer.dispose(); + }); + } + + boolean isLeaseEnabled = leaseEnabled; + + if (SetupFrameFlyweight.honorLease(setupFrame) && !isLeaseEnabled) { + return sendError(multiplexer, new InvalidSetupException("lease is not supported")) + .doFinally( + signalType -> { + setupFrame.release(); + multiplexer.dispose(); + }); + } + + return serverSetup.acceptRSocketSetup( + setupFrame, + multiplexer, + (keepAliveHandler, wrappedMultiplexer) -> { + ConnectionSetupPayload setupPayload = new DefaultConnectionSetupPayload(setupFrame); + + Leases leases = leasesSupplier.get(); + RequesterLeaseHandler requesterLeaseHandler = + isLeaseEnabled + ? new RequesterLeaseHandler.Impl(SERVER_TAG, leases.receiver()) + : RequesterLeaseHandler.None; + + RSocket rSocketRequester = + new RSocketRequester( + allocator, + wrappedMultiplexer.asServerConnection(), + payloadDecoder, + errorConsumer, + StreamIdSupplier.serverSupplier(), + setupPayload.keepAliveInterval(), + setupPayload.keepAliveMaxLifetime(), + keepAliveHandler, + requesterLeaseHandler); + + if (multiSubscriberRequester) { + rSocketRequester = new MultiSubscriberRSocket(rSocketRequester); + } + RSocket wrappedRSocketRequester = plugins.applyRequester(rSocketRequester); + + return plugins + .applySocketAcceptorInterceptor(acceptor) + .accept(setupPayload, wrappedRSocketRequester) + .onErrorResume( + err -> sendError(multiplexer, rejectedSetupError(err)).then(Mono.error(err))) + .doOnNext( + rSocketHandler -> { + RSocket wrappedRSocketHandler = plugins.applyResponder(rSocketHandler); + + ResponderLeaseHandler responderLeaseHandler = + isLeaseEnabled + ? new ResponderLeaseHandler.Impl<>( + SERVER_TAG, + allocator, + leases.sender(), + errorConsumer, + leases.stats()) + : ResponderLeaseHandler.None; + + RSocket rSocketResponder = + new RSocketResponder( + allocator, + wrappedMultiplexer.asClientConnection(), + wrappedRSocketHandler, + payloadDecoder, + errorConsumer, + responderLeaseHandler); + }) + .doFinally(signalType -> setupPayload.release()) + .then(); + }); + } + + @Override + public Mono start() { + return Mono.defer( + new Supplier>() { + + ServerSetup serverSetup = serverSetup(); + + @Override + public Mono get() { + return Mono.fromSupplier(transportServer) + .flatMap( + transport -> + transport.start( + duplexConnection -> acceptor(serverSetup, duplexConnection), mtu)) + .doOnNext(c -> c.onClose().doFinally(v -> serverSetup.dispose()).subscribe()); + } + }); + } + + private ServerSetup serverSetup() { + return resumeSupported + ? new ServerSetup.ResumableServerSetup( + allocator, + new SessionManager(), + resumeSessionDuration, + resumeStreamTimeout, + resumeStoreFactory, + resumeCleanupStoreOnKeepAlive) + : new ServerSetup.DefaultServerSetup(allocator); + } + + private Mono acceptUnknown(ByteBuf frame, ClientServerInputMultiplexer multiplexer) { + return sendError( + multiplexer, + new InvalidSetupException( + "invalid setup frame: " + FrameHeaderFlyweight.frameType(frame))) + .doFinally( + signalType -> { + frame.release(); + multiplexer.dispose(); + }); + } + + private Mono sendError(ClientServerInputMultiplexer multiplexer, Exception exception) { + return ConnectionUtils.sendError(allocator, multiplexer, exception); + } + + private Exception rejectedSetupError(Throwable err) { + String msg = err.getMessage(); + return new RejectedSetupException(msg == null ? "rejected by server acceptor" : msg); + } + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java similarity index 99% rename from rsocket-core/src/main/java/io/rsocket/RSocketRequester.java rename to rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index ed13e9f6e..e39fa7a29 100644 --- a/rsocket-core/src/main/java/io/rsocket/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.rsocket; +package io.rsocket.core; import static io.rsocket.keepalive.KeepAliveSupport.ClientKeepAliveSupport; import static io.rsocket.keepalive.KeepAliveSupport.KeepAlive; @@ -23,6 +23,9 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.util.ReferenceCountUtil; import io.netty.util.collection.IntObjectMap; +import io.rsocket.DuplexConnection; +import io.rsocket.Payload; +import io.rsocket.RSocket; import io.rsocket.exceptions.ConnectionErrorException; import io.rsocket.exceptions.Exceptions; import io.rsocket.frame.CancelFrameFlyweight; diff --git a/rsocket-core/src/main/java/io/rsocket/RSocketResponder.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java similarity index 99% rename from rsocket-core/src/main/java/io/rsocket/RSocketResponder.java rename to rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java index 53ced9763..d742300d1 100644 --- a/rsocket-core/src/main/java/io/rsocket/RSocketResponder.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java @@ -14,12 +14,16 @@ * limitations under the License. */ -package io.rsocket; +package io.rsocket.core; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.util.ReferenceCountUtil; import io.netty.util.collection.IntObjectMap; +import io.rsocket.DuplexConnection; +import io.rsocket.Payload; +import io.rsocket.RSocket; +import io.rsocket.ResponderRSocket; import io.rsocket.exceptions.ApplicationErrorException; import io.rsocket.frame.*; import io.rsocket.frame.decoder.PayloadDecoder; diff --git a/rsocket-core/src/main/java/io/rsocket/ReconnectMono.java b/rsocket-core/src/main/java/io/rsocket/core/ReconnectMono.java similarity index 99% rename from rsocket-core/src/main/java/io/rsocket/ReconnectMono.java rename to rsocket-core/src/main/java/io/rsocket/core/ReconnectMono.java index b2c36908d..81f6625f0 100644 --- a/rsocket-core/src/main/java/io/rsocket/ReconnectMono.java +++ b/rsocket-core/src/main/java/io/rsocket/core/ReconnectMono.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.rsocket; +package io.rsocket.core; import java.time.Duration; import java.util.concurrent.CancellationException; diff --git a/rsocket-core/src/main/java/io/rsocket/StreamIdSupplier.java b/rsocket-core/src/main/java/io/rsocket/core/StreamIdSupplier.java similarity index 98% rename from rsocket-core/src/main/java/io/rsocket/StreamIdSupplier.java rename to rsocket-core/src/main/java/io/rsocket/core/StreamIdSupplier.java index af8c6b3d0..70734b8c0 100644 --- a/rsocket-core/src/main/java/io/rsocket/StreamIdSupplier.java +++ b/rsocket-core/src/main/java/io/rsocket/core/StreamIdSupplier.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.rsocket; +package io.rsocket.core; import io.netty.util.collection.IntObjectMap; import java.util.concurrent.atomic.AtomicLongFieldUpdater; diff --git a/rsocket-core/src/main/java/io/rsocket/core/package-info.java b/rsocket-core/src/main/java/io/rsocket/core/package-info.java new file mode 100644 index 000000000..a70bb3b16 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/core/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Contains core RSocket protocol, client and server implementation classes, including factories to + * create and configure them. + */ +@NonNullApi +package io.rsocket.core; + +import reactor.util.annotation.NonNullApi; diff --git a/rsocket-core/src/main/java/io/rsocket/plugins/SocketAcceptorInterceptor.java b/rsocket-core/src/main/java/io/rsocket/plugins/SocketAcceptorInterceptor.java index c9201ca5b..0cb9d92d2 100644 --- a/rsocket-core/src/main/java/io/rsocket/plugins/SocketAcceptorInterceptor.java +++ b/rsocket-core/src/main/java/io/rsocket/plugins/SocketAcceptorInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2015-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/rsocket-core/src/test/java/io/rsocket/AbstractSocketRule.java b/rsocket-core/src/test/java/io/rsocket/core/AbstractSocketRule.java similarity index 97% rename from rsocket-core/src/test/java/io/rsocket/AbstractSocketRule.java rename to rsocket-core/src/test/java/io/rsocket/core/AbstractSocketRule.java index 22568bfcc..dc01e7911 100644 --- a/rsocket-core/src/test/java/io/rsocket/AbstractSocketRule.java +++ b/rsocket-core/src/test/java/io/rsocket/core/AbstractSocketRule.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package io.rsocket; +package io.rsocket.core; +import io.rsocket.RSocket; import io.rsocket.test.util.TestDuplexConnection; import io.rsocket.test.util.TestSubscriber; import java.util.concurrent.ConcurrentLinkedQueue; diff --git a/rsocket-core/src/test/java/io/rsocket/ConnectionSetupPayloadTest.java b/rsocket-core/src/test/java/io/rsocket/core/ConnectionSetupPayloadTest.java similarity index 89% rename from rsocket-core/src/test/java/io/rsocket/ConnectionSetupPayloadTest.java rename to rsocket-core/src/test/java/io/rsocket/core/ConnectionSetupPayloadTest.java index 16e0f2ec7..ea3142d25 100644 --- a/rsocket-core/src/test/java/io/rsocket/ConnectionSetupPayloadTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/ConnectionSetupPayloadTest.java @@ -1,4 +1,4 @@ -package io.rsocket; +package io.rsocket.core; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -6,6 +6,8 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; +import io.rsocket.ConnectionSetupPayload; +import io.rsocket.Payload; import io.rsocket.frame.SetupFrameFlyweight; import io.rsocket.util.DefaultPayload; import org.junit.jupiter.api.Test; @@ -24,7 +26,7 @@ void testSetupPayloadWithDataMetadata() { boolean leaseEnabled = true; ByteBuf frame = encodeSetupFrame(leaseEnabled, payload); - ConnectionSetupPayload setupPayload = ConnectionSetupPayload.create(frame); + ConnectionSetupPayload setupPayload = new DefaultConnectionSetupPayload(frame); assertTrue(setupPayload.willClientHonorLease()); assertEquals(KEEP_ALIVE_INTERVAL, setupPayload.keepAliveInterval()); @@ -46,7 +48,7 @@ void testSetupPayloadWithNoMetadata() { boolean leaseEnabled = false; ByteBuf frame = encodeSetupFrame(leaseEnabled, payload); - ConnectionSetupPayload setupPayload = ConnectionSetupPayload.create(frame); + ConnectionSetupPayload setupPayload = new DefaultConnectionSetupPayload(frame); assertFalse(setupPayload.willClientHonorLease()); assertFalse(setupPayload.hasMetadata()); @@ -64,7 +66,7 @@ void testSetupPayloadWithEmptyMetadata() { boolean leaseEnabled = false; ByteBuf frame = encodeSetupFrame(leaseEnabled, payload); - ConnectionSetupPayload setupPayload = ConnectionSetupPayload.create(frame); + ConnectionSetupPayload setupPayload = new DefaultConnectionSetupPayload(frame); assertFalse(setupPayload.willClientHonorLease()); assertTrue(setupPayload.hasMetadata()); diff --git a/rsocket-core/src/test/java/io/rsocket/KeepAliveTest.java b/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java similarity index 99% rename from rsocket-core/src/test/java/io/rsocket/KeepAliveTest.java rename to rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java index b275ccc33..6cb05dec1 100644 --- a/rsocket-core/src/test/java/io/rsocket/KeepAliveTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.rsocket; +package io.rsocket.core; import static io.rsocket.keepalive.KeepAliveHandler.DefaultKeepAliveHandler; import static io.rsocket.keepalive.KeepAliveHandler.ResumableKeepAliveHandler; @@ -22,6 +22,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; +import io.rsocket.RSocket; import io.rsocket.exceptions.ConnectionErrorException; import io.rsocket.frame.FrameHeaderFlyweight; import io.rsocket.frame.FrameType; diff --git a/rsocket-core/src/test/java/io/rsocket/RSocketLeaseTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java similarity index 99% rename from rsocket-core/src/test/java/io/rsocket/RSocketLeaseTest.java rename to rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java index 3af8916cd..3cbb3c5d7 100644 --- a/rsocket-core/src/test/java/io/rsocket/RSocketLeaseTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.rsocket; +package io.rsocket.core; import static io.rsocket.frame.FrameType.ERROR; import static io.rsocket.frame.FrameType.SETUP; @@ -25,6 +25,7 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import io.netty.buffer.UnpooledByteBufAllocator; +import io.rsocket.*; import io.rsocket.exceptions.Exceptions; import io.rsocket.exceptions.MissingLeaseException; import io.rsocket.frame.FrameHeaderFlyweight; diff --git a/rsocket-core/src/test/java/io/rsocket/RSocketReconnectTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketReconnectTest.java similarity index 98% rename from rsocket-core/src/test/java/io/rsocket/RSocketReconnectTest.java rename to rsocket-core/src/test/java/io/rsocket/core/RSocketReconnectTest.java index b5ca4858e..f7ba12ada 100644 --- a/rsocket-core/src/test/java/io/rsocket/RSocketReconnectTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketReconnectTest.java @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.rsocket; +package io.rsocket.core; import static org.junit.Assert.assertEquals; +import io.rsocket.RSocket; +import io.rsocket.RSocketFactory; import io.rsocket.test.util.TestClientTransport; import io.rsocket.transport.ClientTransport; import java.io.UncheckedIOException; diff --git a/rsocket-core/src/test/java/io/rsocket/RSocketRequesterSubscribersTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java similarity index 98% rename from rsocket-core/src/test/java/io/rsocket/RSocketRequesterSubscribersTest.java rename to rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java index b49dbe809..6b903292c 100644 --- a/rsocket-core/src/test/java/io/rsocket/RSocketRequesterSubscribersTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package io.rsocket; +package io.rsocket.core; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.rsocket.RSocket; import io.rsocket.frame.FrameHeaderFlyweight; import io.rsocket.frame.FrameType; import io.rsocket.frame.decoder.PayloadDecoder; diff --git a/rsocket-core/src/test/java/io/rsocket/RSocketRequesterTerminationTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTerminationTest.java similarity index 93% rename from rsocket-core/src/test/java/io/rsocket/RSocketRequesterTerminationTest.java rename to rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTerminationTest.java index a2c17cf95..de6f86c57 100644 --- a/rsocket-core/src/test/java/io/rsocket/RSocketRequesterTerminationTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTerminationTest.java @@ -1,6 +1,8 @@ -package io.rsocket; +package io.rsocket.core; -import io.rsocket.RSocketRequesterTest.ClientSocketRule; +import io.rsocket.Payload; +import io.rsocket.RSocket; +import io.rsocket.core.RSocketRequesterTest.ClientSocketRule; import io.rsocket.util.EmptyPayload; import java.nio.channels.ClosedChannelException; import java.time.Duration; diff --git a/rsocket-core/src/test/java/io/rsocket/RSocketRequesterTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java similarity index 99% rename from rsocket-core/src/test/java/io/rsocket/RSocketRequesterTest.java rename to rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java index a739f2e67..68d2f881b 100644 --- a/rsocket-core/src/test/java/io/rsocket/RSocketRequesterTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.rsocket; +package io.rsocket.core; import static io.rsocket.frame.FrameHeaderFlyweight.frameType; import static io.rsocket.frame.FrameType.*; @@ -25,6 +25,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.rsocket.Payload; import io.rsocket.exceptions.ApplicationErrorException; import io.rsocket.exceptions.RejectedSetupException; import io.rsocket.frame.*; diff --git a/rsocket-core/src/test/java/io/rsocket/RSocketResponderTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java similarity index 98% rename from rsocket-core/src/test/java/io/rsocket/RSocketResponderTest.java rename to rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java index b6281414d..10157532a 100644 --- a/rsocket-core/src/test/java/io/rsocket/RSocketResponderTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.rsocket; +package io.rsocket.core; import static io.rsocket.frame.FrameHeaderFlyweight.frameType; import static org.hamcrest.MatcherAssert.assertThat; @@ -23,6 +23,9 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; +import io.rsocket.AbstractRSocket; +import io.rsocket.Payload; +import io.rsocket.RSocket; import io.rsocket.frame.*; import io.rsocket.lease.ResponderLeaseHandler; import io.rsocket.test.util.TestDuplexConnection; diff --git a/rsocket-core/src/test/java/io/rsocket/RSocketTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java similarity index 98% rename from rsocket-core/src/test/java/io/rsocket/RSocketTest.java rename to rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java index 80865ec47..d5dcfa2f6 100644 --- a/rsocket-core/src/test/java/io/rsocket/RSocketTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.rsocket; +package io.rsocket.core; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; @@ -23,6 +23,9 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.rsocket.AbstractRSocket; +import io.rsocket.Payload; +import io.rsocket.RSocket; import io.rsocket.exceptions.ApplicationErrorException; import io.rsocket.exceptions.CustomRSocketException; import io.rsocket.lease.RequesterLeaseHandler; diff --git a/rsocket-core/src/test/java/io/rsocket/ReconnectMonoTests.java b/rsocket-core/src/test/java/io/rsocket/core/ReconnectMonoTests.java similarity index 99% rename from rsocket-core/src/test/java/io/rsocket/ReconnectMonoTests.java rename to rsocket-core/src/test/java/io/rsocket/core/ReconnectMonoTests.java index fb7707509..968a1a793 100644 --- a/rsocket-core/src/test/java/io/rsocket/ReconnectMonoTests.java +++ b/rsocket-core/src/test/java/io/rsocket/core/ReconnectMonoTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.rsocket; +package io.rsocket.core; import static org.junit.Assert.assertEquals; diff --git a/rsocket-core/src/test/java/io/rsocket/SetupRejectionTest.java b/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java similarity index 99% rename from rsocket-core/src/test/java/io/rsocket/SetupRejectionTest.java rename to rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java index e6972eec0..daab5d246 100644 --- a/rsocket-core/src/test/java/io/rsocket/SetupRejectionTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java @@ -1,10 +1,11 @@ -package io.rsocket; +package io.rsocket.core; import static io.rsocket.transport.ServerTransport.ConnectionAcceptor; import static org.assertj.core.api.Assertions.assertThat; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.rsocket.*; import io.rsocket.exceptions.Exceptions; import io.rsocket.exceptions.RejectedSetupException; import io.rsocket.frame.ErrorFrameFlyweight; diff --git a/rsocket-core/src/test/java/io/rsocket/StreamIdSupplierTest.java b/rsocket-core/src/test/java/io/rsocket/core/StreamIdSupplierTest.java similarity index 99% rename from rsocket-core/src/test/java/io/rsocket/StreamIdSupplierTest.java rename to rsocket-core/src/test/java/io/rsocket/core/StreamIdSupplierTest.java index 766a6aaf7..00248b6d8 100644 --- a/rsocket-core/src/test/java/io/rsocket/StreamIdSupplierTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/StreamIdSupplierTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.rsocket; +package io.rsocket.core; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/rsocket-core/src/test/java/io/rsocket/TestingStuff.java b/rsocket-core/src/test/java/io/rsocket/core/TestingStuff.java similarity index 97% rename from rsocket-core/src/test/java/io/rsocket/TestingStuff.java rename to rsocket-core/src/test/java/io/rsocket/core/TestingStuff.java index 64c790053..e0ebf5064 100644 --- a/rsocket-core/src/test/java/io/rsocket/TestingStuff.java +++ b/rsocket-core/src/test/java/io/rsocket/core/TestingStuff.java @@ -1,4 +1,4 @@ -package io.rsocket; +package io.rsocket.core; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; @@ -16,6 +16,6 @@ public void testStuff() { ByteBuf byteBuf = Unpooled.wrappedBuffer(ByteBufUtil.decodeHexDump(f1)); System.out.println(ByteBufUtil.prettyHexDump(byteBuf)); - ConnectionSetupPayload.create(byteBuf); + new DefaultConnectionSetupPayload(byteBuf); } } From 8bdaee2846f0e2109e7e237287045fbff769c5cf Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Thu, 9 Apr 2020 20:04:14 +0300 Subject: [PATCH 12/62] [Bugfix] Incorrect Behaviour of RequestChannel (#736) * provides failing test Signed-off-by: Oleh Dokuka * provides requestChannel bugfix Signed-off-by: Oleh Dokuka * more fixes Signed-off-by: Oleh Dokuka * rollbacks unwanted changes Signed-off-by: Oleh Dokuka * fixes incorrect requestN behaviour on the requester side Signed-off-by: Oleh Dokuka --- .../io/rsocket/core/RSocketRequester.java | 184 +++++++------ .../io/rsocket/core/RSocketResponder.java | 146 +++++------ .../main/java/io/rsocket/frame/FrameUtil.java | 10 + .../RateLimitableRequestPublisher.java | 242 ------------------ .../core/RSocketRequesterSubscribersTest.java | 25 +- .../io/rsocket/core/RSocketRequesterTest.java | 47 ++++ .../java/io/rsocket/core/RSocketTest.java | 18 ++ .../RateLimitableRequestPublisherTest.java | 140 ---------- .../java/io/rsocket/test/TestRSocket.java | 2 +- .../java/io/rsocket/test/TransportTest.java | 11 +- 10 files changed, 261 insertions(+), 564 deletions(-) delete mode 100755 rsocket-core/src/main/java/io/rsocket/internal/RateLimitableRequestPublisher.java delete mode 100644 rsocket-core/src/test/java/io/rsocket/internal/RateLimitableRequestPublisherTest.java diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index e39fa7a29..6c26361a2 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -40,7 +40,6 @@ import io.rsocket.frame.RequestResponseFrameFlyweight; import io.rsocket.frame.RequestStreamFrameFlyweight; import io.rsocket.frame.decoder.PayloadDecoder; -import io.rsocket.internal.RateLimitableRequestPublisher; import io.rsocket.internal.SynchronizedIntObjectHashMap; import io.rsocket.internal.UnboundedProcessor; import io.rsocket.internal.UnicastMonoEmpty; @@ -51,6 +50,7 @@ import io.rsocket.lease.RequesterLeaseHandler; import io.rsocket.util.MonoLifecycleHandler; import java.nio.channels.ClosedChannelException; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.Consumer; import java.util.function.LongConsumer; @@ -60,6 +60,7 @@ import org.reactivestreams.Processor; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; import reactor.core.publisher.BaseSubscriber; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -84,7 +85,7 @@ class RSocketRequester implements RSocket { private final PayloadDecoder payloadDecoder; private final Consumer errorConsumer; private final StreamIdSupplier streamIdSupplier; - private final IntObjectMap senders; + private final IntObjectMap senders; private final IntObjectMap> receivers; private final UnboundedProcessor sendProcessor; private final RequesterLeaseHandler leaseHandler; @@ -258,6 +259,7 @@ private Flux handleRequestStream(final Payload payload) { final UnboundedProcessor sendProcessor = this.sendProcessor; final UnicastProcessor receiver = UnicastProcessor.create(); + final AtomicBoolean payloadReleasedFlag = new AtomicBoolean(false); receivers.put(streamId, receiver); @@ -279,7 +281,9 @@ public void accept(long n) { n, payload.sliceMetadata().retain(), payload.sliceData().retain())); - payload.release(); + if (!payloadReleasedFlag.getAndSet(true)) { + payload.release(); + } } else if (contains(streamId) && !receiver.isDisposed()) { sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); } @@ -293,6 +297,9 @@ public void accept(long n) { }) .doOnCancel( () -> { + if (!payloadReleasedFlag.getAndSet(true)) { + payload.release(); + } if (contains(streamId) && !receiver.isDisposed()) { sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); } @@ -306,10 +313,67 @@ private Flux handleChannel(Flux request) { return Flux.error(err); } + return request.switchOnFirst( + (s, flux) -> { + Payload payload = s.get(); + if (payload != null) { + return handleChannel(payload, flux); + } else { + return flux; + } + }, + false); + } + + private Flux handleChannel(Payload initialPayload, Flux inboundFlux) { final UnboundedProcessor sendProcessor = this.sendProcessor; - final UnicastProcessor receiver = UnicastProcessor.create(); + final AtomicBoolean payloadReleasedFlag = new AtomicBoolean(false); final int streamId = streamIdSupplier.nextStreamId(receivers); + final UnicastProcessor receiver = UnicastProcessor.create(); + final BaseSubscriber upstreamSubscriber = + new BaseSubscriber() { + + boolean first = true; + + @Override + protected void hookOnSubscribe(Subscription subscription) { + // noops + } + + @Override + protected void hookOnNext(Payload payload) { + if (first) { + // need to skip first since we have already sent it + first = false; + return; + } + final ByteBuf frame = + PayloadFrameFlyweight.encode(allocator, streamId, false, false, true, payload); + + sendProcessor.onNext(frame); + payload.release(); + } + + @Override + protected void hookOnComplete() { + ByteBuf frame = PayloadFrameFlyweight.encodeComplete(allocator, streamId); + sendProcessor.onNext(frame); + } + + @Override + protected void hookOnError(Throwable t) { + ByteBuf frame = ErrorFrameFlyweight.encode(allocator, streamId, t); + sendProcessor.onNext(frame); + receiver.onError(t); + } + + @Override + protected void hookFinally(SignalType type) { + senders.remove(streamId, this); + } + }; + return receiver .doOnRequest( new LongConsumer() { @@ -320,85 +384,47 @@ private Flux handleChannel(Flux request) { public void accept(long n) { if (firstRequest) { firstRequest = false; - request - .transform( - f -> { - RateLimitableRequestPublisher wrapped = - RateLimitableRequestPublisher.wrap(f, Queues.SMALL_BUFFER_SIZE); - // Need to set this to one for first the frame - wrapped.request(1); - senders.put(streamId, wrapped); - receivers.put(streamId, receiver); - - return wrapped; - }) - .subscribe( - new BaseSubscriber() { - - boolean firstPayload = true; - - @Override - protected void hookOnNext(Payload payload) { - final ByteBuf frame; - - if (firstPayload) { - firstPayload = false; - frame = - RequestChannelFrameFlyweight.encode( - allocator, - streamId, - false, - false, - n, - payload.sliceMetadata().retain(), - payload.sliceData().retain()); - } else { - frame = - PayloadFrameFlyweight.encode( - allocator, streamId, false, false, true, payload); - } - - sendProcessor.onNext(frame); - payload.release(); - } - - @Override - protected void hookOnComplete() { - if (contains(streamId) && !receiver.isDisposed()) { - sendProcessor.onNext( - PayloadFrameFlyweight.encodeComplete(allocator, streamId)); - } - if (firstPayload) { - receiver.onComplete(); - } - } - - @Override - protected void hookOnError(Throwable t) { - errorConsumer.accept(t); - receiver.dispose(); - } - }); - } else { - if (contains(streamId) && !receiver.isDisposed()) { - sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); + senders.put(streamId, upstreamSubscriber); + receivers.put(streamId, receiver); + + inboundFlux.limitRate(Queues.SMALL_BUFFER_SIZE).subscribe(upstreamSubscriber); + if (!payloadReleasedFlag.getAndSet(true)) { + ByteBuf frame = + RequestChannelFrameFlyweight.encode( + allocator, + streamId, + false, + false, + n, + initialPayload.sliceMetadata().retain(), + initialPayload.sliceData().retain()); + + sendProcessor.onNext(frame); + + initialPayload.release(); } + } else { + sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); } } }) .doOnError( t -> { - if (contains(streamId) && !receiver.isDisposed()) { - sendProcessor.onNext(ErrorFrameFlyweight.encode(allocator, streamId, t)); + if (receivers.remove(streamId, receiver)) { + upstreamSubscriber.cancel(); } }) + .doOnComplete(() -> receivers.remove(streamId, receiver)) .doOnCancel( () -> { - if (contains(streamId) && !receiver.isDisposed()) { + if (!payloadReleasedFlag.getAndSet(true)) { + initialPayload.release(); + } + if (receivers.remove(streamId, receiver)) { sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); + upstreamSubscriber.cancel(); } - }) - .doFinally(s -> removeStreamReceiverAndSender(streamId)); + }); } private Mono handleMetadataPush(Payload payload) { @@ -487,7 +513,7 @@ private void handleFrame(int streamId, FrameType type, ByteBuf frame) { break; case CANCEL: { - RateLimitableRequestPublisher sender = senders.remove(streamId); + Subscription sender = senders.remove(streamId); if (sender != null) { sender.cancel(); } @@ -498,7 +524,7 @@ private void handleFrame(int streamId, FrameType type, ByteBuf frame) { break; case REQUEST_N: { - RateLimitableRequestPublisher sender = senders.get(streamId); + Subscription sender = senders.get(streamId); if (sender != null) { int n = RequestNFrameFlyweight.requestN(frame); sender.request(n >= Integer.MAX_VALUE ? Long.MAX_VALUE : n); @@ -606,18 +632,6 @@ private void removeStreamReceiver(int streamId) { } } - private void removeStreamReceiverAndSender(int streamId) { - /*on termination senders & receivers are explicitly cleared to avoid removing from map while iterating over one - of its views*/ - if (terminationError == null) { - receivers.remove(streamId); - RateLimitableRequestPublisher sender = senders.remove(streamId); - if (sender != null) { - sender.cancel(); - } - } - } - private void handleSendProcessorError(Throwable t) { connection.dispose(); } diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java index d742300d1..de6e8ad23 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java @@ -27,11 +27,11 @@ import io.rsocket.exceptions.ApplicationErrorException; import io.rsocket.frame.*; import io.rsocket.frame.decoder.PayloadDecoder; -import io.rsocket.internal.RateLimitableRequestPublisher; import io.rsocket.internal.SynchronizedIntObjectHashMap; import io.rsocket.internal.UnboundedProcessor; import io.rsocket.lease.ResponderLeaseHandler; import java.util.function.Consumer; +import java.util.function.LongConsumer; import org.reactivestreams.Processor; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; @@ -51,7 +51,6 @@ class RSocketResponder implements ResponderRSocket { private final Consumer errorConsumer; private final ResponderLeaseHandler leaseHandler; - private final IntObjectMap sendingLimitableSubscriptions; private final IntObjectMap sendingSubscriptions; private final IntObjectMap> channelProcessors; @@ -75,7 +74,6 @@ class RSocketResponder implements ResponderRSocket { this.payloadDecoder = payloadDecoder; this.errorConsumer = errorConsumer; this.leaseHandler = leaseHandler; - this.sendingLimitableSubscriptions = new SynchronizedIntObjectHashMap<>(); this.sendingSubscriptions = new SynchronizedIntObjectHashMap<>(); this.channelProcessors = new SynchronizedIntObjectHashMap<>(); @@ -114,17 +112,6 @@ private void handleSendProcessorError(Throwable t) { } }); - sendingLimitableSubscriptions - .values() - .forEach( - subscription -> { - try { - subscription.cancel(); - } catch (Throwable e) { - errorConsumer.accept(e); - } - }); - channelProcessors .values() .forEach( @@ -153,17 +140,6 @@ private void handleSendProcessorCancel(SignalType t) { } }); - sendingLimitableSubscriptions - .values() - .forEach( - subscription -> { - try { - subscription.cancel(); - } catch (Throwable e) { - errorConsumer.accept(e); - } - }); - channelProcessors .values() .forEach( @@ -280,9 +256,6 @@ private void cleanup() { private synchronized void cleanUpSendingSubscriptions() { sendingSubscriptions.values().forEach(Subscription::cancel); sendingSubscriptions.clear(); - - sendingLimitableSubscriptions.values().forEach(Subscription::cancel); - sendingLimitableSubscriptions.clear(); } private synchronized void cleanUpChannelProcessors() { @@ -388,16 +361,10 @@ protected void hookFinally(SignalType type) { } private void handleRequestResponse(int streamId, Mono response) { - response.subscribe( + final BaseSubscriber subscriber = new BaseSubscriber() { private boolean isEmpty = true; - @Override - protected void hookOnSubscribe(Subscription subscription) { - sendingSubscriptions.put(streamId, subscription); - subscription.request(Long.MAX_VALUE); - } - @Override protected void hookOnNext(Payload payload) { if (isEmpty) { @@ -431,55 +398,56 @@ protected void hookOnComplete() { @Override protected void hookFinally(SignalType type) { - sendingSubscriptions.remove(streamId); + sendingSubscriptions.remove(streamId, this); } - }); + }; + + sendingSubscriptions.put(streamId, subscriber); + response.subscribe(subscriber); } private void handleStream(int streamId, Flux response, int initialRequestN) { - response - .transform( - frameFlux -> { - RateLimitableRequestPublisher payloads = - RateLimitableRequestPublisher.wrap(frameFlux, Queues.SMALL_BUFFER_SIZE); - sendingLimitableSubscriptions.put(streamId, payloads); - payloads.request( - initialRequestN >= Integer.MAX_VALUE ? Long.MAX_VALUE : initialRequestN); - return payloads; - }) - .subscribe( - new BaseSubscriber() { - - @Override - protected void hookOnNext(Payload payload) { - ByteBuf byteBuf; - try { - byteBuf = PayloadFrameFlyweight.encodeNext(allocator, streamId, payload); - } catch (Throwable t) { - payload.release(); - throw Exceptions.propagate(t); - } - - payload.release(); - - sendProcessor.onNext(byteBuf); - } + final BaseSubscriber subscriber = + new BaseSubscriber() { - @Override - protected void hookOnComplete() { - sendProcessor.onNext(PayloadFrameFlyweight.encodeComplete(allocator, streamId)); - } + @Override + protected void hookOnSubscribe(Subscription s) { + s.request(initialRequestN >= Integer.MAX_VALUE ? Long.MAX_VALUE : initialRequestN); + } - @Override - protected void hookOnError(Throwable throwable) { - handleError(streamId, throwable); - } + @Override + protected void hookOnNext(Payload payload) { + ByteBuf byteBuf; + try { + byteBuf = PayloadFrameFlyweight.encodeNext(allocator, streamId, payload); + } catch (Throwable t) { + payload.release(); + throw Exceptions.propagate(t); + } - @Override - protected void hookFinally(SignalType type) { - sendingLimitableSubscriptions.remove(streamId); - } - }); + payload.release(); + + sendProcessor.onNext(byteBuf); + } + + @Override + protected void hookOnComplete() { + sendProcessor.onNext(PayloadFrameFlyweight.encodeComplete(allocator, streamId)); + } + + @Override + protected void hookOnError(Throwable throwable) { + handleError(streamId, throwable); + } + + @Override + protected void hookFinally(SignalType type) { + sendingSubscriptions.remove(streamId); + } + }; + + sendingSubscriptions.put(streamId, subscriber); + response.limitRate(Queues.SMALL_BUFFER_SIZE).subscribe(subscriber); } private void handleChannel(int streamId, Payload payload, int initialRequestN) { @@ -492,7 +460,21 @@ private void handleChannel(int streamId, Payload payload, int initialRequestN) { () -> sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId))) .doOnError(t -> handleError(streamId, t)) .doOnRequest( - l -> sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, l))) + new LongConsumer() { + boolean first = true; + + @Override + public void accept(long l) { + long n; + if (first) { + first = false; + n = l - 1L; + } else { + n = l; + } + sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); + } + }) .doFinally(signalType -> channelProcessors.remove(streamId)); // not chained, as the payload should be enqueued in the Unicast processor before this method @@ -525,10 +507,6 @@ protected void hookOnError(Throwable throwable) { private void handleCancelFrame(int streamId) { Subscription subscription = sendingSubscriptions.remove(streamId); - if (subscription == null) { - subscription = sendingLimitableSubscriptions.remove(streamId); - } - if (subscription != null) { subscription.cancel(); } @@ -542,10 +520,6 @@ private void handleError(int streamId, Throwable t) { private void handleRequestN(int streamId, ByteBuf frame) { Subscription subscription = sendingSubscriptions.get(streamId); - if (subscription == null) { - subscription = sendingLimitableSubscriptions.get(streamId); - } - if (subscription != null) { int n = RequestNFrameFlyweight.requestN(frame); subscription.request(n >= Integer.MAX_VALUE ? Long.MAX_VALUE : n); diff --git a/rsocket-core/src/main/java/io/rsocket/frame/FrameUtil.java b/rsocket-core/src/main/java/io/rsocket/frame/FrameUtil.java index 0d2175fb6..6662d34af 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/FrameUtil.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/FrameUtil.java @@ -22,6 +22,16 @@ public static String toString(ByteBuf frame) { .append(Integer.toBinaryString(FrameHeaderFlyweight.flags(frame))) .append(" Length: " + frame.readableBytes()); + if (frameType.hasInitialRequestN()) { + payload + .append(" InitialRequestN: ") + .append(RequestStreamFrameFlyweight.initialRequestN(frame)); + } + + if (frameType == FrameType.REQUEST_N) { + payload.append(" RequestN: ").append(RequestNFrameFlyweight.requestN(frame)); + } + if (FrameHeaderFlyweight.hasMetadata(frame)) { payload.append("\nMetadata:\n"); diff --git a/rsocket-core/src/main/java/io/rsocket/internal/RateLimitableRequestPublisher.java b/rsocket-core/src/main/java/io/rsocket/internal/RateLimitableRequestPublisher.java deleted file mode 100755 index cdb0d0c0c..000000000 --- a/rsocket-core/src/main/java/io/rsocket/internal/RateLimitableRequestPublisher.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.internal; - -import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; -import javax.annotation.Nullable; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; -import reactor.core.CoreSubscriber; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Operators; - -/** */ -public class RateLimitableRequestPublisher extends Flux implements Subscription { - - private static final int NOT_CANCELED_STATE = 0; - private static final int CANCELED_STATE = 1; - - private final Publisher source; - - private volatile int canceled; - private static final AtomicIntegerFieldUpdater CANCELED = - AtomicIntegerFieldUpdater.newUpdater(RateLimitableRequestPublisher.class, "canceled"); - - private final long prefetch; - private final long limit; - - private long externalRequested; // need sync - private int pendingToFulfil; // need sync since should be checked/zerroed in onNext - // and increased in request - private int deliveredElements; // no need to sync since increased zerroed only in - // the request method - - private boolean subscribed; - - private @Nullable Subscription internalSubscription; - - private RateLimitableRequestPublisher(Publisher source, long prefetch) { - this.source = source; - this.prefetch = prefetch; - this.limit = prefetch == Integer.MAX_VALUE ? Integer.MAX_VALUE : (prefetch - (prefetch >> 2)); - } - - public static RateLimitableRequestPublisher wrap(Publisher source, long prefetch) { - return new RateLimitableRequestPublisher<>(source, prefetch); - } - - @Override - public void subscribe(CoreSubscriber destination) { - synchronized (this) { - if (subscribed) { - throw new IllegalStateException("only one subscriber at a time"); - } - - subscribed = true; - } - final InnerOperator s = new InnerOperator(destination); - - source.subscribe(s); - destination.onSubscribe(s); - } - - @Override - public void request(long n) { - synchronized (this) { - long requested = externalRequested; - if (requested == Long.MAX_VALUE) { - return; - } - externalRequested = Operators.addCap(n, requested); - } - - requestN(); - } - - private void requestN() { - final long r; - final Subscription s; - - synchronized (this) { - s = internalSubscription; - if (s == null) { - return; - } - - final long er = externalRequested; - final long p = prefetch; - final int pendingFulfil = pendingToFulfil; - - if (er != Long.MAX_VALUE || p != Integer.MAX_VALUE) { - // shortcut - if (pendingFulfil == p) { - return; - } - - r = Math.min(p - pendingFulfil, er); - if (er != Long.MAX_VALUE) { - externalRequested -= r; - } - if (p != Integer.MAX_VALUE) { - pendingToFulfil += r; - } - } else { - r = Long.MAX_VALUE; - } - } - - if (r > 0) { - s.request(r); - } - } - - public void cancel() { - if (!isCanceled() && CANCELED.compareAndSet(this, NOT_CANCELED_STATE, CANCELED_STATE)) { - Subscription s; - - synchronized (this) { - s = internalSubscription; - internalSubscription = null; - subscribed = false; - } - - if (s != null) { - s.cancel(); - } - } - } - - private boolean isCanceled() { - return canceled == CANCELED_STATE; - } - - private class InnerOperator implements CoreSubscriber, Subscription { - final Subscriber destination; - - private InnerOperator(Subscriber destination) { - this.destination = destination; - } - - @Override - public void onSubscribe(Subscription s) { - synchronized (RateLimitableRequestPublisher.this) { - RateLimitableRequestPublisher.this.internalSubscription = s; - - if (isCanceled()) { - s.cancel(); - subscribed = false; - RateLimitableRequestPublisher.this.internalSubscription = null; - } - } - - requestN(); - } - - @Override - public void onNext(T t) { - try { - destination.onNext(t); - - if (prefetch == Integer.MAX_VALUE) { - return; - } - - final long l = limit; - int d = deliveredElements + 1; - - if (d == l) { - d = 0; - final long r; - final Subscription s; - - synchronized (RateLimitableRequestPublisher.this) { - long er = externalRequested; - s = internalSubscription; - - if (s == null) { - return; - } - - if (er >= l) { - er -= l; - // keep pendingToFulfil as is since it is eq to prefetch - r = l; - } else { - pendingToFulfil -= l; - if (er > 0) { - r = er; - er = 0; - pendingToFulfil += r; - } else { - r = 0; - } - } - - externalRequested = er; - } - - if (r > 0) { - s.request(r); - } - } - - deliveredElements = d; - } catch (Throwable e) { - onError(e); - } - } - - @Override - public void onError(Throwable t) { - destination.onError(t); - } - - @Override - public void onComplete() { - destination.onComplete(); - } - - @Override - public void request(long n) {} - - @Override - public void cancel() { - RateLimitableRequestPublisher.this.cancel(); - } - } -} diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java index 6b903292c..8a2e114cc 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java @@ -39,7 +39,6 @@ import org.junit.jupiter.params.provider.MethodSource; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; import reactor.test.StepVerifier; class RSocketRequesterSubscribersTest { @@ -76,9 +75,15 @@ void setUp() { @MethodSource("allInteractions") void multiSubscriber(Function> interaction) { RSocket multiSubsRSocket = new MultiSubscriberRSocket(rSocketRequester); - Flux response = Flux.from(interaction.apply(multiSubsRSocket)).take(Duration.ofMillis(10)); - StepVerifier.create(response).expectComplete().verify(Duration.ofSeconds(5)); - StepVerifier.create(response).expectComplete().verify(Duration.ofSeconds(5)); + Flux response = Flux.from(interaction.apply(multiSubsRSocket)); + StepVerifier.withVirtualTime(() -> response.take(Duration.ofMillis(10))) + .thenAwait(Duration.ofMillis(10)) + .expectComplete() + .verify(Duration.ofSeconds(5)); + StepVerifier.withVirtualTime(() -> response.take(Duration.ofMillis(10))) + .thenAwait(Duration.ofMillis(10)) + .expectComplete() + .verify(Duration.ofSeconds(5)); Assertions.assertThat(requestFramesCount(connection.getSent())).isEqualTo(2); } @@ -86,9 +91,13 @@ void multiSubscriber(Function> interaction) { @ParameterizedTest @MethodSource("allInteractions") void singleSubscriber(Function> interaction) { - Flux response = Flux.from(interaction.apply(rSocketRequester)).take(Duration.ofMillis(10)); - StepVerifier.create(response).expectComplete().verify(Duration.ofSeconds(5)); - StepVerifier.create(response) + Flux response = Flux.from(interaction.apply(rSocketRequester)); + StepVerifier.withVirtualTime(() -> response.take(Duration.ofMillis(10))) + .thenAwait(Duration.ofMillis(10)) + .expectComplete() + .verify(Duration.ofSeconds(5)); + StepVerifier.withVirtualTime(() -> response.take(Duration.ofMillis(10))) + .thenAwait(Duration.ofMillis(10)) .expectError(IllegalStateException.class) .verify(Duration.ofSeconds(5)); @@ -115,7 +124,7 @@ static Stream>> allInteractions() { rSocket -> rSocket.fireAndForget(DefaultPayload.create("test")), rSocket -> rSocket.requestResponse(DefaultPayload.create("test")), rSocket -> rSocket.requestStream(DefaultPayload.create("test")), - rSocket -> rSocket.requestChannel(Mono.just(DefaultPayload.create("test"))), + // rSocket -> rSocket.requestChannel(Mono.just(DefaultPayload.create("test"))), rSocket -> rSocket.metadataPush(DefaultPayload.create("test"))); } } diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java index 68d2f881b..20b1825fa 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java @@ -25,6 +25,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.util.CharsetUtil; import io.rsocket.Payload; import io.rsocket.exceptions.ApplicationErrorException; import io.rsocket.exceptions.RejectedSetupException; @@ -36,6 +37,7 @@ import io.rsocket.util.MultiSubscriberRSocket; import java.time.Duration; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; import org.assertj.core.api.Assertions; @@ -195,6 +197,19 @@ public void testChannelRequestCancellation() { .blockFirst(); } + @Test + public void testChannelRequestCancellation2() { + MonoProcessor cancelled = MonoProcessor.create(); + Flux request = + Flux.just(EmptyPayload.INSTANCE).repeat(259).doOnCancel(cancelled::onComplete); + rule.socket.requestChannel(request).subscribe().dispose(); + Flux.first( + cancelled, + Flux.error(new IllegalStateException("Channel request not cancelled")) + .delaySubscription(Duration.ofSeconds(1))) + .blockFirst(); + } + @Test public void testChannelRequestServerSideCancellation() { MonoProcessor cancelled = MonoProcessor.create(); @@ -215,6 +230,38 @@ public void testChannelRequestServerSideCancellation() { Assertions.assertThat(request.isDisposed()).isTrue(); } + @Test + public void testCorrectFrameOrder() { + MonoProcessor delayer = MonoProcessor.create(); + BaseSubscriber subscriber = + new BaseSubscriber() { + @Override + protected void hookOnSubscribe(Subscription subscription) {} + }; + rule.socket + .requestChannel( + Flux.concat(Flux.just(0).delayUntil(i -> delayer), Flux.range(1, 999)) + .map(i -> DefaultPayload.create(i + ""))) + .subscribe(subscriber); + + subscriber.request(1); + subscriber.request(Long.MAX_VALUE); + delayer.onComplete(); + + Iterator iterator = rule.connection.getSent().iterator(); + + ByteBuf initialFrame = iterator.next(); + + Assertions.assertThat(FrameHeaderFlyweight.frameType(initialFrame)).isEqualTo(REQUEST_CHANNEL); + Assertions.assertThat(RequestChannelFrameFlyweight.initialRequestN(initialFrame)) + .isEqualTo(Integer.MAX_VALUE); + Assertions.assertThat( + RequestChannelFrameFlyweight.data(initialFrame).toString(CharsetUtil.UTF_8)) + .isEqualTo("0"); + + Assertions.assertThat(iterator.hasNext()).isFalse(); + } + public int sendRequestResponse(Publisher response) { Subscriber sub = TestSubscriber.create(); response.subscribe(sub); diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java index d5dcfa2f6..b18fad890 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java @@ -35,6 +35,8 @@ import io.rsocket.util.DefaultPayload; import io.rsocket.util.EmptyPayload; import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicReference; +import org.assertj.core.api.Assertions; import org.hamcrest.MatcherAssert; import org.junit.Assert; import org.junit.Rule; @@ -133,6 +135,22 @@ public void testChannel() throws Exception { StepVerifier.create(responses).expectNextCount(10).expectComplete().verify(); } + @Test(timeout = 2000) + public void testErrorPropagatesCorrectly() { + AtomicReference error = new AtomicReference<>(); + rule.setRequestAcceptor( + new AbstractRSocket() { + @Override + public Flux requestChannel(Publisher payloads) { + return Flux.from(payloads).doOnError(error::set); + } + }); + Flux requests = Flux.error(new RuntimeException("test")); + Flux responses = rule.crs.requestChannel(requests); + StepVerifier.create(responses).expectErrorMessage("test").verify(); + Assertions.assertThat(error.get()).isNull(); + } + public static class SocketRule extends ExternalResource { DirectProcessor serverProcessor; diff --git a/rsocket-core/src/test/java/io/rsocket/internal/RateLimitableRequestPublisherTest.java b/rsocket-core/src/test/java/io/rsocket/internal/RateLimitableRequestPublisherTest.java deleted file mode 100644 index af4c528e9..000000000 --- a/rsocket-core/src/test/java/io/rsocket/internal/RateLimitableRequestPublisherTest.java +++ /dev/null @@ -1,140 +0,0 @@ -package io.rsocket.internal; - -import static org.junit.jupiter.api.Assertions.*; - -import java.time.Duration; -import java.util.concurrent.ThreadLocalRandom; -import java.util.function.Consumer; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; -import reactor.core.scheduler.Schedulers; -import reactor.test.StepVerifier; - -class RateLimitableRequestPublisherTest { - - @Test - public void testThatRequest1WillBePropagatedUpstream() { - Flux source = - Flux.just(1) - .subscribeOn(Schedulers.parallel()) - .doOnRequest(r -> Assertions.assertThat(r).isLessThanOrEqualTo(128)); - - RateLimitableRequestPublisher rateLimitableRequestPublisher = - RateLimitableRequestPublisher.wrap(source, 128); - - StepVerifier.create(rateLimitableRequestPublisher) - .then(() -> rateLimitableRequestPublisher.request(1)) - .expectNext(1) - .expectComplete() - .verify(Duration.ofMillis(1000)); - } - - @Test - public void testThatRequest256WillBePropagatedToUpstreamWithLimitedRate() { - Flux source = - Flux.range(0, 256) - .subscribeOn(Schedulers.parallel()) - .doOnRequest(r -> Assertions.assertThat(r).isLessThanOrEqualTo(128)); - - RateLimitableRequestPublisher rateLimitableRequestPublisher = - RateLimitableRequestPublisher.wrap(source, 128); - - StepVerifier.create(rateLimitableRequestPublisher) - .then(() -> rateLimitableRequestPublisher.request(256)) - .expectNextCount(256) - .expectComplete() - .verify(Duration.ofMillis(1000)); - } - - @Test - public void testThatRequest256WillBePropagatedToUpstreamWithLimitedRateInFewSteps() { - Flux source = - Flux.range(0, 256) - .subscribeOn(Schedulers.parallel()) - .doOnRequest(r -> Assertions.assertThat(r).isLessThanOrEqualTo(128)); - - RateLimitableRequestPublisher rateLimitableRequestPublisher = - RateLimitableRequestPublisher.wrap(source, 128); - - StepVerifier.create(rateLimitableRequestPublisher) - .then(() -> rateLimitableRequestPublisher.request(10)) - .expectNextCount(5) - .then(() -> rateLimitableRequestPublisher.request(128)) - .expectNextCount(133) - .expectNoEvent(Duration.ofMillis(10)) - .then(() -> rateLimitableRequestPublisher.request(Long.MAX_VALUE)) - .expectNextCount(118) - .expectComplete() - .verify(Duration.ofMillis(1000)); - } - - @Test - public void testThatRequestInRandomFashionWillBePropagatedToUpstreamWithLimitedRateInFewSteps() { - Flux source = - Flux.range(0, 10000000) - .subscribeOn(Schedulers.parallel()) - .doOnRequest(r -> Assertions.assertThat(r).isLessThanOrEqualTo(128)); - - RateLimitableRequestPublisher rateLimitableRequestPublisher = - RateLimitableRequestPublisher.wrap(source, 128); - - StepVerifier.create(rateLimitableRequestPublisher) - .then( - () -> - Flux.interval(Duration.ofMillis(1000)) - .onBackpressureDrop() - .subscribe( - new Consumer() { - int count = 10000000; - - @Override - public void accept(Long __) { - int random = ThreadLocalRandom.current().nextInt(1, 512); - - long request = Math.min(random, count); - - count -= request; - - rateLimitableRequestPublisher.request(count); - } - })) - .expectNextCount(10000000) - .expectComplete() - .verify(Duration.ofMillis(30000)); - } - - @Test - public void testThatRequestLongMaxValueWillBeDeliveredInSeparateChunks() { - Flux source = - Flux.range(0, 10000000) - .subscribeOn(Schedulers.parallel()) - .doOnRequest(r -> Assertions.assertThat(r).isLessThanOrEqualTo(128)); - - RateLimitableRequestPublisher rateLimitableRequestPublisher = - RateLimitableRequestPublisher.wrap(source, 128); - - StepVerifier.create(rateLimitableRequestPublisher) - .then(() -> rateLimitableRequestPublisher.request(Long.MAX_VALUE)) - .expectNextCount(10000000) - .expectComplete() - .verify(Duration.ofMillis(30000)); - } - - @Test - public void testThatRequestLongMaxWithIntegerMaxValuePrefetchWillBeDeliveredAsLongMaxValue() { - Flux source = - Flux.range(0, 10000000) - .subscribeOn(Schedulers.parallel()) - .doOnRequest(r -> Assertions.assertThat(r).isEqualTo(Long.MAX_VALUE)); - - RateLimitableRequestPublisher rateLimitableRequestPublisher = - RateLimitableRequestPublisher.wrap(source, Integer.MAX_VALUE); - - StepVerifier.create(rateLimitableRequestPublisher) - .then(() -> rateLimitableRequestPublisher.request(Long.MAX_VALUE)) - .expectNextCount(10000000) - .expectComplete() - .verify(Duration.ofMillis(30000)); - } -} diff --git a/rsocket-test/src/main/java/io/rsocket/test/TestRSocket.java b/rsocket-test/src/main/java/io/rsocket/test/TestRSocket.java index 57a2e5c3c..26163d3a6 100644 --- a/rsocket-test/src/main/java/io/rsocket/test/TestRSocket.java +++ b/rsocket-test/src/main/java/io/rsocket/test/TestRSocket.java @@ -55,6 +55,6 @@ public Mono fireAndForget(Payload payload) { @Override public Flux requestChannel(Publisher payloads) { // TODO is defensive copy neccesary? - return Flux.from(payloads).map(DefaultPayload::create); + return Flux.from(payloads).map(Payload::retain); } } diff --git a/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java b/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java index 6721acf2c..1c00b0502 100644 --- a/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java +++ b/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java @@ -26,11 +26,13 @@ import java.io.BufferedReader; import java.io.InputStreamReader; import java.time.Duration; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.zip.GZIPInputStream; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -215,14 +217,19 @@ default void requestChannel2_000_000() { @DisplayName("makes 1 requestChannel request with 3 payloads") @Test default void requestChannel3() { - Flux payloads = Flux.range(0, 3).map(this::createTestPayload); + AtomicLong requested = new AtomicLong(); + Flux payloads = + Flux.range(0, 3).doOnRequest(requested::addAndGet).map(this::createTestPayload); getClient() .requestChannel(payloads) - .as(StepVerifier::create) + .as(publisher -> StepVerifier.create(publisher, 3)) .expectNextCount(3) .expectComplete() .verify(getTimeout()); + + Assertions.assertThat(requested.get()) + .isEqualTo(256L); // 256 because of eager behavior of limitRate } @DisplayName("makes 1 requestChannel request with 512 payloads") From c2475c2acd0399b2696c917f9bf1abb1974b8988 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Thu, 9 Apr 2020 23:13:49 +0300 Subject: [PATCH 13/62] Payload length validation (#768) * provides failing test Signed-off-by: Oleh Dokuka * provides requestChannel bugfix Signed-off-by: Oleh Dokuka * more fixes Signed-off-by: Oleh Dokuka * initial Signed-off-by: Oleh Dokuka * provides payload length validation in case fragmentation is disabled Signed-off-by: Oleh Dokuka * provides payload validation Signed-off-by: Oleh Dokuka * fixes tests and changes error message Signed-off-by: Oleh Dokuka * rollbacks unwanted changes Signed-off-by: Oleh Dokuka * moves validator to core package and makes it pkg private Signed-off-by: Oleh Dokuka * simplifies validation utils. puts error message to constant field Signed-off-by: Oleh Dokuka * fixes IDE warning Signed-off-by: Oleh Dokuka --- .../core/DefaultClientRSocketFactory.java | 4 +- .../core/DefaultServerRSocketFactory.java | 4 +- .../rsocket/core/PayloadValidationUtils.java | 32 ++++++ .../io/rsocket/core/RSocketRequester.java | 43 ++++++++ .../io/rsocket/core/RSocketResponder.java | 26 ++++- .../fragmentation/FrameFragmenter.java | 9 +- .../java/io/rsocket/core/KeepAliveTest.java | 2 + .../core/PayloadValidationUtilsTest.java | 99 +++++++++++++++++++ .../io/rsocket/core/RSocketLeaseTest.java | 4 +- .../core/RSocketRequesterSubscribersTest.java | 1 + .../io/rsocket/core/RSocketRequesterTest.java | 87 +++++++++++++++- .../io/rsocket/core/RSocketResponderTest.java | 60 ++++++++++- .../java/io/rsocket/core/RSocketTest.java | 4 +- .../io/rsocket/core/SetupRejectionTest.java | 2 + 14 files changed, 367 insertions(+), 10 deletions(-) create mode 100644 rsocket-core/src/main/java/io/rsocket/core/PayloadValidationUtils.java create mode 100644 rsocket-core/src/test/java/io/rsocket/core/PayloadValidationUtilsTest.java diff --git a/rsocket-core/src/main/java/io/rsocket/core/DefaultClientRSocketFactory.java b/rsocket-core/src/main/java/io/rsocket/core/DefaultClientRSocketFactory.java index ce43cd1fd..b7cad7042 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/DefaultClientRSocketFactory.java +++ b/rsocket-core/src/main/java/io/rsocket/core/DefaultClientRSocketFactory.java @@ -331,6 +331,7 @@ public Mono start() { payloadDecoder, errorConsumer, StreamIdSupplier.clientSupplier(), + mtu, keepAliveTickPeriod(), keepAliveTimeout(), keepAliveHandler, @@ -379,7 +380,8 @@ public Mono start() { wrappedRSocketHandler, payloadDecoder, errorConsumer, - responderLeaseHandler); + responderLeaseHandler, + mtu); return wrappedConnection .sendOne(setupFrame) diff --git a/rsocket-core/src/main/java/io/rsocket/core/DefaultServerRSocketFactory.java b/rsocket-core/src/main/java/io/rsocket/core/DefaultServerRSocketFactory.java index f2acb9af0..85543181a 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/DefaultServerRSocketFactory.java +++ b/rsocket-core/src/main/java/io/rsocket/core/DefaultServerRSocketFactory.java @@ -281,6 +281,7 @@ private Mono acceptSetup( payloadDecoder, errorConsumer, StreamIdSupplier.serverSupplier(), + mtu, setupPayload.keepAliveInterval(), setupPayload.keepAliveMaxLifetime(), keepAliveHandler, @@ -317,7 +318,8 @@ private Mono acceptSetup( wrappedRSocketHandler, payloadDecoder, errorConsumer, - responderLeaseHandler); + responderLeaseHandler, + mtu); }) .doFinally(signalType -> setupPayload.release()) .then(); diff --git a/rsocket-core/src/main/java/io/rsocket/core/PayloadValidationUtils.java b/rsocket-core/src/main/java/io/rsocket/core/PayloadValidationUtils.java new file mode 100644 index 000000000..3b6b375d1 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/core/PayloadValidationUtils.java @@ -0,0 +1,32 @@ +package io.rsocket.core; + +import io.rsocket.Payload; +import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.FrameLengthFlyweight; + +final class PayloadValidationUtils { + static final String INVALID_PAYLOAD_ERROR_MESSAGE = + "The payload is too big to send as a single frame with a 24-bit encoded length. Consider enabling fragmentation via RSocketFactory."; + + static boolean isValid(int mtu, Payload payload) { + if (mtu > 0) { + return true; + } + + if (payload.hasMetadata()) { + return (((FrameHeaderFlyweight.size() + + FrameLengthFlyweight.FRAME_LENGTH_SIZE + + FrameHeaderFlyweight.size() + + payload.data().readableBytes() + + payload.metadata().readableBytes()) + & ~FrameLengthFlyweight.FRAME_LENGTH_MASK) + == 0); + } else { + return (((FrameHeaderFlyweight.size() + + payload.data().readableBytes() + + FrameLengthFlyweight.FRAME_LENGTH_SIZE) + & ~FrameLengthFlyweight.FRAME_LENGTH_MASK) + == 0); + } + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index 6c26361a2..fc3175b15 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -16,6 +16,7 @@ package io.rsocket.core; +import static io.rsocket.core.PayloadValidationUtils.INVALID_PAYLOAD_ERROR_MESSAGE; import static io.rsocket.keepalive.KeepAliveSupport.ClientKeepAliveSupport; import static io.rsocket.keepalive.KeepAliveSupport.KeepAlive; @@ -88,6 +89,7 @@ class RSocketRequester implements RSocket { private final IntObjectMap senders; private final IntObjectMap> receivers; private final UnboundedProcessor sendProcessor; + private final int mtu; private final RequesterLeaseHandler leaseHandler; private final ByteBufAllocator allocator; private final KeepAliveFramesAcceptor keepAliveFramesAcceptor; @@ -99,6 +101,7 @@ class RSocketRequester implements RSocket { PayloadDecoder payloadDecoder, Consumer errorConsumer, StreamIdSupplier streamIdSupplier, + int mtu, int keepAliveTickPeriod, int keepAliveAckTimeout, @Nullable KeepAliveHandler keepAliveHandler, @@ -108,6 +111,7 @@ class RSocketRequester implements RSocket { this.payloadDecoder = payloadDecoder; this.errorConsumer = errorConsumer; this.streamIdSupplier = streamIdSupplier; + this.mtu = mtu; this.leaseHandler = leaseHandler; this.senders = new SynchronizedIntObjectHashMap<>(); this.receivers = new SynchronizedIntObjectHashMap<>(); @@ -186,6 +190,11 @@ private Mono handleFireAndForget(Payload payload) { return Mono.error(err); } + if (!PayloadValidationUtils.isValid(this.mtu, payload)) { + payload.release(); + return Mono.error(new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE)); + } + final int streamId = streamIdSupplier.nextStreamId(receivers); return UnicastMonoEmpty.newInstance( @@ -210,6 +219,11 @@ private Mono handleRequestResponse(final Payload payload) { return Mono.error(err); } + if (!PayloadValidationUtils.isValid(this.mtu, payload)) { + payload.release(); + return Mono.error(new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE)); + } + int streamId = streamIdSupplier.nextStreamId(receivers); final UnboundedProcessor sendProcessor = this.sendProcessor; @@ -255,6 +269,11 @@ private Flux handleRequestStream(final Payload payload) { return Flux.error(err); } + if (!PayloadValidationUtils.isValid(this.mtu, payload)) { + payload.release(); + return Flux.error(new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE)); + } + int streamId = streamIdSupplier.nextStreamId(receivers); final UnboundedProcessor sendProcessor = this.sendProcessor; @@ -317,6 +336,13 @@ private Flux handleChannel(Flux request) { (s, flux) -> { Payload payload = s.get(); if (payload != null) { + if (!PayloadValidationUtils.isValid(mtu, payload)) { + payload.release(); + final IllegalArgumentException t = + new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE); + errorConsumer.accept(t); + return Mono.error(t); + } return handleChannel(payload, flux); } else { return flux; @@ -348,6 +374,17 @@ protected void hookOnNext(Payload payload) { first = false; return; } + if (!PayloadValidationUtils.isValid(mtu, payload)) { + payload.release(); + cancel(); + final IllegalArgumentException t = + new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE); + errorConsumer.accept(t); + // no need to send any errors. + sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); + receiver.onError(t); + return; + } final ByteBuf frame = PayloadFrameFlyweight.encode(allocator, streamId, false, false, true, payload); @@ -434,6 +471,11 @@ private Mono handleMetadataPush(Payload payload) { return Mono.error(err); } + if (!PayloadValidationUtils.isValid(this.mtu, payload)) { + payload.release(); + return Mono.error(new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE)); + } + return UnicastMonoEmpty.newInstance( () -> { ByteBuf metadataPushFrame = @@ -444,6 +486,7 @@ private Mono handleMetadataPush(Payload payload) { }); } + @Nullable private Throwable checkAvailable() { Throwable err = this.terminationError; if (err != null) { diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java index de6e8ad23..6f235587a 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java @@ -16,6 +16,8 @@ package io.rsocket.core; +import static io.rsocket.core.PayloadValidationUtils.INVALID_PAYLOAD_ERROR_MESSAGE; + import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.util.ReferenceCountUtil; @@ -51,6 +53,8 @@ class RSocketResponder implements ResponderRSocket { private final Consumer errorConsumer; private final ResponderLeaseHandler leaseHandler; + private final int mtu; + private final IntObjectMap sendingSubscriptions; private final IntObjectMap> channelProcessors; @@ -63,9 +67,11 @@ class RSocketResponder implements ResponderRSocket { RSocket requestHandler, PayloadDecoder payloadDecoder, Consumer errorConsumer, - ResponderLeaseHandler leaseHandler) { + ResponderLeaseHandler leaseHandler, + int mtu) { this.allocator = allocator; this.connection = connection; + this.mtu = mtu; this.requestHandler = requestHandler; this.responderRSocket = @@ -371,6 +377,15 @@ protected void hookOnNext(Payload payload) { isEmpty = false; } + if (!PayloadValidationUtils.isValid(mtu, payload)) { + payload.release(); + cancel(); + final IllegalArgumentException t = + new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE); + handleError(streamId, t); + return; + } + ByteBuf byteBuf; try { byteBuf = PayloadFrameFlyweight.encodeNextComplete(allocator, streamId, payload); @@ -417,6 +432,15 @@ protected void hookOnSubscribe(Subscription s) { @Override protected void hookOnNext(Payload payload) { + if (!PayloadValidationUtils.isValid(mtu, payload)) { + payload.release(); + cancel(); + final IllegalArgumentException t = + new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE); + handleError(streamId, t); + return; + } + ByteBuf byteBuf; try { byteBuf = PayloadFrameFlyweight.encodeNext(allocator, streamId, payload); diff --git a/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameFragmenter.java b/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameFragmenter.java index d634f7374..e59ece86f 100644 --- a/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameFragmenter.java +++ b/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameFragmenter.java @@ -20,7 +20,14 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import io.netty.util.ReferenceCountUtil; -import io.rsocket.frame.*; +import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.FrameLengthFlyweight; +import io.rsocket.frame.FrameType; +import io.rsocket.frame.PayloadFrameFlyweight; +import io.rsocket.frame.RequestChannelFrameFlyweight; +import io.rsocket.frame.RequestFireAndForgetFrameFlyweight; +import io.rsocket.frame.RequestResponseFrameFlyweight; +import io.rsocket.frame.RequestStreamFrameFlyweight; import java.util.function.Consumer; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; diff --git a/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java b/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java index 6cb05dec1..10725238a 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java @@ -61,6 +61,7 @@ static RSocketState requester(int tickPeriod, int timeout) { DefaultPayload::create, errors, StreamIdSupplier.clientSupplier(), + 0, tickPeriod, timeout, new DefaultKeepAliveHandler(connection), @@ -86,6 +87,7 @@ static ResumableRSocketState resumableRequester(int tickPeriod, int timeout) { DefaultPayload::create, errors, StreamIdSupplier.clientSupplier(), + 0, tickPeriod, timeout, new ResumableKeepAliveHandler(resumableConnection), diff --git a/rsocket-core/src/test/java/io/rsocket/core/PayloadValidationUtilsTest.java b/rsocket-core/src/test/java/io/rsocket/core/PayloadValidationUtilsTest.java new file mode 100644 index 000000000..e91fce848 --- /dev/null +++ b/rsocket-core/src/test/java/io/rsocket/core/PayloadValidationUtilsTest.java @@ -0,0 +1,99 @@ +package io.rsocket.core; + +import io.rsocket.Payload; +import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.FrameLengthFlyweight; +import io.rsocket.util.DefaultPayload; +import java.util.concurrent.ThreadLocalRandom; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +class PayloadValidationUtilsTest { + + @Test + void shouldBeValidFrameWithNoFragmentation() { + byte[] data = + new byte + [FrameLengthFlyweight.FRAME_LENGTH_MASK + - FrameLengthFlyweight.FRAME_LENGTH_SIZE + - FrameHeaderFlyweight.size()]; + ThreadLocalRandom.current().nextBytes(data); + final Payload payload = DefaultPayload.create(data); + + Assertions.assertThat(PayloadValidationUtils.isValid(0, payload)).isTrue(); + } + + @Test + void shouldBeInValidFrameWithNoFragmentation() { + byte[] data = + new byte + [FrameLengthFlyweight.FRAME_LENGTH_MASK + - FrameLengthFlyweight.FRAME_LENGTH_SIZE + - FrameHeaderFlyweight.size() + + 1]; + ThreadLocalRandom.current().nextBytes(data); + final Payload payload = DefaultPayload.create(data); + + Assertions.assertThat(PayloadValidationUtils.isValid(0, payload)).isFalse(); + } + + @Test + void shouldBeValidFrameWithNoFragmentation0() { + byte[] metadata = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK / 2]; + byte[] data = + new byte + [FrameLengthFlyweight.FRAME_LENGTH_MASK / 2 + - FrameLengthFlyweight.FRAME_LENGTH_SIZE + - FrameHeaderFlyweight.size() + - FrameHeaderFlyweight.size()]; + ThreadLocalRandom.current().nextBytes(data); + ThreadLocalRandom.current().nextBytes(metadata); + final Payload payload = DefaultPayload.create(data, metadata); + + Assertions.assertThat(PayloadValidationUtils.isValid(0, payload)).isTrue(); + } + + @Test + void shouldBeInValidFrameWithNoFragmentation1() { + byte[] metadata = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; + byte[] data = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; + ThreadLocalRandom.current().nextBytes(metadata); + ThreadLocalRandom.current().nextBytes(data); + final Payload payload = DefaultPayload.create(data, metadata); + + Assertions.assertThat(PayloadValidationUtils.isValid(0, payload)).isFalse(); + } + + @Test + void shouldBeValidFrameWithNoFragmentation2() { + byte[] metadata = new byte[1]; + byte[] data = new byte[1]; + ThreadLocalRandom.current().nextBytes(metadata); + ThreadLocalRandom.current().nextBytes(data); + final Payload payload = DefaultPayload.create(data, metadata); + + Assertions.assertThat(PayloadValidationUtils.isValid(0, payload)).isTrue(); + } + + @Test + void shouldBeValidFrameWithNoFragmentation3() { + byte[] metadata = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; + byte[] data = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; + ThreadLocalRandom.current().nextBytes(metadata); + ThreadLocalRandom.current().nextBytes(data); + final Payload payload = DefaultPayload.create(data, metadata); + + Assertions.assertThat(PayloadValidationUtils.isValid(64, payload)).isTrue(); + } + + @Test + void shouldBeValidFrameWithNoFragmentation4() { + byte[] metadata = new byte[1]; + byte[] data = new byte[1]; + ThreadLocalRandom.current().nextBytes(metadata); + ThreadLocalRandom.current().nextBytes(data); + final Payload payload = DefaultPayload.create(data, metadata); + + Assertions.assertThat(PayloadValidationUtils.isValid(64, payload)).isTrue(); + } +} diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java index 3cbb3c5d7..0a7f7a196 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java @@ -94,6 +94,7 @@ void setUp() { StreamIdSupplier.clientSupplier(), 0, 0, + 0, null, requesterLeaseHandler); @@ -111,7 +112,8 @@ void setUp() { mockRSocketHandler, payloadDecoder, err -> {}, - responderLeaseHandler); + responderLeaseHandler, + 0); } @Test diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java index 8a2e114cc..8380290f2 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java @@ -67,6 +67,7 @@ void setUp() { StreamIdSupplier.clientSupplier(), 0, 0, + 0, null, RequesterLeaseHandler.None); } diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java index 20b1825fa..101500da7 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java @@ -16,10 +16,21 @@ package io.rsocket.core; +import static io.rsocket.core.PayloadValidationUtils.INVALID_PAYLOAD_ERROR_MESSAGE; import static io.rsocket.frame.FrameHeaderFlyweight.frameType; -import static io.rsocket.frame.FrameType.*; +import static io.rsocket.frame.FrameType.CANCEL; +import static io.rsocket.frame.FrameType.KEEPALIVE; +import static io.rsocket.frame.FrameType.REQUEST_CHANNEL; +import static io.rsocket.frame.FrameType.REQUEST_RESPONSE; +import static io.rsocket.frame.FrameType.REQUEST_STREAM; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; @@ -27,9 +38,18 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.util.CharsetUtil; import io.rsocket.Payload; +import io.rsocket.RSocket; import io.rsocket.exceptions.ApplicationErrorException; import io.rsocket.exceptions.RejectedSetupException; -import io.rsocket.frame.*; +import io.rsocket.frame.CancelFrameFlyweight; +import io.rsocket.frame.ErrorFrameFlyweight; +import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.FrameLengthFlyweight; +import io.rsocket.frame.FrameType; +import io.rsocket.frame.PayloadFrameFlyweight; +import io.rsocket.frame.RequestChannelFrameFlyweight; +import io.rsocket.frame.RequestNFrameFlyweight; +import io.rsocket.frame.RequestStreamFrameFlyweight; import io.rsocket.lease.RequesterLeaseHandler; import io.rsocket.test.util.TestSubscriber; import io.rsocket.util.DefaultPayload; @@ -39,7 +59,10 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.BiFunction; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.assertj.core.api.Assertions; import org.junit.Rule; import org.junit.Test; @@ -51,6 +74,7 @@ import reactor.core.publisher.Mono; import reactor.core.publisher.MonoProcessor; import reactor.core.publisher.UnicastProcessor; +import reactor.test.StepVerifier; public class RSocketRequesterTest { @@ -262,6 +286,62 @@ protected void hookOnSubscribe(Subscription subscription) {} Assertions.assertThat(iterator.hasNext()).isFalse(); } + @Test + public void shouldThrownExceptionIfGivenPayloadIsExitsSizeAllowanceWithNoFragmentation() { + prepareCalls() + .forEach( + generator -> { + byte[] metadata = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; + byte[] data = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; + ThreadLocalRandom.current().nextBytes(metadata); + ThreadLocalRandom.current().nextBytes(data); + StepVerifier.create( + generator.apply(rule.socket, DefaultPayload.create(data, metadata))) + .expectSubscription() + .expectErrorSatisfies( + t -> + Assertions.assertThat(t) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(INVALID_PAYLOAD_ERROR_MESSAGE)) + .verify(); + }); + } + + @Test + public void + shouldThrownExceptionIfGivenPayloadIsExitsSizeAllowanceWithNoFragmentationForRequestChannelCase() { + byte[] metadata = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; + byte[] data = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; + ThreadLocalRandom.current().nextBytes(metadata); + ThreadLocalRandom.current().nextBytes(data); + StepVerifier.create( + rule.socket.requestChannel( + Flux.just(EmptyPayload.INSTANCE, DefaultPayload.create(data, metadata)))) + .expectSubscription() + .then( + () -> + rule.connection.addToReceivedBuffer( + RequestNFrameFlyweight.encode( + ByteBufAllocator.DEFAULT, + rule.getStreamIdForRequestType(REQUEST_CHANNEL), + 2))) + .expectErrorSatisfies( + t -> + Assertions.assertThat(t) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(INVALID_PAYLOAD_ERROR_MESSAGE)) + .verify(); + } + + static Stream>> prepareCalls() { + return Stream.of( + RSocket::fireAndForget, + RSocket::requestResponse, + RSocket::requestStream, + (rSocket, payload) -> rSocket.requestChannel(Flux.just(payload)), + RSocket::metadataPush); + } + public int sendRequestResponse(Publisher response) { Subscriber sub = TestSubscriber.create(); response.subscribe(sub); @@ -285,6 +365,7 @@ protected RSocketRequester newRSocket() { StreamIdSupplier.clientSupplier(), 0, 0, + 0, null, RequesterLeaseHandler.None); } diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java index 10157532a..5c147f46f 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java @@ -16,6 +16,7 @@ package io.rsocket.core; +import static io.rsocket.core.PayloadValidationUtils.INVALID_PAYLOAD_ERROR_MESSAGE; import static io.rsocket.frame.FrameHeaderFlyweight.frameType; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -34,11 +35,15 @@ import io.rsocket.util.EmptyPayload; import java.util.Collection; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicBoolean; +import org.assertj.core.api.Assertions; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; +import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; public class RSocketResponderTest { @@ -110,6 +115,58 @@ public Mono requestResponse(Payload payload) { assertThat("Subscription not cancelled.", cancelled.get(), is(true)); } + @Test + public void shouldThrownExceptionIfGivenPayloadIsExitsSizeAllowanceWithNoFragmentation() { + final int streamId = 4; + final AtomicBoolean cancelled = new AtomicBoolean(); + byte[] metadata = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; + byte[] data = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; + ThreadLocalRandom.current().nextBytes(metadata); + ThreadLocalRandom.current().nextBytes(data); + final Payload payload = DefaultPayload.create(data, metadata); + final AbstractRSocket acceptingSocket = + new AbstractRSocket() { + @Override + public Mono requestResponse(Payload p) { + return Mono.just(payload).doOnCancel(() -> cancelled.set(true)); + } + + @Override + public Flux requestStream(Payload p) { + return Flux.just(payload).doOnCancel(() -> cancelled.set(true)); + } + + @Override + public Flux requestChannel(Publisher payloads) { + return Flux.just(payload).doOnCancel(() -> cancelled.set(true)); + } + }; + rule.setAcceptingSocket(acceptingSocket); + + final Runnable[] runnables = { + () -> rule.sendRequest(streamId, FrameType.REQUEST_RESPONSE), + () -> rule.sendRequest(streamId, FrameType.REQUEST_STREAM), + () -> rule.sendRequest(streamId, FrameType.REQUEST_CHANNEL) + }; + + for (Runnable runnable : runnables) { + runnable.run(); + Assertions.assertThat(rule.errors) + .first() + .isInstanceOf(IllegalArgumentException.class) + .hasToString("java.lang.IllegalArgumentException: " + INVALID_PAYLOAD_ERROR_MESSAGE); + Assertions.assertThat(rule.connection.getSent()) + .hasSize(1) + .first() + .matches(bb -> FrameHeaderFlyweight.frameType(bb) == FrameType.ERROR) + .matches(bb -> ErrorFrameFlyweight.dataUtf8(bb).contains(INVALID_PAYLOAD_ERROR_MESSAGE)); + + assertThat("Subscription not cancelled.", cancelled.get(), is(true)); + rule.init(); + rule.setAcceptingSocket(acceptingSocket); + } + } + public static class ServerSocketRule extends AbstractSocketRule { private RSocket acceptingSocket; @@ -151,7 +208,8 @@ protected RSocketResponder newRSocket() { acceptingSocket, DefaultPayload::create, throwable -> errors.add(throwable), - ResponderLeaseHandler.None); + ResponderLeaseHandler.None, + 0); } private void sendRequest(int streamId, FrameType frameType) { diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java index b18fad890..edcc8971f 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java @@ -222,7 +222,8 @@ public Flux requestChannel(Publisher payloads) { requestAcceptor, DefaultPayload::create, throwable -> serverErrors.add(throwable), - ResponderLeaseHandler.None); + ResponderLeaseHandler.None, + 0); crs = new RSocketRequester( @@ -233,6 +234,7 @@ public Flux requestChannel(Publisher payloads) { StreamIdSupplier.clientSupplier(), 0, 0, + 0, null, RequesterLeaseHandler.None); } diff --git a/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java b/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java index daab5d246..9344d69da 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java @@ -58,6 +58,7 @@ void requesterStreamsTerminatedOnZeroErrorFrame() { StreamIdSupplier.clientSupplier(), 0, 0, + 0, null, RequesterLeaseHandler.None); @@ -93,6 +94,7 @@ void requesterNewStreamsTerminatedAfterZeroErrorFrame() { StreamIdSupplier.clientSupplier(), 0, 0, + 0, null, RequesterLeaseHandler.None); From a9c385aeb6cc6ba139da07859c391f86d77b2814 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Sat, 11 Apr 2020 12:57:08 +0100 Subject: [PATCH 14/62] reduce package cycles (#774) * Remove package cycle between resume and internal Move ClientSetup and ServerSetup are closely associated with the RSocketFactory implementations. Moving them into core where they are used from breaks a package cycle between resume and internal. ClientSetup is straight-forward and folds easily into DefaultClientRSocketFactory. ConnectionUtil is closely associated with ServerSetup and has been folded into it. Signed-off-by: Rossen Stoyanchev * Remove package cycle between lease and exceptions MissingLeaseException is moved to the lease package next to the contracts it is declared in breaking a cycle between lease and exceptions. This is an API breakage but I the impact should be minor (none? the chances of something handling it explicitly) and hence an acceptable trade-off. Signed-off-by: Rossen Stoyanchev * Add RSocketProtocolException in top-level package Replaces (now deprecated) RSocketException from the exceptions package which can lead to cycles (e.g. with frame package). Other minor refinements: - error code validation - shared errorCode() implementation - error code in toString() - avoid NPE for null message, it's better to keep the original exception and null is allowed by getMessage(). Signed-off-by: Rossen Stoyanchev * Deprecate ErrorType This change deprecates ErrorType as the same constants are already declared in ErrorFrameFlyweight and there is hierarchy of exceptions with one exception per error code. Signed-off-by: Rossen Stoyanchev --- .../io/rsocket/RSocketErrorException.java | 82 +++++++++++++ .../core/DefaultClientRSocketFactory.java | 50 ++++---- .../core/DefaultServerRSocketFactory.java | 40 +++---- .../{internal => core}/ServerSetup.java | 48 ++++---- .../exceptions/ApplicationErrorException.java | 18 +-- .../rsocket/exceptions/CanceledException.java | 18 +-- .../exceptions/ConnectionCloseException.java | 18 +-- .../exceptions/ConnectionErrorException.java | 18 +-- .../exceptions/CustomRSocketException.java | 47 ++++---- .../io/rsocket/exceptions/Exceptions.java | 20 +++- .../rsocket/exceptions/InvalidException.java | 18 +-- .../exceptions/InvalidSetupException.java | 18 +-- .../rsocket/exceptions/RSocketException.java | 37 +++--- .../rsocket/exceptions/RejectedException.java | 18 +-- .../exceptions/RejectedResumeException.java | 18 +-- .../exceptions/RejectedSetupException.java | 18 +-- .../io/rsocket/exceptions/SetupException.java | 28 ++++- .../exceptions/UnsupportedSetupException.java | 18 +-- .../io/rsocket/frame/ErrorFrameFlyweight.java | 15 +-- .../main/java/io/rsocket/frame/ErrorType.java | 2 + .../java/io/rsocket/internal/ClientSetup.java | 112 ------------------ .../MissingLeaseException.java | 19 ++- .../rsocket/lease/RequesterLeaseHandler.java | 1 - .../rsocket/lease/ResponderLeaseHandler.java | 1 - .../java/io/rsocket/util/ConnectionUtils.java | 17 --- .../io/rsocket/core/RSocketLeaseTest.java | 2 +- .../java/io/rsocket/core/RSocketTest.java | 5 +- .../exceptions/RSocketExceptionTest.java | 13 +- 28 files changed, 329 insertions(+), 390 deletions(-) create mode 100644 rsocket-core/src/main/java/io/rsocket/RSocketErrorException.java rename rsocket-core/src/main/java/io/rsocket/{internal => core}/ServerSetup.java (83%) delete mode 100644 rsocket-core/src/main/java/io/rsocket/internal/ClientSetup.java rename rsocket-core/src/main/java/io/rsocket/{exceptions => lease}/MissingLeaseException.java (60%) delete mode 100644 rsocket-core/src/main/java/io/rsocket/util/ConnectionUtils.java diff --git a/rsocket-core/src/main/java/io/rsocket/RSocketErrorException.java b/rsocket-core/src/main/java/io/rsocket/RSocketErrorException.java new file mode 100644 index 000000000..b43b14bae --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/RSocketErrorException.java @@ -0,0 +1,82 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.rsocket; + +import reactor.util.annotation.Nullable; + +/** + * Exception that represents an RSocket protocol error. + * + * @see ERROR + * Frame (0x0B) + */ +public class RSocketErrorException extends RuntimeException { + + private static final long serialVersionUID = -1628781753426267554L; + + private static final int MIN_ERROR_CODE = 0x00000001; + + private static final int MAX_ERROR_CODE = 0xFFFFFFFE; + + private final int errorCode; + + /** + * Constructor with a protocol error code and a message. + * + * @param errorCode the RSocket protocol error code + * @param message error explanation + */ + public RSocketErrorException(int errorCode, String message) { + this(errorCode, message, null); + } + + /** + * Alternative to {@link #RSocketErrorException(int, String)} with a root cause. + * + * @param errorCode the RSocket protocol error code + * @param message error explanation + * @param cause a root cause for the error + */ + public RSocketErrorException(int errorCode, String message, @Nullable Throwable cause) { + super(message, cause); + this.errorCode = errorCode; + if (errorCode > MAX_ERROR_CODE && errorCode < MIN_ERROR_CODE) { + throw new IllegalArgumentException( + "Allowed errorCode value should be in range [0x00000001-0xFFFFFFFE]", this); + } + } + + /** + * Return the RSocket error code + * represented by this exception + * + * @return the RSocket protocol error code + */ + public int errorCode() { + return errorCode; + } + + @Override + public String toString() { + return getClass().getSimpleName() + + " (0x" + + Integer.toHexString(errorCode) + + "): " + + getMessage(); + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/core/DefaultClientRSocketFactory.java b/rsocket-core/src/main/java/io/rsocket/core/DefaultClientRSocketFactory.java index b7cad7042..2ecde3663 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/DefaultClientRSocketFactory.java +++ b/rsocket-core/src/main/java/io/rsocket/core/DefaultClientRSocketFactory.java @@ -17,6 +17,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; import io.rsocket.AbstractRSocket; import io.rsocket.ConnectionSetupPayload; import io.rsocket.DuplexConnection; @@ -28,7 +29,6 @@ import io.rsocket.frame.SetupFrameFlyweight; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.internal.ClientServerInputMultiplexer; -import io.rsocket.internal.ClientSetup; import io.rsocket.keepalive.KeepAliveHandler; import io.rsocket.lease.LeaseStats; import io.rsocket.lease.Leases; @@ -39,6 +39,7 @@ import io.rsocket.plugins.Plugins; import io.rsocket.plugins.RSocketInterceptor; import io.rsocket.plugins.SocketAcceptorInterceptor; +import io.rsocket.resume.ClientRSocketSession; import io.rsocket.resume.ExponentialBackoffResumeStrategy; import io.rsocket.resume.InMemoryResumableFramesStore; import io.rsocket.resume.ResumableFramesStore; @@ -309,10 +310,31 @@ public Mono start() { return newConnection() .flatMap( connection -> { - ClientSetup clientSetup = clientSetup(connection); - ByteBuf resumeToken = clientSetup.resumeToken(); - KeepAliveHandler keepAliveHandler = clientSetup.keepAliveHandler(); - DuplexConnection wrappedConnection = clientSetup.connection(); + ByteBuf resumeToken; + KeepAliveHandler keepAliveHandler; + DuplexConnection wrappedConnection; + + if (resumeEnabled) { + resumeToken = resumeTokenSupplier.get(); + ClientRSocketSession session = + new ClientRSocketSession( + connection, + allocator, + resumeSessionDuration, + resumeStrategySupplier, + resumeStoreFactory.apply(resumeToken), + resumeStreamTimeout, + resumeCleanupStoreOnKeepAlive) + .continueWith(newConnection()) + .resumeToken(resumeToken); + keepAliveHandler = + new KeepAliveHandler.ResumableKeepAliveHandler(session.resumableConnection()); + wrappedConnection = session.resumableConnection(); + } else { + resumeToken = Unpooled.EMPTY_BUFFER; + keepAliveHandler = new KeepAliveHandler.DefaultKeepAliveHandler(connection); + wrappedConnection = connection; + } ClientServerInputMultiplexer multiplexer = new ClientServerInputMultiplexer(wrappedConnection, plugins, true); @@ -407,24 +429,6 @@ private int keepAliveTimeout() { return (int) (ackTimeout.toMillis() + tickPeriod.toMillis() * missedAcks); } - private ClientSetup clientSetup(DuplexConnection startConnection) { - if (resumeEnabled) { - ByteBuf resumeToken = resumeTokenSupplier.get(); - return new ClientSetup.ResumableClientSetup( - allocator, - startConnection, - newConnection(), - resumeToken, - resumeStoreFactory.apply(resumeToken), - resumeSessionDuration, - resumeStreamTimeout, - resumeStrategySupplier, - resumeCleanupStoreOnKeepAlive); - } else { - return new ClientSetup.DefaultClientSetup(startConnection); - } - } - private Mono newConnection() { return Mono.fromSupplier(transportClient).flatMap(t -> t.connect(mtu)); } diff --git a/rsocket-core/src/main/java/io/rsocket/core/DefaultServerRSocketFactory.java b/rsocket-core/src/main/java/io/rsocket/core/DefaultServerRSocketFactory.java index 85543181a..73d3513da 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/DefaultServerRSocketFactory.java +++ b/rsocket-core/src/main/java/io/rsocket/core/DefaultServerRSocketFactory.java @@ -29,7 +29,6 @@ import io.rsocket.frame.SetupFrameFlyweight; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.internal.ClientServerInputMultiplexer; -import io.rsocket.internal.ServerSetup; import io.rsocket.lease.Leases; import io.rsocket.lease.RequesterLeaseHandler; import io.rsocket.lease.ResponderLeaseHandler; @@ -42,7 +41,6 @@ import io.rsocket.resume.ResumableFramesStore; import io.rsocket.resume.SessionManager; import io.rsocket.transport.ServerTransport; -import io.rsocket.util.ConnectionUtils; import io.rsocket.util.MultiSubscriberRSocket; import java.time.Duration; import java.util.Objects; @@ -232,7 +230,16 @@ private Mono accept( case RESUME: return acceptResume(serverSetup, startFrame, multiplexer); default: - return acceptUnknown(startFrame, multiplexer); + return serverSetup + .sendError( + multiplexer, + new InvalidSetupException( + "invalid setup frame: " + FrameHeaderFlyweight.frameType(startFrame))) + .doFinally( + signalType -> { + startFrame.release(); + multiplexer.dispose(); + }); } } @@ -240,7 +247,8 @@ private Mono acceptSetup( ServerSetup serverSetup, ByteBuf setupFrame, ClientServerInputMultiplexer multiplexer) { if (!SetupFrameFlyweight.isSupportedVersion(setupFrame)) { - return sendError( + return serverSetup + .sendError( multiplexer, new InvalidSetupException( "Unsupported version: " + SetupFrameFlyweight.humanReadableVersion(setupFrame))) @@ -254,7 +262,8 @@ private Mono acceptSetup( boolean isLeaseEnabled = leaseEnabled; if (SetupFrameFlyweight.honorLease(setupFrame) && !isLeaseEnabled) { - return sendError(multiplexer, new InvalidSetupException("lease is not supported")) + return serverSetup + .sendError(multiplexer, new InvalidSetupException("lease is not supported")) .doFinally( signalType -> { setupFrame.release(); @@ -296,7 +305,10 @@ private Mono acceptSetup( .applySocketAcceptorInterceptor(acceptor) .accept(setupPayload, wrappedRSocketRequester) .onErrorResume( - err -> sendError(multiplexer, rejectedSetupError(err)).then(Mono.error(err))) + err -> + serverSetup + .sendError(multiplexer, rejectedSetupError(err)) + .then(Mono.error(err))) .doOnNext( rSocketHandler -> { RSocket wrappedRSocketHandler = plugins.applyResponder(rSocketHandler); @@ -357,22 +369,6 @@ private ServerSetup serverSetup() { : new ServerSetup.DefaultServerSetup(allocator); } - private Mono acceptUnknown(ByteBuf frame, ClientServerInputMultiplexer multiplexer) { - return sendError( - multiplexer, - new InvalidSetupException( - "invalid setup frame: " + FrameHeaderFlyweight.frameType(frame))) - .doFinally( - signalType -> { - frame.release(); - multiplexer.dispose(); - }); - } - - private Mono sendError(ClientServerInputMultiplexer multiplexer, Exception exception) { - return ConnectionUtils.sendError(allocator, multiplexer, exception); - } - private Exception rejectedSetupError(Throwable err) { String msg = err.getMessage(); return new RejectedSetupException(msg == null ? "rejected by server acceptor" : msg); diff --git a/rsocket-core/src/main/java/io/rsocket/internal/ServerSetup.java b/rsocket-core/src/main/java/io/rsocket/core/ServerSetup.java similarity index 83% rename from rsocket-core/src/main/java/io/rsocket/internal/ServerSetup.java rename to rsocket-core/src/main/java/io/rsocket/core/ServerSetup.java index dbd8bc173..16f5f61b1 100644 --- a/rsocket-core/src/main/java/io/rsocket/internal/ServerSetup.java +++ b/rsocket-core/src/main/java/io/rsocket/core/ServerSetup.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2019 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.rsocket.internal; +package io.rsocket.core; import static io.rsocket.keepalive.KeepAliveHandler.*; @@ -22,32 +22,45 @@ import io.netty.buffer.ByteBufAllocator; import io.rsocket.exceptions.RejectedResumeException; import io.rsocket.exceptions.UnsupportedSetupException; +import io.rsocket.frame.ErrorFrameFlyweight; import io.rsocket.frame.ResumeFrameFlyweight; import io.rsocket.frame.SetupFrameFlyweight; +import io.rsocket.internal.ClientServerInputMultiplexer; import io.rsocket.keepalive.KeepAliveHandler; import io.rsocket.resume.*; -import io.rsocket.util.ConnectionUtils; import java.time.Duration; import java.util.function.BiFunction; import java.util.function.Function; import reactor.core.publisher.Mono; -public interface ServerSetup { +abstract class ServerSetup { - Mono acceptRSocketSetup( + final ByteBufAllocator allocator; + + public ServerSetup(ByteBufAllocator allocator) { + this.allocator = allocator; + } + + abstract Mono acceptRSocketSetup( ByteBuf frame, ClientServerInputMultiplexer multiplexer, BiFunction> then); - Mono acceptRSocketResume(ByteBuf frame, ClientServerInputMultiplexer multiplexer); + abstract Mono acceptRSocketResume(ByteBuf frame, ClientServerInputMultiplexer multiplexer); - default void dispose() {} + void dispose() {} - class DefaultServerSetup implements ServerSetup { - private final ByteBufAllocator allocator; + Mono sendError(ClientServerInputMultiplexer multiplexer, Exception exception) { + return multiplexer + .asSetupConnection() + .sendOne(ErrorFrameFlyweight.encode(allocator, 0, exception)) + .onErrorResume(err -> Mono.empty()); + } - public DefaultServerSetup(ByteBufAllocator allocator) { - this.allocator = allocator; + static class DefaultServerSetup extends ServerSetup { + + DefaultServerSetup(ByteBufAllocator allocator) { + super(allocator); } @Override @@ -78,13 +91,9 @@ public Mono acceptRSocketResume(ByteBuf frame, ClientServerInputMultiplexe multiplexer.dispose(); }); } - - private Mono sendError(ClientServerInputMultiplexer multiplexer, Exception exception) { - return ConnectionUtils.sendError(allocator, multiplexer, exception); - } } - class ResumableServerSetup implements ServerSetup { + static class ResumableServerSetup extends ServerSetup { private final ByteBufAllocator allocator; private final SessionManager sessionManager; private final Duration resumeSessionDuration; @@ -92,13 +101,14 @@ class ResumableServerSetup implements ServerSetup { private final Function resumeStoreFactory; private final boolean cleanupStoreOnKeepAlive; - public ResumableServerSetup( + ResumableServerSetup( ByteBufAllocator allocator, SessionManager sessionManager, Duration resumeSessionDuration, Duration resumeStreamTimeout, Function resumeStoreFactory, boolean cleanupStoreOnKeepAlive) { + super(allocator); this.allocator = allocator; this.sessionManager = sessionManager; this.resumeSessionDuration = resumeSessionDuration; @@ -155,10 +165,6 @@ public Mono acceptRSocketResume(ByteBuf frame, ClientServerInputMultiplexe } } - private Mono sendError(ClientServerInputMultiplexer multiplexer, Exception exception) { - return ConnectionUtils.sendError(allocator, multiplexer, exception); - } - @Override public void dispose() { sessionManager.dispose(); diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/ApplicationErrorException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/ApplicationErrorException.java index e92534b2a..351e045a3 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/ApplicationErrorException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/ApplicationErrorException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,8 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorType; +import io.rsocket.frame.ErrorFrameFlyweight; +import javax.annotation.Nullable; /** * Application layer logic generating a Reactive Streams {@code onError} event. @@ -32,10 +33,9 @@ public final class ApplicationErrorException extends RSocketException { * Constructs a new exception with the specified message. * * @param message the message - * @throws NullPointerException if {@code message} is {@code null} */ public ApplicationErrorException(String message) { - super(message); + this(message, null); } /** @@ -43,14 +43,8 @@ public ApplicationErrorException(String message) { * * @param message the message * @param cause the cause of this exception - * @throws NullPointerException if {@code message} or {@code cause} is {@code null} */ - public ApplicationErrorException(String message, Throwable cause) { - super(message, cause); - } - - @Override - public int errorCode() { - return ErrorType.APPLICATION_ERROR; + public ApplicationErrorException(String message, @Nullable Throwable cause) { + super(ErrorFrameFlyweight.APPLICATION_ERROR, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/CanceledException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/CanceledException.java index 984e8249b..537cf2bf2 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/CanceledException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/CanceledException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,8 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorType; +import io.rsocket.frame.ErrorFrameFlyweight; +import javax.annotation.Nullable; /** * The Responder canceled the request but may have started processing it (similar to REJECTED but @@ -33,10 +34,9 @@ public final class CanceledException extends RSocketException { * Constructs a new exception with the specified message. * * @param message the message - * @throws NullPointerException if {@code message} is {@code null} */ public CanceledException(String message) { - super(message); + this(message, null); } /** @@ -44,14 +44,8 @@ public CanceledException(String message) { * * @param message the message * @param cause the cause of this exception - * @throws NullPointerException if {@code message} or {@code cause} is {@code null} */ - public CanceledException(String message, Throwable cause) { - super(message, cause); - } - - @Override - public int errorCode() { - return ErrorType.CANCELED; + public CanceledException(String message, @Nullable Throwable cause) { + super(ErrorFrameFlyweight.CANCELED, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionCloseException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionCloseException.java index 3f4f4309d..f1f1a47d8 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionCloseException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionCloseException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,8 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorType; +import io.rsocket.frame.ErrorFrameFlyweight; +import javax.annotation.Nullable; /** * The connection is being terminated. Sender or Receiver of this frame MUST wait for outstanding @@ -33,10 +34,9 @@ public final class ConnectionCloseException extends RSocketException { * Constructs a new exception with the specified message. * * @param message the message - * @throws NullPointerException if {@code message} is {@code null} */ public ConnectionCloseException(String message) { - super(message); + this(message, null); } /** @@ -44,14 +44,8 @@ public ConnectionCloseException(String message) { * * @param message the message * @param cause the cause of this exception - * @throws NullPointerException if {@code message} or {@code cause} is {@code null} */ - public ConnectionCloseException(String message, Throwable cause) { - super(message, cause); - } - - @Override - public int errorCode() { - return ErrorType.CONNECTION_CLOSE; + public ConnectionCloseException(String message, @Nullable Throwable cause) { + super(ErrorFrameFlyweight.CONNECTION_CLOSE, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionErrorException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionErrorException.java index beaa3d0d0..9581cfc97 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionErrorException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionErrorException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,8 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorType; +import io.rsocket.frame.ErrorFrameFlyweight; +import javax.annotation.Nullable; /** * The connection is being terminated. Sender or Receiver of this frame MAY close the connection @@ -33,10 +34,9 @@ public final class ConnectionErrorException extends RSocketException implements * Constructs a new exception with the specified message. * * @param message the message - * @throws NullPointerException if {@code message} is {@code null} */ public ConnectionErrorException(String message) { - super(message); + this(message, null); } /** @@ -44,14 +44,8 @@ public ConnectionErrorException(String message) { * * @param message the message * @param cause the cause of this exception - * @throws NullPointerException if {@code message} or {@code cause} is {@code null} */ - public ConnectionErrorException(String message, Throwable cause) { - super(message, cause); - } - - @Override - public int errorCode() { - return ErrorType.CONNECTION_ERROR; + public ConnectionErrorException(String message, @Nullable Throwable cause) { + super(ErrorFrameFlyweight.CONNECTION_ERROR, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/CustomRSocketException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/CustomRSocketException.java index 6315206b5..5c1154ebd 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/CustomRSocketException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/CustomRSocketException.java @@ -1,28 +1,36 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.rsocket.exceptions; -import io.rsocket.frame.ErrorType; +import io.rsocket.frame.ErrorFrameFlyweight; +import javax.annotation.Nullable; public class CustomRSocketException extends RSocketException { private static final long serialVersionUID = 7873267740343446585L; - private final int errorCode; - /** * Constructs a new exception with the specified message. * * @param errorCode customizable error code. Should be in range [0x00000301-0xFFFFFFFE] * @param message the message - * @throws NullPointerException if {@code message} is {@code null} * @throws IllegalArgumentException if {@code errorCode} is out of allowed range */ public CustomRSocketException(int errorCode, String message) { - super(message); - if (errorCode > ErrorType.MAX_USER_ALLOWED_ERROR_CODE - && errorCode < ErrorType.MIN_USER_ALLOWED_ERROR_CODE) { - throw new IllegalArgumentException( - "Allowed errorCode value should be in range [0x00000301-0xFFFFFFFE]"); - } - this.errorCode = errorCode; + this(errorCode, message, null); } /** @@ -31,21 +39,14 @@ public CustomRSocketException(int errorCode, String message) { * @param errorCode customizable error code. Should be in range [0x00000301-0xFFFFFFFE] * @param message the message * @param cause the cause of this exception - * @throws NullPointerException if {@code message} or {@code cause} is {@code null} * @throws IllegalArgumentException if {@code errorCode} is out of allowed range */ - public CustomRSocketException(int errorCode, String message, Throwable cause) { - super(message, cause); - if (errorCode > ErrorType.MAX_USER_ALLOWED_ERROR_CODE - && errorCode < ErrorType.MIN_USER_ALLOWED_ERROR_CODE) { + public CustomRSocketException(int errorCode, String message, @Nullable Throwable cause) { + super(errorCode, message, cause); + if (errorCode > ErrorFrameFlyweight.MAX_USER_ALLOWED_ERROR_CODE + && errorCode < ErrorFrameFlyweight.MIN_USER_ALLOWED_ERROR_CODE) { throw new IllegalArgumentException( - "Allowed errorCode value should be in range [0x00000301-0xFFFFFFFE]"); + "Allowed errorCode value should be in range [0x00000301-0xFFFFFFFE]", this); } - this.errorCode = errorCode; - } - - @Override - public int errorCode() { - return errorCode; } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/Exceptions.java b/rsocket-core/src/main/java/io/rsocket/exceptions/Exceptions.java index 3a10410f0..fe2d304f5 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/Exceptions.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/Exceptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,21 @@ package io.rsocket.exceptions; -import static io.rsocket.frame.ErrorFrameFlyweight.*; +import static io.rsocket.frame.ErrorFrameFlyweight.APPLICATION_ERROR; +import static io.rsocket.frame.ErrorFrameFlyweight.CANCELED; +import static io.rsocket.frame.ErrorFrameFlyweight.CONNECTION_CLOSE; +import static io.rsocket.frame.ErrorFrameFlyweight.CONNECTION_ERROR; +import static io.rsocket.frame.ErrorFrameFlyweight.INVALID; +import static io.rsocket.frame.ErrorFrameFlyweight.INVALID_SETUP; +import static io.rsocket.frame.ErrorFrameFlyweight.MAX_USER_ALLOWED_ERROR_CODE; +import static io.rsocket.frame.ErrorFrameFlyweight.MIN_USER_ALLOWED_ERROR_CODE; +import static io.rsocket.frame.ErrorFrameFlyweight.REJECTED; +import static io.rsocket.frame.ErrorFrameFlyweight.REJECTED_RESUME; +import static io.rsocket.frame.ErrorFrameFlyweight.REJECTED_SETUP; +import static io.rsocket.frame.ErrorFrameFlyweight.UNSUPPORTED_SETUP; import io.netty.buffer.ByteBuf; +import io.rsocket.RSocketErrorException; import io.rsocket.frame.ErrorFrameFlyweight; import java.util.Objects; @@ -28,10 +40,10 @@ public final class Exceptions { private Exceptions() {} /** - * Create a {@link RSocketException} from a Frame that matches the error code it contains. + * Create a {@link RSocketErrorException} from a Frame that matches the error code it contains. * * @param frame the frame to retrieve the error code and message from - * @return a {@link RSocketException} that matches the error code in the Frame + * @return a {@link RSocketErrorException} that matches the error code in the Frame * @throws NullPointerException if {@code frame} is {@code null} */ public static RuntimeException from(int streamId, ByteBuf frame) { diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidException.java index 4783b1590..a4b28659f 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,8 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorType; +import io.rsocket.frame.ErrorFrameFlyweight; +import javax.annotation.Nullable; /** * The request is invalid. @@ -32,10 +33,9 @@ public final class InvalidException extends RSocketException { * Constructs a new exception with the specified message. * * @param message the message - * @throws NullPointerException if {@code message} is {@code null} */ public InvalidException(String message) { - super(message); + this(message, null); } /** @@ -43,14 +43,8 @@ public InvalidException(String message) { * * @param message the message * @param cause the cause of this exception - * @throws NullPointerException if {@code message} or {@code cause} is {@code null} */ - public InvalidException(String message, Throwable cause) { - super(message, cause); - } - - @Override - public int errorCode() { - return ErrorType.INVALID; + public InvalidException(String message, @Nullable Throwable cause) { + super(ErrorFrameFlyweight.INVALID, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidSetupException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidSetupException.java index b3705d5b7..1ff53d51d 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidSetupException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidSetupException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,8 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorType; +import io.rsocket.frame.ErrorFrameFlyweight; +import javax.annotation.Nullable; /** * The Setup frame is invalid for the server (it could be that the client is too recent for the old @@ -33,10 +34,9 @@ public final class InvalidSetupException extends SetupException { * Constructs a new exception with the specified message. * * @param message the message - * @throws NullPointerException if {@code message} is {@code null} */ public InvalidSetupException(String message) { - super(message); + this(message, null); } /** @@ -44,14 +44,8 @@ public InvalidSetupException(String message) { * * @param message the message * @param cause the cause of this exception - * @throws NullPointerException if {@code message} or {@code cause} is {@code null} */ - public InvalidSetupException(String message, Throwable cause) { - super(message, cause); - } - - @Override - public int errorCode() { - return ErrorType.INVALID_SETUP; + public InvalidSetupException(String message, @Nullable Throwable cause) { + super(ErrorFrameFlyweight.INVALID_SETUP, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/RSocketException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/RSocketException.java index 7508a1ee3..93c49d5e2 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/RSocketException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/RSocketException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,41 +16,48 @@ package io.rsocket.exceptions; -import java.util.Objects; +import io.rsocket.RSocketErrorException; +import io.rsocket.frame.ErrorFrameFlyweight; import reactor.util.annotation.Nullable; -/** The root of the RSocket exception hierarchy. */ -public abstract class RSocketException extends RuntimeException { +/** + * The root of the RSocket exception hierarchy. + * + * @deprecated please use {@link RSocketErrorException} instead + */ +@Deprecated +public abstract class RSocketException extends RSocketErrorException { private static final long serialVersionUID = 2912815394105575423L; /** - * Constructs a new exception with the specified message. + * Constructs a new exception with the specified message and error code 0x201 (Application error). * * @param message the message - * @throws NullPointerException if {@code message} is {@code null} */ public RSocketException(String message) { - super(Objects.requireNonNull(message, "message must not be null")); + this(message, null); } /** - * Constructs a new exception with the specified message and cause. + * Constructs a new exception with the specified message and cause and error code 0x201 + * (Application error). * * @param message the message * @param cause the cause of this exception - * @throws NullPointerException if {@code message} is {@code null} */ public RSocketException(String message, @Nullable Throwable cause) { - super(Objects.requireNonNull(message, "message must not be null"), cause); + super(ErrorFrameFlyweight.APPLICATION_ERROR, message, cause); } /** - * Returns the RSocket error code - * represented by this exception + * Constructs a new exception with the specified error code, message and cause. * - * @return the RSocket error code + * @param errorCode the RSocket protocol error code + * @param message the message + * @param cause the cause of this exception */ - public abstract int errorCode(); + public RSocketException(int errorCode, String message, @Nullable Throwable cause) { + super(errorCode, message, cause); + } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedException.java index 4ab83182e..3fad3f396 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,8 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorType; +import io.rsocket.frame.ErrorFrameFlyweight; +import javax.annotation.Nullable; /** * Despite being a valid request, the Responder decided to reject it. The Responder guarantees that @@ -34,10 +35,9 @@ public class RejectedException extends RSocketException implements Retryable { * Constructs a new exception with the specified message. * * @param message the message - * @throws NullPointerException if {@code message} is {@code null} */ public RejectedException(String message) { - super(message); + this(message, null); } /** @@ -45,14 +45,8 @@ public RejectedException(String message) { * * @param message the message * @param cause the cause of this exception - * @throws NullPointerException if {@code message} or {@code cause} is {@code null} */ - public RejectedException(String message, Throwable cause) { - super(message, cause); - } - - @Override - public int errorCode() { - return ErrorType.REJECTED; + public RejectedException(String message, @Nullable Throwable cause) { + super(ErrorFrameFlyweight.REJECTED, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedResumeException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedResumeException.java index 0d4116538..a10eb4197 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedResumeException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedResumeException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,8 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorType; +import io.rsocket.frame.ErrorFrameFlyweight; +import javax.annotation.Nullable; /** * The server rejected the resume, it can specify the reason in the payload. @@ -32,10 +33,9 @@ public final class RejectedResumeException extends RSocketException { * Constructs a new exception with the specified message. * * @param message the message - * @throws NullPointerException if {@code message} is {@code null} */ public RejectedResumeException(String message) { - super(message); + this(message, null); } /** @@ -43,14 +43,8 @@ public RejectedResumeException(String message) { * * @param message the message * @param cause the cause of this exception - * @throws NullPointerException if {@code message} or {@code cause} is {@code null} */ - public RejectedResumeException(String message, Throwable cause) { - super(message, cause); - } - - @Override - public int errorCode() { - return ErrorType.REJECTED_RESUME; + public RejectedResumeException(String message, @Nullable Throwable cause) { + super(ErrorFrameFlyweight.REJECTED_RESUME, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedSetupException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedSetupException.java index 1fa5f604e..6b5dc0f8b 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedSetupException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedSetupException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,8 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorType; +import io.rsocket.frame.ErrorFrameFlyweight; +import javax.annotation.Nullable; /** * The server rejected the setup, it can specify the reason in the payload. @@ -32,10 +33,9 @@ public final class RejectedSetupException extends SetupException implements Retr * Constructs a new exception with the specified message. * * @param message the message - * @throws NullPointerException if {@code message} is {@code null} */ public RejectedSetupException(String message) { - super(message); + this(message, null); } /** @@ -43,14 +43,8 @@ public RejectedSetupException(String message) { * * @param message the message * @param cause the cause of this exception - * @throws NullPointerException if {@code message} or {@code cause} is {@code null} */ - public RejectedSetupException(String message, Throwable cause) { - super(message, cause); - } - - @Override - public int errorCode() { - return ErrorType.REJECTED_SETUP; + public RejectedSetupException(String message, @Nullable Throwable cause) { + super(ErrorFrameFlyweight.REJECTED_SETUP, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/SetupException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/SetupException.java index 2111a51b1..712508f0b 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/SetupException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/SetupException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,9 @@ package io.rsocket.exceptions; +import io.rsocket.frame.ErrorFrameFlyweight; +import javax.annotation.Nullable; + /** The root of the setup exception hierarchy. */ public abstract class SetupException extends RSocketException { @@ -25,10 +28,11 @@ public abstract class SetupException extends RSocketException { * Constructs a new exception with the specified message. * * @param message the message - * @throws NullPointerException if {@code message} is {@code null} + * @deprecated please use {@link #SetupException(int, String, Throwable)} */ + @Deprecated public SetupException(String message) { - super(message); + this(message, null); } /** @@ -36,9 +40,21 @@ public SetupException(String message) { * * @param message the message * @param cause the cause of this exception - * @throws NullPointerException if {@code message} or {@code cause} is {@code null} + * @deprecated please use {@link #SetupException(int, String, Throwable)} + */ + @Deprecated + public SetupException(String message, @Nullable Throwable cause) { + this(ErrorFrameFlyweight.INVALID_SETUP, message, cause); + } + + /** + * Constructs a new exception with the specified error code, message and cause. + * + * @param errorCode the RSocket protocol code + * @param message the message + * @param cause the cause of this exception */ - public SetupException(String message, Throwable cause) { - super(message, cause); + public SetupException(int errorCode, String message, @Nullable Throwable cause) { + super(errorCode, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/UnsupportedSetupException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/UnsupportedSetupException.java index 7d14bc5d2..b112b95be 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/UnsupportedSetupException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/UnsupportedSetupException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,8 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorType; +import io.rsocket.frame.ErrorFrameFlyweight; +import javax.annotation.Nullable; /** * Some (or all) of the parameters specified by the client are unsupported by the server. @@ -32,10 +33,9 @@ public final class UnsupportedSetupException extends SetupException { * Constructs a new exception with the specified message. * * @param message the message - * @throws NullPointerException if {@code message} is {@code null} */ public UnsupportedSetupException(String message) { - super(message); + this(message, null); } /** @@ -43,14 +43,8 @@ public UnsupportedSetupException(String message) { * * @param message the message * @param cause the cause of this exception - * @throws NullPointerException if {@code message} or {@code cause} is {@code null} */ - public UnsupportedSetupException(String message, Throwable cause) { - super(message, cause); - } - - @Override - public int errorCode() { - return ErrorType.UNSUPPORTED_SETUP; + public UnsupportedSetupException(String message, @Nullable Throwable cause) { + super(ErrorFrameFlyweight.UNSUPPORTED_SETUP, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/ErrorFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/ErrorFrameFlyweight.java index df9d39ba8..ab26233f1 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/ErrorFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/ErrorFrameFlyweight.java @@ -3,7 +3,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufUtil; -import io.rsocket.exceptions.RSocketException; +import io.rsocket.RSocketErrorException; import java.nio.charset.StandardCharsets; public class ErrorFrameFlyweight { @@ -28,7 +28,10 @@ public static ByteBuf encode( ByteBufAllocator allocator, int streamId, Throwable t, ByteBuf data) { ByteBuf header = FrameHeaderFlyweight.encode(allocator, streamId, FrameType.ERROR, 0); - int errorCode = errorCodeFromException(t); + int errorCode = + t instanceof RSocketErrorException + ? ((RSocketErrorException) t).errorCode() + : APPLICATION_ERROR; header.writeInt(errorCode); @@ -41,14 +44,6 @@ public static ByteBuf encode(ByteBufAllocator allocator, int streamId, Throwable return encode(allocator, streamId, t, data); } - public static int errorCodeFromException(Throwable t) { - if (t instanceof RSocketException) { - return ((RSocketException) t).errorCode(); - } - - return APPLICATION_ERROR; - } - public static int errorCode(ByteBuf byteBuf) { byteBuf.markReaderIndex(); byteBuf.skipBytes(FrameHeaderFlyweight.size()); diff --git a/rsocket-core/src/main/java/io/rsocket/frame/ErrorType.java b/rsocket-core/src/main/java/io/rsocket/frame/ErrorType.java index ffd99930d..b41a5d59e 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/ErrorType.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/ErrorType.java @@ -5,7 +5,9 @@ * * @see Error * Codes + * @deprecated please use constants in {@link ErrorFrameFlyweight}. */ +@Deprecated public final class ErrorType { /** diff --git a/rsocket-core/src/main/java/io/rsocket/internal/ClientSetup.java b/rsocket-core/src/main/java/io/rsocket/internal/ClientSetup.java deleted file mode 100644 index 38217bdc2..000000000 --- a/rsocket-core/src/main/java/io/rsocket/internal/ClientSetup.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2015-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.internal; - -import static io.rsocket.keepalive.KeepAliveHandler.*; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.Unpooled; -import io.rsocket.DuplexConnection; -import io.rsocket.keepalive.KeepAliveHandler; -import io.rsocket.resume.ClientRSocketSession; -import io.rsocket.resume.ResumableDuplexConnection; -import io.rsocket.resume.ResumableFramesStore; -import io.rsocket.resume.ResumeStrategy; -import java.time.Duration; -import java.util.function.Supplier; -import reactor.core.publisher.Mono; - -public interface ClientSetup { - - DuplexConnection connection(); - - KeepAliveHandler keepAliveHandler(); - - ByteBuf resumeToken(); - - class DefaultClientSetup implements ClientSetup { - private final DuplexConnection connection; - - public DefaultClientSetup(DuplexConnection connection) { - this.connection = connection; - } - - @Override - public DuplexConnection connection() { - return connection; - } - - @Override - public KeepAliveHandler keepAliveHandler() { - return new DefaultKeepAliveHandler(connection); - } - - @Override - public ByteBuf resumeToken() { - return Unpooled.EMPTY_BUFFER; - } - } - - class ResumableClientSetup implements ClientSetup { - private final ByteBuf resumeToken; - private final ResumableDuplexConnection duplexConnection; - private final ResumableKeepAliveHandler keepAliveHandler; - - public ResumableClientSetup( - ByteBufAllocator allocator, - DuplexConnection connection, - Mono newConnectionFactory, - ByteBuf resumeToken, - ResumableFramesStore resumableFramesStore, - Duration resumeSessionDuration, - Duration resumeStreamTimeout, - Supplier resumeStrategySupplier, - boolean cleanupStoreOnKeepAlive) { - - ClientRSocketSession rSocketSession = - new ClientRSocketSession( - connection, - allocator, - resumeSessionDuration, - resumeStrategySupplier, - resumableFramesStore, - resumeStreamTimeout, - cleanupStoreOnKeepAlive) - .continueWith(newConnectionFactory) - .resumeToken(resumeToken); - this.duplexConnection = rSocketSession.resumableConnection(); - this.keepAliveHandler = new ResumableKeepAliveHandler(duplexConnection); - this.resumeToken = resumeToken; - } - - @Override - public DuplexConnection connection() { - return duplexConnection; - } - - @Override - public KeepAliveHandler keepAliveHandler() { - return keepAliveHandler; - } - - @Override - public ByteBuf resumeToken() { - return resumeToken; - } - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/MissingLeaseException.java b/rsocket-core/src/main/java/io/rsocket/lease/MissingLeaseException.java similarity index 60% rename from rsocket-core/src/main/java/io/rsocket/exceptions/MissingLeaseException.java rename to rsocket-core/src/main/java/io/rsocket/lease/MissingLeaseException.java index 4bd6ffb99..734d16d07 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/MissingLeaseException.java +++ b/rsocket-core/src/main/java/io/rsocket/lease/MissingLeaseException.java @@ -1,6 +1,21 @@ -package io.rsocket.exceptions; +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.rsocket.lease; -import io.rsocket.lease.Lease; +import io.rsocket.exceptions.RejectedException; import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/rsocket-core/src/main/java/io/rsocket/lease/RequesterLeaseHandler.java b/rsocket-core/src/main/java/io/rsocket/lease/RequesterLeaseHandler.java index ca2111e87..dd4247090 100644 --- a/rsocket-core/src/main/java/io/rsocket/lease/RequesterLeaseHandler.java +++ b/rsocket-core/src/main/java/io/rsocket/lease/RequesterLeaseHandler.java @@ -18,7 +18,6 @@ import io.netty.buffer.ByteBuf; import io.rsocket.Availability; -import io.rsocket.exceptions.MissingLeaseException; import io.rsocket.frame.LeaseFrameFlyweight; import java.util.function.Consumer; import reactor.core.Disposable; diff --git a/rsocket-core/src/main/java/io/rsocket/lease/ResponderLeaseHandler.java b/rsocket-core/src/main/java/io/rsocket/lease/ResponderLeaseHandler.java index c517a55c4..5ca745ee7 100644 --- a/rsocket-core/src/main/java/io/rsocket/lease/ResponderLeaseHandler.java +++ b/rsocket-core/src/main/java/io/rsocket/lease/ResponderLeaseHandler.java @@ -19,7 +19,6 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.rsocket.Availability; -import io.rsocket.exceptions.MissingLeaseException; import io.rsocket.frame.LeaseFrameFlyweight; import java.util.Optional; import java.util.function.Consumer; diff --git a/rsocket-core/src/main/java/io/rsocket/util/ConnectionUtils.java b/rsocket-core/src/main/java/io/rsocket/util/ConnectionUtils.java deleted file mode 100644 index dd8bbf907..000000000 --- a/rsocket-core/src/main/java/io/rsocket/util/ConnectionUtils.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.rsocket.util; - -import io.netty.buffer.ByteBufAllocator; -import io.rsocket.frame.ErrorFrameFlyweight; -import io.rsocket.internal.ClientServerInputMultiplexer; -import reactor.core.publisher.Mono; - -public class ConnectionUtils { - - public static Mono sendError( - ByteBufAllocator allocator, ClientServerInputMultiplexer multiplexer, Exception exception) { - return multiplexer - .asSetupConnection() - .sendOne(ErrorFrameFlyweight.encode(allocator, 0, exception)) - .onErrorResume(err -> Mono.empty()); - } -} diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java index 0a7f7a196..a9076428c 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java @@ -27,7 +27,6 @@ import io.netty.buffer.UnpooledByteBufAllocator; import io.rsocket.*; import io.rsocket.exceptions.Exceptions; -import io.rsocket.exceptions.MissingLeaseException; import io.rsocket.frame.FrameHeaderFlyweight; import io.rsocket.frame.FrameType; import io.rsocket.frame.LeaseFrameFlyweight; @@ -35,6 +34,7 @@ import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.internal.ClientServerInputMultiplexer; import io.rsocket.lease.*; +import io.rsocket.lease.MissingLeaseException; import io.rsocket.plugins.PluginRegistry; import io.rsocket.test.util.TestClientTransport; import io.rsocket.test.util.TestDuplexConnection; diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java index edcc8971f..376614070 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -117,8 +117,7 @@ public Mono requestResponse(Payload payload) { // Client sees error through normal API rule.assertNoClientErrors(); - rule.assertServerError( - "io.rsocket.exceptions.CustomRSocketException: Deliberate Custom exception."); + rule.assertServerError("CustomRSocketException (0x501): Deliberate Custom exception."); } @Test(timeout = 2000) diff --git a/rsocket-core/src/test/java/io/rsocket/exceptions/RSocketExceptionTest.java b/rsocket-core/src/test/java/io/rsocket/exceptions/RSocketExceptionTest.java index 8c39e8250..ccf7649d2 100644 --- a/rsocket-core/src/test/java/io/rsocket/exceptions/RSocketExceptionTest.java +++ b/rsocket-core/src/test/java/io/rsocket/exceptions/RSocketExceptionTest.java @@ -17,27 +17,22 @@ package io.rsocket.exceptions; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatNullPointerException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; interface RSocketExceptionTest { - @DisplayName("constructor throws NullPointerException with null message") + @DisplayName("constructor does not throw NullPointerException with null message") @Test default void constructorWithNullMessage() { - assertThatNullPointerException() - .isThrownBy(() -> getException(null)) - .withMessage("message must not be null"); + assertThat(getException(null)).hasMessage(null); } - @DisplayName("constructor throws NullPointerException with null message and cause") + @DisplayName("constructor does not throw NullPointerException with null message and cause") @Test default void constructorWithNullMessageAndCause() { - assertThatNullPointerException() - .isThrownBy(() -> getException(null, new Exception())) - .withMessage("message must not be null"); + assertThat(getException(null)).hasMessage(null); } @DisplayName("errorCode returns specified value") From 0ac54d4bf41a66363648ca89b6e397b90bd4dd12 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 15 Apr 2020 17:56:37 +0100 Subject: [PATCH 15/62] RSocketFactory API enhancements (#780) This commit adds RSocketConnector and RSocketServer (io.rsocket.core) as replacements for the now deprecated RSocketFactory (io.rsocket). A key goal for the change is to avoid a class in the top-level package with references to sub-packages which leads to package cycles, but also an opportunity to review and improve the API. Closes gh-778 Signed-off-by: Rossen Stoyanchev --- README.md | 31 +- .../main/java/io/rsocket/RSocketFactory.java | 402 ++++++++++++---- .../core/DefaultClientRSocketFactory.java | 436 ------------------ .../core/DefaultServerRSocketFactory.java | 377 --------------- .../io/rsocket/core/RSocketConnector.java | 375 +++++++++++++++ .../java/io/rsocket/core/RSocketServer.java | 299 ++++++++++++ .../src/main/java/io/rsocket/core/Resume.java | 96 ++++ .../ClientServerInputMultiplexer.java | 17 +- .../InitializingInterceptorRegistry.java | 52 +++ .../rsocket/plugins/InterceptorRegistry.java | 83 ++++ .../io/rsocket/plugins/PluginRegistry.java | 111 ----- .../main/java/io/rsocket/plugins/Plugins.java | 40 -- .../io/rsocket/core/RSocketLeaseTest.java | 22 +- .../io/rsocket/core/RSocketReconnectTest.java | 41 +- .../core/RSocketServerFragmentationTest.java | 43 ++ .../io/rsocket/core/SetupRejectionTest.java | 2 +- .../ClientServerInputMultiplexerTest.java | 8 +- .../tcp/channel/ChannelEchoClient.java | 64 +-- .../transport/tcp/duplex/DuplexClient.java | 37 +- .../transport/tcp/lease/LeaseExample.java | 33 +- .../tcp/requestresponse/HelloWorldClient.java | 16 +- .../tcp/resume/ResumeFileTransfer.java | 44 +- .../transport/tcp/stream/StreamingClient.java | 21 +- .../transport/ws/WebSocketHeadersSample.java | 29 +- .../rsocket/integration/IntegrationTest.java | 72 ++- .../integration/InteractionsLoadTest.java | 20 +- .../integration/TcpIntegrationTest.java | 15 +- .../rsocket/integration/TestingStreaming.java | 153 +++--- .../rsocket/resume/ResumeIntegrationTest.java | 65 ++- .../java/io/rsocket/test/ClientSetupRule.java | 15 +- .../java/io/rsocket/test/TransportTest.java | 16 +- .../transport/local/LocalPingPong.java | 20 +- .../io/rsocket/integration/FragmentTest.java | 16 +- ...actoryNettyTransportFragmentationTest.java | 91 +--- .../transport/netty/SetupRejectionTest.java | 15 +- .../io/rsocket/transport/netty/TcpPing.java | 16 +- .../transport/netty/TcpPongServer.java | 15 +- .../WebSocketTransportIntegrationTest.java | 12 +- .../transport/netty/WebsocketPing.java | 11 +- .../WebsocketPingPongIntegrationTest.java | 18 +- .../transport/netty/WebsocketPongServer.java | 12 +- 41 files changed, 1661 insertions(+), 1600 deletions(-) delete mode 100644 rsocket-core/src/main/java/io/rsocket/core/DefaultClientRSocketFactory.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/core/DefaultServerRSocketFactory.java create mode 100644 rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java create mode 100644 rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java create mode 100644 rsocket-core/src/main/java/io/rsocket/core/Resume.java create mode 100644 rsocket-core/src/main/java/io/rsocket/plugins/InitializingInterceptorRegistry.java create mode 100644 rsocket-core/src/main/java/io/rsocket/plugins/InterceptorRegistry.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/plugins/PluginRegistry.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/plugins/Plugins.java create mode 100644 rsocket-core/src/test/java/io/rsocket/core/RSocketServerFragmentationTest.java diff --git a/README.md b/README.md index 173c3e1ad..bb427cc35 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,8 @@ Example: ```groovy dependencies { - implementation 'io.rsocket:rsocket-core:1.0.0-RC3' - implementation 'io.rsocket:rsocket-transport-netty:1.0.0-RC3' + implementation 'io.rsocket:rsocket-core:1.0.0-RC6' + implementation 'io.rsocket:rsocket-transport-netty:1.0.0-RC6' // implementation 'io.rsocket:rsocket-core:1.0.0-RC4-SNAPSHOT' // implementation 'io.rsocket:rsocket-transport-netty:1.0.0-RC4-SNAPSHOT' } @@ -57,7 +57,7 @@ package io.rsocket.transport.netty; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; +import io.rsocket.core.RSocketConnector; import io.rsocket.transport.netty.client.WebsocketClientTransport; import io.rsocket.util.DefaultPayload; import reactor.core.publisher.Flux; @@ -67,14 +67,14 @@ import java.net.URI; public class ExampleClient { public static void main(String[] args) { WebsocketClientTransport ws = WebsocketClientTransport.create(URI.create("ws://rsocket-demo.herokuapp.com/ws")); - RSocket client = RSocketFactory.connect().keepAlive().transport(ws).start().block(); + RSocket clientRSocket = RSocketConnector.connectWith(ws).block(); try { - Flux s = client.requestStream(DefaultPayload.create("peace")); + Flux s = clientRSocket.requestStream(DefaultPayload.create("peace")); s.take(10).doOnNext(p -> System.out.println(p.getDataUtf8())).blockLast(); } finally { - client.dispose(); + clientRSocket.dispose(); } } } @@ -89,12 +89,10 @@ or you will get a memory leak. Used correctly this will reduce latency and incre ### Example Server setup ```java -RSocketFactory.receive() +RSocketServer.create(new PingHandler()) // Enable Zero Copy - .frameDecoder(PayloadDecoder.ZERO_COPY) - .acceptor(new PingHandler()) - .transport(TcpServerTransport.create(7878)) - .start() + .payloadDecoder(PayloadDecoder.ZERO_COPY) + .bind(TcpServerTransport.create(7878)) .block() .onClose() .block(); @@ -102,12 +100,13 @@ RSocketFactory.receive() ### Example Client setup ```java -Mono client = - RSocketFactory.connect() +RSocket clientRSocket = + RSocketConnector.create() // Enable Zero Copy - .frameDecoder(PayloadDecoder.ZERO_COPY) - .transport(TcpClientTransport.create(7878)) - .start(); + .payloadDecoder(PayloadDecoder.ZERO_COPY) + .connect(TcpClientTransport.create(7878)) + .start() + .block(); ``` ## Bugs and Feedback diff --git a/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java b/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java index 0d83d06dc..d488d2c08 100644 --- a/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java +++ b/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java @@ -17,6 +17,9 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; +import io.rsocket.core.Resume; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.lease.LeaseStats; import io.rsocket.lease.Leases; @@ -27,7 +30,6 @@ import io.rsocket.resume.ResumeStrategy; import io.rsocket.transport.ClientTransport; import io.rsocket.transport.ServerTransport; -import java.lang.reflect.Constructor; import java.time.Duration; import java.util.function.Consumer; import java.util.function.Function; @@ -44,76 +46,171 @@ *
  • {@link ServerRSocketFactory} to start a server. Use {@link #receive()} for a default * instance. * + * + * @deprecated please use {@link RSocketConnector} and {@link RSocketServer}. */ +@Deprecated public final class RSocketFactory { - private static final Constructor clientFactoryConstructor = - getConstructorFor("io.rsocket.core.DefaultClientRSocketFactory"); - - private static final Constructor serverFactoryConstructor = - getConstructorFor("io.rsocket.core.DefaultServerRSocketFactory"); - /** - * Create a {@link ClientRSocketFactory} to connect to a remote RSocket endpoint. A shortcut for - * creating {@link io.rsocket.core.DefaultClientRSocketFactory}. + * Create a {@code ClientRSocketFactory} to connect to a remote RSocket endpoint. Internally + * delegates to {@link RSocketConnector}. * * @return the {@code ClientRSocketFactory} instance */ public static ClientRSocketFactory connect() { - try { - // Avoid explicit dependency and a package cycle - return (ClientRSocketFactory) clientFactoryConstructor.newInstance(); - } catch (Exception ex) { - throw new IllegalStateException("Failed to create ClientRSocketFactory", ex); - } + return new ClientRSocketFactory(); } /** - * Create a {@link ServerRSocketFactory} to accept connections from RSocket clients. A shortcut - * for creating {@link io.rsocket.core.DefaultServerRSocketFactory}. + * Create a {@code ServerRSocketFactory} to accept connections from RSocket clients. Internally + * delegates to {@link RSocketServer}. * * @return the {@code ClientRSocketFactory} instance */ public static ServerRSocketFactory receive() { - try { - // Avoid explicit dependency and a package cycle - return (ServerRSocketFactory) serverFactoryConstructor.newInstance(); - } catch (Exception ex) { - throw new IllegalStateException("Failed to create ServerRSocketFactory", ex); + return new ServerRSocketFactory(); + } + + public interface Start { + Mono start(); + } + + public interface ClientTransportAcceptor { + Start transport(Supplier transport); + + default Start transport(ClientTransport transport) { + return transport(() -> transport); } } + + public interface ServerTransportAcceptor { + + ServerTransport.ConnectionAcceptor toConnectionAcceptor(); + + Start transport(Supplier> transport); + + default Start transport(ServerTransport transport) { + return transport(() -> transport); + } + } + /** Factory to create and configure an RSocket client, and connect to a server. */ - public interface ClientRSocketFactory extends ClientTransportAcceptor { + public static class ClientRSocketFactory implements ClientTransportAcceptor { + private final RSocketConnector connector = RSocketConnector.create(); - ClientRSocketFactory byteBufAllocator(ByteBufAllocator allocator); + private Duration tickPeriod = Duration.ofSeconds(20); + private Duration ackTimeout = Duration.ofSeconds(30); + private int missedAcks = 3; - ClientRSocketFactory addConnectionPlugin(DuplexConnectionInterceptor interceptor); + private Resume resume; - ClientRSocketFactory addRequesterPlugin(RSocketInterceptor interceptor); + public ClientRSocketFactory byteBufAllocator(ByteBufAllocator allocator) { + connector.byteBufAllocator(allocator); + return this; + } - ClientRSocketFactory addResponderPlugin(RSocketInterceptor interceptor); + public ClientRSocketFactory addConnectionPlugin(DuplexConnectionInterceptor interceptor) { + connector.interceptors(registry -> registry.forConnection(interceptor)); + return this; + } - ClientRSocketFactory addSocketAcceptorPlugin(SocketAcceptorInterceptor interceptor); + /** Deprecated. Use {@link #addRequesterPlugin(RSocketInterceptor)} instead */ + @Deprecated + public ClientRSocketFactory addClientPlugin(RSocketInterceptor interceptor) { + return addRequesterPlugin(interceptor); + } - ClientRSocketFactory keepAlive(Duration tickPeriod, Duration ackTimeout, int missedAcks); + public ClientRSocketFactory addRequesterPlugin(RSocketInterceptor interceptor) { + connector.interceptors(registry -> registry.forRequester(interceptor)); + return this; + } - ClientRSocketFactory keepAliveTickPeriod(Duration tickPeriod); + /** Deprecated. Use {@link #addResponderPlugin(RSocketInterceptor)} instead */ + @Deprecated + public ClientRSocketFactory addServerPlugin(RSocketInterceptor interceptor) { + return addResponderPlugin(interceptor); + } - ClientRSocketFactory keepAliveAckTimeout(Duration ackTimeout); + public ClientRSocketFactory addResponderPlugin(RSocketInterceptor interceptor) { + connector.interceptors(registry -> registry.forResponder(interceptor)); + return this; + } - ClientRSocketFactory keepAliveMissedAcks(int missedAcks); + public ClientRSocketFactory addSocketAcceptorPlugin(SocketAcceptorInterceptor interceptor) { + connector.interceptors(registry -> registry.forSocketAcceptor(interceptor)); + return this; + } - ClientRSocketFactory mimeType(String metadataMimeType, String dataMimeType); + /** + * Deprecated without replacement as Keep-Alive is not optional according to spec + * + * @return this ClientRSocketFactory + */ + @Deprecated + public ClientRSocketFactory keepAlive() { + connector.keepAlive(tickPeriod, ackTimeout.plus(tickPeriod.multipliedBy(missedAcks))); + return this; + } - ClientRSocketFactory dataMimeType(String dataMimeType); + public ClientTransportAcceptor keepAlive( + Duration tickPeriod, Duration ackTimeout, int missedAcks) { + this.tickPeriod = tickPeriod; + this.ackTimeout = ackTimeout; + this.missedAcks = missedAcks; + keepAlive(); + return this; + } - ClientRSocketFactory metadataMimeType(String metadataMimeType); + public ClientRSocketFactory keepAliveTickPeriod(Duration tickPeriod) { + this.tickPeriod = tickPeriod; + keepAlive(); + return this; + } - ClientRSocketFactory lease(Supplier> leasesSupplier); + public ClientRSocketFactory keepAliveAckTimeout(Duration ackTimeout) { + this.ackTimeout = ackTimeout; + keepAlive(); + return this; + } - ClientRSocketFactory lease(); + public ClientRSocketFactory keepAliveMissedAcks(int missedAcks) { + this.missedAcks = missedAcks; + keepAlive(); + return this; + } + + public ClientRSocketFactory mimeType(String metadataMimeType, String dataMimeType) { + connector.metadataMimeType(metadataMimeType); + connector.dataMimeType(dataMimeType); + return this; + } - ClientRSocketFactory singleSubscriberRequester(); + public ClientRSocketFactory dataMimeType(String dataMimeType) { + connector.dataMimeType(dataMimeType); + return this; + } + + public ClientRSocketFactory metadataMimeType(String metadataMimeType) { + connector.metadataMimeType(metadataMimeType); + return this; + } + + public ClientRSocketFactory lease(Supplier> supplier) { + connector.lease(supplier); + return this; + } + + public ClientRSocketFactory lease() { + connector.lease(Leases::new); + return this; + } + + /** @deprecated without a replacement and no longer used. */ + @Deprecated + public ClientRSocketFactory singleSubscriberRequester() { + return this; + } /** * Enables a reconnectable, shared instance of {@code Mono} so every subscriber will @@ -124,7 +221,6 @@ public interface ClientRSocketFactory extends ClientTransportAcceptor { * Mono sharedRSocketMono = * RSocketFactory * .connect() - * .singleSubscriberRequester() * .reconnect(Retry.fixedDelay(3, Duration.ofSeconds(1))) * .transport(transport) * .start(); @@ -144,7 +240,6 @@ public interface ClientRSocketFactory extends ClientTransportAcceptor { * Mono sharedRSocketMono = * RSocketFactory * .connect() - * .singleSubscriberRequester() * .reconnect(Retry.fixedDelay(3, Duration.ofSeconds(1))) * .transport(transport) * .start(); @@ -175,7 +270,6 @@ public interface ClientRSocketFactory extends ClientTransportAcceptor { * Mono sharedRSocketMono = * RSocketFactory * .connect() - * .singleSubscriberRequester() * .reconnect(Retry.fixedDelay(3, Duration.ofSeconds(1))) * .transport(transport) * .start(); @@ -189,108 +283,212 @@ public interface ClientRSocketFactory extends ClientTransportAcceptor { * @param retrySpec a retry factory applied for {@link Mono#retryWhen(Retry)} * @return a shared instance of {@code Mono}. */ - ClientRSocketFactory reconnect(Retry retrySpec); + public ClientRSocketFactory reconnect(Retry retrySpec) { + connector.reconnect(retrySpec); + return this; + } - ClientRSocketFactory resume(); + public ClientRSocketFactory resume() { + resume = resume != null ? resume : new Resume(); + connector.resume(resume); + return this; + } - ClientRSocketFactory resumeToken(Supplier resumeTokenSupplier); + public ClientRSocketFactory resumeToken(Supplier supplier) { + resume(); + resume.token(supplier); + return this; + } - ClientRSocketFactory resumeStore( - Function resumeStoreFactory); + public ClientRSocketFactory resumeStore( + Function storeFactory) { + resume(); + resume.storeFactory(storeFactory); + return this; + } - ClientRSocketFactory resumeSessionDuration(Duration sessionDuration); + public ClientRSocketFactory resumeSessionDuration(Duration sessionDuration) { + resume(); + resume.sessionDuration(sessionDuration); + return this; + } - ClientRSocketFactory resumeStreamTimeout(Duration resumeStreamTimeout); + public ClientRSocketFactory resumeStreamTimeout(Duration streamTimeout) { + resume(); + resume.streamTimeout(streamTimeout); + return this; + } - ClientRSocketFactory resumeStrategy(Supplier resumeStrategy); + public ClientRSocketFactory resumeStrategy(Supplier resumeStrategy) { + resume(); + resume.resumeStrategy(resumeStrategy); + return this; + } - ClientRSocketFactory resumeCleanupOnKeepAlive(); + public ClientRSocketFactory resumeCleanupOnKeepAlive() { + resume(); + resume.cleanupStoreOnKeepAlive(); + return this; + } - @Override - Start transport(Supplier transportClient); + public Start transport(Supplier transport) { + return () -> connector.connect(transport); + } - ClientTransportAcceptor acceptor(Function acceptor); + public ClientTransportAcceptor acceptor(Function acceptor) { + return acceptor(() -> acceptor); + } - ClientTransportAcceptor acceptor(Supplier> acceptor); + public ClientTransportAcceptor acceptor(Supplier> acceptorSupplier) { + return acceptor( + (setup, sendingSocket) -> { + acceptorSupplier.get().apply(sendingSocket); + return Mono.empty(); + }); + } - ClientTransportAcceptor acceptor(SocketAcceptor acceptor); + public ClientTransportAcceptor acceptor(SocketAcceptor acceptor) { + connector.acceptor(acceptor); + return this; + } - ClientRSocketFactory fragment(int mtu); + public ClientRSocketFactory fragment(int mtu) { + connector.fragment(mtu); + return this; + } - ClientRSocketFactory errorConsumer(Consumer errorConsumer); + public ClientRSocketFactory errorConsumer(Consumer errorConsumer) { + connector.errorConsumer(errorConsumer); + return this; + } - ClientRSocketFactory setupPayload(Payload payload); + public ClientRSocketFactory setupPayload(Payload payload) { + connector.setupPayload(payload); + return this; + } - ClientRSocketFactory frameDecoder(PayloadDecoder payloadDecoder); + public ClientRSocketFactory frameDecoder(PayloadDecoder payloadDecoder) { + connector.payloadDecoder(payloadDecoder); + return this; + } } /** Factory to create, configure, and start an RSocket server. */ - public interface ServerRSocketFactory { - ServerRSocketFactory byteBufAllocator(ByteBufAllocator allocator); - - ServerRSocketFactory addConnectionPlugin(DuplexConnectionInterceptor interceptor); - - ServerRSocketFactory addRequesterPlugin(RSocketInterceptor interceptor); + public static class ServerRSocketFactory implements ServerTransportAcceptor { + private final RSocketServer server = RSocketServer.create(); - ServerRSocketFactory addResponderPlugin(RSocketInterceptor interceptor); + private Resume resume; - ServerRSocketFactory addSocketAcceptorPlugin(SocketAcceptorInterceptor interceptor); - - ServerTransportAcceptor acceptor(SocketAcceptor acceptor); + public ServerRSocketFactory byteBufAllocator(ByteBufAllocator allocator) { + server.byteBufAllocator(allocator); + return this; + } - ServerRSocketFactory frameDecoder(PayloadDecoder payloadDecoder); + public ServerRSocketFactory addConnectionPlugin(DuplexConnectionInterceptor interceptor) { + server.interceptors(registry -> registry.forConnection(interceptor)); + return this; + } + /** Deprecated. Use {@link #addRequesterPlugin(RSocketInterceptor)} instead */ + @Deprecated + public ServerRSocketFactory addClientPlugin(RSocketInterceptor interceptor) { + return addRequesterPlugin(interceptor); + } - ServerRSocketFactory fragment(int mtu); + public ServerRSocketFactory addRequesterPlugin(RSocketInterceptor interceptor) { + server.interceptors(registry -> registry.forRequester(interceptor)); + return this; + } - ServerRSocketFactory errorConsumer(Consumer errorConsumer); + /** Deprecated. Use {@link #addResponderPlugin(RSocketInterceptor)} instead */ + @Deprecated + public ServerRSocketFactory addServerPlugin(RSocketInterceptor interceptor) { + return addResponderPlugin(interceptor); + } - ServerRSocketFactory lease(Supplier> leasesSupplier); + public ServerRSocketFactory addResponderPlugin(RSocketInterceptor interceptor) { + server.interceptors(registry -> registry.forResponder(interceptor)); + return this; + } - ServerRSocketFactory lease(); + public ServerRSocketFactory addSocketAcceptorPlugin(SocketAcceptorInterceptor interceptor) { + server.interceptors(registry -> registry.forSocketAcceptor(interceptor)); + return this; + } - ServerRSocketFactory singleSubscriberRequester(); + public ServerTransportAcceptor acceptor(SocketAcceptor acceptor) { + return this; + } - ServerRSocketFactory resume(); + public ServerRSocketFactory frameDecoder(PayloadDecoder payloadDecoder) { + server.payloadDecoder(payloadDecoder); + return this; + } - ServerRSocketFactory resumeStore( - Function resumeStoreFactory); + public ServerRSocketFactory fragment(int mtu) { + server.fragment(mtu); + return this; + } - ServerRSocketFactory resumeSessionDuration(Duration sessionDuration); + public ServerRSocketFactory errorConsumer(Consumer errorConsumer) { + server.errorConsumer(errorConsumer); + return this; + } - ServerRSocketFactory resumeStreamTimeout(Duration resumeStreamTimeout); + public ServerRSocketFactory lease(Supplier> supplier) { + server.lease(supplier); + return this; + } - ServerRSocketFactory resumeCleanupOnKeepAlive(); - } + public ServerRSocketFactory lease() { + server.lease(Leases::new); + return this; + } - public interface ClientTransportAcceptor { - Start transport(Supplier transport); + /** @deprecated without a replacement and no longer used. */ + @Deprecated + public ServerRSocketFactory singleSubscriberRequester() { + return this; + } - default Start transport(ClientTransport transport) { - return transport(() -> transport); + public ServerRSocketFactory resume() { + resume = resume != null ? resume : new Resume(); + server.resume(resume); + return this; } - } - public interface ServerTransportAcceptor { + public ServerRSocketFactory resumeStore( + Function storeFactory) { + resume(); + resume.storeFactory(storeFactory); + return this; + } - ServerTransport.ConnectionAcceptor toConnectionAcceptor(); + public ServerRSocketFactory resumeSessionDuration(Duration sessionDuration) { + resume(); + resume.sessionDuration(sessionDuration); + return this; + } - Start transport(Supplier> transport); + public ServerRSocketFactory resumeStreamTimeout(Duration streamTimeout) { + resume(); + resume.streamTimeout(streamTimeout); + return this; + } - default Start transport(ServerTransport transport) { - return transport(() -> transport); + public ServerRSocketFactory resumeCleanupOnKeepAlive() { + resume(); + resume.cleanupStoreOnKeepAlive(); + return this; } - } - public interface Start { - Mono start(); - } + @Override + public ServerTransport.ConnectionAcceptor toConnectionAcceptor() { + return server.asConnectionAcceptor(); + } - private static Constructor getConstructorFor(String className) { - try { - Class clazz = Class.forName(className); - return clazz.getDeclaredConstructor(); - } catch (Throwable ex) { - throw new IllegalStateException("No " + className); + @Override + public Start transport(Supplier> transport) { + return () -> server.bind(transport.get()); } } } diff --git a/rsocket-core/src/main/java/io/rsocket/core/DefaultClientRSocketFactory.java b/rsocket-core/src/main/java/io/rsocket/core/DefaultClientRSocketFactory.java deleted file mode 100644 index 2ecde3663..000000000 --- a/rsocket-core/src/main/java/io/rsocket/core/DefaultClientRSocketFactory.java +++ /dev/null @@ -1,436 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.rsocket.core; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.Unpooled; -import io.rsocket.AbstractRSocket; -import io.rsocket.ConnectionSetupPayload; -import io.rsocket.DuplexConnection; -import io.rsocket.Payload; -import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; -import io.rsocket.SocketAcceptor; -import io.rsocket.frame.ResumeFrameFlyweight; -import io.rsocket.frame.SetupFrameFlyweight; -import io.rsocket.frame.decoder.PayloadDecoder; -import io.rsocket.internal.ClientServerInputMultiplexer; -import io.rsocket.keepalive.KeepAliveHandler; -import io.rsocket.lease.LeaseStats; -import io.rsocket.lease.Leases; -import io.rsocket.lease.RequesterLeaseHandler; -import io.rsocket.lease.ResponderLeaseHandler; -import io.rsocket.plugins.DuplexConnectionInterceptor; -import io.rsocket.plugins.PluginRegistry; -import io.rsocket.plugins.Plugins; -import io.rsocket.plugins.RSocketInterceptor; -import io.rsocket.plugins.SocketAcceptorInterceptor; -import io.rsocket.resume.ClientRSocketSession; -import io.rsocket.resume.ExponentialBackoffResumeStrategy; -import io.rsocket.resume.InMemoryResumableFramesStore; -import io.rsocket.resume.ResumableFramesStore; -import io.rsocket.resume.ResumeStrategy; -import io.rsocket.transport.ClientTransport; -import io.rsocket.util.EmptyPayload; -import io.rsocket.util.MultiSubscriberRSocket; -import java.time.Duration; -import java.util.Objects; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; -import reactor.core.Disposable; -import reactor.core.publisher.Mono; -import reactor.util.retry.Retry; - -/** - * Default implementation of {@link RSocketFactory.ClientRSocketFactory} that can be instantiated - * directly or through the shortcut {@link RSocketFactory#connect()}. - */ -public class DefaultClientRSocketFactory implements RSocketFactory.ClientRSocketFactory { - private static final String CLIENT_TAG = "client"; - - private static final BiConsumer INVALIDATE_FUNCTION = - (r, i) -> r.onClose().subscribe(null, null, i::invalidate); - - private SocketAcceptor acceptor = (setup, sendingSocket) -> Mono.just(new AbstractRSocket() {}); - - private Consumer errorConsumer = Throwable::printStackTrace; - private int mtu = 0; - private PluginRegistry plugins = new PluginRegistry(Plugins.defaultPlugins()); - - private Payload setupPayload = EmptyPayload.INSTANCE; - private PayloadDecoder payloadDecoder = PayloadDecoder.DEFAULT; - - private Duration tickPeriod = Duration.ofSeconds(20); - private Duration ackTimeout = Duration.ofSeconds(30); - private int missedAcks = 3; - - private String metadataMimeType = "application/binary"; - private String dataMimeType = "application/binary"; - - private boolean resumeEnabled; - private boolean resumeCleanupStoreOnKeepAlive; - private Supplier resumeTokenSupplier = ResumeFrameFlyweight::generateResumeToken; - private Function resumeStoreFactory = - token -> new InMemoryResumableFramesStore(CLIENT_TAG, 100_000); - private Duration resumeSessionDuration = Duration.ofMinutes(2); - private Duration resumeStreamTimeout = Duration.ofSeconds(10); - private Supplier resumeStrategySupplier = - () -> new ExponentialBackoffResumeStrategy(Duration.ofSeconds(1), Duration.ofSeconds(16), 2); - - private boolean multiSubscriberRequester = true; - private boolean leaseEnabled; - private Supplier> leasesSupplier = Leases::new; - private boolean reconnectEnabled; - private Retry retrySpec; - - private ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; - - @Override - public RSocketFactory.ClientRSocketFactory byteBufAllocator(ByteBufAllocator allocator) { - Objects.requireNonNull(allocator); - this.allocator = allocator; - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory addConnectionPlugin( - DuplexConnectionInterceptor interceptor) { - plugins.addConnectionPlugin(interceptor); - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory addRequesterPlugin(RSocketInterceptor interceptor) { - plugins.addRequesterPlugin(interceptor); - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory addResponderPlugin(RSocketInterceptor interceptor) { - plugins.addResponderPlugin(interceptor); - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory addSocketAcceptorPlugin( - SocketAcceptorInterceptor interceptor) { - plugins.addSocketAcceptorPlugin(interceptor); - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory keepAlive( - Duration tickPeriod, Duration ackTimeout, int missedAcks) { - this.tickPeriod = tickPeriod; - this.ackTimeout = ackTimeout; - this.missedAcks = missedAcks; - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory keepAliveTickPeriod(Duration tickPeriod) { - this.tickPeriod = tickPeriod; - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory keepAliveAckTimeout(Duration ackTimeout) { - this.ackTimeout = ackTimeout; - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory keepAliveMissedAcks(int missedAcks) { - this.missedAcks = missedAcks; - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory mimeType( - String metadataMimeType, String dataMimeType) { - this.dataMimeType = dataMimeType; - this.metadataMimeType = metadataMimeType; - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory dataMimeType(String dataMimeType) { - this.dataMimeType = dataMimeType; - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory metadataMimeType(String metadataMimeType) { - this.metadataMimeType = metadataMimeType; - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory lease( - Supplier> leasesSupplier) { - this.leaseEnabled = true; - this.leasesSupplier = Objects.requireNonNull(leasesSupplier); - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory lease() { - this.leaseEnabled = true; - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory singleSubscriberRequester() { - this.multiSubscriberRequester = false; - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory reconnect(Retry retrySpec) { - this.retrySpec = Objects.requireNonNull(retrySpec); - this.reconnectEnabled = true; - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory resume() { - this.resumeEnabled = true; - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory resumeToken(Supplier resumeTokenSupplier) { - this.resumeTokenSupplier = Objects.requireNonNull(resumeTokenSupplier); - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory resumeStore( - Function resumeStoreFactory) { - this.resumeStoreFactory = resumeStoreFactory; - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory resumeSessionDuration(Duration sessionDuration) { - this.resumeSessionDuration = Objects.requireNonNull(sessionDuration); - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory resumeStreamTimeout(Duration resumeStreamTimeout) { - this.resumeStreamTimeout = Objects.requireNonNull(resumeStreamTimeout); - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory resumeStrategy( - Supplier resumeStrategy) { - this.resumeStrategySupplier = Objects.requireNonNull(resumeStrategy); - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory resumeCleanupOnKeepAlive() { - resumeCleanupStoreOnKeepAlive = true; - return this; - } - - @Override - public RSocketFactory.Start transport(Supplier transportClient) { - return new StartClient(transportClient); - } - - @Override - public RSocketFactory.ClientTransportAcceptor acceptor(Function acceptor) { - return acceptor(() -> acceptor); - } - - @Override - public RSocketFactory.ClientTransportAcceptor acceptor( - Supplier> acceptor) { - return acceptor((setup, sendingSocket) -> Mono.just(acceptor.get().apply(sendingSocket))); - } - - @Override - public RSocketFactory.ClientTransportAcceptor acceptor(SocketAcceptor acceptor) { - this.acceptor = acceptor; - return StartClient::new; - } - - @Override - public RSocketFactory.ClientRSocketFactory fragment(int mtu) { - this.mtu = mtu; - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory errorConsumer(Consumer errorConsumer) { - this.errorConsumer = errorConsumer; - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory setupPayload(Payload payload) { - this.setupPayload = payload; - return this; - } - - @Override - public RSocketFactory.ClientRSocketFactory frameDecoder(PayloadDecoder payloadDecoder) { - this.payloadDecoder = payloadDecoder; - return this; - } - - private class StartClient implements RSocketFactory.Start { - private final Supplier transportClient; - - StartClient(Supplier transportClient) { - this.transportClient = transportClient; - } - - @Override - public Mono start() { - return newConnection() - .flatMap( - connection -> { - ByteBuf resumeToken; - KeepAliveHandler keepAliveHandler; - DuplexConnection wrappedConnection; - - if (resumeEnabled) { - resumeToken = resumeTokenSupplier.get(); - ClientRSocketSession session = - new ClientRSocketSession( - connection, - allocator, - resumeSessionDuration, - resumeStrategySupplier, - resumeStoreFactory.apply(resumeToken), - resumeStreamTimeout, - resumeCleanupStoreOnKeepAlive) - .continueWith(newConnection()) - .resumeToken(resumeToken); - keepAliveHandler = - new KeepAliveHandler.ResumableKeepAliveHandler(session.resumableConnection()); - wrappedConnection = session.resumableConnection(); - } else { - resumeToken = Unpooled.EMPTY_BUFFER; - keepAliveHandler = new KeepAliveHandler.DefaultKeepAliveHandler(connection); - wrappedConnection = connection; - } - - ClientServerInputMultiplexer multiplexer = - new ClientServerInputMultiplexer(wrappedConnection, plugins, true); - - boolean isLeaseEnabled = leaseEnabled; - Leases leases = leasesSupplier.get(); - RequesterLeaseHandler requesterLeaseHandler = - isLeaseEnabled - ? new RequesterLeaseHandler.Impl(CLIENT_TAG, leases.receiver()) - : RequesterLeaseHandler.None; - - RSocket rSocketRequester = - new RSocketRequester( - allocator, - multiplexer.asClientConnection(), - payloadDecoder, - errorConsumer, - StreamIdSupplier.clientSupplier(), - mtu, - keepAliveTickPeriod(), - keepAliveTimeout(), - keepAliveHandler, - requesterLeaseHandler); - - if (multiSubscriberRequester) { - rSocketRequester = new MultiSubscriberRSocket(rSocketRequester); - } - - RSocket wrappedRSocketRequester = plugins.applyRequester(rSocketRequester); - - ByteBuf setupFrame = - SetupFrameFlyweight.encode( - allocator, - isLeaseEnabled, - keepAliveTickPeriod(), - keepAliveTimeout(), - resumeToken, - metadataMimeType, - dataMimeType, - setupPayload); - - ConnectionSetupPayload setup = new DefaultConnectionSetupPayload(setupFrame); - - return plugins - .applySocketAcceptorInterceptor(acceptor) - .accept(setup, wrappedRSocketRequester) - .flatMap( - rSocketHandler -> { - RSocket wrappedRSocketHandler = plugins.applyResponder(rSocketHandler); - - ResponderLeaseHandler responderLeaseHandler = - isLeaseEnabled - ? new ResponderLeaseHandler.Impl<>( - CLIENT_TAG, - allocator, - leases.sender(), - errorConsumer, - leases.stats()) - : ResponderLeaseHandler.None; - - RSocket rSocketResponder = - new RSocketResponder( - allocator, - multiplexer.asServerConnection(), - wrappedRSocketHandler, - payloadDecoder, - errorConsumer, - responderLeaseHandler, - mtu); - - return wrappedConnection - .sendOne(setupFrame) - .thenReturn(wrappedRSocketRequester); - }); - }) - .as( - source -> { - if (reconnectEnabled) { - return new ReconnectMono<>( - source.retryWhen(retrySpec), Disposable::dispose, INVALIDATE_FUNCTION); - } else { - return source; - } - }); - } - - private int keepAliveTickPeriod() { - return (int) tickPeriod.toMillis(); - } - - private int keepAliveTimeout() { - return (int) (ackTimeout.toMillis() + tickPeriod.toMillis() * missedAcks); - } - - private Mono newConnection() { - return Mono.fromSupplier(transportClient).flatMap(t -> t.connect(mtu)); - } - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/core/DefaultServerRSocketFactory.java b/rsocket-core/src/main/java/io/rsocket/core/DefaultServerRSocketFactory.java deleted file mode 100644 index 73d3513da..000000000 --- a/rsocket-core/src/main/java/io/rsocket/core/DefaultServerRSocketFactory.java +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Copyright 2015-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.rsocket.core; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.rsocket.Closeable; -import io.rsocket.ConnectionSetupPayload; -import io.rsocket.DuplexConnection; -import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; -import io.rsocket.SocketAcceptor; -import io.rsocket.exceptions.InvalidSetupException; -import io.rsocket.exceptions.RejectedSetupException; -import io.rsocket.frame.FrameHeaderFlyweight; -import io.rsocket.frame.SetupFrameFlyweight; -import io.rsocket.frame.decoder.PayloadDecoder; -import io.rsocket.internal.ClientServerInputMultiplexer; -import io.rsocket.lease.Leases; -import io.rsocket.lease.RequesterLeaseHandler; -import io.rsocket.lease.ResponderLeaseHandler; -import io.rsocket.plugins.DuplexConnectionInterceptor; -import io.rsocket.plugins.PluginRegistry; -import io.rsocket.plugins.Plugins; -import io.rsocket.plugins.RSocketInterceptor; -import io.rsocket.plugins.SocketAcceptorInterceptor; -import io.rsocket.resume.InMemoryResumableFramesStore; -import io.rsocket.resume.ResumableFramesStore; -import io.rsocket.resume.SessionManager; -import io.rsocket.transport.ServerTransport; -import io.rsocket.util.MultiSubscriberRSocket; -import java.time.Duration; -import java.util.Objects; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; -import reactor.core.publisher.Mono; - -/** - * Default implementation of {@link RSocketFactory.ServerRSocketFactory} that can be instantiated - * directly or through the shortcut {@link RSocketFactory#receive()}. - */ -public class DefaultServerRSocketFactory implements RSocketFactory.ServerRSocketFactory { - private static final String SERVER_TAG = "server"; - - private SocketAcceptor acceptor; - private PayloadDecoder payloadDecoder = PayloadDecoder.DEFAULT; - private Consumer errorConsumer = Throwable::printStackTrace; - private int mtu = 0; - private PluginRegistry plugins = new PluginRegistry(Plugins.defaultPlugins()); - - private boolean resumeSupported; - private Duration resumeSessionDuration = Duration.ofSeconds(120); - private Duration resumeStreamTimeout = Duration.ofSeconds(10); - private Function resumeStoreFactory = - token -> new InMemoryResumableFramesStore(SERVER_TAG, 100_000); - - private boolean multiSubscriberRequester = true; - private boolean leaseEnabled; - private Supplier> leasesSupplier = Leases::new; - - private ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; - private boolean resumeCleanupStoreOnKeepAlive; - - @Override - public RSocketFactory.ServerRSocketFactory byteBufAllocator(ByteBufAllocator allocator) { - Objects.requireNonNull(allocator); - this.allocator = allocator; - return this; - } - - @Override - public RSocketFactory.ServerRSocketFactory addConnectionPlugin( - DuplexConnectionInterceptor interceptor) { - plugins.addConnectionPlugin(interceptor); - return this; - } - - @Override - public RSocketFactory.ServerRSocketFactory addRequesterPlugin(RSocketInterceptor interceptor) { - plugins.addRequesterPlugin(interceptor); - return this; - } - - @Override - public RSocketFactory.ServerRSocketFactory addResponderPlugin(RSocketInterceptor interceptor) { - plugins.addResponderPlugin(interceptor); - return this; - } - - @Override - public RSocketFactory.ServerRSocketFactory addSocketAcceptorPlugin( - SocketAcceptorInterceptor interceptor) { - plugins.addSocketAcceptorPlugin(interceptor); - return this; - } - - @Override - public RSocketFactory.ServerTransportAcceptor acceptor(SocketAcceptor acceptor) { - this.acceptor = acceptor; - return new ServerStart<>(); - } - - @Override - public RSocketFactory.ServerRSocketFactory frameDecoder(PayloadDecoder payloadDecoder) { - this.payloadDecoder = payloadDecoder; - return this; - } - - @Override - public RSocketFactory.ServerRSocketFactory fragment(int mtu) { - this.mtu = mtu; - return this; - } - - @Override - public RSocketFactory.ServerRSocketFactory errorConsumer(Consumer errorConsumer) { - this.errorConsumer = errorConsumer; - return this; - } - - @Override - public RSocketFactory.ServerRSocketFactory lease(Supplier> leasesSupplier) { - this.leaseEnabled = true; - this.leasesSupplier = Objects.requireNonNull(leasesSupplier); - return this; - } - - @Override - public RSocketFactory.ServerRSocketFactory lease() { - this.leaseEnabled = true; - return this; - } - - @Override - public RSocketFactory.ServerRSocketFactory singleSubscriberRequester() { - this.multiSubscriberRequester = false; - return this; - } - - @Override - public RSocketFactory.ServerRSocketFactory resume() { - this.resumeSupported = true; - return this; - } - - @Override - public RSocketFactory.ServerRSocketFactory resumeStore( - Function resumeStoreFactory) { - this.resumeStoreFactory = resumeStoreFactory; - return this; - } - - @Override - public RSocketFactory.ServerRSocketFactory resumeSessionDuration(Duration sessionDuration) { - this.resumeSessionDuration = Objects.requireNonNull(sessionDuration); - return this; - } - - @Override - public RSocketFactory.ServerRSocketFactory resumeStreamTimeout(Duration resumeStreamTimeout) { - this.resumeStreamTimeout = Objects.requireNonNull(resumeStreamTimeout); - return this; - } - - @Override - public RSocketFactory.ServerRSocketFactory resumeCleanupOnKeepAlive() { - resumeCleanupStoreOnKeepAlive = true; - return this; - } - - private class ServerStart - implements RSocketFactory.Start, RSocketFactory.ServerTransportAcceptor { - private Supplier> transportServer; - - @Override - public ServerTransport.ConnectionAcceptor toConnectionAcceptor() { - return new ServerTransport.ConnectionAcceptor() { - private final ServerSetup serverSetup = serverSetup(); - - @Override - public Mono apply(DuplexConnection connection) { - return acceptor(serverSetup, connection); - } - }; - } - - @Override - @SuppressWarnings("unchecked") - public RSocketFactory.Start transport( - Supplier> transport) { - this.transportServer = (Supplier) transport; - return (RSocketFactory.Start) this::start; - } - - private Mono acceptor(ServerSetup serverSetup, DuplexConnection connection) { - ClientServerInputMultiplexer multiplexer = - new ClientServerInputMultiplexer(connection, plugins, false); - - return multiplexer - .asSetupConnection() - .receive() - .next() - .flatMap(startFrame -> accept(serverSetup, startFrame, multiplexer)); - } - - private Mono acceptResume( - ServerSetup serverSetup, ByteBuf resumeFrame, ClientServerInputMultiplexer multiplexer) { - return serverSetup.acceptRSocketResume(resumeFrame, multiplexer); - } - - private Mono accept( - ServerSetup serverSetup, ByteBuf startFrame, ClientServerInputMultiplexer multiplexer) { - switch (FrameHeaderFlyweight.frameType(startFrame)) { - case SETUP: - return acceptSetup(serverSetup, startFrame, multiplexer); - case RESUME: - return acceptResume(serverSetup, startFrame, multiplexer); - default: - return serverSetup - .sendError( - multiplexer, - new InvalidSetupException( - "invalid setup frame: " + FrameHeaderFlyweight.frameType(startFrame))) - .doFinally( - signalType -> { - startFrame.release(); - multiplexer.dispose(); - }); - } - } - - private Mono acceptSetup( - ServerSetup serverSetup, ByteBuf setupFrame, ClientServerInputMultiplexer multiplexer) { - - if (!SetupFrameFlyweight.isSupportedVersion(setupFrame)) { - return serverSetup - .sendError( - multiplexer, - new InvalidSetupException( - "Unsupported version: " + SetupFrameFlyweight.humanReadableVersion(setupFrame))) - .doFinally( - signalType -> { - setupFrame.release(); - multiplexer.dispose(); - }); - } - - boolean isLeaseEnabled = leaseEnabled; - - if (SetupFrameFlyweight.honorLease(setupFrame) && !isLeaseEnabled) { - return serverSetup - .sendError(multiplexer, new InvalidSetupException("lease is not supported")) - .doFinally( - signalType -> { - setupFrame.release(); - multiplexer.dispose(); - }); - } - - return serverSetup.acceptRSocketSetup( - setupFrame, - multiplexer, - (keepAliveHandler, wrappedMultiplexer) -> { - ConnectionSetupPayload setupPayload = new DefaultConnectionSetupPayload(setupFrame); - - Leases leases = leasesSupplier.get(); - RequesterLeaseHandler requesterLeaseHandler = - isLeaseEnabled - ? new RequesterLeaseHandler.Impl(SERVER_TAG, leases.receiver()) - : RequesterLeaseHandler.None; - - RSocket rSocketRequester = - new RSocketRequester( - allocator, - wrappedMultiplexer.asServerConnection(), - payloadDecoder, - errorConsumer, - StreamIdSupplier.serverSupplier(), - mtu, - setupPayload.keepAliveInterval(), - setupPayload.keepAliveMaxLifetime(), - keepAliveHandler, - requesterLeaseHandler); - - if (multiSubscriberRequester) { - rSocketRequester = new MultiSubscriberRSocket(rSocketRequester); - } - RSocket wrappedRSocketRequester = plugins.applyRequester(rSocketRequester); - - return plugins - .applySocketAcceptorInterceptor(acceptor) - .accept(setupPayload, wrappedRSocketRequester) - .onErrorResume( - err -> - serverSetup - .sendError(multiplexer, rejectedSetupError(err)) - .then(Mono.error(err))) - .doOnNext( - rSocketHandler -> { - RSocket wrappedRSocketHandler = plugins.applyResponder(rSocketHandler); - - ResponderLeaseHandler responderLeaseHandler = - isLeaseEnabled - ? new ResponderLeaseHandler.Impl<>( - SERVER_TAG, - allocator, - leases.sender(), - errorConsumer, - leases.stats()) - : ResponderLeaseHandler.None; - - RSocket rSocketResponder = - new RSocketResponder( - allocator, - wrappedMultiplexer.asClientConnection(), - wrappedRSocketHandler, - payloadDecoder, - errorConsumer, - responderLeaseHandler, - mtu); - }) - .doFinally(signalType -> setupPayload.release()) - .then(); - }); - } - - @Override - public Mono start() { - return Mono.defer( - new Supplier>() { - - ServerSetup serverSetup = serverSetup(); - - @Override - public Mono get() { - return Mono.fromSupplier(transportServer) - .flatMap( - transport -> - transport.start( - duplexConnection -> acceptor(serverSetup, duplexConnection), mtu)) - .doOnNext(c -> c.onClose().doFinally(v -> serverSetup.dispose()).subscribe()); - } - }); - } - - private ServerSetup serverSetup() { - return resumeSupported - ? new ServerSetup.ResumableServerSetup( - allocator, - new SessionManager(), - resumeSessionDuration, - resumeStreamTimeout, - resumeStoreFactory, - resumeCleanupStoreOnKeepAlive) - : new ServerSetup.DefaultServerSetup(allocator); - } - - private Exception rejectedSetupError(Throwable err) { - String msg = err.getMessage(); - return new RejectedSetupException(msg == null ? "rejected by server acceptor" : msg); - } - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java new file mode 100644 index 000000000..92c5220c0 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java @@ -0,0 +1,375 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.rsocket.core; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; +import io.rsocket.AbstractRSocket; +import io.rsocket.ConnectionSetupPayload; +import io.rsocket.DuplexConnection; +import io.rsocket.Payload; +import io.rsocket.RSocket; +import io.rsocket.SocketAcceptor; +import io.rsocket.frame.SetupFrameFlyweight; +import io.rsocket.frame.decoder.PayloadDecoder; +import io.rsocket.internal.ClientServerInputMultiplexer; +import io.rsocket.keepalive.KeepAliveHandler; +import io.rsocket.lease.LeaseStats; +import io.rsocket.lease.Leases; +import io.rsocket.lease.RequesterLeaseHandler; +import io.rsocket.lease.ResponderLeaseHandler; +import io.rsocket.plugins.InitializingInterceptorRegistry; +import io.rsocket.plugins.InterceptorRegistry; +import io.rsocket.resume.ClientRSocketSession; +import io.rsocket.transport.ClientTransport; +import io.rsocket.util.EmptyPayload; +import java.time.Duration; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; +import reactor.core.Disposable; +import reactor.core.publisher.Mono; +import reactor.util.retry.Retry; + +public class RSocketConnector { + private static final String CLIENT_TAG = "client"; + private static final int MIN_MTU_SIZE = 64; + + private static final BiConsumer INVALIDATE_FUNCTION = + (r, i) -> r.onClose().subscribe(null, null, i::invalidate); + + private Payload setupPayload = EmptyPayload.INSTANCE; + private String metadataMimeType = "application/binary"; + private String dataMimeType = "application/binary"; + + private SocketAcceptor acceptor = (setup, sendingSocket) -> Mono.just(new AbstractRSocket() {}); + private InitializingInterceptorRegistry interceptors = new InitializingInterceptorRegistry(); + + private Duration keepAliveInterval = Duration.ofSeconds(20); + private Duration keepAliveMaxLifeTime = Duration.ofSeconds(90); + + private Retry retrySpec; + private Resume resume; + private Supplier> leasesSupplier; + + private int mtu = 0; + private PayloadDecoder payloadDecoder = PayloadDecoder.DEFAULT; + + private Consumer errorConsumer = Throwable::printStackTrace; + private ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; + + private RSocketConnector() {} + + public static RSocketConnector create() { + return new RSocketConnector(); + } + + public static Mono connectWith(ClientTransport transport) { + return RSocketConnector.create().connect(() -> transport); + } + + public RSocketConnector setupPayload(Payload payload) { + this.setupPayload = payload; + return this; + } + + public RSocketConnector dataMimeType(String dataMimeType) { + this.dataMimeType = dataMimeType; + return this; + } + + public RSocketConnector metadataMimeType(String metadataMimeType) { + this.metadataMimeType = metadataMimeType; + return this; + } + + public RSocketConnector interceptors(Consumer consumer) { + consumer.accept(this.interceptors); + return this; + } + + public RSocketConnector acceptor(SocketAcceptor acceptor) { + this.acceptor = acceptor; + return this; + } + + /** + * Set the time {@code interval} between KEEPALIVE frames sent by this client, and the {@code + * maxLifeTime} that this client will allow between KEEPALIVE frames from the server before + * assuming it is dead. + * + *

    Note that reasonable values for the time interval may vary significantly. For + * server-to-server connections the spec suggests 500ms, while for for mobile-to-server + * connections it suggests 30+ seconds. In addition {@code maxLifeTime} should allow plenty of + * room for multiple missed ticks from the server. + * + *

    By default {@code interval} is set to 20 seconds and {@code maxLifeTime} to 90 seconds. + * + * @param interval the time between KEEPALIVE frames sent, must be > 0. + * @param maxLifeTime the max time allowed between KEEPALIVE frames received, must be > 0. + */ + public RSocketConnector keepAlive(Duration interval, Duration maxLifeTime) { + if (!interval.negated().isNegative()) { + throw new IllegalArgumentException("`interval` for keepAlive must be > 0"); + } + if (!maxLifeTime.negated().isNegative()) { + throw new IllegalArgumentException("`maxLifeTime` for keepAlive must be > 0"); + } + this.keepAliveInterval = interval; + this.keepAliveMaxLifeTime = maxLifeTime; + return this; + } + + /** + * Enables a reconnectable, shared instance of {@code Mono} so every subscriber will + * observe the same RSocket instance up on connection establishment.
    + * For example: + * + *

    {@code
    +   * Mono sharedRSocketMono =
    +   *   RSocketConnector.create()
    +   *           .reconnect(Retry.fixedDelay(3, Duration.ofSeconds(1)))
    +   *           .connect(transport);
    +   *
    +   *  RSocket r1 = sharedRSocketMono.block();
    +   *  RSocket r2 = sharedRSocketMono.block();
    +   *
    +   *  assert r1 == r2;
    +   *
    +   * }
    + * + * Apart of the shared behavior, if the connection is lost, the same {@code Mono} + * instance will transparently re-establish the connection for subsequent subscribers.
    + * For example: + * + *
    {@code
    +   * Mono sharedRSocketMono =
    +   *   RSocketConnector.create()
    +   *           .reconnect(Retry.fixedDelay(3, Duration.ofSeconds(1)))
    +   *           .connect(transport);
    +   *
    +   *  RSocket r1 = sharedRSocketMono.block();
    +   *  RSocket r2 = sharedRSocketMono.block();
    +   *
    +   *  assert r1 == r2;
    +   *
    +   *  r1.dispose()
    +   *
    +   *  assert r2.isDisposed()
    +   *
    +   *  RSocket r3 = sharedRSocketMono.block();
    +   *  RSocket r4 = sharedRSocketMono.block();
    +   *
    +   *  assert r1 != r3;
    +   *  assert r4 == r3;
    +   *
    +   * }
    + * + * Note, having reconnect() enabled does not eliminate the need to accompany each + * individual request with the corresponding retry logic.
    + * For example: + * + *
    {@code
    +   * Mono sharedRSocketMono =
    +   *   RSocketConnector.create()
    +   *           .reconnect(Retry.fixedDelay(3, Duration.ofSeconds(1)))
    +   *           .connect(transport);
    +   *
    +   *  sharedRSocket.flatMap(rSocket -> rSocket.requestResponse(...))
    +   *               .retryWhen(ownRetry)
    +   *               .subscribe()
    +   *
    +   * }
    + * + * @param retrySpec a retry factory applied for {@link Mono#retryWhen(Retry)} + * @return a shared instance of {@code Mono}. + */ + public RSocketConnector reconnect(Retry retrySpec) { + this.retrySpec = Objects.requireNonNull(retrySpec); + return this; + } + + public RSocketConnector resume(Resume resume) { + this.resume = resume; + return this; + } + + public RSocketConnector lease(Supplier> supplier) { + this.leasesSupplier = supplier; + return this; + } + + public RSocketConnector fragment(int mtu) { + if (mtu > 0 && mtu < MIN_MTU_SIZE || mtu < 0) { + String msg = + String.format("smallest allowed mtu size is %d bytes, provided: %d", MIN_MTU_SIZE, mtu); + throw new IllegalArgumentException(msg); + } + this.mtu = mtu; + return this; + } + + public RSocketConnector payloadDecoder(PayloadDecoder payloadDecoder) { + Objects.requireNonNull(payloadDecoder); + this.payloadDecoder = payloadDecoder; + return this; + } + + /** + * @deprecated this is deprecated with no replacement and will be removed after {@link + * io.rsocket.RSocketFactory} is removed. + */ + @Deprecated + public RSocketConnector errorConsumer(Consumer errorConsumer) { + Objects.requireNonNull(errorConsumer); + this.errorConsumer = errorConsumer; + return this; + } + + /** + * @deprecated this is deprecated with no replacement and will be removed after {@link + * io.rsocket.RSocketFactory} is removed. + */ + public RSocketConnector byteBufAllocator(ByteBufAllocator allocator) { + Objects.requireNonNull(allocator); + this.allocator = allocator; + return this; + } + + public Mono connect(ClientTransport transport) { + return connect(() -> transport); + } + + public Mono connect(Supplier transportSupplier) { + Mono connectionMono = + Mono.fromSupplier(transportSupplier).flatMap(t -> t.connect(mtu)); + return connectionMono + .flatMap( + connection -> { + ByteBuf resumeToken; + KeepAliveHandler keepAliveHandler; + DuplexConnection wrappedConnection; + + if (resume != null) { + ClientRSocketSession session = createSession(connection, connectionMono); + resumeToken = session.token(); + keepAliveHandler = + new KeepAliveHandler.ResumableKeepAliveHandler(session.resumableConnection()); + wrappedConnection = session.resumableConnection(); + } else { + resumeToken = Unpooled.EMPTY_BUFFER; + keepAliveHandler = new KeepAliveHandler.DefaultKeepAliveHandler(connection); + wrappedConnection = connection; + } + + ClientServerInputMultiplexer multiplexer = + new ClientServerInputMultiplexer(wrappedConnection, interceptors, true); + + boolean leaseEnabled = leasesSupplier != null; + Leases leases = leaseEnabled ? leasesSupplier.get() : null; + RequesterLeaseHandler requesterLeaseHandler = + leaseEnabled + ? new RequesterLeaseHandler.Impl(CLIENT_TAG, leases.receiver()) + : RequesterLeaseHandler.None; + + RSocket rSocketRequester = + new RSocketRequester( + allocator, + multiplexer.asClientConnection(), + payloadDecoder, + errorConsumer, + StreamIdSupplier.clientSupplier(), + mtu, + (int) keepAliveInterval.toMillis(), + (int) keepAliveMaxLifeTime.toMillis(), + keepAliveHandler, + requesterLeaseHandler); + + RSocket wrappedRSocketRequester = interceptors.initRequester(rSocketRequester); + + ByteBuf setupFrame = + SetupFrameFlyweight.encode( + allocator, + leaseEnabled, + (int) keepAliveInterval.toMillis(), + (int) keepAliveMaxLifeTime.toMillis(), + resumeToken, + metadataMimeType, + dataMimeType, + setupPayload); + + ConnectionSetupPayload setup = new DefaultConnectionSetupPayload(setupFrame); + + return interceptors + .initSocketAcceptor(acceptor) + .accept(setup, wrappedRSocketRequester) + .flatMap( + rSocketHandler -> { + RSocket wrappedRSocketHandler = interceptors.initResponder(rSocketHandler); + + ResponderLeaseHandler responderLeaseHandler = + leaseEnabled + ? new ResponderLeaseHandler.Impl<>( + CLIENT_TAG, + allocator, + leases.sender(), + errorConsumer, + leases.stats()) + : ResponderLeaseHandler.None; + + RSocket rSocketResponder = + new RSocketResponder( + allocator, + multiplexer.asServerConnection(), + wrappedRSocketHandler, + payloadDecoder, + errorConsumer, + responderLeaseHandler, + mtu); + + return wrappedConnection + .sendOne(setupFrame) + .thenReturn(wrappedRSocketRequester); + }); + }) + .as( + source -> { + if (retrySpec != null) { + return new ReconnectMono<>( + source.retryWhen(retrySpec), Disposable::dispose, INVALIDATE_FUNCTION); + } else { + return source; + } + }); + } + + private ClientRSocketSession createSession( + DuplexConnection connection, Mono connectionMono) { + ByteBuf resumeToken = resume.getTokenSupplier().get(); + ClientRSocketSession session = + new ClientRSocketSession( + connection, + allocator, + resume.getSessionDuration(), + resume.getResumeStrategySupplier(), + resume.getStoreFactory(CLIENT_TAG).apply(resumeToken), + resume.getStreamTimeout(), + resume.isCleanupStoreOnKeepAlive()); + return session.continueWith(connectionMono).resumeToken(resumeToken); + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java new file mode 100644 index 000000000..113b4283f --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java @@ -0,0 +1,299 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.rsocket.core; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.rsocket.AbstractRSocket; +import io.rsocket.Closeable; +import io.rsocket.ConnectionSetupPayload; +import io.rsocket.DuplexConnection; +import io.rsocket.RSocket; +import io.rsocket.SocketAcceptor; +import io.rsocket.exceptions.InvalidSetupException; +import io.rsocket.exceptions.RejectedSetupException; +import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.SetupFrameFlyweight; +import io.rsocket.frame.decoder.PayloadDecoder; +import io.rsocket.internal.ClientServerInputMultiplexer; +import io.rsocket.lease.Leases; +import io.rsocket.lease.RequesterLeaseHandler; +import io.rsocket.lease.ResponderLeaseHandler; +import io.rsocket.plugins.InitializingInterceptorRegistry; +import io.rsocket.plugins.InterceptorRegistry; +import io.rsocket.resume.SessionManager; +import io.rsocket.transport.ServerTransport; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; +import reactor.core.publisher.Mono; + +public final class RSocketServer { + private static final String SERVER_TAG = "server"; + private static final int MIN_MTU_SIZE = 64; + + private SocketAcceptor acceptor = (setup, sendingSocket) -> Mono.just(new AbstractRSocket() {}); + private InitializingInterceptorRegistry interceptors = new InitializingInterceptorRegistry(); + private int mtu = 0; + + private Resume resume; + private Supplier> leasesSupplier = null; + + private Consumer errorConsumer = Throwable::printStackTrace; + private PayloadDecoder payloadDecoder = PayloadDecoder.DEFAULT; + private ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; + + private RSocketServer() {} + + public static RSocketServer create() { + return new RSocketServer(); + } + + public static RSocketServer create(SocketAcceptor acceptor) { + return RSocketServer.create().acceptor(acceptor); + } + + public RSocketServer acceptor(SocketAcceptor acceptor) { + Objects.requireNonNull(acceptor); + this.acceptor = acceptor; + return this; + } + + public RSocketServer interceptors(Consumer consumer) { + consumer.accept(this.interceptors); + return this; + } + + public RSocketServer fragment(int mtu) { + if (mtu > 0 && mtu < MIN_MTU_SIZE || mtu < 0) { + String msg = + String.format("smallest allowed mtu size is %d bytes, provided: %d", MIN_MTU_SIZE, mtu); + throw new IllegalArgumentException(msg); + } + this.mtu = mtu; + return this; + } + + public RSocketServer resume(Resume resume) { + this.resume = resume; + return this; + } + + public RSocketServer lease(Supplier> supplier) { + this.leasesSupplier = supplier; + return this; + } + + public RSocketServer payloadDecoder(PayloadDecoder payloadDecoder) { + Objects.requireNonNull(payloadDecoder); + this.payloadDecoder = payloadDecoder; + return this; + } + + /** + * @deprecated this is deprecated with no replacement and will be removed after {@link + * io.rsocket.RSocketFactory} is removed. + */ + @Deprecated + public RSocketServer errorConsumer(Consumer errorConsumer) { + this.errorConsumer = errorConsumer; + return this; + } + + /** + * @deprecated this is deprecated with no replacement and will be removed after {@link + * io.rsocket.RSocketFactory} is removed. + */ + @Deprecated + public RSocketServer byteBufAllocator(ByteBufAllocator allocator) { + Objects.requireNonNull(allocator); + this.allocator = allocator; + return this; + } + + public ServerTransport.ConnectionAcceptor asConnectionAcceptor() { + return new ServerTransport.ConnectionAcceptor() { + private final ServerSetup serverSetup = serverSetup(); + + @Override + public Mono apply(DuplexConnection connection) { + return acceptor(serverSetup, connection); + } + }; + } + + public Mono bind(ServerTransport transport) { + return Mono.defer( + new Supplier>() { + ServerSetup serverSetup = serverSetup(); + + @Override + public Mono get() { + return transport + .start(duplexConnection -> acceptor(serverSetup, duplexConnection), mtu) + .doOnNext(c -> c.onClose().doFinally(v -> serverSetup.dispose()).subscribe()); + } + }); + } + + private Mono acceptor(ServerSetup serverSetup, DuplexConnection connection) { + ClientServerInputMultiplexer multiplexer = + new ClientServerInputMultiplexer(connection, interceptors, false); + + return multiplexer + .asSetupConnection() + .receive() + .next() + .flatMap(startFrame -> accept(serverSetup, startFrame, multiplexer)); + } + + private Mono acceptResume( + ServerSetup serverSetup, ByteBuf resumeFrame, ClientServerInputMultiplexer multiplexer) { + return serverSetup.acceptRSocketResume(resumeFrame, multiplexer); + } + + private Mono accept( + ServerSetup serverSetup, ByteBuf startFrame, ClientServerInputMultiplexer multiplexer) { + switch (FrameHeaderFlyweight.frameType(startFrame)) { + case SETUP: + return acceptSetup(serverSetup, startFrame, multiplexer); + case RESUME: + return acceptResume(serverSetup, startFrame, multiplexer); + default: + return serverSetup + .sendError( + multiplexer, + new InvalidSetupException( + "invalid setup frame: " + FrameHeaderFlyweight.frameType(startFrame))) + .doFinally( + signalType -> { + startFrame.release(); + multiplexer.dispose(); + }); + } + } + + private Mono acceptSetup( + ServerSetup serverSetup, ByteBuf setupFrame, ClientServerInputMultiplexer multiplexer) { + + if (!SetupFrameFlyweight.isSupportedVersion(setupFrame)) { + return serverSetup + .sendError( + multiplexer, + new InvalidSetupException( + "Unsupported version: " + SetupFrameFlyweight.humanReadableVersion(setupFrame))) + .doFinally( + signalType -> { + setupFrame.release(); + multiplexer.dispose(); + }); + } + + boolean leaseEnabled = leasesSupplier != null; + if (SetupFrameFlyweight.honorLease(setupFrame) && !leaseEnabled) { + return serverSetup + .sendError(multiplexer, new InvalidSetupException("lease is not supported")) + .doFinally( + signalType -> { + setupFrame.release(); + multiplexer.dispose(); + }); + } + + return serverSetup.acceptRSocketSetup( + setupFrame, + multiplexer, + (keepAliveHandler, wrappedMultiplexer) -> { + ConnectionSetupPayload setupPayload = new DefaultConnectionSetupPayload(setupFrame); + + Leases leases = leaseEnabled ? leasesSupplier.get() : null; + RequesterLeaseHandler requesterLeaseHandler = + leaseEnabled + ? new RequesterLeaseHandler.Impl(SERVER_TAG, leases.receiver()) + : RequesterLeaseHandler.None; + + RSocket rSocketRequester = + new RSocketRequester( + allocator, + wrappedMultiplexer.asServerConnection(), + payloadDecoder, + errorConsumer, + StreamIdSupplier.serverSupplier(), + mtu, + setupPayload.keepAliveInterval(), + setupPayload.keepAliveMaxLifetime(), + keepAliveHandler, + requesterLeaseHandler); + + RSocket wrappedRSocketRequester = interceptors.initRequester(rSocketRequester); + + return interceptors + .initSocketAcceptor(acceptor) + .accept(setupPayload, wrappedRSocketRequester) + .onErrorResume( + err -> + serverSetup + .sendError(multiplexer, rejectedSetupError(err)) + .then(Mono.error(err))) + .doOnNext( + rSocketHandler -> { + RSocket wrappedRSocketHandler = interceptors.initResponder(rSocketHandler); + + ResponderLeaseHandler responderLeaseHandler = + leaseEnabled + ? new ResponderLeaseHandler.Impl<>( + SERVER_TAG, + allocator, + leases.sender(), + errorConsumer, + leases.stats()) + : ResponderLeaseHandler.None; + + RSocket rSocketResponder = + new RSocketResponder( + allocator, + wrappedMultiplexer.asClientConnection(), + wrappedRSocketHandler, + payloadDecoder, + errorConsumer, + responderLeaseHandler, + mtu); + }) + .doFinally(signalType -> setupPayload.release()) + .then(); + }); + } + + private ServerSetup serverSetup() { + return resume != null ? createSetup() : new ServerSetup.DefaultServerSetup(allocator); + } + + ServerSetup createSetup() { + return new ServerSetup.ResumableServerSetup( + allocator, + new SessionManager(), + resume.getSessionDuration(), + resume.getStreamTimeout(), + resume.getStoreFactory(SERVER_TAG), + resume.isCleanupStoreOnKeepAlive()); + } + + private Exception rejectedSetupError(Throwable err) { + String msg = err.getMessage(); + return new RejectedSetupException(msg == null ? "rejected by server acceptor" : msg); + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/core/Resume.java b/rsocket-core/src/main/java/io/rsocket/core/Resume.java new file mode 100644 index 000000000..4b0edab00 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/core/Resume.java @@ -0,0 +1,96 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.rsocket.core; + +import io.netty.buffer.ByteBuf; +import io.rsocket.frame.ResumeFrameFlyweight; +import io.rsocket.resume.ExponentialBackoffResumeStrategy; +import io.rsocket.resume.InMemoryResumableFramesStore; +import io.rsocket.resume.ResumableFramesStore; +import io.rsocket.resume.ResumeStrategy; +import java.time.Duration; +import java.util.function.Function; +import java.util.function.Supplier; + +public class Resume { + Duration sessionDuration = Duration.ofMinutes(2); + Duration streamTimeout = Duration.ofSeconds(10); + boolean cleanupStoreOnKeepAlive; + Function storeFactory; + + private Supplier tokenSupplier = ResumeFrameFlyweight::generateResumeToken; + private Supplier resumeStrategySupplier = + () -> new ExponentialBackoffResumeStrategy(Duration.ofSeconds(1), Duration.ofSeconds(16), 2); + + public Resume() {} + + public Resume sessionDuration(Duration sessionDuration) { + this.sessionDuration = sessionDuration; + return this; + } + + public Resume streamTimeout(Duration streamTimeout) { + this.streamTimeout = streamTimeout; + return this; + } + + public Resume cleanupStoreOnKeepAlive() { + this.cleanupStoreOnKeepAlive = true; + return this; + } + + public Resume storeFactory( + Function storeFactory) { + this.storeFactory = storeFactory; + return this; + } + + public Resume token(Supplier supplier) { + this.tokenSupplier = supplier; + return this; + } + + public Resume resumeStrategy(Supplier supplier) { + this.resumeStrategySupplier = supplier; + return this; + } + + Duration getSessionDuration() { + return sessionDuration; + } + + Duration getStreamTimeout() { + return streamTimeout; + } + + boolean isCleanupStoreOnKeepAlive() { + return cleanupStoreOnKeepAlive; + } + + Function getStoreFactory(String tag) { + return storeFactory != null + ? storeFactory + : token -> new InMemoryResumableFramesStore(tag, 100_000); + } + + Supplier getTokenSupplier() { + return tokenSupplier; + } + + Supplier getResumeStrategySupplier() { + return resumeStrategySupplier; + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java b/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java index 1e76b6898..68098e279 100644 --- a/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java +++ b/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java @@ -22,7 +22,7 @@ import io.rsocket.frame.FrameHeaderFlyweight; import io.rsocket.frame.FrameUtil; import io.rsocket.plugins.DuplexConnectionInterceptor.Type; -import io.rsocket.plugins.PluginRegistry; +import io.rsocket.plugins.InitializingInterceptorRegistry; import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,7 +45,8 @@ */ public class ClientServerInputMultiplexer implements Closeable { private static final Logger LOGGER = LoggerFactory.getLogger("io.rsocket.FrameLogger"); - private static final PluginRegistry emptyPluginRegistry = new PluginRegistry(); + private static final InitializingInterceptorRegistry emptyInterceptorRegistry = + new InitializingInterceptorRegistry(); private final DuplexConnection setupConnection; private final DuplexConnection serverConnection; @@ -54,23 +55,23 @@ public class ClientServerInputMultiplexer implements Closeable { private final DuplexConnection clientServerConnection; public ClientServerInputMultiplexer(DuplexConnection source) { - this(source, emptyPluginRegistry, false); + this(source, emptyInterceptorRegistry, false); } public ClientServerInputMultiplexer( - DuplexConnection source, PluginRegistry plugins, boolean isClient) { + DuplexConnection source, InitializingInterceptorRegistry registry, boolean isClient) { this.source = source; final MonoProcessor> setup = MonoProcessor.create(); final MonoProcessor> server = MonoProcessor.create(); final MonoProcessor> client = MonoProcessor.create(); - source = plugins.applyConnection(Type.SOURCE, source); + source = registry.initConnection(Type.SOURCE, source); setupConnection = - plugins.applyConnection(Type.SETUP, new InternalDuplexConnection(source, setup)); + registry.initConnection(Type.SETUP, new InternalDuplexConnection(source, setup)); serverConnection = - plugins.applyConnection(Type.SERVER, new InternalDuplexConnection(source, server)); + registry.initConnection(Type.SERVER, new InternalDuplexConnection(source, server)); clientConnection = - plugins.applyConnection(Type.CLIENT, new InternalDuplexConnection(source, client)); + registry.initConnection(Type.CLIENT, new InternalDuplexConnection(source, client)); clientServerConnection = new InternalDuplexConnection(source, client, server); source diff --git a/rsocket-core/src/main/java/io/rsocket/plugins/InitializingInterceptorRegistry.java b/rsocket-core/src/main/java/io/rsocket/plugins/InitializingInterceptorRegistry.java new file mode 100644 index 000000000..cf911b954 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/plugins/InitializingInterceptorRegistry.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.rsocket.plugins; + +import io.rsocket.DuplexConnection; +import io.rsocket.RSocket; +import io.rsocket.SocketAcceptor; + +public class InitializingInterceptorRegistry extends InterceptorRegistry { + + public DuplexConnection initConnection( + DuplexConnectionInterceptor.Type type, DuplexConnection connection) { + for (DuplexConnectionInterceptor interceptor : getConnectionInterceptors()) { + connection = interceptor.apply(type, connection); + } + return connection; + } + + public RSocket initRequester(RSocket rsocket) { + for (RSocketInterceptor interceptor : getRequesterInteceptors()) { + rsocket = interceptor.apply(rsocket); + } + return rsocket; + } + + public RSocket initResponder(RSocket rsocket) { + for (RSocketInterceptor interceptor : getResponderInterceptors()) { + rsocket = interceptor.apply(rsocket); + } + return rsocket; + } + + public SocketAcceptor initSocketAcceptor(SocketAcceptor acceptor) { + for (SocketAcceptorInterceptor interceptor : getSocketAcceptorInterceptors()) { + acceptor = interceptor.apply(acceptor); + } + return acceptor; + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/plugins/InterceptorRegistry.java b/rsocket-core/src/main/java/io/rsocket/plugins/InterceptorRegistry.java new file mode 100644 index 000000000..f9ee151a8 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/plugins/InterceptorRegistry.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.rsocket.plugins; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class InterceptorRegistry { + private List connectionInterceptors = new ArrayList<>(); + private List requesterInteceptors = new ArrayList<>(); + private List responderInterceptors = new ArrayList<>(); + private List socketAcceptorInterceptors = new ArrayList<>(); + + public InterceptorRegistry forConnection(DuplexConnectionInterceptor interceptor) { + connectionInterceptors.add(interceptor); + return this; + } + + public InterceptorRegistry forConnection(Consumer> consumer) { + consumer.accept(connectionInterceptors); + return this; + } + + public InterceptorRegistry forRequester(RSocketInterceptor interceptor) { + requesterInteceptors.add(interceptor); + return this; + } + + public InterceptorRegistry forRequester(Consumer> consumer) { + consumer.accept(requesterInteceptors); + return this; + } + + public InterceptorRegistry forResponder(RSocketInterceptor interceptor) { + responderInterceptors.add(interceptor); + return this; + } + + public InterceptorRegistry forResponder(Consumer> consumer) { + consumer.accept(responderInterceptors); + return this; + } + + public InterceptorRegistry forSocketAcceptor(SocketAcceptorInterceptor interceptor) { + socketAcceptorInterceptors.add(interceptor); + return this; + } + + public InterceptorRegistry forSocketAcceptor(Consumer> consumer) { + consumer.accept(socketAcceptorInterceptors); + return this; + } + + List getConnectionInterceptors() { + return connectionInterceptors; + } + + List getRequesterInteceptors() { + return requesterInteceptors; + } + + List getResponderInterceptors() { + return responderInterceptors; + } + + List getSocketAcceptorInterceptors() { + return socketAcceptorInterceptors; + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/plugins/PluginRegistry.java b/rsocket-core/src/main/java/io/rsocket/plugins/PluginRegistry.java deleted file mode 100644 index e3a19367c..000000000 --- a/rsocket-core/src/main/java/io/rsocket/plugins/PluginRegistry.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.plugins; - -import io.rsocket.DuplexConnection; -import io.rsocket.RSocket; -import io.rsocket.SocketAcceptor; -import java.util.ArrayList; -import java.util.List; - -public class PluginRegistry { - private List connections = new ArrayList<>(); - private List requesters = new ArrayList<>(); - private List responders = new ArrayList<>(); - private List socketAcceptorInterceptors = new ArrayList<>(); - - public PluginRegistry() {} - - public PluginRegistry(PluginRegistry defaults) { - this.connections.addAll(defaults.connections); - this.requesters.addAll(defaults.requesters); - this.responders.addAll(defaults.responders); - } - - public void addConnectionPlugin(DuplexConnectionInterceptor interceptor) { - connections.add(interceptor); - } - - /** Deprecated. Use {@link #addRequesterPlugin(RSocketInterceptor)} instead */ - @Deprecated - public void addClientPlugin(RSocketInterceptor interceptor) { - addRequesterPlugin(interceptor); - } - - public void addRequesterPlugin(RSocketInterceptor interceptor) { - requesters.add(interceptor); - } - - /** Deprecated. Use {@link #addResponderPlugin(RSocketInterceptor)} instead */ - @Deprecated - public void addServerPlugin(RSocketInterceptor interceptor) { - addResponderPlugin(interceptor); - } - - public void addResponderPlugin(RSocketInterceptor interceptor) { - responders.add(interceptor); - } - - public void addSocketAcceptorPlugin(SocketAcceptorInterceptor interceptor) { - socketAcceptorInterceptors.add(interceptor); - } - - /** Deprecated. Use {@link #applyRequester(RSocket)} instead */ - @Deprecated - public RSocket applyClient(RSocket rSocket) { - return applyRequester(rSocket); - } - - public RSocket applyRequester(RSocket rSocket) { - for (RSocketInterceptor i : requesters) { - rSocket = i.apply(rSocket); - } - - return rSocket; - } - - /** Deprecated. Use {@link #applyResponder(RSocket)} instead */ - @Deprecated - public RSocket applyServer(RSocket rSocket) { - return applyResponder(rSocket); - } - - public RSocket applyResponder(RSocket rSocket) { - for (RSocketInterceptor i : responders) { - rSocket = i.apply(rSocket); - } - - return rSocket; - } - - public SocketAcceptor applySocketAcceptorInterceptor(SocketAcceptor acceptor) { - for (SocketAcceptorInterceptor i : socketAcceptorInterceptors) { - acceptor = i.apply(acceptor); - } - - return acceptor; - } - - public DuplexConnection applyConnection( - DuplexConnectionInterceptor.Type type, DuplexConnection connection) { - for (DuplexConnectionInterceptor i : connections) { - connection = i.apply(type, connection); - } - - return connection; - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/plugins/Plugins.java b/rsocket-core/src/main/java/io/rsocket/plugins/Plugins.java deleted file mode 100644 index 1ac147687..000000000 --- a/rsocket-core/src/main/java/io/rsocket/plugins/Plugins.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.plugins; - -/** JVM wide plugins for RSocket */ -public class Plugins { - private static PluginRegistry DEFAULT = new PluginRegistry(); - - private Plugins() {} - - public static void interceptConnection(DuplexConnectionInterceptor interceptor) { - DEFAULT.addConnectionPlugin(interceptor); - } - - public static void interceptClient(RSocketInterceptor interceptor) { - DEFAULT.addClientPlugin(interceptor); - } - - public static void interceptServer(RSocketInterceptor interceptor) { - DEFAULT.addServerPlugin(interceptor); - } - - public static PluginRegistry defaultPlugins() { - return DEFAULT; - } -} diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java index a9076428c..01ee1eb6d 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2019 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,13 +19,16 @@ import static io.rsocket.frame.FrameType.ERROR; import static io.rsocket.frame.FrameType.SETUP; import static org.assertj.core.data.Offset.offset; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import io.netty.buffer.UnpooledByteBufAllocator; -import io.rsocket.*; +import io.rsocket.Payload; +import io.rsocket.RSocket; import io.rsocket.exceptions.Exceptions; import io.rsocket.frame.FrameHeaderFlyweight; import io.rsocket.frame.FrameType; @@ -35,7 +38,7 @@ import io.rsocket.internal.ClientServerInputMultiplexer; import io.rsocket.lease.*; import io.rsocket.lease.MissingLeaseException; -import io.rsocket.plugins.PluginRegistry; +import io.rsocket.plugins.InitializingInterceptorRegistry; import io.rsocket.test.util.TestClientTransport; import io.rsocket.test.util.TestDuplexConnection; import io.rsocket.test.util.TestServerTransport; @@ -84,7 +87,7 @@ void setUp() { TAG, byteBufAllocator, stats -> leaseSender, err -> {}, Optional.empty()); ClientServerInputMultiplexer multiplexer = - new ClientServerInputMultiplexer(connection, new PluginRegistry(), true); + new ClientServerInputMultiplexer(connection, new InitializingInterceptorRegistry(), true); rSocketRequester = new RSocketRequester( byteBufAllocator, @@ -130,12 +133,7 @@ public void serverRSocketFactoryRejectsUnsupportedLease() { payload); TestServerTransport transport = new TestServerTransport(); - Closeable server = - RSocketFactory.receive() - .acceptor((setup, sendingSocket) -> Mono.just(new AbstractRSocket() {})) - .transport(transport) - .start() - .block(); + RSocketServer.create().bind(transport).block(); TestDuplexConnection connection = transport.connect(); connection.addToReceivedBuffer(setupFrame); @@ -151,7 +149,7 @@ public void serverRSocketFactoryRejectsUnsupportedLease() { @Test public void clientRSocketFactorySetsLeaseFlag() { TestClientTransport clientTransport = new TestClientTransport(); - RSocketFactory.connect().lease().transport(clientTransport).start().block(); + RSocketConnector.create().lease(Leases::new).connect(clientTransport).block(); Collection sent = clientTransport.testConnection().getSent(); Assertions.assertThat(sent).hasSize(1); diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketReconnectTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketReconnectTest.java index f7ba12ada..dc76b5450 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketReconnectTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketReconnectTest.java @@ -18,7 +18,6 @@ import static org.junit.Assert.assertEquals; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; import io.rsocket.test.util.TestClientTransport; import io.rsocket.transport.ClientTransport; import java.io.UncheckedIOException; @@ -27,7 +26,6 @@ import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.function.Consumer; -import java.util.function.Supplier; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -44,14 +42,9 @@ public void shouldBeASharedReconnectableInstanceOfRSocketMono() { TestClientTransport[] testClientTransport = new TestClientTransport[] {new TestClientTransport()}; Mono rSocketMono = - RSocketFactory.connect() - .singleSubscriberRequester() + RSocketConnector.create() .reconnect(Retry.indefinitely()) - .transport( - () -> { - return testClientTransport[0]; - }) - .start(); + .connect(() -> testClientTransport[0]); RSocket rSocket1 = rSocketMono.block(); RSocket rSocket2 = rSocketMono.block(); @@ -70,22 +63,20 @@ public void shouldBeASharedReconnectableInstanceOfRSocketMono() { @Test @SuppressWarnings({"rawtype", "unchecked"}) public void shouldBeRetrieableConnectionSharedReconnectableInstanceOfRSocketMono() { - Supplier mockTransportSupplier = Mockito.mock(Supplier.class); - Mockito.when(mockTransportSupplier.get()) + ClientTransport transport = Mockito.mock(ClientTransport.class); + Mockito.when(transport.connect(0)) .thenThrow(UncheckedIOException.class) .thenThrow(UncheckedIOException.class) .thenThrow(UncheckedIOException.class) .thenThrow(UncheckedIOException.class) - .thenReturn(new TestClientTransport()); + .thenReturn(new TestClientTransport().connect(0)); Mono rSocketMono = - RSocketFactory.connect() - .singleSubscriberRequester() + RSocketConnector.create() .reconnect( Retry.backoff(4, Duration.ofMillis(100)) .maxBackoff(Duration.ofMillis(500)) .doAfterRetry(onRetry())) - .transport(mockTransportSupplier) - .start(); + .connect(transport); RSocket rSocket1 = rSocketMono.block(); RSocket rSocket2 = rSocketMono.block(); @@ -101,23 +92,21 @@ public void shouldBeRetrieableConnectionSharedReconnectableInstanceOfRSocketMono @Test @SuppressWarnings({"rawtype", "unchecked"}) public void shouldBeExaustedRetrieableConnectionSharedReconnectableInstanceOfRSocketMono() { - Supplier mockTransportSupplier = Mockito.mock(Supplier.class); - Mockito.when(mockTransportSupplier.get()) + ClientTransport transport = Mockito.mock(ClientTransport.class); + Mockito.when(transport.connect(0)) .thenThrow(UncheckedIOException.class) .thenThrow(UncheckedIOException.class) .thenThrow(UncheckedIOException.class) .thenThrow(UncheckedIOException.class) .thenThrow(UncheckedIOException.class) - .thenReturn(new TestClientTransport()); + .thenReturn(new TestClientTransport().connect(0)); Mono rSocketMono = - RSocketFactory.connect() - .singleSubscriberRequester() + RSocketConnector.create() .reconnect( Retry.backoff(4, Duration.ofMillis(100)) .maxBackoff(Duration.ofMillis(500)) .doAfterRetry(onRetry())) - .transport(mockTransportSupplier) - .start(); + .connect(transport); Assertions.assertThatThrownBy(rSocketMono::block) .matches(Exceptions::isRetryExhausted) @@ -137,11 +126,7 @@ public void shouldBeExaustedRetrieableConnectionSharedReconnectableInstanceOfRSo @Test public void shouldBeNotBeASharedReconnectableInstanceOfRSocketMono() { - Mono rSocketMono = - RSocketFactory.connect() - .singleSubscriberRequester() - .transport(new TestClientTransport()) - .start(); + Mono rSocketMono = RSocketConnector.connectWith(new TestClientTransport()); RSocket rSocket1 = rSocketMono.block(); RSocket rSocket2 = rSocketMono.block(); diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketServerFragmentationTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketServerFragmentationTest.java new file mode 100644 index 000000000..9d105a8c9 --- /dev/null +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketServerFragmentationTest.java @@ -0,0 +1,43 @@ +package io.rsocket.core; + +import io.rsocket.test.util.TestClientTransport; +import io.rsocket.test.util.TestServerTransport; +import org.assertj.core.api.Assertions; +import org.junit.Test; + +public class RSocketServerFragmentationTest { + + @Test + public void serverErrorsWithEnabledFragmentationOnInsufficientMtu() { + Assertions.assertThatIllegalArgumentException() + .isThrownBy(() -> RSocketServer.create().fragment(2)) + .withMessage("smallest allowed mtu size is 64 bytes, provided: 2"); + } + + @Test + public void serverSucceedsWithEnabledFragmentationOnSufficientMtu() { + RSocketServer.create().fragment(100).bind(new TestServerTransport()).block(); + } + + @Test + public void serverSucceedsWithDisabledFragmentation() { + RSocketServer.create().bind(new TestServerTransport()).block(); + } + + @Test + public void clientErrorsWithEnabledFragmentationOnInsufficientMtu() { + Assertions.assertThatIllegalArgumentException() + .isThrownBy(() -> RSocketConnector.create().fragment(2)) + .withMessage("smallest allowed mtu size is 64 bytes, provided: 2"); + } + + @Test + public void clientSucceedsWithEnabledFragmentationOnSufficientMtu() { + RSocketConnector.create().fragment(100).connect(TestClientTransport::new).block(); + } + + @Test + public void clientSucceedsWithDisabledFragmentation() { + RSocketConnector.connectWith(new TestClientTransport()).block(); + } +} diff --git a/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java b/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java index 9344d69da..5b489896b 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java @@ -32,7 +32,7 @@ void responderRejectSetup() { String errorMsg = "error"; RejectingAcceptor acceptor = new RejectingAcceptor(errorMsg); - RSocketFactory.receive().acceptor(acceptor).transport(transport).start().block(); + RSocketServer.create().acceptor(acceptor).bind(transport).block(); transport.connect(); diff --git a/rsocket-core/src/test/java/io/rsocket/internal/ClientServerInputMultiplexerTest.java b/rsocket-core/src/test/java/io/rsocket/internal/ClientServerInputMultiplexerTest.java index 8f56608d8..9a4b5d2db 100644 --- a/rsocket-core/src/test/java/io/rsocket/internal/ClientServerInputMultiplexerTest.java +++ b/rsocket-core/src/test/java/io/rsocket/internal/ClientServerInputMultiplexerTest.java @@ -22,7 +22,7 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import io.rsocket.frame.*; -import io.rsocket.plugins.PluginRegistry; +import io.rsocket.plugins.InitializingInterceptorRegistry; import io.rsocket.test.util.TestDuplexConnection; import io.rsocket.util.DefaultPayload; import java.util.concurrent.atomic.AtomicInteger; @@ -38,8 +38,10 @@ public class ClientServerInputMultiplexerTest { @Before public void setup() { source = new TestDuplexConnection(); - clientMultiplexer = new ClientServerInputMultiplexer(source, new PluginRegistry(), true); - serverMultiplexer = new ClientServerInputMultiplexer(source, new PluginRegistry(), false); + clientMultiplexer = + new ClientServerInputMultiplexer(source, new InitializingInterceptorRegistry(), true); + serverMultiplexer = + new ClientServerInputMultiplexer(source, new InitializingInterceptorRegistry(), false); } @Test diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/channel/ChannelEchoClient.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/channel/ChannelEchoClient.java index ac889ecfc..44daf8c67 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/channel/ChannelEchoClient.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/channel/ChannelEchoClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,67 +20,49 @@ import io.rsocket.ConnectionSetupPayload; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; import io.rsocket.SocketAcceptor; -import io.rsocket.frame.decoder.PayloadDecoder; -import io.rsocket.transport.local.LocalClientTransport; -import io.rsocket.transport.local.LocalServerTransport; -import io.rsocket.util.ByteBufPayload; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; +import io.rsocket.transport.netty.client.TcpClientTransport; +import io.rsocket.transport.netty.server.TcpServerTransport; +import io.rsocket.util.DefaultPayload; import java.time.Duration; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; public final class ChannelEchoClient { - static final Payload payload1 = ByteBufPayload.create("Hello "); public static void main(String[] args) { - RSocketFactory.receive() - .frameDecoder(PayloadDecoder.ZERO_COPY) - .acceptor(new SocketAcceptorImpl()) - .transport(LocalServerTransport.create("localhost")) - .start() + RSocketServer.create(new EchoAcceptor()) + .bind(TcpServerTransport.create("localhost", 7000)) .subscribe(); RSocket socket = - RSocketFactory.connect() - .keepAliveAckTimeout(Duration.ofMinutes(10)) - .frameDecoder(PayloadDecoder.ZERO_COPY) - .transport(LocalClientTransport.create("localhost")) - .start() - .block(); + RSocketConnector.connectWith(TcpClientTransport.create("localhost", 7000)).block(); - Flux.range(0, 100000000) - .concatMap(i -> socket.fireAndForget(payload1.retain())) - // .doOnNext(p -> { - //// System.out.println(p.getDataUtf8()); - // p.release(); - // }) - .blockLast(); + socket + .requestChannel( + Flux.interval(Duration.ofMillis(1000)).map(i -> DefaultPayload.create("Hello"))) + .map(Payload::getDataUtf8) + .doOnNext(System.out::println) + .take(10) + .doFinally(signalType -> socket.dispose()) + .then() + .block(); } - private static class SocketAcceptorImpl implements SocketAcceptor { + private static class EchoAcceptor implements SocketAcceptor { @Override public Mono accept(ConnectionSetupPayload setupPayload, RSocket reactiveSocket) { return Mono.just( new AbstractRSocket() { - - @Override - public Mono fireAndForget(Payload payload) { - // System.out.println(payload.getDataUtf8()); - payload.release(); - return Mono.empty(); - } - - @Override - public Mono requestResponse(Payload payload) { - return Mono.just(payload); - } - @Override public Flux requestChannel(Publisher payloads) { - return Flux.from(payloads).subscribeOn(Schedulers.single()); + return Flux.from(payloads) + .map(Payload::getDataUtf8) + .map(s -> "Echo: " + s) + .map(DefaultPayload::create); } }); } diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/duplex/DuplexClient.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/duplex/DuplexClient.java index c0a271d66..bfa58bf40 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/duplex/DuplexClient.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/duplex/DuplexClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,8 @@ import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; import io.rsocket.transport.netty.client.TcpClientTransport; import io.rsocket.transport.netty.server.TcpServerTransport; import io.rsocket.util.DefaultPayload; @@ -30,10 +31,9 @@ public final class DuplexClient { public static void main(String[] args) { - RSocketFactory.receive() - .acceptor( - (setup, reactiveSocket) -> { - reactiveSocket + RSocketServer.create( + (setup, rsocket) -> { + rsocket .requestStream(DefaultPayload.create("Hello-Bidi")) .map(Payload::getDataUtf8) .log() @@ -41,23 +41,22 @@ public static void main(String[] args) { return Mono.just(new AbstractRSocket() {}); }) - .transport(TcpServerTransport.create("localhost", 7000)) - .start() + .bind(TcpServerTransport.create("localhost", 7000)) .subscribe(); RSocket socket = - RSocketFactory.connect() + RSocketConnector.create() .acceptor( - rSocket -> - new AbstractRSocket() { - @Override - public Flux requestStream(Payload payload) { - return Flux.interval(Duration.ofSeconds(1)) - .map(aLong -> DefaultPayload.create("Bi-di Response => " + aLong)); - } - }) - .transport(TcpClientTransport.create("localhost", 7000)) - .start() + (setup, rsocket) -> + Mono.just( + new AbstractRSocket() { + @Override + public Flux requestStream(Payload payload) { + return Flux.interval(Duration.ofSeconds(1)) + .map(aLong -> DefaultPayload.create("Bi-di Response => " + aLong)); + } + })) + .connect(TcpClientTransport.create("localhost", 7000)) .block(); socket.onClose().block(); diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/lease/LeaseExample.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/lease/LeaseExample.java index 7482c7d1a..a12c9a170 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/lease/LeaseExample.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/lease/LeaseExample.java @@ -1,3 +1,19 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.rsocket.examples.transport.tcp.lease; import static java.time.Duration.ofSeconds; @@ -5,7 +21,8 @@ import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; import io.rsocket.lease.Lease; import io.rsocket.lease.LeaseStats; import io.rsocket.lease.Leases; @@ -27,28 +44,26 @@ public class LeaseExample { public static void main(String[] args) { CloseableChannel server = - RSocketFactory.receive() + RSocketServer.create( + (setup, sendingRSocket) -> Mono.just(new ServerAcceptor(sendingRSocket))) .lease( () -> Leases.create() .sender(new LeaseSender(SERVER_TAG, 7_000, 5)) .receiver(new LeaseReceiver(SERVER_TAG)) .stats(new NoopStats())) - .acceptor((setup, sendingRSocket) -> Mono.just(new ServerAcceptor(sendingRSocket))) - .transport(TcpServerTransport.create("localhost", 7000)) - .start() + .bind(TcpServerTransport.create("localhost", 7000)) .block(); RSocket clientRSocket = - RSocketFactory.connect() + RSocketConnector.create() .lease( () -> Leases.create() .sender(new LeaseSender(CLIENT_TAG, 3_000, 5)) .receiver(new LeaseReceiver(CLIENT_TAG))) - .acceptor(rSocket -> new ClientAcceptor()) - .transport(TcpClientTransport.create(server.address())) - .start() + .acceptor((rSocket, setup) -> Mono.just(new ClientAcceptor())) + .connect(TcpClientTransport.create(server.address())) .block(); Flux.interval(ofSeconds(1)) diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/requestresponse/HelloWorldClient.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/requestresponse/HelloWorldClient.java index 537485fa4..26a79c4e1 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/requestresponse/HelloWorldClient.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/requestresponse/HelloWorldClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,8 @@ import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; import io.rsocket.transport.netty.client.TcpClientTransport; import io.rsocket.transport.netty.server.TcpServerTransport; import io.rsocket.util.DefaultPayload; @@ -28,8 +29,7 @@ public final class HelloWorldClient { public static void main(String[] args) { - RSocketFactory.receive() - .acceptor( + RSocketServer.create( (setupPayload, reactiveSocket) -> Mono.just( new AbstractRSocket() { @@ -45,15 +45,11 @@ public Mono requestResponse(Payload p) { } } })) - .transport(TcpServerTransport.create("localhost", 7000)) - .start() + .bind(TcpServerTransport.create("localhost", 7000)) .subscribe(); RSocket socket = - RSocketFactory.connect() - .transport(TcpClientTransport.create("localhost", 7000)) - .start() - .block(); + RSocketConnector.connectWith(TcpClientTransport.create("localhost", 7000)).block(); socket .requestResponse(DefaultPayload.create("Hello")) diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/ResumeFileTransfer.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/ResumeFileTransfer.java index ca115d281..3cae64409 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/ResumeFileTransfer.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/ResumeFileTransfer.java @@ -1,9 +1,27 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.rsocket.examples.transport.tcp.resume; import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; +import io.rsocket.core.Resume; import io.rsocket.resume.ClientResume; import io.rsocket.resume.PeriodicResumeStrategy; import io.rsocket.resume.ResumeStrategy; @@ -22,24 +40,22 @@ public class ResumeFileTransfer { public static void main(String[] args) { RequestCodec requestCodec = new RequestCodec(); + Resume resume = + new Resume() + .sessionDuration(Duration.ofMinutes(5)) + .resumeStrategy( + () -> new VerboseResumeStrategy(new PeriodicResumeStrategy(Duration.ofSeconds(1)))); CloseableChannel server = - RSocketFactory.receive() - .resume() - .resumeSessionDuration(Duration.ofMinutes(5)) - .acceptor((setup, rSocket) -> Mono.just(new FileServer(requestCodec))) - .transport(TcpServerTransport.create("localhost", 8000)) - .start() + RSocketServer.create((setup, rSocket) -> Mono.just(new FileServer(requestCodec))) + .resume(resume) + .bind(TcpServerTransport.create("localhost", 8000)) .block(); RSocket client = - RSocketFactory.connect() - .resume() - .resumeStrategy( - () -> new VerboseResumeStrategy(new PeriodicResumeStrategy(Duration.ofSeconds(1)))) - .resumeSessionDuration(Duration.ofMinutes(5)) - .transport(TcpClientTransport.create("localhost", 8001)) - .start() + RSocketConnector.create() + .resume(resume) + .connect(TcpClientTransport.create("localhost", 8001)) .block(); client diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/stream/StreamingClient.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/stream/StreamingClient.java index 57a659c1d..5f7c777db 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/stream/StreamingClient.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/stream/StreamingClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,13 @@ package io.rsocket.examples.transport.tcp.stream; -import io.rsocket.*; +import io.rsocket.AbstractRSocket; +import io.rsocket.ConnectionSetupPayload; +import io.rsocket.Payload; +import io.rsocket.RSocket; +import io.rsocket.SocketAcceptor; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; import io.rsocket.transport.netty.client.TcpClientTransport; import io.rsocket.transport.netty.server.TcpServerTransport; import io.rsocket.util.DefaultPayload; @@ -27,17 +33,12 @@ public final class StreamingClient { public static void main(String[] args) { - RSocketFactory.receive() - .acceptor(new SocketAcceptorImpl()) - .transport(TcpServerTransport.create("localhost", 7000)) - .start() + RSocketServer.create(new SocketAcceptorImpl()) + .bind(TcpServerTransport.create("localhost", 7000)) .subscribe(); RSocket socket = - RSocketFactory.connect() - .transport(TcpClientTransport.create("localhost", 7000)) - .start() - .block(); + RSocketConnector.connectWith(TcpClientTransport.create("localhost", 7000)).block(); socket .requestStream(DefaultPayload.create("Hello")) diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/ws/WebSocketHeadersSample.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/ws/WebSocketHeadersSample.java index d3865c01b..908505a2f 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/ws/WebSocketHeadersSample.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/ws/WebSocketHeadersSample.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,8 +22,9 @@ import io.rsocket.DuplexConnection; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; import io.rsocket.SocketAcceptor; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.transport.ServerTransport; import io.rsocket.transport.netty.WebsocketDuplexConnection; @@ -45,10 +46,10 @@ public class WebSocketHeadersSample { public static void main(String[] args) { ServerTransport.ConnectionAcceptor acceptor = - RSocketFactory.receive() - .frameDecoder(PayloadDecoder.ZERO_COPY) + RSocketServer.create() + .payloadDecoder(PayloadDecoder.ZERO_COPY) .acceptor(new SocketAcceptorImpl()) - .toConnectionAcceptor(); + .asConnectionAcceptor(); DisposableServer disposableServer = HttpServer.create() @@ -82,11 +83,10 @@ public static void main(String[] args) { }); RSocket socket = - RSocketFactory.connect() - .keepAliveAckTimeout(Duration.ofMinutes(10)) - .frameDecoder(PayloadDecoder.ZERO_COPY) - .transport(clientTransport) - .start() + RSocketConnector.create() + .keepAlive(Duration.ofMinutes(10), Duration.ofMinutes(10)) + .payloadDecoder(PayloadDecoder.ZERO_COPY) + .connect(clientTransport) .block(); Flux.range(0, 100) @@ -102,11 +102,10 @@ public static void main(String[] args) { WebsocketClientTransport.create(disposableServer.host(), disposableServer.port()); RSocket rSocket = - RSocketFactory.connect() - .keepAliveAckTimeout(Duration.ofMinutes(10)) - .frameDecoder(PayloadDecoder.ZERO_COPY) - .transport(clientTransport2) - .start() + RSocketConnector.create() + .keepAlive(Duration.ofMinutes(10), Duration.ofMinutes(10)) + .payloadDecoder(PayloadDecoder.ZERO_COPY) + .connect(clientTransport2) .block(); // expect error here because of closed channel diff --git a/rsocket-examples/src/test/java/io/rsocket/integration/IntegrationTest.java b/rsocket-examples/src/test/java/io/rsocket/integration/IntegrationTest.java index 19c29061b..1ef7771cd 100644 --- a/rsocket-examples/src/test/java/io/rsocket/integration/IntegrationTest.java +++ b/rsocket-examples/src/test/java/io/rsocket/integration/IntegrationTest.java @@ -26,7 +26,8 @@ import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; import io.rsocket.plugins.DuplexConnectionInterceptor; import io.rsocket.plugins.RSocketInterceptor; import io.rsocket.plugins.SocketAcceptorInterceptor; @@ -39,7 +40,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.reactivestreams.Publisher; @@ -49,19 +49,20 @@ public class IntegrationTest { - private static final RSocketInterceptor requesterPlugin; - private static final RSocketInterceptor responderPlugin; - private static final SocketAcceptorInterceptor clientAcceptorPlugin; - private static final SocketAcceptorInterceptor serverAcceptorPlugin; - private static final DuplexConnectionInterceptor connectionPlugin; - public static volatile boolean calledRequester = false; - public static volatile boolean calledResponder = false; - public static volatile boolean calledClientAcceptor = false; - public static volatile boolean calledServerAcceptor = false; - public static volatile boolean calledFrame = false; + private static final RSocketInterceptor requesterInterceptor; + private static final RSocketInterceptor responderInterceptor; + private static final SocketAcceptorInterceptor clientAcceptorInterceptor; + private static final SocketAcceptorInterceptor serverAcceptorInterceptor; + private static final DuplexConnectionInterceptor connectionInterceptor; + + private static volatile boolean calledRequester = false; + private static volatile boolean calledResponder = false; + private static volatile boolean calledClientAcceptor = false; + private static volatile boolean calledServerAcceptor = false; + private static volatile boolean calledFrame = false; static { - requesterPlugin = + requesterInterceptor = reactiveSocket -> new RSocketProxy(reactiveSocket) { @Override @@ -71,7 +72,7 @@ public Mono requestResponse(Payload payload) { } }; - responderPlugin = + responderInterceptor = reactiveSocket -> new RSocketProxy(reactiveSocket) { @Override @@ -81,21 +82,21 @@ public Mono requestResponse(Payload payload) { } }; - clientAcceptorPlugin = + clientAcceptorInterceptor = acceptor -> (setup, sendingSocket) -> { calledClientAcceptor = true; return acceptor.accept(setup, sendingSocket); }; - serverAcceptorPlugin = + serverAcceptorInterceptor = acceptor -> (setup, sendingSocket) -> { calledServerAcceptor = true; return acceptor.accept(setup, sendingSocket); }; - connectionPlugin = + connectionInterceptor = (type, connection) -> { calledFrame = true; return connection; @@ -114,18 +115,8 @@ public void startup() { requestCount = new AtomicInteger(); disconnectionCounter = new CountDownLatch(1); - TcpServerTransport serverTransport = TcpServerTransport.create("localhost", 0); - server = - RSocketFactory.receive() - .addResponderPlugin(responderPlugin) - .addSocketAcceptorPlugin(serverAcceptorPlugin) - .addConnectionPlugin(connectionPlugin) - .errorConsumer( - t -> { - errorCount.incrementAndGet(); - }) - .acceptor( + RSocketServer.create( (setup, sendingSocket) -> { sendingSocket .onClose() @@ -152,17 +143,24 @@ public Flux requestChannel(Publisher payloads) { } }); }) - .transport(serverTransport) - .start() + .interceptors( + registry -> + registry + .forResponder(responderInterceptor) + .forSocketAcceptor(serverAcceptorInterceptor) + .forConnection(connectionInterceptor)) + .bind(TcpServerTransport.create("localhost", 0)) .block(); client = - RSocketFactory.connect() - .addRequesterPlugin(requesterPlugin) - .addSocketAcceptorPlugin(clientAcceptorPlugin) - .addConnectionPlugin(connectionPlugin) - .transport(TcpClientTransport.create(server.address())) - .start() + RSocketConnector.create() + .interceptors( + registry -> + registry + .forRequester(requesterInterceptor) + .forSocketAcceptor(clientAcceptorInterceptor) + .forConnection(connectionInterceptor)) + .connect(TcpClientTransport.create(server.address())) .block(); } @@ -204,8 +202,6 @@ public void testCallRequestWithErrorAndThenRequest() { } catch (Throwable t) { } - Assert.assertEquals(1, errorCount.incrementAndGet()); - testRequest(); } } diff --git a/rsocket-examples/src/test/java/io/rsocket/integration/InteractionsLoadTest.java b/rsocket-examples/src/test/java/io/rsocket/integration/InteractionsLoadTest.java index 7a30a7fd1..d24083ea6 100644 --- a/rsocket-examples/src/test/java/io/rsocket/integration/InteractionsLoadTest.java +++ b/rsocket-examples/src/test/java/io/rsocket/integration/InteractionsLoadTest.java @@ -3,7 +3,8 @@ import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; import io.rsocket.test.SlowTest; import io.rsocket.transport.netty.client.TcpClientTransport; import io.rsocket.transport.netty.server.CloseableChannel; @@ -21,25 +22,20 @@ public class InteractionsLoadTest { @Test @SlowTest public void channel() { - TcpServerTransport serverTransport = TcpServerTransport.create("localhost", 0); - CloseableChannel server = - RSocketFactory.receive() - .acceptor((setup, rsocket) -> Mono.just(new EchoRSocket())) - .transport(serverTransport) - .start() + RSocketServer.create((setup, rsocket) -> Mono.just(new EchoRSocket())) + .bind(TcpServerTransport.create("localhost", 0)) .block(Duration.ofSeconds(10)); - TcpClientTransport transport = TcpClientTransport.create(server.address()); - - RSocket client = - RSocketFactory.connect().transport(transport).start().block(Duration.ofSeconds(10)); + RSocket clientRSocket = + RSocketConnector.connectWith(TcpClientTransport.create(server.address())) + .block(Duration.ofSeconds(10)); int concurrency = 16; Flux.range(1, concurrency) .flatMap( v -> - client + clientRSocket .requestChannel( input().onBackpressureDrop().map(iv -> DefaultPayload.create("foo"))) .limitRate(10000), diff --git a/rsocket-examples/src/test/java/io/rsocket/integration/TcpIntegrationTest.java b/rsocket-examples/src/test/java/io/rsocket/integration/TcpIntegrationTest.java index 9e7f5b0a7..7133820ca 100644 --- a/rsocket-examples/src/test/java/io/rsocket/integration/TcpIntegrationTest.java +++ b/rsocket-examples/src/test/java/io/rsocket/integration/TcpIntegrationTest.java @@ -22,7 +22,8 @@ import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; import io.rsocket.transport.netty.client.TcpClientTransport; import io.rsocket.transport.netty.server.CloseableChannel; import io.rsocket.transport.netty.server.TcpServerTransport; @@ -46,20 +47,14 @@ public class TcpIntegrationTest { @Before public void startup() { - TcpServerTransport serverTransport = TcpServerTransport.create("localhost", 0); server = - RSocketFactory.receive() - .acceptor((setup, sendingSocket) -> Mono.just(new RSocketProxy(handler))) - .transport(serverTransport) - .start() + RSocketServer.create((setup, sendingSocket) -> Mono.just(new RSocketProxy(handler))) + .bind(TcpServerTransport.create("localhost", 0)) .block(); } private RSocket buildClient() { - return RSocketFactory.connect() - .transport(TcpClientTransport.create(server.address())) - .start() - .block(); + return RSocketConnector.connectWith(TcpClientTransport.create(server.address())).block(); } @After diff --git a/rsocket-examples/src/test/java/io/rsocket/integration/TestingStreaming.java b/rsocket-examples/src/test/java/io/rsocket/integration/TestingStreaming.java index ec1d41bf9..8fe09430a 100644 --- a/rsocket-examples/src/test/java/io/rsocket/integration/TestingStreaming.java +++ b/rsocket-examples/src/test/java/io/rsocket/integration/TestingStreaming.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,60 +16,52 @@ package io.rsocket.integration; -import io.rsocket.*; +import io.rsocket.AbstractRSocket; +import io.rsocket.Closeable; +import io.rsocket.Payload; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; import io.rsocket.exceptions.ApplicationErrorException; -import io.rsocket.transport.ClientTransport; -import io.rsocket.transport.ServerTransport; import io.rsocket.transport.local.LocalClientTransport; import io.rsocket.transport.local.LocalServerTransport; import io.rsocket.util.DefaultPayload; import java.time.Duration; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; import org.junit.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; public class TestingStreaming { - private Supplier> serverSupplier = - () -> LocalServerTransport.create("test"); - - private Supplier clientSupplier = () -> LocalClientTransport.create("test"); + LocalServerTransport serverTransport = LocalServerTransport.create("test"); @Test(expected = ApplicationErrorException.class) public void testRangeButThrowException() { Closeable server = null; try { server = - RSocketFactory.receive() - .errorConsumer(Throwable::printStackTrace) - .acceptor( - (connectionSetupPayload, rSocket) -> { - AbstractRSocket abstractRSocket = - new AbstractRSocket() { - @Override - public double availability() { - return 1.0; - } - - @Override - public Flux requestStream(Payload payload) { - return Flux.range(1, 1000) - .doOnNext( - i -> { - if (i > 3) { - throw new RuntimeException("BOOM!"); - } - }) - .map(l -> DefaultPayload.create("l -> " + l)) - .cast(Payload.class); - } - }; - - return Mono.just(abstractRSocket); - }) - .transport(serverSupplier.get()) - .start() + RSocketServer.create( + (connectionSetupPayload, rSocket) -> + Mono.just( + new AbstractRSocket() { + @Override + public double availability() { + return 1.0; + } + + @Override + public Flux requestStream(Payload payload) { + return Flux.range(1, 1000) + .doOnNext( + i -> { + if (i > 3) { + throw new RuntimeException("BOOM!"); + } + }) + .map(l -> DefaultPayload.create("l -> " + l)) + .cast(Payload.class); + } + })) + .bind(serverTransport) .block(); Flux.range(1, 6).flatMap(i -> consumer("connection number -> " + i)).blockLast(); @@ -85,29 +77,23 @@ public void testRangeOfConsumers() { Closeable server = null; try { server = - RSocketFactory.receive() - .errorConsumer(Throwable::printStackTrace) - .acceptor( - (connectionSetupPayload, rSocket) -> { - AbstractRSocket abstractRSocket = - new AbstractRSocket() { - @Override - public double availability() { - return 1.0; - } - - @Override - public Flux requestStream(Payload payload) { - return Flux.range(1, 1000) - .map(l -> DefaultPayload.create("l -> " + l)) - .cast(Payload.class); - } - }; - - return Mono.just(abstractRSocket); - }) - .transport(serverSupplier.get()) - .start() + RSocketServer.create( + (connectionSetupPayload, rSocket) -> + Mono.just( + new AbstractRSocket() { + @Override + public double availability() { + return 1.0; + } + + @Override + public Flux requestStream(Payload payload) { + return Flux.range(1, 1000) + .map(l -> DefaultPayload.create("l -> " + l)) + .cast(Payload.class); + } + })) + .bind(serverTransport) .block(); Flux.range(1, 6).flatMap(i -> consumer("connection number -> " + i)).blockLast(); @@ -119,10 +105,7 @@ public Flux requestStream(Payload payload) { } private Flux consumer(String s) { - return RSocketFactory.connect() - .errorConsumer(Throwable::printStackTrace) - .transport(clientSupplier) - .start() + return RSocketConnector.connectWith(LocalClientTransport.create("test")) .flatMapMany( rSocket -> { AtomicInteger count = new AtomicInteger(); @@ -135,31 +118,25 @@ private Flux consumer(String s) { @Test public void testSingleConsumer() { Closeable server = null; - try { server = - RSocketFactory.receive() - .acceptor( - (connectionSetupPayload, rSocket) -> { - AbstractRSocket abstractRSocket = - new AbstractRSocket() { - @Override - public double availability() { - return 1.0; - } - - @Override - public Flux requestStream(Payload payload) { - return Flux.range(1, 10_000) - .map(l -> DefaultPayload.create("l -> " + l)) - .cast(Payload.class); - } - }; - - return Mono.just(abstractRSocket); - }) - .transport(serverSupplier.get()) - .start() + RSocketServer.create( + (connectionSetupPayload, rSocket) -> + Mono.just( + new AbstractRSocket() { + @Override + public double availability() { + return 1.0; + } + + @Override + public Flux requestStream(Payload payload) { + return Flux.range(1, 10_000) + .map(l -> DefaultPayload.create("l -> " + l)) + .cast(Payload.class); + } + })) + .bind(serverTransport) .block(); consumer("1").blockLast(); diff --git a/rsocket-examples/src/test/java/io/rsocket/resume/ResumeIntegrationTest.java b/rsocket-examples/src/test/java/io/rsocket/resume/ResumeIntegrationTest.java index 009d0d8db..685cef41e 100644 --- a/rsocket-examples/src/test/java/io/rsocket/resume/ResumeIntegrationTest.java +++ b/rsocket-examples/src/test/java/io/rsocket/resume/ResumeIntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2019 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,9 @@ import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; +import io.rsocket.core.Resume; import io.rsocket.exceptions.RejectedResumeException; import io.rsocket.exceptions.UnsupportedSetupException; import io.rsocket.test.SlowTest; @@ -106,8 +108,7 @@ public void reconnectOnMissingSession() { ErrorConsumer errorConsumer = new ErrorConsumer(); int clientSessionDurationSeconds = 10; - RSocket rSocket = - newClientRSocket(clientTransport, clientSessionDurationSeconds, errorConsumer).block(); + RSocket rSocket = newClientRSocket(clientTransport, clientSessionDurationSeconds).block(); Mono.delay(Duration.ofSeconds(1)) .subscribe(v -> clientTransport.disconnectFor(Duration.ofSeconds(3))); @@ -129,20 +130,16 @@ public void reconnectOnMissingSession() { @Test void serverMissingResume() { CloseableChannel closeableChannel = - RSocketFactory.receive() - .acceptor((setupPayload, rSocket) -> Mono.just(new TestResponderRSocket())) - .transport(serverTransport(SERVER_HOST, SERVER_PORT)) - .start() + RSocketServer.create((setupPayload, rSocket) -> Mono.just(new TestResponderRSocket())) + .bind(serverTransport(SERVER_HOST, SERVER_PORT)) .block(); ErrorConsumer errorConsumer = new ErrorConsumer(); RSocket rSocket = - RSocketFactory.connect() - .resume() - .errorConsumer(errorConsumer) - .transport(clientTransport(closeableChannel.address())) - .start() + RSocketConnector.create() + .resume(new Resume()) + .connect(clientTransport(closeableChannel.address())) .block(); StepVerifier.create(errorConsumer.errors().next().doFinally(s -> closeableChannel.dispose())) @@ -201,24 +198,15 @@ private void throwOnNonContinuous(AtomicInteger counter, String x) { private static Mono newClientRSocket( DisconnectableClientTransport clientTransport, int sessionDurationSeconds) { - return newClientRSocket(clientTransport, sessionDurationSeconds, err -> {}); - } - - private static Mono newClientRSocket( - DisconnectableClientTransport clientTransport, - int sessionDurationSeconds, - Consumer errConsumer) { - return RSocketFactory.connect() - .resume() - .resumeSessionDuration(Duration.ofSeconds(sessionDurationSeconds)) - .resumeStore(t -> new InMemoryResumableFramesStore("client", 500_000)) - .resumeCleanupOnKeepAlive() - .keepAliveTickPeriod(Duration.ofSeconds(5)) - .keepAliveAckTimeout(Duration.ofMinutes(5)) - .errorConsumer(errConsumer) - .resumeStrategy(() -> new PeriodicResumeStrategy(Duration.ofSeconds(1))) - .transport(clientTransport) - .start(); + return RSocketConnector.create() + .resume( + new Resume() + .sessionDuration(Duration.ofSeconds(sessionDurationSeconds)) + .storeFactory(t -> new InMemoryResumableFramesStore("client", 500_000)) + .cleanupStoreOnKeepAlive() + .resumeStrategy(() -> new PeriodicResumeStrategy(Duration.ofSeconds(1)))) + .keepAlive(Duration.ofSeconds(5), Duration.ofMinutes(5)) + .connect(clientTransport); } private static Mono newServerRSocket() { @@ -226,14 +214,13 @@ private static Mono newServerRSocket() { } private static Mono newServerRSocket(int sessionDurationSeconds) { - return RSocketFactory.receive() - .resume() - .resumeStore(t -> new InMemoryResumableFramesStore("server", 500_000)) - .resumeSessionDuration(Duration.ofSeconds(sessionDurationSeconds)) - .resumeCleanupOnKeepAlive() - .acceptor((setupPayload, rSocket) -> Mono.just(new TestResponderRSocket())) - .transport(serverTransport(SERVER_HOST, SERVER_PORT)) - .start(); + return RSocketServer.create((setup, rsocket) -> Mono.just(new TestResponderRSocket())) + .resume( + new Resume() + .sessionDuration(Duration.ofSeconds(sessionDurationSeconds)) + .cleanupStoreOnKeepAlive() + .storeFactory(t -> new InMemoryResumableFramesStore("server", 500_000))) + .bind(serverTransport(SERVER_HOST, SERVER_PORT)); } private static class TestResponderRSocket extends AbstractRSocket { diff --git a/rsocket-test/src/main/java/io/rsocket/test/ClientSetupRule.java b/rsocket-test/src/main/java/io/rsocket/test/ClientSetupRule.java index ec143b7ab..6f562875f 100644 --- a/rsocket-test/src/main/java/io/rsocket/test/ClientSetupRule.java +++ b/rsocket-test/src/main/java/io/rsocket/test/ClientSetupRule.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,8 @@ import io.rsocket.Closeable; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; import io.rsocket.transport.ClientTransport; import io.rsocket.transport.ServerTransport; import java.util.function.BiFunction; @@ -47,17 +48,13 @@ public ClientSetupRule( this.serverInit = address -> - RSocketFactory.receive() - .acceptor((setup, sendingSocket) -> Mono.just(new TestRSocket(data, metadata))) - .transport(serverTransportSupplier.apply(address)) - .start() + RSocketServer.create((setup, rsocket) -> Mono.just(new TestRSocket(data, metadata))) + .bind(serverTransportSupplier.apply(address)) .block(); this.clientConnector = (address, server) -> - RSocketFactory.connect() - .transport(clientTransportSupplier.apply(address, server)) - .start() + RSocketConnector.connectWith(clientTransportSupplier.apply(address, server)) .doOnError(Throwable::printStackTrace) .block(); } diff --git a/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java b/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java index 1c00b0502..e4ff75b2a 100644 --- a/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java +++ b/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,8 @@ import io.rsocket.Closeable; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; import io.rsocket.transport.ClientTransport; import io.rsocket.transport.ServerTransport; import io.rsocket.util.DefaultPayload; @@ -380,17 +381,12 @@ public TransportPair( T address = addressSupplier.get(); server = - RSocketFactory.receive() - .acceptor((setup, sendingSocket) -> Mono.just(new TestRSocket(data, metadata))) - .transport(serverTransportSupplier.apply(address)) - .start() + RSocketServer.create((setup, sendingSocket) -> Mono.just(new TestRSocket(data, metadata))) + .bind(serverTransportSupplier.apply(address)) .block(); client = - RSocketFactory.connect() - .keepAlive(Duration.ZERO, Duration.ZERO, 1) - .transport(clientTransportSupplier.apply(address, server)) - .start() + RSocketConnector.connectWith(clientTransportSupplier.apply(address, server)) .doOnError(Throwable::printStackTrace) .block(); } diff --git a/rsocket-transport-local/src/test/java/io/rsocket/transport/local/LocalPingPong.java b/rsocket-transport-local/src/test/java/io/rsocket/transport/local/LocalPingPong.java index 2e4f93ac4..9228e2d05 100644 --- a/rsocket-transport-local/src/test/java/io/rsocket/transport/local/LocalPingPong.java +++ b/rsocket-transport-local/src/test/java/io/rsocket/transport/local/LocalPingPong.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,8 @@ package io.rsocket.transport.local; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.test.PingClient; import io.rsocket.test.PingHandler; @@ -28,18 +29,15 @@ public final class LocalPingPong { public static void main(String... args) { - RSocketFactory.receive() - .frameDecoder(PayloadDecoder.ZERO_COPY) - .acceptor(new PingHandler()) - .transport(LocalServerTransport.create("test-local-server")) - .start() + RSocketServer.create(new PingHandler()) + .payloadDecoder(PayloadDecoder.ZERO_COPY) + .bind(LocalServerTransport.create("test-local-server")) .block(); Mono client = - RSocketFactory.connect() - .frameDecoder(PayloadDecoder.ZERO_COPY) - .transport(LocalClientTransport.create("test-local-server")) - .start(); + RSocketConnector.create() + .payloadDecoder(PayloadDecoder.ZERO_COPY) + .connect(LocalClientTransport.create("test-local-server")); PingClient pingClient = new PingClient(client); diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/integration/FragmentTest.java b/rsocket-transport-netty/src/test/java/io/rsocket/integration/FragmentTest.java index 575993c18..db2f07bda 100644 --- a/rsocket-transport-netty/src/test/java/io/rsocket/integration/FragmentTest.java +++ b/rsocket-transport-netty/src/test/java/io/rsocket/integration/FragmentTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,8 @@ import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; import io.rsocket.transport.netty.client.TcpClientTransport; import io.rsocket.transport.netty.server.CloseableChannel; import io.rsocket.transport.netty.server.TcpServerTransport; @@ -59,19 +60,16 @@ public void startup() { TcpServerTransport serverTransport = TcpServerTransport.create("localhost", randomPort); server = - RSocketFactory.receive() + RSocketServer.create((setup, sendingSocket) -> Mono.just(new RSocketProxy(handler))) .fragment(frameSize) - .acceptor((setup, sendingSocket) -> Mono.just(new RSocketProxy(handler))) - .transport(serverTransport) - .start() + .bind(serverTransport) .block(); } private RSocket buildClient() { - return RSocketFactory.connect() + return RSocketConnector.create() .fragment(frameSize) - .transport(TcpClientTransport.create(server.address())) - .start() + .connect(TcpClientTransport.create(server.address())) .block(); } diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/RSocketFactoryNettyTransportFragmentationTest.java b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/RSocketFactoryNettyTransportFragmentationTest.java index 07e9378fa..b9c0d4f60 100644 --- a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/RSocketFactoryNettyTransportFragmentationTest.java +++ b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/RSocketFactoryNettyTransportFragmentationTest.java @@ -1,18 +1,15 @@ package io.rsocket.transport.netty; -import static io.rsocket.RSocketFactory.*; - import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; import io.rsocket.SocketAcceptor; -import io.rsocket.transport.ClientTransport; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; import io.rsocket.transport.ServerTransport; import io.rsocket.transport.netty.client.TcpClientTransport; import io.rsocket.transport.netty.server.CloseableChannel; import io.rsocket.transport.netty.server.TcpServerTransport; import io.rsocket.transport.netty.server.WebsocketServerTransport; import java.time.Duration; -import java.util.function.Function; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -22,104 +19,62 @@ class RSocketFactoryNettyTransportFragmentationTest { - @ParameterizedTest - @MethodSource("serverTransportProvider") - void serverErrorsWithEnabledFragmentationOnInsufficientMtu( - ServerTransport serverTransport) { - Mono server = createServer(serverTransport, f -> f.fragment(2)); - - StepVerifier.create(server) - .expectErrorMatches( - err -> - err instanceof IllegalArgumentException - && "smallest allowed mtu size is 64 bytes, provided: 2" - .equals(err.getMessage())) - .verify(Duration.ofSeconds(5)); + static Stream> arguments() { + return Stream.of(TcpServerTransport.create(0), WebsocketServerTransport.create(0)); } @ParameterizedTest - @MethodSource("serverTransportProvider") + @MethodSource("arguments") void serverSucceedsWithEnabledFragmentationOnSufficientMtu( ServerTransport serverTransport) { Mono server = - createServer(serverTransport, f -> f.fragment(100)).doOnNext(CloseableChannel::dispose); + RSocketServer.create(mockAcceptor()) + .fragment(100) + .bind(serverTransport) + .doOnNext(CloseableChannel::dispose); StepVerifier.create(server).expectNextCount(1).expectComplete().verify(Duration.ofSeconds(5)); } @ParameterizedTest - @MethodSource("serverTransportProvider") - void serverSucceedsWithDisabledFragmentation() { + @MethodSource("arguments") + void serverSucceedsWithDisabledFragmentation(ServerTransport serverTransport) { Mono server = - createServer(TcpServerTransport.create("localhost", 0), Function.identity()) + RSocketServer.create(mockAcceptor()) + .bind(serverTransport) .doOnNext(CloseableChannel::dispose); StepVerifier.create(server).expectNextCount(1).expectComplete().verify(Duration.ofSeconds(5)); } @ParameterizedTest - @MethodSource("serverTransportProvider") - void clientErrorsWithEnabledFragmentationOnInsufficientMtu( - ServerTransport serverTransport) { - CloseableChannel server = createServer(serverTransport, f -> f.fragment(100)).block(); - - Mono rSocket = - createClient(TcpClientTransport.create(server.address()), f -> f.fragment(2)) - .doFinally(s -> server.dispose()); - - StepVerifier.create(rSocket) - .expectErrorMatches( - err -> - err instanceof IllegalArgumentException - && "smallest allowed mtu size is 64 bytes, provided: 2" - .equals(err.getMessage())) - .verify(Duration.ofSeconds(5)); - } - - @ParameterizedTest - @MethodSource("serverTransportProvider") + @MethodSource("arguments") void clientSucceedsWithEnabledFragmentationOnSufficientMtu( ServerTransport serverTransport) { - CloseableChannel server = createServer(serverTransport, f -> f.fragment(100)).block(); + CloseableChannel server = + RSocketServer.create(mockAcceptor()).fragment(100).bind(serverTransport).block(); Mono rSocket = - createClient(TcpClientTransport.create(server.address()), f -> f.fragment(100)) + RSocketConnector.create() + .fragment(100) + .connect(TcpClientTransport.create(server.address())) .doFinally(s -> server.dispose()); StepVerifier.create(rSocket).expectNextCount(1).expectComplete().verify(Duration.ofSeconds(5)); } @ParameterizedTest - @MethodSource("serverTransportProvider") - void clientSucceedsWithDisabledFragmentation() { - CloseableChannel server = - createServer(TcpServerTransport.create("localhost", 0), Function.identity()).block(); + @MethodSource("arguments") + void clientSucceedsWithDisabledFragmentation(ServerTransport serverTransport) { + CloseableChannel server = RSocketServer.create(mockAcceptor()).bind(serverTransport).block(); Mono rSocket = - createClient(TcpClientTransport.create(server.address()), Function.identity()) + RSocketConnector.connectWith(TcpClientTransport.create(server.address())) .doFinally(s -> server.dispose()); StepVerifier.create(rSocket).expectNextCount(1).expectComplete().verify(Duration.ofSeconds(5)); } - private Mono createClient( - ClientTransport transport, Function f) { - return f.apply(RSocketFactory.connect()).transport(transport).start(); - } - - private Mono createServer( - ServerTransport transport, - Function f) { - return f.apply(receive()).acceptor(mockAcceptor()).transport(transport).start(); - } - private SocketAcceptor mockAcceptor() { SocketAcceptor mock = Mockito.mock(SocketAcceptor.class); Mockito.when(mock.accept(Mockito.any(), Mockito.any())) .thenReturn(Mono.just(Mockito.mock(RSocket.class))); return mock; } - - static Stream> serverTransportProvider() { - String host = "localhost"; - int port = 0; - return Stream.of( - TcpServerTransport.create(host, port), WebsocketServerTransport.create(host, port)); - } } diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/SetupRejectionTest.java b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/SetupRejectionTest.java index f32d28a0b..6fd3de791 100644 --- a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/SetupRejectionTest.java +++ b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/SetupRejectionTest.java @@ -2,8 +2,9 @@ import io.rsocket.ConnectionSetupPayload; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; import io.rsocket.SocketAcceptor; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; import io.rsocket.exceptions.RejectedSetupException; import io.rsocket.transport.ClientTransport; import io.rsocket.transport.ServerTransport; @@ -41,19 +42,15 @@ void rejectSetupTcp( Mono serverRequester = acceptor.requesterRSocket(); CloseableChannel channel = - RSocketFactory.receive() - .acceptor(acceptor) - .transport(serverTransport.apply(new InetSocketAddress("localhost", 0))) - .start() + RSocketServer.create(acceptor) + .bind(serverTransport.apply(new InetSocketAddress("localhost", 0))) .block(Duration.ofSeconds(5)); ErrorConsumer errorConsumer = new ErrorConsumer(); RSocket clientRequester = - RSocketFactory.connect() - .errorConsumer(errorConsumer) - .transport(clientTransport.apply(channel.address())) - .start() + RSocketConnector.connectWith(clientTransport.apply(channel.address())) + .doOnError(errorConsumer) .block(Duration.ofSeconds(5)); StepVerifier.create(errorConsumer.errors().next()) diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpPing.java b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpPing.java index c2e136635..88c64648c 100644 --- a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpPing.java +++ b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpPing.java @@ -17,7 +17,8 @@ package io.rsocket.transport.netty; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.Resume; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.test.PerfTest; import io.rsocket.test.PingClient; @@ -81,16 +82,15 @@ private static PingClient newResumablePingClient() { } private static PingClient newPingClient(boolean isResumable) { - RSocketFactory.ClientRSocketFactory clientRSocketFactory = RSocketFactory.connect(); + RSocketConnector connector = RSocketConnector.create(); if (isResumable) { - clientRSocketFactory.resume(); + connector.resume(new Resume()); } Mono rSocket = - clientRSocketFactory - .frameDecoder(PayloadDecoder.ZERO_COPY) - .keepAlive(Duration.ofMinutes(1), Duration.ofMinutes(30), 3) - .transport(TcpClientTransport.create(port)) - .start(); + connector + .payloadDecoder(PayloadDecoder.ZERO_COPY) + .keepAlive(Duration.ofMinutes(1), Duration.ofMinutes(30)) + .connect(TcpClientTransport.create(port)); return new PingClient(rSocket); } diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpPongServer.java b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpPongServer.java index b40f35e51..338868470 100644 --- a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpPongServer.java +++ b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpPongServer.java @@ -16,7 +16,8 @@ package io.rsocket.transport.netty; -import io.rsocket.RSocketFactory; +import io.rsocket.core.RSocketServer; +import io.rsocket.core.Resume; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.test.PingHandler; import io.rsocket.transport.netty.server.TcpServerTransport; @@ -31,15 +32,13 @@ public static void main(String... args) { System.out.println("port: " + port); System.out.println("resume enabled: " + isResume); - RSocketFactory.ServerRSocketFactory serverRSocketFactory = RSocketFactory.receive(); + RSocketServer server = RSocketServer.create(new PingHandler()); if (isResume) { - serverRSocketFactory.resume(); + server.resume(new Resume()); } - serverRSocketFactory - .frameDecoder(PayloadDecoder.ZERO_COPY) - .acceptor(new PingHandler()) - .transport(TcpServerTransport.create("localhost", port)) - .start() + server + .payloadDecoder(PayloadDecoder.ZERO_COPY) + .bind(TcpServerTransport.create("localhost", port)) .block() .onClose() .block(); diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebSocketTransportIntegrationTest.java b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebSocketTransportIntegrationTest.java index 4fe40d232..7028a3846 100644 --- a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebSocketTransportIntegrationTest.java +++ b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebSocketTransportIntegrationTest.java @@ -3,7 +3,8 @@ import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; import io.rsocket.transport.ServerTransport; import io.rsocket.transport.netty.client.WebsocketClientTransport; import io.rsocket.transport.netty.server.WebsocketRouteTransport; @@ -23,8 +24,7 @@ public class WebSocketTransportIntegrationTest { @Test public void sendStreamOfDataWithExternalHttpServerTest() { ServerTransport.ConnectionAcceptor acceptor = - RSocketFactory.receive() - .acceptor( + RSocketServer.create( (setupPayload, sendingRSocket) -> { return Mono.just( new AbstractRSocket() { @@ -35,7 +35,7 @@ public Flux requestStream(Payload payload) { } }); }) - .toConnectionAcceptor(); + .asConnectionAcceptor(); DisposableServer server = HttpServer.create() @@ -44,11 +44,9 @@ public Flux requestStream(Payload payload) { .bindNow(); RSocket rsocket = - RSocketFactory.connect() - .transport( + RSocketConnector.connectWith( WebsocketClientTransport.create( URI.create("ws://" + server.host() + ":" + server.port() + "/test"))) - .start() .block(); StepVerifier.create(rsocket.requestStream(EmptyPayload.INSTANCE)) diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketPing.java b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketPing.java index 306be4e43..a784a43c0 100644 --- a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketPing.java +++ b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketPing.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ package io.rsocket.transport.netty; import io.rsocket.RSocket; -import io.rsocket.RSocketFactory; +import io.rsocket.core.RSocketConnector; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.test.PingClient; import io.rsocket.transport.netty.client.WebsocketClientTransport; @@ -29,10 +29,9 @@ public final class WebsocketPing { public static void main(String... args) { Mono client = - RSocketFactory.connect() - .frameDecoder(PayloadDecoder.ZERO_COPY) - .transport(WebsocketClientTransport.create(7878)) - .start(); + RSocketConnector.create() + .payloadDecoder(PayloadDecoder.ZERO_COPY) + .connect(WebsocketClientTransport.create(7878)); PingClient pingClient = new PingClient(client); diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketPingPongIntegrationTest.java b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketPingPongIntegrationTest.java index eac091dd8..ab6c343de 100644 --- a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketPingPongIntegrationTest.java +++ b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketPingPongIntegrationTest.java @@ -8,7 +8,12 @@ import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketFrame; import io.netty.util.ReferenceCountUtil; -import io.rsocket.*; +import io.rsocket.AbstractRSocket; +import io.rsocket.Closeable; +import io.rsocket.Payload; +import io.rsocket.RSocket; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; import io.rsocket.transport.ServerTransport; import io.rsocket.transport.netty.client.WebsocketClientTransport; import io.rsocket.transport.netty.server.WebsocketRouteTransport; @@ -42,10 +47,8 @@ void tearDown() { @MethodSource("provideServerTransport") void webSocketPingPong(ServerTransport serverTransport) { server = - RSocketFactory.receive() - .acceptor((setup, sendingSocket) -> Mono.just(new EchoRSocket())) - .transport(serverTransport) - .start() + RSocketServer.create((setup, sendingSocket) -> Mono.just(new EchoRSocket())) + .bind(serverTransport) .block(); String expectedData = "data"; @@ -63,10 +66,7 @@ void webSocketPingPong(ServerTransport serverTransport) { .port(port)); RSocket rSocket = - RSocketFactory.connect() - .transport(WebsocketClientTransport.create(httpClient, "/")) - .start() - .block(); + RSocketConnector.connectWith(WebsocketClientTransport.create(httpClient, "/")).block(); rSocket .requestResponse(DefaultPayload.create(expectedData)) diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketPongServer.java b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketPongServer.java index 7fdb1813a..84dc816be 100644 --- a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketPongServer.java +++ b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketPongServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ package io.rsocket.transport.netty; -import io.rsocket.RSocketFactory; +import io.rsocket.core.RSocketServer; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.test.PingHandler; import io.rsocket.transport.netty.server.WebsocketServerTransport; @@ -24,11 +24,9 @@ public final class WebsocketPongServer { public static void main(String... args) { - RSocketFactory.receive() - .frameDecoder(PayloadDecoder.ZERO_COPY) - .acceptor(new PingHandler()) - .transport(WebsocketServerTransport.create(7878)) - .start() + RSocketServer.create(new PingHandler()) + .payloadDecoder(PayloadDecoder.ZERO_COPY) + .bind(WebsocketServerTransport.create(7878)) .block() .onClose() .block(); From 5d3faef0d7899490db91d872cb797661e81b15de Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Wed, 15 Apr 2020 21:28:49 +0300 Subject: [PATCH 16/62] [Bugfix] Enforces reassembly to always be enabled (#775) * enforces reassembly on the receiver side Signed-off-by: Oleh Dokuka * optimizes FragmentationDuplexConnection by extending ReassemblyDuplexConnection Signed-off-by: Oleh Dokuka * fixes test Signed-off-by: Oleh Dokuka * fixes docs and headers Signed-off-by: Oleh Dokuka --- .../FragmentationDuplexConnection.java | 40 +-- .../fragmentation/FrameFragmenter.java | 2 +- .../fragmentation/FrameReassembler.java | 2 +- .../ReassemblyDuplexConnection.java | 89 ++++++ .../rsocket/fragmentation/package-info.java | 2 +- .../FragmentationDuplexConnectionTest.java | 221 +-------------- .../ReassembleDuplexConnectionTest.java | 266 ++++++++++++++++++ .../transport/ws/WebSocketHeadersSample.java | 7 +- .../transport/local/LocalClientTransport.java | 20 +- .../transport/local/LocalServerTransport.java | 4 + .../netty/client/TcpClientTransport.java | 4 +- .../client/WebsocketClientTransport.java | 4 + .../netty/server/TcpServerTransport.java | 5 +- .../netty/server/WebsocketRouteTransport.java | 3 + .../server/WebsocketServerTransport.java | 5 + .../io/rsocket/integration/FragmentTest.java | 40 ++- 16 files changed, 437 insertions(+), 277 deletions(-) create mode 100644 rsocket-core/src/main/java/io/rsocket/fragmentation/ReassemblyDuplexConnection.java create mode 100644 rsocket-core/src/test/java/io/rsocket/fragmentation/ReassembleDuplexConnectionTest.java diff --git a/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java b/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java index cbe989d4b..333787e9f 100644 --- a/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java +++ b/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,8 @@ * href="https://github.com/rsocket/rsocket/blob/master/Protocol.md#fragmentation-and-reassembly">Fragmentation * and Reassembly */ -public final class FragmentationDuplexConnection implements DuplexConnection { +public final class FragmentationDuplexConnection extends ReassemblyDuplexConnection + implements DuplexConnection { private static final int MIN_MTU_SIZE = 64; private static final Logger logger = LoggerFactory.getLogger(FragmentationDuplexConnection.class); private final DuplexConnection delegate; @@ -54,11 +55,13 @@ public FragmentationDuplexConnection( DuplexConnection delegate, ByteBufAllocator allocator, int mtu, - boolean encodeLength, + boolean encodeAndEncodeLength, String type) { + super(delegate, allocator, encodeAndEncodeLength); + Objects.requireNonNull(delegate, "delegate must not be null"); Objects.requireNonNull(allocator, "byteBufAllocator must not be null"); - this.encodeLength = encodeLength; + this.encodeLength = encodeAndEncodeLength; this.allocator = allocator; this.delegate = delegate; this.mtu = assertMtu(mtu); @@ -137,33 +140,4 @@ private ByteBuf encode(ByteBuf frame) { return frame; } } - - private ByteBuf decode(ByteBuf frame) { - if (encodeLength) { - return FrameLengthFlyweight.frame(frame).retain(); - } else { - return frame; - } - } - - @Override - public Flux receive() { - return delegate - .receive() - .handle( - (byteBuf, sink) -> { - ByteBuf decode = decode(byteBuf); - frameReassembler.reassembleFrame(decode, sink); - }); - } - - @Override - public Mono onClose() { - return delegate.onClose(); - } - - @Override - public void dispose() { - delegate.dispose(); - } } diff --git a/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameFragmenter.java b/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameFragmenter.java index e59ece86f..8593d2be7 100644 --- a/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameFragmenter.java +++ b/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameFragmenter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameReassembler.java b/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameReassembler.java index 0c446a7c4..d8537ec1a 100644 --- a/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameReassembler.java +++ b/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameReassembler.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/rsocket-core/src/main/java/io/rsocket/fragmentation/ReassemblyDuplexConnection.java b/rsocket-core/src/main/java/io/rsocket/fragmentation/ReassemblyDuplexConnection.java new file mode 100644 index 000000000..bf0d7482c --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/fragmentation/ReassemblyDuplexConnection.java @@ -0,0 +1,89 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.rsocket.fragmentation; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.rsocket.DuplexConnection; +import io.rsocket.frame.FrameLengthFlyweight; +import java.util.Objects; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * A {@link DuplexConnection} implementation that reassembles {@link ByteBuf}s. + * + * @see Fragmentation + * and Reassembly + */ +public class ReassemblyDuplexConnection implements DuplexConnection { + private final DuplexConnection delegate; + private final FrameReassembler frameReassembler; + private final boolean decodeLength; + + public ReassemblyDuplexConnection( + DuplexConnection delegate, ByteBufAllocator allocator, boolean decodeLength) { + Objects.requireNonNull(delegate, "delegate must not be null"); + Objects.requireNonNull(allocator, "byteBufAllocator must not be null"); + this.decodeLength = decodeLength; + this.delegate = delegate; + this.frameReassembler = new FrameReassembler(allocator); + + delegate.onClose().doFinally(s -> frameReassembler.dispose()).subscribe(); + } + + @Override + public Mono send(Publisher frames) { + return delegate.send(frames); + } + + @Override + public Mono sendOne(ByteBuf frame) { + return delegate.sendOne(frame); + } + + private ByteBuf decode(ByteBuf frame) { + if (decodeLength) { + return FrameLengthFlyweight.frame(frame).retain(); + } else { + return frame; + } + } + + @Override + public Flux receive() { + return delegate + .receive() + .handle( + (byteBuf, sink) -> { + ByteBuf decode = decode(byteBuf); + frameReassembler.reassembleFrame(decode, sink); + }); + } + + @Override + public Mono onClose() { + return delegate.onClose(); + } + + @Override + public void dispose() { + delegate.dispose(); + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/fragmentation/package-info.java b/rsocket-core/src/main/java/io/rsocket/fragmentation/package-info.java index 4431f98dd..8cc3fb41a 100644 --- a/rsocket-core/src/main/java/io/rsocket/fragmentation/package-info.java +++ b/rsocket-core/src/main/java/io/rsocket/fragmentation/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationDuplexConnectionTest.java b/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationDuplexConnectionTest.java index 3d96bfd12..a6918c497 100644 --- a/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationDuplexConnectionTest.java +++ b/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationDuplexConnectionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,18 +22,15 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.CompositeByteBuf; import io.netty.buffer.Unpooled; import io.rsocket.DuplexConnection; import io.rsocket.frame.*; -import io.rsocket.util.DefaultPayload; -import java.util.Arrays; -import java.util.List; import java.util.concurrent.ThreadLocalRandom; import org.junit.Assert; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -50,6 +47,10 @@ final class FragmentationDuplexConnectionTest { private final DuplexConnection delegate = mock(DuplexConnection.class, RETURNS_SMART_NULLS); + { + Mockito.when(delegate.onClose()).thenReturn(Mono.never()); + } + @SuppressWarnings("unchecked") private final ArgumentCaptor> publishers = ArgumentCaptor.forClass(Publisher.class); @@ -91,216 +92,6 @@ void constructorNullDelegate() { .withMessage("delegate must not be null"); } - @DisplayName("reassembles data") - @Test - void reassembleData() { - List byteBufs = - Arrays.asList( - RequestResponseFrameFlyweight.encode(allocator, 1, true, DefaultPayload.create(data)), - PayloadFrameFlyweight.encode( - allocator, 1, true, false, true, DefaultPayload.create(data)), - PayloadFrameFlyweight.encode( - allocator, 1, true, false, true, DefaultPayload.create(data)), - PayloadFrameFlyweight.encode( - allocator, 1, true, false, true, DefaultPayload.create(data)), - PayloadFrameFlyweight.encode( - allocator, 1, false, false, true, DefaultPayload.create(data))); - - CompositeByteBuf data = - allocator - .compositeDirectBuffer() - .addComponents( - true, - Unpooled.wrappedBuffer(FragmentationDuplexConnectionTest.data), - Unpooled.wrappedBuffer(FragmentationDuplexConnectionTest.data), - Unpooled.wrappedBuffer(FragmentationDuplexConnectionTest.data), - Unpooled.wrappedBuffer(FragmentationDuplexConnectionTest.data), - Unpooled.wrappedBuffer(FragmentationDuplexConnectionTest.data)); - - when(delegate.receive()).thenReturn(Flux.fromIterable(byteBufs)); - when(delegate.onClose()).thenReturn(Mono.never()); - - new FragmentationDuplexConnection(delegate, allocator, 1030, false, "") - .receive() - .as(StepVerifier::create) - .assertNext( - byteBuf -> { - Assert.assertEquals(data, RequestResponseFrameFlyweight.data(byteBuf)); - }) - .verifyComplete(); - } - - @DisplayName("reassembles metadata") - @Test - void reassembleMetadata() { - List byteBufs = - Arrays.asList( - RequestResponseFrameFlyweight.encode( - allocator, - 1, - true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), - PayloadFrameFlyweight.encode( - allocator, - 1, - true, - false, - true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), - PayloadFrameFlyweight.encode( - allocator, - 1, - true, - false, - true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), - PayloadFrameFlyweight.encode( - allocator, - 1, - true, - false, - true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), - PayloadFrameFlyweight.encode( - allocator, - 1, - false, - false, - true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata)))); - - CompositeByteBuf metadata = - allocator - .compositeDirectBuffer() - .addComponents( - true, - Unpooled.wrappedBuffer(FragmentationDuplexConnectionTest.metadata), - Unpooled.wrappedBuffer(FragmentationDuplexConnectionTest.metadata), - Unpooled.wrappedBuffer(FragmentationDuplexConnectionTest.metadata), - Unpooled.wrappedBuffer(FragmentationDuplexConnectionTest.metadata), - Unpooled.wrappedBuffer(FragmentationDuplexConnectionTest.metadata)); - - when(delegate.receive()).thenReturn(Flux.fromIterable(byteBufs)); - when(delegate.onClose()).thenReturn(Mono.never()); - - new FragmentationDuplexConnection(delegate, allocator, 1030, false, "") - .receive() - .as(StepVerifier::create) - .assertNext( - byteBuf -> { - System.out.println(byteBuf.readableBytes()); - ByteBuf m = RequestResponseFrameFlyweight.metadata(byteBuf); - Assert.assertEquals(metadata, m); - }) - .verifyComplete(); - } - - @DisplayName("reassembles metadata and data") - @Test - void reassembleMetadataAndData() { - List byteBufs = - Arrays.asList( - RequestResponseFrameFlyweight.encode( - allocator, - 1, - true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), - PayloadFrameFlyweight.encode( - allocator, - 1, - true, - false, - true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), - PayloadFrameFlyweight.encode( - allocator, - 1, - true, - false, - true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), - PayloadFrameFlyweight.encode( - allocator, - 1, - true, - false, - true, - DefaultPayload.create( - Unpooled.wrappedBuffer(data), Unpooled.wrappedBuffer(metadata))), - PayloadFrameFlyweight.encode( - allocator, 1, false, false, true, DefaultPayload.create(data))); - - CompositeByteBuf data = - allocator - .compositeDirectBuffer() - .addComponents( - true, - Unpooled.wrappedBuffer(FragmentationDuplexConnectionTest.data), - Unpooled.wrappedBuffer(FragmentationDuplexConnectionTest.data)); - - CompositeByteBuf metadata = - allocator - .compositeDirectBuffer() - .addComponents( - true, - Unpooled.wrappedBuffer(FragmentationDuplexConnectionTest.metadata), - Unpooled.wrappedBuffer(FragmentationDuplexConnectionTest.metadata), - Unpooled.wrappedBuffer(FragmentationDuplexConnectionTest.metadata), - Unpooled.wrappedBuffer(FragmentationDuplexConnectionTest.metadata)); - - when(delegate.receive()).thenReturn(Flux.fromIterable(byteBufs)); - when(delegate.onClose()).thenReturn(Mono.never()); - - new FragmentationDuplexConnection(delegate, allocator, 1030, false, "") - .receive() - .as(StepVerifier::create) - .assertNext( - byteBuf -> { - Assert.assertEquals(data, RequestResponseFrameFlyweight.data(byteBuf)); - Assert.assertEquals(metadata, RequestResponseFrameFlyweight.metadata(byteBuf)); - }) - .verifyComplete(); - } - - @DisplayName("does not reassemble a non-fragment frame") - @Test - void reassembleNonFragment() { - ByteBuf encode = - RequestResponseFrameFlyweight.encode( - allocator, 1, false, DefaultPayload.create(Unpooled.wrappedBuffer(data))); - - when(delegate.receive()).thenReturn(Flux.just(encode)); - when(delegate.onClose()).thenReturn(Mono.never()); - - new FragmentationDuplexConnection(delegate, allocator, 1030, false, "") - .receive() - .as(StepVerifier::create) - .assertNext( - byteBuf -> { - Assert.assertEquals( - Unpooled.wrappedBuffer(data), RequestResponseFrameFlyweight.data(byteBuf)); - }) - .verifyComplete(); - } - - @DisplayName("does not reassemble non fragmentable frame") - @Test - void reassembleNonFragmentableFrame() { - ByteBuf encode = CancelFrameFlyweight.encode(allocator, 2); - - when(delegate.receive()).thenReturn(Flux.just(encode)); - when(delegate.onClose()).thenReturn(Mono.never()); - - new FragmentationDuplexConnection(delegate, allocator, 1030, false, "") - .receive() - .as(StepVerifier::create) - .assertNext( - byteBuf -> { - Assert.assertEquals(FrameType.CANCEL, FrameHeaderFlyweight.frameType(byteBuf)); - }) - .verifyComplete(); - } - @DisplayName("fragments data") @Test void sendData() { diff --git a/rsocket-core/src/test/java/io/rsocket/fragmentation/ReassembleDuplexConnectionTest.java b/rsocket-core/src/test/java/io/rsocket/fragmentation/ReassembleDuplexConnectionTest.java new file mode 100644 index 000000000..b21a7c9da --- /dev/null +++ b/rsocket-core/src/test/java/io/rsocket/fragmentation/ReassembleDuplexConnectionTest.java @@ -0,0 +1,266 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.rsocket.fragmentation; + +import static org.mockito.Mockito.RETURNS_SMART_NULLS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.CompositeByteBuf; +import io.netty.buffer.Unpooled; +import io.rsocket.DuplexConnection; +import io.rsocket.frame.CancelFrameFlyweight; +import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.FrameType; +import io.rsocket.frame.PayloadFrameFlyweight; +import io.rsocket.frame.RequestResponseFrameFlyweight; +import io.rsocket.util.DefaultPayload; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import org.junit.Assert; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +final class ReassembleDuplexConnectionTest { + private static byte[] data = new byte[1024]; + private static byte[] metadata = new byte[1024]; + + static { + ThreadLocalRandom.current().nextBytes(data); + ThreadLocalRandom.current().nextBytes(metadata); + } + + private final DuplexConnection delegate = mock(DuplexConnection.class, RETURNS_SMART_NULLS); + + private ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; + + @DisplayName("reassembles data") + @Test + void reassembleData() { + List byteBufs = + Arrays.asList( + RequestResponseFrameFlyweight.encode(allocator, 1, true, DefaultPayload.create(data)), + PayloadFrameFlyweight.encode( + allocator, 1, true, false, true, DefaultPayload.create(data)), + PayloadFrameFlyweight.encode( + allocator, 1, true, false, true, DefaultPayload.create(data)), + PayloadFrameFlyweight.encode( + allocator, 1, true, false, true, DefaultPayload.create(data)), + PayloadFrameFlyweight.encode( + allocator, 1, false, false, true, DefaultPayload.create(data))); + + CompositeByteBuf data = + allocator + .compositeDirectBuffer() + .addComponents( + true, + Unpooled.wrappedBuffer(ReassembleDuplexConnectionTest.data), + Unpooled.wrappedBuffer(ReassembleDuplexConnectionTest.data), + Unpooled.wrappedBuffer(ReassembleDuplexConnectionTest.data), + Unpooled.wrappedBuffer(ReassembleDuplexConnectionTest.data), + Unpooled.wrappedBuffer(ReassembleDuplexConnectionTest.data)); + + when(delegate.receive()).thenReturn(Flux.fromIterable(byteBufs)); + when(delegate.onClose()).thenReturn(Mono.never()); + + new ReassemblyDuplexConnection(delegate, allocator, false) + .receive() + .as(StepVerifier::create) + .assertNext( + byteBuf -> { + Assert.assertEquals(data, RequestResponseFrameFlyweight.data(byteBuf)); + }) + .verifyComplete(); + } + + @DisplayName("reassembles metadata") + @Test + void reassembleMetadata() { + List byteBufs = + Arrays.asList( + RequestResponseFrameFlyweight.encode( + allocator, + 1, + true, + DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + PayloadFrameFlyweight.encode( + allocator, + 1, + true, + false, + true, + DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + PayloadFrameFlyweight.encode( + allocator, + 1, + true, + false, + true, + DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + PayloadFrameFlyweight.encode( + allocator, + 1, + true, + false, + true, + DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + PayloadFrameFlyweight.encode( + allocator, + 1, + false, + false, + true, + DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata)))); + + CompositeByteBuf metadata = + allocator + .compositeDirectBuffer() + .addComponents( + true, + Unpooled.wrappedBuffer(ReassembleDuplexConnectionTest.metadata), + Unpooled.wrappedBuffer(ReassembleDuplexConnectionTest.metadata), + Unpooled.wrappedBuffer(ReassembleDuplexConnectionTest.metadata), + Unpooled.wrappedBuffer(ReassembleDuplexConnectionTest.metadata), + Unpooled.wrappedBuffer(ReassembleDuplexConnectionTest.metadata)); + + when(delegate.receive()).thenReturn(Flux.fromIterable(byteBufs)); + when(delegate.onClose()).thenReturn(Mono.never()); + + new ReassemblyDuplexConnection(delegate, allocator, false) + .receive() + .as(StepVerifier::create) + .assertNext( + byteBuf -> { + System.out.println(byteBuf.readableBytes()); + ByteBuf m = RequestResponseFrameFlyweight.metadata(byteBuf); + Assert.assertEquals(metadata, m); + }) + .verifyComplete(); + } + + @DisplayName("reassembles metadata and data") + @Test + void reassembleMetadataAndData() { + List byteBufs = + Arrays.asList( + RequestResponseFrameFlyweight.encode( + allocator, + 1, + true, + DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + PayloadFrameFlyweight.encode( + allocator, + 1, + true, + false, + true, + DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + PayloadFrameFlyweight.encode( + allocator, + 1, + true, + false, + true, + DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + PayloadFrameFlyweight.encode( + allocator, + 1, + true, + false, + true, + DefaultPayload.create( + Unpooled.wrappedBuffer(data), Unpooled.wrappedBuffer(metadata))), + PayloadFrameFlyweight.encode( + allocator, 1, false, false, true, DefaultPayload.create(data))); + + CompositeByteBuf data = + allocator + .compositeDirectBuffer() + .addComponents( + true, + Unpooled.wrappedBuffer(ReassembleDuplexConnectionTest.data), + Unpooled.wrappedBuffer(ReassembleDuplexConnectionTest.data)); + + CompositeByteBuf metadata = + allocator + .compositeDirectBuffer() + .addComponents( + true, + Unpooled.wrappedBuffer(ReassembleDuplexConnectionTest.metadata), + Unpooled.wrappedBuffer(ReassembleDuplexConnectionTest.metadata), + Unpooled.wrappedBuffer(ReassembleDuplexConnectionTest.metadata), + Unpooled.wrappedBuffer(ReassembleDuplexConnectionTest.metadata)); + + when(delegate.receive()).thenReturn(Flux.fromIterable(byteBufs)); + when(delegate.onClose()).thenReturn(Mono.never()); + + new ReassemblyDuplexConnection(delegate, allocator, false) + .receive() + .as(StepVerifier::create) + .assertNext( + byteBuf -> { + Assert.assertEquals(data, RequestResponseFrameFlyweight.data(byteBuf)); + Assert.assertEquals(metadata, RequestResponseFrameFlyweight.metadata(byteBuf)); + }) + .verifyComplete(); + } + + @DisplayName("does not reassemble a non-fragment frame") + @Test + void reassembleNonFragment() { + ByteBuf encode = + RequestResponseFrameFlyweight.encode( + allocator, 1, false, DefaultPayload.create(Unpooled.wrappedBuffer(data))); + + when(delegate.receive()).thenReturn(Flux.just(encode)); + when(delegate.onClose()).thenReturn(Mono.never()); + + new ReassemblyDuplexConnection(delegate, allocator, false) + .receive() + .as(StepVerifier::create) + .assertNext( + byteBuf -> { + Assert.assertEquals( + Unpooled.wrappedBuffer(data), RequestResponseFrameFlyweight.data(byteBuf)); + }) + .verifyComplete(); + } + + @DisplayName("does not reassemble non fragmentable frame") + @Test + void reassembleNonFragmentableFrame() { + ByteBuf encode = CancelFrameFlyweight.encode(allocator, 2); + + when(delegate.receive()).thenReturn(Flux.just(encode)); + when(delegate.onClose()).thenReturn(Mono.never()); + + new ReassemblyDuplexConnection(delegate, allocator, false) + .receive() + .as(StepVerifier::create) + .assertNext( + byteBuf -> { + Assert.assertEquals(FrameType.CANCEL, FrameHeaderFlyweight.frameType(byteBuf)); + }) + .verifyComplete(); + } +} diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/ws/WebSocketHeadersSample.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/ws/WebSocketHeadersSample.java index 908505a2f..fd2dcbbd6 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/ws/WebSocketHeadersSample.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/ws/WebSocketHeadersSample.java @@ -16,6 +16,7 @@ package io.rsocket.examples.transport.ws; +import io.netty.buffer.ByteBufAllocator; import io.netty.handler.codec.http.HttpResponseStatus; import io.rsocket.AbstractRSocket; import io.rsocket.ConnectionSetupPayload; @@ -25,6 +26,7 @@ import io.rsocket.SocketAcceptor; import io.rsocket.core.RSocketConnector; import io.rsocket.core.RSocketServer; +import io.rsocket.fragmentation.ReassemblyDuplexConnection; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.transport.ServerTransport; import io.rsocket.transport.netty.WebsocketDuplexConnection; @@ -62,7 +64,10 @@ public static void main(String[] args) { (in, out) -> { if (in.headers().containsValue("Authorization", "test", true)) { DuplexConnection connection = - new WebsocketDuplexConnection((Connection) in); + new ReassemblyDuplexConnection( + new WebsocketDuplexConnection((Connection) in), + ByteBufAllocator.DEFAULT, + false); return acceptor.apply(connection).then(out.neverComplete()); } diff --git a/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalClientTransport.java b/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalClientTransport.java index 990acddfe..0d6a10391 100644 --- a/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalClientTransport.java +++ b/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalClientTransport.java @@ -20,6 +20,7 @@ import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; import io.rsocket.fragmentation.FragmentationDuplexConnection; +import io.rsocket.fragmentation.ReassemblyDuplexConnection; import io.rsocket.internal.UnboundedProcessor; import io.rsocket.transport.ClientTransport; import io.rsocket.transport.ServerTransport; @@ -75,13 +76,16 @@ private Mono connect() { public Mono connect(int mtu) { Mono isError = FragmentationDuplexConnection.checkMtu(mtu); Mono connect = isError != null ? isError : connect(); - if (mtu > 0) { - return connect.map( - duplexConnection -> - new FragmentationDuplexConnection( - duplexConnection, ByteBufAllocator.DEFAULT, mtu, false, "client")); - } else { - return connect; - } + + return connect.map( + duplexConnection -> { + if (mtu > 0) { + return new FragmentationDuplexConnection( + duplexConnection, ByteBufAllocator.DEFAULT, mtu, false, "client"); + } else { + return new ReassemblyDuplexConnection( + duplexConnection, ByteBufAllocator.DEFAULT, false); + } + }); } } diff --git a/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalServerTransport.java b/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalServerTransport.java index d755859d2..329b4e38c 100644 --- a/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalServerTransport.java +++ b/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalServerTransport.java @@ -20,6 +20,7 @@ import io.rsocket.Closeable; import io.rsocket.DuplexConnection; import io.rsocket.fragmentation.FragmentationDuplexConnection; +import io.rsocket.fragmentation.ReassemblyDuplexConnection; import io.rsocket.transport.ClientTransport; import io.rsocket.transport.ServerTransport; import java.util.Objects; @@ -168,6 +169,9 @@ public void accept(DuplexConnection duplexConnection) { duplexConnection = new FragmentationDuplexConnection( duplexConnection, ByteBufAllocator.DEFAULT, mtu, false, "server"); + } else { + duplexConnection = + new ReassemblyDuplexConnection(duplexConnection, ByteBufAllocator.DEFAULT, false); } acceptor.apply(duplexConnection).subscribe(); diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/TcpClientTransport.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/TcpClientTransport.java index f5e79e9bf..8059b36bd 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/TcpClientTransport.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/TcpClientTransport.java @@ -19,6 +19,7 @@ import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; import io.rsocket.fragmentation.FragmentationDuplexConnection; +import io.rsocket.fragmentation.ReassemblyDuplexConnection; import io.rsocket.transport.ClientTransport; import io.rsocket.transport.ServerTransport; import io.rsocket.transport.netty.RSocketLengthCodec; @@ -110,7 +111,8 @@ public Mono connect(int mtu) { true, "client"); } else { - return new TcpDuplexConnection(c); + return new ReassemblyDuplexConnection( + new TcpDuplexConnection(c), ByteBufAllocator.DEFAULT, false); } }); } diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java index 62bbd9b99..49a2c2e92 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java @@ -23,6 +23,7 @@ import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; import io.rsocket.fragmentation.FragmentationDuplexConnection; +import io.rsocket.fragmentation.ReassemblyDuplexConnection; import io.rsocket.transport.ClientTransport; import io.rsocket.transport.ServerTransport; import io.rsocket.transport.TransportHeaderAware; @@ -165,6 +166,9 @@ public Mono connect(int mtu) { connection = new FragmentationDuplexConnection( connection, ByteBufAllocator.DEFAULT, mtu, false, "client"); + } else { + connection = + new ReassemblyDuplexConnection(connection, ByteBufAllocator.DEFAULT, false); } return connection; }); diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/TcpServerTransport.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/TcpServerTransport.java index 54ef016c0..d39cc8e67 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/TcpServerTransport.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/TcpServerTransport.java @@ -19,6 +19,7 @@ import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; import io.rsocket.fragmentation.FragmentationDuplexConnection; +import io.rsocket.fragmentation.ReassemblyDuplexConnection; import io.rsocket.transport.ClientTransport; import io.rsocket.transport.ServerTransport; import io.rsocket.transport.netty.RSocketLengthCodec; @@ -111,7 +112,9 @@ public Mono start(ConnectionAcceptor acceptor, int mtu) { true, "server"); } else { - connection = new TcpDuplexConnection(c); + connection = + new ReassemblyDuplexConnection( + new TcpDuplexConnection(c), ByteBufAllocator.DEFAULT, false); } acceptor .apply(connection) diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketRouteTransport.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketRouteTransport.java index 60a34c9b1..1d8769cc6 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketRouteTransport.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketRouteTransport.java @@ -22,6 +22,7 @@ import io.rsocket.Closeable; import io.rsocket.DuplexConnection; import io.rsocket.fragmentation.FragmentationDuplexConnection; +import io.rsocket.fragmentation.ReassemblyDuplexConnection; import io.rsocket.transport.ServerTransport; import io.rsocket.transport.netty.WebsocketDuplexConnection; import java.util.Objects; @@ -107,6 +108,8 @@ public static BiFunction> n connection = new FragmentationDuplexConnection( connection, ByteBufAllocator.DEFAULT, mtu, false, "server"); + } else { + connection = new ReassemblyDuplexConnection(connection, ByteBufAllocator.DEFAULT, false); } return acceptor.apply(connection).then(out.neverComplete()); }; diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketServerTransport.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketServerTransport.java index 13caa6345..01c519ea3 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketServerTransport.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketServerTransport.java @@ -21,6 +21,7 @@ import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; import io.rsocket.fragmentation.FragmentationDuplexConnection; +import io.rsocket.fragmentation.ReassemblyDuplexConnection; import io.rsocket.transport.ClientTransport; import io.rsocket.transport.ServerTransport; import io.rsocket.transport.TransportHeaderAware; @@ -130,6 +131,10 @@ public Mono start(ConnectionAcceptor acceptor, int mtu) { connection = new FragmentationDuplexConnection( connection, ByteBufAllocator.DEFAULT, mtu, false, "server"); + } else { + connection = + new ReassemblyDuplexConnection( + connection, ByteBufAllocator.DEFAULT, false); } return acceptor.apply(connection).then(out.neverComplete()); }, diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/integration/FragmentTest.java b/rsocket-transport-netty/src/test/java/io/rsocket/integration/FragmentTest.java index db2f07bda..0ea938af2 100644 --- a/rsocket-transport-netty/src/test/java/io/rsocket/integration/FragmentTest.java +++ b/rsocket-transport-netty/src/test/java/io/rsocket/integration/FragmentTest.java @@ -29,22 +29,26 @@ import io.rsocket.util.DefaultPayload; import io.rsocket.util.RSocketProxy; import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Stream; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; public class FragmentTest { - private static final int frameSize = 64; private AbstractRSocket handler; private CloseableChannel server; private String message = null; private String metaData = null; private String responseMessage = null; - @BeforeEach - public void startup() { + private static Stream cases() { + return Stream.of(Arguments.of(0, 64), Arguments.of(64, 0), Arguments.of(64, 64)); + } + + public void startup(int frameSize) { int randomPort = ThreadLocalRandom.current().nextInt(10_000, 20_000); StringBuilder message = new StringBuilder(); StringBuilder responseMessage = new StringBuilder(); @@ -66,7 +70,7 @@ public void startup() { .block(); } - private RSocket buildClient() { + private RSocket buildClient(int frameSize) { return RSocketConnector.create() .fragment(frameSize) .connect(TcpClientTransport.create(server.address())) @@ -78,8 +82,10 @@ public void cleanup() { server.dispose(); } - @Test - void testFragmentNoMetaData() { + @ParameterizedTest + @MethodSource("cases") + void testFragmentNoMetaData(int clientFrameSize, int serverFrameSize) { + startup(serverFrameSize); System.out.println( "-------------------------------------------------testFragmentNoMetaData-------------------------------------------------"); handler = @@ -95,7 +101,7 @@ public Flux requestStream(Payload payload) { } }; - RSocket client = buildClient(); + RSocket client = buildClient(clientFrameSize); System.out.println("original message: " + message); System.out.println("original metadata: " + metaData); @@ -106,8 +112,10 @@ public Flux requestStream(Payload payload) { assertThat(responseMessage).isEqualTo(payload.getDataUtf8()); } - @Test - void testFragmentRequestMetaDataOnly() { + @ParameterizedTest + @MethodSource("cases") + void testFragmentRequestMetaDataOnly(int clientFrameSize, int serverFrameSize) { + startup(serverFrameSize); System.out.println( "-------------------------------------------------testFragmentRequestMetaDataOnly-------------------------------------------------"); handler = @@ -123,7 +131,7 @@ public Flux requestStream(Payload payload) { } }; - RSocket client = buildClient(); + RSocket client = buildClient(clientFrameSize); System.out.println("original message: " + message); System.out.println("original metadata: " + metaData); @@ -134,8 +142,10 @@ public Flux requestStream(Payload payload) { assertThat(responseMessage).isEqualTo(payload.getDataUtf8()); } - @Test - void testFragmentBothMetaData() { + @ParameterizedTest + @MethodSource("cases") + void testFragmentBothMetaData(int clientFrameSize, int serverFrameSize) { + startup(serverFrameSize); Payload responsePayload = DefaultPayload.create(responseMessage); System.out.println( "-------------------------------------------------testFragmentBothMetaData-------------------------------------------------"); @@ -162,7 +172,7 @@ public Mono requestResponse(Payload payload) { } }; - RSocket client = buildClient(); + RSocket client = buildClient(clientFrameSize); System.out.println("original message: " + message); System.out.println("original metadata: " + metaData); From c99c5ae709ba9bbc0c3d37f4c97773be4671ea93 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 17 Apr 2020 16:57:39 +0100 Subject: [PATCH 17/62] Follow-up fix for commit 0ac54d4b (#784) See gh-778 Signed-off-by: Rossen Stoyanchev --- rsocket-core/src/main/java/io/rsocket/RSocketFactory.java | 1 + .../src/main/java/io/rsocket/core/RSocketConnector.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java b/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java index d488d2c08..7749d27cd 100644 --- a/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java +++ b/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java @@ -416,6 +416,7 @@ public ServerRSocketFactory addSocketAcceptorPlugin(SocketAcceptorInterceptor in } public ServerTransportAcceptor acceptor(SocketAcceptor acceptor) { + server.acceptor(acceptor); return this; } diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java index 92c5220c0..23ecb9ba6 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java @@ -120,8 +120,8 @@ public RSocketConnector acceptor(SocketAcceptor acceptor) { * *

    By default {@code interval} is set to 20 seconds and {@code maxLifeTime} to 90 seconds. * - * @param interval the time between KEEPALIVE frames sent, must be > 0. - * @param maxLifeTime the max time allowed between KEEPALIVE frames received, must be > 0. + * @param interval the time between KEEPALIVE frames sent, must be greater than 0. + * @param maxLifeTime the max time between KEEPALIVE frames received, must be greater than 0. */ public RSocketConnector keepAlive(Duration interval, Duration maxLifeTime) { if (!interval.negated().isNegative()) { From cf3bf11eb328bee9828ba71d87d5bc98d667cfe9 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Sat, 18 Apr 2020 09:03:54 +0100 Subject: [PATCH 18/62] Restore ConnectionSetupPayload as abstract class (#785) Another follow-up fix for commit 0ac54d4b. Changing ConnectionSetupPayload from abstract class to interface is problematic when framework code compiled against newer RSocket is used in an application on existing version. ConnectionSetupPayload is now an abstract class again but DefaultConnectionSetupPayload remains in the "core" sub-package to avoid the package cycle with "frame". See gh-778 Signed-off-by: Rossen Stoyanchev --- .../io/rsocket/ConnectionSetupPayload.java | 49 ++++++++++++--- .../core/DefaultConnectionSetupPayload.java | 61 ++++++++----------- 2 files changed, 64 insertions(+), 46 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/ConnectionSetupPayload.java b/rsocket-core/src/main/java/io/rsocket/ConnectionSetupPayload.java index 47cb0cf11..bd4582e2b 100644 --- a/rsocket-core/src/main/java/io/rsocket/ConnectionSetupPayload.java +++ b/rsocket-core/src/main/java/io/rsocket/ConnectionSetupPayload.java @@ -13,31 +13,60 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package io.rsocket; import io.netty.buffer.ByteBuf; -import io.netty.util.ReferenceCounted; +import io.netty.util.AbstractReferenceCounted; +import io.rsocket.core.DefaultConnectionSetupPayload; import javax.annotation.Nullable; /** * Exposes information from the {@code SETUP} frame to a server, as well as to client responders. */ -public interface ConnectionSetupPayload extends ReferenceCounted, Payload { +public abstract class ConnectionSetupPayload extends AbstractReferenceCounted implements Payload { - String metadataMimeType(); + public abstract String metadataMimeType(); - String dataMimeType(); + public abstract String dataMimeType(); - int keepAliveInterval(); + public abstract int keepAliveInterval(); - int keepAliveMaxLifetime(); + public abstract int keepAliveMaxLifetime(); - int getFlags(); + public abstract int getFlags(); - boolean willClientHonorLease(); + public abstract boolean willClientHonorLease(); - boolean isResumeEnabled(); + public abstract boolean isResumeEnabled(); @Nullable - ByteBuf resumeToken(); + public abstract ByteBuf resumeToken(); + + @Override + public ConnectionSetupPayload retain() { + super.retain(); + return this; + } + + @Override + public ConnectionSetupPayload retain(int increment) { + super.retain(increment); + return this; + } + + @Override + public abstract ConnectionSetupPayload touch(); + + /** + * Create a {@code ConnectionSetupPayload}. + * + * @deprecated as of 1.0 RC7. Please, use {@link + * DefaultConnectionSetupPayload#DefaultConnectionSetupPayload(ByteBuf) + * DefaultConnectionSetupPayload} constructor. + */ + @Deprecated + public static ConnectionSetupPayload create(final ByteBuf setupFrame) { + return new DefaultConnectionSetupPayload(setupFrame); + } } diff --git a/rsocket-core/src/main/java/io/rsocket/core/DefaultConnectionSetupPayload.java b/rsocket-core/src/main/java/io/rsocket/core/DefaultConnectionSetupPayload.java index 23eeac160..8710aa61a 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/DefaultConnectionSetupPayload.java +++ b/rsocket-core/src/main/java/io/rsocket/core/DefaultConnectionSetupPayload.java @@ -17,14 +17,15 @@ package io.rsocket.core; import io.netty.buffer.ByteBuf; -import io.netty.util.AbstractReferenceCounted; import io.rsocket.ConnectionSetupPayload; import io.rsocket.frame.FrameHeaderFlyweight; import io.rsocket.frame.SetupFrameFlyweight; -/** Default implementation of {@link ConnectionSetupPayload}. */ -class DefaultConnectionSetupPayload extends AbstractReferenceCounted - implements ConnectionSetupPayload { +/** + * Default implementation of {@link ConnectionSetupPayload}. Primarily for internal use within + * RSocket Java but may be created in an application, e.g. for testing purposes. + */ +public class DefaultConnectionSetupPayload extends ConnectionSetupPayload { private final ByteBuf setupFrame; @@ -33,30 +34,28 @@ public DefaultConnectionSetupPayload(ByteBuf setupFrame) { } @Override - public ConnectionSetupPayload retain() { - super.retain(); - return this; + public boolean hasMetadata() { + return FrameHeaderFlyweight.hasMetadata(setupFrame); } @Override - public ConnectionSetupPayload retain(int increment) { - super.retain(increment); - return this; + public ByteBuf sliceMetadata() { + return SetupFrameFlyweight.metadata(setupFrame); } @Override - public boolean hasMetadata() { - return FrameHeaderFlyweight.hasMetadata(setupFrame); + public ByteBuf sliceData() { + return SetupFrameFlyweight.data(setupFrame); } @Override - public int keepAliveInterval() { - return SetupFrameFlyweight.keepAliveInterval(setupFrame); + public ByteBuf data() { + return sliceData(); } @Override - public int keepAliveMaxLifetime() { - return SetupFrameFlyweight.keepAliveMaxLifetime(setupFrame); + public ByteBuf metadata() { + return sliceMetadata(); } @Override @@ -69,6 +68,16 @@ public String dataMimeType() { return SetupFrameFlyweight.dataMimeType(setupFrame); } + @Override + public int keepAliveInterval() { + return SetupFrameFlyweight.keepAliveInterval(setupFrame); + } + + @Override + public int keepAliveMaxLifetime() { + return SetupFrameFlyweight.keepAliveMaxLifetime(setupFrame); + } + @Override public int getFlags() { return FrameHeaderFlyweight.flags(setupFrame); @@ -105,24 +114,4 @@ public ConnectionSetupPayload touch(Object hint) { protected void deallocate() { setupFrame.release(); } - - @Override - public ByteBuf sliceMetadata() { - return SetupFrameFlyweight.metadata(setupFrame); - } - - @Override - public ByteBuf sliceData() { - return SetupFrameFlyweight.data(setupFrame); - } - - @Override - public ByteBuf data() { - return sliceData(); - } - - @Override - public ByteBuf metadata() { - return sliceMetadata(); - } } From c05eb428bf79907aad872b1e8904bfe948334924 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Sun, 19 Apr 2020 09:25:30 +0100 Subject: [PATCH 19/62] RSocketFactory wrapping constructors (#788) Allow RSocketFactory to be created with a pre-created instance of RSocketConnector or RSocketServer. This helps higher level frameworks to support both old and current APIs. Signed-off-by: Rossen Stoyanchev --- .../main/java/io/rsocket/RSocketFactory.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java b/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java index 7749d27cd..0e1ae12cb 100644 --- a/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java +++ b/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java @@ -97,7 +97,7 @@ default Start transport(ServerTransport transport) { /** Factory to create and configure an RSocket client, and connect to a server. */ public static class ClientRSocketFactory implements ClientTransportAcceptor { - private final RSocketConnector connector = RSocketConnector.create(); + private final RSocketConnector connector; private Duration tickPeriod = Duration.ofSeconds(20); private Duration ackTimeout = Duration.ofSeconds(30); @@ -105,6 +105,14 @@ public static class ClientRSocketFactory implements ClientTransportAcceptor { private Resume resume; + public ClientRSocketFactory() { + this(RSocketConnector.create()); + } + + public ClientRSocketFactory(RSocketConnector connector) { + this.connector = connector; + } + public ClientRSocketFactory byteBufAllocator(ByteBufAllocator allocator) { connector.byteBufAllocator(allocator); return this; @@ -375,10 +383,18 @@ public ClientRSocketFactory frameDecoder(PayloadDecoder payloadDecoder) { /** Factory to create, configure, and start an RSocket server. */ public static class ServerRSocketFactory implements ServerTransportAcceptor { - private final RSocketServer server = RSocketServer.create(); + private final RSocketServer server; private Resume resume; + public ServerRSocketFactory() { + this(RSocketServer.create()); + } + + public ServerRSocketFactory(RSocketServer server) { + this.server = server; + } + public ServerRSocketFactory byteBufAllocator(ByteBufAllocator allocator) { server.byteBufAllocator(allocator); return this; From 070cffee5251fc2e9f7204f7b24cd82192c0d268 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Tue, 21 Apr 2020 23:12:05 +0300 Subject: [PATCH 20/62] Track Discarded Payloads (#777) * provides extra hooks to ensure we capture all discarded elements Signed-off-by: Oleh Dokuka * provides leaks tracking tooling Signed-off-by: Oleh Dokuka * provides leaks tracking tests and tooling Signed-off-by: Oleh Dokuka * more tests Signed-off-by: Oleh Dokuka * provides mechanism for terminates queue on calling clear Signed-off-by: Oleh Dokuka * provides workaround for FluxPublishOn to ensure that all elements are released in case of racing Signed-off-by: Oleh Dokuka * provides more tests part of the tests are on racing (ignored for now) another few on verification that elements are discarded properly Signed-off-by: Oleh Dokuka * provide fixes to RequestChannel responder and related tests. Ensures there is no leaks in RSocketRequesterTest and RSocketResponder tests Signed-off-by: Oleh Dokuka * tries to migrate to junit 5 Signed-off-by: Oleh Dokuka * fixes leaks in tests Signed-off-by: Oleh Dokuka * optimizes discarded/dropped BB consumption and releasing Signed-off-by: Oleh Dokuka * fixes javadocs Signed-off-by: Oleh Dokuka * removes hooks from Decoder Signed-off-by: Oleh Dokuka * fixes format Signed-off-by: Oleh Dokuka * rollbacks some fixes that should be delivered separately Signed-off-by: Oleh Dokuka * rollbacks some build.gradle refactoring Signed-off-by: Oleh Dokuka * fixes test Signed-off-by: Oleh Dokuka * fixes test Signed-off-by: Oleh Dokuka --- .../io/rsocket/core/RSocketRequester.java | 25 +- .../io/rsocket/core/RSocketResponder.java | 22 +- .../java/io/rsocket/util/CharByteBufUtil.java | 7 +- .../buffer/LeaksTrackingByteBufAllocator.java | 167 ++++++ .../io/rsocket/core/AbstractSocketRule.java | 16 +- .../io/rsocket/core/RSocketRequesterTest.java | 386 +++++++++++-- .../io/rsocket/core/RSocketResponderTest.java | 521 ++++++++++++++++-- .../rsocket/frame/ByteBufRepresentation.java | 7 +- 8 files changed, 1049 insertions(+), 102 deletions(-) create mode 100644 rsocket-core/src/test/java/io/rsocket/buffer/LeaksTrackingByteBufAllocator.java diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index fc3175b15..70b2a1889 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -22,7 +22,9 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.util.IllegalReferenceCountException; import io.netty.util.ReferenceCountUtil; +import io.netty.util.ReferenceCounted; import io.netty.util.collection.IntObjectMap; import io.rsocket.DuplexConnection; import io.rsocket.Payload; @@ -77,6 +79,16 @@ class RSocketRequester implements RSocket { AtomicReferenceFieldUpdater.newUpdater( RSocketRequester.class, Throwable.class, "terminationError"); private static final Exception CLOSED_CHANNEL_EXCEPTION = new ClosedChannelException(); + private static final Consumer DROPPED_ELEMENTS_CONSUMER = + referenceCounted -> { + if (referenceCounted.refCnt() > 0) { + try { + referenceCounted.release(); + } catch (IllegalReferenceCountException e) { + // ignored + } + } + }; static { CLOSED_CHANNEL_EXCEPTION.setStackTrace(new StackTraceElement[0]); @@ -259,7 +271,7 @@ public void doOnTerminal( }); receivers.put(streamId, receiver); - return receiver; + return receiver.doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER); } private Flux handleRequestStream(final Payload payload) { @@ -323,7 +335,8 @@ public void accept(long n) { sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); } }) - .doFinally(s -> removeStreamReceiver(streamId)); + .doFinally(s -> removeStreamReceiver(streamId)) + .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER); } private Flux handleChannel(Flux request) { @@ -424,7 +437,10 @@ public void accept(long n) { senders.put(streamId, upstreamSubscriber); receivers.put(streamId, receiver); - inboundFlux.limitRate(Queues.SMALL_BUFFER_SIZE).subscribe(upstreamSubscriber); + inboundFlux + .limitRate(Queues.SMALL_BUFFER_SIZE) + .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER) + .subscribe(upstreamSubscriber); if (!payloadReleasedFlag.getAndSet(true)) { ByteBuf frame = RequestChannelFrameFlyweight.encode( @@ -461,7 +477,8 @@ public void accept(long n) { sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); upstreamSubscriber.cancel(); } - }); + }) + .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER); } private Mono handleMetadataPush(Payload payload) { diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java index 6f235587a..e01000e49 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java @@ -20,7 +20,9 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.util.IllegalReferenceCountException; import io.netty.util.ReferenceCountUtil; +import io.netty.util.ReferenceCounted; import io.netty.util.collection.IntObjectMap; import io.rsocket.DuplexConnection; import io.rsocket.Payload; @@ -45,6 +47,16 @@ /** Responder side of RSocket. Receives {@link ByteBuf}s from a peer's {@link RSocketRequester} */ class RSocketResponder implements ResponderRSocket { + private static final Consumer DROPPED_ELEMENTS_CONSUMER = + referenceCounted -> { + if (referenceCounted.refCnt() > 0) { + try { + referenceCounted.release(); + } catch (IllegalReferenceCountException e) { + // ignored + } + } + }; private final DuplexConnection connection; private final RSocket requestHandler; @@ -418,7 +430,7 @@ protected void hookFinally(SignalType type) { }; sendingSubscriptions.put(streamId, subscriber); - response.subscribe(subscriber); + response.doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER).subscribe(subscriber); } private void handleStream(int streamId, Flux response, int initialRequestN) { @@ -471,7 +483,10 @@ protected void hookFinally(SignalType type) { }; sendingSubscriptions.put(streamId, subscriber); - response.limitRate(Queues.SMALL_BUFFER_SIZE).subscribe(subscriber); + response + .limitRate(Queues.SMALL_BUFFER_SIZE) + .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER) + .subscribe(subscriber); } private void handleChannel(int streamId, Payload payload, int initialRequestN) { @@ -499,7 +514,8 @@ public void accept(long l) { sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); } }) - .doFinally(signalType -> channelProcessors.remove(streamId)); + .doFinally(signalType -> channelProcessors.remove(streamId)) + .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER); // not chained, as the payload should be enqueued in the Unicast processor before this method // returns diff --git a/rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java b/rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java index 6f2aa7150..328fb8435 100644 --- a/rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java +++ b/rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java @@ -99,7 +99,7 @@ private static char[] checkCharSequenceBounds(char[] seq, int start, int end) { } /** - * Encode a {@link char[]} in UTF-8 and write it + * Encode a {@code char[]} in UTF-8 and write it * into {@link ByteBuf}. * *

    This method returns the actual number of bytes written. @@ -109,9 +109,8 @@ public static int writeUtf8(ByteBuf buf, char[] seq) { } /** - * Equivalent to {@link #writeUtf8(ByteBuf, char[]) - * writeUtf8(buf, seq.subSequence(start, end), reserveBytes)} but avoids subsequence object - * allocation if possible. + * Equivalent to {@link #writeUtf8(ByteBuf, char[]) writeUtf8(buf, seq.subSequence(start, end), + * reserveBytes)} but avoids subsequence object allocation if possible. * * @return actual number of bytes written */ diff --git a/rsocket-core/src/test/java/io/rsocket/buffer/LeaksTrackingByteBufAllocator.java b/rsocket-core/src/test/java/io/rsocket/buffer/LeaksTrackingByteBufAllocator.java new file mode 100644 index 000000000..2044779ef --- /dev/null +++ b/rsocket-core/src/test/java/io/rsocket/buffer/LeaksTrackingByteBufAllocator.java @@ -0,0 +1,167 @@ +package io.rsocket.buffer; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.CompositeByteBuf; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +import org.assertj.core.api.Assertions; + +/** + * Additional Utils which allows to decorate a ByteBufAllocator and track/assertOnLeaks all created + * ByteBuffs + */ +public class LeaksTrackingByteBufAllocator implements ByteBufAllocator { + + /** + * Allows to instrument any given the instance of ByteBufAllocator + * + * @param allocator + * @return + */ + public static LeaksTrackingByteBufAllocator instrument(ByteBufAllocator allocator) { + return new LeaksTrackingByteBufAllocator(allocator); + } + + final ConcurrentLinkedQueue tracker = new ConcurrentLinkedQueue<>(); + + final ByteBufAllocator delegate; + + private LeaksTrackingByteBufAllocator(ByteBufAllocator delegate) { + this.delegate = delegate; + } + + public LeaksTrackingByteBufAllocator assertHasNoLeaks() { + try { + Assertions.assertThat(tracker) + .allSatisfy( + buf -> { + if (buf instanceof CompositeByteBuf) { + if (buf.refCnt() > 0) { + List decomposed = + ((CompositeByteBuf) buf).decompose(0, buf.readableBytes()); + for (int i = 0; i < decomposed.size(); i++) { + Assertions.assertThat(decomposed.get(i)) + .matches(bb -> bb.refCnt() == 0, "Got unreleased CompositeByteBuf"); + } + } + + } else { + Assertions.assertThat(buf) + .matches(bb -> bb.refCnt() == 0, "buffer should be released"); + } + }); + } finally { + tracker.clear(); + } + return this; + } + + // Delegating logic with tracking of buffers + + @Override + public ByteBuf buffer() { + return track(delegate.buffer()); + } + + @Override + public ByteBuf buffer(int initialCapacity) { + return track(delegate.buffer(initialCapacity)); + } + + @Override + public ByteBuf buffer(int initialCapacity, int maxCapacity) { + return track(delegate.buffer(initialCapacity, maxCapacity)); + } + + @Override + public ByteBuf ioBuffer() { + return track(delegate.ioBuffer()); + } + + @Override + public ByteBuf ioBuffer(int initialCapacity) { + return track(delegate.ioBuffer(initialCapacity)); + } + + @Override + public ByteBuf ioBuffer(int initialCapacity, int maxCapacity) { + return track(delegate.ioBuffer(initialCapacity, maxCapacity)); + } + + @Override + public ByteBuf heapBuffer() { + return track(delegate.heapBuffer()); + } + + @Override + public ByteBuf heapBuffer(int initialCapacity) { + return track(delegate.heapBuffer(initialCapacity)); + } + + @Override + public ByteBuf heapBuffer(int initialCapacity, int maxCapacity) { + return track(delegate.heapBuffer(initialCapacity, maxCapacity)); + } + + @Override + public ByteBuf directBuffer() { + return track(delegate.directBuffer()); + } + + @Override + public ByteBuf directBuffer(int initialCapacity) { + return track(delegate.directBuffer(initialCapacity)); + } + + @Override + public ByteBuf directBuffer(int initialCapacity, int maxCapacity) { + return track(delegate.directBuffer(initialCapacity, maxCapacity)); + } + + @Override + public CompositeByteBuf compositeBuffer() { + return track(delegate.compositeBuffer()); + } + + @Override + public CompositeByteBuf compositeBuffer(int maxNumComponents) { + return track(delegate.compositeBuffer(maxNumComponents)); + } + + @Override + public CompositeByteBuf compositeHeapBuffer() { + return track(delegate.compositeHeapBuffer()); + } + + @Override + public CompositeByteBuf compositeHeapBuffer(int maxNumComponents) { + return track(delegate.compositeHeapBuffer(maxNumComponents)); + } + + @Override + public CompositeByteBuf compositeDirectBuffer() { + return track(delegate.compositeDirectBuffer()); + } + + @Override + public CompositeByteBuf compositeDirectBuffer(int maxNumComponents) { + return track(delegate.compositeDirectBuffer(maxNumComponents)); + } + + @Override + public boolean isDirectBufferPooled() { + return delegate.isDirectBufferPooled(); + } + + @Override + public int calculateNewCapacity(int minNewCapacity, int maxCapacity) { + return delegate.calculateNewCapacity(minNewCapacity, maxCapacity); + } + + T track(T buffer) { + tracker.offer(buffer); + + return buffer; + } +} diff --git a/rsocket-core/src/test/java/io/rsocket/core/AbstractSocketRule.java b/rsocket-core/src/test/java/io/rsocket/core/AbstractSocketRule.java index dc01e7911..5a43838c7 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/AbstractSocketRule.java +++ b/rsocket-core/src/test/java/io/rsocket/core/AbstractSocketRule.java @@ -16,7 +16,9 @@ package io.rsocket.core; +import io.netty.buffer.ByteBufAllocator; import io.rsocket.RSocket; +import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.test.util.TestDuplexConnection; import io.rsocket.test.util.TestSubscriber; import java.util.concurrent.ConcurrentLinkedQueue; @@ -32,6 +34,7 @@ public abstract class AbstractSocketRule extends ExternalReso protected Subscriber connectSub; protected T socket; protected ConcurrentLinkedQueue errors; + protected LeaksTrackingByteBufAllocator allocator; @Override public Statement apply(final Statement base, Description description) { @@ -41,6 +44,7 @@ public void evaluate() throws Throwable { connection = new TestDuplexConnection(); connectSub = TestSubscriber.create(); errors = new ConcurrentLinkedQueue<>(); + allocator = LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); init(); base.evaluate(); } @@ -48,14 +52,22 @@ public void evaluate() throws Throwable { } protected void init() { - socket = newRSocket(); + socket = newRSocket(allocator); } - protected abstract T newRSocket(); + protected abstract T newRSocket(LeaksTrackingByteBufAllocator allocator); public void assertNoConnectionErrors() { if (errors.size() > 1) { Assert.fail("No connection errors expected: " + errors.peek().toString()); } } + + public ByteBufAllocator alloc() { + return allocator; + } + + public void assertHasNoLeaks() { + allocator.assertHasNoLeaks(); + } } diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java index 101500da7..586c9cfd3 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java @@ -19,7 +19,6 @@ import static io.rsocket.core.PayloadValidationUtils.INVALID_PAYLOAD_ERROR_MESSAGE; import static io.rsocket.frame.FrameHeaderFlyweight.frameType; import static io.rsocket.frame.FrameType.CANCEL; -import static io.rsocket.frame.FrameType.KEEPALIVE; import static io.rsocket.frame.FrameType.REQUEST_CHANNEL; import static io.rsocket.frame.FrameType.REQUEST_RESPONSE; import static io.rsocket.frame.FrameType.REQUEST_STREAM; @@ -37,9 +36,12 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.util.CharsetUtil; +import io.netty.util.ReferenceCounted; import io.rsocket.Payload; import io.rsocket.RSocket; +import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.exceptions.ApplicationErrorException; +import io.rsocket.exceptions.CustomRSocketException; import io.rsocket.exceptions.RejectedSetupException; import io.rsocket.frame.CancelFrameFlyweight; import io.rsocket.frame.ErrorFrameFlyweight; @@ -50,8 +52,11 @@ import io.rsocket.frame.RequestChannelFrameFlyweight; import io.rsocket.frame.RequestNFrameFlyweight; import io.rsocket.frame.RequestStreamFrameFlyweight; +import io.rsocket.frame.decoder.PayloadDecoder; +import io.rsocket.internal.subscriber.AssertSubscriber; import io.rsocket.lease.RequesterLeaseHandler; import io.rsocket.test.util.TestSubscriber; +import io.rsocket.util.ByteBufPayload; import io.rsocket.util.DefaultPayload; import io.rsocket.util.EmptyPayload; import io.rsocket.util.MultiSubscriberRSocket; @@ -60,12 +65,17 @@ import java.util.Iterator; import java.util.List; import java.util.concurrent.ThreadLocalRandom; +import java.util.function.BiConsumer; import java.util.function.BiFunction; -import java.util.stream.Collectors; +import java.util.function.Function; import java.util.stream.Stream; import org.assertj.core.api.Assertions; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.runners.model.Statement; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; @@ -75,23 +85,39 @@ import reactor.core.publisher.MonoProcessor; import reactor.core.publisher.UnicastProcessor; import reactor.test.StepVerifier; +import reactor.test.publisher.TestPublisher; +import reactor.test.util.RaceTestUtils; public class RSocketRequesterTest { - @Rule public final ClientSocketRule rule = new ClientSocketRule(); + ClientSocketRule rule; + + @BeforeEach + public void setUp() throws Throwable { + rule = new ClientSocketRule(); + rule.apply( + new Statement() { + @Override + public void evaluate() {} + }, + null) + .evaluate(); + } - @Test(timeout = 2_000) + @Test + @Timeout(2_000) public void testInvalidFrameOnStream0() { - rule.connection.addToReceivedBuffer( - RequestNFrameFlyweight.encode(ByteBufAllocator.DEFAULT, 0, 10)); + rule.connection.addToReceivedBuffer(RequestNFrameFlyweight.encode(rule.alloc(), 0, 10)); assertThat("Unexpected errors.", rule.errors, hasSize(1)); assertThat( "Unexpected error received.", rule.errors, contains(instanceOf(IllegalStateException.class))); + rule.assertHasNoLeaks(); } - @Test(timeout = 2_000) + @Test + @Timeout(2_000) public void testStreamInitialN() { Flux stream = rule.socket.requestStream(EmptyPayload.INSTANCE); @@ -100,19 +126,15 @@ public void testStreamInitialN() { @Override protected void hookOnSubscribe(Subscription subscription) { // don't request here - // subscription.request(3); } }; stream.subscribe(subscriber); + Assertions.assertThat(rule.connection.getSent()).isEmpty(); + subscriber.request(5); - List sent = - rule.connection - .getSent() - .stream() - .filter(f -> frameType(f) != KEEPALIVE) - .collect(Collectors.toList()); + List sent = new ArrayList<>(rule.connection.getSent()); assertThat("sent frame count", sent.size(), is(1)); @@ -120,21 +142,25 @@ protected void hookOnSubscribe(Subscription subscription) { assertThat("initial frame", frameType(f), is(REQUEST_STREAM)); assertThat("initial request n", RequestStreamFrameFlyweight.initialRequestN(f), is(5)); + assertThat("should be released", f.release(), is(true)); + rule.assertHasNoLeaks(); } - @Test(timeout = 2_000) + @Test + @Timeout(2_000) public void testHandleSetupException() { rule.connection.addToReceivedBuffer( - ErrorFrameFlyweight.encode( - ByteBufAllocator.DEFAULT, 0, new RejectedSetupException("boom"))); + ErrorFrameFlyweight.encode(rule.alloc(), 0, new RejectedSetupException("boom"))); assertThat("Unexpected errors.", rule.errors, hasSize(1)); assertThat( "Unexpected error received.", rule.errors, contains(instanceOf(RejectedSetupException.class))); + rule.assertHasNoLeaks(); } - @Test(timeout = 2_000) + @Test + @Timeout(2_000) public void testHandleApplicationException() { rule.connection.clearSendReceiveBuffers(); Publisher response = rule.socket.requestResponse(EmptyPayload.INSTANCE); @@ -143,13 +169,20 @@ public void testHandleApplicationException() { int streamId = rule.getStreamIdForRequestType(REQUEST_RESPONSE); rule.connection.addToReceivedBuffer( - ErrorFrameFlyweight.encode( - ByteBufAllocator.DEFAULT, streamId, new ApplicationErrorException("error"))); + ErrorFrameFlyweight.encode(rule.alloc(), streamId, new ApplicationErrorException("error"))); verify(responseSub).onError(any(ApplicationErrorException.class)); + + Assertions.assertThat(rule.connection.getSent()) + // requestResponseFrame FIXME + // .hasSize(1) + .allMatch(ReferenceCounted::release); + + rule.assertHasNoLeaks(); } - @Test(timeout = 2_000) + @Test + @Timeout(2_000) public void testHandleValidFrame() { Publisher response = rule.socket.requestResponse(EmptyPayload.INSTANCE); Subscriber sub = TestSubscriber.create(); @@ -157,13 +190,15 @@ public void testHandleValidFrame() { int streamId = rule.getStreamIdForRequestType(REQUEST_RESPONSE); rule.connection.addToReceivedBuffer( - PayloadFrameFlyweight.encodeNext( - ByteBufAllocator.DEFAULT, streamId, EmptyPayload.INSTANCE)); + PayloadFrameFlyweight.encodeNext(rule.alloc(), streamId, EmptyPayload.INSTANCE)); verify(sub).onComplete(); + Assertions.assertThat(rule.connection.getSent()).hasSize(1).allMatch(ReferenceCounted::release); + rule.assertHasNoLeaks(); } - @Test(timeout = 2_000) + @Test + @Timeout(2_000) public void testRequestReplyWithCancel() { Mono response = rule.socket.requestResponse(EmptyPayload.INSTANCE); @@ -172,19 +207,18 @@ public void testRequestReplyWithCancel() { } catch (IllegalStateException ise) { } - List sent = - rule.connection - .getSent() - .stream() - .filter(f -> frameType(f) != KEEPALIVE) - .collect(Collectors.toList()); + List sent = new ArrayList<>(rule.connection.getSent()); assertThat( "Unexpected frame sent on the connection.", frameType(sent.get(0)), is(REQUEST_RESPONSE)); assertThat("Unexpected frame sent on the connection.", frameType(sent.get(1)), is(CANCEL)); + Assertions.assertThat(sent).hasSize(2).allMatch(ReferenceCounted::release); + rule.assertHasNoLeaks(); } - @Test(timeout = 2_000) + @Test + @Disabled("invalid") + @Timeout(2_000) public void testRequestReplyErrorOnSend() { rule.connection.setAvailability(0); // Fails send Mono response = rule.socket.requestResponse(EmptyPayload.INSTANCE); @@ -195,21 +229,28 @@ public void testRequestReplyErrorOnSend() { verify(responseSub).onSubscribe(any(Subscription.class)); + rule.assertHasNoLeaks(); // TODO this should get the error reported through the response subscription // verify(responseSub).onError(any(RuntimeException.class)); } - @Test(timeout = 2_000) + @Test + @Timeout(2_000) public void testLazyRequestResponse() { Publisher response = new MultiSubscriberRSocket(rule.socket).requestResponse(EmptyPayload.INSTANCE); int streamId = sendRequestResponse(response); + Assertions.assertThat(rule.connection.getSent()).hasSize(1).allMatch(ReferenceCounted::release); + rule.assertHasNoLeaks(); rule.connection.clearSendReceiveBuffers(); int streamId2 = sendRequestResponse(response); assertThat("Stream ID reused.", streamId2, not(equalTo(streamId))); + Assertions.assertThat(rule.connection.getSent()).hasSize(1).allMatch(ReferenceCounted::release); + rule.assertHasNoLeaks(); } @Test + @Timeout(2_000) public void testChannelRequestCancellation() { MonoProcessor cancelled = MonoProcessor.create(); Flux request = Flux.never().doOnCancel(cancelled::onComplete); @@ -219,9 +260,11 @@ public void testChannelRequestCancellation() { Flux.error(new IllegalStateException("Channel request not cancelled")) .delaySubscription(Duration.ofSeconds(1))) .blockFirst(); + rule.assertHasNoLeaks(); } @Test + @Timeout(2_000) public void testChannelRequestCancellation2() { MonoProcessor cancelled = MonoProcessor.create(); Flux request = @@ -232,6 +275,8 @@ public void testChannelRequestCancellation2() { Flux.error(new IllegalStateException("Channel request not cancelled")) .delaySubscription(Duration.ofSeconds(1))) .blockFirst(); + Assertions.assertThat(rule.connection.getSent()).allMatch(ReferenceCounted::release); + rule.assertHasNoLeaks(); } @Test @@ -241,10 +286,9 @@ public void testChannelRequestServerSideCancellation() { request.onNext(EmptyPayload.INSTANCE); rule.socket.requestChannel(request).subscribe(cancelled); int streamId = rule.getStreamIdForRequestType(REQUEST_CHANNEL); + rule.connection.addToReceivedBuffer(CancelFrameFlyweight.encode(rule.alloc(), streamId)); rule.connection.addToReceivedBuffer( - CancelFrameFlyweight.encode(ByteBufAllocator.DEFAULT, streamId)); - rule.connection.addToReceivedBuffer( - PayloadFrameFlyweight.encodeComplete(ByteBufAllocator.DEFAULT, streamId)); + PayloadFrameFlyweight.encodeComplete(rule.alloc(), streamId)); Flux.first( cancelled, Flux.error(new IllegalStateException("Channel request not cancelled")) @@ -252,6 +296,12 @@ public void testChannelRequestServerSideCancellation() { .blockFirst(); Assertions.assertThat(request.isDisposed()).isTrue(); + Assertions.assertThat(rule.connection.getSent()) + .hasSize(1) + .first() + .matches(bb -> frameType(bb) == REQUEST_CHANNEL) + .matches(ReferenceCounted::release); + rule.assertHasNoLeaks(); } @Test @@ -282,8 +332,10 @@ protected void hookOnSubscribe(Subscription subscription) {} Assertions.assertThat( RequestChannelFrameFlyweight.data(initialFrame).toString(CharsetUtil.UTF_8)) .isEqualTo("0"); + Assertions.assertThat(initialFrame.release()).isTrue(); Assertions.assertThat(iterator.hasNext()).isFalse(); + rule.assertHasNoLeaks(); } @Test @@ -304,9 +356,21 @@ public void shouldThrownExceptionIfGivenPayloadIsExitsSizeAllowanceWithNoFragmen .isInstanceOf(IllegalArgumentException.class) .hasMessage(INVALID_PAYLOAD_ERROR_MESSAGE)) .verify(); + // FIXME: should be removed + Assertions.assertThat(rule.connection.getSent()).allMatch(bb -> bb.release()); + rule.assertHasNoLeaks(); }); } + static Stream>> prepareCalls() { + return Stream.of( + RSocket::fireAndForget, + RSocket::requestResponse, + RSocket::requestStream, + (rSocket, payload) -> rSocket.requestChannel(Flux.just(payload)), + RSocket::metadataPush); + } + @Test public void shouldThrownExceptionIfGivenPayloadIsExitsSizeAllowanceWithNoFragmentationForRequestChannelCase() { @@ -322,24 +386,245 @@ public void shouldThrownExceptionIfGivenPayloadIsExitsSizeAllowanceWithNoFragmen () -> rule.connection.addToReceivedBuffer( RequestNFrameFlyweight.encode( - ByteBufAllocator.DEFAULT, - rule.getStreamIdForRequestType(REQUEST_CHANNEL), - 2))) + rule.alloc(), rule.getStreamIdForRequestType(REQUEST_CHANNEL), 2))) .expectErrorSatisfies( t -> Assertions.assertThat(t) .isInstanceOf(IllegalArgumentException.class) .hasMessage(INVALID_PAYLOAD_ERROR_MESSAGE)) .verify(); + Assertions.assertThat(rule.connection.getSent()) + // expect to be sent RequestChannelFrame + // expect to be sent CancelFrame + .hasSize(2) + .allMatch(ReferenceCounted::release); + rule.assertHasNoLeaks(); } - static Stream>> prepareCalls() { + @Test + @Disabled("Due to https://github.com/reactor/reactor-core/pull/2114") + @SuppressWarnings("unchecked") + public void checkNoLeaksOnRacingTest() { + + racingCases() + .forEach( + a -> { + ((Runnable) a.get()[0]).run(); + checkNoLeaksOnRacing( + (Function>) a.get()[1], + (BiConsumer, ClientSocketRule>) a.get()[2]); + }); + } + + public void checkNoLeaksOnRacing( + Function> initiator, + BiConsumer, ClientSocketRule> runner) { + for (int i = 0; i < 10000; i++) { + ClientSocketRule clientSocketRule = new ClientSocketRule(); + try { + clientSocketRule + .apply( + new Statement() { + @Override + public void evaluate() {} + }, + null) + .evaluate(); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + + Publisher payloadP = initiator.apply(clientSocketRule); + AssertSubscriber assertSubscriber = AssertSubscriber.create(); + + if (payloadP instanceof Flux) { + ((Flux) payloadP).doOnNext(Payload::release).subscribe(assertSubscriber); + } else { + ((Mono) payloadP).doOnNext(Payload::release).subscribe(assertSubscriber); + } + + runner.accept(assertSubscriber, clientSocketRule); + + Assertions.assertThat(clientSocketRule.connection.getSent()) + .allMatch(ReferenceCounted::release); + + rule.assertHasNoLeaks(); + } + } + + private static Stream racingCases() { return Stream.of( - RSocket::fireAndForget, - RSocket::requestResponse, - RSocket::requestStream, - (rSocket, payload) -> rSocket.requestChannel(Flux.just(payload)), - RSocket::metadataPush); + Arguments.of( + (Runnable) () -> System.out.println("RequestStream downstream cancellation case"), + (Function>) + (rule) -> rule.socket.requestStream(EmptyPayload.INSTANCE), + (BiConsumer, ClientSocketRule>) + (as, rule) -> { + ByteBufAllocator allocator = rule.alloc(); + ByteBuf metadata = allocator.buffer(); + metadata.writeCharSequence("abc", CharsetUtil.UTF_8); + ByteBuf data = allocator.buffer(); + data.writeCharSequence("def", CharsetUtil.UTF_8); + int streamId = rule.getStreamIdForRequestType(REQUEST_STREAM); + ByteBuf frame = + PayloadFrameFlyweight.encode( + allocator, streamId, false, false, true, metadata, data); + + RaceTestUtils.race(as::cancel, () -> rule.connection.addToReceivedBuffer(frame)); + }), + Arguments.of( + (Runnable) () -> System.out.println("RequestChannel downstream cancellation case"), + (Function>) + (rule) -> rule.socket.requestChannel(Flux.just(EmptyPayload.INSTANCE)), + (BiConsumer, ClientSocketRule>) + (as, rule) -> { + ByteBufAllocator allocator = rule.alloc(); + ByteBuf metadata = allocator.buffer(); + metadata.writeCharSequence("abc", CharsetUtil.UTF_8); + ByteBuf data = allocator.buffer(); + data.writeCharSequence("def", CharsetUtil.UTF_8); + int streamId = rule.getStreamIdForRequestType(REQUEST_CHANNEL); + ByteBuf frame = + PayloadFrameFlyweight.encode( + allocator, streamId, false, false, true, metadata, data); + + RaceTestUtils.race(as::cancel, () -> rule.connection.addToReceivedBuffer(frame)); + }), + Arguments.of( + (Runnable) () -> System.out.println("RequestChannel upstream cancellation 1"), + (Function>) + (rule) -> { + ByteBufAllocator allocator = rule.alloc(); + ByteBuf metadata = allocator.buffer(); + metadata.writeCharSequence("abc", CharsetUtil.UTF_8); + ByteBuf data = allocator.buffer(); + data.writeCharSequence("def", CharsetUtil.UTF_8); + return rule.socket.requestChannel( + Flux.just(ByteBufPayload.create(data, metadata))); + }, + (BiConsumer, ClientSocketRule>) + (as, rule) -> { + ByteBufAllocator allocator = rule.alloc(); + int streamId = rule.getStreamIdForRequestType(REQUEST_CHANNEL); + ByteBuf frame = CancelFrameFlyweight.encode(allocator, streamId); + + RaceTestUtils.race( + () -> as.request(1), () -> rule.connection.addToReceivedBuffer(frame)); + }), + Arguments.of( + (Runnable) () -> System.out.println("RequestChannel upstream cancellation 2"), + (Function>) + (rule) -> + rule.socket.requestChannel( + Flux.generate( + () -> 1L, + (index, sink) -> { + final Payload payload = + ByteBufPayload.create("d" + index, "m" + index); + sink.next(payload); + return ++index; + })), + (BiConsumer, ClientSocketRule>) + (as, rule) -> { + ByteBufAllocator allocator = rule.alloc(); + int streamId = rule.getStreamIdForRequestType(REQUEST_CHANNEL); + ByteBuf frame = CancelFrameFlyweight.encode(allocator, streamId); + + as.request(1); + + RaceTestUtils.race( + () -> as.request(Long.MAX_VALUE), + () -> rule.connection.addToReceivedBuffer(frame)); + }), + Arguments.of( + (Runnable) () -> System.out.println("RequestChannel remote error"), + (Function>) + (rule) -> + rule.socket.requestChannel( + Flux.generate( + () -> 1L, + (index, sink) -> { + final Payload payload = + ByteBufPayload.create("d" + index, "m" + index); + sink.next(payload); + return ++index; + })), + (BiConsumer, ClientSocketRule>) + (as, rule) -> { + ByteBufAllocator allocator = rule.alloc(); + int streamId = rule.getStreamIdForRequestType(REQUEST_CHANNEL); + ByteBuf frame = + ErrorFrameFlyweight.encode(allocator, streamId, new RuntimeException("test")); + + as.request(1); + + RaceTestUtils.race( + () -> as.request(Long.MAX_VALUE), + () -> rule.connection.addToReceivedBuffer(frame)); + }), + Arguments.of( + (Runnable) () -> System.out.println("RequestResponse downstream cancellation"), + (Function>) + (rule) -> rule.socket.requestResponse(EmptyPayload.INSTANCE), + (BiConsumer, ClientSocketRule>) + (as, rule) -> { + ByteBufAllocator allocator = rule.alloc(); + ByteBuf metadata = allocator.buffer(); + metadata.writeCharSequence("abc", CharsetUtil.UTF_8); + ByteBuf data = allocator.buffer(); + data.writeCharSequence("def", CharsetUtil.UTF_8); + int streamId = rule.getStreamIdForRequestType(REQUEST_RESPONSE); + ByteBuf frame = + PayloadFrameFlyweight.encode( + allocator, streamId, false, false, true, metadata, data); + + RaceTestUtils.race(as::cancel, () -> rule.connection.addToReceivedBuffer(frame)); + })); + } + + @Test + public void simpleOnDiscardRequestChannelTest() { + AssertSubscriber assertSubscriber = AssertSubscriber.create(1); + TestPublisher testPublisher = TestPublisher.create(); + + Flux payloadFlux = rule.socket.requestChannel(testPublisher); + + payloadFlux.subscribe(assertSubscriber); + + testPublisher.next( + ByteBufPayload.create("d", "m"), + ByteBufPayload.create("d1", "m1"), + ByteBufPayload.create("d2", "m2")); + + assertSubscriber.cancel(); + + Assertions.assertThat(rule.connection.getSent()).allMatch(ByteBuf::release); + + rule.assertHasNoLeaks(); + } + + @Test + public void simpleOnDiscardRequestChannelTest2() { + ByteBufAllocator allocator = rule.alloc(); + AssertSubscriber assertSubscriber = AssertSubscriber.create(1); + TestPublisher testPublisher = TestPublisher.create(); + + Flux payloadFlux = rule.socket.requestChannel(testPublisher); + + payloadFlux.subscribe(assertSubscriber); + + testPublisher.next(ByteBufPayload.create("d", "m")); + + int streamId = rule.getStreamIdForRequestType(REQUEST_CHANNEL); + testPublisher.next(ByteBufPayload.create("d1", "m1"), ByteBufPayload.create("d2", "m2")); + + rule.connection.addToReceivedBuffer( + ErrorFrameFlyweight.encode( + allocator, streamId, new CustomRSocketException(0x00000404, "test"))); + + Assertions.assertThat(rule.connection.getSent()).allMatch(ByteBuf::release); + + rule.assertHasNoLeaks(); } public int sendRequestResponse(Publisher response) { @@ -347,8 +632,7 @@ public int sendRequestResponse(Publisher response) { response.subscribe(sub); int streamId = rule.getStreamIdForRequestType(REQUEST_RESPONSE); rule.connection.addToReceivedBuffer( - PayloadFrameFlyweight.encodeNextComplete( - ByteBufAllocator.DEFAULT, streamId, EmptyPayload.INSTANCE)); + PayloadFrameFlyweight.encodeNextComplete(rule.alloc(), streamId, EmptyPayload.INSTANCE)); verify(sub).onNext(any(Payload.class)); verify(sub).onComplete(); return streamId; @@ -356,11 +640,11 @@ public int sendRequestResponse(Publisher response) { public static class ClientSocketRule extends AbstractSocketRule { @Override - protected RSocketRequester newRSocket() { + protected RSocketRequester newRSocket(LeaksTrackingByteBufAllocator allocator) { return new RSocketRequester( - ByteBufAllocator.DEFAULT, + allocator, connection, - DefaultPayload::create, + PayloadDecoder.ZERO_COPY, throwable -> errors.add(throwable), StreamIdSupplier.clientSupplier(), 0, diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java index 5c147f46f..d31fc3bf7 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java @@ -18,43 +18,93 @@ import static io.rsocket.core.PayloadValidationUtils.INVALID_PAYLOAD_ERROR_MESSAGE; import static io.rsocket.frame.FrameHeaderFlyweight.frameType; +import static io.rsocket.frame.FrameType.REQUEST_CHANNEL; +import static io.rsocket.frame.FrameType.REQUEST_RESPONSE; +import static io.rsocket.frame.FrameType.REQUEST_STREAM; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; +import io.netty.util.CharsetUtil; +import io.netty.util.ReferenceCountUtil; +import io.netty.util.ReferenceCounted; import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.frame.*; +import io.rsocket.buffer.LeaksTrackingByteBufAllocator; +import io.rsocket.frame.CancelFrameFlyweight; +import io.rsocket.frame.ErrorFrameFlyweight; +import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.FrameLengthFlyweight; +import io.rsocket.frame.FrameType; +import io.rsocket.frame.KeepAliveFrameFlyweight; +import io.rsocket.frame.PayloadFrameFlyweight; +import io.rsocket.frame.RequestChannelFrameFlyweight; +import io.rsocket.frame.RequestNFrameFlyweight; +import io.rsocket.frame.RequestResponseFrameFlyweight; +import io.rsocket.frame.RequestStreamFrameFlyweight; +import io.rsocket.frame.decoder.PayloadDecoder; +import io.rsocket.internal.subscriber.AssertSubscriber; import io.rsocket.lease.ResponderLeaseHandler; import io.rsocket.test.util.TestDuplexConnection; import io.rsocket.test.util.TestSubscriber; +import io.rsocket.util.ByteBufPayload; import io.rsocket.util.DefaultPayload; -import io.rsocket.util.EmptyPayload; import java.util.Collection; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicBoolean; import org.assertj.core.api.Assertions; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.runners.model.Statement; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; +import reactor.core.CoreSubscriber; import reactor.core.publisher.Flux; +import reactor.core.publisher.FluxSink; +import reactor.core.publisher.Hooks; import reactor.core.publisher.Mono; +import reactor.core.publisher.Operators; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; +import reactor.test.util.RaceTestUtils; public class RSocketResponderTest { - @Rule public final ServerSocketRule rule = new ServerSocketRule(); + ServerSocketRule rule; - @Test(timeout = 2000) - @Ignore + @BeforeEach + public void setUp() throws Throwable { + rule = new ServerSocketRule(); + rule.apply( + new Statement() { + @Override + public void evaluate() {} + }, + null) + .evaluate(); + } + + @AfterEach + public void tearDown() { + Hooks.resetOnErrorDropped(); + } + + @Test + @Timeout(2_000) + @Disabled public void testHandleKeepAlive() throws Exception { rule.connection.addToReceivedBuffer( - KeepAliveFrameFlyweight.encode(ByteBufAllocator.DEFAULT, true, 0, Unpooled.EMPTY_BUFFER)); + KeepAliveFrameFlyweight.encode(rule.alloc(), true, 0, Unpooled.EMPTY_BUFFER)); ByteBuf sent = rule.connection.awaitSend(); assertThat("Unexpected frame sent.", frameType(sent), is(FrameType.KEEPALIVE)); /*Keep alive ack must not have respond flag else, it will result in infinite ping-pong of keep alive frames.*/ @@ -64,8 +114,9 @@ public void testHandleKeepAlive() throws Exception { is(false)); } - @Test(timeout = 2000) - @Ignore + @Test + @Timeout(2_000) + @Disabled public void testHandleResponseFrameNoError() throws Exception { final int streamId = 4; rule.connection.clearSendReceiveBuffers(); @@ -82,8 +133,9 @@ public void testHandleResponseFrameNoError() throws Exception { anyOf(is(FrameType.COMPLETE), is(FrameType.NEXT_COMPLETE))); } - @Test(timeout = 2000) - @Ignore + @Test + @Timeout(2_000) + @Disabled public void testHandlerEmitsError() throws Exception { final int streamId = 4; rule.sendRequest(streamId, FrameType.REQUEST_STREAM); @@ -92,14 +144,17 @@ public void testHandlerEmitsError() throws Exception { "Unexpected frame sent.", frameType(rule.connection.awaitSend()), is(FrameType.ERROR)); } - @Test(timeout = 2_0000) + @Test + @Timeout(20_000) public void testCancel() { + ByteBufAllocator allocator = rule.alloc(); final int streamId = 4; final AtomicBoolean cancelled = new AtomicBoolean(); rule.setAcceptingSocket( new AbstractRSocket() { @Override public Mono requestResponse(Payload payload) { + payload.release(); return Mono.never().doOnCancel(() -> cancelled.set(true)); } }); @@ -108,14 +163,15 @@ public Mono requestResponse(Payload payload) { assertThat("Unexpected error.", rule.errors, is(empty())); assertThat("Unexpected frame sent.", rule.connection.getSent(), is(empty())); - rule.connection.addToReceivedBuffer( - CancelFrameFlyweight.encode(ByteBufAllocator.DEFAULT, streamId)); + rule.connection.addToReceivedBuffer(CancelFrameFlyweight.encode(allocator, streamId)); assertThat("Unexpected frame sent.", rule.connection.getSent(), is(empty())); assertThat("Subscription not cancelled.", cancelled.get(), is(true)); + rule.assertHasNoLeaks(); } @Test + @Timeout(2_000) public void shouldThrownExceptionIfGivenPayloadIsExitsSizeAllowanceWithNoFragmentation() { final int streamId = 4; final AtomicBoolean cancelled = new AtomicBoolean(); @@ -128,48 +184,429 @@ public void shouldThrownExceptionIfGivenPayloadIsExitsSizeAllowanceWithNoFragmen new AbstractRSocket() { @Override public Mono requestResponse(Payload p) { + p.release(); return Mono.just(payload).doOnCancel(() -> cancelled.set(true)); } @Override public Flux requestStream(Payload p) { + p.release(); return Flux.just(payload).doOnCancel(() -> cancelled.set(true)); } - - @Override - public Flux requestChannel(Publisher payloads) { - return Flux.just(payload).doOnCancel(() -> cancelled.set(true)); - } + // FIXME + // @Override + // public Flux requestChannel(Publisher payloads) { + // Flux.from(payloads) + // .doOnNext(Payload::release) + // .subscribe( + // new BaseSubscriber() { + // @Override + // protected void hookOnSubscribe(Subscription subscription) { + // subscription.request(1); + // } + // }); + // return Flux.just(payload).doOnCancel(() -> cancelled.set(true)); + // } }; rule.setAcceptingSocket(acceptingSocket); final Runnable[] runnables = { () -> rule.sendRequest(streamId, FrameType.REQUEST_RESPONSE), - () -> rule.sendRequest(streamId, FrameType.REQUEST_STREAM), - () -> rule.sendRequest(streamId, FrameType.REQUEST_CHANNEL) + () -> rule.sendRequest(streamId, FrameType.REQUEST_STREAM) /* FIXME, + () -> rule.sendRequest(streamId, FrameType.REQUEST_CHANNEL)*/ }; for (Runnable runnable : runnables) { + rule.connection.clearSendReceiveBuffers(); runnable.run(); Assertions.assertThat(rule.errors) .first() .isInstanceOf(IllegalArgumentException.class) .hasToString("java.lang.IllegalArgumentException: " + INVALID_PAYLOAD_ERROR_MESSAGE); Assertions.assertThat(rule.connection.getSent()) + .filteredOn(bb -> FrameHeaderFlyweight.frameType(bb) == FrameType.ERROR) .hasSize(1) .first() - .matches(bb -> FrameHeaderFlyweight.frameType(bb) == FrameType.ERROR) - .matches(bb -> ErrorFrameFlyweight.dataUtf8(bb).contains(INVALID_PAYLOAD_ERROR_MESSAGE)); + .matches(bb -> ErrorFrameFlyweight.dataUtf8(bb).contains(INVALID_PAYLOAD_ERROR_MESSAGE)) + .matches(ReferenceCounted::release); assertThat("Subscription not cancelled.", cancelled.get(), is(true)); - rule.init(); - rule.setAcceptingSocket(acceptingSocket); } + + rule.assertHasNoLeaks(); + } + + @Test + @Disabled("Due to https://github.com/reactor/reactor-core/pull/2114") + public void checkNoLeaksOnRacingCancelFromRequestChannelAndNextFromUpstream() { + + ByteBufAllocator allocator = rule.alloc(); + for (int i = 0; i < 10000; i++) { + AssertSubscriber assertSubscriber = AssertSubscriber.create(); + + rule.setAcceptingSocket( + new AbstractRSocket() { + @Override + public Flux requestChannel(Publisher payloads) { + ((Flux) payloads) + .doOnNext(ReferenceCountUtil::safeRelease) + .subscribe(assertSubscriber); + return Flux.never(); + } + }, + Integer.MAX_VALUE); + + rule.sendRequest(1, REQUEST_CHANNEL); + ByteBuf metadata1 = allocator.buffer(); + metadata1.writeCharSequence("abc", CharsetUtil.UTF_8); + ByteBuf data1 = allocator.buffer(); + data1.writeCharSequence("def", CharsetUtil.UTF_8); + ByteBuf nextFrame1 = + PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata1, data1); + + ByteBuf metadata2 = allocator.buffer(); + metadata2.writeCharSequence("abc", CharsetUtil.UTF_8); + ByteBuf data2 = allocator.buffer(); + data2.writeCharSequence("def", CharsetUtil.UTF_8); + ByteBuf nextFrame2 = + PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata2, data2); + + ByteBuf metadata3 = allocator.buffer(); + metadata3.writeCharSequence("abc", CharsetUtil.UTF_8); + ByteBuf data3 = allocator.buffer(); + data3.writeCharSequence("def", CharsetUtil.UTF_8); + ByteBuf nextFrame3 = + PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata3, data3); + + RaceTestUtils.race( + () -> { + rule.connection.addToReceivedBuffer(nextFrame1, nextFrame2, nextFrame3); + }, + assertSubscriber::cancel); + + Assertions.assertThat(rule.connection.getSent()).allMatch(ReferenceCounted::release); + + rule.assertHasNoLeaks(); + } + } + + @Test + @Disabled("Due to https://github.com/reactor/reactor-core/pull/2114") + public void checkNoLeaksOnRacingBetweenDownstreamCancelAndOnNextFromRequestChannelTest() { + Hooks.onErrorDropped((e) -> {}); + ByteBufAllocator allocator = rule.alloc(); + for (int i = 0; i < 10000; i++) { + AssertSubscriber assertSubscriber = AssertSubscriber.create(); + + FluxSink[] sinks = new FluxSink[1]; + + rule.setAcceptingSocket( + new AbstractRSocket() { + @Override + public Flux requestChannel(Publisher payloads) { + ((Flux) payloads) + .doOnNext(ReferenceCountUtil::safeRelease) + .subscribe(assertSubscriber); + return Flux.create(sink -> sinks[0] = sink, FluxSink.OverflowStrategy.IGNORE); + } + }, + 1); + + rule.sendRequest(1, REQUEST_CHANNEL); + + ByteBuf cancelFrame = CancelFrameFlyweight.encode(allocator, 1); + FluxSink sink = sinks[0]; + RaceTestUtils.race( + () -> rule.connection.addToReceivedBuffer(cancelFrame), + () -> { + sink.next(ByteBufPayload.create("d1", "m1")); + sink.next(ByteBufPayload.create("d2", "m2")); + sink.next(ByteBufPayload.create("d3", "m3")); + }); + + Assertions.assertThat(rule.connection.getSent()).allMatch(ReferenceCounted::release); + + rule.assertHasNoLeaks(); + } + } + + @Test + @Disabled("Due to https://github.com/reactor/reactor-core/pull/2114") + public void checkNoLeaksOnRacingBetweenDownstreamCancelAndOnNextFromRequestChannelTest1() { + Scheduler parallel = Schedulers.parallel(); + Hooks.onErrorDropped((e) -> {}); + ByteBufAllocator allocator = rule.alloc(); + for (int i = 0; i < 10000; i++) { + AssertSubscriber assertSubscriber = AssertSubscriber.create(); + + FluxSink[] sinks = new FluxSink[1]; + + rule.setAcceptingSocket( + new AbstractRSocket() { + @Override + public Flux requestChannel(Publisher payloads) { + ((Flux) payloads) + .doOnNext(ReferenceCountUtil::safeRelease) + .subscribe(assertSubscriber); + return Flux.create(sink -> sinks[0] = sink, FluxSink.OverflowStrategy.IGNORE); + } + }, + 1); + + rule.sendRequest(1, REQUEST_CHANNEL); + + ByteBuf cancelFrame = CancelFrameFlyweight.encode(allocator, 1); + ByteBuf requestNFrame = RequestNFrameFlyweight.encode(allocator, 1, Integer.MAX_VALUE); + FluxSink sink = sinks[0]; + RaceTestUtils.race( + () -> + RaceTestUtils.race( + () -> rule.connection.addToReceivedBuffer(requestNFrame), + () -> rule.connection.addToReceivedBuffer(cancelFrame), + parallel), + () -> { + sink.next(ByteBufPayload.create("d1", "m1")); + sink.next(ByteBufPayload.create("d2", "m2")); + sink.next(ByteBufPayload.create("d3", "m3")); + }, + parallel); + + Assertions.assertThat(rule.connection.getSent()).allMatch(ReferenceCounted::release); + + rule.assertHasNoLeaks(); + } + } + + @Test + @Disabled("Due to https://github.com/reactor/reactor-core/pull/2114") + public void + checkNoLeaksOnRacingBetweenDownstreamCancelAndOnNextFromUpstreamOnErrorFromRequestChannelTest1() + throws InterruptedException { + Scheduler parallel = Schedulers.parallel(); + Hooks.onErrorDropped((e) -> {}); + ByteBufAllocator allocator = rule.alloc(); + for (int i = 0; i < 10000; i++) { + FluxSink[] sinks = new FluxSink[1]; + + rule.setAcceptingSocket( + new AbstractRSocket() { + @Override + public Flux requestChannel(Publisher payloads) { + + return Flux.create( + sink -> { + sinks[0] = sink; + }, + FluxSink.OverflowStrategy.IGNORE) + .mergeWith(payloads); + } + }, + 1); + + rule.sendRequest(1, REQUEST_CHANNEL); + + ByteBuf metadata1 = allocator.buffer(); + metadata1.writeCharSequence("abc", CharsetUtil.UTF_8); + ByteBuf data1 = allocator.buffer(); + data1.writeCharSequence("def", CharsetUtil.UTF_8); + ByteBuf nextFrame1 = + PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata1, data1); + + ByteBuf metadata2 = allocator.buffer(); + metadata2.writeCharSequence("abc", CharsetUtil.UTF_8); + ByteBuf data2 = allocator.buffer(); + data2.writeCharSequence("def", CharsetUtil.UTF_8); + ByteBuf nextFrame2 = + PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata2, data2); + + ByteBuf metadata3 = allocator.buffer(); + metadata3.writeCharSequence("abc", CharsetUtil.UTF_8); + ByteBuf data3 = allocator.buffer(); + data3.writeCharSequence("def", CharsetUtil.UTF_8); + ByteBuf nextFrame3 = + PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata3, data3); + + ByteBuf requestNFrame = RequestNFrameFlyweight.encode(allocator, 1, Integer.MAX_VALUE); + + FluxSink sink = sinks[0]; + RaceTestUtils.race( + () -> + RaceTestUtils.race( + () -> rule.connection.addToReceivedBuffer(requestNFrame), + () -> rule.connection.addToReceivedBuffer(nextFrame1, nextFrame2, nextFrame3), + parallel), + () -> { + sink.next(ByteBufPayload.create("d1", "m1")); + sink.next(ByteBufPayload.create("d2", "m2")); + sink.next(ByteBufPayload.create("d3", "m3")); + sink.error(new RuntimeException()); + }, + parallel); + + Assertions.assertThat(rule.connection.getSent()).allMatch(ReferenceCounted::release); + + rule.assertHasNoLeaks(); + } + } + + @Test + @Disabled("Due to https://github.com/reactor/reactor-core/pull/2114") + public void checkNoLeaksOnRacingBetweenDownstreamCancelAndOnNextFromRequestStreamTest1() { + Scheduler parallel = Schedulers.parallel(); + Hooks.onErrorDropped((e) -> {}); + ByteBufAllocator allocator = rule.alloc(); + for (int i = 0; i < 10000; i++) { + FluxSink[] sinks = new FluxSink[1]; + + rule.setAcceptingSocket( + new AbstractRSocket() { + @Override + public Flux requestStream(Payload payload) { + payload.release(); + return Flux.create(sink -> sinks[0] = sink, FluxSink.OverflowStrategy.IGNORE); + } + }, + Integer.MAX_VALUE); + + rule.sendRequest(1, REQUEST_STREAM); + + ByteBuf cancelFrame = CancelFrameFlyweight.encode(allocator, 1); + FluxSink sink = sinks[0]; + RaceTestUtils.race( + () -> rule.connection.addToReceivedBuffer(cancelFrame), + () -> { + sink.next(ByteBufPayload.create("d1", "m1")); + sink.next(ByteBufPayload.create("d2", "m2")); + sink.next(ByteBufPayload.create("d3", "m3")); + }, + parallel); + + Assertions.assertThat(rule.connection.getSent()).allMatch(ReferenceCounted::release); + + rule.assertHasNoLeaks(); + } + } + + @Test + public void checkNoLeaksOnRacingBetweenDownstreamCancelAndOnNextFromRequestResponseTest1() { + Scheduler parallel = Schedulers.parallel(); + Hooks.onErrorDropped((e) -> {}); + ByteBufAllocator allocator = rule.alloc(); + for (int i = 0; i < 10000; i++) { + Operators.MonoSubscriber[] sources = new Operators.MonoSubscriber[1]; + + rule.setAcceptingSocket( + new AbstractRSocket() { + @Override + public Mono requestResponse(Payload payload) { + payload.release(); + return new Mono() { + @Override + public void subscribe(CoreSubscriber actual) { + sources[0] = new Operators.MonoSubscriber<>(actual); + actual.onSubscribe(sources[0]); + } + }; + } + }, + Integer.MAX_VALUE); + + rule.sendRequest(1, REQUEST_RESPONSE); + + ByteBuf cancelFrame = CancelFrameFlyweight.encode(allocator, 1); + RaceTestUtils.race( + () -> rule.connection.addToReceivedBuffer(cancelFrame), + () -> { + sources[0].complete(ByteBufPayload.create("d1", "m1")); + }, + parallel); + + Assertions.assertThat(rule.connection.getSent()).allMatch(ReferenceCounted::release); + + rule.assertHasNoLeaks(); + } + } + + @Test + public void simpleDiscardRequestStreamTest() { + ByteBufAllocator allocator = rule.alloc(); + FluxSink[] sinks = new FluxSink[1]; + + rule.setAcceptingSocket( + new AbstractRSocket() { + @Override + public Flux requestStream(Payload payload) { + payload.release(); + return Flux.create(sink -> sinks[0] = sink, FluxSink.OverflowStrategy.IGNORE); + } + }, + 1); + + rule.sendRequest(1, REQUEST_STREAM); + + ByteBuf cancelFrame = CancelFrameFlyweight.encode(allocator, 1); + FluxSink sink = sinks[0]; + + sink.next(ByteBufPayload.create("d1", "m1")); + sink.next(ByteBufPayload.create("d2", "m2")); + sink.next(ByteBufPayload.create("d3", "m3")); + rule.connection.addToReceivedBuffer(cancelFrame); + + Assertions.assertThat(rule.connection.getSent()).allMatch(ReferenceCounted::release); + + rule.assertHasNoLeaks(); + } + + @Test + public void simpleDiscardRequestChannelTest() { + ByteBufAllocator allocator = rule.alloc(); + + rule.setAcceptingSocket( + new AbstractRSocket() { + @Override + public Flux requestChannel(Publisher payloads) { + return (Flux) payloads; + } + }, + 1); + + rule.sendRequest(1, REQUEST_STREAM); + + ByteBuf cancelFrame = CancelFrameFlyweight.encode(allocator, 1); + + ByteBuf metadata1 = allocator.buffer(); + metadata1.writeCharSequence("abc", CharsetUtil.UTF_8); + ByteBuf data1 = allocator.buffer(); + data1.writeCharSequence("def", CharsetUtil.UTF_8); + ByteBuf nextFrame1 = + PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata1, data1); + + ByteBuf metadata2 = allocator.buffer(); + metadata2.writeCharSequence("abc", CharsetUtil.UTF_8); + ByteBuf data2 = allocator.buffer(); + data2.writeCharSequence("def", CharsetUtil.UTF_8); + ByteBuf nextFrame2 = + PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata2, data2); + + ByteBuf metadata3 = allocator.buffer(); + metadata3.writeCharSequence("abc", CharsetUtil.UTF_8); + ByteBuf data3 = allocator.buffer(); + data3.writeCharSequence("def", CharsetUtil.UTF_8); + ByteBuf nextFrame3 = + PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata3, data3); + rule.connection.addToReceivedBuffer(nextFrame1, nextFrame2, nextFrame3); + + rule.connection.addToReceivedBuffer(cancelFrame); + + Assertions.assertThat(rule.connection.getSent()).allMatch(ReferenceCounted::release); + + rule.assertHasNoLeaks(); } public static class ServerSocketRule extends AbstractSocketRule { private RSocket acceptingSocket; + private volatile int prefetch; @Override protected void init() { @@ -188,25 +625,26 @@ public void setAcceptingSocket(RSocket acceptingSocket) { connection = new TestDuplexConnection(); connectSub = TestSubscriber.create(); errors = new ConcurrentLinkedQueue<>(); + this.prefetch = Integer.MAX_VALUE; super.init(); } public void setAcceptingSocket(RSocket acceptingSocket, int prefetch) { this.acceptingSocket = acceptingSocket; connection = new TestDuplexConnection(); - connection.setInitialSendRequestN(prefetch); connectSub = TestSubscriber.create(); errors = new ConcurrentLinkedQueue<>(); + this.prefetch = prefetch; super.init(); } @Override - protected RSocketResponder newRSocket() { + protected RSocketResponder newRSocket(LeaksTrackingByteBufAllocator allocator) { return new RSocketResponder( - ByteBufAllocator.DEFAULT, + allocator, connection, acceptingSocket, - DefaultPayload::create, + PayloadDecoder.ZERO_COPY, throwable -> errors.add(throwable), ResponderLeaseHandler.None, 0); @@ -219,25 +657,34 @@ private void sendRequest(int streamId, FrameType frameType) { case REQUEST_CHANNEL: request = RequestChannelFrameFlyweight.encode( - ByteBufAllocator.DEFAULT, streamId, false, false, 1, EmptyPayload.INSTANCE); + allocator, + streamId, + false, + false, + prefetch, + Unpooled.EMPTY_BUFFER, + Unpooled.EMPTY_BUFFER); break; case REQUEST_STREAM: request = RequestStreamFrameFlyweight.encode( - ByteBufAllocator.DEFAULT, streamId, false, 1, EmptyPayload.INSTANCE); + allocator, + streamId, + false, + prefetch, + Unpooled.EMPTY_BUFFER, + Unpooled.EMPTY_BUFFER); break; case REQUEST_RESPONSE: request = RequestResponseFrameFlyweight.encode( - ByteBufAllocator.DEFAULT, streamId, false, EmptyPayload.INSTANCE); + allocator, streamId, false, Unpooled.EMPTY_BUFFER, Unpooled.EMPTY_BUFFER); break; default: throw new IllegalArgumentException("unsupported type: " + frameType); } connection.addToReceivedBuffer(request); - connection.addToReceivedBuffer( - RequestNFrameFlyweight.encode(ByteBufAllocator.DEFAULT, streamId, 2)); } } } diff --git a/rsocket-core/src/test/java/io/rsocket/frame/ByteBufRepresentation.java b/rsocket-core/src/test/java/io/rsocket/frame/ByteBufRepresentation.java index b22a95c0b..5e94935c5 100644 --- a/rsocket-core/src/test/java/io/rsocket/frame/ByteBufRepresentation.java +++ b/rsocket-core/src/test/java/io/rsocket/frame/ByteBufRepresentation.java @@ -17,6 +17,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; +import io.netty.util.IllegalReferenceCountException; import org.assertj.core.presentation.StandardRepresentation; public final class ByteBufRepresentation extends StandardRepresentation { @@ -24,7 +25,11 @@ public final class ByteBufRepresentation extends StandardRepresentation { @Override protected String fallbackToStringOf(Object object) { if (object instanceof ByteBuf) { - return ByteBufUtil.prettyHexDump((ByteBuf) object); + try { + return ByteBufUtil.prettyHexDump((ByteBuf) object); + } catch (IllegalReferenceCountException e) { + // noops + } } return super.fallbackToStringOf(object); From 49e93c95efc626b4abdd62e3bf96f3ce9bb40546 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Tue, 21 Apr 2020 23:13:56 +0300 Subject: [PATCH 21/62] improves test logging Signed-off-by: Oleh Dokuka --- build.gradle | 52 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index 7e5eb9823..dd5135f01 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ plugins { subprojects { apply plugin: 'io.spring.dependency-management' apply plugin: 'com.github.sherter.google-java-format' - + ext['reactor-bom.version'] = 'Dysprosium-SR6' ext['logback.version'] = '1.2.3' ext['findbugs.version'] = '3.0.2' @@ -109,25 +109,61 @@ subprojects { } javadoc { + def jdk = JavaVersion.current().majorVersion + def jdkJavadoc = "https://docs.oracle.com/javase/$jdk/docs/api/" + if (JavaVersion.current().isJava11Compatible()) { + jdkJavadoc = "https://docs.oracle.com/en/java/javase/$jdk/docs/api/" + } options.with { - links 'https://docs.oracle.com/javase/8/docs/api/' + links jdkJavadoc links 'https://projectreactor.io/docs/core/release/api/' links 'https://netty.io/4.1/api/' } } + tasks.named("javadoc").configure { + onlyIf { System.getenv('SKIP_RELEASE') != "true" } + } + test { useJUnitPlatform() + systemProperty "io.netty.leakDetection.level", "ADVANCED" + } + + //all test tasks will show FAILED for each test method, + // common exclusions, no scanning + project.tasks.withType(Test).all { testLogging { - events "started", "passed", "skipped", "failed" + events "FAILED" + showExceptions true + exceptionFormat "FULL" + stackTraceFilters "ENTRY_POINT" + maxGranularity 3 } - systemProperty "io.netty.leakDetection.level", "ADVANCED" - } + if (JavaVersion.current().isJava9Compatible()) { + println "Java 9+: lowering MaxGCPauseMillis to 20ms in ${project.name} ${name}" + jvmArgs = ["-XX:MaxGCPauseMillis=20"] + } - tasks.named("javadoc").configure { - onlyIf { System.getenv('SKIP_RELEASE') != "true" } + systemProperty("java.awt.headless", "true") + systemProperty("reactor.trace.cancel", "true") + systemProperty("reactor.trace.nocapacity", "true") + systemProperty("testGroups", project.properties.get("testGroups")) + scanForTestClasses = false + exclude '**/*Abstract*.*' + + //allow re-run of failed tests only without special test tasks failing + // because the filter is too restrictive + filter.setFailOnNoMatchingTests(false) + + //display intermediate results for special test tasks + afterSuite { desc, result -> + if (!desc.parent) { // will match the outermost suite + println('\n' + "${desc} Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)") + } + } } } From 91e894a2ca57ebe4f81f3bca4c526cf9078b0f86 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Tue, 21 Apr 2020 23:56:45 +0300 Subject: [PATCH 22/62] removes redundant frames being sent (#792) Signed-off-by: Oleh Dokuka --- .../io/rsocket/core/RSocketRequester.java | 4 +- .../io/rsocket/core/RSocketResponder.java | 40 ++++++++++++++----- .../io/rsocket/core/RSocketRequesterTest.java | 6 +-- .../io/rsocket/core/RSocketResponderTest.java | 36 +++++++++-------- 4 files changed, 53 insertions(+), 33 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index 70b2a1889..5bb2890c1 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -261,9 +261,7 @@ public void doOnTerminal( @Nonnull SignalType signalType, @Nullable Payload element, @Nullable Throwable e) { - if (signalType == SignalType.ON_ERROR) { - sendProcessor.onNext(ErrorFrameFlyweight.encode(allocator, streamId, e)); - } else if (signalType == SignalType.CANCEL) { + if (signalType == SignalType.CANCEL) { sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); } removeStreamReceiver(streamId); diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java index e01000e49..563cfe624 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java @@ -36,6 +36,7 @@ import io.rsocket.lease.ResponderLeaseHandler; import java.util.function.Consumer; import java.util.function.LongConsumer; +import javax.annotation.Nullable; import org.reactivestreams.Processor; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; @@ -302,7 +303,7 @@ private void handleFrame(ByteBuf frame) { case REQUEST_STREAM: int streamInitialRequestN = RequestStreamFrameFlyweight.initialRequestN(frame); Payload streamPayload = payloadDecoder.apply(frame); - handleStream(streamId, requestStream(streamPayload), streamInitialRequestN); + handleStream(streamId, requestStream(streamPayload), streamInitialRequestN, null); break; case REQUEST_CHANNEL: int channelInitialRequestN = RequestChannelFrameFlyweight.initialRequestN(frame); @@ -433,7 +434,11 @@ protected void hookFinally(SignalType type) { response.doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER).subscribe(subscriber); } - private void handleStream(int streamId, Flux response, int initialRequestN) { + private void handleStream( + int streamId, + Flux response, + int initialRequestN, + @Nullable UnicastProcessor requestChannel) { final BaseSubscriber subscriber = new BaseSubscriber() { @@ -446,6 +451,17 @@ protected void hookOnSubscribe(Subscription s) { protected void hookOnNext(Payload payload) { if (!PayloadValidationUtils.isValid(mtu, payload)) { payload.release(); + // specifically for requestChannel case so when Payload is invalid we will not be + // sending CancelFrame and ErrorFrame + // Note: CancelFrame is redundant and due to spec + // (https://github.com/rsocket/rsocket/blob/master/Protocol.md#request-channel) + // Upon receiving an ERROR[APPLICATION_ERROR|REJECTED|CANCELED|INVALID], the stream is + // terminated on both Requester and Responder. + // Upon sending an ERROR[APPLICATION_ERROR|REJECTED|CANCELED|INVALID], the stream is + // terminated on both the Requester and Responder. + if (requestChannel != null) { + channelProcessors.remove(streamId, requestChannel); + } cancel(); final IllegalArgumentException t = new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE); @@ -495,9 +511,6 @@ private void handleChannel(int streamId, Payload payload, int initialRequestN) { Flux payloads = frames - .doOnCancel( - () -> sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId))) - .doOnError(t -> handleError(streamId, t)) .doOnRequest( new LongConsumer() { boolean first = true; @@ -511,10 +524,19 @@ public void accept(long l) { } else { n = l; } - sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); + if (n > 0) { + sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); + } + } + }) + .doFinally( + signalType -> { + if (channelProcessors.remove(streamId, frames)) { + if (signalType == SignalType.CANCEL) { + sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); + } } }) - .doFinally(signalType -> channelProcessors.remove(streamId)) .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER); // not chained, as the payload should be enqueued in the Unicast processor before this method @@ -523,9 +545,9 @@ public void accept(long l) { frames.onNext(payload); if (responderRSocket != null) { - handleStream(streamId, requestChannel(payload, payloads), initialRequestN); + handleStream(streamId, requestChannel(payload, payloads), initialRequestN, frames); } else { - handleStream(streamId, requestChannel(payloads), initialRequestN); + handleStream(streamId, requestChannel(payloads), initialRequestN, frames); } } diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java index 586c9cfd3..36abb14b4 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java @@ -174,8 +174,8 @@ public void testHandleApplicationException() { verify(responseSub).onError(any(ApplicationErrorException.class)); Assertions.assertThat(rule.connection.getSent()) - // requestResponseFrame FIXME - // .hasSize(1) + // requestResponseFrame + .hasSize(1) .allMatch(ReferenceCounted::release); rule.assertHasNoLeaks(); @@ -356,8 +356,6 @@ public void shouldThrownExceptionIfGivenPayloadIsExitsSizeAllowanceWithNoFragmen .isInstanceOf(IllegalArgumentException.class) .hasMessage(INVALID_PAYLOAD_ERROR_MESSAGE)) .verify(); - // FIXME: should be removed - Assertions.assertThat(rule.connection.getSent()).allMatch(bb -> bb.release()); rule.assertHasNoLeaks(); }); } diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java index d31fc3bf7..05f9fd46e 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java @@ -68,7 +68,9 @@ import org.junit.runners.model.Statement; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; +import reactor.core.publisher.BaseSubscriber; import reactor.core.publisher.Flux; import reactor.core.publisher.FluxSink; import reactor.core.publisher.Hooks; @@ -193,27 +195,27 @@ public Flux requestStream(Payload p) { p.release(); return Flux.just(payload).doOnCancel(() -> cancelled.set(true)); } - // FIXME - // @Override - // public Flux requestChannel(Publisher payloads) { - // Flux.from(payloads) - // .doOnNext(Payload::release) - // .subscribe( - // new BaseSubscriber() { - // @Override - // protected void hookOnSubscribe(Subscription subscription) { - // subscription.request(1); - // } - // }); - // return Flux.just(payload).doOnCancel(() -> cancelled.set(true)); - // } + + @Override + public Flux requestChannel(Publisher payloads) { + Flux.from(payloads) + .doOnNext(Payload::release) + .subscribe( + new BaseSubscriber() { + @Override + protected void hookOnSubscribe(Subscription subscription) { + subscription.request(1); + } + }); + return Flux.just(payload).doOnCancel(() -> cancelled.set(true)); + } }; rule.setAcceptingSocket(acceptingSocket); final Runnable[] runnables = { () -> rule.sendRequest(streamId, FrameType.REQUEST_RESPONSE), - () -> rule.sendRequest(streamId, FrameType.REQUEST_STREAM) /* FIXME, - () -> rule.sendRequest(streamId, FrameType.REQUEST_CHANNEL)*/ + () -> rule.sendRequest(streamId, FrameType.REQUEST_STREAM), + () -> rule.sendRequest(streamId, FrameType.REQUEST_CHANNEL) }; for (Runnable runnable : runnables) { @@ -224,9 +226,9 @@ public Flux requestStream(Payload p) { .isInstanceOf(IllegalArgumentException.class) .hasToString("java.lang.IllegalArgumentException: " + INVALID_PAYLOAD_ERROR_MESSAGE); Assertions.assertThat(rule.connection.getSent()) - .filteredOn(bb -> FrameHeaderFlyweight.frameType(bb) == FrameType.ERROR) .hasSize(1) .first() + .matches(bb -> FrameHeaderFlyweight.frameType(bb) == FrameType.ERROR) .matches(bb -> ErrorFrameFlyweight.dataUtf8(bb).contains(INVALID_PAYLOAD_ERROR_MESSAGE)) .matches(ReferenceCounted::release); From 5ee6c38d472939a3d2189605606988146954168f Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Wed, 22 Apr 2020 20:52:35 +0300 Subject: [PATCH 23/62] fixes incorrect request propagation Initially that issue was hidden because both sides uses limitRate which does prefetch 256 elements in advance so it is almost impossible to track underflow in request Signed-off-by: Oleh Dokuka --- .../io/rsocket/core/RSocketRequester.java | 3 +++ .../java/io/rsocket/core/RSocketTest.java | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index 5bb2890c1..d95015fae 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -382,7 +382,10 @@ protected void hookOnSubscribe(Subscription subscription) { protected void hookOnNext(Payload payload) { if (first) { // need to skip first since we have already sent it + // no need to release it since it was released earlier on the request establishment + // phase first = false; + request(1); return; } if (!PayloadValidationUtils.isValid(mtu, payload)) { diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java index 376614070..f0ae4ffd5 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java @@ -34,6 +34,7 @@ import io.rsocket.test.util.TestSubscriber; import io.rsocket.util.DefaultPayload; import io.rsocket.util.EmptyPayload; +import java.time.Duration; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicReference; import org.assertj.core.api.Assertions; @@ -120,6 +121,32 @@ public Mono requestResponse(Payload payload) { rule.assertServerError("CustomRSocketException (0x501): Deliberate Custom exception."); } + @Test(timeout = 2000) + public void testRequestPropagatesCorrectlyForRequestChannel() { + rule.setRequestAcceptor( + new AbstractRSocket() { + @Override + public Flux requestChannel(Publisher payloads) { + return Flux.from(payloads) + // specifically limits request to 3 in order to prevent 256 request from limitRate + // hidden on the responder side + .limitRequest(3); + } + }); + + Flux.range(0, 3) + .map(i -> DefaultPayload.create("" + i)) + .as(rule.crs::requestChannel) + .as(publisher -> StepVerifier.create(publisher, 3)) + .expectSubscription() + .expectNextCount(3) + .expectComplete() + .verify(Duration.ofMillis(5000)); + + rule.assertNoClientErrors(); + rule.assertNoServerErrors(); + } + @Test(timeout = 2000) public void testStream() throws Exception { Flux responses = rule.crs.requestStream(DefaultPayload.create("Payload In")); From 480b5018c0660013a251faf13626c65ae408b640 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Thu, 23 Apr 2020 13:03:50 +0300 Subject: [PATCH 24/62] fixes requestChannel and ensure support of half-closed state (#794) The following is the cases which MUST be supported OUTER PUBLISHER COMPLETED - INNER PUBLISHER CAN SEND OUTER PUBLISHER CANCELLED - INNER PUBLISHER CAN SEND INNER PUBLISHER COMPLETED - OUTER PUBLISHER CAN SEND INNER PUBLISHER CANCELLED - the WHOLE CHAIN IS TERMINATED --- .../io/rsocket/core/RSocketRequester.java | 84 ++++--- .../io/rsocket/core/RSocketResponder.java | 19 ++ .../java/io/rsocket/core/RSocketTest.java | 237 +++++++++++++++++- 3 files changed, 302 insertions(+), 38 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index d95015fae..4d7d47f6a 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -560,46 +560,58 @@ private void handleStreamZero(FrameType type, ByteBuf frame) { private void handleFrame(int streamId, FrameType type, ByteBuf frame) { Subscriber receiver = receivers.get(streamId); - if (receiver == null) { - handleMissingResponseProcessor(streamId, type, frame); - } else { - switch (type) { - case ERROR: - receiver.onError(Exceptions.from(streamId, frame)); - receivers.remove(streamId); - break; - case NEXT_COMPLETE: - receiver.onNext(payloadDecoder.apply(frame)); - receiver.onComplete(); - break; - case CANCEL: - { - Subscription sender = senders.remove(streamId); - if (sender != null) { - sender.cancel(); - } - break; + switch (type) { + case NEXT: + if (receiver == null) { + handleMissingResponseProcessor(streamId, type, frame); + return; + } + receiver.onNext(payloadDecoder.apply(frame)); + break; + case NEXT_COMPLETE: + if (receiver == null) { + handleMissingResponseProcessor(streamId, type, frame); + return; + } + receiver.onNext(payloadDecoder.apply(frame)); + receiver.onComplete(); + break; + case COMPLETE: + if (receiver == null) { + handleMissingResponseProcessor(streamId, type, frame); + return; + } + receiver.onComplete(); + receivers.remove(streamId); + break; + case ERROR: + if (receiver == null) { + handleMissingResponseProcessor(streamId, type, frame); + return; + } + receiver.onError(Exceptions.from(streamId, frame)); + receivers.remove(streamId); + break; + case CANCEL: + { + Subscription sender = senders.remove(streamId); + if (sender != null) { + sender.cancel(); } - case NEXT: - receiver.onNext(payloadDecoder.apply(frame)); break; - case REQUEST_N: - { - Subscription sender = senders.get(streamId); - if (sender != null) { - int n = RequestNFrameFlyweight.requestN(frame); - sender.request(n >= Integer.MAX_VALUE ? Long.MAX_VALUE : n); - } - break; + } + case REQUEST_N: + { + Subscription sender = senders.get(streamId); + if (sender != null) { + int n = RequestNFrameFlyweight.requestN(frame); + sender.request(n >= Integer.MAX_VALUE ? Long.MAX_VALUE : n); } - case COMPLETE: - receiver.onComplete(); - receivers.remove(streamId); break; - default: - throw new IllegalStateException( - "Client received supported frame on stream " + streamId + ": " + frame.toString()); - } + } + default: + throw new IllegalStateException( + "Client received supported frame on stream " + streamId + ": " + frame.toString()); } } diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java index 563cfe624..f9c3d1c65 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java @@ -492,6 +492,24 @@ protected void hookOnError(Throwable throwable) { handleError(streamId, throwable); } + @Override + protected void hookOnCancel() { + // specifically for requestChannel case so when requester sends Cancel frame so the + // whole chain MUST be terminated + // Note: CancelFrame is redundant from the responder side due to spec + // (https://github.com/rsocket/rsocket/blob/master/Protocol.md#request-channel) + // Upon receiving a CANCEL, the stream is terminated on the Responder. + // Upon sending a CANCEL, the stream is terminated on the Requester. + if (requestChannel != null) { + channelProcessors.remove(streamId, requestChannel); + try { + requestChannel.dispose(); + } catch (Exception e) { + // might be thrown back if stream is cancelled + } + } + } + @Override protected void hookFinally(SignalType type) { sendingSubscriptions.remove(streamId); @@ -568,6 +586,7 @@ protected void hookOnError(Throwable throwable) { private void handleCancelFrame(int streamId) { Subscription subscription = sendingSubscriptions.remove(streamId); + channelProcessors.remove(streamId); if (subscription != null) { subscription.cancel(); diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java index f0ae4ffd5..f29f4409c 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java @@ -28,6 +28,8 @@ import io.rsocket.RSocket; import io.rsocket.exceptions.ApplicationErrorException; import io.rsocket.exceptions.CustomRSocketException; +import io.rsocket.frame.decoder.PayloadDecoder; +import io.rsocket.internal.subscriber.AssertSubscriber; import io.rsocket.lease.RequesterLeaseHandler; import io.rsocket.lease.ResponderLeaseHandler; import io.rsocket.test.util.LocalDuplexConnection; @@ -36,6 +38,7 @@ import io.rsocket.util.EmptyPayload; import java.time.Duration; import java.util.ArrayList; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.assertj.core.api.Assertions; import org.hamcrest.MatcherAssert; @@ -52,6 +55,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import reactor.test.publisher.TestPublisher; public class RSocketTest { @@ -177,6 +181,235 @@ public Flux requestChannel(Publisher payloads) { Assertions.assertThat(error.get()).isNull(); } + @Test + public void requestChannelCase_StreamIsTerminatedAfterBothSidesSentCompletion1() { + TestPublisher outerPublisher = TestPublisher.create(); + AssertSubscriber outerAssertSubscriber = new AssertSubscriber<>(0); + + AssertSubscriber innerAssertSubscriber = new AssertSubscriber<>(0); + TestPublisher innerPublisher = TestPublisher.create(); + + initRequestChannelCase( + outerPublisher, outerAssertSubscriber, innerPublisher, innerAssertSubscriber); + + nextFromOuterPublisher(outerPublisher, innerAssertSubscriber); + + completeFromOuterPublisher(outerPublisher, innerAssertSubscriber); + + nextFromInnerPublisher(innerPublisher, outerAssertSubscriber); + + completeFromInnerPublisher(innerPublisher, outerAssertSubscriber); + } + + @Test + public void requestChannelCase_StreamIsTerminatedAfterBothSidesSentCompletion2() { + TestPublisher outerPublisher = TestPublisher.create(); + AssertSubscriber outerAssertSubscriber = new AssertSubscriber<>(0); + + AssertSubscriber innerAssertSubscriber = new AssertSubscriber<>(0); + TestPublisher innerPublisher = TestPublisher.create(); + + initRequestChannelCase( + outerPublisher, outerAssertSubscriber, innerPublisher, innerAssertSubscriber); + + nextFromInnerPublisher(innerPublisher, outerAssertSubscriber); + + completeFromInnerPublisher(innerPublisher, outerAssertSubscriber); + + nextFromOuterPublisher(outerPublisher, innerAssertSubscriber); + + completeFromOuterPublisher(outerPublisher, innerAssertSubscriber); + } + + @Test + public void + requestChannelCase_CancellationFromResponderShouldLeaveStreamInHalfClosedStateWithNextCompletionPossibleFromRequester() { + TestPublisher outerPublisher = TestPublisher.create(); + AssertSubscriber outerAssertSubscriber = new AssertSubscriber<>(0); + + AssertSubscriber innerAssertSubscriber = new AssertSubscriber<>(0); + TestPublisher innerPublisher = TestPublisher.create(); + + initRequestChannelCase( + outerPublisher, outerAssertSubscriber, innerPublisher, innerAssertSubscriber); + + nextFromOuterPublisher(outerPublisher, innerAssertSubscriber); + + cancelFromInnerSubscriber(outerPublisher, innerAssertSubscriber); + + nextFromInnerPublisher(innerPublisher, outerAssertSubscriber); + + completeFromInnerPublisher(innerPublisher, outerAssertSubscriber); + } + + @Test + public void + requestChannelCase_CompletionFromRequesterShouldLeaveStreamInHalfClosedStateWithNextCancellationPossibleFromResponder() { + TestPublisher outerPublisher = TestPublisher.create(); + AssertSubscriber outerAssertSubscriber = new AssertSubscriber<>(0); + + AssertSubscriber innerAssertSubscriber = new AssertSubscriber<>(0); + TestPublisher innerPublisher = TestPublisher.create(); + + initRequestChannelCase( + outerPublisher, outerAssertSubscriber, innerPublisher, innerAssertSubscriber); + + nextFromInnerPublisher(innerPublisher, outerAssertSubscriber); + + completeFromInnerPublisher(innerPublisher, outerAssertSubscriber); + + nextFromOuterPublisher(outerPublisher, innerAssertSubscriber); + + cancelFromInnerSubscriber(outerPublisher, innerAssertSubscriber); + } + + @Test + public void + requestChannelCase_ensureThatRequesterSubscriberCancellationTerminatesStreamsOnBothSides() { + TestPublisher outerPublisher = TestPublisher.create(); + AssertSubscriber outerAssertSubscriber = new AssertSubscriber<>(0); + + AssertSubscriber innerAssertSubscriber = new AssertSubscriber<>(0); + TestPublisher innerPublisher = TestPublisher.create(); + + initRequestChannelCase( + outerPublisher, outerAssertSubscriber, innerPublisher, innerAssertSubscriber); + + nextFromInnerPublisher(innerPublisher, outerAssertSubscriber); + + nextFromOuterPublisher(outerPublisher, innerAssertSubscriber); + + // ensures both sides are terminated + cancelFromOuterSubscriber( + outerPublisher, outerAssertSubscriber, innerPublisher, innerAssertSubscriber); + } + + void initRequestChannelCase( + TestPublisher outerPublisher, + AssertSubscriber outerAssertSubscriber, + TestPublisher innerPublisher, + AssertSubscriber innerAssertSubscriber) { + rule.setRequestAcceptor( + new AbstractRSocket() { + @Override + public Flux requestChannel(Publisher payloads) { + payloads.subscribe(innerAssertSubscriber); + return innerPublisher.flux(); + } + }); + + rule.crs.requestChannel(outerPublisher).subscribe(outerAssertSubscriber); + + outerPublisher.assertWasSubscribed(); + outerAssertSubscriber.assertSubscribed(); + + innerAssertSubscriber.assertNotSubscribed(); + innerPublisher.assertWasNotSubscribed(); + + // firstRequest + outerAssertSubscriber.request(1); + outerPublisher.assertMaxRequested(1); + outerPublisher.next(DefaultPayload.create("initialData", "initialMetadata")); + + innerAssertSubscriber.assertSubscribed(); + innerPublisher.assertWasSubscribed(); + } + + void nextFromOuterPublisher( + TestPublisher outerPublisher, AssertSubscriber innerAssertSubscriber) { + // ensures that outerUpstream and innerSubscriber is not terminated so the requestChannel + outerPublisher.assertSubscribers(1); + innerAssertSubscriber.assertNotTerminated(); + + innerAssertSubscriber.request(6); + outerPublisher.next( + DefaultPayload.create("d1", "m1"), + DefaultPayload.create("d2"), + DefaultPayload.create("d3", "m3"), + DefaultPayload.create("d4"), + DefaultPayload.create("d5", "m5")); + + List innerPayloads = innerAssertSubscriber.awaitAndAssertNextValueCount(6).values(); + Assertions.assertThat(innerPayloads.stream().map(Payload::getDataUtf8)) + .containsExactly("initialData", "d1", "d2", "d3", "d4", "d5"); + // fixme: incorrect behaviour of metadata encoding + // Assertions + // .assertThat(innerPayloads + // .stream() + // .map(Payload::hasMetadata) + // ) + // .containsExactly(true, true, false, true, false, true); + Assertions.assertThat(innerPayloads.stream().map(Payload::getMetadataUtf8)) + .containsExactly("initialMetadata", "m1", "", "m3", "", "m5"); + } + + void completeFromOuterPublisher( + TestPublisher outerPublisher, AssertSubscriber innerAssertSubscriber) { + // ensures that after sending complete upstream part is closed + outerPublisher.complete(); + innerAssertSubscriber.assertTerminated(); + outerPublisher.assertNoSubscribers(); + } + + void cancelFromInnerSubscriber( + TestPublisher outerPublisher, AssertSubscriber innerAssertSubscriber) { + // ensures that after sending complete upstream part is closed + innerAssertSubscriber.cancel(); + outerPublisher.assertWasCancelled(); + outerPublisher.assertNoSubscribers(); + } + + void nextFromInnerPublisher( + TestPublisher innerPublisher, AssertSubscriber outerAssertSubscriber) { + // ensures that downstream is not terminated so the requestChannel state is half-closed + innerPublisher.assertSubscribers(1); + outerAssertSubscriber.assertNotTerminated(); + + // ensures innerPublisher can send messages and outerSubscriber can receive them + outerAssertSubscriber.request(5); + innerPublisher.next( + DefaultPayload.create("rd1", "rm1"), + DefaultPayload.create("rd2"), + DefaultPayload.create("rd3", "rm3"), + DefaultPayload.create("rd4"), + DefaultPayload.create("rd5", "rm5")); + + List outerPayloads = outerAssertSubscriber.awaitAndAssertNextValueCount(5).values(); + Assertions.assertThat(outerPayloads.stream().map(Payload::getDataUtf8)) + .containsExactly("rd1", "rd2", "rd3", "rd4", "rd5"); + // fixme: incorrect behaviour of metadata encoding + // Assertions + // .assertThat(outerPayloads + // .stream() + // .map(Payload::hasMetadata) + // ) + // .containsExactly(true, false, true, false, true); + Assertions.assertThat(outerPayloads.stream().map(Payload::getMetadataUtf8)) + .containsExactly("rm1", "", "rm3", "", "rm5"); + } + + void completeFromInnerPublisher( + TestPublisher innerPublisher, AssertSubscriber outerAssertSubscriber) { + // ensures that after sending complete inner upstream is closed + innerPublisher.complete(); + outerAssertSubscriber.assertTerminated(); + innerPublisher.assertNoSubscribers(); + } + + void cancelFromOuterSubscriber( + TestPublisher outerPublisher, + AssertSubscriber outerAssertSubscriber, + TestPublisher innerPublisher, + AssertSubscriber innerAssertSubscriber) { + // ensures that after sending cancel the whole requestChannel is terminated + outerAssertSubscriber.cancel(); + innerPublisher.assertWasCancelled(); + innerPublisher.assertNoSubscribers(); + // ensures that cancellation is propagated to the actual upstream + outerPublisher.assertWasCancelled(); + outerPublisher.assertNoSubscribers(); + } + public static class SocketRule extends ExternalResource { DirectProcessor serverProcessor; @@ -246,7 +479,7 @@ public Flux requestChannel(Publisher payloads) { ByteBufAllocator.DEFAULT, serverConnection, requestAcceptor, - DefaultPayload::create, + PayloadDecoder.DEFAULT, throwable -> serverErrors.add(throwable), ResponderLeaseHandler.None, 0); @@ -255,7 +488,7 @@ public Flux requestChannel(Publisher payloads) { new RSocketRequester( ByteBufAllocator.DEFAULT, clientConnection, - DefaultPayload::create, + PayloadDecoder.DEFAULT, throwable -> clientErrors.add(throwable), StreamIdSupplier.clientSupplier(), 0, From cd67e541d5eb64b72a9df330ec15ff015d0c4e49 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 23 Apr 2020 19:40:44 +0100 Subject: [PATCH 25/62] Drop UriHandler and UriTransportRegistry (#795) Closes gh-782 Signed-off-by: Rossen Stoyanchev --- .../main/java/io/rsocket/uri/UriHandler.java | 58 ------------- .../io/rsocket/uri/UriTransportRegistry.java | 87 ------------------- .../java/io/rsocket/uri/TestUriHandler.java | 46 ---------- .../rsocket/uri/UriTransportRegistryTest.java | 42 --------- .../services/io.rsocket.uri.UriHandler | 17 ---- .../java/io/rsocket/test/UriHandlerTest.java | 74 ---------------- .../transport/local/LocalUriHandler.java | 55 ------------ .../services/io.rsocket.uri.UriHandler | 17 ---- .../transport/local/LocalUriHandlerTest.java | 38 -------- .../local/LocalUriTransportRegistryTest.java | 54 ------------ .../transport/netty/TcpUriHandler.java | 59 ------------- .../transport/netty/WebsocketUriHandler.java | 64 -------------- .../services/io.rsocket.uri.UriHandler | 18 ---- .../transport/netty/TcpUriHandlerTest.java | 38 -------- .../netty/TcpUriTransportRegistryTest.java | 60 ------------- .../netty/WebsocketUriHandlerTest.java | 38 -------- .../WebsocketUriTransportRegistryTest.java | 60 ------------- 17 files changed, 825 deletions(-) delete mode 100644 rsocket-core/src/main/java/io/rsocket/uri/UriHandler.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/uri/UriTransportRegistry.java delete mode 100644 rsocket-core/src/test/java/io/rsocket/uri/TestUriHandler.java delete mode 100644 rsocket-core/src/test/java/io/rsocket/uri/UriTransportRegistryTest.java delete mode 100644 rsocket-core/src/test/resources/META-INF/services/io.rsocket.uri.UriHandler delete mode 100644 rsocket-test/src/main/java/io/rsocket/test/UriHandlerTest.java delete mode 100644 rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalUriHandler.java delete mode 100644 rsocket-transport-local/src/main/resources/META-INF/services/io.rsocket.uri.UriHandler delete mode 100644 rsocket-transport-local/src/test/java/io/rsocket/transport/local/LocalUriHandlerTest.java delete mode 100644 rsocket-transport-local/src/test/java/io/rsocket/transport/local/LocalUriTransportRegistryTest.java delete mode 100644 rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/TcpUriHandler.java delete mode 100644 rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/WebsocketUriHandler.java delete mode 100644 rsocket-transport-netty/src/main/resources/META-INF/services/io.rsocket.uri.UriHandler delete mode 100644 rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpUriHandlerTest.java delete mode 100644 rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpUriTransportRegistryTest.java delete mode 100644 rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketUriHandlerTest.java delete mode 100644 rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketUriTransportRegistryTest.java diff --git a/rsocket-core/src/main/java/io/rsocket/uri/UriHandler.java b/rsocket-core/src/main/java/io/rsocket/uri/UriHandler.java deleted file mode 100644 index ec3d4ab3c..000000000 --- a/rsocket-core/src/main/java/io/rsocket/uri/UriHandler.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.uri; - -import io.rsocket.transport.ClientTransport; -import io.rsocket.transport.ServerTransport; -import java.net.URI; -import java.util.Optional; -import java.util.ServiceLoader; - -/** Maps a {@link URI} to a {@link ClientTransport} or {@link ServerTransport}. */ -public interface UriHandler { - - /** - * Load all registered instances of {@code UriHandler}. - * - * @return all registered instances of {@code UriHandler} - */ - static ServiceLoader loadServices() { - return ServiceLoader.load(UriHandler.class); - } - - /** - * Returns an implementation of {@link ClientTransport} unambiguously mapped to a {@link URI}, - * otherwise {@link Optional#EMPTY}. - * - * @param uri the uri to map - * @return an implementation of {@link ClientTransport} unambiguously mapped to a {@link URI}, * - * otherwise {@link Optional#EMPTY} - * @throws NullPointerException if {@code uri} is {@code null} - */ - Optional buildClient(URI uri); - - /** - * Returns an implementation of {@link ServerTransport} unambiguously mapped to a {@link URI}, - * otherwise {@link Optional#EMPTY}. - * - * @param uri the uri to map - * @return an implementation of {@link ServerTransport} unambiguously mapped to a {@link URI}, * - * otherwise {@link Optional#EMPTY} - * @throws NullPointerException if {@code uri} is {@code null} - */ - Optional buildServer(URI uri); -} diff --git a/rsocket-core/src/main/java/io/rsocket/uri/UriTransportRegistry.java b/rsocket-core/src/main/java/io/rsocket/uri/UriTransportRegistry.java deleted file mode 100644 index 204c5d1ea..000000000 --- a/rsocket-core/src/main/java/io/rsocket/uri/UriTransportRegistry.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.uri; - -import static io.rsocket.uri.UriHandler.loadServices; - -import io.rsocket.transport.ClientTransport; -import io.rsocket.transport.ServerTransport; -import java.net.URI; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.ServiceLoader; -import reactor.core.publisher.Mono; - -/** - * Registry for looking up transports by URI. - * - *

    Uses the Jar Services mechanism with services defined by {@link UriHandler}. - */ -public class UriTransportRegistry { - private static final ClientTransport FAILED_CLIENT_LOOKUP = - (mtu) -> Mono.error(new UnsupportedOperationException()); - private static final ServerTransport FAILED_SERVER_LOOKUP = - (acceptor, mtu) -> Mono.error(new UnsupportedOperationException()); - - private List handlers; - - public UriTransportRegistry(ServiceLoader services) { - handlers = new ArrayList<>(); - services.forEach(handlers::add); - } - - public static UriTransportRegistry fromServices() { - ServiceLoader services = loadServices(); - - return new UriTransportRegistry(services); - } - - public static ClientTransport clientForUri(String uri) { - return UriTransportRegistry.fromServices().findClient(uri); - } - - public static ServerTransport serverForUri(String uri) { - return UriTransportRegistry.fromServices().findServer(uri); - } - - private ClientTransport findClient(String uriString) { - URI uri = URI.create(uriString); - - for (UriHandler h : handlers) { - Optional r = h.buildClient(uri); - if (r.isPresent()) { - return r.get(); - } - } - - return FAILED_CLIENT_LOOKUP; - } - - private ServerTransport findServer(String uriString) { - URI uri = URI.create(uriString); - - for (UriHandler h : handlers) { - Optional r = h.buildServer(uri); - if (r.isPresent()) { - return r.get(); - } - } - - return FAILED_SERVER_LOOKUP; - } -} diff --git a/rsocket-core/src/test/java/io/rsocket/uri/TestUriHandler.java b/rsocket-core/src/test/java/io/rsocket/uri/TestUriHandler.java deleted file mode 100644 index 526757fbe..000000000 --- a/rsocket-core/src/test/java/io/rsocket/uri/TestUriHandler.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.uri; - -import io.rsocket.test.util.TestDuplexConnection; -import io.rsocket.transport.ClientTransport; -import io.rsocket.transport.ServerTransport; -import java.net.URI; -import java.util.Objects; -import java.util.Optional; -import reactor.core.publisher.Mono; - -public final class TestUriHandler implements UriHandler { - - private static final String SCHEME = "test"; - - @Override - public Optional buildClient(URI uri) { - Objects.requireNonNull(uri, "uri must not be null"); - - if (!SCHEME.equals(uri.getScheme())) { - return Optional.empty(); - } - - return Optional.of((mtu) -> Mono.just(new TestDuplexConnection())); - } - - @Override - public Optional buildServer(URI uri) { - return Optional.empty(); - } -} diff --git a/rsocket-core/src/test/java/io/rsocket/uri/UriTransportRegistryTest.java b/rsocket-core/src/test/java/io/rsocket/uri/UriTransportRegistryTest.java deleted file mode 100644 index 7aeef708f..000000000 --- a/rsocket-core/src/test/java/io/rsocket/uri/UriTransportRegistryTest.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.uri; - -import static org.junit.Assert.assertTrue; - -import io.rsocket.DuplexConnection; -import io.rsocket.test.util.TestDuplexConnection; -import io.rsocket.transport.ClientTransport; -import org.junit.Test; - -public class UriTransportRegistryTest { - @Test - public void testTestRegistered() { - ClientTransport test = UriTransportRegistry.clientForUri("test://test"); - - DuplexConnection duplexConnection = test.connect(0).block(); - - assertTrue(duplexConnection instanceof TestDuplexConnection); - } - - @Test(expected = UnsupportedOperationException.class) - public void testTestUnregistered() { - ClientTransport test = UriTransportRegistry.clientForUri("mailto://bonson@baulsupp.net"); - - test.connect(0).block(); - } -} diff --git a/rsocket-core/src/test/resources/META-INF/services/io.rsocket.uri.UriHandler b/rsocket-core/src/test/resources/META-INF/services/io.rsocket.uri.UriHandler deleted file mode 100644 index 068667aa7..000000000 --- a/rsocket-core/src/test/resources/META-INF/services/io.rsocket.uri.UriHandler +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2015-2018 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -io.rsocket.uri.TestUriHandler diff --git a/rsocket-test/src/main/java/io/rsocket/test/UriHandlerTest.java b/rsocket-test/src/main/java/io/rsocket/test/UriHandlerTest.java deleted file mode 100644 index ad45e106a..000000000 --- a/rsocket-test/src/main/java/io/rsocket/test/UriHandlerTest.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatNullPointerException; - -import io.rsocket.uri.UriHandler; -import java.net.URI; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -public interface UriHandlerTest { - - @DisplayName("returns empty Optional client with invalid URI") - @Test - default void buildClientInvalidUri() { - assertThat(getUriHandler().buildClient(URI.create(getInvalidUri()))).isEmpty(); - } - - @DisplayName("buildClient throws NullPointerException with null uri") - @Test - default void buildClientNullUri() { - assertThatNullPointerException() - .isThrownBy(() -> getUriHandler().buildClient(null)) - .withMessage("uri must not be null"); - } - - @DisplayName("returns client with value URI") - @Test - default void buildClientValidUri() { - assertThat(getUriHandler().buildClient(URI.create(getValidUri()))).isNotEmpty(); - } - - @DisplayName("returns empty Optional server with invalid URI") - @Test - default void buildServerInvalidUri() { - assertThat(getUriHandler().buildServer(URI.create(getInvalidUri()))).isEmpty(); - } - - @DisplayName("buildServer throws NullPointerException with null uri") - @Test - default void buildServerNullUri() { - assertThatNullPointerException() - .isThrownBy(() -> getUriHandler().buildServer(null)) - .withMessage("uri must not be null"); - } - - @DisplayName("returns server with value URI") - @Test - default void buildServerValidUri() { - assertThat(getUriHandler().buildServer(URI.create(getValidUri()))).isNotEmpty(); - } - - String getInvalidUri(); - - UriHandler getUriHandler(); - - String getValidUri(); -} diff --git a/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalUriHandler.java b/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalUriHandler.java deleted file mode 100644 index 89c816d7a..000000000 --- a/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalUriHandler.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.transport.local; - -import io.rsocket.transport.ClientTransport; -import io.rsocket.transport.ServerTransport; -import io.rsocket.uri.UriHandler; -import java.net.URI; -import java.util.Objects; -import java.util.Optional; - -/** - * An implementation of {@link UriHandler} that creates {@link LocalClientTransport}s and {@link - * LocalServerTransport}s. - */ -public final class LocalUriHandler implements UriHandler { - - private static final String SCHEME = "local"; - - @Override - public Optional buildClient(URI uri) { - Objects.requireNonNull(uri, "uri must not be null"); - - if (!SCHEME.equals(uri.getScheme())) { - return Optional.empty(); - } - - return Optional.of(LocalClientTransport.create(uri.getSchemeSpecificPart())); - } - - @Override - public Optional buildServer(URI uri) { - Objects.requireNonNull(uri, "uri must not be null"); - - if (!SCHEME.equals(uri.getScheme())) { - return Optional.empty(); - } - - return Optional.of(LocalServerTransport.create(uri.getSchemeSpecificPart())); - } -} diff --git a/rsocket-transport-local/src/main/resources/META-INF/services/io.rsocket.uri.UriHandler b/rsocket-transport-local/src/main/resources/META-INF/services/io.rsocket.uri.UriHandler deleted file mode 100644 index 6ff8ffb50..000000000 --- a/rsocket-transport-local/src/main/resources/META-INF/services/io.rsocket.uri.UriHandler +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2015-2018 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -io.rsocket.transport.local.LocalUriHandler diff --git a/rsocket-transport-local/src/test/java/io/rsocket/transport/local/LocalUriHandlerTest.java b/rsocket-transport-local/src/test/java/io/rsocket/transport/local/LocalUriHandlerTest.java deleted file mode 100644 index ed8e6cd1d..000000000 --- a/rsocket-transport-local/src/test/java/io/rsocket/transport/local/LocalUriHandlerTest.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.transport.local; - -import io.rsocket.test.UriHandlerTest; -import io.rsocket.uri.UriHandler; - -final class LocalUriHandlerTest implements UriHandlerTest { - - @Override - public String getInvalidUri() { - return "http://test"; - } - - @Override - public UriHandler getUriHandler() { - return new LocalUriHandler(); - } - - @Override - public String getValidUri() { - return "local:test"; - } -} diff --git a/rsocket-transport-local/src/test/java/io/rsocket/transport/local/LocalUriTransportRegistryTest.java b/rsocket-transport-local/src/test/java/io/rsocket/transport/local/LocalUriTransportRegistryTest.java deleted file mode 100644 index f6b5cda7e..000000000 --- a/rsocket-transport-local/src/test/java/io/rsocket/transport/local/LocalUriTransportRegistryTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.transport.local; - -import static org.assertj.core.api.Assertions.assertThat; - -import io.rsocket.uri.UriTransportRegistry; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -final class LocalUriTransportRegistryTest { - - @DisplayName("local URI returns LocalClientTransport") - @Test - void clientForUri() { - assertThat(UriTransportRegistry.clientForUri("local:test1")) - .isInstanceOf(LocalClientTransport.class); - } - - @DisplayName("non-local URI does not return LocalClientTransport") - @Test - void clientForUriInvalid() { - assertThat(UriTransportRegistry.clientForUri("http://localhost")) - .isNotInstanceOf(LocalClientTransport.class); - } - - @DisplayName("local URI returns LocalServerTransport") - @Test - void serverForUri() { - assertThat(UriTransportRegistry.serverForUri("local:test1")) - .isInstanceOf(LocalServerTransport.class); - } - - @DisplayName("non-local URI does not return LocalServerTransport") - @Test - void serverForUriInvalid() { - assertThat(UriTransportRegistry.serverForUri("http://localhost")) - .isNotInstanceOf(LocalServerTransport.class); - } -} diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/TcpUriHandler.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/TcpUriHandler.java deleted file mode 100644 index d4ebd57b7..000000000 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/TcpUriHandler.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.transport.netty; - -import io.rsocket.transport.ClientTransport; -import io.rsocket.transport.ServerTransport; -import io.rsocket.transport.netty.client.TcpClientTransport; -import io.rsocket.transport.netty.server.TcpServerTransport; -import io.rsocket.uri.UriHandler; -import java.net.URI; -import java.util.Objects; -import java.util.Optional; -import reactor.netty.tcp.TcpServer; - -/** - * An implementation of {@link UriHandler} that creates {@link TcpClientTransport}s and {@link - * TcpServerTransport}s. - */ -public final class TcpUriHandler implements UriHandler { - - private static final String SCHEME = "tcp"; - - @Override - public Optional buildClient(URI uri) { - Objects.requireNonNull(uri, "uri must not be null"); - - if (!SCHEME.equals(uri.getScheme())) { - return Optional.empty(); - } - - return Optional.of(TcpClientTransport.create(uri.getHost(), uri.getPort())); - } - - @Override - public Optional buildServer(URI uri) { - Objects.requireNonNull(uri, "uri must not be null"); - - if (!SCHEME.equals(uri.getScheme())) { - return Optional.empty(); - } - - return Optional.of( - TcpServerTransport.create(TcpServer.create().host(uri.getHost()).port(uri.getPort()))); - } -} diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/WebsocketUriHandler.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/WebsocketUriHandler.java deleted file mode 100644 index 6438c4e28..000000000 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/WebsocketUriHandler.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.transport.netty; - -import static io.rsocket.transport.netty.UriUtils.getPort; -import static io.rsocket.transport.netty.UriUtils.isSecure; - -import io.rsocket.transport.ClientTransport; -import io.rsocket.transport.ServerTransport; -import io.rsocket.transport.netty.client.WebsocketClientTransport; -import io.rsocket.transport.netty.server.WebsocketServerTransport; -import io.rsocket.uri.UriHandler; -import java.net.URI; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -/** - * An implementation of {@link UriHandler} that creates {@link WebsocketClientTransport}s and {@link - * WebsocketServerTransport}s. - */ -public final class WebsocketUriHandler implements UriHandler { - - private static final List SCHEME = Arrays.asList("ws", "wss", "http", "https"); - - @Override - public Optional buildClient(URI uri) { - Objects.requireNonNull(uri, "uri must not be null"); - - if (SCHEME.stream().noneMatch(scheme -> scheme.equals(uri.getScheme()))) { - return Optional.empty(); - } - - return Optional.of(WebsocketClientTransport.create(uri)); - } - - @Override - public Optional buildServer(URI uri) { - Objects.requireNonNull(uri, "uri must not be null"); - - if (SCHEME.stream().noneMatch(scheme -> scheme.equals(uri.getScheme()))) { - return Optional.empty(); - } - - int port = isSecure(uri) ? getPort(uri, 443) : getPort(uri, 80); - - return Optional.of(WebsocketServerTransport.create(uri.getHost(), port)); - } -} diff --git a/rsocket-transport-netty/src/main/resources/META-INF/services/io.rsocket.uri.UriHandler b/rsocket-transport-netty/src/main/resources/META-INF/services/io.rsocket.uri.UriHandler deleted file mode 100644 index ec7ddcb80..000000000 --- a/rsocket-transport-netty/src/main/resources/META-INF/services/io.rsocket.uri.UriHandler +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2015-2018 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -io.rsocket.transport.netty.TcpUriHandler -io.rsocket.transport.netty.WebsocketUriHandler diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpUriHandlerTest.java b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpUriHandlerTest.java deleted file mode 100644 index 25b443dd6..000000000 --- a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpUriHandlerTest.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.transport.netty; - -import io.rsocket.test.UriHandlerTest; -import io.rsocket.uri.UriHandler; - -final class TcpUriHandlerTest implements UriHandlerTest { - - @Override - public String getInvalidUri() { - return "http://test"; - } - - @Override - public UriHandler getUriHandler() { - return new TcpUriHandler(); - } - - @Override - public String getValidUri() { - return "tcp://test:9898"; - } -} diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpUriTransportRegistryTest.java b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpUriTransportRegistryTest.java deleted file mode 100644 index a71cc27f9..000000000 --- a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpUriTransportRegistryTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.transport.netty; - -import static org.assertj.core.api.Assertions.assertThat; - -import io.rsocket.transport.netty.client.TcpClientTransport; -import io.rsocket.transport.netty.client.WebsocketClientTransport; -import io.rsocket.transport.netty.server.TcpServerTransport; -import io.rsocket.transport.netty.server.WebsocketServerTransport; -import io.rsocket.uri.UriTransportRegistry; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -final class TcpUriTransportRegistryTest { - - @DisplayName("non-tcp URI does not return TcpClientTransport") - @Test - void clientForUriInvalid() { - assertThat(UriTransportRegistry.clientForUri("amqp://localhost")) - .isNotInstanceOf(TcpClientTransport.class) - .isNotInstanceOf(WebsocketClientTransport.class); - } - - @DisplayName("tcp URI returns TcpClientTransport") - @Test - void clientForUriTcp() { - assertThat(UriTransportRegistry.clientForUri("tcp://test:9898")) - .isInstanceOf(TcpClientTransport.class); - } - - @DisplayName("non-tcp URI does not return TcpServerTransport") - @Test - void serverForUriInvalid() { - assertThat(UriTransportRegistry.serverForUri("amqp://localhost")) - .isNotInstanceOf(TcpServerTransport.class) - .isNotInstanceOf(WebsocketServerTransport.class); - } - - @DisplayName("tcp URI returns TcpServerTransport") - @Test - void serverForUriTcp() { - assertThat(UriTransportRegistry.serverForUri("tcp://test:9898")) - .isInstanceOf(TcpServerTransport.class); - } -} diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketUriHandlerTest.java b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketUriHandlerTest.java deleted file mode 100644 index 72a700b0e..000000000 --- a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketUriHandlerTest.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.transport.netty; - -import io.rsocket.test.UriHandlerTest; -import io.rsocket.uri.UriHandler; - -final class WebsocketUriHandlerTest implements UriHandlerTest { - - @Override - public String getInvalidUri() { - return "amqp://test"; - } - - @Override - public UriHandler getUriHandler() { - return new WebsocketUriHandler(); - } - - @Override - public String getValidUri() { - return "ws://test:9898"; - } -} diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketUriTransportRegistryTest.java b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketUriTransportRegistryTest.java deleted file mode 100644 index 5688f14ed..000000000 --- a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketUriTransportRegistryTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.transport.netty; - -import static org.assertj.core.api.Assertions.assertThat; - -import io.rsocket.transport.netty.client.TcpClientTransport; -import io.rsocket.transport.netty.client.WebsocketClientTransport; -import io.rsocket.transport.netty.server.TcpServerTransport; -import io.rsocket.transport.netty.server.WebsocketServerTransport; -import io.rsocket.uri.UriTransportRegistry; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -final class WebsocketUriTransportRegistryTest { - - @DisplayName("non-ws URI does not return WebsocketClientTransport") - @Test - void clientForUriInvalid() { - assertThat(UriTransportRegistry.clientForUri("amqp://localhost")) - .isNotInstanceOf(TcpClientTransport.class) - .isNotInstanceOf(WebsocketClientTransport.class); - } - - @DisplayName("ws URI returns WebsocketClientTransport") - @Test - void clientForUriWebsocket() { - assertThat(UriTransportRegistry.clientForUri("ws://test:9898")) - .isInstanceOf(WebsocketClientTransport.class); - } - - @DisplayName("non-ws URI does not return WebsocketServerTransport") - @Test - void serverForUriInvalid() { - assertThat(UriTransportRegistry.serverForUri("amqp://localhost")) - .isNotInstanceOf(TcpServerTransport.class) - .isNotInstanceOf(WebsocketServerTransport.class); - } - - @DisplayName("ws URI returns WebsocketServerTransport") - @Test - void serverForUriWebsocket() { - assertThat(UriTransportRegistry.serverForUri("ws://test:9898")) - .isInstanceOf(WebsocketServerTransport.class); - } -} From 3a5a4c240d206db04b11658a3e81e9b9469f2ed8 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Fri, 24 Apr 2020 10:47:55 +0300 Subject: [PATCH 26/62] Fixes Payload#hasMetadata to strictly flow the flag in frame This PR ensures that Payload#hasMetadata will have identically value as it was encoded in frame decoded to Payload. Also, this PR ensures that during the encoding the current behavior of Payload#metadata \ Payload#sliceMetadata which always return a buffer (even though them hasMetadata is false) will not brake encoding process and the result flag in the encoded frame will be mirroring exactly what `Payload#hasMetadata` returned * fixes behaviour of flight-weights when it gets unreadable buffers ensures that ByteBufPayload is not accessible anymore when it has been released Signed-off-by: Oleh Dokuka * partial Signed-off-by: Oleh Dokuka * fixes incorrect request propagation Initially that issue was hidden because both sides uses limitRate which does prefetch 256 elements in advance so it is almost impossible to track underflow in request Signed-off-by: Oleh Dokuka * provides refactoring related to ensuring that hasMetadata is propagated correctly Right now there was observed a few issues related to that Payload with no metadata was incorrectly incoded so it results to hasMetadata true on the received side Also, optimized the API of Flyweights to ensure that we have common things in one place Signed-off-by: Oleh Dokuka * fixes failing tests related to changes of initialRequestN Signed-off-by: Oleh Dokuka * fixes compilation errors Signed-off-by: Oleh Dokuka * uncomment assertions related to proper metadata propagation Signed-off-by: Oleh Dokuka * moves payload releasing to codecs Signed-off-by: Oleh Dokuka --- .../core/DefaultConnectionSetupPayload.java | 4 +- .../io/rsocket/core/RSocketRequester.java | 52 +--- .../io/rsocket/core/RSocketResponder.java | 39 +-- .../fragmentation/FrameReassembler.java | 16 +- .../frame/DataAndMetadataFlyweight.java | 53 ++--- .../frame/ExtensionFrameFlyweight.java | 20 +- .../rsocket/frame/FragmentationFlyweight.java | 9 +- .../frame/KeepAliveFrameFlyweight.java | 2 +- .../io/rsocket/frame/LeaseFrameFlyweight.java | 13 +- .../frame/MetadataPushFrameFlyweight.java | 8 + .../rsocket/frame/PayloadFrameFlyweight.java | 74 +++--- .../frame/RequestChannelFrameFlyweight.java | 34 +-- .../RequestFireAndForgetFrameFlyweight.java | 13 +- .../io/rsocket/frame/RequestFlyweight.java | 25 +- .../rsocket/frame/RequestNFrameFlyweight.java | 15 +- .../frame/RequestResponseFrameFlyweight.java | 13 +- .../frame/RequestStreamFrameFlyweight.java | 51 ++-- .../io/rsocket/frame/SetupFrameFlyweight.java | 23 +- .../frame/decoder/DefaultPayloadDecoder.java | 25 +- .../frame/decoder/ZeroCopyPayloadDecoder.java | 11 +- .../java/io/rsocket/util/ByteBufPayload.java | 29 ++- .../io/rsocket/core/RSocketRequesterTest.java | 111 ++++++++- .../io/rsocket/core/RSocketResponderTest.java | 142 +++++++++-- .../java/io/rsocket/core/RSocketTest.java | 222 +++++++++--------- .../FragmentationIntegrationTest.java | 3 +- .../fragmentation/FrameFragmenterTest.java | 55 +++-- .../fragmentation/FrameReassemblerTest.java | 112 ++++----- .../ReassembleDuplexConnectionTest.java | 48 ++-- .../frame/DataAndMetadataFlyweightTest.java | 51 ---- .../frame/ExtensionFrameFlyweightTest.java | 2 +- .../rsocket/frame/PayloadFlyweightTest.java | 31 ++- .../rsocket/frame/RequestFlyweightTest.java | 47 ++-- .../io/rsocket/util/ByteBufPayloadTest.java | 64 +++++ .../io/rsocket/util/DefaultPayloadTest.java | 30 ++- .../main/java/io/rsocket/test/TestFrames.java | 4 +- .../java/io/rsocket/test/TransportTest.java | 10 +- 36 files changed, 872 insertions(+), 589 deletions(-) delete mode 100644 rsocket-core/src/test/java/io/rsocket/frame/DataAndMetadataFlyweightTest.java create mode 100644 rsocket-core/src/test/java/io/rsocket/util/ByteBufPayloadTest.java diff --git a/rsocket-core/src/main/java/io/rsocket/core/DefaultConnectionSetupPayload.java b/rsocket-core/src/main/java/io/rsocket/core/DefaultConnectionSetupPayload.java index 8710aa61a..feeb5c481 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/DefaultConnectionSetupPayload.java +++ b/rsocket-core/src/main/java/io/rsocket/core/DefaultConnectionSetupPayload.java @@ -17,6 +17,7 @@ package io.rsocket.core; import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import io.rsocket.ConnectionSetupPayload; import io.rsocket.frame.FrameHeaderFlyweight; import io.rsocket.frame.SetupFrameFlyweight; @@ -40,7 +41,8 @@ public boolean hasMetadata() { @Override public ByteBuf sliceMetadata() { - return SetupFrameFlyweight.metadata(setupFrame); + final ByteBuf metadata = SetupFrameFlyweight.metadata(setupFrame); + return metadata == null ? Unpooled.EMPTY_BUFFER : metadata; } @Override diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index 4d7d47f6a..42a6a524d 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -212,13 +212,8 @@ private Mono handleFireAndForget(Payload payload) { return UnicastMonoEmpty.newInstance( () -> { ByteBuf requestFrame = - RequestFireAndForgetFrameFlyweight.encode( - allocator, - streamId, - false, - payload.hasMetadata() ? payload.sliceMetadata().retain() : null, - payload.sliceData().retain()); - payload.release(); + RequestFireAndForgetFrameFlyweight.encodeReleasingPayload( + allocator, streamId, payload); sendProcessor.onNext(requestFrame); }); @@ -245,13 +240,8 @@ private Mono handleRequestResponse(final Payload payload) { @Override public void doOnSubscribe() { final ByteBuf requestFrame = - RequestResponseFrameFlyweight.encode( - allocator, - streamId, - false, - payload.sliceMetadata().retain(), - payload.sliceData().retain()); - payload.release(); + RequestResponseFrameFlyweight.encodeReleasingPayload( + allocator, streamId, payload); sendProcessor.onNext(requestFrame); } @@ -302,16 +292,10 @@ private Flux handleRequestStream(final Payload payload) { public void accept(long n) { if (firstRequest && !receiver.isDisposed()) { firstRequest = false; - sendProcessor.onNext( - RequestStreamFrameFlyweight.encode( - allocator, - streamId, - false, - n, - payload.sliceMetadata().retain(), - payload.sliceData().retain())); if (!payloadReleasedFlag.getAndSet(true)) { - payload.release(); + sendProcessor.onNext( + RequestStreamFrameFlyweight.encodeReleasingPayload( + allocator, streamId, n, payload)); } } else if (contains(streamId) && !receiver.isDisposed()) { sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); @@ -400,10 +384,9 @@ protected void hookOnNext(Payload payload) { return; } final ByteBuf frame = - PayloadFrameFlyweight.encode(allocator, streamId, false, false, true, payload); + PayloadFrameFlyweight.encodeNextReleasingPayload(allocator, streamId, payload); sendProcessor.onNext(frame); - payload.release(); } @Override @@ -444,18 +427,10 @@ public void accept(long n) { .subscribe(upstreamSubscriber); if (!payloadReleasedFlag.getAndSet(true)) { ByteBuf frame = - RequestChannelFrameFlyweight.encode( - allocator, - streamId, - false, - false, - n, - initialPayload.sliceMetadata().retain(), - initialPayload.sliceData().retain()); + RequestChannelFrameFlyweight.encodeReleasingPayload( + allocator, streamId, false, n, initialPayload); sendProcessor.onNext(frame); - - initialPayload.release(); } } else { sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); @@ -497,8 +472,7 @@ private Mono handleMetadataPush(Payload payload) { return UnicastMonoEmpty.newInstance( () -> { ByteBuf metadataPushFrame = - MetadataPushFrameFlyweight.encode(allocator, payload.sliceMetadata().retain()); - payload.release(); + MetadataPushFrameFlyweight.encodeReleasingPayload(allocator, payload); sendProcessor.onNextPrioritized(metadataPushFrame); }); @@ -604,8 +578,8 @@ private void handleFrame(int streamId, FrameType type, ByteBuf frame) { { Subscription sender = senders.get(streamId); if (sender != null) { - int n = RequestNFrameFlyweight.requestN(frame); - sender.request(n >= Integer.MAX_VALUE ? Long.MAX_VALUE : n); + long n = RequestNFrameFlyweight.requestN(frame); + sender.request(n); } break; } diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java index f9c3d1c65..5aef7eed2 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java @@ -301,12 +301,12 @@ private void handleFrame(ByteBuf frame) { handleRequestN(streamId, frame); break; case REQUEST_STREAM: - int streamInitialRequestN = RequestStreamFrameFlyweight.initialRequestN(frame); + long streamInitialRequestN = RequestStreamFrameFlyweight.initialRequestN(frame); Payload streamPayload = payloadDecoder.apply(frame); handleStream(streamId, requestStream(streamPayload), streamInitialRequestN, null); break; case REQUEST_CHANNEL: - int channelInitialRequestN = RequestChannelFrameFlyweight.initialRequestN(frame); + long channelInitialRequestN = RequestChannelFrameFlyweight.initialRequestN(frame); Payload channelPayload = payloadDecoder.apply(frame); handleChannel(streamId, channelPayload, channelInitialRequestN); break; @@ -399,16 +399,9 @@ protected void hookOnNext(Payload payload) { return; } - ByteBuf byteBuf; - try { - byteBuf = PayloadFrameFlyweight.encodeNextComplete(allocator, streamId, payload); - } catch (Throwable t) { - payload.release(); - throw Exceptions.propagate(t); - } - - payload.release(); - + ByteBuf byteBuf = + PayloadFrameFlyweight.encodeNextCompleteReleasingPayload( + allocator, streamId, payload); sendProcessor.onNext(byteBuf); } @@ -437,14 +430,14 @@ protected void hookFinally(SignalType type) { private void handleStream( int streamId, Flux response, - int initialRequestN, + long initialRequestN, @Nullable UnicastProcessor requestChannel) { final BaseSubscriber subscriber = new BaseSubscriber() { @Override protected void hookOnSubscribe(Subscription s) { - s.request(initialRequestN >= Integer.MAX_VALUE ? Long.MAX_VALUE : initialRequestN); + s.request(initialRequestN); } @Override @@ -469,16 +462,8 @@ protected void hookOnNext(Payload payload) { return; } - ByteBuf byteBuf; - try { - byteBuf = PayloadFrameFlyweight.encodeNext(allocator, streamId, payload); - } catch (Throwable t) { - payload.release(); - throw Exceptions.propagate(t); - } - - payload.release(); - + ByteBuf byteBuf = + PayloadFrameFlyweight.encodeNextReleasingPayload(allocator, streamId, payload); sendProcessor.onNext(byteBuf); } @@ -523,7 +508,7 @@ protected void hookFinally(SignalType type) { .subscribe(subscriber); } - private void handleChannel(int streamId, Payload payload, int initialRequestN) { + private void handleChannel(int streamId, Payload payload, long initialRequestN) { UnicastProcessor frames = UnicastProcessor.create(); channelProcessors.put(streamId, frames); @@ -602,8 +587,8 @@ private void handleRequestN(int streamId, ByteBuf frame) { Subscription subscription = sendingSubscriptions.get(streamId); if (subscription != null) { - int n = RequestNFrameFlyweight.requestN(frame); - subscription.request(n >= Integer.MAX_VALUE ? Long.MAX_VALUE : n); + long n = RequestNFrameFlyweight.requestN(frame); + subscription.request(n); } } } diff --git a/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameReassembler.java b/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameReassembler.java index d8537ec1a..1a8d242b2 100644 --- a/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameReassembler.java +++ b/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameReassembler.java @@ -166,8 +166,8 @@ void handleFollowsFlag(ByteBuf frame, int streamId, FrameType frameType) { header = frame.copy(frame.readerIndex(), FrameHeaderFlyweight.size()); if (frameType == FrameType.REQUEST_CHANNEL || frameType == FrameType.REQUEST_STREAM) { - int i = RequestChannelFrameFlyweight.initialRequestN(frame); - header.writeInt(i); + long i = RequestChannelFrameFlyweight.initialRequestN(frame); + header.writeInt(i > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) i); } putHeader(streamId, header); } @@ -261,10 +261,16 @@ void reassembleFrame(ByteBuf frame, SynchronousSink sink) { private ByteBuf assembleFrameWithMetadata(ByteBuf frame, int streamId, ByteBuf header) { ByteBuf metadata; CompositeByteBuf cm = removeMetadata(streamId); - if (cm != null) { - metadata = cm.addComponents(true, PayloadFrameFlyweight.metadata(frame).retain()); + + ByteBuf decodedMetadata = PayloadFrameFlyweight.metadata(frame); + if (decodedMetadata != null) { + if (cm != null) { + metadata = cm.addComponents(true, decodedMetadata.retain()); + } else { + metadata = PayloadFrameFlyweight.metadata(frame).retain(); + } } else { - metadata = PayloadFrameFlyweight.metadata(frame).retain(); + metadata = cm != null ? cm : null; } ByteBuf data = assembleData(frame, streamId); diff --git a/rsocket-core/src/main/java/io/rsocket/frame/DataAndMetadataFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/DataAndMetadataFlyweight.java index d910fe92f..ea54aa374 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/DataAndMetadataFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/DataAndMetadataFlyweight.java @@ -30,38 +30,35 @@ private static int decodeLength(final ByteBuf byteBuf) { return length; } - static ByteBuf encodeOnlyMetadata( - ByteBufAllocator allocator, final ByteBuf header, ByteBuf metadata) { - return allocator.compositeBuffer(2).addComponents(true, header, metadata); - } - - static ByteBuf encodeOnlyData(ByteBufAllocator allocator, final ByteBuf header, ByteBuf data) { - return allocator.compositeBuffer(2).addComponents(true, header, data); - } - static ByteBuf encode( - ByteBufAllocator allocator, final ByteBuf header, ByteBuf metadata, ByteBuf data) { + ByteBufAllocator allocator, + final ByteBuf header, + ByteBuf metadata, + boolean hasMetadata, + ByteBuf data) { - int length = metadata.readableBytes(); - encodeLength(header, length); - return allocator.compositeBuffer(3).addComponents(true, header, metadata, data); - } + final boolean addData = data != null && data.isReadable(); + final boolean addMetadata = hasMetadata && metadata.isReadable(); - static ByteBuf metadataWithoutMarking(ByteBuf byteBuf, boolean hasMetadata) { if (hasMetadata) { - int length = decodeLength(byteBuf); - return byteBuf.readSlice(length); + int length = metadata.readableBytes(); + encodeLength(header, length); + } + + if (addMetadata && addData) { + return allocator.compositeBuffer(3).addComponents(true, header, metadata, data); + } else if (addMetadata) { + return allocator.compositeBuffer(2).addComponents(true, header, metadata); + } else if (addData) { + return allocator.compositeBuffer(2).addComponents(true, header, data); } else { - return Unpooled.EMPTY_BUFFER; + return header; } } - static ByteBuf metadata(ByteBuf byteBuf, boolean hasMetadata) { - byteBuf.markReaderIndex(); - byteBuf.skipBytes(6); - ByteBuf metadata = metadataWithoutMarking(byteBuf, hasMetadata); - byteBuf.resetReaderIndex(); - return metadata; + static ByteBuf metadataWithoutMarking(ByteBuf byteBuf) { + int length = decodeLength(byteBuf); + return byteBuf.readSlice(length); } static ByteBuf dataWithoutMarking(ByteBuf byteBuf, boolean hasMetadata) { @@ -76,12 +73,4 @@ static ByteBuf dataWithoutMarking(ByteBuf byteBuf, boolean hasMetadata) { return Unpooled.EMPTY_BUFFER; } } - - static ByteBuf data(ByteBuf byteBuf, boolean hasMetadata) { - byteBuf.markReaderIndex(); - byteBuf.skipBytes(6); - ByteBuf data = dataWithoutMarking(byteBuf, hasMetadata); - byteBuf.resetReaderIndex(); - return data; - } } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/ExtensionFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/ExtensionFrameFlyweight.java index df8b308e9..8cb01b08f 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/ExtensionFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/ExtensionFrameFlyweight.java @@ -14,21 +14,18 @@ public static ByteBuf encode( @Nullable ByteBuf metadata, ByteBuf data) { + final boolean hasMetadata = metadata != null; + int flags = FrameHeaderFlyweight.FLAGS_I; - if (metadata != null) { + if (hasMetadata) { flags |= FrameHeaderFlyweight.FLAGS_M; } - ByteBuf header = FrameHeaderFlyweight.encode(allocator, streamId, FrameType.EXT, flags); + final ByteBuf header = FrameHeaderFlyweight.encode(allocator, streamId, FrameType.EXT, flags); header.writeInt(extendedType); - if (data == null && metadata == null) { - return header; - } else if (metadata != null) { - return DataAndMetadataFlyweight.encode(allocator, header, metadata, data); - } else { - return DataAndMetadataFlyweight.encodeOnlyData(allocator, header, data); - } + + return DataAndMetadataFlyweight.encode(allocator, header, metadata, hasMetadata, data); } public static int extendedType(ByteBuf byteBuf) { @@ -56,10 +53,13 @@ public static ByteBuf metadata(ByteBuf byteBuf) { FrameHeaderFlyweight.ensureFrameType(FrameType.EXT, byteBuf); boolean hasMetadata = FrameHeaderFlyweight.hasMetadata(byteBuf); + if (!hasMetadata) { + return null; + } byteBuf.markReaderIndex(); // Extended type byteBuf.skipBytes(FrameHeaderFlyweight.size() + Integer.BYTES); - ByteBuf metadata = DataAndMetadataFlyweight.metadataWithoutMarking(byteBuf, hasMetadata); + ByteBuf metadata = DataAndMetadataFlyweight.metadataWithoutMarking(byteBuf); byteBuf.resetReaderIndex(); return metadata; } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/FragmentationFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/FragmentationFlyweight.java index 06efeab6c..a91d52782 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/FragmentationFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/FragmentationFlyweight.java @@ -13,12 +13,7 @@ public static ByteBuf encode(final ByteBufAllocator allocator, ByteBuf header, B public static ByteBuf encode( final ByteBufAllocator allocator, ByteBuf header, @Nullable ByteBuf metadata, ByteBuf data) { - if (data == null && metadata == null) { - return header; - } else if (metadata != null) { - return DataAndMetadataFlyweight.encode(allocator, header, metadata, data); - } else { - return DataAndMetadataFlyweight.encodeOnlyData(allocator, header, data); - } + final boolean hasMetadata = metadata != null; + return DataAndMetadataFlyweight.encode(allocator, header, metadata, hasMetadata, data); } } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/KeepAliveFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/KeepAliveFrameFlyweight.java index e4e6029b3..b591412a6 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/KeepAliveFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/KeepAliveFrameFlyweight.java @@ -29,7 +29,7 @@ public static ByteBuf encode( header.writeLong(lp); - return DataAndMetadataFlyweight.encodeOnlyData(allocator, header, data); + return DataAndMetadataFlyweight.encode(allocator, header, null, false, data); } public static boolean respondFlag(ByteBuf byteBuf) { diff --git a/rsocket-core/src/main/java/io/rsocket/frame/LeaseFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/LeaseFrameFlyweight.java index 4676f4c9d..039c72886 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/LeaseFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/LeaseFrameFlyweight.java @@ -13,21 +13,24 @@ public static ByteBuf encode( final int numRequests, @Nullable final ByteBuf metadata) { + final boolean hasMetadata = metadata != null; + final boolean addMetadata = hasMetadata && metadata.isReadable(); + int flags = 0; - if (metadata != null) { + if (hasMetadata) { flags |= FrameHeaderFlyweight.FLAGS_M; } - ByteBuf header = + final ByteBuf header = FrameHeaderFlyweight.encodeStreamZero(allocator, FrameType.LEASE, flags) .writeInt(ttl) .writeInt(numRequests); - if (metadata == null) { - return header; + if (addMetadata) { + return allocator.compositeBuffer(2).addComponents(true, header, metadata); } else { - return DataAndMetadataFlyweight.encodeOnlyMetadata(allocator, header, metadata); + return header; } } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/MetadataPushFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/MetadataPushFrameFlyweight.java index d37b573ba..e3a9a47ba 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/MetadataPushFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/MetadataPushFrameFlyweight.java @@ -2,8 +2,16 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.rsocket.Payload; public class MetadataPushFrameFlyweight { + + public static ByteBuf encodeReleasingPayload(ByteBufAllocator allocator, Payload payload) { + final ByteBuf metadata = payload.metadata().retain(); + payload.release(); + return encode(allocator, metadata); + } + public static ByteBuf encode(ByteBufAllocator allocator, ByteBuf metadata) { ByteBuf header = FrameHeaderFlyweight.encodeStreamZero( diff --git a/rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameFlyweight.java index 4f67d9c72..4c2ebdf6e 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameFlyweight.java @@ -9,6 +9,33 @@ public class PayloadFrameFlyweight { private PayloadFrameFlyweight() {} + public static ByteBuf encodeNextReleasingPayload( + ByteBufAllocator allocator, int streamId, Payload payload) { + return encodeReleasingPayload(allocator, streamId, false, payload); + } + + public static ByteBuf encodeNextCompleteReleasingPayload( + ByteBufAllocator allocator, int streamId, Payload payload) { + + return encodeReleasingPayload(allocator, streamId, true, payload); + } + + static ByteBuf encodeReleasingPayload( + ByteBufAllocator allocator, int streamId, boolean complete, Payload payload) { + + final boolean hasMetadata = payload.hasMetadata(); + final ByteBuf metadata = hasMetadata ? payload.metadata().retain() : null; + final ByteBuf data = payload.data().retain(); + + payload.release(); + + return encode(allocator, streamId, false, complete, true, metadata, data); + } + + public static ByteBuf encodeComplete(ByteBufAllocator allocator, int streamId) { + return encode(allocator, streamId, false, true, false, null, null); + } + public static ByteBuf encode( ByteBufAllocator allocator, int streamId, @@ -21,53 +48,6 @@ public static ByteBuf encode( allocator, streamId, fragmentFollows, complete, next, 0, metadata, data); } - public static ByteBuf encode( - ByteBufAllocator allocator, - int streamId, - boolean fragmentFollows, - boolean complete, - boolean next, - Payload payload) { - return FLYWEIGHT.encode( - allocator, - streamId, - fragmentFollows, - complete, - next, - 0, - payload.hasMetadata() ? payload.metadata().retain() : null, - payload.data().retain()); - } - - public static ByteBuf encodeNextComplete( - ByteBufAllocator allocator, int streamId, Payload payload) { - return FLYWEIGHT.encode( - allocator, - streamId, - false, - true, - true, - 0, - payload.hasMetadata() ? payload.metadata().retain() : null, - payload.data().retain()); - } - - public static ByteBuf encodeNext(ByteBufAllocator allocator, int streamId, Payload payload) { - return FLYWEIGHT.encode( - allocator, - streamId, - false, - false, - true, - 0, - payload.hasMetadata() ? payload.metadata().retain() : null, - payload.data().retain()); - } - - public static ByteBuf encodeComplete(ByteBufAllocator allocator, int streamId) { - return FLYWEIGHT.encode(allocator, streamId, false, true, false, 0, null, null); - } - public static ByteBuf data(ByteBuf byteBuf) { return FLYWEIGHT.data(byteBuf); } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameFlyweight.java index 06ddcda03..7c3cbb574 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameFlyweight.java @@ -10,25 +10,20 @@ public class RequestChannelFrameFlyweight { private RequestChannelFrameFlyweight() {} - public static ByteBuf encode( + public static ByteBuf encodeReleasingPayload( ByteBufAllocator allocator, int streamId, - boolean fragmentFollows, boolean complete, - long requestN, + long initialRequestN, Payload payload) { - int reqN = requestN > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) requestN; + final boolean hasMetadata = payload.hasMetadata(); + final ByteBuf metadata = hasMetadata ? payload.metadata().retain() : null; + final ByteBuf data = payload.data().retain(); - return FLYWEIGHT.encode( - allocator, - streamId, - fragmentFollows, - complete, - false, - reqN, - payload.metadata(), - payload.data()); + payload.release(); + + return encode(allocator, streamId, false, complete, initialRequestN, metadata, data); } public static ByteBuf encode( @@ -36,11 +31,15 @@ public static ByteBuf encode( int streamId, boolean fragmentFollows, boolean complete, - long requestN, + long initialRequestN, ByteBuf metadata, ByteBuf data) { - int reqN = requestN > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) requestN; + if (initialRequestN < 1) { + throw new IllegalArgumentException("request n is less than 1"); + } + + int reqN = initialRequestN > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) initialRequestN; return FLYWEIGHT.encode( allocator, streamId, fragmentFollows, complete, false, reqN, metadata, data); @@ -54,7 +53,8 @@ public static ByteBuf metadata(ByteBuf byteBuf) { return FLYWEIGHT.metadataWithRequestN(byteBuf); } - public static int initialRequestN(ByteBuf byteBuf) { - return FLYWEIGHT.initialRequestN(byteBuf); + public static long initialRequestN(ByteBuf byteBuf) { + int requestN = FLYWEIGHT.initialRequestN(byteBuf); + return requestN == Integer.MAX_VALUE ? Long.MAX_VALUE : requestN; } } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameFlyweight.java index 5f2d606e4..287f765f7 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameFlyweight.java @@ -10,11 +10,16 @@ public class RequestFireAndForgetFrameFlyweight { private RequestFireAndForgetFrameFlyweight() {} - public static ByteBuf encode( - ByteBufAllocator allocator, int streamId, boolean fragmentFollows, Payload payload) { + public static ByteBuf encodeReleasingPayload( + ByteBufAllocator allocator, int streamId, Payload payload) { + + final boolean hasMetadata = payload.hasMetadata(); + final ByteBuf metadata = hasMetadata ? payload.metadata().retain() : null; + final ByteBuf data = payload.data().retain(); + + payload.release(); - return FLYWEIGHT.encode( - allocator, streamId, fragmentFollows, payload.metadata(), payload.data()); + return FLYWEIGHT.encode(allocator, streamId, false, metadata, data); } public static ByteBuf encode( diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestFlyweight.java index 98d862f36..15fac9f55 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/RequestFlyweight.java @@ -29,9 +29,12 @@ ByteBuf encode( int requestN, @Nullable ByteBuf metadata, ByteBuf data) { + + final boolean hasMetadata = metadata != null; + int flags = 0; - if (metadata != null) { + if (hasMetadata) { flags |= FrameHeaderFlyweight.FLAGS_M; } @@ -47,19 +50,13 @@ ByteBuf encode( flags |= FrameHeaderFlyweight.FLAGS_N; } - ByteBuf header = FrameHeaderFlyweight.encode(allocator, streamId, frameType, flags); + final ByteBuf header = FrameHeaderFlyweight.encode(allocator, streamId, frameType, flags); if (requestN > 0) { header.writeInt(requestN); } - if (data == null && metadata == null) { - return header; - } else if (metadata != null) { - return DataAndMetadataFlyweight.encode(allocator, header, metadata, data); - } else { - return DataAndMetadataFlyweight.encodeOnlyData(allocator, header, data); - } + return DataAndMetadataFlyweight.encode(allocator, header, metadata, hasMetadata, data); } ByteBuf data(ByteBuf byteBuf) { @@ -73,9 +70,12 @@ ByteBuf data(ByteBuf byteBuf) { ByteBuf metadata(ByteBuf byteBuf) { boolean hasMetadata = FrameHeaderFlyweight.hasMetadata(byteBuf); + if (!hasMetadata) { + return null; + } byteBuf.markReaderIndex(); byteBuf.skipBytes(FrameHeaderFlyweight.size()); - ByteBuf metadata = DataAndMetadataFlyweight.metadataWithoutMarking(byteBuf, hasMetadata); + ByteBuf metadata = DataAndMetadataFlyweight.metadataWithoutMarking(byteBuf); byteBuf.resetReaderIndex(); return metadata; } @@ -91,9 +91,12 @@ ByteBuf dataWithRequestN(ByteBuf byteBuf) { ByteBuf metadataWithRequestN(ByteBuf byteBuf) { boolean hasMetadata = FrameHeaderFlyweight.hasMetadata(byteBuf); + if (!hasMetadata) { + return null; + } byteBuf.markReaderIndex(); byteBuf.skipBytes(FrameHeaderFlyweight.size() + Integer.BYTES); - ByteBuf metadata = DataAndMetadataFlyweight.metadataWithoutMarking(byteBuf, hasMetadata); + ByteBuf metadata = DataAndMetadataFlyweight.metadataWithoutMarking(byteBuf); byteBuf.resetReaderIndex(); return metadata; } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestNFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestNFrameFlyweight.java index 5a4c4c273..fe2c752cf 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestNFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/RequestNFrameFlyweight.java @@ -8,26 +8,23 @@ private RequestNFrameFlyweight() {} public static ByteBuf encode( final ByteBufAllocator allocator, final int streamId, long requestN) { - int reqN = requestN > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) requestN; - return encode(allocator, streamId, reqN); - } - - public static ByteBuf encode(final ByteBufAllocator allocator, final int streamId, int requestN) { - ByteBuf header = FrameHeaderFlyweight.encode(allocator, streamId, FrameType.REQUEST_N, 0); if (requestN < 1) { throw new IllegalArgumentException("request n is less than 1"); } - return header.writeInt(requestN); + int reqN = requestN > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) requestN; + + ByteBuf header = FrameHeaderFlyweight.encode(allocator, streamId, FrameType.REQUEST_N, 0); + return header.writeInt(reqN); } - public static int requestN(ByteBuf byteBuf) { + public static long requestN(ByteBuf byteBuf) { FrameHeaderFlyweight.ensureFrameType(FrameType.REQUEST_N, byteBuf); byteBuf.markReaderIndex(); byteBuf.skipBytes(FrameHeaderFlyweight.size()); int i = byteBuf.readInt(); byteBuf.resetReaderIndex(); - return i; + return i == Integer.MAX_VALUE ? Long.MAX_VALUE : i; } } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameFlyweight.java index 2e06c9b82..3fbac27d2 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameFlyweight.java @@ -10,9 +10,16 @@ public class RequestResponseFrameFlyweight { private RequestResponseFrameFlyweight() {} - public static ByteBuf encode( - ByteBufAllocator allocator, int streamId, boolean fragmentFollows, Payload payload) { - return encode(allocator, streamId, fragmentFollows, payload.metadata(), payload.data()); + public static ByteBuf encodeReleasingPayload( + ByteBufAllocator allocator, int streamId, Payload payload) { + + final boolean hasMetadata = payload.hasMetadata(); + final ByteBuf metadata = hasMetadata ? payload.metadata().retain() : null; + final ByteBuf data = payload.data().retain(); + + payload.release(); + + return encode(allocator, streamId, false, metadata, data); } public static ByteBuf encode( diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameFlyweight.java index 171c41990..ff1435652 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameFlyweight.java @@ -10,46 +10,34 @@ public class RequestStreamFrameFlyweight { private RequestStreamFrameFlyweight() {} - public static ByteBuf encode( - ByteBufAllocator allocator, - int streamId, - boolean fragmentFollows, - long requestN, - Payload payload) { - return encode( - allocator, streamId, fragmentFollows, requestN, payload.metadata(), payload.data()); - } + public static ByteBuf encodeReleasingPayload( + ByteBufAllocator allocator, int streamId, long initialRequestN, Payload payload) { - public static ByteBuf encode( - ByteBufAllocator allocator, - int streamId, - boolean fragmentFollows, - int requestN, - Payload payload) { - return encode( - allocator, streamId, fragmentFollows, requestN, payload.metadata(), payload.data()); - } + final boolean hasMetadata = payload.hasMetadata(); + final ByteBuf metadata = hasMetadata ? payload.metadata().retain() : null; + final ByteBuf data = payload.data().retain(); - public static ByteBuf encode( - ByteBufAllocator allocator, - int streamId, - boolean fragmentFollows, - long requestN, - ByteBuf metadata, - ByteBuf data) { - int reqN = requestN > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) requestN; - return encode(allocator, streamId, fragmentFollows, reqN, metadata, data); + payload.release(); + + return encode(allocator, streamId, false, initialRequestN, metadata, data); } public static ByteBuf encode( ByteBufAllocator allocator, int streamId, boolean fragmentFollows, - int requestN, + long initialRequestN, ByteBuf metadata, ByteBuf data) { + + if (initialRequestN < 1) { + throw new IllegalArgumentException("request n is less than 1"); + } + + int reqN = initialRequestN > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) initialRequestN; + return FLYWEIGHT.encode( - allocator, streamId, fragmentFollows, false, false, requestN, metadata, data); + allocator, streamId, fragmentFollows, false, false, reqN, metadata, data); } public static ByteBuf data(ByteBuf byteBuf) { @@ -60,7 +48,8 @@ public static ByteBuf metadata(ByteBuf byteBuf) { return FLYWEIGHT.metadataWithRequestN(byteBuf); } - public static int initialRequestN(ByteBuf byteBuf) { - return FLYWEIGHT.initialRequestN(byteBuf); + public static long initialRequestN(ByteBuf byteBuf) { + int requestN = FLYWEIGHT.initialRequestN(byteBuf); + return requestN == Integer.MAX_VALUE ? Long.MAX_VALUE : requestN; } } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/SetupFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/SetupFrameFlyweight.java index 9f92e715f..bfb73fe22 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/SetupFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/SetupFrameFlyweight.java @@ -55,8 +55,9 @@ public static ByteBuf encode( final String dataMimeType, final Payload setupPayload) { - ByteBuf metadata = setupPayload.hasMetadata() ? setupPayload.sliceMetadata() : null; - ByteBuf data = setupPayload.sliceData(); + final ByteBuf data = setupPayload.sliceData(); + final boolean hasMetadata = setupPayload.hasMetadata(); + final ByteBuf metadata = hasMetadata ? setupPayload.sliceMetadata() : null; int flags = 0; @@ -68,11 +69,11 @@ public static ByteBuf encode( flags |= FLAGS_WILL_HONOR_LEASE; } - if (metadata != null) { + if (hasMetadata) { flags |= FrameHeaderFlyweight.FLAGS_M; } - ByteBuf header = FrameHeaderFlyweight.encodeStreamZero(allocator, FrameType.SETUP, flags); + final ByteBuf header = FrameHeaderFlyweight.encodeStreamZero(allocator, FrameType.SETUP, flags); header.writeInt(CURRENT_VERSION).writeInt(keepaliveInterval).writeInt(maxLifetime); @@ -91,13 +92,8 @@ public static ByteBuf encode( length = ByteBufUtil.utf8Bytes(dataMimeType); header.writeByte(length); ByteBufUtil.writeUtf8(header, dataMimeType); - if (data == null && metadata == null) { - return header; - } else if (metadata != null) { - return DataAndMetadataFlyweight.encode(allocator, header, metadata, data); - } else { - return DataAndMetadataFlyweight.encodeOnlyData(allocator, header, data); - } + + return DataAndMetadataFlyweight.encode(allocator, header, metadata, hasMetadata, data); } public static int version(ByteBuf byteBuf) { @@ -192,9 +188,12 @@ public static String dataMimeType(ByteBuf byteBuf) { public static ByteBuf metadata(ByteBuf byteBuf) { boolean hasMetadata = FrameHeaderFlyweight.hasMetadata(byteBuf); + if (!hasMetadata) { + return null; + } byteBuf.markReaderIndex(); skipToPayload(byteBuf); - ByteBuf metadata = DataAndMetadataFlyweight.metadataWithoutMarking(byteBuf, hasMetadata); + ByteBuf metadata = DataAndMetadataFlyweight.metadataWithoutMarking(byteBuf); byteBuf.resetReaderIndex(); return metadata; } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/decoder/DefaultPayloadDecoder.java b/rsocket-core/src/main/java/io/rsocket/frame/decoder/DefaultPayloadDecoder.java index 692dcb363..0a77e3820 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/decoder/DefaultPayloadDecoder.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/decoder/DefaultPayloadDecoder.java @@ -3,8 +3,15 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.rsocket.Payload; -import io.rsocket.frame.*; -import io.rsocket.util.ByteBufPayload; +import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.FrameType; +import io.rsocket.frame.MetadataPushFrameFlyweight; +import io.rsocket.frame.PayloadFrameFlyweight; +import io.rsocket.frame.RequestChannelFrameFlyweight; +import io.rsocket.frame.RequestFireAndForgetFrameFlyweight; +import io.rsocket.frame.RequestResponseFrameFlyweight; +import io.rsocket.frame.RequestStreamFrameFlyweight; +import io.rsocket.util.DefaultPayload; import java.nio.ByteBuffer; /** Default Frame decoder that copies the frames contents for easy of use. */ @@ -45,14 +52,18 @@ public Payload apply(ByteBuf byteBuf) { throw new IllegalArgumentException("unsupported frame type: " + type); } - ByteBuffer metadata = ByteBuffer.allocateDirect(m.readableBytes()); ByteBuffer data = ByteBuffer.allocateDirect(d.readableBytes()); - data.put(d.nioBuffer()); data.flip(); - metadata.put(m.nioBuffer()); - metadata.flip(); - return ByteBufPayload.create(data, metadata); + if (m != null) { + ByteBuffer metadata = ByteBuffer.allocateDirect(m.readableBytes()); + metadata.put(m.nioBuffer()); + metadata.flip(); + + return DefaultPayload.create(data, metadata); + } + + return DefaultPayload.create(data); } } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/decoder/ZeroCopyPayloadDecoder.java b/rsocket-core/src/main/java/io/rsocket/frame/decoder/ZeroCopyPayloadDecoder.java index 0b63590e8..c92f82428 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/decoder/ZeroCopyPayloadDecoder.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/decoder/ZeroCopyPayloadDecoder.java @@ -3,7 +3,14 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.rsocket.Payload; -import io.rsocket.frame.*; +import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.FrameType; +import io.rsocket.frame.MetadataPushFrameFlyweight; +import io.rsocket.frame.PayloadFrameFlyweight; +import io.rsocket.frame.RequestChannelFrameFlyweight; +import io.rsocket.frame.RequestFireAndForgetFrameFlyweight; +import io.rsocket.frame.RequestResponseFrameFlyweight; +import io.rsocket.frame.RequestStreamFrameFlyweight; import io.rsocket.util.ByteBufPayload; /** @@ -46,6 +53,6 @@ public Payload apply(ByteBuf byteBuf) { throw new IllegalArgumentException("unsupported frame type: " + type); } - return ByteBufPayload.create(d.retain(), m.retain()); + return ByteBufPayload.create(d.retain(), m != null ? m.retain() : null); } } diff --git a/rsocket-core/src/main/java/io/rsocket/util/ByteBufPayload.java b/rsocket-core/src/main/java/io/rsocket/util/ByteBufPayload.java index b91cf8ac6..f5d747f7f 100644 --- a/rsocket-core/src/main/java/io/rsocket/util/ByteBufPayload.java +++ b/rsocket-core/src/main/java/io/rsocket/util/ByteBufPayload.java @@ -21,6 +21,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.util.AbstractReferenceCounted; +import io.netty.util.IllegalReferenceCountException; import io.netty.util.Recycler; import io.netty.util.Recycler.Handle; import io.rsocket.Payload; @@ -112,9 +113,10 @@ public static Payload create(ByteBuf data) { public static Payload create(ByteBuf data, @Nullable ByteBuf metadata) { ByteBufPayload payload = RECYCLER.get(); - payload.setRefCnt(1); payload.data = data; payload.metadata = metadata; + // unsure data and metadata is set before refCnt change + payload.setRefCnt(1); return payload; } @@ -126,26 +128,31 @@ public static Payload create(Payload payload) { @Override public boolean hasMetadata() { + ensureAccessible(); return metadata != null; } @Override public ByteBuf sliceMetadata() { + ensureAccessible(); return metadata == null ? Unpooled.EMPTY_BUFFER : metadata.slice(); } @Override public ByteBuf data() { + ensureAccessible(); return data; } @Override public ByteBuf metadata() { + ensureAccessible(); return metadata == null ? Unpooled.EMPTY_BUFFER : metadata; } @Override public ByteBuf sliceData() { + ensureAccessible(); return data.slice(); } @@ -163,6 +170,7 @@ public ByteBufPayload retain(int increment) { @Override public ByteBufPayload touch() { + ensureAccessible(); data.touch(); if (metadata != null) { metadata.touch(); @@ -172,6 +180,7 @@ public ByteBufPayload touch() { @Override public ByteBufPayload touch(Object hint) { + ensureAccessible(); data.touch(hint); if (metadata != null) { metadata.touch(hint); @@ -189,4 +198,22 @@ protected void deallocate() { } handle.recycle(this); } + + /** + * Should be called by every method that tries to access the buffers content to check if the + * buffer was released before. + */ + void ensureAccessible() { + if (!isAccessible()) { + throw new IllegalReferenceCountException(0); + } + } + + /** + * Used internally by {@link ByteBufPayload#ensureAccessible()} to try to guard against using the + * buffer after it was released (best-effort). + */ + boolean isAccessible() { + return refCnt() != 0; + } } diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java index 36abb14b4..bc19d8132 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java @@ -20,6 +20,7 @@ import static io.rsocket.frame.FrameHeaderFlyweight.frameType; import static io.rsocket.frame.FrameType.CANCEL; import static io.rsocket.frame.FrameType.REQUEST_CHANNEL; +import static io.rsocket.frame.FrameType.REQUEST_FNF; import static io.rsocket.frame.FrameType.REQUEST_RESPONSE; import static io.rsocket.frame.FrameType.REQUEST_STREAM; import static org.hamcrest.MatcherAssert.assertThat; @@ -35,6 +36,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; import io.netty.util.CharsetUtil; import io.netty.util.ReferenceCounted; import io.rsocket.Payload; @@ -74,7 +76,9 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.runners.model.Statement; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; @@ -141,7 +145,7 @@ protected void hookOnSubscribe(Subscription subscription) { ByteBuf f = sent.get(0); assertThat("initial frame", frameType(f), is(REQUEST_STREAM)); - assertThat("initial request n", RequestStreamFrameFlyweight.initialRequestN(f), is(5)); + assertThat("initial request n", RequestStreamFrameFlyweight.initialRequestN(f), is(5L)); assertThat("should be released", f.release(), is(true)); rule.assertHasNoLeaks(); } @@ -190,7 +194,8 @@ public void testHandleValidFrame() { int streamId = rule.getStreamIdForRequestType(REQUEST_RESPONSE); rule.connection.addToReceivedBuffer( - PayloadFrameFlyweight.encodeNext(rule.alloc(), streamId, EmptyPayload.INSTANCE)); + PayloadFrameFlyweight.encodeNextReleasingPayload( + rule.alloc(), streamId, EmptyPayload.INSTANCE)); verify(sub).onComplete(); Assertions.assertThat(rule.connection.getSent()).hasSize(1).allMatch(ReferenceCounted::release); @@ -328,7 +333,7 @@ protected void hookOnSubscribe(Subscription subscription) {} Assertions.assertThat(FrameHeaderFlyweight.frameType(initialFrame)).isEqualTo(REQUEST_CHANNEL); Assertions.assertThat(RequestChannelFrameFlyweight.initialRequestN(initialFrame)) - .isEqualTo(Integer.MAX_VALUE); + .isEqualTo(Long.MAX_VALUE); Assertions.assertThat( RequestChannelFrameFlyweight.data(initialFrame).toString(CharsetUtil.UTF_8)) .isEqualTo("0"); @@ -625,12 +630,110 @@ public void simpleOnDiscardRequestChannelTest2() { rule.assertHasNoLeaks(); } + @ParameterizedTest + @MethodSource("encodeDecodePayloadCases") + public void verifiesThatFrameWithNoMetadataHasDecodedCorrectlyIntoPayload( + FrameType frameType, int framesCnt, int responsesCnt) { + ByteBufAllocator allocator = rule.alloc(); + AssertSubscriber assertSubscriber = AssertSubscriber.create(responsesCnt); + TestPublisher testPublisher = TestPublisher.create(); + + Publisher response; + + switch (frameType) { + case REQUEST_FNF: + response = + testPublisher.mono().flatMap(p -> rule.socket.fireAndForget(p).then(Mono.empty())); + break; + case REQUEST_RESPONSE: + response = testPublisher.mono().flatMap(p -> rule.socket.requestResponse(p)); + break; + case REQUEST_STREAM: + response = testPublisher.mono().flatMapMany(p -> rule.socket.requestStream(p)); + break; + case REQUEST_CHANNEL: + response = rule.socket.requestChannel(testPublisher.flux()); + break; + default: + throw new UnsupportedOperationException("illegal case"); + } + + response.subscribe(assertSubscriber); + testPublisher.next(ByteBufPayload.create("d")); + + int streamId = rule.getStreamIdForRequestType(frameType); + + if (responsesCnt > 0) { + for (int i = 0; i < responsesCnt - 1; i++) { + rule.connection.addToReceivedBuffer( + PayloadFrameFlyweight.encode( + allocator, + streamId, + false, + false, + true, + null, + Unpooled.wrappedBuffer(("rd" + (i + 1)).getBytes()))); + } + + rule.connection.addToReceivedBuffer( + PayloadFrameFlyweight.encode( + allocator, + streamId, + false, + true, + true, + null, + Unpooled.wrappedBuffer(("rd" + responsesCnt).getBytes()))); + } + + if (framesCnt > 1) { + rule.connection.addToReceivedBuffer( + RequestNFrameFlyweight.encode(allocator, streamId, framesCnt)); + } + + for (int i = 1; i < framesCnt; i++) { + testPublisher.next(ByteBufPayload.create("d" + i)); + } + + Assertions.assertThat(rule.connection.getSent()) + .describedAs( + "Interaction Type :[%s]. Expected to observe %s frames sent", frameType, framesCnt) + .hasSize(framesCnt) + .allMatch(bb -> !FrameHeaderFlyweight.hasMetadata(bb)) + .allMatch(ByteBuf::release); + + Assertions.assertThat(assertSubscriber.isTerminated()) + .describedAs("Interaction Type :[%s]. Expected to be terminated", frameType) + .isTrue(); + + Assertions.assertThat(assertSubscriber.values()) + .describedAs( + "Interaction Type :[%s]. Expected to observe %s frames received", + frameType, responsesCnt) + .hasSize(responsesCnt) + .allMatch(p -> !p.hasMetadata()) + .allMatch(p -> p.release()); + + rule.assertHasNoLeaks(); + rule.connection.clearSendReceiveBuffers(); + } + + static Stream encodeDecodePayloadCases() { + return Stream.of( + Arguments.of(REQUEST_FNF, 1, 0), + Arguments.of(REQUEST_RESPONSE, 1, 1), + Arguments.of(REQUEST_STREAM, 1, 5), + Arguments.of(REQUEST_CHANNEL, 5, 5)); + } + public int sendRequestResponse(Publisher response) { Subscriber sub = TestSubscriber.create(); response.subscribe(sub); int streamId = rule.getStreamIdForRequestType(REQUEST_RESPONSE); rule.connection.addToReceivedBuffer( - PayloadFrameFlyweight.encodeNextComplete(rule.alloc(), streamId, EmptyPayload.INSTANCE)); + PayloadFrameFlyweight.encodeNextCompleteReleasingPayload( + rule.alloc(), streamId, EmptyPayload.INSTANCE)); verify(sub).onNext(any(Payload.class)); verify(sub).onComplete(); return streamId; diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java index 05f9fd46e..78027aa3d 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java @@ -19,6 +19,8 @@ import static io.rsocket.core.PayloadValidationUtils.INVALID_PAYLOAD_ERROR_MESSAGE; import static io.rsocket.frame.FrameHeaderFlyweight.frameType; import static io.rsocket.frame.FrameType.REQUEST_CHANNEL; +import static io.rsocket.frame.FrameType.REQUEST_FNF; +import static io.rsocket.frame.FrameType.REQUEST_N; import static io.rsocket.frame.FrameType.REQUEST_RESPONSE; import static io.rsocket.frame.FrameType.REQUEST_STREAM; import static org.hamcrest.MatcherAssert.assertThat; @@ -45,6 +47,7 @@ import io.rsocket.frame.KeepAliveFrameFlyweight; import io.rsocket.frame.PayloadFrameFlyweight; import io.rsocket.frame.RequestChannelFrameFlyweight; +import io.rsocket.frame.RequestFireAndForgetFrameFlyweight; import io.rsocket.frame.RequestNFrameFlyweight; import io.rsocket.frame.RequestResponseFrameFlyweight; import io.rsocket.frame.RequestStreamFrameFlyweight; @@ -55,16 +58,21 @@ import io.rsocket.test.util.TestSubscriber; import io.rsocket.util.ByteBufPayload; import io.rsocket.util.DefaultPayload; +import io.rsocket.util.EmptyPayload; import java.util.Collection; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Stream; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.runners.model.Statement; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; @@ -78,6 +86,7 @@ import reactor.core.publisher.Operators; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; +import reactor.test.publisher.TestPublisher; import reactor.test.util.RaceTestUtils; public class RSocketResponderTest { @@ -605,6 +614,108 @@ public Flux requestChannel(Publisher payloads) { rule.assertHasNoLeaks(); } + @ParameterizedTest + @MethodSource("encodeDecodePayloadCases") + public void verifiesThatFrameWithNoMetadataHasDecodedCorrectlyIntoPayload( + FrameType frameType, int framesCnt, int responsesCnt) { + ByteBufAllocator allocator = rule.alloc(); + AssertSubscriber assertSubscriber = AssertSubscriber.create(framesCnt); + TestPublisher testPublisher = TestPublisher.create(); + + rule.setAcceptingSocket( + new AbstractRSocket() { + @Override + public Mono fireAndForget(Payload payload) { + Mono.just(payload).subscribe(assertSubscriber); + return Mono.empty(); + } + + @Override + public Mono requestResponse(Payload payload) { + Mono.just(payload).subscribe(assertSubscriber); + return testPublisher.mono(); + } + + @Override + public Flux requestStream(Payload payload) { + Mono.just(payload).subscribe(assertSubscriber); + return testPublisher.flux(); + } + + @Override + public Flux requestChannel(Publisher payloads) { + payloads.subscribe(assertSubscriber); + return testPublisher.flux(); + } + }, + 1); + + rule.sendRequest(1, frameType, ByteBufPayload.create("d")); + + // if responses number is bigger than 1 we have to send one extra requestN + if (responsesCnt > 1) { + rule.connection.addToReceivedBuffer( + RequestNFrameFlyweight.encode(allocator, 1, responsesCnt - 1)); + } + + // respond with specific number of elements + for (int i = 0; i < responsesCnt; i++) { + testPublisher.next(ByteBufPayload.create("rd" + i)); + } + + // Listen to incoming frames. Valid for RequestChannel case only + if (framesCnt > 1) { + for (int i = 1; i < responsesCnt; i++) { + rule.connection.addToReceivedBuffer( + PayloadFrameFlyweight.encode( + allocator, + 1, + false, + false, + true, + null, + Unpooled.wrappedBuffer(("d" + (i + 1)).getBytes()))); + } + } + + if (responsesCnt > 0) { + Assertions.assertThat( + rule.connection.getSent().stream().filter(bb -> frameType(bb) != REQUEST_N)) + .describedAs( + "Interaction Type :[%s]. Expected to observe %s frames sent", frameType, responsesCnt) + .hasSize(responsesCnt) + .allMatch(bb -> !FrameHeaderFlyweight.hasMetadata(bb)); + } + + if (framesCnt > 1) { + Assertions.assertThat( + rule.connection.getSent().stream().filter(bb -> frameType(bb) == REQUEST_N)) + .describedAs( + "Interaction Type :[%s]. Expected to observe single RequestN(%s) frame", + frameType, framesCnt - 1) + .hasSize(1) + .first() + .matches(bb -> RequestNFrameFlyweight.requestN(bb) == (framesCnt - 1)); + } + + Assertions.assertThat(rule.connection.getSent()).allMatch(ReferenceCounted::release); + + Assertions.assertThat(assertSubscriber.awaitAndAssertNextValueCount(framesCnt).values()) + .hasSize(framesCnt) + .allMatch(p -> !p.hasMetadata()) + .allMatch(ReferenceCounted::release); + + rule.assertHasNoLeaks(); + } + + static Stream encodeDecodePayloadCases() { + return Stream.of( + Arguments.of(REQUEST_FNF, 1, 0), + Arguments.of(REQUEST_RESPONSE, 1, 1), + Arguments.of(REQUEST_STREAM, 1, 5), + Arguments.of(REQUEST_CHANNEL, 5, 5)); + } + public static class ServerSocketRule extends AbstractSocketRule { private RSocket acceptingSocket; @@ -653,34 +764,31 @@ protected RSocketResponder newRSocket(LeaksTrackingByteBufAllocator allocator) { } private void sendRequest(int streamId, FrameType frameType) { + sendRequest(streamId, frameType, EmptyPayload.INSTANCE); + } + + private void sendRequest(int streamId, FrameType frameType, Payload payload) { ByteBuf request; switch (frameType) { case REQUEST_CHANNEL: request = - RequestChannelFrameFlyweight.encode( - allocator, - streamId, - false, - false, - prefetch, - Unpooled.EMPTY_BUFFER, - Unpooled.EMPTY_BUFFER); + RequestChannelFrameFlyweight.encodeReleasingPayload( + allocator, streamId, false, prefetch, payload); break; case REQUEST_STREAM: request = - RequestStreamFrameFlyweight.encode( - allocator, - streamId, - false, - prefetch, - Unpooled.EMPTY_BUFFER, - Unpooled.EMPTY_BUFFER); + RequestStreamFrameFlyweight.encodeReleasingPayload( + allocator, streamId, prefetch, payload); break; case REQUEST_RESPONSE: request = - RequestResponseFrameFlyweight.encode( - allocator, streamId, false, Unpooled.EMPTY_BUFFER, Unpooled.EMPTY_BUFFER); + RequestResponseFrameFlyweight.encodeReleasingPayload(allocator, streamId, payload); + break; + case REQUEST_FNF: + request = + RequestFireAndForgetFrameFlyweight.encodeReleasingPayload( + allocator, streamId, payload); break; default: throw new IllegalArgumentException("unsupported type: " + frameType); diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java index f29f4409c..568f8eed6 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java @@ -183,231 +183,223 @@ public Flux requestChannel(Publisher payloads) { @Test public void requestChannelCase_StreamIsTerminatedAfterBothSidesSentCompletion1() { - TestPublisher outerPublisher = TestPublisher.create(); - AssertSubscriber outerAssertSubscriber = new AssertSubscriber<>(0); + TestPublisher requesterPublisher = TestPublisher.create(); + AssertSubscriber requesterSubscriber = new AssertSubscriber<>(0); - AssertSubscriber innerAssertSubscriber = new AssertSubscriber<>(0); - TestPublisher innerPublisher = TestPublisher.create(); + AssertSubscriber responderSubscriber = new AssertSubscriber<>(0); + TestPublisher responderPublisher = TestPublisher.create(); initRequestChannelCase( - outerPublisher, outerAssertSubscriber, innerPublisher, innerAssertSubscriber); + requesterPublisher, requesterSubscriber, responderPublisher, responderSubscriber); - nextFromOuterPublisher(outerPublisher, innerAssertSubscriber); + nextFromRequesterPublisher(requesterPublisher, responderSubscriber); - completeFromOuterPublisher(outerPublisher, innerAssertSubscriber); + completeFromRequesterPublisher(requesterPublisher, responderSubscriber); - nextFromInnerPublisher(innerPublisher, outerAssertSubscriber); + nextFromResponderPublisher(responderPublisher, requesterSubscriber); - completeFromInnerPublisher(innerPublisher, outerAssertSubscriber); + completeFromResponderPublisher(responderPublisher, requesterSubscriber); } @Test public void requestChannelCase_StreamIsTerminatedAfterBothSidesSentCompletion2() { - TestPublisher outerPublisher = TestPublisher.create(); - AssertSubscriber outerAssertSubscriber = new AssertSubscriber<>(0); + TestPublisher requesterPublisher = TestPublisher.create(); + AssertSubscriber requesterSubscriber = new AssertSubscriber<>(0); - AssertSubscriber innerAssertSubscriber = new AssertSubscriber<>(0); - TestPublisher innerPublisher = TestPublisher.create(); + AssertSubscriber responderSubscriber = new AssertSubscriber<>(0); + TestPublisher responderPublisher = TestPublisher.create(); initRequestChannelCase( - outerPublisher, outerAssertSubscriber, innerPublisher, innerAssertSubscriber); + requesterPublisher, requesterSubscriber, responderPublisher, responderSubscriber); - nextFromInnerPublisher(innerPublisher, outerAssertSubscriber); + nextFromResponderPublisher(responderPublisher, requesterSubscriber); - completeFromInnerPublisher(innerPublisher, outerAssertSubscriber); + completeFromResponderPublisher(responderPublisher, requesterSubscriber); - nextFromOuterPublisher(outerPublisher, innerAssertSubscriber); + nextFromRequesterPublisher(requesterPublisher, responderSubscriber); - completeFromOuterPublisher(outerPublisher, innerAssertSubscriber); + completeFromRequesterPublisher(requesterPublisher, responderSubscriber); } @Test public void requestChannelCase_CancellationFromResponderShouldLeaveStreamInHalfClosedStateWithNextCompletionPossibleFromRequester() { - TestPublisher outerPublisher = TestPublisher.create(); - AssertSubscriber outerAssertSubscriber = new AssertSubscriber<>(0); + TestPublisher requesterPublisher = TestPublisher.create(); + AssertSubscriber requesterSubscriber = new AssertSubscriber<>(0); - AssertSubscriber innerAssertSubscriber = new AssertSubscriber<>(0); - TestPublisher innerPublisher = TestPublisher.create(); + AssertSubscriber responderSubscriber = new AssertSubscriber<>(0); + TestPublisher responderPublisher = TestPublisher.create(); initRequestChannelCase( - outerPublisher, outerAssertSubscriber, innerPublisher, innerAssertSubscriber); + requesterPublisher, requesterSubscriber, responderPublisher, responderSubscriber); - nextFromOuterPublisher(outerPublisher, innerAssertSubscriber); + nextFromRequesterPublisher(requesterPublisher, responderSubscriber); - cancelFromInnerSubscriber(outerPublisher, innerAssertSubscriber); + cancelFromResponderSubscriber(requesterPublisher, responderSubscriber); - nextFromInnerPublisher(innerPublisher, outerAssertSubscriber); + nextFromResponderPublisher(responderPublisher, requesterSubscriber); - completeFromInnerPublisher(innerPublisher, outerAssertSubscriber); + completeFromResponderPublisher(responderPublisher, requesterSubscriber); } @Test public void requestChannelCase_CompletionFromRequesterShouldLeaveStreamInHalfClosedStateWithNextCancellationPossibleFromResponder() { - TestPublisher outerPublisher = TestPublisher.create(); - AssertSubscriber outerAssertSubscriber = new AssertSubscriber<>(0); + TestPublisher requesterPublisher = TestPublisher.create(); + AssertSubscriber requesterSubscriber = new AssertSubscriber<>(0); - AssertSubscriber innerAssertSubscriber = new AssertSubscriber<>(0); - TestPublisher innerPublisher = TestPublisher.create(); + AssertSubscriber responderSubscriber = new AssertSubscriber<>(0); + TestPublisher responderPublisher = TestPublisher.create(); initRequestChannelCase( - outerPublisher, outerAssertSubscriber, innerPublisher, innerAssertSubscriber); + requesterPublisher, requesterSubscriber, responderPublisher, responderSubscriber); - nextFromInnerPublisher(innerPublisher, outerAssertSubscriber); + nextFromResponderPublisher(responderPublisher, requesterSubscriber); - completeFromInnerPublisher(innerPublisher, outerAssertSubscriber); + completeFromResponderPublisher(responderPublisher, requesterSubscriber); - nextFromOuterPublisher(outerPublisher, innerAssertSubscriber); + nextFromRequesterPublisher(requesterPublisher, responderSubscriber); - cancelFromInnerSubscriber(outerPublisher, innerAssertSubscriber); + cancelFromResponderSubscriber(requesterPublisher, responderSubscriber); } @Test public void requestChannelCase_ensureThatRequesterSubscriberCancellationTerminatesStreamsOnBothSides() { - TestPublisher outerPublisher = TestPublisher.create(); - AssertSubscriber outerAssertSubscriber = new AssertSubscriber<>(0); + TestPublisher requesterPublisher = TestPublisher.create(); + AssertSubscriber requesterSubscriber = new AssertSubscriber<>(0); - AssertSubscriber innerAssertSubscriber = new AssertSubscriber<>(0); - TestPublisher innerPublisher = TestPublisher.create(); + AssertSubscriber responderSubscriber = new AssertSubscriber<>(0); + TestPublisher responderPublisher = TestPublisher.create(); initRequestChannelCase( - outerPublisher, outerAssertSubscriber, innerPublisher, innerAssertSubscriber); + requesterPublisher, requesterSubscriber, responderPublisher, responderSubscriber); - nextFromInnerPublisher(innerPublisher, outerAssertSubscriber); + nextFromResponderPublisher(responderPublisher, requesterSubscriber); - nextFromOuterPublisher(outerPublisher, innerAssertSubscriber); + nextFromRequesterPublisher(requesterPublisher, responderSubscriber); // ensures both sides are terminated - cancelFromOuterSubscriber( - outerPublisher, outerAssertSubscriber, innerPublisher, innerAssertSubscriber); + cancelFromRequesterSubscriber( + requesterPublisher, requesterSubscriber, responderPublisher, responderSubscriber); } void initRequestChannelCase( - TestPublisher outerPublisher, - AssertSubscriber outerAssertSubscriber, - TestPublisher innerPublisher, - AssertSubscriber innerAssertSubscriber) { + TestPublisher requesterPublisher, + AssertSubscriber requesterSubscriber, + TestPublisher responderPublisher, + AssertSubscriber responderSubscriber) { rule.setRequestAcceptor( new AbstractRSocket() { @Override public Flux requestChannel(Publisher payloads) { - payloads.subscribe(innerAssertSubscriber); - return innerPublisher.flux(); + payloads.subscribe(responderSubscriber); + return responderPublisher.flux(); } }); - rule.crs.requestChannel(outerPublisher).subscribe(outerAssertSubscriber); + rule.crs.requestChannel(requesterPublisher).subscribe(requesterSubscriber); - outerPublisher.assertWasSubscribed(); - outerAssertSubscriber.assertSubscribed(); + requesterPublisher.assertWasSubscribed(); + requesterSubscriber.assertSubscribed(); - innerAssertSubscriber.assertNotSubscribed(); - innerPublisher.assertWasNotSubscribed(); + responderSubscriber.assertNotSubscribed(); + responderPublisher.assertWasNotSubscribed(); // firstRequest - outerAssertSubscriber.request(1); - outerPublisher.assertMaxRequested(1); - outerPublisher.next(DefaultPayload.create("initialData", "initialMetadata")); + requesterSubscriber.request(1); + requesterPublisher.assertMaxRequested(1); + requesterPublisher.next(DefaultPayload.create("initialData", "initialMetadata")); - innerAssertSubscriber.assertSubscribed(); - innerPublisher.assertWasSubscribed(); + responderSubscriber.assertSubscribed(); + responderPublisher.assertWasSubscribed(); } - void nextFromOuterPublisher( - TestPublisher outerPublisher, AssertSubscriber innerAssertSubscriber) { + void nextFromRequesterPublisher( + TestPublisher requesterPublisher, AssertSubscriber responderSubscriber) { // ensures that outerUpstream and innerSubscriber is not terminated so the requestChannel - outerPublisher.assertSubscribers(1); - innerAssertSubscriber.assertNotTerminated(); + requesterPublisher.assertSubscribers(1); + responderSubscriber.assertNotTerminated(); - innerAssertSubscriber.request(6); - outerPublisher.next( + responderSubscriber.request(6); + requesterPublisher.next( DefaultPayload.create("d1", "m1"), DefaultPayload.create("d2"), DefaultPayload.create("d3", "m3"), DefaultPayload.create("d4"), DefaultPayload.create("d5", "m5")); - List innerPayloads = innerAssertSubscriber.awaitAndAssertNextValueCount(6).values(); + List innerPayloads = responderSubscriber.awaitAndAssertNextValueCount(6).values(); Assertions.assertThat(innerPayloads.stream().map(Payload::getDataUtf8)) .containsExactly("initialData", "d1", "d2", "d3", "d4", "d5"); - // fixme: incorrect behaviour of metadata encoding - // Assertions - // .assertThat(innerPayloads - // .stream() - // .map(Payload::hasMetadata) - // ) - // .containsExactly(true, true, false, true, false, true); + Assertions.assertThat(innerPayloads.stream().map(Payload::hasMetadata)) + .containsExactly(true, true, false, true, false, true); Assertions.assertThat(innerPayloads.stream().map(Payload::getMetadataUtf8)) .containsExactly("initialMetadata", "m1", "", "m3", "", "m5"); } - void completeFromOuterPublisher( - TestPublisher outerPublisher, AssertSubscriber innerAssertSubscriber) { + void completeFromRequesterPublisher( + TestPublisher requesterPublisher, AssertSubscriber responderSubscriber) { // ensures that after sending complete upstream part is closed - outerPublisher.complete(); - innerAssertSubscriber.assertTerminated(); - outerPublisher.assertNoSubscribers(); + requesterPublisher.complete(); + responderSubscriber.assertTerminated(); + requesterPublisher.assertNoSubscribers(); } - void cancelFromInnerSubscriber( - TestPublisher outerPublisher, AssertSubscriber innerAssertSubscriber) { + void cancelFromResponderSubscriber( + TestPublisher requesterPublisher, AssertSubscriber responderSubscriber) { // ensures that after sending complete upstream part is closed - innerAssertSubscriber.cancel(); - outerPublisher.assertWasCancelled(); - outerPublisher.assertNoSubscribers(); + responderSubscriber.cancel(); + requesterPublisher.assertWasCancelled(); + requesterPublisher.assertNoSubscribers(); } - void nextFromInnerPublisher( - TestPublisher innerPublisher, AssertSubscriber outerAssertSubscriber) { + void nextFromResponderPublisher( + TestPublisher responderPublisher, AssertSubscriber requesterSubscriber) { // ensures that downstream is not terminated so the requestChannel state is half-closed - innerPublisher.assertSubscribers(1); - outerAssertSubscriber.assertNotTerminated(); + responderPublisher.assertSubscribers(1); + requesterSubscriber.assertNotTerminated(); - // ensures innerPublisher can send messages and outerSubscriber can receive them - outerAssertSubscriber.request(5); - innerPublisher.next( + // ensures responderPublisher can send messages and outerSubscriber can receive them + requesterSubscriber.request(5); + responderPublisher.next( DefaultPayload.create("rd1", "rm1"), DefaultPayload.create("rd2"), DefaultPayload.create("rd3", "rm3"), DefaultPayload.create("rd4"), DefaultPayload.create("rd5", "rm5")); - List outerPayloads = outerAssertSubscriber.awaitAndAssertNextValueCount(5).values(); + List outerPayloads = requesterSubscriber.awaitAndAssertNextValueCount(5).values(); Assertions.assertThat(outerPayloads.stream().map(Payload::getDataUtf8)) .containsExactly("rd1", "rd2", "rd3", "rd4", "rd5"); - // fixme: incorrect behaviour of metadata encoding - // Assertions - // .assertThat(outerPayloads - // .stream() - // .map(Payload::hasMetadata) - // ) - // .containsExactly(true, false, true, false, true); + Assertions.assertThat(outerPayloads.stream().map(Payload::hasMetadata)) + .containsExactly(true, false, true, false, true); Assertions.assertThat(outerPayloads.stream().map(Payload::getMetadataUtf8)) .containsExactly("rm1", "", "rm3", "", "rm5"); } - void completeFromInnerPublisher( - TestPublisher innerPublisher, AssertSubscriber outerAssertSubscriber) { + void completeFromResponderPublisher( + TestPublisher responderPublisher, AssertSubscriber requesterSubscriber) { // ensures that after sending complete inner upstream is closed - innerPublisher.complete(); - outerAssertSubscriber.assertTerminated(); - innerPublisher.assertNoSubscribers(); + responderPublisher.complete(); + requesterSubscriber.assertTerminated(); + responderPublisher.assertNoSubscribers(); } - void cancelFromOuterSubscriber( - TestPublisher outerPublisher, - AssertSubscriber outerAssertSubscriber, - TestPublisher innerPublisher, - AssertSubscriber innerAssertSubscriber) { + void cancelFromRequesterSubscriber( + TestPublisher requesterPublisher, + AssertSubscriber requesterSubscriber, + TestPublisher responderPublisher, + AssertSubscriber responderSubscriber) { // ensures that after sending cancel the whole requestChannel is terminated - outerAssertSubscriber.cancel(); - innerPublisher.assertWasCancelled(); - innerPublisher.assertNoSubscribers(); + requesterSubscriber.cancel(); + // error should be propagated + responderSubscriber.assertTerminated(); + responderPublisher.assertWasCancelled(); + responderPublisher.assertNoSubscribers(); // ensures that cancellation is propagated to the actual upstream - outerPublisher.assertWasCancelled(); - outerPublisher.assertNoSubscribers(); + requesterPublisher.assertWasCancelled(); + requesterPublisher.assertNoSubscribers(); } public static class SocketRule extends ExternalResource { diff --git a/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationIntegrationTest.java b/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationIntegrationTest.java index 984207936..a8569ef3b 100644 --- a/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationIntegrationTest.java +++ b/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationIntegrationTest.java @@ -28,7 +28,8 @@ public class FragmentationIntegrationTest { @Test void fragmentAndReassembleData() { ByteBuf frame = - PayloadFrameFlyweight.encodeNextComplete(allocator, 2, DefaultPayload.create(data)); + PayloadFrameFlyweight.encodeNextCompleteReleasingPayload( + allocator, 2, DefaultPayload.create(data)); System.out.println(FrameUtil.toString(frame)); frame.retain(); diff --git a/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameFragmenterTest.java b/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameFragmenterTest.java index f5a013357..c6b1735e6 100644 --- a/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameFragmenterTest.java +++ b/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameFragmenterTest.java @@ -20,7 +20,6 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import io.rsocket.frame.*; -import io.rsocket.util.DefaultPayload; import java.util.concurrent.ThreadLocalRandom; import org.junit.Assert; import org.junit.jupiter.api.DisplayName; @@ -43,14 +42,17 @@ final class FrameFragmenterTest { @Test void testGettingData() { ByteBuf rr = - RequestResponseFrameFlyweight.encode(allocator, 1, true, DefaultPayload.create(data)); + RequestResponseFrameFlyweight.encode( + allocator, 1, true, null, Unpooled.wrappedBuffer(data)); ByteBuf fnf = - RequestFireAndForgetFrameFlyweight.encode(allocator, 1, true, DefaultPayload.create(data)); + RequestFireAndForgetFrameFlyweight.encode( + allocator, 1, true, null, Unpooled.wrappedBuffer(data)); ByteBuf rs = - RequestStreamFrameFlyweight.encode(allocator, 1, true, 1, DefaultPayload.create(data)); + RequestStreamFrameFlyweight.encode( + allocator, 1, true, 1, null, Unpooled.wrappedBuffer(data)); ByteBuf rc = RequestChannelFrameFlyweight.encode( - allocator, 1, true, false, 1, DefaultPayload.create(data)); + allocator, 1, true, false, 1, null, Unpooled.wrappedBuffer(data)); ByteBuf data = FrameFragmenter.getData(rr, FrameType.REQUEST_RESPONSE); Assert.assertEquals(data, Unpooled.wrappedBuffer(data)); @@ -73,16 +75,22 @@ void testGettingData() { void testGettingMetadata() { ByteBuf rr = RequestResponseFrameFlyweight.encode( - allocator, 1, true, DefaultPayload.create(data, metadata)); + allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.wrappedBuffer(data)); ByteBuf fnf = RequestFireAndForgetFrameFlyweight.encode( - allocator, 1, true, DefaultPayload.create(data, metadata)); + allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.wrappedBuffer(data)); ByteBuf rs = RequestStreamFrameFlyweight.encode( - allocator, 1, true, 1, DefaultPayload.create(data, metadata)); + allocator, 1, true, 1, Unpooled.wrappedBuffer(metadata), Unpooled.wrappedBuffer(data)); ByteBuf rc = RequestChannelFrameFlyweight.encode( - allocator, 1, true, false, 1, DefaultPayload.create(data, metadata)); + allocator, + 1, + true, + false, + 1, + Unpooled.wrappedBuffer(metadata), + Unpooled.wrappedBuffer(data)); ByteBuf data = FrameFragmenter.getMetadata(rr, FrameType.REQUEST_RESPONSE); Assert.assertEquals(data, Unpooled.wrappedBuffer(metadata)); @@ -104,7 +112,8 @@ void testGettingMetadata() { @Test void returnEmptBufferWhenNoMetadataPresent() { ByteBuf rr = - RequestResponseFrameFlyweight.encode(allocator, 1, true, DefaultPayload.create(data)); + RequestResponseFrameFlyweight.encode( + allocator, 1, true, null, Unpooled.wrappedBuffer(data)); ByteBuf data = FrameFragmenter.getMetadata(rr, FrameType.REQUEST_RESPONSE); Assert.assertEquals(data, Unpooled.EMPTY_BUFFER); @@ -115,7 +124,8 @@ void returnEmptBufferWhenNoMetadataPresent() { @Test void encodeFirstFrameWithData() { ByteBuf rr = - RequestResponseFrameFlyweight.encode(allocator, 1, true, DefaultPayload.create(data)); + RequestResponseFrameFlyweight.encode( + allocator, 1, true, null, Unpooled.wrappedBuffer(data)); ByteBuf fragment = FrameFragmenter.encodeFirstFragment( @@ -144,7 +154,7 @@ void encodeFirstFrameWithData() { void encodeFirstWithDataChannel() { ByteBuf rc = RequestChannelFrameFlyweight.encode( - allocator, 1, true, false, 10, DefaultPayload.create(data)); + allocator, 1, true, false, 10, null, Unpooled.wrappedBuffer(data)); ByteBuf fragment = FrameFragmenter.encodeFirstFragment( @@ -173,7 +183,8 @@ void encodeFirstWithDataChannel() { @Test void encodeFirstWithDataStream() { ByteBuf rc = - RequestStreamFrameFlyweight.encode(allocator, 1, true, 50, DefaultPayload.create(data)); + RequestStreamFrameFlyweight.encode( + allocator, 1, true, 50, null, Unpooled.wrappedBuffer(data)); ByteBuf fragment = FrameFragmenter.encodeFirstFragment( @@ -203,10 +214,7 @@ void encodeFirstWithDataStream() { void encodeFirstFrameWithMetadata() { ByteBuf rr = RequestResponseFrameFlyweight.encode( - allocator, - 1, - true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))); + allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER); ByteBuf fragment = FrameFragmenter.encodeFirstFragment( @@ -234,7 +242,7 @@ void encodeFirstFrameWithMetadata() { void encodeFirstWithDataAndMetadataStream() { ByteBuf rc = RequestStreamFrameFlyweight.encode( - allocator, 1, true, 50, DefaultPayload.create(data, metadata)); + allocator, 1, true, 50, Unpooled.wrappedBuffer(metadata), Unpooled.wrappedBuffer(data)); ByteBuf fragment = FrameFragmenter.encodeFirstFragment( @@ -266,7 +274,8 @@ void encodeFirstWithDataAndMetadataStream() { @Test void fragmentData() { ByteBuf rr = - RequestResponseFrameFlyweight.encode(allocator, 1, true, DefaultPayload.create(data)); + RequestResponseFrameFlyweight.encode( + allocator, 1, true, null, Unpooled.wrappedBuffer(data)); Publisher fragments = FrameFragmenter.fragmentFrame(allocator, 1024, rr, FrameType.REQUEST_RESPONSE, false); @@ -293,11 +302,7 @@ void fragmentData() { void fragmentMetadata() { ByteBuf rr = RequestStreamFrameFlyweight.encode( - allocator, - 1, - true, - 10, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))); + allocator, 1, true, 10, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER); Publisher fragments = FrameFragmenter.fragmentFrame(allocator, 1024, rr, FrameType.REQUEST_STREAM, false); @@ -324,7 +329,7 @@ void fragmentMetadata() { void fragmentDataAndMetadata() { ByteBuf rr = RequestResponseFrameFlyweight.encode( - allocator, 1, true, DefaultPayload.create(data, metadata)); + allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.wrappedBuffer(data)); Publisher fragments = FrameFragmenter.fragmentFrame(allocator, 1024, rr, FrameType.REQUEST_RESPONSE, false); diff --git a/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameReassemblerTest.java b/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameReassemblerTest.java index 6e0d0dc1b..13632165b 100644 --- a/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameReassemblerTest.java +++ b/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameReassemblerTest.java @@ -22,7 +22,6 @@ import io.netty.buffer.Unpooled; import io.netty.util.ReferenceCountUtil; import io.rsocket.frame.*; -import io.rsocket.util.DefaultPayload; import java.util.Arrays; import java.util.List; import java.util.concurrent.ThreadLocalRandom; @@ -48,15 +47,16 @@ final class FrameReassemblerTest { void reassembleData() { List byteBufs = Arrays.asList( - RequestResponseFrameFlyweight.encode(allocator, 1, true, DefaultPayload.create(data)), + RequestResponseFrameFlyweight.encode( + allocator, 1, true, null, Unpooled.wrappedBuffer(data)), PayloadFrameFlyweight.encode( - allocator, 1, true, false, true, DefaultPayload.create(data)), + allocator, 1, true, false, true, null, Unpooled.wrappedBuffer(data)), PayloadFrameFlyweight.encode( - allocator, 1, true, false, true, DefaultPayload.create(data)), + allocator, 1, true, false, true, null, Unpooled.wrappedBuffer(data)), PayloadFrameFlyweight.encode( - allocator, 1, true, false, true, DefaultPayload.create(data)), + allocator, 1, true, false, true, null, Unpooled.wrappedBuffer(data)), PayloadFrameFlyweight.encode( - allocator, 1, false, false, true, DefaultPayload.create(data))); + allocator, 1, false, false, true, null, Unpooled.wrappedBuffer(data))); FrameReassembler reassembler = new FrameReassembler(allocator); @@ -88,7 +88,8 @@ void reassembleData() { void passthrough() { List byteBufs = Arrays.asList( - RequestResponseFrameFlyweight.encode(allocator, 1, false, DefaultPayload.create(data))); + RequestResponseFrameFlyweight.encode( + allocator, 1, false, null, Unpooled.wrappedBuffer(data))); FrameReassembler reassembler = new FrameReassembler(allocator); @@ -115,38 +116,39 @@ void reassembleMetadata() { List byteBufs = Arrays.asList( RequestResponseFrameFlyweight.encode( - allocator, - 1, - true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, false, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata)))); + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER)); FrameReassembler reassembler = new FrameReassembler(allocator); @@ -184,35 +186,40 @@ void reassembleMetadataChannel() { true, false, 100, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, false, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata)))); + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER)); FrameReassembler reassembler = new FrameReassembler(allocator); @@ -249,39 +256,39 @@ void reassembleMetadataStream() { List byteBufs = Arrays.asList( RequestStreamFrameFlyweight.encode( - allocator, - 1, - true, - 250, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + allocator, 1, true, 250, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, false, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata)))); + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER)); FrameReassembler reassembler = new FrameReassembler(allocator); @@ -319,34 +326,33 @@ void reassembleMetadataAndData() { List byteBufs = Arrays.asList( RequestResponseFrameFlyweight.encode( - allocator, - 1, - true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create( - Unpooled.wrappedBuffer(data), Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.wrappedBuffer(data)), PayloadFrameFlyweight.encode( - allocator, 1, false, false, true, DefaultPayload.create(data))); + allocator, 1, false, false, true, null, Unpooled.wrappedBuffer(data))); FrameReassembler reassembler = new FrameReassembler(allocator); @@ -387,32 +393,31 @@ public void cancelBeforeAssembling() { List byteBufs = Arrays.asList( RequestResponseFrameFlyweight.encode( - allocator, - 1, - true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create( - Unpooled.wrappedBuffer(data), Unpooled.wrappedBuffer(metadata)))); + Unpooled.wrappedBuffer(metadata), + Unpooled.wrappedBuffer(data))); FrameReassembler reassembler = new FrameReassembler(allocator); Flux.fromIterable(byteBufs).handle(reassembler::reassembleFrame).blockLast(); @@ -436,32 +441,31 @@ public void dispose() { List byteBufs = Arrays.asList( RequestResponseFrameFlyweight.encode( - allocator, - 1, - true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create( - Unpooled.wrappedBuffer(data), Unpooled.wrappedBuffer(metadata)))); + Unpooled.wrappedBuffer(metadata), + Unpooled.wrappedBuffer(data))); FrameReassembler reassembler = new FrameReassembler(allocator); Flux.fromIterable(byteBufs).handle(reassembler::reassembleFrame).blockLast(); diff --git a/rsocket-core/src/test/java/io/rsocket/fragmentation/ReassembleDuplexConnectionTest.java b/rsocket-core/src/test/java/io/rsocket/fragmentation/ReassembleDuplexConnectionTest.java index b21a7c9da..c5abce339 100644 --- a/rsocket-core/src/test/java/io/rsocket/fragmentation/ReassembleDuplexConnectionTest.java +++ b/rsocket-core/src/test/java/io/rsocket/fragmentation/ReassembleDuplexConnectionTest.java @@ -30,7 +30,6 @@ import io.rsocket.frame.FrameType; import io.rsocket.frame.PayloadFrameFlyweight; import io.rsocket.frame.RequestResponseFrameFlyweight; -import io.rsocket.util.DefaultPayload; import java.util.Arrays; import java.util.List; import java.util.concurrent.ThreadLocalRandom; @@ -59,15 +58,16 @@ final class ReassembleDuplexConnectionTest { void reassembleData() { List byteBufs = Arrays.asList( - RequestResponseFrameFlyweight.encode(allocator, 1, true, DefaultPayload.create(data)), + RequestResponseFrameFlyweight.encode( + allocator, 1, true, null, Unpooled.wrappedBuffer(data)), PayloadFrameFlyweight.encode( - allocator, 1, true, false, true, DefaultPayload.create(data)), + allocator, 1, true, false, true, null, Unpooled.wrappedBuffer(data)), PayloadFrameFlyweight.encode( - allocator, 1, true, false, true, DefaultPayload.create(data)), + allocator, 1, true, false, true, null, Unpooled.wrappedBuffer(data)), PayloadFrameFlyweight.encode( - allocator, 1, true, false, true, DefaultPayload.create(data)), + allocator, 1, true, false, true, null, Unpooled.wrappedBuffer(data)), PayloadFrameFlyweight.encode( - allocator, 1, false, false, true, DefaultPayload.create(data))); + allocator, 1, false, false, true, null, Unpooled.wrappedBuffer(data))); CompositeByteBuf data = allocator @@ -99,38 +99,39 @@ void reassembleMetadata() { List byteBufs = Arrays.asList( RequestResponseFrameFlyweight.encode( - allocator, - 1, - true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, false, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata)))); + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER)); CompositeByteBuf metadata = allocator @@ -164,34 +165,33 @@ void reassembleMetadataAndData() { List byteBufs = Arrays.asList( RequestResponseFrameFlyweight.encode( - allocator, - 1, - true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create(Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.EMPTY_BUFFER), PayloadFrameFlyweight.encode( allocator, 1, true, false, true, - DefaultPayload.create( - Unpooled.wrappedBuffer(data), Unpooled.wrappedBuffer(metadata))), + Unpooled.wrappedBuffer(metadata), + Unpooled.wrappedBuffer(data)), PayloadFrameFlyweight.encode( - allocator, 1, false, false, true, DefaultPayload.create(data))); + allocator, 1, false, false, true, null, Unpooled.wrappedBuffer(data))); CompositeByteBuf data = allocator @@ -230,7 +230,7 @@ void reassembleMetadataAndData() { void reassembleNonFragment() { ByteBuf encode = RequestResponseFrameFlyweight.encode( - allocator, 1, false, DefaultPayload.create(Unpooled.wrappedBuffer(data))); + allocator, 1, false, null, Unpooled.wrappedBuffer(data)); when(delegate.receive()).thenReturn(Flux.just(encode)); when(delegate.onClose()).thenReturn(Mono.never()); diff --git a/rsocket-core/src/test/java/io/rsocket/frame/DataAndMetadataFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/frame/DataAndMetadataFlyweightTest.java deleted file mode 100644 index 6f9113d73..000000000 --- a/rsocket-core/src/test/java/io/rsocket/frame/DataAndMetadataFlyweightTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.rsocket.frame; - -import io.netty.buffer.*; -import org.junit.jupiter.api.Test; - -class DataAndMetadataFlyweightTest { - @Test - void testEncodeData() { - ByteBuf header = FrameHeaderFlyweight.encode(ByteBufAllocator.DEFAULT, 1, FrameType.PAYLOAD, 0); - ByteBuf data = ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, "_I'm data_"); - ByteBuf frame = DataAndMetadataFlyweight.encodeOnlyData(ByteBufAllocator.DEFAULT, header, data); - ByteBuf d = DataAndMetadataFlyweight.data(frame, false); - String s = ByteBufUtil.prettyHexDump(d); - System.out.println(s); - } - - @Test - void testEncodeMetadata() { - ByteBuf header = FrameHeaderFlyweight.encode(ByteBufAllocator.DEFAULT, 1, FrameType.PAYLOAD, 0); - ByteBuf data = ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, "_I'm metadata_"); - ByteBuf frame = - DataAndMetadataFlyweight.encodeOnlyMetadata(ByteBufAllocator.DEFAULT, header, data); - ByteBuf d = DataAndMetadataFlyweight.data(frame, false); - String s = ByteBufUtil.prettyHexDump(d); - System.out.println(s); - } - - @Test - void testEncodeDataAndMetadata() { - ByteBuf header = - FrameHeaderFlyweight.encode(ByteBufAllocator.DEFAULT, 1, FrameType.REQUEST_RESPONSE, 0); - ByteBuf data = ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, "_I'm data_"); - ByteBuf metadata = ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, "_I'm metadata_"); - ByteBuf frame = - DataAndMetadataFlyweight.encode(ByteBufAllocator.DEFAULT, header, metadata, data); - ByteBuf m = DataAndMetadataFlyweight.metadata(frame, true); - String s = ByteBufUtil.prettyHexDump(m); - System.out.println(s); - FrameType frameType = FrameHeaderFlyweight.frameType(frame); - System.out.println(frameType); - - for (int i = 0; i < 10_000_000; i++) { - ByteBuf d1 = ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, "_I'm data_"); - ByteBuf m1 = ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, "_I'm metadata_"); - ByteBuf h1 = - FrameHeaderFlyweight.encode(ByteBufAllocator.DEFAULT, 1, FrameType.REQUEST_RESPONSE, 0); - ByteBuf f1 = DataAndMetadataFlyweight.encode(ByteBufAllocator.DEFAULT, h1, m1, d1); - f1.release(); - } - } -} diff --git a/rsocket-core/src/test/java/io/rsocket/frame/ExtensionFrameFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/frame/ExtensionFrameFlyweightTest.java index e337d4332..eea72c03e 100644 --- a/rsocket-core/src/test/java/io/rsocket/frame/ExtensionFrameFlyweightTest.java +++ b/rsocket-core/src/test/java/io/rsocket/frame/ExtensionFrameFlyweightTest.java @@ -35,7 +35,7 @@ void extensionData() { Assertions.assertFalse(FrameHeaderFlyweight.hasMetadata(extension)); Assertions.assertEquals(extendedType, ExtensionFrameFlyweight.extendedType(extension)); - Assertions.assertEquals(0, ExtensionFrameFlyweight.metadata(extension).readableBytes()); + Assertions.assertNull(ExtensionFrameFlyweight.metadata(extension)); Assertions.assertEquals(data, ExtensionFrameFlyweight.data(extension)); extension.release(); } diff --git a/rsocket-core/src/test/java/io/rsocket/frame/PayloadFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/frame/PayloadFlyweightTest.java index 9ef89326a..439d23c15 100644 --- a/rsocket-core/src/test/java/io/rsocket/frame/PayloadFlyweightTest.java +++ b/rsocket-core/src/test/java/io/rsocket/frame/PayloadFlyweightTest.java @@ -15,7 +15,8 @@ public class PayloadFlyweightTest { void nextCompleteDataMetadata() { Payload payload = DefaultPayload.create("d", "md"); ByteBuf nextComplete = - PayloadFrameFlyweight.encodeNextComplete(ByteBufAllocator.DEFAULT, 1, payload); + PayloadFrameFlyweight.encodeNextCompleteReleasingPayload( + ByteBufAllocator.DEFAULT, 1, payload); String data = PayloadFrameFlyweight.data(nextComplete).toString(StandardCharsets.UTF_8); String metadata = PayloadFrameFlyweight.metadata(nextComplete).toString(StandardCharsets.UTF_8); Assertions.assertEquals("d", data); @@ -27,11 +28,12 @@ void nextCompleteDataMetadata() { void nextCompleteData() { Payload payload = DefaultPayload.create("d"); ByteBuf nextComplete = - PayloadFrameFlyweight.encodeNextComplete(ByteBufAllocator.DEFAULT, 1, payload); + PayloadFrameFlyweight.encodeNextCompleteReleasingPayload( + ByteBufAllocator.DEFAULT, 1, payload); String data = PayloadFrameFlyweight.data(nextComplete).toString(StandardCharsets.UTF_8); ByteBuf metadata = PayloadFrameFlyweight.metadata(nextComplete); Assertions.assertEquals("d", data); - Assertions.assertTrue(metadata.readableBytes() == 0); + Assertions.assertNull(metadata); nextComplete.release(); } @@ -42,7 +44,8 @@ void nextCompleteMetaData() { Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer("md".getBytes(StandardCharsets.UTF_8))); ByteBuf nextComplete = - PayloadFrameFlyweight.encodeNextComplete(ByteBufAllocator.DEFAULT, 1, payload); + PayloadFrameFlyweight.encodeNextCompleteReleasingPayload( + ByteBufAllocator.DEFAULT, 1, payload); ByteBuf data = PayloadFrameFlyweight.data(nextComplete); String metadata = PayloadFrameFlyweight.metadata(nextComplete).toString(StandardCharsets.UTF_8); Assertions.assertTrue(data.readableBytes() == 0); @@ -53,7 +56,8 @@ void nextCompleteMetaData() { @Test void nextDataMetadata() { Payload payload = DefaultPayload.create("d", "md"); - ByteBuf next = PayloadFrameFlyweight.encodeNext(ByteBufAllocator.DEFAULT, 1, payload); + ByteBuf next = + PayloadFrameFlyweight.encodeNextReleasingPayload(ByteBufAllocator.DEFAULT, 1, payload); String data = PayloadFrameFlyweight.data(next).toString(StandardCharsets.UTF_8); String metadata = PayloadFrameFlyweight.metadata(next).toString(StandardCharsets.UTF_8); Assertions.assertEquals("d", data); @@ -64,11 +68,24 @@ void nextDataMetadata() { @Test void nextData() { Payload payload = DefaultPayload.create("d"); - ByteBuf next = PayloadFrameFlyweight.encodeNext(ByteBufAllocator.DEFAULT, 1, payload); + ByteBuf next = + PayloadFrameFlyweight.encodeNextReleasingPayload(ByteBufAllocator.DEFAULT, 1, payload); String data = PayloadFrameFlyweight.data(next).toString(StandardCharsets.UTF_8); ByteBuf metadata = PayloadFrameFlyweight.metadata(next); Assertions.assertEquals("d", data); - Assertions.assertTrue(metadata.readableBytes() == 0); + Assertions.assertNull(metadata); + next.release(); + } + + @Test + void nextDataEmptyMetadata() { + Payload payload = DefaultPayload.create("d".getBytes(), new byte[0]); + ByteBuf next = + PayloadFrameFlyweight.encodeNextReleasingPayload(ByteBufAllocator.DEFAULT, 1, payload); + String data = PayloadFrameFlyweight.data(next).toString(StandardCharsets.UTF_8); + ByteBuf metadata = PayloadFrameFlyweight.metadata(next); + Assertions.assertEquals("d", data); + Assertions.assertEquals(metadata.readableBytes(), 0); next.release(); } } diff --git a/rsocket-core/src/test/java/io/rsocket/frame/RequestFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/frame/RequestFlyweightTest.java index 9acec2c81..c19d4e1f4 100644 --- a/rsocket-core/src/test/java/io/rsocket/frame/RequestFlyweightTest.java +++ b/rsocket-core/src/test/java/io/rsocket/frame/RequestFlyweightTest.java @@ -22,8 +22,15 @@ void testEncoding() { Unpooled.copiedBuffer("d", StandardCharsets.UTF_8)); frame = FrameLengthFlyweight.encode(ByteBufAllocator.DEFAULT, frame.readableBytes(), frame); - - assertEquals("000010000000011900000000010000026d6464", ByteBufUtil.hexDump(frame)); + // Encoded FrameLength⌍ ⌌ Encoded Headers + // | | ⌌ Encoded Request(1) + // | | | ⌌Encoded Metadata Length + // | | | | ⌌Encoded Metadata + // | | | | | ⌌Encoded Data + // __|________|_________|______|____|___| + // ↓ ↓↓ ↓↓ ↓↓ ↓↓ ↓↓↓ + String expected = "000010000000011900000000010000026d6464"; + assertEquals(expected, ByteBufUtil.hexDump(frame)); frame.release(); } @@ -39,8 +46,14 @@ void testEncodingWithEmptyMetadata() { Unpooled.copiedBuffer("d", StandardCharsets.UTF_8)); frame = FrameLengthFlyweight.encode(ByteBufAllocator.DEFAULT, frame.readableBytes(), frame); - - assertEquals("00000e0000000119000000000100000064", ByteBufUtil.hexDump(frame)); + // Encoded FrameLength⌍ ⌌ Encoded Headers + // | | ⌌ Encoded Request(1) + // | | | ⌌Encoded Metadata Length (0) + // | | | | ⌌Encoded Data + // __|________|_________|_______|___| + // ↓ ↓↓ ↓↓ ↓↓ ↓↓↓ + String expected = "00000e0000000119000000000100000064"; + assertEquals(expected, ByteBufUtil.hexDump(frame)); frame.release(); } @@ -57,7 +70,13 @@ void testEncodingWithNullMetadata() { frame = FrameLengthFlyweight.encode(ByteBufAllocator.DEFAULT, frame.readableBytes(), frame); - assertEquals("00000b0000000118000000000164", ByteBufUtil.hexDump(frame)); + // Encoded FrameLength⌍ ⌌ Encoded Headers + // | | ⌌ Encoded Request(1) + // | | | ⌌Encoded Data + // __|________|_________|_____| + // ↓<-> ↓↓ <-> ↓↓ <-> ↓↓↓ + String expected = "00000b0000000118000000000164"; + assertEquals(expected, ByteBufUtil.hexDump(frame)); frame.release(); } @@ -96,7 +115,7 @@ void requestResponseData() { assertFalse(FrameHeaderFlyweight.hasMetadata(request)); assertEquals("d", data); - assertTrue(metadata.readableBytes() == 0); + assertNull(metadata); request.release(); } @@ -131,13 +150,13 @@ void requestStreamDataMetadata() { Unpooled.copiedBuffer("md", StandardCharsets.UTF_8), Unpooled.copiedBuffer("d", StandardCharsets.UTF_8)); - int actualRequest = RequestStreamFrameFlyweight.initialRequestN(request); + long actualRequest = RequestStreamFrameFlyweight.initialRequestN(request); String data = RequestStreamFrameFlyweight.data(request).toString(StandardCharsets.UTF_8); String metadata = RequestStreamFrameFlyweight.metadata(request).toString(StandardCharsets.UTF_8); assertTrue(FrameHeaderFlyweight.hasMetadata(request)); - assertEquals(Integer.MAX_VALUE, actualRequest); + assertEquals(Long.MAX_VALUE, actualRequest); assertEquals("md", metadata); assertEquals("d", data); request.release(); @@ -154,13 +173,13 @@ void requestStreamData() { null, Unpooled.copiedBuffer("d", StandardCharsets.UTF_8)); - int actualRequest = RequestStreamFrameFlyweight.initialRequestN(request); + long actualRequest = RequestStreamFrameFlyweight.initialRequestN(request); String data = RequestStreamFrameFlyweight.data(request).toString(StandardCharsets.UTF_8); ByteBuf metadata = RequestStreamFrameFlyweight.metadata(request); assertFalse(FrameHeaderFlyweight.hasMetadata(request)); - assertEquals(42, actualRequest); - assertTrue(metadata.readableBytes() == 0); + assertEquals(42L, actualRequest); + assertNull(metadata); assertEquals("d", data); request.release(); } @@ -176,13 +195,13 @@ void requestStreamMetadata() { Unpooled.copiedBuffer("md", StandardCharsets.UTF_8), Unpooled.EMPTY_BUFFER); - int actualRequest = RequestStreamFrameFlyweight.initialRequestN(request); + long actualRequest = RequestStreamFrameFlyweight.initialRequestN(request); ByteBuf data = RequestStreamFrameFlyweight.data(request); String metadata = RequestStreamFrameFlyweight.metadata(request).toString(StandardCharsets.UTF_8); assertTrue(FrameHeaderFlyweight.hasMetadata(request)); - assertEquals(42, actualRequest); + assertEquals(42L, actualRequest); assertTrue(data.readableBytes() == 0); assertEquals("md", metadata); request.release(); @@ -223,7 +242,7 @@ void requestFnfData() { assertFalse(FrameHeaderFlyweight.hasMetadata(request)); assertEquals("d", data); - assertTrue(metadata.readableBytes() == 0); + assertNull(metadata); request.release(); } diff --git a/rsocket-core/src/test/java/io/rsocket/util/ByteBufPayloadTest.java b/rsocket-core/src/test/java/io/rsocket/util/ByteBufPayloadTest.java new file mode 100644 index 000000000..2ad944d09 --- /dev/null +++ b/rsocket-core/src/test/java/io/rsocket/util/ByteBufPayloadTest.java @@ -0,0 +1,64 @@ +package io.rsocket.util; + +import io.netty.buffer.Unpooled; +import io.netty.util.IllegalReferenceCountException; +import io.rsocket.Payload; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +public class ByteBufPayloadTest { + + @Test + public void shouldIndicateThatItHasMetadata() { + Payload payload = ByteBufPayload.create("data", "metadata"); + + Assertions.assertThat(payload.hasMetadata()).isTrue(); + Assertions.assertThat(payload.release()).isTrue(); + } + + @Test + public void shouldIndicateThatItHasNotMetadata() { + Payload payload = ByteBufPayload.create("data"); + + Assertions.assertThat(payload.hasMetadata()).isFalse(); + Assertions.assertThat(payload.release()).isTrue(); + } + + @Test + public void shouldIndicateThatItHasMetadata1() { + Payload payload = + ByteBufPayload.create(Unpooled.wrappedBuffer("data".getBytes()), Unpooled.EMPTY_BUFFER); + + Assertions.assertThat(payload.hasMetadata()).isTrue(); + Assertions.assertThat(payload.release()).isTrue(); + } + + @Test + public void shouldThrowExceptionIfAccessAfterRelease() { + Payload payload = ByteBufPayload.create("data", "metadata"); + + Assertions.assertThat(payload.release()).isTrue(); + + Assertions.assertThatThrownBy(payload::hasMetadata) + .isInstanceOf(IllegalReferenceCountException.class); + Assertions.assertThatThrownBy(payload::data).isInstanceOf(IllegalReferenceCountException.class); + Assertions.assertThatThrownBy(payload::metadata) + .isInstanceOf(IllegalReferenceCountException.class); + Assertions.assertThatThrownBy(payload::sliceData) + .isInstanceOf(IllegalReferenceCountException.class); + Assertions.assertThatThrownBy(payload::sliceMetadata) + .isInstanceOf(IllegalReferenceCountException.class); + Assertions.assertThatThrownBy(payload::touch) + .isInstanceOf(IllegalReferenceCountException.class); + Assertions.assertThatThrownBy(() -> payload.touch("test")) + .isInstanceOf(IllegalReferenceCountException.class); + Assertions.assertThatThrownBy(payload::getData) + .isInstanceOf(IllegalReferenceCountException.class); + Assertions.assertThatThrownBy(payload::getMetadata) + .isInstanceOf(IllegalReferenceCountException.class); + Assertions.assertThatThrownBy(payload::getDataUtf8) + .isInstanceOf(IllegalReferenceCountException.class); + Assertions.assertThatThrownBy(payload::getMetadataUtf8) + .isInstanceOf(IllegalReferenceCountException.class); + } +} diff --git a/rsocket-core/src/test/java/io/rsocket/util/DefaultPayloadTest.java b/rsocket-core/src/test/java/io/rsocket/util/DefaultPayloadTest.java index 45ee4eacb..6bae0886b 100644 --- a/rsocket-core/src/test/java/io/rsocket/util/DefaultPayloadTest.java +++ b/rsocket-core/src/test/java/io/rsocket/util/DefaultPayloadTest.java @@ -16,10 +16,13 @@ package io.rsocket.util; -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import io.netty.buffer.Unpooled; import io.rsocket.Payload; +import java.nio.ByteBuffer; +import org.assertj.core.api.Assertions; import org.junit.Test; public class DefaultPayloadTest { @@ -48,4 +51,27 @@ public void staticMethods() { assertDataAndMetadata(DefaultPayload.create(DATA_VAL, METADATA_VAL), DATA_VAL, METADATA_VAL); assertDataAndMetadata(DefaultPayload.create(DATA_VAL), DATA_VAL, null); } + + @Test + public void shouldIndicateThatItHasNotMetadata() { + Payload payload = DefaultPayload.create("data"); + + Assertions.assertThat(payload.hasMetadata()).isFalse(); + } + + @Test + public void shouldIndicateThatItHasMetadata1() { + Payload payload = + DefaultPayload.create(Unpooled.wrappedBuffer("data".getBytes()), Unpooled.EMPTY_BUFFER); + + Assertions.assertThat(payload.hasMetadata()).isTrue(); + } + + @Test + public void shouldIndicateThatItHasMetadata2() { + Payload payload = + DefaultPayload.create(ByteBuffer.wrap("data".getBytes()), ByteBuffer.allocate(0)); + + Assertions.assertThat(payload.hasMetadata()).isTrue(); + } } diff --git a/rsocket-test/src/main/java/io/rsocket/test/TestFrames.java b/rsocket-test/src/main/java/io/rsocket/test/TestFrames.java index 2651b14ec..60ff05124 100644 --- a/rsocket-test/src/main/java/io/rsocket/test/TestFrames.java +++ b/rsocket-test/src/main/java/io/rsocket/test/TestFrames.java @@ -87,12 +87,12 @@ public static ByteBuf createTestRequestNFrame() { /** @return {@link ByteBuf} representing test instance of Request-Response frame */ public static ByteBuf createTestRequestResponseFrame() { - return RequestResponseFrameFlyweight.encode(allocator, 1, false, emptyPayload); + return RequestResponseFrameFlyweight.encodeReleasingPayload(allocator, 1, emptyPayload); } /** @return {@link ByteBuf} representing test instance of Request-Stream frame */ public static ByteBuf createTestRequestStreamFrame() { - return RequestStreamFrameFlyweight.encode(allocator, 1, false, 1L, emptyPayload); + return RequestStreamFrameFlyweight.encodeReleasingPayload(allocator, 1, 1L, emptyPayload); } /** @return {@link ByteBuf} representing test instance of Setup frame */ diff --git a/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java b/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java index e4ff75b2a..583f58634 100644 --- a/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java +++ b/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java @@ -35,10 +35,12 @@ import java.util.zip.GZIPInputStream; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import reactor.core.Disposable; import reactor.core.publisher.Flux; +import reactor.core.publisher.Hooks; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; import reactor.test.StepVerifier; @@ -64,9 +66,15 @@ static String read(String resourceName) { } } + @BeforeEach + default void setUp() { + Hooks.onOperatorDebug(); + } + @AfterEach default void close() { getTransportPair().dispose(); + Hooks.resetOnOperatorDebug(); } default Payload createTestPayload(int metadataPresent) { @@ -175,7 +183,7 @@ default void requestChannel200_000() { .verify(getTimeout()); } - @DisplayName("makes 1 requestChannel request with 2,000 large payloads") + @DisplayName("makes 1 requestChannel request with 200 large payloads") @Test default void largePayloadRequestChannel200() { Flux payloads = Flux.range(0, 200).map(__ -> LARGE_PAYLOAD); From b3087efc5f3d44f02542c5e560c2534f092b376a Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Fri, 24 Apr 2020 19:50:54 +0300 Subject: [PATCH 27/62] Refactors how `ByteBufAllocator` is used and setup (#796) --- .../java/io/rsocket/DuplexConnection.java | 8 ++++ .../main/java/io/rsocket/RSocketFactory.java | 47 ++++++++++++++++++- .../io/rsocket/core/RSocketConnector.java | 19 +------- .../io/rsocket/core/RSocketRequester.java | 5 +- .../io/rsocket/core/RSocketResponder.java | 3 +- .../java/io/rsocket/core/RSocketServer.java | 23 ++------- .../java/io/rsocket/core/ServerSetup.java | 23 ++------- .../FragmentationDuplexConnection.java | 20 +++----- .../ReassemblyDuplexConnection.java | 11 +++-- .../ClientServerInputMultiplexer.java | 6 +++ .../rsocket/resume/ClientRSocketSession.java | 3 +- .../resume/ResumableDuplexConnection.java | 6 +++ .../rsocket/resume/ServerRSocketSession.java | 3 +- .../rsocket/util/DuplexConnectionProxy.java | 6 +++ .../io/rsocket/core/AbstractSocketRule.java | 8 ++-- .../java/io/rsocket/core/KeepAliveTest.java | 38 +++++++++++---- .../io/rsocket/core/RSocketLeaseTest.java | 8 ++-- .../core/RSocketRequesterSubscribersTest.java | 6 ++- .../io/rsocket/core/RSocketRequesterTest.java | 4 +- .../io/rsocket/core/RSocketResponderTest.java | 8 ++-- .../java/io/rsocket/core/RSocketTest.java | 14 ++++-- .../io/rsocket/core/SetupRejectionTest.java | 18 ++++--- .../FragmentationDuplexConnectionTest.java | 24 ++++------ .../ReassembleDuplexConnectionTest.java | 19 +++++--- .../ClientServerInputMultiplexerTest.java | 6 ++- .../test/util/LocalDuplexConnection.java | 15 +++++- .../test/util/TestClientTransport.java | 11 ++++- .../test/util/TestDuplexConnection.java | 24 ++++++---- .../test/util/TestServerTransport.java | 10 +++- .../transport/ws/WebSocketHeadersSample.java | 5 +- .../MicrometerDuplexConnection.java | 6 +++ .../transport/local/LocalClientTransport.java | 35 ++++++++++---- .../local/LocalDuplexConnection.java | 14 +++++- .../transport/local/LocalServerTransport.java | 7 +-- .../transport/netty/TcpDuplexConnection.java | 5 ++ .../netty/WebsocketDuplexConnection.java | 6 +++ .../netty/client/TcpClientTransport.java | 10 +--- .../client/WebsocketClientTransport.java | 7 +-- .../netty/server/TcpServerTransport.java | 11 +---- .../netty/server/WebsocketRouteTransport.java | 7 +-- .../server/WebsocketServerTransport.java | 8 +--- 41 files changed, 305 insertions(+), 212 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/DuplexConnection.java b/rsocket-core/src/main/java/io/rsocket/DuplexConnection.java index b87ed0570..6190d24e3 100644 --- a/rsocket-core/src/main/java/io/rsocket/DuplexConnection.java +++ b/rsocket-core/src/main/java/io/rsocket/DuplexConnection.java @@ -17,6 +17,7 @@ package io.rsocket; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import java.nio.channels.ClosedChannelException; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; @@ -78,6 +79,13 @@ default Mono sendOne(ByteBuf frame) { */ Flux receive(); + /** + * Returns the assigned {@link ByteBufAllocator}. + * + * @return the {@link ByteBufAllocator} + */ + ByteBufAllocator alloc(); + @Override default double availability() { return isDisposed() ? 0.0 : 1.0; diff --git a/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java b/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java index 0e1ae12cb..43d344b9d 100644 --- a/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java +++ b/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java @@ -113,8 +113,29 @@ public ClientRSocketFactory(RSocketConnector connector) { this.connector = connector; } + /** + * @deprecated this method is deprecated and deliberately has no effect anymore. Right now, in + * order configure the custom {@link ByteBufAllocator} it is recommended to use the + * following setup for Reactor Netty based transport:
    + * 1. For Client:
    + *

    {@code
    +     * TcpClient.create()
    +     *          ...
    +     *          .bootstrap(bootstrap -> bootstrap.option(ChannelOption.ALLOCATOR, clientAllocator))
    +     * }
    + *
    + * 2. For server:
    + *
    {@code
    +     * TcpServer.create()
    +     *          ...
    +     *          .bootstrap(serverBootstrap -> serverBootstrap.childOption(ChannelOption.ALLOCATOR, serverAllocator))
    +     * }
    + * Or in case of local transport, to use corresponding factory method {@code + * LocalClientTransport.creat(String, ByteBufAllocator)} + * @param allocator instance of {@link ByteBufAllocator} + * @return this factory instance + */ public ClientRSocketFactory byteBufAllocator(ByteBufAllocator allocator) { - connector.byteBufAllocator(allocator); return this; } @@ -395,8 +416,30 @@ public ServerRSocketFactory(RSocketServer server) { this.server = server; } + /** + * @deprecated this method is deprecated and deliberately has no effect anymore. Right now, in + * order configure the custom {@link ByteBufAllocator} it is recommended to use the + * following setup for Reactor Netty based transport:
    + * 1. For Client:
    + *
    {@code
    +     * TcpClient.create()
    +     *          ...
    +     *          .bootstrap(bootstrap -> bootstrap.option(ChannelOption.ALLOCATOR, clientAllocator))
    +     * }
    + *
    + * 2. For server:
    + *
    {@code
    +     * TcpServer.create()
    +     *          ...
    +     *          .bootstrap(serverBootstrap -> serverBootstrap.childOption(ChannelOption.ALLOCATOR, serverAllocator))
    +     * }
    + * Or in case of local transport, to use corresponding factory method {@code + * LocalClientTransport.creat(String, ByteBufAllocator)} + * @param allocator instance of {@link ByteBufAllocator} + * @return this factory instance + */ + @Deprecated public ServerRSocketFactory byteBufAllocator(ByteBufAllocator allocator) { - server.byteBufAllocator(allocator); return this; } diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java index 23ecb9ba6..dc5be2430 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java @@ -16,7 +16,6 @@ package io.rsocket.core; import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import io.rsocket.AbstractRSocket; import io.rsocket.ConnectionSetupPayload; @@ -71,7 +70,6 @@ public class RSocketConnector { private PayloadDecoder payloadDecoder = PayloadDecoder.DEFAULT; private Consumer errorConsumer = Throwable::printStackTrace; - private ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; private RSocketConnector() {} @@ -241,16 +239,6 @@ public RSocketConnector errorConsumer(Consumer errorConsumer) { return this; } - /** - * @deprecated this is deprecated with no replacement and will be removed after {@link - * io.rsocket.RSocketFactory} is removed. - */ - public RSocketConnector byteBufAllocator(ByteBufAllocator allocator) { - Objects.requireNonNull(allocator); - this.allocator = allocator; - return this; - } - public Mono connect(ClientTransport transport) { return connect(() -> transport); } @@ -289,7 +277,6 @@ public Mono connect(Supplier transportSupplier) { RSocket rSocketRequester = new RSocketRequester( - allocator, multiplexer.asClientConnection(), payloadDecoder, errorConsumer, @@ -304,7 +291,7 @@ public Mono connect(Supplier transportSupplier) { ByteBuf setupFrame = SetupFrameFlyweight.encode( - allocator, + wrappedConnection.alloc(), leaseEnabled, (int) keepAliveInterval.toMillis(), (int) keepAliveMaxLifeTime.toMillis(), @@ -326,7 +313,7 @@ public Mono connect(Supplier transportSupplier) { leaseEnabled ? new ResponderLeaseHandler.Impl<>( CLIENT_TAG, - allocator, + wrappedConnection.alloc(), leases.sender(), errorConsumer, leases.stats()) @@ -334,7 +321,6 @@ public Mono connect(Supplier transportSupplier) { RSocket rSocketResponder = new RSocketResponder( - allocator, multiplexer.asServerConnection(), wrappedRSocketHandler, payloadDecoder, @@ -364,7 +350,6 @@ private ClientRSocketSession createSession( ClientRSocketSession session = new ClientRSocketSession( connection, - allocator, resume.getSessionDuration(), resume.getResumeStrategySupplier(), resume.getStoreFactory(CLIENT_TAG).apply(resumeToken), diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index 42a6a524d..04c766eec 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -108,7 +108,6 @@ class RSocketRequester implements RSocket { private volatile Throwable terminationError; RSocketRequester( - ByteBufAllocator allocator, DuplexConnection connection, PayloadDecoder payloadDecoder, Consumer errorConsumer, @@ -118,8 +117,8 @@ class RSocketRequester implements RSocket { int keepAliveAckTimeout, @Nullable KeepAliveHandler keepAliveHandler, RequesterLeaseHandler leaseHandler) { - this.allocator = allocator; this.connection = connection; + this.allocator = connection.alloc(); this.payloadDecoder = payloadDecoder; this.errorConsumer = errorConsumer; this.streamIdSupplier = streamIdSupplier; @@ -141,7 +140,7 @@ class RSocketRequester implements RSocket { if (keepAliveTickPeriod != 0 && keepAliveHandler != null) { KeepAliveSupport keepAliveSupport = - new ClientKeepAliveSupport(allocator, keepAliveTickPeriod, keepAliveAckTimeout); + new ClientKeepAliveSupport(this.allocator, keepAliveTickPeriod, keepAliveAckTimeout); this.keepAliveFramesAcceptor = keepAliveHandler.start( keepAliveSupport, sendProcessor::onNextPrioritized, this::tryTerminateOnKeepAlive); diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java index 5aef7eed2..f992b7577 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java @@ -75,15 +75,14 @@ class RSocketResponder implements ResponderRSocket { private final ByteBufAllocator allocator; RSocketResponder( - ByteBufAllocator allocator, DuplexConnection connection, RSocket requestHandler, PayloadDecoder payloadDecoder, Consumer errorConsumer, ResponderLeaseHandler leaseHandler, int mtu) { - this.allocator = allocator; this.connection = connection; + this.allocator = connection.alloc(); this.mtu = mtu; this.requestHandler = requestHandler; diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java index 113b4283f..6c289d707 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java @@ -17,7 +17,6 @@ package io.rsocket.core; import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; import io.rsocket.AbstractRSocket; import io.rsocket.Closeable; import io.rsocket.ConnectionSetupPayload; @@ -55,7 +54,6 @@ public final class RSocketServer { private Consumer errorConsumer = Throwable::printStackTrace; private PayloadDecoder payloadDecoder = PayloadDecoder.DEFAULT; - private ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; private RSocketServer() {} @@ -114,17 +112,6 @@ public RSocketServer errorConsumer(Consumer errorConsumer) { return this; } - /** - * @deprecated this is deprecated with no replacement and will be removed after {@link - * io.rsocket.RSocketFactory} is removed. - */ - @Deprecated - public RSocketServer byteBufAllocator(ByteBufAllocator allocator) { - Objects.requireNonNull(allocator); - this.allocator = allocator; - return this; - } - public ServerTransport.ConnectionAcceptor asConnectionAcceptor() { return new ServerTransport.ConnectionAcceptor() { private final ServerSetup serverSetup = serverSetup(); @@ -228,7 +215,6 @@ private Mono acceptSetup( RSocket rSocketRequester = new RSocketRequester( - allocator, wrappedMultiplexer.asServerConnection(), payloadDecoder, errorConsumer, @@ -252,12 +238,13 @@ private Mono acceptSetup( .doOnNext( rSocketHandler -> { RSocket wrappedRSocketHandler = interceptors.initResponder(rSocketHandler); + DuplexConnection connection = wrappedMultiplexer.asClientConnection(); ResponderLeaseHandler responderLeaseHandler = leaseEnabled ? new ResponderLeaseHandler.Impl<>( SERVER_TAG, - allocator, + connection.alloc(), leases.sender(), errorConsumer, leases.stats()) @@ -265,8 +252,7 @@ private Mono acceptSetup( RSocket rSocketResponder = new RSocketResponder( - allocator, - wrappedMultiplexer.asClientConnection(), + connection, wrappedRSocketHandler, payloadDecoder, errorConsumer, @@ -279,12 +265,11 @@ private Mono acceptSetup( } private ServerSetup serverSetup() { - return resume != null ? createSetup() : new ServerSetup.DefaultServerSetup(allocator); + return resume != null ? createSetup() : new ServerSetup.DefaultServerSetup(); } ServerSetup createSetup() { return new ServerSetup.ResumableServerSetup( - allocator, new SessionManager(), resume.getSessionDuration(), resume.getStreamTimeout(), diff --git a/rsocket-core/src/main/java/io/rsocket/core/ServerSetup.java b/rsocket-core/src/main/java/io/rsocket/core/ServerSetup.java index 16f5f61b1..3e20d3c60 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/ServerSetup.java +++ b/rsocket-core/src/main/java/io/rsocket/core/ServerSetup.java @@ -19,7 +19,7 @@ import static io.rsocket.keepalive.KeepAliveHandler.*; import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; +import io.rsocket.DuplexConnection; import io.rsocket.exceptions.RejectedResumeException; import io.rsocket.exceptions.UnsupportedSetupException; import io.rsocket.frame.ErrorFrameFlyweight; @@ -35,12 +35,6 @@ abstract class ServerSetup { - final ByteBufAllocator allocator; - - public ServerSetup(ByteBufAllocator allocator) { - this.allocator = allocator; - } - abstract Mono acceptRSocketSetup( ByteBuf frame, ClientServerInputMultiplexer multiplexer, @@ -51,18 +45,14 @@ abstract Mono acceptRSocketSetup( void dispose() {} Mono sendError(ClientServerInputMultiplexer multiplexer, Exception exception) { - return multiplexer - .asSetupConnection() - .sendOne(ErrorFrameFlyweight.encode(allocator, 0, exception)) + DuplexConnection duplexConnection = multiplexer.asSetupConnection(); + return duplexConnection + .sendOne(ErrorFrameFlyweight.encode(duplexConnection.alloc(), 0, exception)) .onErrorResume(err -> Mono.empty()); } static class DefaultServerSetup extends ServerSetup { - DefaultServerSetup(ByteBufAllocator allocator) { - super(allocator); - } - @Override public Mono acceptRSocketSetup( ByteBuf frame, @@ -94,7 +84,6 @@ public Mono acceptRSocketResume(ByteBuf frame, ClientServerInputMultiplexe } static class ResumableServerSetup extends ServerSetup { - private final ByteBufAllocator allocator; private final SessionManager sessionManager; private final Duration resumeSessionDuration; private final Duration resumeStreamTimeout; @@ -102,14 +91,11 @@ static class ResumableServerSetup extends ServerSetup { private final boolean cleanupStoreOnKeepAlive; ResumableServerSetup( - ByteBufAllocator allocator, SessionManager sessionManager, Duration resumeSessionDuration, Duration resumeStreamTimeout, Function resumeStoreFactory, boolean cleanupStoreOnKeepAlive) { - super(allocator); - this.allocator = allocator; this.sessionManager = sessionManager; this.resumeSessionDuration = resumeSessionDuration; this.resumeStreamTimeout = resumeStreamTimeout; @@ -131,7 +117,6 @@ public Mono acceptRSocketSetup( .save( new ServerRSocketSession( multiplexer.asClientServerConnection(), - allocator, resumeSessionDuration, resumeStreamTimeout, resumeStoreFactory, diff --git a/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java b/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java index 333787e9f..316643e10 100644 --- a/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java +++ b/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java @@ -19,7 +19,6 @@ import static io.rsocket.fragmentation.FrameFragmenter.fragmentFrame; import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufUtil; import io.rsocket.DuplexConnection; import io.rsocket.frame.FrameHeaderFlyweight; @@ -46,26 +45,19 @@ public final class FragmentationDuplexConnection extends ReassemblyDuplexConnect private static final Logger logger = LoggerFactory.getLogger(FragmentationDuplexConnection.class); private final DuplexConnection delegate; private final int mtu; - private final ByteBufAllocator allocator; private final FrameReassembler frameReassembler; private final boolean encodeLength; private final String type; public FragmentationDuplexConnection( - DuplexConnection delegate, - ByteBufAllocator allocator, - int mtu, - boolean encodeAndEncodeLength, - String type) { - super(delegate, allocator, encodeAndEncodeLength); + DuplexConnection delegate, int mtu, boolean encodeAndEncodeLength, String type) { + super(delegate, encodeAndEncodeLength); Objects.requireNonNull(delegate, "delegate must not be null"); - Objects.requireNonNull(allocator, "byteBufAllocator must not be null"); this.encodeLength = encodeAndEncodeLength; - this.allocator = allocator; this.delegate = delegate; this.mtu = assertMtu(mtu); - this.frameReassembler = new FrameReassembler(allocator); + this.frameReassembler = new FrameReassembler(delegate.alloc()); this.type = type; delegate.onClose().doFinally(s -> frameReassembler.dispose()).subscribe(); @@ -113,7 +105,7 @@ public Mono sendOne(ByteBuf frame) { if (shouldFragment(frameType, readableBytes)) { if (logger.isDebugEnabled()) { return delegate.send( - Flux.from(fragmentFrame(allocator, mtu, frame, frameType, encodeLength)) + Flux.from(fragmentFrame(alloc(), mtu, frame, frameType, encodeLength)) .doOnNext( byteBuf -> { ByteBuf f = encodeLength ? FrameLengthFlyweight.frame(byteBuf) : byteBuf; @@ -126,7 +118,7 @@ public Mono sendOne(ByteBuf frame) { })); } else { return delegate.send( - Flux.from(fragmentFrame(allocator, mtu, frame, frameType, encodeLength))); + Flux.from(fragmentFrame(alloc(), mtu, frame, frameType, encodeLength))); } } else { return delegate.sendOne(encode(frame)); @@ -135,7 +127,7 @@ public Mono sendOne(ByteBuf frame) { private ByteBuf encode(ByteBuf frame) { if (encodeLength) { - return FrameLengthFlyweight.encode(allocator, frame.readableBytes(), frame); + return FrameLengthFlyweight.encode(alloc(), frame.readableBytes(), frame); } else { return frame; } diff --git a/rsocket-core/src/main/java/io/rsocket/fragmentation/ReassemblyDuplexConnection.java b/rsocket-core/src/main/java/io/rsocket/fragmentation/ReassemblyDuplexConnection.java index bf0d7482c..933755bb2 100644 --- a/rsocket-core/src/main/java/io/rsocket/fragmentation/ReassemblyDuplexConnection.java +++ b/rsocket-core/src/main/java/io/rsocket/fragmentation/ReassemblyDuplexConnection.java @@ -37,13 +37,11 @@ public class ReassemblyDuplexConnection implements DuplexConnection { private final FrameReassembler frameReassembler; private final boolean decodeLength; - public ReassemblyDuplexConnection( - DuplexConnection delegate, ByteBufAllocator allocator, boolean decodeLength) { + public ReassemblyDuplexConnection(DuplexConnection delegate, boolean decodeLength) { Objects.requireNonNull(delegate, "delegate must not be null"); - Objects.requireNonNull(allocator, "byteBufAllocator must not be null"); this.decodeLength = decodeLength; this.delegate = delegate; - this.frameReassembler = new FrameReassembler(allocator); + this.frameReassembler = new FrameReassembler(delegate.alloc()); delegate.onClose().doFinally(s -> frameReassembler.dispose()).subscribe(); } @@ -77,6 +75,11 @@ public Flux receive() { }); } + @Override + public ByteBufAllocator alloc() { + return delegate.alloc(); + } + @Override public Mono onClose() { return delegate.onClose(); diff --git a/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java b/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java index 68098e279..cf3eeb120 100644 --- a/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java +++ b/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java @@ -17,6 +17,7 @@ package io.rsocket.internal; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import io.rsocket.Closeable; import io.rsocket.DuplexConnection; import io.rsocket.frame.FrameHeaderFlyweight; @@ -201,6 +202,11 @@ public Flux receive() { })); } + @Override + public ByteBufAllocator alloc() { + return source.alloc(); + } + @Override public void dispose() { source.dispose(); diff --git a/rsocket-core/src/main/java/io/rsocket/resume/ClientRSocketSession.java b/rsocket-core/src/main/java/io/rsocket/resume/ClientRSocketSession.java index b347642e3..509fb5168 100644 --- a/rsocket-core/src/main/java/io/rsocket/resume/ClientRSocketSession.java +++ b/rsocket-core/src/main/java/io/rsocket/resume/ClientRSocketSession.java @@ -41,13 +41,12 @@ public class ClientRSocketSession implements RSocketSession resumeStrategy, ResumableFramesStore resumableFramesStore, Duration resumeStreamTimeout, boolean cleanupStoreOnKeepAlive) { - this.allocator = allocator; + this.allocator = duplexConnection.alloc(); this.resumableConnection = new ResumableDuplexConnection( "client", diff --git a/rsocket-core/src/main/java/io/rsocket/resume/ResumableDuplexConnection.java b/rsocket-core/src/main/java/io/rsocket/resume/ResumableDuplexConnection.java index 49401d560..b9c93f4cd 100644 --- a/rsocket-core/src/main/java/io/rsocket/resume/ResumableDuplexConnection.java +++ b/rsocket-core/src/main/java/io/rsocket/resume/ResumableDuplexConnection.java @@ -17,6 +17,7 @@ package io.rsocket.resume; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import io.rsocket.Closeable; import io.rsocket.DuplexConnection; import io.rsocket.frame.FrameHeaderFlyweight; @@ -105,6 +106,11 @@ public ResumableDuplexConnection( reconnect(duplexConnection); } + @Override + public ByteBufAllocator alloc() { + return curConnection.alloc(); + } + public void disconnect() { DuplexConnection c = this.curConnection; if (c != null) { diff --git a/rsocket-core/src/main/java/io/rsocket/resume/ServerRSocketSession.java b/rsocket-core/src/main/java/io/rsocket/resume/ServerRSocketSession.java index 1a0605497..5d55559cc 100644 --- a/rsocket-core/src/main/java/io/rsocket/resume/ServerRSocketSession.java +++ b/rsocket-core/src/main/java/io/rsocket/resume/ServerRSocketSession.java @@ -43,13 +43,12 @@ public class ServerRSocketSession implements RSocketSession { public ServerRSocketSession( DuplexConnection duplexConnection, - ByteBufAllocator allocator, Duration resumeSessionDuration, Duration resumeStreamTimeout, Function resumeStoreFactory, ByteBuf resumeToken, boolean cleanupStoreOnKeepAlive) { - this.allocator = allocator; + this.allocator = duplexConnection.alloc(); this.resumeToken = resumeToken; this.resumableConnection = new ResumableDuplexConnection( diff --git a/rsocket-core/src/main/java/io/rsocket/util/DuplexConnectionProxy.java b/rsocket-core/src/main/java/io/rsocket/util/DuplexConnectionProxy.java index fa19553a7..2f5d1da4b 100644 --- a/rsocket-core/src/main/java/io/rsocket/util/DuplexConnectionProxy.java +++ b/rsocket-core/src/main/java/io/rsocket/util/DuplexConnectionProxy.java @@ -17,6 +17,7 @@ package io.rsocket.util; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; @@ -44,6 +45,11 @@ public double availability() { return connection.availability(); } + @Override + public ByteBufAllocator alloc() { + return connection.alloc(); + } + @Override public Mono onClose() { return connection.onClose(); diff --git a/rsocket-core/src/test/java/io/rsocket/core/AbstractSocketRule.java b/rsocket-core/src/test/java/io/rsocket/core/AbstractSocketRule.java index 5a43838c7..20972a0d3 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/AbstractSocketRule.java +++ b/rsocket-core/src/test/java/io/rsocket/core/AbstractSocketRule.java @@ -41,10 +41,10 @@ public Statement apply(final Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { - connection = new TestDuplexConnection(); + allocator = LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); + connection = new TestDuplexConnection(allocator); connectSub = TestSubscriber.create(); errors = new ConcurrentLinkedQueue<>(); - allocator = LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); init(); base.evaluate(); } @@ -52,10 +52,10 @@ public void evaluate() throws Throwable { } protected void init() { - socket = newRSocket(allocator); + socket = newRSocket(); } - protected abstract T newRSocket(LeaksTrackingByteBufAllocator allocator); + protected abstract T newRSocket(); public void assertNoConnectionErrors() { if (errors.size() > 1) { diff --git a/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java b/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java index 10725238a..e8f3f4190 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java @@ -23,6 +23,7 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import io.rsocket.RSocket; +import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.exceptions.ConnectionErrorException; import io.rsocket.frame.FrameHeaderFlyweight; import io.rsocket.frame.FrameType; @@ -52,11 +53,12 @@ public class KeepAliveTest { private ResumableRSocketState resumableRequesterState; static RSocketState requester(int tickPeriod, int timeout) { - TestDuplexConnection connection = new TestDuplexConnection(); + LeaksTrackingByteBufAllocator allocator = + LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); + TestDuplexConnection connection = new TestDuplexConnection(allocator); Errors errors = new Errors(); RSocketRequester rSocket = new RSocketRequester( - ByteBufAllocator.DEFAULT, connection, DefaultPayload::create, errors, @@ -66,11 +68,13 @@ static RSocketState requester(int tickPeriod, int timeout) { timeout, new DefaultKeepAliveHandler(connection), RequesterLeaseHandler.None); - return new RSocketState(rSocket, errors, connection); + return new RSocketState(rSocket, errors, allocator, connection); } static ResumableRSocketState resumableRequester(int tickPeriod, int timeout) { - TestDuplexConnection connection = new TestDuplexConnection(); + LeaksTrackingByteBufAllocator allocator = + LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); + TestDuplexConnection connection = new TestDuplexConnection(allocator); ResumableDuplexConnection resumableConnection = new ResumableDuplexConnection( "test", @@ -82,7 +86,6 @@ static ResumableRSocketState resumableRequester(int tickPeriod, int timeout) { Errors errors = new Errors(); RSocketRequester rSocket = new RSocketRequester( - ByteBufAllocator.DEFAULT, resumableConnection, DefaultPayload::create, errors, @@ -92,7 +95,7 @@ static ResumableRSocketState resumableRequester(int tickPeriod, int timeout) { timeout, new ResumableKeepAliveHandler(resumableConnection), RequesterLeaseHandler.None); - return new ResumableRSocketState(rSocket, errors, connection, resumableConnection); + return new ResumableRSocketState(rSocket, errors, connection, resumableConnection, allocator); } @BeforeEach @@ -194,7 +197,7 @@ void resumableRequesterKeepAlivesAfterReconnect() { resumableRequester(KEEP_ALIVE_INTERVAL, KEEP_ALIVE_TIMEOUT); ResumableDuplexConnection resumableDuplexConnection = rSocketState.resumableDuplexConnection(); resumableDuplexConnection.disconnect(); - TestDuplexConnection newTestConnection = new TestDuplexConnection(); + TestDuplexConnection newTestConnection = new TestDuplexConnection(rSocketState.alloc()); resumableDuplexConnection.reconnect(newTestConnection); resumableDuplexConnection.resume(0, 0, ignored -> Mono.empty()); @@ -244,11 +247,17 @@ static class RSocketState { private final RSocket rSocket; private final Errors errors; private final TestDuplexConnection connection; + private final LeaksTrackingByteBufAllocator allocator; - public RSocketState(RSocket rSocket, Errors errors, TestDuplexConnection connection) { + public RSocketState( + RSocket rSocket, + Errors errors, + LeaksTrackingByteBufAllocator allocator, + TestDuplexConnection connection) { this.rSocket = rSocket; this.errors = errors; this.connection = connection; + this.allocator = allocator; } public TestDuplexConnection connection() { @@ -262,6 +271,10 @@ public RSocket rSocket() { public Errors errors() { return errors; } + + public LeaksTrackingByteBufAllocator alloc() { + return allocator; + } } static class ResumableRSocketState { @@ -269,16 +282,19 @@ static class ResumableRSocketState { private final Errors errors; private final TestDuplexConnection connection; private final ResumableDuplexConnection resumableDuplexConnection; + private final LeaksTrackingByteBufAllocator allocator; public ResumableRSocketState( RSocket rSocket, Errors errors, TestDuplexConnection connection, - ResumableDuplexConnection resumableDuplexConnection) { + ResumableDuplexConnection resumableDuplexConnection, + LeaksTrackingByteBufAllocator allocator) { this.rSocket = rSocket; this.errors = errors; this.connection = connection; this.resumableDuplexConnection = resumableDuplexConnection; + this.allocator = allocator; } public TestDuplexConnection connection() { @@ -296,6 +312,10 @@ public RSocket rSocket() { public Errors errors() { return errors; } + + public LeaksTrackingByteBufAllocator alloc() { + return allocator; + } } static class Errors implements Consumer { diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java index 01ee1eb6d..51f5afc24 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java @@ -26,9 +26,9 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; -import io.netty.buffer.UnpooledByteBufAllocator; import io.rsocket.Payload; import io.rsocket.RSocket; +import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.exceptions.Exceptions; import io.rsocket.frame.FrameHeaderFlyweight; import io.rsocket.frame.FrameType; @@ -77,10 +77,10 @@ class RSocketLeaseTest { @BeforeEach void setUp() { - connection = new TestDuplexConnection(); PayloadDecoder payloadDecoder = PayloadDecoder.DEFAULT; - byteBufAllocator = UnpooledByteBufAllocator.DEFAULT; + byteBufAllocator = LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); + connection = new TestDuplexConnection(byteBufAllocator); requesterLeaseHandler = new RequesterLeaseHandler.Impl(TAG, leases -> leaseReceiver = leases); responderLeaseHandler = new ResponderLeaseHandler.Impl<>( @@ -90,7 +90,6 @@ void setUp() { new ClientServerInputMultiplexer(connection, new InitializingInterceptorRegistry(), true); rSocketRequester = new RSocketRequester( - byteBufAllocator, multiplexer.asClientConnection(), payloadDecoder, err -> {}, @@ -110,7 +109,6 @@ void setUp() { rSocketResponder = new RSocketResponder( - byteBufAllocator, multiplexer.asServerConnection(), mockRSocketHandler, payloadDecoder, diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java index 8380290f2..01cf99e26 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java @@ -19,6 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.rsocket.RSocket; +import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.frame.FrameHeaderFlyweight; import io.rsocket.frame.FrameType; import io.rsocket.frame.decoder.PayloadDecoder; @@ -52,15 +53,16 @@ class RSocketRequesterSubscribersTest { FrameType.REQUEST_STREAM, FrameType.REQUEST_CHANNEL)); + private LeaksTrackingByteBufAllocator allocator; private RSocket rSocketRequester; private TestDuplexConnection connection; @BeforeEach void setUp() { - connection = new TestDuplexConnection(); + allocator = LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); + connection = new TestDuplexConnection(allocator); rSocketRequester = new RSocketRequester( - ByteBufAllocator.DEFAULT, connection, PayloadDecoder.DEFAULT, err -> {}, diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java index bc19d8132..e536d2db4 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java @@ -41,7 +41,6 @@ import io.netty.util.ReferenceCounted; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.exceptions.ApplicationErrorException; import io.rsocket.exceptions.CustomRSocketException; import io.rsocket.exceptions.RejectedSetupException; @@ -741,9 +740,8 @@ public int sendRequestResponse(Publisher response) { public static class ClientSocketRule extends AbstractSocketRule { @Override - protected RSocketRequester newRSocket(LeaksTrackingByteBufAllocator allocator) { + protected RSocketRequester newRSocket() { return new RSocketRequester( - allocator, connection, PayloadDecoder.ZERO_COPY, throwable -> errors.add(throwable), diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java index 78027aa3d..48910b3a2 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java @@ -38,7 +38,6 @@ import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.frame.CancelFrameFlyweight; import io.rsocket.frame.ErrorFrameFlyweight; import io.rsocket.frame.FrameHeaderFlyweight; @@ -735,7 +734,7 @@ public Mono requestResponse(Payload payload) { public void setAcceptingSocket(RSocket acceptingSocket) { this.acceptingSocket = acceptingSocket; - connection = new TestDuplexConnection(); + connection = new TestDuplexConnection(alloc()); connectSub = TestSubscriber.create(); errors = new ConcurrentLinkedQueue<>(); this.prefetch = Integer.MAX_VALUE; @@ -744,7 +743,7 @@ public void setAcceptingSocket(RSocket acceptingSocket) { public void setAcceptingSocket(RSocket acceptingSocket, int prefetch) { this.acceptingSocket = acceptingSocket; - connection = new TestDuplexConnection(); + connection = new TestDuplexConnection(alloc()); connectSub = TestSubscriber.create(); errors = new ConcurrentLinkedQueue<>(); this.prefetch = prefetch; @@ -752,9 +751,8 @@ public void setAcceptingSocket(RSocket acceptingSocket, int prefetch) { } @Override - protected RSocketResponder newRSocket(LeaksTrackingByteBufAllocator allocator) { + protected RSocketResponder newRSocket() { return new RSocketResponder( - allocator, connection, acceptingSocket, PayloadDecoder.ZERO_COPY, diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java index 568f8eed6..4a2c43ef8 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java @@ -26,6 +26,7 @@ import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; +import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.exceptions.ApplicationErrorException; import io.rsocket.exceptions.CustomRSocketException; import io.rsocket.frame.decoder.PayloadDecoder; @@ -415,6 +416,8 @@ public static class SocketRule extends ExternalResource { private ArrayList clientErrors = new ArrayList<>(); private ArrayList serverErrors = new ArrayList<>(); + private LeaksTrackingByteBufAllocator allocator; + @Override public Statement apply(Statement base, Description description) { return new Statement() { @@ -426,14 +429,19 @@ public void evaluate() throws Throwable { }; } + public LeaksTrackingByteBufAllocator alloc() { + return allocator; + } + protected void init() { + allocator = LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); serverProcessor = DirectProcessor.create(); clientProcessor = DirectProcessor.create(); LocalDuplexConnection serverConnection = - new LocalDuplexConnection("server", clientProcessor, serverProcessor); + new LocalDuplexConnection("server", allocator, clientProcessor, serverProcessor); LocalDuplexConnection clientConnection = - new LocalDuplexConnection("client", serverProcessor, clientProcessor); + new LocalDuplexConnection("client", allocator, serverProcessor, clientProcessor); requestAcceptor = null != requestAcceptor @@ -468,7 +476,6 @@ public Flux requestChannel(Publisher payloads) { srs = new RSocketResponder( - ByteBufAllocator.DEFAULT, serverConnection, requestAcceptor, PayloadDecoder.DEFAULT, @@ -478,7 +485,6 @@ public Flux requestChannel(Publisher payloads) { crs = new RSocketRequester( - ByteBufAllocator.DEFAULT, clientConnection, PayloadDecoder.DEFAULT, throwable -> clientErrors.add(throwable), diff --git a/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java b/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java index 5b489896b..db72c7775 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java @@ -6,6 +6,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.rsocket.*; +import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.exceptions.Exceptions; import io.rsocket.exceptions.RejectedSetupException; import io.rsocket.frame.ErrorFrameFlyweight; @@ -47,11 +48,12 @@ void responderRejectSetup() { @Test void requesterStreamsTerminatedOnZeroErrorFrame() { - TestDuplexConnection conn = new TestDuplexConnection(); + LeaksTrackingByteBufAllocator allocator = + LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); + TestDuplexConnection conn = new TestDuplexConnection(allocator); List errors = new ArrayList<>(); RSocketRequester rSocket = new RSocketRequester( - ByteBufAllocator.DEFAULT, conn, DefaultPayload::create, errors::add, @@ -84,10 +86,11 @@ void requesterStreamsTerminatedOnZeroErrorFrame() { @Test void requesterNewStreamsTerminatedAfterZeroErrorFrame() { - TestDuplexConnection conn = new TestDuplexConnection(); + LeaksTrackingByteBufAllocator allocator = + LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); + TestDuplexConnection conn = new TestDuplexConnection(allocator); RSocketRequester rSocket = new RSocketRequester( - ByteBufAllocator.DEFAULT, conn, DefaultPayload::create, err -> {}, @@ -132,7 +135,9 @@ public Mono senderRSocket() { private static class SingleConnectionTransport implements ServerTransport { - private final TestDuplexConnection conn = new TestDuplexConnection(); + private final LeaksTrackingByteBufAllocator allocator = + LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); + private final TestDuplexConnection conn = new TestDuplexConnection(allocator); @Override public Mono start(ConnectionAcceptor acceptor, int mtu) { @@ -150,8 +155,7 @@ public ByteBuf awaitSent() { public void connect() { Payload payload = DefaultPayload.create(DefaultPayload.EMPTY_BUFFER); ByteBuf setup = - SetupFrameFlyweight.encode( - ByteBufAllocator.DEFAULT, false, 0, 42, "mdMime", "dMime", payload); + SetupFrameFlyweight.encode(allocator, false, 0, 42, "mdMime", "dMime", payload); conn.addToReceivedBuffer(setup); } diff --git a/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationDuplexConnectionTest.java b/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationDuplexConnectionTest.java index a6918c497..9050eaa90 100644 --- a/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationDuplexConnectionTest.java +++ b/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationDuplexConnectionTest.java @@ -24,6 +24,7 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import io.rsocket.DuplexConnection; +import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.frame.*; import java.util.concurrent.ThreadLocalRandom; import org.junit.Assert; @@ -55,16 +56,14 @@ final class FragmentationDuplexConnectionTest { private final ArgumentCaptor> publishers = ArgumentCaptor.forClass(Publisher.class); - private ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; + private LeaksTrackingByteBufAllocator allocator = + LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); @DisplayName("constructor throws IllegalArgumentException with negative maxFragmentLength") @Test void constructorInvalidMaxFragmentSize() { assertThatIllegalArgumentException() - .isThrownBy( - () -> - new FragmentationDuplexConnection( - delegate, allocator, Integer.MIN_VALUE, false, "")) + .isThrownBy(() -> new FragmentationDuplexConnection(delegate, Integer.MIN_VALUE, false, "")) .withMessage("smallest allowed mtu size is 64 bytes, provided: -2147483648"); } @@ -72,23 +71,15 @@ void constructorInvalidMaxFragmentSize() { @Test void constructorMtuLessThanMin() { assertThatIllegalArgumentException() - .isThrownBy(() -> new FragmentationDuplexConnection(delegate, allocator, 2, false, "")) + .isThrownBy(() -> new FragmentationDuplexConnection(delegate, 2, false, "")) .withMessage("smallest allowed mtu size is 64 bytes, provided: 2"); } - @DisplayName("constructor throws NullPointerException with null byteBufAllocator") - @Test - void constructorNullByteBufAllocator() { - assertThatNullPointerException() - .isThrownBy(() -> new FragmentationDuplexConnection(delegate, null, 64, false, "")) - .withMessage("byteBufAllocator must not be null"); - } - @DisplayName("constructor throws NullPointerException with null delegate") @Test void constructorNullDelegate() { assertThatNullPointerException() - .isThrownBy(() -> new FragmentationDuplexConnection(null, allocator, 64, false, "")) + .isThrownBy(() -> new FragmentationDuplexConnection(null, 64, false, "")) .withMessage("delegate must not be null"); } @@ -100,8 +91,9 @@ void sendData() { allocator, 1, false, Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(data)); when(delegate.onClose()).thenReturn(Mono.never()); + when(delegate.alloc()).thenReturn(allocator); - new FragmentationDuplexConnection(delegate, allocator, 64, false, "").sendOne(encode.retain()); + new FragmentationDuplexConnection(delegate, 64, false, "").sendOne(encode.retain()); verify(delegate).send(publishers.capture()); diff --git a/rsocket-core/src/test/java/io/rsocket/fragmentation/ReassembleDuplexConnectionTest.java b/rsocket-core/src/test/java/io/rsocket/fragmentation/ReassembleDuplexConnectionTest.java index c5abce339..013e2ebc2 100644 --- a/rsocket-core/src/test/java/io/rsocket/fragmentation/ReassembleDuplexConnectionTest.java +++ b/rsocket-core/src/test/java/io/rsocket/fragmentation/ReassembleDuplexConnectionTest.java @@ -25,6 +25,7 @@ import io.netty.buffer.CompositeByteBuf; import io.netty.buffer.Unpooled; import io.rsocket.DuplexConnection; +import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.frame.CancelFrameFlyweight; import io.rsocket.frame.FrameHeaderFlyweight; import io.rsocket.frame.FrameType; @@ -51,7 +52,8 @@ final class ReassembleDuplexConnectionTest { private final DuplexConnection delegate = mock(DuplexConnection.class, RETURNS_SMART_NULLS); - private ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; + private LeaksTrackingByteBufAllocator allocator = + LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); @DisplayName("reassembles data") @Test @@ -82,8 +84,9 @@ void reassembleData() { when(delegate.receive()).thenReturn(Flux.fromIterable(byteBufs)); when(delegate.onClose()).thenReturn(Mono.never()); + when(delegate.alloc()).thenReturn(allocator); - new ReassemblyDuplexConnection(delegate, allocator, false) + new ReassemblyDuplexConnection(delegate, false) .receive() .as(StepVerifier::create) .assertNext( @@ -146,8 +149,9 @@ void reassembleMetadata() { when(delegate.receive()).thenReturn(Flux.fromIterable(byteBufs)); when(delegate.onClose()).thenReturn(Mono.never()); + when(delegate.alloc()).thenReturn(allocator); - new ReassemblyDuplexConnection(delegate, allocator, false) + new ReassemblyDuplexConnection(delegate, false) .receive() .as(StepVerifier::create) .assertNext( @@ -213,8 +217,9 @@ void reassembleMetadataAndData() { when(delegate.receive()).thenReturn(Flux.fromIterable(byteBufs)); when(delegate.onClose()).thenReturn(Mono.never()); + when(delegate.alloc()).thenReturn(allocator); - new ReassemblyDuplexConnection(delegate, allocator, false) + new ReassemblyDuplexConnection(delegate, false) .receive() .as(StepVerifier::create) .assertNext( @@ -234,8 +239,9 @@ void reassembleNonFragment() { when(delegate.receive()).thenReturn(Flux.just(encode)); when(delegate.onClose()).thenReturn(Mono.never()); + when(delegate.alloc()).thenReturn(allocator); - new ReassemblyDuplexConnection(delegate, allocator, false) + new ReassemblyDuplexConnection(delegate, false) .receive() .as(StepVerifier::create) .assertNext( @@ -253,8 +259,9 @@ void reassembleNonFragmentableFrame() { when(delegate.receive()).thenReturn(Flux.just(encode)); when(delegate.onClose()).thenReturn(Mono.never()); + when(delegate.alloc()).thenReturn(allocator); - new ReassemblyDuplexConnection(delegate, allocator, false) + new ReassemblyDuplexConnection(delegate, false) .receive() .as(StepVerifier::create) .assertNext( diff --git a/rsocket-core/src/test/java/io/rsocket/internal/ClientServerInputMultiplexerTest.java b/rsocket-core/src/test/java/io/rsocket/internal/ClientServerInputMultiplexerTest.java index 9a4b5d2db..efa962c48 100644 --- a/rsocket-core/src/test/java/io/rsocket/internal/ClientServerInputMultiplexerTest.java +++ b/rsocket-core/src/test/java/io/rsocket/internal/ClientServerInputMultiplexerTest.java @@ -21,6 +21,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; +import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.frame.*; import io.rsocket.plugins.InitializingInterceptorRegistry; import io.rsocket.test.util.TestDuplexConnection; @@ -32,12 +33,13 @@ public class ClientServerInputMultiplexerTest { private TestDuplexConnection source; private ClientServerInputMultiplexer clientMultiplexer; - private ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; + private LeaksTrackingByteBufAllocator allocator = + LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); private ClientServerInputMultiplexer serverMultiplexer; @Before public void setup() { - source = new TestDuplexConnection(); + source = new TestDuplexConnection(allocator); clientMultiplexer = new ClientServerInputMultiplexer(source, new InitializingInterceptorRegistry(), true); serverMultiplexer = diff --git a/rsocket-core/src/test/java/io/rsocket/test/util/LocalDuplexConnection.java b/rsocket-core/src/test/java/io/rsocket/test/util/LocalDuplexConnection.java index d945dd45d..58323c066 100644 --- a/rsocket-core/src/test/java/io/rsocket/test/util/LocalDuplexConnection.java +++ b/rsocket-core/src/test/java/io/rsocket/test/util/LocalDuplexConnection.java @@ -17,6 +17,7 @@ package io.rsocket.test.util; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; import org.reactivestreams.Publisher; import reactor.core.publisher.DirectProcessor; @@ -25,17 +26,22 @@ import reactor.core.publisher.MonoProcessor; public class LocalDuplexConnection implements DuplexConnection { + private final ByteBufAllocator allocator; private final DirectProcessor send; private final DirectProcessor receive; private final MonoProcessor onClose; private final String name; public LocalDuplexConnection( - String name, DirectProcessor send, DirectProcessor receive) { + String name, + ByteBufAllocator allocator, + DirectProcessor send, + DirectProcessor receive) { this.name = name; + this.allocator = allocator; this.send = send; this.receive = receive; - onClose = MonoProcessor.create(); + this.onClose = MonoProcessor.create(); } @Override @@ -52,6 +58,11 @@ public Flux receive() { return receive.doOnNext(f -> System.out.println(name + " - " + f.toString())); } + @Override + public ByteBufAllocator alloc() { + return allocator; + } + @Override public void dispose() { onClose.onComplete(); diff --git a/rsocket-core/src/test/java/io/rsocket/test/util/TestClientTransport.java b/rsocket-core/src/test/java/io/rsocket/test/util/TestClientTransport.java index 37ad8ee5b..a30e75875 100644 --- a/rsocket-core/src/test/java/io/rsocket/test/util/TestClientTransport.java +++ b/rsocket-core/src/test/java/io/rsocket/test/util/TestClientTransport.java @@ -1,12 +1,15 @@ package io.rsocket.test.util; +import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; +import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.transport.ClientTransport; import reactor.core.publisher.Mono; public class TestClientTransport implements ClientTransport { - - private final TestDuplexConnection testDuplexConnection = new TestDuplexConnection(); + private final LeaksTrackingByteBufAllocator allocator = + LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); + private final TestDuplexConnection testDuplexConnection = new TestDuplexConnection(allocator); @Override public Mono connect(int mtu) { @@ -16,4 +19,8 @@ public Mono connect(int mtu) { public TestDuplexConnection testConnection() { return testDuplexConnection; } + + public LeaksTrackingByteBufAllocator alloc() { + return allocator; + } } diff --git a/rsocket-core/src/test/java/io/rsocket/test/util/TestDuplexConnection.java b/rsocket-core/src/test/java/io/rsocket/test/util/TestDuplexConnection.java index 6298b0c3a..17a19b8c9 100644 --- a/rsocket-core/src/test/java/io/rsocket/test/util/TestDuplexConnection.java +++ b/rsocket-core/src/test/java/io/rsocket/test/util/TestDuplexConnection.java @@ -17,6 +17,7 @@ package io.rsocket.test.util; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; import java.util.Collection; import java.util.concurrent.ConcurrentLinkedQueue; @@ -46,17 +47,19 @@ public class TestDuplexConnection implements DuplexConnection { private final FluxSink receivedSink; private final MonoProcessor onClose; private final ConcurrentLinkedQueue> sendSubscribers; + private final ByteBufAllocator allocator; private volatile double availability = 1; private volatile int initialSendRequestN = Integer.MAX_VALUE; - public TestDuplexConnection() { - sent = new LinkedBlockingQueue<>(); - received = DirectProcessor.create(); - receivedSink = received.sink(); - sentPublisher = DirectProcessor.create(); - sendSink = sentPublisher.sink(); - sendSubscribers = new ConcurrentLinkedQueue<>(); - onClose = MonoProcessor.create(); + public TestDuplexConnection(ByteBufAllocator allocator) { + this.allocator = allocator; + this.sent = new LinkedBlockingQueue<>(); + this.received = DirectProcessor.create(); + this.receivedSink = received.sink(); + this.sentPublisher = DirectProcessor.create(); + this.sendSink = sentPublisher.sink(); + this.sendSubscribers = new ConcurrentLinkedQueue<>(); + this.onClose = MonoProcessor.create(); } @Override @@ -83,6 +86,11 @@ public Flux receive() { return received; } + @Override + public ByteBufAllocator alloc() { + return allocator; + } + @Override public double availability() { return availability; diff --git a/rsocket-core/src/test/java/io/rsocket/test/util/TestServerTransport.java b/rsocket-core/src/test/java/io/rsocket/test/util/TestServerTransport.java index 5cebf0da1..325496148 100644 --- a/rsocket-core/src/test/java/io/rsocket/test/util/TestServerTransport.java +++ b/rsocket-core/src/test/java/io/rsocket/test/util/TestServerTransport.java @@ -1,12 +1,16 @@ package io.rsocket.test.util; +import io.netty.buffer.ByteBufAllocator; import io.rsocket.Closeable; +import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.transport.ServerTransport; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoProcessor; public class TestServerTransport implements ServerTransport { private final MonoProcessor conn = MonoProcessor.create(); + private final LeaksTrackingByteBufAllocator allocator = + LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); @Override public Mono start(ConnectionAcceptor acceptor, int mtu) { @@ -39,8 +43,12 @@ private void disposeConnection() { } public TestDuplexConnection connect() { - TestDuplexConnection c = new TestDuplexConnection(); + TestDuplexConnection c = new TestDuplexConnection(allocator); conn.onNext(c); return c; } + + public LeaksTrackingByteBufAllocator alloc() { + return allocator; + } } diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/ws/WebSocketHeadersSample.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/ws/WebSocketHeadersSample.java index fd2dcbbd6..24f029845 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/ws/WebSocketHeadersSample.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/ws/WebSocketHeadersSample.java @@ -16,7 +16,6 @@ package io.rsocket.examples.transport.ws; -import io.netty.buffer.ByteBufAllocator; import io.netty.handler.codec.http.HttpResponseStatus; import io.rsocket.AbstractRSocket; import io.rsocket.ConnectionSetupPayload; @@ -65,9 +64,7 @@ public static void main(String[] args) { if (in.headers().containsValue("Authorization", "test", true)) { DuplexConnection connection = new ReassemblyDuplexConnection( - new WebsocketDuplexConnection((Connection) in), - ByteBufAllocator.DEFAULT, - false); + new WebsocketDuplexConnection((Connection) in), false); return acceptor.apply(connection).then(out.neverComplete()); } diff --git a/rsocket-micrometer/src/main/java/io/rsocket/micrometer/MicrometerDuplexConnection.java b/rsocket-micrometer/src/main/java/io/rsocket/micrometer/MicrometerDuplexConnection.java index 20d58dcb7..9904c2b24 100644 --- a/rsocket-micrometer/src/main/java/io/rsocket/micrometer/MicrometerDuplexConnection.java +++ b/rsocket-micrometer/src/main/java/io/rsocket/micrometer/MicrometerDuplexConnection.java @@ -20,6 +20,7 @@ import io.micrometer.core.instrument.*; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; import io.rsocket.frame.FrameHeaderFlyweight; import io.rsocket.frame.FrameType; @@ -82,6 +83,11 @@ final class MicrometerDuplexConnection implements DuplexConnection { this.frameCounters = new FrameCounters(connectionType, meterRegistry, tags); } + @Override + public ByteBufAllocator alloc() { + return delegate.alloc(); + } + @Override public void dispose() { delegate.dispose(); diff --git a/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalClientTransport.java b/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalClientTransport.java index 0d6a10391..d69bd65e8 100644 --- a/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalClientTransport.java +++ b/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalClientTransport.java @@ -37,21 +37,39 @@ public final class LocalClientTransport implements ClientTransport { private final String name; - private LocalClientTransport(String name) { + private final ByteBufAllocator allocator; + + private LocalClientTransport(String name, ByteBufAllocator allocator) { this.name = name; + this.allocator = allocator; } /** * Creates a new instance. * - * @param name the name of the {@link ServerTransport} instance to connect to + * @param name the name of the {@link ClientTransport} instance to connect to * @return a new instance * @throws NullPointerException if {@code name} is {@code null} */ public static LocalClientTransport create(String name) { Objects.requireNonNull(name, "name must not be null"); - return new LocalClientTransport(name); + return create(name, ByteBufAllocator.DEFAULT); + } + + /** + * Creates a new instance. + * + * @param name the name of the {@link ClientTransport} instance to connect to + * @param allocator the allocator used by {@link ClientTransport} instance + * @return a new instance + * @throws NullPointerException if {@code name} is {@code null} + */ + public static LocalClientTransport create(String name, ByteBufAllocator allocator) { + Objects.requireNonNull(name, "name must not be null"); + Objects.requireNonNull(allocator, "allocator must not be null"); + + return new LocalClientTransport(name, allocator); } private Mono connect() { @@ -66,9 +84,10 @@ private Mono connect() { UnboundedProcessor out = new UnboundedProcessor<>(); MonoProcessor closeNotifier = MonoProcessor.create(); - server.accept(new LocalDuplexConnection(out, in, closeNotifier)); + server.accept(new LocalDuplexConnection(allocator, out, in, closeNotifier)); - return Mono.just((DuplexConnection) new LocalDuplexConnection(in, out, closeNotifier)); + return Mono.just( + (DuplexConnection) new LocalDuplexConnection(allocator, in, out, closeNotifier)); }); } @@ -80,11 +99,9 @@ public Mono connect(int mtu) { return connect.map( duplexConnection -> { if (mtu > 0) { - return new FragmentationDuplexConnection( - duplexConnection, ByteBufAllocator.DEFAULT, mtu, false, "client"); + return new FragmentationDuplexConnection(duplexConnection, mtu, false, "client"); } else { - return new ReassemblyDuplexConnection( - duplexConnection, ByteBufAllocator.DEFAULT, false); + return new ReassemblyDuplexConnection(duplexConnection, false); } }); } diff --git a/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalDuplexConnection.java b/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalDuplexConnection.java index f9501717c..afaa14f95 100644 --- a/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalDuplexConnection.java +++ b/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalDuplexConnection.java @@ -17,6 +17,7 @@ package io.rsocket.transport.local; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; import java.util.Objects; import org.reactivestreams.Publisher; @@ -28,6 +29,7 @@ /** An implementation of {@link DuplexConnection} that connects inside the same JVM. */ final class LocalDuplexConnection implements DuplexConnection { + private final ByteBufAllocator allocator; private final Flux in; private final MonoProcessor onClose; @@ -42,7 +44,12 @@ final class LocalDuplexConnection implements DuplexConnection { * @param onClose the closing notifier * @throws NullPointerException if {@code in}, {@code out}, or {@code onClose} are {@code null} */ - LocalDuplexConnection(Flux in, Subscriber out, MonoProcessor onClose) { + LocalDuplexConnection( + ByteBufAllocator allocator, + Flux in, + Subscriber out, + MonoProcessor onClose) { + this.allocator = Objects.requireNonNull(allocator, "allocator must not be null"); this.in = Objects.requireNonNull(in, "in must not be null"); this.out = Objects.requireNonNull(out, "out must not be null"); this.onClose = Objects.requireNonNull(onClose, "onClose must not be null"); @@ -82,4 +89,9 @@ public Mono sendOne(ByteBuf frame) { out.onNext(frame); return Mono.empty(); } + + @Override + public ByteBufAllocator alloc() { + return allocator; + } } diff --git a/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalServerTransport.java b/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalServerTransport.java index 329b4e38c..382b4533a 100644 --- a/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalServerTransport.java +++ b/rsocket-transport-local/src/main/java/io/rsocket/transport/local/LocalServerTransport.java @@ -16,7 +16,6 @@ package io.rsocket.transport.local; -import io.netty.buffer.ByteBufAllocator; import io.rsocket.Closeable; import io.rsocket.DuplexConnection; import io.rsocket.fragmentation.FragmentationDuplexConnection; @@ -167,11 +166,9 @@ public void accept(DuplexConnection duplexConnection) { if (mtu > 0) { duplexConnection = - new FragmentationDuplexConnection( - duplexConnection, ByteBufAllocator.DEFAULT, mtu, false, "server"); + new FragmentationDuplexConnection(duplexConnection, mtu, false, "server"); } else { - duplexConnection = - new ReassemblyDuplexConnection(duplexConnection, ByteBufAllocator.DEFAULT, false); + duplexConnection = new ReassemblyDuplexConnection(duplexConnection, false); } acceptor.apply(duplexConnection).subscribe(); diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/TcpDuplexConnection.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/TcpDuplexConnection.java index c9c29f0a9..aa28ab8b9 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/TcpDuplexConnection.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/TcpDuplexConnection.java @@ -62,6 +62,11 @@ public TcpDuplexConnection(Connection connection, boolean encodeLength) { }); } + @Override + public ByteBufAllocator alloc() { + return connection.channel().alloc(); + } + @Override protected void doOnClose() { if (!connection.isDisposed()) { diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/WebsocketDuplexConnection.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/WebsocketDuplexConnection.java index ead297928..0183ef19d 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/WebsocketDuplexConnection.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/WebsocketDuplexConnection.java @@ -16,6 +16,7 @@ package io.rsocket.transport.netty; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import io.rsocket.DuplexConnection; import io.rsocket.internal.BaseDuplexConnection; @@ -53,6 +54,11 @@ public WebsocketDuplexConnection(Connection connection) { }); } + @Override + public ByteBufAllocator alloc() { + return connection.channel().alloc(); + } + @Override protected void doOnClose() { if (!connection.isDisposed()) { diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/TcpClientTransport.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/TcpClientTransport.java index 8059b36bd..8be019f1c 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/TcpClientTransport.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/TcpClientTransport.java @@ -16,7 +16,6 @@ package io.rsocket.transport.netty.client; -import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; import io.rsocket.fragmentation.FragmentationDuplexConnection; import io.rsocket.fragmentation.ReassemblyDuplexConnection; @@ -105,14 +104,9 @@ public Mono connect(int mtu) { c -> { if (mtu > 0) { return new FragmentationDuplexConnection( - new TcpDuplexConnection(c, false), - ByteBufAllocator.DEFAULT, - mtu, - true, - "client"); + new TcpDuplexConnection(c, false), mtu, true, "client"); } else { - return new ReassemblyDuplexConnection( - new TcpDuplexConnection(c), ByteBufAllocator.DEFAULT, false); + return new ReassemblyDuplexConnection(new TcpDuplexConnection(c), false); } }); } diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java index 49a2c2e92..b19621d46 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java @@ -20,7 +20,6 @@ import static io.rsocket.transport.netty.UriUtils.getPort; import static io.rsocket.transport.netty.UriUtils.isSecure; -import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; import io.rsocket.fragmentation.FragmentationDuplexConnection; import io.rsocket.fragmentation.ReassemblyDuplexConnection; @@ -164,11 +163,9 @@ public Mono connect(int mtu) { DuplexConnection connection = new WebsocketDuplexConnection(c); if (mtu > 0) { connection = - new FragmentationDuplexConnection( - connection, ByteBufAllocator.DEFAULT, mtu, false, "client"); + new FragmentationDuplexConnection(connection, mtu, false, "client"); } else { - connection = - new ReassemblyDuplexConnection(connection, ByteBufAllocator.DEFAULT, false); + connection = new ReassemblyDuplexConnection(connection, false); } return connection; }); diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/TcpServerTransport.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/TcpServerTransport.java index d39cc8e67..56dd59d45 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/TcpServerTransport.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/TcpServerTransport.java @@ -16,7 +16,6 @@ package io.rsocket.transport.netty.server; -import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; import io.rsocket.fragmentation.FragmentationDuplexConnection; import io.rsocket.fragmentation.ReassemblyDuplexConnection; @@ -106,15 +105,9 @@ public Mono start(ConnectionAcceptor acceptor, int mtu) { if (mtu > 0) { connection = new FragmentationDuplexConnection( - new TcpDuplexConnection(c, false), - ByteBufAllocator.DEFAULT, - mtu, - true, - "server"); + new TcpDuplexConnection(c, false), mtu, true, "server"); } else { - connection = - new ReassemblyDuplexConnection( - new TcpDuplexConnection(c), ByteBufAllocator.DEFAULT, false); + connection = new ReassemblyDuplexConnection(new TcpDuplexConnection(c), false); } acceptor .apply(connection) diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketRouteTransport.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketRouteTransport.java index 1d8769cc6..83cb010b7 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketRouteTransport.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketRouteTransport.java @@ -18,7 +18,6 @@ import static io.rsocket.frame.FrameLengthFlyweight.FRAME_LENGTH_MASK; -import io.netty.buffer.ByteBufAllocator; import io.rsocket.Closeable; import io.rsocket.DuplexConnection; import io.rsocket.fragmentation.FragmentationDuplexConnection; @@ -105,11 +104,9 @@ public static BiFunction> n return (in, out) -> { DuplexConnection connection = new WebsocketDuplexConnection((Connection) in); if (mtu > 0) { - connection = - new FragmentationDuplexConnection( - connection, ByteBufAllocator.DEFAULT, mtu, false, "server"); + connection = new FragmentationDuplexConnection(connection, mtu, false, "server"); } else { - connection = new ReassemblyDuplexConnection(connection, ByteBufAllocator.DEFAULT, false); + connection = new ReassemblyDuplexConnection(connection, false); } return acceptor.apply(connection).then(out.neverComplete()); }; diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketServerTransport.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketServerTransport.java index 01c519ea3..4a0331c08 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketServerTransport.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketServerTransport.java @@ -18,7 +18,6 @@ import static io.rsocket.frame.FrameLengthFlyweight.FRAME_LENGTH_MASK; -import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; import io.rsocket.fragmentation.FragmentationDuplexConnection; import io.rsocket.fragmentation.ReassemblyDuplexConnection; @@ -129,12 +128,9 @@ public Mono start(ConnectionAcceptor acceptor, int mtu) { new WebsocketDuplexConnection((Connection) in); if (mtu > 0) { connection = - new FragmentationDuplexConnection( - connection, ByteBufAllocator.DEFAULT, mtu, false, "server"); + new FragmentationDuplexConnection(connection, mtu, false, "server"); } else { - connection = - new ReassemblyDuplexConnection( - connection, ByteBufAllocator.DEFAULT, false); + connection = new ReassemblyDuplexConnection(connection, false); } return acceptor.apply(connection).then(out.neverComplete()); }, From 414193f1bb8803a0954ae91dd81d869fba9b2eb4 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Sun, 26 Apr 2020 15:50:31 +0300 Subject: [PATCH 28/62] Send root-cause errors from connection to onClose of RSocket (#797) Co-Authored-By: Rossen Stoyanchev --- .../src/main/java/io/rsocket/Closeable.java | 14 ++- .../io/rsocket/core/RSocketConnector.java | 2 +- .../io/rsocket/core/RSocketRequester.java | 25 +++-- .../io/rsocket/core/RSocketResponder.java | 93 ++++++++++--------- .../rsocket/resume/ResumeIntegrationTest.java | 29 +----- 5 files changed, 80 insertions(+), 83 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/Closeable.java b/rsocket-core/src/main/java/io/rsocket/Closeable.java index 5eb871e18..2ea9a0371 100644 --- a/rsocket-core/src/main/java/io/rsocket/Closeable.java +++ b/rsocket-core/src/main/java/io/rsocket/Closeable.java @@ -16,17 +16,21 @@ package io.rsocket; +import org.reactivestreams.Subscriber; import reactor.core.Disposable; import reactor.core.publisher.Mono; -/** */ +/** An interface which allows listening to when a specific instance of this interface is closed */ public interface Closeable extends Disposable { /** - * Returns a {@code Publisher} that completes when this {@code RSocket} is closed. A {@code - * RSocket} can be closed by explicitly calling {@link RSocket#dispose()} or when the underlying - * transport connection is closed. + * Returns a {@link Mono} that terminates when the instance is terminated by any reason. Note, in + * case of error termination, the cause of error will be propagated as an error signal through + * {@link org.reactivestreams.Subscriber#onError(Throwable)}. Otherwise, {@link + * Subscriber#onComplete()} will be called. * - * @return A {@code Publisher} that completes when this {@code RSocket} close is complete. + * @return a {@link Mono} to track completion with success or error of the underlying resource. + * When the underlying resource is an `RSocket`, the {@code Mono} exposes stream 0 (i.e. + * connection level) errors. */ Mono onClose(); } diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java index dc5be2430..146fd599d 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java @@ -50,7 +50,7 @@ public class RSocketConnector { private static final int MIN_MTU_SIZE = 64; private static final BiConsumer INVALIDATE_FUNCTION = - (r, i) -> r.onClose().subscribe(null, null, i::invalidate); + (r, i) -> r.onClose().subscribe(null, __ -> i.invalidate(), i::invalidate); private Payload setupPayload = EmptyPayload.INSTANCE; private String metadataMimeType = "application/binary"; diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index 04c766eec..21112c24d 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -53,6 +53,7 @@ import io.rsocket.lease.RequesterLeaseHandler; import io.rsocket.util.MonoLifecycleHandler; import java.nio.channels.ClosedChannelException; +import java.util.concurrent.CancellationException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.Consumer; @@ -67,6 +68,7 @@ import reactor.core.publisher.BaseSubscriber; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.core.publisher.MonoProcessor; import reactor.core.publisher.SignalType; import reactor.core.publisher.UnicastProcessor; import reactor.util.concurrent.Queues; @@ -106,6 +108,7 @@ class RSocketRequester implements RSocket { private final ByteBufAllocator allocator; private final KeepAliveFramesAcceptor keepAliveFramesAcceptor; private volatile Throwable terminationError; + private final MonoProcessor onClose; RSocketRequester( DuplexConnection connection, @@ -126,14 +129,15 @@ class RSocketRequester implements RSocket { this.leaseHandler = leaseHandler; this.senders = new SynchronizedIntObjectHashMap<>(); this.receivers = new SynchronizedIntObjectHashMap<>(); + this.onClose = MonoProcessor.create(); // DO NOT Change the order here. The Send processor must be subscribed to before receiving this.sendProcessor = new UnboundedProcessor<>(); connection .onClose() - .doFinally(signalType -> tryTerminateOnConnectionClose()) - .subscribe(null, errorConsumer); + .or(onClose) + .subscribe(null, this::tryTerminateOnConnectionError, this::tryTerminateOnConnectionClose); connection.send(sendProcessor).subscribe(null, this::handleSendProcessorError); connection.receive().subscribe(this::handleIncomingFrames, errorConsumer); @@ -181,17 +185,17 @@ public double availability() { @Override public void dispose() { - connection.dispose(); + tryTerminate(() -> new CancellationException("Disposed")); } @Override public boolean isDisposed() { - return connection.isDisposed(); + return onClose.isDisposed(); } @Override public Mono onClose() { - return connection.onClose(); + return onClose; } private Mono handleFireAndForget(Payload payload) { @@ -619,6 +623,10 @@ private void tryTerminateOnKeepAlive(KeepAlive keepAlive) { String.format("No keep-alive acks for %d ms", keepAlive.getTimeout().toMillis()))); } + private void tryTerminateOnConnectionError(Throwable e) { + tryTerminate(() -> e); + } + private void tryTerminateOnConnectionClose() { tryTerminate(() -> CLOSED_CHANNEL_EXCEPTION); } @@ -627,16 +635,16 @@ private void tryTerminateOnZeroError(ByteBuf errorFrame) { tryTerminate(() -> Exceptions.from(0, errorFrame)); } - private void tryTerminate(Supplier errorSupplier) { + private void tryTerminate(Supplier errorSupplier) { if (terminationError == null) { - Exception e = errorSupplier.get(); + Throwable e = errorSupplier.get(); if (TERMINATION_ERROR.compareAndSet(this, null, e)) { terminate(e); } } } - private void terminate(Exception e) { + private void terminate(Throwable e) { connection.dispose(); leaseHandler.dispose(); @@ -668,6 +676,7 @@ private void terminate(Exception e) { receivers.clear(); sendProcessor.dispose(); errorConsumer.accept(e); + onClose.onError(e); } private void removeStreamReceiver(int streamId) { diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java index f992b7577..b5b298e14 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java @@ -34,8 +34,12 @@ import io.rsocket.internal.SynchronizedIntObjectHashMap; import io.rsocket.internal.UnboundedProcessor; import io.rsocket.lease.ResponderLeaseHandler; +import java.nio.channels.ClosedChannelException; +import java.util.concurrent.CancellationException; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.Consumer; import java.util.function.LongConsumer; +import java.util.function.Supplier; import javax.annotation.Nullable; import org.reactivestreams.Processor; import org.reactivestreams.Publisher; @@ -58,6 +62,7 @@ class RSocketResponder implements ResponderRSocket { } } }; + private static final Exception CLOSED_CHANNEL_EXCEPTION = new ClosedChannelException(); private final DuplexConnection connection; private final RSocket requestHandler; @@ -65,6 +70,13 @@ class RSocketResponder implements ResponderRSocket { private final PayloadDecoder payloadDecoder; private final Consumer errorConsumer; private final ResponderLeaseHandler leaseHandler; + private final Disposable leaseHandlerDisposable; + private final MonoProcessor onClose; + + private volatile Throwable terminationError; + private static final AtomicReferenceFieldUpdater TERMINATION_ERROR = + AtomicReferenceFieldUpdater.newUpdater( + RSocketResponder.class, Throwable.class, "terminationError"); private final int mtu; @@ -94,28 +106,21 @@ class RSocketResponder implements ResponderRSocket { this.leaseHandler = leaseHandler; this.sendingSubscriptions = new SynchronizedIntObjectHashMap<>(); this.channelProcessors = new SynchronizedIntObjectHashMap<>(); + this.onClose = MonoProcessor.create(); // DO NOT Change the order here. The Send processor must be subscribed to before receiving // connections this.sendProcessor = new UnboundedProcessor<>(); - connection - .send(sendProcessor) - .doFinally(this::handleSendProcessorCancel) - .subscribe(null, this::handleSendProcessorError); + connection.send(sendProcessor).subscribe(null, this::handleSendProcessorError); - Disposable receiveDisposable = connection.receive().subscribe(this::handleFrame, errorConsumer); - Disposable sendLeaseDisposable = leaseHandler.send(sendProcessor::onNextPrioritized); + connection.receive().subscribe(this::handleFrame, errorConsumer); + leaseHandlerDisposable = leaseHandler.send(sendProcessor::onNextPrioritized); this.connection .onClose() - .doFinally( - s -> { - cleanup(); - receiveDisposable.dispose(); - sendLeaseDisposable.dispose(); - }) - .subscribe(null, errorConsumer); + .or(onClose) + .subscribe(null, this::tryTerminateOnConnectionError, this::tryTerminateOnConnectionClose); } private void handleSendProcessorError(Throwable t) { @@ -142,32 +147,21 @@ private void handleSendProcessorError(Throwable t) { }); } - private void handleSendProcessorCancel(SignalType t) { - if (SignalType.ON_ERROR == t) { - return; - } + private void tryTerminateOnConnectionError(Throwable e) { + tryTerminate(() -> e); + } - sendingSubscriptions - .values() - .forEach( - subscription -> { - try { - subscription.cancel(); - } catch (Throwable e) { - errorConsumer.accept(e); - } - }); + private void tryTerminateOnConnectionClose() { + tryTerminate(() -> CLOSED_CHANNEL_EXCEPTION); + } - channelProcessors - .values() - .forEach( - subscription -> { - try { - subscription.onComplete(); - } catch (Throwable e) { - errorConsumer.accept(e); - } - }); + private void tryTerminate(Supplier errorSupplier) { + if (terminationError == null) { + Throwable e = errorSupplier.get(); + if (TERMINATION_ERROR.compareAndSet(this, null, e)) { + cleanup(e); + } + } } @Override @@ -250,23 +244,25 @@ public Mono metadataPush(Payload payload) { @Override public void dispose() { - connection.dispose(); + tryTerminate(() -> new CancellationException("Disposed")); } @Override public boolean isDisposed() { - return connection.isDisposed(); + return onClose.isDisposed(); } @Override public Mono onClose() { - return connection.onClose(); + return onClose; } - private void cleanup() { + private void cleanup(Throwable e) { cleanUpSendingSubscriptions(); - cleanUpChannelProcessors(); + cleanUpChannelProcessors(e); + connection.dispose(); + leaseHandlerDisposable.dispose(); requestHandler.dispose(); sendProcessor.dispose(); } @@ -276,8 +272,17 @@ private synchronized void cleanUpSendingSubscriptions() { sendingSubscriptions.clear(); } - private synchronized void cleanUpChannelProcessors() { - channelProcessors.values().forEach(Processor::onComplete); + private synchronized void cleanUpChannelProcessors(Throwable e) { + channelProcessors + .values() + .forEach( + payloadPayloadProcessor -> { + try { + payloadPayloadProcessor.onError(e); + } catch (Throwable t) { + // noops + } + }); channelProcessors.clear(); } diff --git a/rsocket-examples/src/test/java/io/rsocket/resume/ResumeIntegrationTest.java b/rsocket-examples/src/test/java/io/rsocket/resume/ResumeIntegrationTest.java index 685cef41e..6ae870a15 100644 --- a/rsocket-examples/src/test/java/io/rsocket/resume/ResumeIntegrationTest.java +++ b/rsocket-examples/src/test/java/io/rsocket/resume/ResumeIntegrationTest.java @@ -35,13 +35,11 @@ import java.nio.channels.ClosedChannelException; import java.time.Duration; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import reactor.core.publisher.ReplayProcessor; import reactor.core.scheduler.Schedulers; import reactor.test.StepVerifier; @@ -105,7 +103,6 @@ public void reconnectOnMissingSession() { DisconnectableClientTransport clientTransport = new DisconnectableClientTransport(clientTransport(closeable.address())); - ErrorConsumer errorConsumer = new ErrorConsumer(); int clientSessionDurationSeconds = 10; RSocket rSocket = newClientRSocket(clientTransport, clientSessionDurationSeconds).block(); @@ -118,12 +115,11 @@ public void reconnectOnMissingSession() { .expectError() .verify(Duration.ofSeconds(5)); - StepVerifier.create(errorConsumer.errors().next()) - .expectNextMatches( + StepVerifier.create(rSocket.onClose()) + .expectErrorMatches( err -> err instanceof RejectedResumeException && "unknown resume token".equals(err.getMessage())) - .expectComplete() .verify(Duration.ofSeconds(5)); } @@ -134,23 +130,19 @@ void serverMissingResume() { .bind(serverTransport(SERVER_HOST, SERVER_PORT)) .block(); - ErrorConsumer errorConsumer = new ErrorConsumer(); - RSocket rSocket = RSocketConnector.create() .resume(new Resume()) .connect(clientTransport(closeableChannel.address())) .block(); - StepVerifier.create(errorConsumer.errors().next().doFinally(s -> closeableChannel.dispose())) - .expectNextMatches( + StepVerifier.create(rSocket.onClose().doFinally(s -> closeableChannel.dispose())) + .expectErrorMatches( err -> err instanceof UnsupportedSetupException && "resume not supported".equals(err.getMessage())) - .expectComplete() .verify(Duration.ofSeconds(5)); - StepVerifier.create(rSocket.onClose()).expectComplete().verify(Duration.ofSeconds(5)); Assertions.assertThat(rSocket.isDisposed()).isTrue(); } @@ -162,19 +154,6 @@ static ServerTransport serverTransport(String host, int port) return TcpServerTransport.create(host, port); } - private static class ErrorConsumer implements Consumer { - private final ReplayProcessor errors = ReplayProcessor.create(); - - public Flux errors() { - return errors; - } - - @Override - public void accept(Throwable throwable) { - errors.onNext(throwable); - } - } - private static Flux testRequest() { return Flux.interval(Duration.ofMillis(50)) .map(v -> DefaultPayload.create("client_request")) From 00acd1edcca0cd3666841b53e8933c0de0ae2a2c Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 27 Apr 2020 09:09:05 +0100 Subject: [PATCH 29/62] Deprecate ResumeStrategy and replace with Reactor Retry (#798) --- .../main/java/io/rsocket/RSocketFactory.java | 11 ++++- .../io/rsocket/core/RSocketConnector.java | 27 +++++------ .../src/main/java/io/rsocket/core/Resume.java | 30 +++++++----- .../rsocket/resume/ClientRSocketSession.java | 46 ++++++++++++------- .../ExponentialBackoffResumeStrategy.java | 6 +++ .../resume/PeriodicResumeStrategy.java | 6 +++ .../io/rsocket/resume/ResumeStrategy.java | 8 +++- .../tcp/resume/ResumeFileTransfer.java | 26 +++-------- .../rsocket/resume/ResumeIntegrationTest.java | 5 +- 9 files changed, 96 insertions(+), 69 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java b/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java index 43d344b9d..f36a84241 100644 --- a/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java +++ b/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java @@ -17,6 +17,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; import io.rsocket.core.RSocketConnector; import io.rsocket.core.RSocketServer; import io.rsocket.core.Resume; @@ -26,6 +27,7 @@ import io.rsocket.plugins.DuplexConnectionInterceptor; import io.rsocket.plugins.RSocketInterceptor; import io.rsocket.plugins.SocketAcceptorInterceptor; +import io.rsocket.resume.ClientResume; import io.rsocket.resume.ResumableFramesStore; import io.rsocket.resume.ResumeStrategy; import io.rsocket.transport.ClientTransport; @@ -97,6 +99,9 @@ default Start transport(ServerTransport transport) { /** Factory to create and configure an RSocket client, and connect to a server. */ public static class ClientRSocketFactory implements ClientTransportAcceptor { + private static final ClientResume CLIENT_RESUME = + new ClientResume(Duration.ofMinutes(2), Unpooled.EMPTY_BUFFER); + private final RSocketConnector connector; private Duration tickPeriod = Duration.ofSeconds(20); @@ -348,9 +353,11 @@ public ClientRSocketFactory resumeStreamTimeout(Duration streamTimeout) { return this; } - public ClientRSocketFactory resumeStrategy(Supplier resumeStrategy) { + public ClientRSocketFactory resumeStrategy(Supplier strategy) { resume(); - resume.resumeStrategy(resumeStrategy); + resume.retry( + Retry.from( + signals -> signals.flatMap(s -> strategy.get().apply(CLIENT_RESUME, s.failure())))); return this; } diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java index 146fd599d..ed20f892e 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java @@ -254,8 +254,17 @@ public Mono connect(Supplier transportSupplier) { DuplexConnection wrappedConnection; if (resume != null) { - ClientRSocketSession session = createSession(connection, connectionMono); - resumeToken = session.token(); + resumeToken = resume.getTokenSupplier().get(); + ClientRSocketSession session = + new ClientRSocketSession( + connection, + resume.getSessionDuration(), + resume.getRetry(), + resume.getStoreFactory(CLIENT_TAG).apply(resumeToken), + resume.getStreamTimeout(), + resume.isCleanupStoreOnKeepAlive()) + .continueWith(connectionMono) + .resumeToken(resumeToken); keepAliveHandler = new KeepAliveHandler.ResumableKeepAliveHandler(session.resumableConnection()); wrappedConnection = session.resumableConnection(); @@ -343,18 +352,4 @@ public Mono connect(Supplier transportSupplier) { } }); } - - private ClientRSocketSession createSession( - DuplexConnection connection, Mono connectionMono) { - ByteBuf resumeToken = resume.getTokenSupplier().get(); - ClientRSocketSession session = - new ClientRSocketSession( - connection, - resume.getSessionDuration(), - resume.getResumeStrategySupplier(), - resume.getStoreFactory(CLIENT_TAG).apply(resumeToken), - resume.getStreamTimeout(), - resume.isCleanupStoreOnKeepAlive()); - return session.continueWith(connectionMono).resumeToken(resumeToken); - } } diff --git a/rsocket-core/src/main/java/io/rsocket/core/Resume.java b/rsocket-core/src/main/java/io/rsocket/core/Resume.java index 4b0edab00..aedcc9e5e 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/Resume.java +++ b/rsocket-core/src/main/java/io/rsocket/core/Resume.java @@ -17,23 +17,29 @@ import io.netty.buffer.ByteBuf; import io.rsocket.frame.ResumeFrameFlyweight; -import io.rsocket.resume.ExponentialBackoffResumeStrategy; import io.rsocket.resume.InMemoryResumableFramesStore; import io.rsocket.resume.ResumableFramesStore; -import io.rsocket.resume.ResumeStrategy; import java.time.Duration; import java.util.function.Function; import java.util.function.Supplier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.util.retry.Retry; public class Resume { - Duration sessionDuration = Duration.ofMinutes(2); - Duration streamTimeout = Duration.ofSeconds(10); - boolean cleanupStoreOnKeepAlive; - Function storeFactory; + private static final Logger logger = LoggerFactory.getLogger(Resume.class); + + private Duration sessionDuration = Duration.ofMinutes(2); + private Duration streamTimeout = Duration.ofSeconds(10); + private boolean cleanupStoreOnKeepAlive; + private Function storeFactory; private Supplier tokenSupplier = ResumeFrameFlyweight::generateResumeToken; - private Supplier resumeStrategySupplier = - () -> new ExponentialBackoffResumeStrategy(Duration.ofSeconds(1), Duration.ofSeconds(16), 2); + private Retry retry = + Retry.backoff(Long.MAX_VALUE, Duration.ofSeconds(1)) + .maxBackoff(Duration.ofSeconds(16)) + .jitter(1.0) + .doBeforeRetry(signal -> logger.debug("Connection error", signal.failure())); public Resume() {} @@ -63,8 +69,8 @@ public Resume token(Supplier supplier) { return this; } - public Resume resumeStrategy(Supplier supplier) { - this.resumeStrategySupplier = supplier; + public Resume retry(Retry retry) { + this.retry = retry; return this; } @@ -90,7 +96,7 @@ Supplier getTokenSupplier() { return tokenSupplier; } - Supplier getResumeStrategySupplier() { - return resumeStrategySupplier; + Retry getRetry() { + return retry; } } diff --git a/rsocket-core/src/main/java/io/rsocket/resume/ClientRSocketSession.java b/rsocket-core/src/main/java/io/rsocket/resume/ClientRSocketSession.java index 509fb5168..01b6dfeae 100644 --- a/rsocket-core/src/main/java/io/rsocket/resume/ClientRSocketSession.java +++ b/rsocket-core/src/main/java/io/rsocket/resume/ClientRSocketSession.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2019 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,10 +26,11 @@ import io.rsocket.internal.ClientServerInputMultiplexer; import java.time.Duration; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.util.retry.Retry; public class ClientRSocketSession implements RSocketSession> { private static final Logger logger = LoggerFactory.getLogger(ClientRSocketSession.class); @@ -42,7 +43,7 @@ public class ClientRSocketSession implements RSocketSession resumeStrategy, + Retry retry, ResumableFramesStore resumableFramesStore, Duration resumeStreamTimeout, boolean cleanupStoreOnKeepAlive) { @@ -63,24 +64,13 @@ public ClientRSocketSession( .flatMap( err -> { logger.debug("Client session connection error. Starting new connection"); - ResumeStrategy reconnectOnError = resumeStrategy.get(); - ClientResume clientResume = new ClientResume(resumeSessionDuration, resumeToken); AtomicBoolean once = new AtomicBoolean(); return newConnection .delaySubscription( once.compareAndSet(false, true) - ? reconnectOnError.apply(clientResume, err) + ? retry.generateCompanion(Flux.just(new RetrySignal(err))) : Mono.empty()) - .retryWhen( - errors -> - errors - .doOnNext( - retryErr -> - logger.debug("Resumption reconnection error", retryErr)) - .flatMap( - retryErr -> - Mono.from(reconnectOnError.apply(clientResume, retryErr)) - .doOnNext(v -> logger.debug("Retrying with: {}", v)))) + .retryWhen(retry) .timeout(resumeSessionDuration); }) .map(ClientServerInputMultiplexer::new) @@ -177,4 +167,28 @@ private static long remotePos(ByteBuf resumeOkFrame) { private static ConnectionErrorException errorFrameThrowable(long impliedPos) { return new ConnectionErrorException("resumption_server_pos=[" + impliedPos + "]"); } + + private static class RetrySignal implements Retry.RetrySignal { + + private final Throwable ex; + + RetrySignal(Throwable ex) { + this.ex = ex; + } + + @Override + public long totalRetries() { + return 0; + } + + @Override + public long totalRetriesInARow() { + return 0; + } + + @Override + public Throwable failure() { + return ex; + } + } } diff --git a/rsocket-core/src/main/java/io/rsocket/resume/ExponentialBackoffResumeStrategy.java b/rsocket-core/src/main/java/io/rsocket/resume/ExponentialBackoffResumeStrategy.java index b46ac864b..461be02d2 100644 --- a/rsocket-core/src/main/java/io/rsocket/resume/ExponentialBackoffResumeStrategy.java +++ b/rsocket-core/src/main/java/io/rsocket/resume/ExponentialBackoffResumeStrategy.java @@ -21,7 +21,13 @@ import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.util.retry.Retry; +/** + * @deprecated as of 1.0 RC7 in favor of passing {@link Retry#backoff(long, Duration)} to {@link + * io.rsocket.core.Resume#retry(Retry)}. + */ +@Deprecated public class ExponentialBackoffResumeStrategy implements ResumeStrategy { private volatile Duration next; private final Duration firstBackoff; diff --git a/rsocket-core/src/main/java/io/rsocket/resume/PeriodicResumeStrategy.java b/rsocket-core/src/main/java/io/rsocket/resume/PeriodicResumeStrategy.java index abfefe0b1..bd447c8a9 100644 --- a/rsocket-core/src/main/java/io/rsocket/resume/PeriodicResumeStrategy.java +++ b/rsocket-core/src/main/java/io/rsocket/resume/PeriodicResumeStrategy.java @@ -19,7 +19,13 @@ import java.time.Duration; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; +import reactor.util.retry.Retry; +/** + * @deprecated as of 1.0 RC7 in favor of passing {@link Retry#fixedDelay(long, Duration)} to {@link + * io.rsocket.core.Resume#retry(Retry)}. + */ +@Deprecated public class PeriodicResumeStrategy implements ResumeStrategy { private final Duration interval; diff --git a/rsocket-core/src/main/java/io/rsocket/resume/ResumeStrategy.java b/rsocket-core/src/main/java/io/rsocket/resume/ResumeStrategy.java index 903431192..d9dec9f54 100644 --- a/rsocket-core/src/main/java/io/rsocket/resume/ResumeStrategy.java +++ b/rsocket-core/src/main/java/io/rsocket/resume/ResumeStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2019 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,12 @@ import java.util.function.BiFunction; import org.reactivestreams.Publisher; +import reactor.util.retry.Retry; +/** + * @deprecated as of 1.0 RC7 in favor of using {@link io.rsocket.core.Resume#retry(Retry)} via + * {@link io.rsocket.core.RSocketConnector} or {@link io.rsocket.core.RSocketServer}. + */ +@Deprecated @FunctionalInterface public interface ResumeStrategy extends BiFunction> {} diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/ResumeFileTransfer.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/ResumeFileTransfer.java index 3cae64409..d8dd3dd2d 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/ResumeFileTransfer.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/ResumeFileTransfer.java @@ -22,17 +22,14 @@ import io.rsocket.core.RSocketConnector; import io.rsocket.core.RSocketServer; import io.rsocket.core.Resume; -import io.rsocket.resume.ClientResume; -import io.rsocket.resume.PeriodicResumeStrategy; -import io.rsocket.resume.ResumeStrategy; import io.rsocket.transport.netty.client.TcpClientTransport; import io.rsocket.transport.netty.server.CloseableChannel; import io.rsocket.transport.netty.server.TcpServerTransport; import io.rsocket.util.DefaultPayload; import java.time.Duration; -import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.util.retry.Retry; public class ResumeFileTransfer { /*amount of file chunks requested by subscriber: n, refilled on n/2 of received items*/ @@ -43,8 +40,11 @@ public static void main(String[] args) { Resume resume = new Resume() .sessionDuration(Duration.ofMinutes(5)) - .resumeStrategy( - () -> new VerboseResumeStrategy(new PeriodicResumeStrategy(Duration.ofSeconds(1)))); + .retry( + Retry.fixedDelay(Long.MAX_VALUE, Duration.ofSeconds(1)) + .doBeforeRetry( + retrySignal -> + System.out.println("Disconnected. Trying to resume connection..."))); CloseableChannel server = RSocketServer.create((setup, rSocket) -> Mono.just(new FileServer(requestCodec))) @@ -88,20 +88,6 @@ public Flux requestStream(Payload payload) { } } - private static class VerboseResumeStrategy implements ResumeStrategy { - private final ResumeStrategy resumeStrategy; - - public VerboseResumeStrategy(ResumeStrategy resumeStrategy) { - this.resumeStrategy = resumeStrategy; - } - - @Override - public Publisher apply(ClientResume clientResume, Throwable throwable) { - return Flux.from(resumeStrategy.apply(clientResume, throwable)) - .doOnNext(v -> System.out.println("Disconnected. Trying to resume connection...")); - } - } - private static class RequestCodec { public Payload encode(Request request) { diff --git a/rsocket-examples/src/test/java/io/rsocket/resume/ResumeIntegrationTest.java b/rsocket-examples/src/test/java/io/rsocket/resume/ResumeIntegrationTest.java index 6ae870a15..bd2db39c7 100644 --- a/rsocket-examples/src/test/java/io/rsocket/resume/ResumeIntegrationTest.java +++ b/rsocket-examples/src/test/java/io/rsocket/resume/ResumeIntegrationTest.java @@ -42,6 +42,7 @@ import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; import reactor.test.StepVerifier; +import reactor.util.retry.Retry; @SlowTest public class ResumeIntegrationTest { @@ -155,7 +156,7 @@ static ServerTransport serverTransport(String host, int port) } private static Flux testRequest() { - return Flux.interval(Duration.ofMillis(50)) + return Flux.interval(Duration.ofMillis(500)) .map(v -> DefaultPayload.create("client_request")) .onBackpressureDrop(); } @@ -183,7 +184,7 @@ private static Mono newClientRSocket( .sessionDuration(Duration.ofSeconds(sessionDurationSeconds)) .storeFactory(t -> new InMemoryResumableFramesStore("client", 500_000)) .cleanupStoreOnKeepAlive() - .resumeStrategy(() -> new PeriodicResumeStrategy(Duration.ofSeconds(1)))) + .retry(Retry.fixedDelay(Long.MAX_VALUE, Duration.ofSeconds(1)))) .keepAlive(Duration.ofSeconds(5), Duration.ofMinutes(5)) .connect(clientTransport); } From d5e8a45bbb6536653a766fcc1400e49350c4596a Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Mon, 27 Apr 2020 13:07:39 +0300 Subject: [PATCH 30/62] Fixes possible elements' leaks occurrence (#799) --- .../rsocket/internal/UnboundedProcessor.java | 100 ++++++++++++------ .../resume/ResumableDuplexConnection.java | 4 + 2 files changed, 70 insertions(+), 34 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/internal/UnboundedProcessor.java b/rsocket-core/src/main/java/io/rsocket/internal/UnboundedProcessor.java index fe664e843..cb8b5d63d 100644 --- a/rsocket-core/src/main/java/io/rsocket/internal/UnboundedProcessor.java +++ b/rsocket-core/src/main/java/io/rsocket/internal/UnboundedProcessor.java @@ -43,28 +43,43 @@ public final class UnboundedProcessor extends FluxProcessor implements Fuseable.QueueSubscription, Fuseable { + final Queue queue; + final Queue priorityQueue; + + volatile boolean done; + Throwable error; + // important to not loose the downstream too early and miss discard hook, while + // having relevant hasDownstreams() + boolean hasDownstream; + volatile CoreSubscriber actual; + + volatile boolean cancelled; + + volatile int once; + @SuppressWarnings("rawtypes") static final AtomicIntegerFieldUpdater ONCE = AtomicIntegerFieldUpdater.newUpdater(UnboundedProcessor.class, "once"); + volatile int wip; + @SuppressWarnings("rawtypes") static final AtomicIntegerFieldUpdater WIP = AtomicIntegerFieldUpdater.newUpdater(UnboundedProcessor.class, "wip"); + volatile int discardGuard; + + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater DISCARD_GUARD = + AtomicIntegerFieldUpdater.newUpdater(UnboundedProcessor.class, "discardGuard"); + + volatile long requested; + @SuppressWarnings("rawtypes") static final AtomicLongFieldUpdater REQUESTED = AtomicLongFieldUpdater.newUpdater(UnboundedProcessor.class, "requested"); - final Queue queue; - final Queue priorityQueue; - volatile boolean done; - Throwable error; - volatile CoreSubscriber actual; - volatile boolean cancelled; - volatile int once; - volatile int wip; - volatile long requested; - volatile boolean outputFused; + boolean outputFused; public UnboundedProcessor() { this.queue = new MpscUnboundedArrayQueue<>(Queues.SMALL_BUFFER_SIZE); @@ -79,6 +94,7 @@ public int getBufferSize() { @Override public Object scanUnsafe(Attr key) { if (Attr.BUFFERED == key) return queue.size(); + if (Attr.PREFETCH == key) return Integer.MAX_VALUE; return super.scanUnsafe(key); } @@ -143,8 +159,8 @@ void drainFused(Subscriber a) { for (; ; ) { if (cancelled) { - clear(); - actual = null; + this.clear(); + hasDownstream = false; return; } @@ -153,7 +169,7 @@ void drainFused(Subscriber a) { a.onNext(null); if (d) { - actual = null; + hasDownstream = false; Throwable ex = error; if (ex != null) { @@ -173,6 +189,9 @@ void drainFused(Subscriber a) { public void drain() { if (WIP.getAndIncrement(this) != 0) { + if (cancelled) { + this.clear(); + } return; } @@ -199,13 +218,13 @@ public void drain() { boolean checkTerminated(boolean d, boolean empty, Subscriber a) { if (cancelled) { - clear(); - actual = null; + this.clear(); + hasDownstream = false; return true; } if (d && empty) { Throwable e = error; - actual = null; + hasDownstream = false; if (e != null) { a.onError(e); } else { @@ -226,10 +245,6 @@ public void onSubscribe(Subscription s) { } } - public long available() { - return requested; - } - @Override public int getPrefetch() { return Integer.MAX_VALUE; @@ -308,7 +323,7 @@ public void subscribe(CoreSubscriber actual) { actual.onSubscribe(this); this.actual = actual; if (cancelled) { - this.actual = null; + this.hasDownstream = false; } else { drain(); } @@ -335,8 +350,8 @@ public void cancel() { cancelled = true; if (WIP.getAndIncrement(this) == 0) { - clear(); - actual = null; + this.clear(); + hasDownstream = false; } } @@ -362,16 +377,29 @@ public boolean isEmpty() { @Override public void clear() { - while (!queue.isEmpty()) { - T t = queue.poll(); - if (t != null) { - release(t); - } + if (DISCARD_GUARD.getAndIncrement(this) != 0) { + return; } - while (!priorityQueue.isEmpty()) { - T t = priorityQueue.poll(); - if (t != null) { - release(t); + + int missed = 1; + + for (; ; ) { + while (!queue.isEmpty()) { + T t = queue.poll(); + if (t != null) { + release(t); + } + } + while (!priorityQueue.isEmpty()) { + T t = priorityQueue.poll(); + if (t != null) { + release(t); + } + } + + missed = DISCARD_GUARD.addAndGet(this, -missed); + if (missed == 0) { + break; } } } @@ -413,14 +441,18 @@ public long downstreamCount() { @Override public boolean hasDownstreams() { - return actual != null; + return hasDownstream; } void release(T t) { if (t instanceof ReferenceCounted) { ReferenceCounted refCounted = (ReferenceCounted) t; if (refCounted.refCnt() > 0) { - refCounted.release(); + try { + refCounted.release(); + } catch (Throwable ex) { + // no ops + } } } } diff --git a/rsocket-core/src/main/java/io/rsocket/resume/ResumableDuplexConnection.java b/rsocket-core/src/main/java/io/rsocket/resume/ResumableDuplexConnection.java index b9c93f4cd..980de2de1 100644 --- a/rsocket-core/src/main/java/io/rsocket/resume/ResumableDuplexConnection.java +++ b/rsocket-core/src/main/java/io/rsocket/resume/ResumableDuplexConnection.java @@ -223,6 +223,10 @@ public boolean isDisposed() { } private void sendFrame(ByteBuf f) { + if (disposed.get()) { + f.release(); + return; + } /*resuming from store so no need to save again*/ if (state != State.RESUME && isResumableFrame(f)) { resumeSaveFrames.onNext(f); From 97bf015786fe07e0850ba486b5505cb7d949e6a3 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Mon, 27 Apr 2020 13:50:28 +0300 Subject: [PATCH 31/62] adds how to access snapshots Signed-off-by: Oleh Dokuka --- README.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bb427cc35..538587154 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,26 @@ Releases are available via Maven Central. Example: ```groovy +repositories { + mavenCentral() +} dependencies { implementation 'io.rsocket:rsocket-core:1.0.0-RC6' implementation 'io.rsocket:rsocket-transport-netty:1.0.0-RC6' -// implementation 'io.rsocket:rsocket-core:1.0.0-RC4-SNAPSHOT' -// implementation 'io.rsocket:rsocket-transport-netty:1.0.0-RC4-SNAPSHOT' +} +``` + +Snapshots are available via [oss.jfrog.org](oss.jfrog.org) (OJO). + +Example: + +```groovy +repositories { + maven { url 'https://oss.jfrog.org/oss-snapshot-local' } +} +dependencies { + implementation 'io.rsocket:rsocket-core:1.0.0-RC7-SNAPSHOT' + implementation 'io.rsocket:rsocket-transport-netty:1.0.0-RC7-SNAPSHOT' } ``` From 482c0c4296e75d540c087ec24d8d8fd2e6a02c1a Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 27 Apr 2020 12:55:51 +0100 Subject: [PATCH 32/62] Fix logging in examples and load-balancer tests (#800) --- rsocket-examples/build.gradle | 2 +- .../tcp/channel/ChannelEchoClient.java | 6 +++- .../tcp/requestresponse/HelloWorldClient.java | 34 +++++++----------- .../examples/transport/tcp/resume/Files.java | 13 +++++-- .../tcp/resume/ResumeFileTransfer.java | 6 +++- .../transport/tcp/stream/StreamingClient.java | 6 +++- .../src/main/resources/log4j.properties | 20 ----------- .../src/main/resources/logback.xml | 35 +++++++++++++++++++ .../src/test/resources/log4j.properties | 21 ----------- .../src/test/resources/logback-test.xml | 33 +++++++++++++++++ rsocket-load-balancer/build.gradle | 1 + .../src/test/resources/log4j.properties | 20 ----------- .../src/test/resources/logback-test.xml | 33 +++++++++++++++++ 13 files changed, 141 insertions(+), 89 deletions(-) delete mode 100644 rsocket-examples/src/main/resources/log4j.properties create mode 100644 rsocket-examples/src/main/resources/logback.xml delete mode 100644 rsocket-examples/src/test/resources/log4j.properties create mode 100644 rsocket-examples/src/test/resources/logback-test.xml delete mode 100644 rsocket-load-balancer/src/test/resources/log4j.properties create mode 100644 rsocket-load-balancer/src/test/resources/logback-test.xml diff --git a/rsocket-examples/build.gradle b/rsocket-examples/build.gradle index 5f63b0761..01e80cfa1 100644 --- a/rsocket-examples/build.gradle +++ b/rsocket-examples/build.gradle @@ -22,13 +22,13 @@ dependencies { implementation project(':rsocket-core') implementation project(':rsocket-transport-local') implementation project(':rsocket-transport-netty') + runtimeOnly 'ch.qos.logback:logback-classic' testImplementation project(':rsocket-test') testImplementation 'org.junit.jupiter:junit-jupiter-api' testImplementation 'org.mockito:mockito-core' testImplementation 'org.assertj:assertj-core' testImplementation 'io.projectreactor:reactor-test' - testImplementation 'ch.qos.logback:logback-classic' // TODO: Remove after JUnit5 migration testCompileOnly 'junit:junit' diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/channel/ChannelEchoClient.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/channel/ChannelEchoClient.java index 44daf8c67..71e48790f 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/channel/ChannelEchoClient.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/channel/ChannelEchoClient.java @@ -28,11 +28,15 @@ import io.rsocket.util.DefaultPayload; import java.time.Duration; import org.reactivestreams.Publisher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; public final class ChannelEchoClient { + private static final Logger logger = LoggerFactory.getLogger(ChannelEchoClient.class); + public static void main(String[] args) { RSocketServer.create(new EchoAcceptor()) .bind(TcpServerTransport.create("localhost", 7000)) @@ -45,7 +49,7 @@ public static void main(String[] args) { .requestChannel( Flux.interval(Duration.ofMillis(1000)).map(i -> DefaultPayload.create("Hello"))) .map(Payload::getDataUtf8) - .doOnNext(System.out::println) + .doOnNext(logger::debug) .take(10) .doFinally(signalType -> socket.dispose()) .then() diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/requestresponse/HelloWorldClient.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/requestresponse/HelloWorldClient.java index 26a79c4e1..1b9994c2f 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/requestresponse/HelloWorldClient.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/requestresponse/HelloWorldClient.java @@ -24,10 +24,14 @@ import io.rsocket.transport.netty.client.TcpClientTransport; import io.rsocket.transport.netty.server.TcpServerTransport; import io.rsocket.util.DefaultPayload; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import reactor.core.publisher.Mono; public final class HelloWorldClient { + private static final Logger logger = LoggerFactory.getLogger(HelloWorldClient.class); + public static void main(String[] args) { RSocketServer.create( (setupPayload, reactiveSocket) -> @@ -39,7 +43,7 @@ public static void main(String[] args) { public Mono requestResponse(Payload p) { if (fail) { fail = false; - return Mono.error(new Throwable()); + return Mono.error(new Throwable("Simulated error")); } else { return Mono.just(p); } @@ -51,26 +55,14 @@ public Mono requestResponse(Payload p) { RSocket socket = RSocketConnector.connectWith(TcpClientTransport.create("localhost", 7000)).block(); - socket - .requestResponse(DefaultPayload.create("Hello")) - .map(Payload::getDataUtf8) - .onErrorReturn("error") - .doOnNext(System.out::println) - .block(); - - socket - .requestResponse(DefaultPayload.create("Hello")) - .map(Payload::getDataUtf8) - .onErrorReturn("error") - .doOnNext(System.out::println) - .block(); - - socket - .requestResponse(DefaultPayload.create("Hello")) - .map(Payload::getDataUtf8) - .onErrorReturn("error") - .doOnNext(System.out::println) - .block(); + for (int i = 0; i < 3; i++) { + socket + .requestResponse(DefaultPayload.create("Hello")) + .map(Payload::getDataUtf8) + .onErrorReturn("error") + .doOnNext(logger::debug) + .block(); + } socket.dispose(); } diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/Files.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/Files.java index e6867f8b5..6724ca93f 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/Files.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/Files.java @@ -3,13 +3,21 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.rsocket.Payload; -import java.io.*; +import java.io.BufferedInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; import reactor.core.publisher.SynchronousSink; class Files { + private static final Logger logger = LoggerFactory.getLogger(Files.class); public static Flux fileSource(String fileName, int chunkSizeBytes) { return Flux.generate( @@ -35,8 +43,7 @@ public void onNext(Payload payload) { ByteBuf data = payload.data(); receivedBytes += data.readableBytes(); receivedCount += 1; - System.out.println( - "Received file chunk: " + receivedCount + ". Total size: " + receivedBytes); + logger.debug("Received file chunk: " + receivedCount + ". Total size: " + receivedBytes); if (outputStream == null) { outputStream = open(fileName); } diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/ResumeFileTransfer.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/ResumeFileTransfer.java index d8dd3dd2d..d449dd205 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/ResumeFileTransfer.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/ResumeFileTransfer.java @@ -27,13 +27,17 @@ import io.rsocket.transport.netty.server.TcpServerTransport; import io.rsocket.util.DefaultPayload; import java.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.retry.Retry; public class ResumeFileTransfer { + /*amount of file chunks requested by subscriber: n, refilled on n/2 of received items*/ private static final int PREFETCH_WINDOW_SIZE = 4; + private static final Logger logger = LoggerFactory.getLogger(ResumeFileTransfer.class); public static void main(String[] args) { RequestCodec requestCodec = new RequestCodec(); @@ -44,7 +48,7 @@ public static void main(String[] args) { Retry.fixedDelay(Long.MAX_VALUE, Duration.ofSeconds(1)) .doBeforeRetry( retrySignal -> - System.out.println("Disconnected. Trying to resume connection..."))); + logger.debug("Disconnected. Trying to resume connection..."))); CloseableChannel server = RSocketServer.create((setup, rSocket) -> Mono.just(new FileServer(requestCodec))) diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/stream/StreamingClient.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/stream/StreamingClient.java index 5f7c777db..1ef2b7a90 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/stream/StreamingClient.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/stream/StreamingClient.java @@ -27,11 +27,15 @@ import io.rsocket.transport.netty.server.TcpServerTransport; import io.rsocket.util.DefaultPayload; import java.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; public final class StreamingClient { + private static final Logger logger = LoggerFactory.getLogger(StreamingClient.class); + public static void main(String[] args) { RSocketServer.create(new SocketAcceptorImpl()) .bind(TcpServerTransport.create("localhost", 7000)) @@ -43,7 +47,7 @@ public static void main(String[] args) { socket .requestStream(DefaultPayload.create("Hello")) .map(Payload::getDataUtf8) - .doOnNext(System.out::println) + .doOnNext(logger::debug) .take(10) .then() .doFinally(signalType -> socket.dispose()) diff --git a/rsocket-examples/src/main/resources/log4j.properties b/rsocket-examples/src/main/resources/log4j.properties deleted file mode 100644 index 035f18ebd..000000000 --- a/rsocket-examples/src/main/resources/log4j.properties +++ /dev/null @@ -1,20 +0,0 @@ -# -# Copyright 2015-2018 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -log4j.rootLogger=DEBUG, stdout - -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d{dd MMM yyyy HH:mm:ss,SSS} %5p [%t] %c{1} - %m%n \ No newline at end of file diff --git a/rsocket-examples/src/main/resources/logback.xml b/rsocket-examples/src/main/resources/logback.xml new file mode 100644 index 000000000..17dd8b5e3 --- /dev/null +++ b/rsocket-examples/src/main/resources/logback.xml @@ -0,0 +1,35 @@ + + + + + + + + %d{dd MMM yyyy HH:mm:ss,SSS} %5p [%t] %c{1} - %m%n + + + + + + + + + + + + + diff --git a/rsocket-examples/src/test/resources/log4j.properties b/rsocket-examples/src/test/resources/log4j.properties deleted file mode 100644 index 51731fc15..000000000 --- a/rsocket-examples/src/test/resources/log4j.properties +++ /dev/null @@ -1,21 +0,0 @@ -# -# Copyright 2015-2018 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -log4j.rootLogger=INFO, stdout - -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %5p [%t] (%F) - %m%n -#log4j.logger.io.rsocket.FrameLogger=Debug \ No newline at end of file diff --git a/rsocket-examples/src/test/resources/logback-test.xml b/rsocket-examples/src/test/resources/logback-test.xml new file mode 100644 index 000000000..13e65b37d --- /dev/null +++ b/rsocket-examples/src/test/resources/logback-test.xml @@ -0,0 +1,33 @@ + + + + + + + + %d{dd MMM yyyy HH:mm:ss,SSS} %5p [%t] %c{1} - %m%n + + + + + + + + + + + diff --git a/rsocket-load-balancer/build.gradle b/rsocket-load-balancer/build.gradle index a2c8b73c7..748f95de6 100644 --- a/rsocket-load-balancer/build.gradle +++ b/rsocket-load-balancer/build.gradle @@ -34,6 +34,7 @@ dependencies { testCompileOnly 'junit:junit' testImplementation 'org.hamcrest:hamcrest-library' testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' + testRuntimeOnly 'ch.qos.logback:logback-classic' } description = 'Transparent Load Balancer for RSocket' diff --git a/rsocket-load-balancer/src/test/resources/log4j.properties b/rsocket-load-balancer/src/test/resources/log4j.properties deleted file mode 100644 index 8fc3a9cdd..000000000 --- a/rsocket-load-balancer/src/test/resources/log4j.properties +++ /dev/null @@ -1,20 +0,0 @@ -# -# Copyright 2015-2018 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -log4j.rootLogger=INFO, stdout - -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d{dd MMM yyyy HH:mm:ss,SSS} %5p [%t] (%F:%L) - %m%n \ No newline at end of file diff --git a/rsocket-load-balancer/src/test/resources/logback-test.xml b/rsocket-load-balancer/src/test/resources/logback-test.xml new file mode 100644 index 000000000..13e65b37d --- /dev/null +++ b/rsocket-load-balancer/src/test/resources/logback-test.xml @@ -0,0 +1,33 @@ + + + + + + + + %d{dd MMM yyyy HH:mm:ss,SSS} %5p [%t] %c{1} - %m%n + + + + + + + + + + + From a81b6391275054f0cff33a46b432b9d34a90b5d2 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 27 Apr 2020 13:45:34 +0100 Subject: [PATCH 33/62] Default errorConsumer to no-op in the new API (#801) --- rsocket-core/src/main/java/io/rsocket/RSocketFactory.java | 6 ++++-- .../src/main/java/io/rsocket/core/RSocketConnector.java | 2 +- .../src/main/java/io/rsocket/core/RSocketServer.java | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java b/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java index f36a84241..178cc4fa9 100644 --- a/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java +++ b/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java @@ -111,7 +111,7 @@ public static class ClientRSocketFactory implements ClientTransportAcceptor { private Resume resume; public ClientRSocketFactory() { - this(RSocketConnector.create()); + this(RSocketConnector.create().errorConsumer(Throwable::printStackTrace)); } public ClientRSocketFactory(RSocketConnector connector) { @@ -393,6 +393,7 @@ public ClientRSocketFactory fragment(int mtu) { return this; } + /** @deprecated this is deprecated with no replacement. */ public ClientRSocketFactory errorConsumer(Consumer errorConsumer) { connector.errorConsumer(errorConsumer); return this; @@ -416,7 +417,7 @@ public static class ServerRSocketFactory implements ServerTransportAcceptor { private Resume resume; public ServerRSocketFactory() { - this(RSocketServer.create()); + this(RSocketServer.create().errorConsumer(Throwable::printStackTrace)); } public ServerRSocketFactory(RSocketServer server) { @@ -496,6 +497,7 @@ public ServerRSocketFactory fragment(int mtu) { return this; } + /** @deprecated this is deprecated with no replacement. */ public ServerRSocketFactory errorConsumer(Consumer errorConsumer) { server.errorConsumer(errorConsumer); return this; diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java index ed20f892e..57aebbdf0 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java @@ -69,7 +69,7 @@ public class RSocketConnector { private int mtu = 0; private PayloadDecoder payloadDecoder = PayloadDecoder.DEFAULT; - private Consumer errorConsumer = Throwable::printStackTrace; + private Consumer errorConsumer = ex -> {}; private RSocketConnector() {} diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java index 6c289d707..c82a2f40a 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java @@ -52,7 +52,7 @@ public final class RSocketServer { private Resume resume; private Supplier> leasesSupplier = null; - private Consumer errorConsumer = Throwable::printStackTrace; + private Consumer errorConsumer = ex -> {}; private PayloadDecoder payloadDecoder = PayloadDecoder.DEFAULT; private RSocketServer() {} From ad12dddc75760bb98f1107a810b997ee611e6455 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Mon, 27 Apr 2020 16:45:21 +0300 Subject: [PATCH 34/62] forces TcpDuplexConnection to use connection allocator Signed-off-by: Oleh Dokuka --- .../java/io/rsocket/transport/netty/TcpDuplexConnection.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/TcpDuplexConnection.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/TcpDuplexConnection.java index aa28ab8b9..d71d6b356 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/TcpDuplexConnection.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/TcpDuplexConnection.java @@ -31,7 +31,6 @@ public final class TcpDuplexConnection extends BaseDuplexConnection { private final Connection connection; - private final ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; private final boolean encodeLength; /** @@ -89,7 +88,7 @@ public Mono send(Publisher frames) { private ByteBuf encode(ByteBuf frame) { if (encodeLength) { - return FrameLengthFlyweight.encode(allocator, frame.readableBytes(), frame); + return FrameLengthFlyweight.encode(alloc(), frame.readableBytes(), frame); } else { return frame; } From 1c3927b2fd271dd690b7a01360db0414280639de Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Mon, 27 Apr 2020 21:27:42 +0300 Subject: [PATCH 35/62] updates to the latest reactor release-train Signed-off-by: Oleh Dokuka --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index dd5135f01..2c7757e0f 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ subprojects { apply plugin: 'io.spring.dependency-management' apply plugin: 'com.github.sherter.google-java-format' - ext['reactor-bom.version'] = 'Dysprosium-SR6' + ext['reactor-bom.version'] = 'Dysprosium-SR7' ext['logback.version'] = '1.2.3' ext['findbugs.version'] = '3.0.2' ext['netty-bom.version'] = '4.1.48.Final' From a5706bf6b92605312862d9a915d3f2213ae59773 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Mon, 27 Apr 2020 23:26:36 +0300 Subject: [PATCH 36/62] provides more `ByteBuf` leaks fixes (#803) 1. Ensures if there goes something during `Payload` to the frame we will not get any memory leaks in the end 2. Fixes `onDiscard` leak which did not work correctly because `actual` subscriber was nulled to early 3. Ensures that we will not have the wrong frame order in case of racing the first `Payload` sending and cancel. Also, it moves `onDiscard` hook to the very bottom in case of `requestChannel` to ensure the first payload is not leaked 4. Enables a set of tests that ensures that #757 and #733 are fully or partially fixed. --- .../io/rsocket/core/RSocketRequester.java | 162 ++++++++++++------ .../frame/DataAndMetadataFlyweight.java | 30 +++- .../io/rsocket/frame/LeaseFrameFlyweight.java | 16 +- .../frame/MetadataPushFrameFlyweight.java | 10 +- .../rsocket/frame/PayloadFrameFlyweight.java | 29 +++- .../frame/RequestChannelFrameFlyweight.java | 29 +++- .../RequestFireAndForgetFrameFlyweight.java | 29 +++- .../frame/RequestResponseFrameFlyweight.java | 29 +++- .../frame/RequestStreamFrameFlyweight.java | 29 +++- .../internal/UnicastMonoProcessor.java | 16 +- .../buffer/LeaksTrackingByteBufAllocator.java | 18 +- .../io/rsocket/core/RSocketRequesterTest.java | 143 +++++++++++----- .../io/rsocket/core/RSocketResponderTest.java | 69 ++++---- .../rsocket/frame/ByteBufRepresentation.java | 8 +- 14 files changed, 437 insertions(+), 180 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index 21112c24d..fefb06003 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -54,7 +54,7 @@ import io.rsocket.util.MonoLifecycleHandler; import java.nio.channels.ClosedChannelException; import java.util.concurrent.CancellationException; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.Consumer; import java.util.function.LongConsumer; @@ -260,6 +260,7 @@ public void doOnTerminal( removeStreamReceiver(streamId); } }); + receivers.put(streamId, receiver); return receiver.doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER); @@ -281,7 +282,7 @@ private Flux handleRequestStream(final Payload payload) { final UnboundedProcessor sendProcessor = this.sendProcessor; final UnicastProcessor receiver = UnicastProcessor.create(); - final AtomicBoolean payloadReleasedFlag = new AtomicBoolean(false); + final AtomicInteger wip = new AtomicInteger(0); receivers.put(streamId, receiver); @@ -293,30 +294,49 @@ private Flux handleRequestStream(final Payload payload) { @Override public void accept(long n) { - if (firstRequest && !receiver.isDisposed()) { + if (firstRequest) { firstRequest = false; - if (!payloadReleasedFlag.getAndSet(true)) { - sendProcessor.onNext( - RequestStreamFrameFlyweight.encodeReleasingPayload( - allocator, streamId, n, payload)); + if (wip.getAndIncrement() != 0) { + // no need to do anything. + // stream was canceled and fist payload has already been discarded + return; } - } else if (contains(streamId) && !receiver.isDisposed()) { + int missed = 1; + boolean firstHasBeenSent = false; + for (; ; ) { + if (!firstHasBeenSent) { + sendProcessor.onNext( + RequestStreamFrameFlyweight.encodeReleasingPayload( + allocator, streamId, n, payload)); + firstHasBeenSent = true; + } else { + // if first frame was sent but we cycling again, it means that wip was + // incremented at doOnCancel + sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); + return; + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + return; + } + } + } else if (!receiver.isDisposed()) { sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); } } }) - .doOnError( - t -> { - if (contains(streamId) && !receiver.isDisposed()) { - sendProcessor.onNext(ErrorFrameFlyweight.encode(allocator, streamId, t)); - } - }) .doOnCancel( () -> { - if (!payloadReleasedFlag.getAndSet(true)) { - payload.release(); + if (wip.getAndIncrement() != 0) { + return; } - if (contains(streamId) && !receiver.isDisposed()) { + + // check if we need to release payload + // only applicable if the cancel appears earlier than actual request + if (payload.refCnt() > 0) { + payload.release(); + } else { sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); } }) @@ -330,30 +350,32 @@ private Flux handleChannel(Flux request) { return Flux.error(err); } - return request.switchOnFirst( - (s, flux) -> { - Payload payload = s.get(); - if (payload != null) { - if (!PayloadValidationUtils.isValid(mtu, payload)) { - payload.release(); - final IllegalArgumentException t = - new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE); - errorConsumer.accept(t); - return Mono.error(t); - } - return handleChannel(payload, flux); - } else { - return flux; - } - }, - false); + return request + .switchOnFirst( + (s, flux) -> { + Payload payload = s.get(); + if (payload != null) { + if (!PayloadValidationUtils.isValid(mtu, payload)) { + payload.release(); + final IllegalArgumentException t = + new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE); + errorConsumer.accept(t); + return Mono.error(t); + } + return handleChannel(payload, flux); + } else { + return flux; + } + }, + false) + .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER); } private Flux handleChannel(Payload initialPayload, Flux inboundFlux) { final UnboundedProcessor sendProcessor = this.sendProcessor; - final AtomicBoolean payloadReleasedFlag = new AtomicBoolean(false); final int streamId = streamIdSupplier.nextStreamId(receivers); + final AtomicInteger wip = new AtomicInteger(0); final UnicastProcessor receiver = UnicastProcessor.create(); final BaseSubscriber upstreamSubscriber = new BaseSubscriber() { @@ -421,19 +443,47 @@ protected void hookFinally(SignalType type) { public void accept(long n) { if (firstRequest) { firstRequest = false; - senders.put(streamId, upstreamSubscriber); - receivers.put(streamId, receiver); - - inboundFlux - .limitRate(Queues.SMALL_BUFFER_SIZE) - .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER) - .subscribe(upstreamSubscriber); - if (!payloadReleasedFlag.getAndSet(true)) { - ByteBuf frame = - RequestChannelFrameFlyweight.encodeReleasingPayload( - allocator, streamId, false, n, initialPayload); - - sendProcessor.onNext(frame); + if (wip.getAndIncrement() != 0) { + // no need to do anything. + // stream was canceled and fist payload has already been discarded + return; + } + int missed = 1; + boolean firstHasBeenSent = false; + for (; ; ) { + if (!firstHasBeenSent) { + ByteBuf frame; + try { + frame = + RequestChannelFrameFlyweight.encodeReleasingPayload( + allocator, streamId, false, n, initialPayload); + } catch (IllegalReferenceCountException e) { + return; + } + + senders.put(streamId, upstreamSubscriber); + receivers.put(streamId, receiver); + + inboundFlux + .limitRate(Queues.SMALL_BUFFER_SIZE) + .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER) + .subscribe(upstreamSubscriber); + + sendProcessor.onNext(frame); + firstHasBeenSent = true; + } else { + // if first frame was sent but we cycling again, it means that wip was + // incremented at doOnCancel + senders.remove(streamId, upstreamSubscriber); + receivers.remove(streamId, receiver); + sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); + return; + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + return; + } } } else { sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); @@ -442,22 +492,22 @@ public void accept(long n) { }) .doOnError( t -> { - if (receivers.remove(streamId, receiver)) { - upstreamSubscriber.cancel(); - } + upstreamSubscriber.cancel(); + receivers.remove(streamId, receiver); }) .doOnComplete(() -> receivers.remove(streamId, receiver)) .doOnCancel( () -> { - if (!payloadReleasedFlag.getAndSet(true)) { - initialPayload.release(); + upstreamSubscriber.cancel(); + if (wip.getAndIncrement() != 0) { + return; } + + // need to send frame only if RequestChannelFrame was sent if (receivers.remove(streamId, receiver)) { sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); - upstreamSubscriber.cancel(); } - }) - .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER); + }); } private Mono handleMetadataPush(Payload payload) { diff --git a/rsocket-core/src/main/java/io/rsocket/frame/DataAndMetadataFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/DataAndMetadataFlyweight.java index ea54aa374..73bfd38f1 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/DataAndMetadataFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/DataAndMetadataFlyweight.java @@ -37,8 +37,34 @@ static ByteBuf encode( boolean hasMetadata, ByteBuf data) { - final boolean addData = data != null && data.isReadable(); - final boolean addMetadata = hasMetadata && metadata.isReadable(); + final boolean addData; + if (data != null) { + if (data.isReadable()) { + addData = true; + } else { + // even though there is nothing to read, we still have to release here since nobody else + // going to do soo + data.release(); + addData = false; + } + } else { + addData = false; + } + + final boolean addMetadata; + if (hasMetadata) { + if (metadata.isReadable()) { + addMetadata = true; + } else { + // even though there is nothing to read, we still have to release here since nobody else + // going to do soo + metadata.release(); + addMetadata = false; + } + } else { + // has no metadata means it is null, thus no need to release anything + addMetadata = false; + } if (hasMetadata) { int length = metadata.readableBytes(); diff --git a/rsocket-core/src/main/java/io/rsocket/frame/LeaseFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/LeaseFrameFlyweight.java index 039c72886..32f086a15 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/LeaseFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/LeaseFrameFlyweight.java @@ -14,7 +14,6 @@ public static ByteBuf encode( @Nullable final ByteBuf metadata) { final boolean hasMetadata = metadata != null; - final boolean addMetadata = hasMetadata && metadata.isReadable(); int flags = 0; @@ -27,6 +26,21 @@ public static ByteBuf encode( .writeInt(ttl) .writeInt(numRequests); + final boolean addMetadata; + if (hasMetadata) { + if (metadata.isReadable()) { + addMetadata = true; + } else { + // even though there is nothing to read, we still have to release here since nobody else + // going to do soo + metadata.release(); + addMetadata = false; + } + } else { + // has no metadata means it is null, thus no need to release anything + addMetadata = false; + } + if (addMetadata) { return allocator.compositeBuffer(2).addComponents(true, header, metadata); } else { diff --git a/rsocket-core/src/main/java/io/rsocket/frame/MetadataPushFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/MetadataPushFrameFlyweight.java index e3a9a47ba..a39acef92 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/MetadataPushFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/MetadataPushFrameFlyweight.java @@ -2,13 +2,21 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.util.IllegalReferenceCountException; import io.rsocket.Payload; public class MetadataPushFrameFlyweight { public static ByteBuf encodeReleasingPayload(ByteBufAllocator allocator, Payload payload) { final ByteBuf metadata = payload.metadata().retain(); - payload.release(); + // releasing payload safely since it can be already released wheres we have to release retained + // data and metadata as well + try { + payload.release(); + } catch (IllegalReferenceCountException e) { + metadata.release(); + throw e; + } return encode(allocator, metadata); } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameFlyweight.java index 4c2ebdf6e..53ac6150b 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameFlyweight.java @@ -2,6 +2,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.util.IllegalReferenceCountException; import io.rsocket.Payload; public class PayloadFrameFlyweight { @@ -23,11 +24,31 @@ public static ByteBuf encodeNextCompleteReleasingPayload( static ByteBuf encodeReleasingPayload( ByteBufAllocator allocator, int streamId, boolean complete, Payload payload) { - final boolean hasMetadata = payload.hasMetadata(); + // if refCnt exceptions throws here it is safe to do no-op + boolean hasMetadata = payload.hasMetadata(); + // if refCnt exceptions throws here it is safe to do no-op still final ByteBuf metadata = hasMetadata ? payload.metadata().retain() : null; - final ByteBuf data = payload.data().retain(); - - payload.release(); + final ByteBuf data; + // retaining data safely. May throw either NPE or RefCntE + try { + data = payload.data().retain(); + } catch (IllegalReferenceCountException | NullPointerException e) { + if (hasMetadata) { + metadata.release(); + } + throw e; + } + // releasing payload safely since it can be already released wheres we have to release retained + // data and metadata as well + try { + payload.release(); + } catch (IllegalReferenceCountException e) { + data.release(); + if (hasMetadata) { + metadata.release(); + } + throw e; + } return encode(allocator, streamId, false, complete, true, metadata, data); } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameFlyweight.java index 7c3cbb574..c0db21170 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameFlyweight.java @@ -2,6 +2,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.util.IllegalReferenceCountException; import io.rsocket.Payload; public class RequestChannelFrameFlyweight { @@ -17,11 +18,31 @@ public static ByteBuf encodeReleasingPayload( long initialRequestN, Payload payload) { - final boolean hasMetadata = payload.hasMetadata(); + // if refCnt exceptions throws here it is safe to do no-op + boolean hasMetadata = payload.hasMetadata(); + // if refCnt exceptions throws here it is safe to do no-op still final ByteBuf metadata = hasMetadata ? payload.metadata().retain() : null; - final ByteBuf data = payload.data().retain(); - - payload.release(); + final ByteBuf data; + // retaining data safely. May throw either NPE or RefCntE + try { + data = payload.data().retain(); + } catch (IllegalReferenceCountException | NullPointerException e) { + if (hasMetadata) { + metadata.release(); + } + throw e; + } + // releasing payload safely since it can be already released wheres we have to release retained + // data and metadata as well + try { + payload.release(); + } catch (IllegalReferenceCountException e) { + data.release(); + if (hasMetadata) { + metadata.release(); + } + throw e; + } return encode(allocator, streamId, false, complete, initialRequestN, metadata, data); } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameFlyweight.java index 287f765f7..e091edcc3 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameFlyweight.java @@ -2,6 +2,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.util.IllegalReferenceCountException; import io.rsocket.Payload; public class RequestFireAndForgetFrameFlyweight { @@ -13,11 +14,31 @@ private RequestFireAndForgetFrameFlyweight() {} public static ByteBuf encodeReleasingPayload( ByteBufAllocator allocator, int streamId, Payload payload) { - final boolean hasMetadata = payload.hasMetadata(); + // if refCnt exceptions throws here it is safe to do no-op + boolean hasMetadata = payload.hasMetadata(); + // if refCnt exceptions throws here it is safe to do no-op still final ByteBuf metadata = hasMetadata ? payload.metadata().retain() : null; - final ByteBuf data = payload.data().retain(); - - payload.release(); + final ByteBuf data; + // retaining data safely. May throw either NPE or RefCntE + try { + data = payload.data().retain(); + } catch (IllegalReferenceCountException | NullPointerException e) { + if (hasMetadata) { + metadata.release(); + } + throw e; + } + // releasing payload safely since it can be already released wheres we have to release retained + // data and metadata as well + try { + payload.release(); + } catch (IllegalReferenceCountException e) { + data.release(); + if (hasMetadata) { + metadata.release(); + } + throw e; + } return FLYWEIGHT.encode(allocator, streamId, false, metadata, data); } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameFlyweight.java index 3fbac27d2..782c70965 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameFlyweight.java @@ -2,6 +2,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.util.IllegalReferenceCountException; import io.rsocket.Payload; public class RequestResponseFrameFlyweight { @@ -13,11 +14,31 @@ private RequestResponseFrameFlyweight() {} public static ByteBuf encodeReleasingPayload( ByteBufAllocator allocator, int streamId, Payload payload) { - final boolean hasMetadata = payload.hasMetadata(); + // if refCnt exceptions throws here it is safe to do no-op + boolean hasMetadata = payload.hasMetadata(); + // if refCnt exceptions throws here it is safe to do no-op still final ByteBuf metadata = hasMetadata ? payload.metadata().retain() : null; - final ByteBuf data = payload.data().retain(); - - payload.release(); + final ByteBuf data; + // retaining data safely. May throw either NPE or RefCntE + try { + data = payload.data().retain(); + } catch (IllegalReferenceCountException | NullPointerException e) { + if (hasMetadata) { + metadata.release(); + } + throw e; + } + // releasing payload safely since it can be already released wheres we have to release retained + // data and metadata as well + try { + payload.release(); + } catch (IllegalReferenceCountException e) { + data.release(); + if (hasMetadata) { + metadata.release(); + } + throw e; + } return encode(allocator, streamId, false, metadata, data); } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameFlyweight.java index ff1435652..2fb209ffb 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameFlyweight.java @@ -2,6 +2,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.util.IllegalReferenceCountException; import io.rsocket.Payload; public class RequestStreamFrameFlyweight { @@ -13,11 +14,31 @@ private RequestStreamFrameFlyweight() {} public static ByteBuf encodeReleasingPayload( ByteBufAllocator allocator, int streamId, long initialRequestN, Payload payload) { - final boolean hasMetadata = payload.hasMetadata(); + // if refCnt exceptions throws here it is safe to do no-op + boolean hasMetadata = payload.hasMetadata(); + // if refCnt exceptions throws here it is safe to do no-op still final ByteBuf metadata = hasMetadata ? payload.metadata().retain() : null; - final ByteBuf data = payload.data().retain(); - - payload.release(); + final ByteBuf data; + // retaining data safely. May throw either NPE or RefCntE + try { + data = payload.data().retain(); + } catch (IllegalReferenceCountException | NullPointerException e) { + if (hasMetadata) { + metadata.release(); + } + throw e; + } + // releasing payload safely since it can be already released wheres we have to release retained + // data and metadata as well + try { + payload.release(); + } catch (IllegalReferenceCountException e) { + data.release(); + if (hasMetadata) { + metadata.release(); + } + throw e; + } return encode(allocator, streamId, false, initialRequestN, metadata, data); } diff --git a/rsocket-core/src/main/java/io/rsocket/internal/UnicastMonoProcessor.java b/rsocket-core/src/main/java/io/rsocket/internal/UnicastMonoProcessor.java index af4c8768b..c5b06e086 100644 --- a/rsocket-core/src/main/java/io/rsocket/internal/UnicastMonoProcessor.java +++ b/rsocket-core/src/main/java/io/rsocket/internal/UnicastMonoProcessor.java @@ -99,6 +99,7 @@ public static UnicastMonoProcessor create(MonoLifecycleHandler lifecyc UnicastMonoProcessor.class, Subscription.class, "subscription"); CoreSubscriber actual; + boolean hasDownstream = false; Throwable error; O value; @@ -185,7 +186,7 @@ private void complete(O v) { if (state == HAS_REQUEST_NO_RESULT) { if (STATE.compareAndSet(this, HAS_REQUEST_NO_RESULT, HAS_REQUEST_HAS_RESULT)) { final Subscriber a = actual; - actual = null; + hasDownstream = false; value = null; lifecycleHandler.doOnTerminal(SignalType.ON_COMPLETE, v, null); a.onNext(v); @@ -222,7 +223,7 @@ private void complete() { if (state == HAS_REQUEST_NO_RESULT || state == NO_REQUEST_NO_RESULT) { if (STATE.compareAndSet(this, state, HAS_REQUEST_HAS_RESULT)) { final Subscriber a = actual; - actual = null; + hasDownstream = false; lifecycleHandler.doOnTerminal(SignalType.ON_COMPLETE, null, null); a.onComplete(); return; @@ -256,7 +257,7 @@ private void complete(Throwable e) { if (state == HAS_REQUEST_NO_RESULT || state == NO_REQUEST_NO_RESULT) { if (STATE.compareAndSet(this, state, HAS_REQUEST_HAS_RESULT)) { final Subscriber a = actual; - actual = null; + hasDownstream = false; lifecycleHandler.doOnTerminal(SignalType.ON_ERROR, null, e); a.onError(e); return; @@ -278,6 +279,7 @@ public void subscribe(CoreSubscriber actual) { lh.doOnSubscribe(); + this.hasDownstream = true; this.actual = actual; int state = this.state; @@ -303,7 +305,7 @@ public void subscribe(CoreSubscriber actual) { // no value // e.g. [onError / onComplete / dispose] only if (state == NO_REQUEST_HAS_RESULT && this.value == null) { - this.actual = null; + this.hasDownstream = false; Throwable e = this.error; // barrier to flush changes STATE.set(this, HAS_REQUEST_HAS_RESULT); @@ -340,7 +342,7 @@ public final void request(long n) { if (STATE.compareAndSet(this, NO_REQUEST_HAS_RESULT, HAS_REQUEST_HAS_RESULT)) { final Subscriber a = actual; final O v = value; - actual = null; + hasDownstream = false; value = null; lifecycleHandler.doOnTerminal(SignalType.ON_COMPLETE, v, null); a.onNext(v); @@ -360,7 +362,7 @@ public final void cancel() { if (STATE.getAndSet(this, CANCELLED) <= HAS_REQUEST_NO_RESULT) { Operators.onDiscard(value, currentContext()); value = null; - actual = null; + hasDownstream = false; lifecycleHandler.doOnTerminal(SignalType.CANCEL, null, null); final Subscription s = UPSTREAM.getAndSet(this, Operators.cancelledSubscription()); if (s != null && s != Operators.cancelledSubscription()) { @@ -502,6 +504,6 @@ public Object scanUnsafe(Attr key) { * @return true if any {@link Subscriber} is actively subscribed */ public final boolean hasDownstream() { - return state > NO_SUBSCRIBER_HAS_RESULT && actual != null; + return state > NO_SUBSCRIBER_HAS_RESULT && hasDownstream; } } diff --git a/rsocket-core/src/test/java/io/rsocket/buffer/LeaksTrackingByteBufAllocator.java b/rsocket-core/src/test/java/io/rsocket/buffer/LeaksTrackingByteBufAllocator.java index 2044779ef..800e5d678 100644 --- a/rsocket-core/src/test/java/io/rsocket/buffer/LeaksTrackingByteBufAllocator.java +++ b/rsocket-core/src/test/java/io/rsocket/buffer/LeaksTrackingByteBufAllocator.java @@ -3,7 +3,6 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.CompositeByteBuf; -import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import org.assertj.core.api.Assertions; @@ -35,22 +34,9 @@ public LeaksTrackingByteBufAllocator assertHasNoLeaks() { try { Assertions.assertThat(tracker) .allSatisfy( - buf -> { - if (buf instanceof CompositeByteBuf) { - if (buf.refCnt() > 0) { - List decomposed = - ((CompositeByteBuf) buf).decompose(0, buf.readableBytes()); - for (int i = 0; i < decomposed.size(); i++) { - Assertions.assertThat(decomposed.get(i)) - .matches(bb -> bb.refCnt() == 0, "Got unreleased CompositeByteBuf"); - } - } - - } else { + buf -> Assertions.assertThat(buf) - .matches(bb -> bb.refCnt() == 0, "buffer should be released"); - } - }); + .matches(bb -> bb.refCnt() == 0, "buffer should be released")); } finally { tracker.clear(); } diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java index e536d2db4..3b62bc437 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java @@ -38,6 +38,7 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import io.netty.util.CharsetUtil; +import io.netty.util.ReferenceCountUtil; import io.netty.util.ReferenceCounted; import io.rsocket.Payload; import io.rsocket.RSocket; @@ -71,6 +72,7 @@ import java.util.function.Function; import java.util.stream.Stream; import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -84,6 +86,7 @@ import org.reactivestreams.Subscription; import reactor.core.publisher.BaseSubscriber; import reactor.core.publisher.Flux; +import reactor.core.publisher.Hooks; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoProcessor; import reactor.core.publisher.UnicastProcessor; @@ -97,6 +100,8 @@ public class RSocketRequesterTest { @BeforeEach public void setUp() throws Throwable { + Hooks.onNextDropped(ReferenceCountUtil::safeRelease); + Hooks.onErrorDropped((t) -> {}); rule = new ClientSocketRule(); rule.apply( new Statement() { @@ -107,6 +112,12 @@ public void evaluate() {} .evaluate(); } + @AfterEach + public void tearDown() { + Hooks.resetOnErrorDropped(); + Hooks.resetOnNextDropped(); + } + @Test @Timeout(2_000) public void testInvalidFrameOnStream0() { @@ -403,21 +414,8 @@ static Stream>> prepareCalls() { rule.assertHasNoLeaks(); } - @Test - @Disabled("Due to https://github.com/reactor/reactor-core/pull/2114") - @SuppressWarnings("unchecked") - public void checkNoLeaksOnRacingTest() { - - racingCases() - .forEach( - a -> { - ((Runnable) a.get()[0]).run(); - checkNoLeaksOnRacing( - (Function>) a.get()[1], - (BiConsumer, ClientSocketRule>) a.get()[2]); - }); - } - + @ParameterizedTest + @MethodSource("racingCases") public void checkNoLeaksOnRacing( Function> initiator, BiConsumer, ClientSocketRule> runner) { @@ -437,7 +435,7 @@ public void evaluate() {} } Publisher payloadP = initiator.apply(clientSocketRule); - AssertSubscriber assertSubscriber = AssertSubscriber.create(); + AssertSubscriber assertSubscriber = AssertSubscriber.create(0); if (payloadP instanceof Flux) { ((Flux) payloadP).doOnNext(Payload::release).subscribe(assertSubscriber); @@ -450,14 +448,13 @@ public void evaluate() {} Assertions.assertThat(clientSocketRule.connection.getSent()) .allMatch(ReferenceCounted::release); - rule.assertHasNoLeaks(); + clientSocketRule.assertHasNoLeaks(); } } private static Stream racingCases() { return Stream.of( Arguments.of( - (Runnable) () -> System.out.println("RequestStream downstream cancellation case"), (Function>) (rule) -> rule.socket.requestStream(EmptyPayload.INSTANCE), (BiConsumer, ClientSocketRule>) @@ -467,6 +464,7 @@ private static Stream racingCases() { metadata.writeCharSequence("abc", CharsetUtil.UTF_8); ByteBuf data = allocator.buffer(); data.writeCharSequence("def", CharsetUtil.UTF_8); + as.request(1); int streamId = rule.getStreamIdForRequestType(REQUEST_STREAM); ByteBuf frame = PayloadFrameFlyweight.encode( @@ -475,7 +473,6 @@ private static Stream racingCases() { RaceTestUtils.race(as::cancel, () -> rule.connection.addToReceivedBuffer(frame)); }), Arguments.of( - (Runnable) () -> System.out.println("RequestChannel downstream cancellation case"), (Function>) (rule) -> rule.socket.requestChannel(Flux.just(EmptyPayload.INSTANCE)), (BiConsumer, ClientSocketRule>) @@ -485,6 +482,7 @@ private static Stream racingCases() { metadata.writeCharSequence("abc", CharsetUtil.UTF_8); ByteBuf data = allocator.buffer(); data.writeCharSequence("def", CharsetUtil.UTF_8); + as.request(1); int streamId = rule.getStreamIdForRequestType(REQUEST_CHANNEL); ByteBuf frame = PayloadFrameFlyweight.encode( @@ -493,79 +491,143 @@ private static Stream racingCases() { RaceTestUtils.race(as::cancel, () -> rule.connection.addToReceivedBuffer(frame)); }), Arguments.of( - (Runnable) () -> System.out.println("RequestChannel upstream cancellation 1"), (Function>) (rule) -> { ByteBufAllocator allocator = rule.alloc(); ByteBuf metadata = allocator.buffer(); - metadata.writeCharSequence("abc", CharsetUtil.UTF_8); + metadata.writeCharSequence("metadata", CharsetUtil.UTF_8); ByteBuf data = allocator.buffer(); - data.writeCharSequence("def", CharsetUtil.UTF_8); - return rule.socket.requestChannel( - Flux.just(ByteBufPayload.create(data, metadata))); + data.writeCharSequence("data", CharsetUtil.UTF_8); + final Payload payload = ByteBufPayload.create(data, metadata); + + return rule.socket.requestStream(payload); }, (BiConsumer, ClientSocketRule>) (as, rule) -> { + RaceTestUtils.race(() -> as.request(1), as::cancel); + // ensures proper frames order + if (rule.connection.getSent().size() > 0) { + Assertions.assertThat(rule.connection.getSent()).hasSize(2); + Assertions.assertThat(rule.connection.getSent()) + .element(0) + .matches( + bb -> frameType(bb) == REQUEST_STREAM, + "Expected first frame matches {" + + REQUEST_STREAM + + "} but was {" + + frameType(rule.connection.getSent().stream().findFirst().get()) + + "}"); + Assertions.assertThat(rule.connection.getSent()) + .element(1) + .matches( + bb -> frameType(bb) == CANCEL, + "Expected first frame matches {" + + CANCEL + + "} but was {" + + frameType( + rule.connection.getSent().stream().skip(1).findFirst().get()) + + "}"); + } + }), + Arguments.of( + (Function>) + (rule) -> { ByteBufAllocator allocator = rule.alloc(); - int streamId = rule.getStreamIdForRequestType(REQUEST_CHANNEL); - ByteBuf frame = CancelFrameFlyweight.encode(allocator, streamId); - - RaceTestUtils.race( - () -> as.request(1), () -> rule.connection.addToReceivedBuffer(frame)); + return rule.socket.requestChannel( + Flux.generate( + () -> 1L, + (index, sink) -> { + ByteBuf metadata = allocator.buffer(); + metadata.writeCharSequence("metadata", CharsetUtil.UTF_8); + ByteBuf data = allocator.buffer(); + data.writeCharSequence("data", CharsetUtil.UTF_8); + final Payload payload = ByteBufPayload.create(data, metadata); + sink.next(payload); + sink.complete(); + return ++index; + })); + }, + (BiConsumer, ClientSocketRule>) + (as, rule) -> { + RaceTestUtils.race(() -> as.request(1), as::cancel); + // ensures proper frames order + if (rule.connection.getSent().size() > 0) { + Assertions.assertThat(rule.connection.getSent()).hasSize(2); + Assertions.assertThat(rule.connection.getSent()) + .element(0) + .matches( + bb -> frameType(bb) == REQUEST_CHANNEL, + "Expected first frame matches {" + + REQUEST_CHANNEL + + "} but was {" + + frameType(rule.connection.getSent().stream().findFirst().get()) + + "}"); + Assertions.assertThat(rule.connection.getSent()) + .element(1) + .matches( + bb -> frameType(bb) == CANCEL, + "Expected first frame matches {" + + CANCEL + + "} but was {" + + frameType( + rule.connection.getSent().stream().skip(1).findFirst().get()) + + "}"); + } }), Arguments.of( - (Runnable) () -> System.out.println("RequestChannel upstream cancellation 2"), (Function>) (rule) -> rule.socket.requestChannel( Flux.generate( () -> 1L, (index, sink) -> { - final Payload payload = - ByteBufPayload.create("d" + index, "m" + index); + ByteBuf data = rule.alloc().buffer(); + data.writeCharSequence("d" + index, CharsetUtil.UTF_8); + ByteBuf metadata = rule.alloc().buffer(); + metadata.writeCharSequence("m" + index, CharsetUtil.UTF_8); + final Payload payload = ByteBufPayload.create(data, metadata); sink.next(payload); return ++index; })), (BiConsumer, ClientSocketRule>) (as, rule) -> { ByteBufAllocator allocator = rule.alloc(); + as.request(1); int streamId = rule.getStreamIdForRequestType(REQUEST_CHANNEL); ByteBuf frame = CancelFrameFlyweight.encode(allocator, streamId); - as.request(1); - RaceTestUtils.race( () -> as.request(Long.MAX_VALUE), () -> rule.connection.addToReceivedBuffer(frame)); }), Arguments.of( - (Runnable) () -> System.out.println("RequestChannel remote error"), (Function>) (rule) -> rule.socket.requestChannel( Flux.generate( () -> 1L, (index, sink) -> { - final Payload payload = - ByteBufPayload.create("d" + index, "m" + index); + ByteBuf data = rule.alloc().buffer(); + data.writeCharSequence("d" + index, CharsetUtil.UTF_8); + ByteBuf metadata = rule.alloc().buffer(); + metadata.writeCharSequence("m" + index, CharsetUtil.UTF_8); + final Payload payload = ByteBufPayload.create(data, metadata); sink.next(payload); return ++index; })), (BiConsumer, ClientSocketRule>) (as, rule) -> { ByteBufAllocator allocator = rule.alloc(); + as.request(1); int streamId = rule.getStreamIdForRequestType(REQUEST_CHANNEL); ByteBuf frame = ErrorFrameFlyweight.encode(allocator, streamId, new RuntimeException("test")); - as.request(1); - RaceTestUtils.race( () -> as.request(Long.MAX_VALUE), () -> rule.connection.addToReceivedBuffer(frame)); }), Arguments.of( - (Runnable) () -> System.out.println("RequestResponse downstream cancellation"), (Function>) (rule) -> rule.socket.requestResponse(EmptyPayload.INSTANCE), (BiConsumer, ClientSocketRule>) @@ -575,6 +637,7 @@ private static Stream racingCases() { metadata.writeCharSequence("abc", CharsetUtil.UTF_8); ByteBuf data = allocator.buffer(); data.writeCharSequence("def", CharsetUtil.UTF_8); + as.request(Long.MAX_VALUE); int streamId = rule.getStreamIdForRequestType(REQUEST_RESPONSE); ByteBuf frame = PayloadFrameFlyweight.encode( diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java index 48910b3a2..c19456548 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java @@ -94,6 +94,8 @@ public class RSocketResponderTest { @BeforeEach public void setUp() throws Throwable { + Hooks.onNextDropped(ReferenceCountUtil::safeRelease); + Hooks.onErrorDropped(t -> {}); rule = new ServerSocketRule(); rule.apply( new Statement() { @@ -107,6 +109,7 @@ public void evaluate() {} @AfterEach public void tearDown() { Hooks.resetOnErrorDropped(); + Hooks.resetOnNextDropped(); } @Test @@ -247,9 +250,7 @@ protected void hookOnSubscribe(Subscription subscription) { } @Test - @Disabled("Due to https://github.com/reactor/reactor-core/pull/2114") public void checkNoLeaksOnRacingCancelFromRequestChannelAndNextFromUpstream() { - ByteBufAllocator allocator = rule.alloc(); for (int i = 0; i < 10000; i++) { AssertSubscriber assertSubscriber = AssertSubscriber.create(); @@ -258,33 +259,32 @@ public void checkNoLeaksOnRacingCancelFromRequestChannelAndNextFromUpstream() { new AbstractRSocket() { @Override public Flux requestChannel(Publisher payloads) { - ((Flux) payloads) - .doOnNext(ReferenceCountUtil::safeRelease) - .subscribe(assertSubscriber); + payloads.subscribe(assertSubscriber); return Flux.never(); } }, Integer.MAX_VALUE); rule.sendRequest(1, REQUEST_CHANNEL); + ByteBuf metadata1 = allocator.buffer(); - metadata1.writeCharSequence("abc", CharsetUtil.UTF_8); + metadata1.writeCharSequence("abc1", CharsetUtil.UTF_8); ByteBuf data1 = allocator.buffer(); - data1.writeCharSequence("def", CharsetUtil.UTF_8); + data1.writeCharSequence("def1", CharsetUtil.UTF_8); ByteBuf nextFrame1 = PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata1, data1); ByteBuf metadata2 = allocator.buffer(); - metadata2.writeCharSequence("abc", CharsetUtil.UTF_8); + metadata2.writeCharSequence("abc2", CharsetUtil.UTF_8); ByteBuf data2 = allocator.buffer(); - data2.writeCharSequence("def", CharsetUtil.UTF_8); + data2.writeCharSequence("def2", CharsetUtil.UTF_8); ByteBuf nextFrame2 = PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata2, data2); ByteBuf metadata3 = allocator.buffer(); - metadata3.writeCharSequence("abc", CharsetUtil.UTF_8); + metadata3.writeCharSequence("abc3", CharsetUtil.UTF_8); ByteBuf data3 = allocator.buffer(); - data3.writeCharSequence("def", CharsetUtil.UTF_8); + data3.writeCharSequence("def3", CharsetUtil.UTF_8); ByteBuf nextFrame3 = PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata3, data3); @@ -294,6 +294,8 @@ public Flux requestChannel(Publisher payloads) { }, assertSubscriber::cancel); + Assertions.assertThat(assertSubscriber.values()).allMatch(ReferenceCounted::release); + Assertions.assertThat(rule.connection.getSent()).allMatch(ReferenceCounted::release); rule.assertHasNoLeaks(); @@ -301,7 +303,6 @@ public Flux requestChannel(Publisher payloads) { } @Test - @Disabled("Due to https://github.com/reactor/reactor-core/pull/2114") public void checkNoLeaksOnRacingBetweenDownstreamCancelAndOnNextFromRequestChannelTest() { Hooks.onErrorDropped((e) -> {}); ByteBufAllocator allocator = rule.alloc(); @@ -341,7 +342,6 @@ public Flux requestChannel(Publisher payloads) { } @Test - @Disabled("Due to https://github.com/reactor/reactor-core/pull/2114") public void checkNoLeaksOnRacingBetweenDownstreamCancelAndOnNextFromRequestChannelTest1() { Scheduler parallel = Schedulers.parallel(); Hooks.onErrorDropped((e) -> {}); @@ -388,27 +388,25 @@ public Flux requestChannel(Publisher payloads) { } @Test - @Disabled("Due to https://github.com/reactor/reactor-core/pull/2114") public void - checkNoLeaksOnRacingBetweenDownstreamCancelAndOnNextFromUpstreamOnErrorFromRequestChannelTest1() - throws InterruptedException { + checkNoLeaksOnRacingBetweenDownstreamCancelAndOnNextFromUpstreamOnErrorFromRequestChannelTest1() { Scheduler parallel = Schedulers.parallel(); Hooks.onErrorDropped((e) -> {}); ByteBufAllocator allocator = rule.alloc(); for (int i = 0; i < 10000; i++) { FluxSink[] sinks = new FluxSink[1]; - + AssertSubscriber assertSubscriber = AssertSubscriber.create(); rule.setAcceptingSocket( new AbstractRSocket() { @Override public Flux requestChannel(Publisher payloads) { + payloads.subscribe(assertSubscriber); return Flux.create( - sink -> { - sinks[0] = sink; - }, - FluxSink.OverflowStrategy.IGNORE) - .mergeWith(payloads); + sink -> { + sinks[0] = sink; + }, + FluxSink.OverflowStrategy.IGNORE); } }, 1); @@ -416,23 +414,23 @@ public Flux requestChannel(Publisher payloads) { rule.sendRequest(1, REQUEST_CHANNEL); ByteBuf metadata1 = allocator.buffer(); - metadata1.writeCharSequence("abc", CharsetUtil.UTF_8); + metadata1.writeCharSequence("abc1", CharsetUtil.UTF_8); ByteBuf data1 = allocator.buffer(); - data1.writeCharSequence("def", CharsetUtil.UTF_8); + data1.writeCharSequence("def1", CharsetUtil.UTF_8); ByteBuf nextFrame1 = PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata1, data1); ByteBuf metadata2 = allocator.buffer(); - metadata2.writeCharSequence("abc", CharsetUtil.UTF_8); + metadata2.writeCharSequence("abc2", CharsetUtil.UTF_8); ByteBuf data2 = allocator.buffer(); - data2.writeCharSequence("def", CharsetUtil.UTF_8); + data2.writeCharSequence("def2", CharsetUtil.UTF_8); ByteBuf nextFrame2 = PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata2, data2); ByteBuf metadata3 = allocator.buffer(); - metadata3.writeCharSequence("abc", CharsetUtil.UTF_8); + metadata3.writeCharSequence("abc3", CharsetUtil.UTF_8); ByteBuf data3 = allocator.buffer(); - data3.writeCharSequence("def", CharsetUtil.UTF_8); + data3.writeCharSequence("def3", CharsetUtil.UTF_8); ByteBuf nextFrame3 = PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata3, data3); @@ -454,13 +452,12 @@ public Flux requestChannel(Publisher payloads) { parallel); Assertions.assertThat(rule.connection.getSent()).allMatch(ReferenceCounted::release); - + Assertions.assertThat(assertSubscriber.values()).allMatch(ReferenceCounted::release); rule.assertHasNoLeaks(); } } @Test - @Disabled("Due to https://github.com/reactor/reactor-core/pull/2114") public void checkNoLeaksOnRacingBetweenDownstreamCancelAndOnNextFromRequestStreamTest1() { Scheduler parallel = Schedulers.parallel(); Hooks.onErrorDropped((e) -> {}); @@ -585,23 +582,23 @@ public Flux requestChannel(Publisher payloads) { ByteBuf cancelFrame = CancelFrameFlyweight.encode(allocator, 1); ByteBuf metadata1 = allocator.buffer(); - metadata1.writeCharSequence("abc", CharsetUtil.UTF_8); + metadata1.writeCharSequence("abc1", CharsetUtil.UTF_8); ByteBuf data1 = allocator.buffer(); - data1.writeCharSequence("def", CharsetUtil.UTF_8); + data1.writeCharSequence("def1", CharsetUtil.UTF_8); ByteBuf nextFrame1 = PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata1, data1); ByteBuf metadata2 = allocator.buffer(); - metadata2.writeCharSequence("abc", CharsetUtil.UTF_8); + metadata2.writeCharSequence("abc2", CharsetUtil.UTF_8); ByteBuf data2 = allocator.buffer(); - data2.writeCharSequence("def", CharsetUtil.UTF_8); + data2.writeCharSequence("def2", CharsetUtil.UTF_8); ByteBuf nextFrame2 = PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata2, data2); ByteBuf metadata3 = allocator.buffer(); - metadata3.writeCharSequence("abc", CharsetUtil.UTF_8); + metadata3.writeCharSequence("abc3", CharsetUtil.UTF_8); ByteBuf data3 = allocator.buffer(); - data3.writeCharSequence("def", CharsetUtil.UTF_8); + data3.writeCharSequence("de3", CharsetUtil.UTF_8); ByteBuf nextFrame3 = PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata3, data3); rule.connection.addToReceivedBuffer(nextFrame1, nextFrame2, nextFrame3); diff --git a/rsocket-core/src/test/java/io/rsocket/frame/ByteBufRepresentation.java b/rsocket-core/src/test/java/io/rsocket/frame/ByteBufRepresentation.java index 5e94935c5..63300c718 100644 --- a/rsocket-core/src/test/java/io/rsocket/frame/ByteBufRepresentation.java +++ b/rsocket-core/src/test/java/io/rsocket/frame/ByteBufRepresentation.java @@ -26,7 +26,13 @@ public final class ByteBufRepresentation extends StandardRepresentation { protected String fallbackToStringOf(Object object) { if (object instanceof ByteBuf) { try { - return ByteBufUtil.prettyHexDump((ByteBuf) object); + String normalBufferString = object.toString(); + String prettyHexDump = ByteBufUtil.prettyHexDump((ByteBuf) object); + return new StringBuilder() + .append(normalBufferString) + .append("\n") + .append(prettyHexDump) + .toString(); } catch (IllegalReferenceCountException e) { // noops } From 388e4e92e1f5028ff9cbff22f47b03abc9c91aec Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Mon, 27 Apr 2020 23:40:39 +0300 Subject: [PATCH 37/62] fixes readme and bumps versions Signed-off-by: Oleh Dokuka --- README.md | 8 ++++---- gradle.properties | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 538587154..0e055721e 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ repositories { mavenCentral() } dependencies { - implementation 'io.rsocket:rsocket-core:1.0.0-RC6' - implementation 'io.rsocket:rsocket-transport-netty:1.0.0-RC6' + implementation 'io.rsocket:rsocket-core:1.0.0-RC7' + implementation 'io.rsocket:rsocket-transport-netty:1.0.0-RC7' } ``` @@ -40,8 +40,8 @@ repositories { maven { url 'https://oss.jfrog.org/oss-snapshot-local' } } dependencies { - implementation 'io.rsocket:rsocket-core:1.0.0-RC7-SNAPSHOT' - implementation 'io.rsocket:rsocket-transport-netty:1.0.0-RC7-SNAPSHOT' + implementation 'io.rsocket:rsocket-core:1.0.0-RC8-SNAPSHOT' + implementation 'io.rsocket:rsocket-transport-netty:1.0.0-RC8-SNAPSHOT' } ``` diff --git a/gradle.properties b/gradle.properties index 3018f4792..2e429e05a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,5 +11,5 @@ # See the License for the specific language governing permissions and # limitations under the License. # -version=1.0.0-RC7 -perfBaselineVersion=1.0.0-RC6 +version=1.0.0-RC8 +perfBaselineVersion=1.0.0-RC7 From 922aa20b2988b50211911037aa8a470dd07a33e3 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Tue, 28 Apr 2020 09:33:14 +0300 Subject: [PATCH 38/62] fixes compilation issues in perf tests Signed-off-by: Oleh Dokuka --- .../io/rsocket/{ => core}/RSocketPerf.java | 26 ++++++++++--------- .../{ => core}/StreamIdSupplierPerf.java | 12 +++++++-- 2 files changed, 24 insertions(+), 14 deletions(-) rename benchmarks/src/main/java/io/rsocket/{ => core}/RSocketPerf.java (90%) rename benchmarks/src/main/java/io/rsocket/{ => core}/StreamIdSupplierPerf.java (66%) diff --git a/benchmarks/src/main/java/io/rsocket/RSocketPerf.java b/benchmarks/src/main/java/io/rsocket/core/RSocketPerf.java similarity index 90% rename from benchmarks/src/main/java/io/rsocket/RSocketPerf.java rename to benchmarks/src/main/java/io/rsocket/core/RSocketPerf.java index 0c6515140..f78843f5b 100644 --- a/benchmarks/src/main/java/io/rsocket/RSocketPerf.java +++ b/benchmarks/src/main/java/io/rsocket/core/RSocketPerf.java @@ -1,5 +1,11 @@ -package io.rsocket; - +package io.rsocket.core; + +import io.rsocket.AbstractRSocket; +import io.rsocket.Closeable; +import io.rsocket.Payload; +import io.rsocket.PayloadsMaxPerfSubscriber; +import io.rsocket.PayloadsPerfSubscriber; +import io.rsocket.RSocket; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.transport.local.LocalClientTransport; import io.rsocket.transport.local.LocalServerTransport; @@ -59,9 +65,7 @@ public void awaitToBeConsumed() { @Setup public void setUp() throws NoSuchFieldException, IllegalAccessException { server = - RSocketFactory.receive() - .frameDecoder(PayloadDecoder.ZERO_COPY) - .acceptor( + RSocketServer.create( (setup, sendingSocket) -> Mono.just( new AbstractRSocket() { @@ -89,16 +93,14 @@ public Flux requestChannel(Publisher payloads) { return Flux.from(payloads); } })) - .transport(LocalServerTransport.create("server")) - .start() + .payloadDecoder(PayloadDecoder.ZERO_COPY) + .bind(LocalServerTransport.create("server")) .block(); client = - RSocketFactory.connect() - .singleSubscriberRequester() - .frameDecoder(PayloadDecoder.ZERO_COPY) - .transport(LocalClientTransport.create("server")) - .start() + RSocketConnector.create() + .payloadDecoder(PayloadDecoder.ZERO_COPY) + .connect(LocalClientTransport.create("server")) .block(); Field sendProcessorField = RSocketRequester.class.getDeclaredField("sendProcessor"); diff --git a/benchmarks/src/main/java/io/rsocket/StreamIdSupplierPerf.java b/benchmarks/src/main/java/io/rsocket/core/StreamIdSupplierPerf.java similarity index 66% rename from benchmarks/src/main/java/io/rsocket/StreamIdSupplierPerf.java rename to benchmarks/src/main/java/io/rsocket/core/StreamIdSupplierPerf.java index c198b7a19..6b4f3f624 100644 --- a/benchmarks/src/main/java/io/rsocket/StreamIdSupplierPerf.java +++ b/benchmarks/src/main/java/io/rsocket/core/StreamIdSupplierPerf.java @@ -1,8 +1,16 @@ -package io.rsocket; +package io.rsocket.core; import io.netty.util.collection.IntObjectMap; import io.rsocket.internal.SynchronizedIntObjectHashMap; -import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.infra.Blackhole; @BenchmarkMode(Mode.Throughput) From 78082027c8e2590ed4af6db7af780dd44cc45917 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 28 Apr 2020 07:34:22 +0100 Subject: [PATCH 39/62] Deprecate ResponderRSocket (#802) --- .../main/java/io/rsocket/ResponderRSocket.java | 5 +++++ .../java/io/rsocket/core/RSocketResponder.java | 16 ++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/ResponderRSocket.java b/rsocket-core/src/main/java/io/rsocket/ResponderRSocket.java index f98901472..22697f130 100644 --- a/rsocket-core/src/main/java/io/rsocket/ResponderRSocket.java +++ b/rsocket-core/src/main/java/io/rsocket/ResponderRSocket.java @@ -1,12 +1,17 @@ package io.rsocket; +import java.util.function.BiFunction; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; /** * Extends the {@link RSocket} that allows an implementer to peek at the first request payload of a * channel. + * + * @deprecated as of 1.0 RC7 in favor of using {@link RSocket#requestChannel(Publisher)} with {@link + * Flux#switchOnFirst(BiFunction)} */ +@Deprecated public interface ResponderRSocket extends RSocket { /** * Implement this method to peak at the first payload of the incoming request stream without diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java index b5b298e14..f5c4aecec 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java @@ -27,7 +27,6 @@ import io.rsocket.DuplexConnection; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.ResponderRSocket; import io.rsocket.exceptions.ApplicationErrorException; import io.rsocket.frame.*; import io.rsocket.frame.decoder.PayloadDecoder; @@ -51,7 +50,7 @@ import reactor.util.concurrent.Queues; /** Responder side of RSocket. Receives {@link ByteBuf}s from a peer's {@link RSocketRequester} */ -class RSocketResponder implements ResponderRSocket { +class RSocketResponder implements RSocket { private static final Consumer DROPPED_ELEMENTS_CONSUMER = referenceCounted -> { if (referenceCounted.refCnt() > 0) { @@ -66,7 +65,10 @@ class RSocketResponder implements ResponderRSocket { private final DuplexConnection connection; private final RSocket requestHandler; - private final ResponderRSocket responderRSocket; + + @SuppressWarnings("deprecation") + private final io.rsocket.ResponderRSocket responderRSocket; + private final PayloadDecoder payloadDecoder; private final Consumer errorConsumer; private final ResponderLeaseHandler leaseHandler; @@ -86,6 +88,7 @@ class RSocketResponder implements ResponderRSocket { private final UnboundedProcessor sendProcessor; private final ByteBufAllocator allocator; + @SuppressWarnings("deprecation") RSocketResponder( DuplexConnection connection, RSocket requestHandler, @@ -99,7 +102,9 @@ class RSocketResponder implements ResponderRSocket { this.requestHandler = requestHandler; this.responderRSocket = - (requestHandler instanceof ResponderRSocket) ? (ResponderRSocket) requestHandler : null; + (requestHandler instanceof io.rsocket.ResponderRSocket) + ? (io.rsocket.ResponderRSocket) requestHandler + : null; this.payloadDecoder = payloadDecoder; this.errorConsumer = errorConsumer; @@ -219,8 +224,7 @@ public Flux requestChannel(Publisher payloads) { } } - @Override - public Flux requestChannel(Payload payload, Publisher payloads) { + private Flux requestChannel(Payload payload, Publisher payloads) { try { if (leaseHandler.useLease()) { return responderRSocket.requestChannel(payload, payloads); From 444709a131fc1e98a9c694e6cba20ad11b016cb7 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Tue, 28 Apr 2020 09:43:34 +0300 Subject: [PATCH 40/62] fixes code sample --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 0e055721e..8a2ba991c 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,6 @@ RSocket clientRSocket = // Enable Zero Copy .payloadDecoder(PayloadDecoder.ZERO_COPY) .connect(TcpClientTransport.create(7878)) - .start() .block(); ``` From 86ac4a0669f3aa70e9a107a454f06024b129bde4 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Tue, 28 Apr 2020 15:54:32 +0300 Subject: [PATCH 41/62] Removes TupleByteBufs (#804) --- .../rsocket/buffer/AbstractTupleByteBuf.java | 607 ------------------ .../java/io/rsocket/buffer/BufferUtil.java | 78 --- .../java/io/rsocket/buffer/Tuple2ByteBuf.java | 394 ------------ .../java/io/rsocket/buffer/Tuple3ByteBuf.java | 571 ---------------- .../java/io/rsocket/buffer/TupleByteBuf.java | 35 - .../io/rsocket/buffer/Tuple3ByteBufTest.java | 98 --- 6 files changed, 1783 deletions(-) delete mode 100644 rsocket-core/src/main/java/io/rsocket/buffer/AbstractTupleByteBuf.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/buffer/BufferUtil.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/buffer/Tuple2ByteBuf.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/buffer/Tuple3ByteBuf.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/buffer/TupleByteBuf.java delete mode 100644 rsocket-core/src/test/java/io/rsocket/buffer/Tuple3ByteBufTest.java diff --git a/rsocket-core/src/main/java/io/rsocket/buffer/AbstractTupleByteBuf.java b/rsocket-core/src/main/java/io/rsocket/buffer/AbstractTupleByteBuf.java deleted file mode 100644 index a80605877..000000000 --- a/rsocket-core/src/main/java/io/rsocket/buffer/AbstractTupleByteBuf.java +++ /dev/null @@ -1,607 +0,0 @@ -package io.rsocket.buffer; - -import io.netty.buffer.AbstractReferenceCountedByteBuf; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.Unpooled; -import io.netty.util.internal.SystemPropertyUtil; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.channels.FileChannel; -import java.nio.channels.ScatteringByteChannel; -import java.nio.charset.Charset; - -abstract class AbstractTupleByteBuf extends AbstractReferenceCountedByteBuf { - static final int DEFAULT_DIRECT_MEMORY_CACHE_ALIGNMENT = - SystemPropertyUtil.getInt("io.netty.allocator.directMemoryCacheAlignment", 0); - static final ByteBuffer EMPTY_NIO_BUFFER = Unpooled.EMPTY_BUFFER.nioBuffer(); - static final int NOT_ENOUGH_BYTES_AT_MAX_CAPACITY_CODE = 3; - - final ByteBufAllocator allocator; - final int capacity; - - AbstractTupleByteBuf(ByteBufAllocator allocator, int capacity) { - super(Integer.MAX_VALUE); - - this.capacity = capacity; - this.allocator = allocator; - super.writerIndex(capacity); - } - - abstract long calculateRelativeIndex(int index); - - abstract ByteBuf getPart(int index); - - @Override - public ByteBuffer nioBuffer(int index, int length) { - checkIndex(index, length); - - ByteBuffer[] buffers = nioBuffers(index, length); - - if (buffers.length == 1) { - return buffers[0].duplicate(); - } - - ByteBuffer merged = - BufferUtil.allocateDirectAligned(length, DEFAULT_DIRECT_MEMORY_CACHE_ALIGNMENT) - .order(order()); - for (ByteBuffer buf : buffers) { - merged.put(buf); - } - - merged.flip(); - return merged; - } - - @Override - public ByteBuffer[] nioBuffers(int index, int length) { - checkIndex(index, length); - if (length == 0) { - return new ByteBuffer[] {EMPTY_NIO_BUFFER}; - } - return _nioBuffers(index, length); - } - - protected abstract ByteBuffer[] _nioBuffers(int index, int length); - - @Override - protected byte _getByte(final int index) { - long ri = calculateRelativeIndex(index); - ByteBuf byteBuf = getPart(index); - - int calculatedIndex = (int) (ri & Integer.MAX_VALUE); - - return byteBuf.getByte(calculatedIndex); - } - - @Override - protected short _getShort(final int index) { - long ri = calculateRelativeIndex(index); - ByteBuf byteBuf = getPart(index); - - final int calculatedIndex = (int) (ri & Integer.MAX_VALUE); - - if (calculatedIndex + Short.BYTES <= byteBuf.writerIndex()) { - return byteBuf.getShort(calculatedIndex); - } else if (order() == ByteOrder.BIG_ENDIAN) { - return (short) ((_getByte(index) & 0xff) << 8 | _getByte(index + 1) & 0xff); - } else { - return (short) (_getByte(index) & 0xff | (_getByte(index + 1) & 0xff) << 8); - } - } - - @Override - protected short _getShortLE(int index) { - long ri = calculateRelativeIndex(index); - ByteBuf byteBuf = getPart(index); - - final int calculatedIndex = (int) (ri & Integer.MAX_VALUE); - - if (calculatedIndex + Short.BYTES <= byteBuf.writerIndex()) { - return byteBuf.getShortLE(calculatedIndex); - } else if (order() == ByteOrder.BIG_ENDIAN) { - return (short) (_getByte(index) & 0xff | (_getByte(index + 1) & 0xff) << 8); - } else { - return (short) ((_getByte(index) & 0xff) << 8 | _getByte(index + 1) & 0xff); - } - } - - @Override - protected int _getUnsignedMedium(final int index) { - long ri = calculateRelativeIndex(index); - ByteBuf byteBuf = getPart(index); - - int calculatedIndex = (int) (ri & Integer.MAX_VALUE); - - if (calculatedIndex + 3 <= byteBuf.writerIndex()) { - return byteBuf.getUnsignedMedium(calculatedIndex); - } else if (order() == ByteOrder.BIG_ENDIAN) { - return (_getShort(index) & 0xffff) << 8 | _getByte(index + 2) & 0xff; - } else { - return _getShort(index) & 0xFFFF | (_getByte(index + 2) & 0xFF) << 16; - } - } - - @Override - protected int _getUnsignedMediumLE(int index) { - long ri = calculateRelativeIndex(index); - ByteBuf byteBuf = getPart(index); - - int calculatedIndex = (int) (ri & Integer.MAX_VALUE); - - if (calculatedIndex + 3 <= byteBuf.writerIndex()) { - return byteBuf.getUnsignedMediumLE(calculatedIndex); - } else if (order() == ByteOrder.BIG_ENDIAN) { - return _getShortLE(index) & 0xffff | (_getByte(index + 2) & 0xff) << 16; - } else { - return (_getShortLE(index) & 0xffff) << 8 | _getByte(index + 2) & 0xff; - } - } - - @Override - protected int _getInt(final int index) { - long ri = calculateRelativeIndex(index); - ByteBuf byteBuf = getPart(index); - - int calculatedIndex = (int) (ri & Integer.MAX_VALUE); - - if (calculatedIndex + Integer.BYTES <= byteBuf.writerIndex()) { - return byteBuf.getInt(calculatedIndex); - } else if (order() == ByteOrder.BIG_ENDIAN) { - return (_getShort(index) & 0xffff) << 16 | _getShort(index + 2) & 0xffff; - } else { - return _getShort(index) & 0xFFFF | (_getShort(index + 2) & 0xFFFF) << 16; - } - } - - @Override - protected int _getIntLE(final int index) { - long ri = calculateRelativeIndex(index); - ByteBuf byteBuf = getPart(index); - - int calculatedIndex = (int) (ri & Integer.MAX_VALUE); - - if (calculatedIndex + Integer.BYTES <= byteBuf.writerIndex()) { - return byteBuf.getIntLE(calculatedIndex); - } else if (order() == ByteOrder.BIG_ENDIAN) { - return _getShortLE(index) & 0xffff | (_getShortLE(index + 2) & 0xffff) << 16; - } else { - return (_getShortLE(index) & 0xffff) << 16 | _getShortLE(index + 2) & 0xffff; - } - } - - @Override - protected long _getLong(final int index) { - long ri = calculateRelativeIndex(index); - ByteBuf byteBuf = getPart(index); - - int calculatedIndex = (int) (ri & Integer.MAX_VALUE); - - if (calculatedIndex + Long.BYTES <= byteBuf.writerIndex()) { - return byteBuf.getLong(calculatedIndex); - } else if (order() == ByteOrder.BIG_ENDIAN) { - return (_getInt(index) & 0xffffffffL) << 32 | _getInt(index + 4) & 0xffffffffL; - } else { - return _getInt(index) & 0xFFFFFFFFL | (_getInt(index + 4) & 0xFFFFFFFFL) << 32; - } - } - - @Override - protected long _getLongLE(final int index) { - long ri = calculateRelativeIndex(index); - ByteBuf byteBuf = getPart(index); - - int calculatedIndex = (int) (ri & Integer.MAX_VALUE); - - if (calculatedIndex + Long.BYTES <= byteBuf.writerIndex()) { - return byteBuf.getLongLE(calculatedIndex); - } else if (order() == ByteOrder.BIG_ENDIAN) { - return (_getInt(index) & 0xffffffffL) << 32 | _getInt(index + 4) & 0xffffffffL; - } else { - return _getInt(index) & 0xFFFFFFFFL | (_getInt(index + 4) & 0xFFFFFFFFL) << 32; - } - } - - @Override - public ByteBufAllocator alloc() { - return allocator; - } - - @Override - public int capacity() { - return capacity; - } - - @Override - public ByteBuf capacity(int newCapacity) { - throw new UnsupportedOperationException(); - } - - @Override - public int maxCapacity() { - return capacity; - } - - @Override - public ByteOrder order() { - return ByteOrder.LITTLE_ENDIAN; - } - - @Override - public ByteBuf order(ByteOrder endianness) { - return this; - } - - @Override - public ByteBuf unwrap() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isReadOnly() { - return true; - } - - @Override - public ByteBuf asReadOnly() { - return this; - } - - @Override - public boolean isWritable() { - return false; - } - - @Override - public boolean isWritable(int size) { - return false; - } - - @Override - public ByteBuf writerIndex(int writerIndex) { - return this; - } - - @Override - public final int writerIndex() { - return capacity; - } - - @Override - public ByteBuf setIndex(int readerIndex, int writerIndex) { - return this; - } - - @Override - public ByteBuf clear() { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf discardReadBytes() { - return this; - } - - @Override - public ByteBuf discardSomeReadBytes() { - return this; - } - - @Override - public ByteBuf ensureWritable(int minWritableBytes) { - return this; - } - - @Override - public int ensureWritable(int minWritableBytes, boolean force) { - return NOT_ENOUGH_BYTES_AT_MAX_CAPACITY_CODE; - } - - @Override - public ByteBuf setFloatLE(int index, float value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setDoubleLE(int index, double value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setBoolean(int index, boolean value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setByte(int index, int value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setShort(int index, int value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setShortLE(int index, int value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setMedium(int index, int value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setMediumLE(int index, int value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setInt(int index, int value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setIntLE(int index, int value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setLong(int index, long value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setLongLE(int index, long value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setChar(int index, int value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setFloat(int index, float value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setDouble(int index, double value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setBytes(int index, ByteBuf src) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setBytes(int index, ByteBuf src, int length) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setBytes(int index, byte[] src) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setBytes(int index, ByteBuffer src) { - throw new UnsupportedOperationException(); - } - - @Override - public int setBytes(int index, InputStream in, int length) { - throw new UnsupportedOperationException(); - } - - @Override - public int setBytes(int index, ScatteringByteChannel in, int length) { - throw new UnsupportedOperationException(); - } - - @Override - public int setBytes(int index, FileChannel in, long position, int length) { - throw new UnsupportedOperationException(); - } - - @Override - public int setCharSequence(int index, CharSequence sequence, Charset charset) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf setZero(int index, int length) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeBoolean(boolean value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeByte(int value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeShort(int value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeShortLE(int value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeMedium(int value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeMediumLE(int value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeInt(int value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeIntLE(int value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeLong(long value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeLongLE(long value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeChar(int value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeFloat(float value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeDouble(double value) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeBytes(ByteBuf src) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeBytes(ByteBuf src, int length) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeBytes(byte[] src) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeBytes(byte[] src, int srcIndex, int length) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeBytes(ByteBuffer src) { - throw new UnsupportedOperationException(); - } - - @Override - public int writeBytes(InputStream in, int length) { - throw new UnsupportedOperationException(); - } - - @Override - public int writeBytes(ScatteringByteChannel in, int length) { - throw new UnsupportedOperationException(); - } - - @Override - public int writeBytes(FileChannel in, long position, int length) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuf writeZero(int length) { - throw new UnsupportedOperationException(); - } - - @Override - public int writeCharSequence(CharSequence sequence, Charset charset) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteBuffer internalNioBuffer(int index, int length) { - return nioBuffer(index, length); - } - - @Override - public boolean hasArray() { - return false; - } - - @Override - public byte[] array() { - throw new UnsupportedOperationException(); - } - - @Override - public int arrayOffset() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean hasMemoryAddress() { - return false; - } - - @Override - public long memoryAddress() { - throw new UnsupportedOperationException(); - } - - @Override - protected void _setByte(int index, int value) {} - - @Override - protected void _setShort(int index, int value) {} - - @Override - protected void _setShortLE(int index, int value) {} - - @Override - protected void _setMedium(int index, int value) {} - - @Override - protected void _setMediumLE(int index, int value) {} - - @Override - protected void _setInt(int index, int value) {} - - @Override - protected void _setIntLE(int index, int value) {} - - @Override - protected void _setLong(int index, long value) {} - - @Override - protected void _setLongLE(int index, long value) {} -} diff --git a/rsocket-core/src/main/java/io/rsocket/buffer/BufferUtil.java b/rsocket-core/src/main/java/io/rsocket/buffer/BufferUtil.java deleted file mode 100644 index 476583ab3..000000000 --- a/rsocket-core/src/main/java/io/rsocket/buffer/BufferUtil.java +++ /dev/null @@ -1,78 +0,0 @@ -package io.rsocket.buffer; - -import java.lang.reflect.Field; -import java.nio.Buffer; -import java.nio.ByteBuffer; -import java.security.AccessController; -import java.security.PrivilegedExceptionAction; -import sun.misc.Unsafe; - -abstract class BufferUtil { - - private static final Unsafe UNSAFE; - - static { - Unsafe unsafe; - try { - final PrivilegedExceptionAction action = - () -> { - final Field f = Unsafe.class.getDeclaredField("theUnsafe"); - f.setAccessible(true); - - return (Unsafe) f.get(null); - }; - - unsafe = AccessController.doPrivileged(action); - } catch (final Exception ex) { - throw new RuntimeException(ex); - } - - UNSAFE = unsafe; - } - - private static final long BYTE_BUFFER_ADDRESS_FIELD_OFFSET; - - static { - try { - BYTE_BUFFER_ADDRESS_FIELD_OFFSET = - UNSAFE.objectFieldOffset(Buffer.class.getDeclaredField("address")); - } catch (final Exception ex) { - throw new RuntimeException(ex); - } - } - - /** - * Allocate a new direct {@link ByteBuffer} that is aligned on a given alignment boundary. - * - * @param capacity required for the buffer. - * @param alignment boundary at which the buffer should begin. - * @return a new {@link ByteBuffer} with the required alignment. - * @throws IllegalArgumentException if the alignment is not a power of 2. - */ - static ByteBuffer allocateDirectAligned(final int capacity, final int alignment) { - if (alignment == 0) { - return ByteBuffer.allocateDirect(capacity); - } - - if (!isPowerOfTwo(alignment)) { - throw new IllegalArgumentException("Must be a power of 2: alignment=" + alignment); - } - - final ByteBuffer buffer = ByteBuffer.allocateDirect(capacity + alignment); - - final long address = UNSAFE.getLong(buffer, BYTE_BUFFER_ADDRESS_FIELD_OFFSET); - final int remainder = (int) (address & (alignment - 1)); - final int offset = alignment - remainder; - - buffer.limit(capacity + offset); - buffer.position(offset); - - return buffer.slice(); - } - - private static boolean isPowerOfTwo(final int value) { - return value > 0 && ((value & (~value + 1)) == value); - } - - private BufferUtil() {} -} diff --git a/rsocket-core/src/main/java/io/rsocket/buffer/Tuple2ByteBuf.java b/rsocket-core/src/main/java/io/rsocket/buffer/Tuple2ByteBuf.java deleted file mode 100644 index ba6620cb0..000000000 --- a/rsocket-core/src/main/java/io/rsocket/buffer/Tuple2ByteBuf.java +++ /dev/null @@ -1,394 +0,0 @@ -package io.rsocket.buffer; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.Unpooled; -import io.netty.util.ReferenceCountUtil; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.channels.GatheringByteChannel; -import java.nio.charset.Charset; - -class Tuple2ByteBuf extends AbstractTupleByteBuf { - - private static final long ONE_MASK = 0x100000000L; - private static final long TWO_MASK = 0x200000000L; - private static final long MASK = 0x700000000L; - - private final ByteBuf one; - private final ByteBuf two; - private final int oneReadIndex; - private final int twoReadIndex; - private final int oneReadableBytes; - private final int twoReadableBytes; - private final int twoRelativeIndex; - - private boolean freed; - - Tuple2ByteBuf(ByteBufAllocator allocator, ByteBuf one, ByteBuf two) { - super(allocator, one.readableBytes() + two.readableBytes()); - - this.one = one; - this.two = two; - - this.oneReadIndex = one.readerIndex(); - this.twoReadIndex = two.readerIndex(); - - this.oneReadableBytes = one.readableBytes(); - this.twoReadableBytes = two.readableBytes(); - - this.twoRelativeIndex = oneReadableBytes; - - this.freed = false; - } - - @Override - long calculateRelativeIndex(int index) { - checkIndex(index, 0); - - long relativeIndex; - long mask; - if (index >= twoRelativeIndex) { - relativeIndex = twoReadIndex + (index - oneReadableBytes); - mask = TWO_MASK; - } else { - relativeIndex = oneReadIndex + index; - mask = ONE_MASK; - } - - return relativeIndex | mask; - } - - @Override - ByteBuf getPart(int index) { - long ri = calculateRelativeIndex(index); - switch ((int) ((ri & MASK) >>> 32L)) { - case 0x1: - return one; - case 0x2: - return two; - default: - throw new IllegalStateException(); - } - } - - @Override - public boolean isDirect() { - return one.isDirect() && two.isDirect(); - } - - @Override - public int nioBufferCount() { - return one.nioBufferCount() + two.nioBufferCount(); - } - - @Override - public ByteBuffer nioBuffer() { - ByteBuffer[] oneBuffers = one.nioBuffers(); - ByteBuffer[] twoBuffers = two.nioBuffers(); - - ByteBuffer merged = - BufferUtil.allocateDirectAligned(capacity, DEFAULT_DIRECT_MEMORY_CACHE_ALIGNMENT) - .order(order()); - - for (ByteBuffer b : oneBuffers) { - merged.put(b); - } - - for (ByteBuffer b : twoBuffers) { - merged.put(b); - } - - merged.flip(); - return merged; - } - - @Override - public ByteBuffer[] _nioBuffers(int index, int length) { - long ri = calculateRelativeIndex(index); - index = (int) (ri & Integer.MAX_VALUE); - switch ((int) ((ri & MASK) >>> 32L)) { - case 0x1: - ByteBuffer[] oneBuffer; - ByteBuffer[] twoBuffer; - int l = Math.min(oneReadableBytes - index, length); - oneBuffer = one.nioBuffers(index, l); - length -= l; - if (length != 0) { - twoBuffer = two.nioBuffers(twoReadIndex, length); - ByteBuffer[] results = new ByteBuffer[oneBuffer.length + twoBuffer.length]; - System.arraycopy(oneBuffer, 0, results, 0, oneBuffer.length); - System.arraycopy(twoBuffer, 0, results, oneBuffer.length, twoBuffer.length); - return results; - } else { - return oneBuffer; - } - case 0x2: - return two.nioBuffers(index, length); - default: - throw new IllegalStateException(); - } - } - - @Override - public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { - checkDstIndex(index, length, dstIndex, dst.capacity()); - if (length == 0) { - return this; - } - - // FIXME: check twice here - long ri = calculateRelativeIndex(index); - index = (int) (ri & Integer.MAX_VALUE); - switch ((int) ((ri & MASK) >>> 32L)) { - case 0x1: - { - int l = Math.min(oneReadableBytes - index, length); - one.getBytes(index, dst, dstIndex, l); - length -= l; - dstIndex += l; - - if (length != 0) { - two.getBytes(twoReadIndex, dst, dstIndex, length); - } - - break; - } - case 0x2: - { - two.getBytes(index, dst, dstIndex, length); - break; - } - default: - throw new IllegalStateException(); - } - - return this; - } - - @Override - public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { - ByteBuf dstBuf = Unpooled.wrappedBuffer(dst); - return getBytes(index, dstBuf, dstIndex, length); - } - - @Override - public ByteBuf getBytes(int index, ByteBuffer dst) { - ByteBuf dstBuf = Unpooled.wrappedBuffer(dst); - return getBytes(index, dstBuf); - } - - @Override - public ByteBuf getBytes(int index, final OutputStream out, int length) throws IOException { - checkIndex(index, length); - if (length == 0) { - return this; - } - - long ri = calculateRelativeIndex(index); - index = (int) (ri & Integer.MAX_VALUE); - switch ((int) ((ri & MASK) >>> 32L)) { - case 0x1: - { - int l = Math.min(oneReadableBytes - index, length); - one.getBytes(index, out, l); - length -= l; - if (length != 0) { - two.getBytes(twoReadIndex, out, length); - } - break; - } - case 0x2: - { - two.getBytes(index, out, length); - break; - } - default: - throw new IllegalStateException(); - } - - return this; - } - - @Override - public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { - checkIndex(index, length); - int read = 0; - long ri = calculateRelativeIndex(index); - index = (int) (ri & Integer.MAX_VALUE); - switch ((int) ((ri & MASK) >>> 32L)) { - case 0x1: - { - int l = Math.min(oneReadableBytes - index, length); - read += one.getBytes(index, out, l); - length -= l; - if (length != 0) { - read += two.getBytes(twoReadIndex, out, length); - } - break; - } - case 0x2: - { - read += two.getBytes(index, out, length); - break; - } - default: - throw new IllegalStateException(); - } - - return read; - } - - @Override - public int getBytes(int index, FileChannel out, long position, int length) throws IOException { - checkIndex(index, length); - int read = 0; - long ri = calculateRelativeIndex(index); - index = (int) (ri & Integer.MAX_VALUE); - switch ((int) ((ri & MASK) >>> 32L)) { - case 0x1: - { - int l = Math.min(oneReadableBytes - index, length); - read += one.getBytes(index, out, position, l); - length -= l; - position += l; - if (length != 0) { - read += two.getBytes(twoReadIndex, out, position, length); - } - break; - } - case 0x2: - { - read += two.getBytes(index, out, position, length); - break; - } - default: - throw new IllegalStateException(); - } - - return read; - } - - @Override - public ByteBuf copy(int index, int length) { - checkIndex(index, length); - - ByteBuf buffer = allocator.buffer(length); - - if (index == 0 && length == capacity) { - buffer.writeBytes(one, oneReadIndex, oneReadableBytes); - buffer.writeBytes(two, twoReadIndex, twoReadableBytes); - - return buffer; - } - - long ri = calculateRelativeIndex(index); - index = (int) (ri & Integer.MAX_VALUE); - - switch ((int) ((ri & MASK) >>> 32L)) { - case 0x1: - { - int l = Math.min(oneReadableBytes - index, length); - buffer.writeBytes(one, index, l); - - length -= l; - - if (length != 0) { - buffer.writeBytes(two, twoReadIndex, length); - } - - return buffer; - } - case 0x2: - { - return buffer.writeBytes(two, index, length); - } - default: - throw new IllegalStateException(); - } - } - - @Override - public ByteBuf slice(final int readIndex, int length) { - checkIndex(readIndex, length); - - if (readIndex == 0 && length == capacity) { - return new Tuple2ByteBuf( - allocator, - one.slice(oneReadIndex, oneReadableBytes), - two.slice(twoReadIndex, twoReadableBytes)); - } - - long ri = calculateRelativeIndex(readIndex); - int index = (int) (ri & Integer.MAX_VALUE); - - switch ((int) ((ri & MASK) >>> 32L)) { - case 0x1: - { - ByteBuf oneSlice; - ByteBuf twoSlice; - - int l = Math.min(oneReadableBytes - index, length); - oneSlice = one.slice(index, l); - length -= l; - if (length != 0) { - twoSlice = two.slice(twoReadIndex, length); - return new Tuple2ByteBuf(allocator, oneSlice, twoSlice); - } else { - return oneSlice; - } - } - case 0x2: - { - return two.slice(index, length); - } - default: - throw new IllegalStateException(); - } - } - - @Override - protected void deallocate() { - if (freed) { - return; - } - - freed = true; - ReferenceCountUtil.safeRelease(one); - ReferenceCountUtil.safeRelease(two); - } - - @Override - public String toString(Charset charset) { - StringBuilder builder = new StringBuilder(capacity); - builder.append(one.toString(charset)); - builder.append(two.toString(charset)); - return builder.toString(); - } - - @Override - public String toString() { - return "Tuple2ByteBuf{" - + "capacity=" - + capacity - + ", one=" - + one - + ", two=" - + two - + ", allocator=" - + allocator - + ", oneReadIndex=" - + oneReadIndex - + ", twoReadIndex=" - + twoReadIndex - + ", oneReadableBytes=" - + oneReadableBytes - + ", twoReadableBytes=" - + twoReadableBytes - + ", twoRelativeIndex=" - + twoRelativeIndex - + '}'; - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/buffer/Tuple3ByteBuf.java b/rsocket-core/src/main/java/io/rsocket/buffer/Tuple3ByteBuf.java deleted file mode 100644 index be593019f..000000000 --- a/rsocket-core/src/main/java/io/rsocket/buffer/Tuple3ByteBuf.java +++ /dev/null @@ -1,571 +0,0 @@ -package io.rsocket.buffer; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.Unpooled; -import io.netty.util.ReferenceCountUtil; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.channels.GatheringByteChannel; -import java.nio.charset.Charset; - -class Tuple3ByteBuf extends AbstractTupleByteBuf { - private static final long ONE_MASK = 0x100000000L; - private static final long TWO_MASK = 0x200000000L; - private static final long THREE_MASK = 0x400000000L; - private static final long MASK = 0x700000000L; - - private final ByteBuf one; - private final ByteBuf two; - private final ByteBuf three; - private final int oneReadIndex; - private final int twoReadIndex; - private final int threeReadIndex; - private final int oneReadableBytes; - private final int twoReadableBytes; - private final int threeReadableBytes; - private final int twoRelativeIndex; - private final int threeRelativeIndex; - - private boolean freed; - - Tuple3ByteBuf(ByteBufAllocator allocator, ByteBuf one, ByteBuf two, ByteBuf three) { - super(allocator, one.readableBytes() + two.readableBytes() + three.readableBytes()); - - this.one = one; - this.two = two; - this.three = three; - - this.oneReadIndex = one.readerIndex(); - this.twoReadIndex = two.readerIndex(); - this.threeReadIndex = three.readerIndex(); - - this.oneReadableBytes = one.readableBytes(); - this.twoReadableBytes = two.readableBytes(); - this.threeReadableBytes = three.readableBytes(); - - this.twoRelativeIndex = oneReadableBytes; - this.threeRelativeIndex = twoRelativeIndex + twoReadableBytes; - - this.freed = false; - } - - @Override - public boolean isDirect() { - return one.isDirect() && two.isDirect() && three.isDirect(); - } - - @Override - public long calculateRelativeIndex(int index) { - checkIndex(index, 0); - long relativeIndex; - long mask; - if (index >= threeRelativeIndex) { - relativeIndex = threeReadIndex + (index - twoReadableBytes - oneReadableBytes); - mask = THREE_MASK; - } else if (index >= twoRelativeIndex) { - relativeIndex = twoReadIndex + (index - oneReadableBytes); - mask = TWO_MASK; - } else { - relativeIndex = oneReadIndex + index; - mask = ONE_MASK; - } - - return relativeIndex | mask; - } - - @Override - public ByteBuf getPart(int index) { - long ri = calculateRelativeIndex(index); - switch ((int) ((ri & MASK) >>> 32L)) { - case 0x1: - return one; - case 0x2: - return two; - case 0x4: - return three; - default: - throw new IllegalStateException(); - } - } - - @Override - public int nioBufferCount() { - return one.nioBufferCount() + two.nioBufferCount() + three.nioBufferCount(); - } - - @Override - public ByteBuffer nioBuffer() { - - ByteBuffer[] oneBuffers = one.nioBuffers(); - ByteBuffer[] twoBuffers = two.nioBuffers(); - ByteBuffer[] threeBuffers = three.nioBuffers(); - - ByteBuffer merged = - BufferUtil.allocateDirectAligned(capacity, DEFAULT_DIRECT_MEMORY_CACHE_ALIGNMENT) - .order(order()); - - for (ByteBuffer b : oneBuffers) { - merged.put(b); - } - - for (ByteBuffer b : twoBuffers) { - merged.put(b); - } - - for (ByteBuffer b : threeBuffers) { - merged.put(b); - } - - merged.flip(); - return merged; - } - - @Override - public ByteBuffer[] _nioBuffers(int index, int length) { - long ri = calculateRelativeIndex(index); - index = (int) (ri & Integer.MAX_VALUE); - switch ((int) ((ri & MASK) >>> 32L)) { - case 0x1: - { - ByteBuffer[] oneBuffer; - ByteBuffer[] twoBuffer; - ByteBuffer[] threeBuffer; - int l = Math.min(oneReadableBytes - index, length); - oneBuffer = one.nioBuffers(index, l); - length -= l; - if (length != 0) { - l = Math.min(twoReadableBytes, length); - twoBuffer = two.nioBuffers(twoReadIndex, l); - length -= l; - if (length != 0) { - threeBuffer = three.nioBuffers(threeReadIndex, length); - ByteBuffer[] results = - new ByteBuffer[oneBuffer.length + twoBuffer.length + threeBuffer.length]; - System.arraycopy(oneBuffer, 0, results, 0, oneBuffer.length); - System.arraycopy(twoBuffer, 0, results, oneBuffer.length, twoBuffer.length); - System.arraycopy( - threeBuffer, 0, results, oneBuffer.length + twoBuffer.length, threeBuffer.length); - return results; - } else { - ByteBuffer[] results = new ByteBuffer[oneBuffer.length + twoBuffer.length]; - System.arraycopy(oneBuffer, 0, results, 0, oneBuffer.length); - System.arraycopy(twoBuffer, 0, results, oneBuffer.length, twoBuffer.length); - return results; - } - } else { - return oneBuffer; - } - } - case 0x2: - { - ByteBuffer[] twoBuffer; - ByteBuffer[] threeBuffer; - int l = Math.min(twoReadableBytes - index, length); - twoBuffer = two.nioBuffers(index, l); - length -= l; - if (length != 0) { - threeBuffer = three.nioBuffers(threeReadIndex, length); - ByteBuffer[] results = new ByteBuffer[twoBuffer.length + threeBuffer.length]; - System.arraycopy(twoBuffer, 0, results, 0, twoBuffer.length); - System.arraycopy(threeBuffer, 0, results, twoBuffer.length, threeBuffer.length); - return results; - } else { - return twoBuffer; - } - } - case 0x4: - return three.nioBuffers(index, length); - default: - throw new IllegalStateException(); - } - } - - @Override - public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { - checkDstIndex(index, length, dstIndex, dst.capacity()); - long ri = calculateRelativeIndex(index); - index = (int) (ri & Integer.MAX_VALUE); - switch ((int) ((ri & MASK) >>> 32L)) { - case 0x1: - { - int l = Math.min(oneReadableBytes - index, length); - one.getBytes(index, dst, dstIndex, l); - length -= l; - dstIndex += l; - - if (length != 0) { - l = Math.min(twoReadableBytes, length); - two.getBytes(twoReadIndex, dst, dstIndex, l); - length -= l; - dstIndex += l; - - if (length != 0) { - three.getBytes(threeReadIndex, dst, dstIndex, length); - } - } - break; - } - case 0x2: - { - int l = Math.min(twoReadableBytes - index, length); - two.getBytes(index, dst, dstIndex, l); - length -= l; - dstIndex += l; - - if (length != 0) { - three.getBytes(threeReadIndex, dst, dstIndex, length); - } - break; - } - case 0x4: - { - three.getBytes(index, dst, dstIndex, length); - break; - } - default: - throw new IllegalStateException(); - } - - return this; - } - - @Override - public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { - ByteBuf dstBuf = Unpooled.wrappedBuffer(dst); - return getBytes(index, dstBuf, dstIndex, length); - } - - @Override - public ByteBuf getBytes(int index, ByteBuffer dst) { - ByteBuf dstBuf = Unpooled.wrappedBuffer(dst); - return getBytes(index, dstBuf); - } - - @Override - public ByteBuf getBytes(int index, final OutputStream out, int length) throws IOException { - checkIndex(index, length); - long ri = calculateRelativeIndex(index); - index = (int) (ri & Integer.MAX_VALUE); - switch ((int) ((ri & MASK) >>> 32L)) { - case 0x1: - { - int l = Math.min(oneReadableBytes - index, length); - one.getBytes(index, out, l); - length -= l; - if (length != 0) { - l = Math.min(twoReadableBytes, length); - two.getBytes(twoReadIndex, out, l); - length -= l; - if (length != 0) { - three.getBytes(threeReadIndex, out, length); - } - } - break; - } - case 0x2: - { - int l = Math.min(twoReadableBytes - index, length); - two.getBytes(index, out, l); - length -= l; - - if (length != 0) { - three.getBytes(threeReadIndex, out, length); - } - break; - } - case 0x4: - { - three.getBytes(index, out, length); - - break; - } - default: - throw new IllegalStateException(); - } - - return this; - } - - @Override - public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { - checkIndex(index, length); - int read = 0; - long ri = calculateRelativeIndex(index); - index = (int) (ri & Integer.MAX_VALUE); - switch ((int) ((ri & MASK) >>> 32L)) { - case 0x1: - { - int l = Math.min(oneReadableBytes - index, length); - read += one.getBytes(index, out, l); - length -= l; - if (length != 0) { - l = Math.min(twoReadableBytes, length); - read += two.getBytes(twoReadIndex, out, l); - length -= l; - if (length != 0) { - read += three.getBytes(threeReadIndex, out, length); - } - } - break; - } - case 0x2: - { - int l = Math.min(twoReadableBytes - index, length); - read += two.getBytes(index, out, l); - length -= l; - - if (length != 0) { - read += three.getBytes(threeReadIndex, out, length); - } - break; - } - case 0x4: - { - read += three.getBytes(index, out, length); - - break; - } - default: - throw new IllegalStateException(); - } - - return read; - } - - @Override - public int getBytes(int index, FileChannel out, long position, int length) throws IOException { - checkIndex(index, length); - int read = 0; - long ri = calculateRelativeIndex(index); - index = (int) (ri & Integer.MAX_VALUE); - switch ((int) ((ri & MASK) >>> 32L)) { - case 0x1: - { - int l = Math.min(oneReadableBytes - index, length); - read += one.getBytes(index, out, position, l); - length -= l; - position += l; - - if (length != 0) { - l = Math.min(twoReadableBytes, length); - read += two.getBytes(twoReadIndex, out, position, l); - length -= l; - position += l; - - if (length != 0) { - read += three.getBytes(threeReadIndex, out, position, length); - } - } - break; - } - case 0x2: - { - int l = Math.min(twoReadableBytes - index, length); - read += two.getBytes(index, out, position, l); - length -= l; - position += l; - - if (length != 0) { - read += three.getBytes(threeReadIndex, out, position, length); - } - break; - } - case 0x4: - { - read += three.getBytes(index, out, position, length); - - break; - } - default: - throw new IllegalStateException(); - } - - return read; - } - - @Override - public ByteBuf copy(int index, int length) { - checkIndex(index, length); - - ByteBuf buffer = allocator.buffer(length); - - if (index == 0 && length == capacity) { - buffer.writeBytes(one, oneReadIndex, oneReadableBytes); - buffer.writeBytes(two, twoReadIndex, twoReadableBytes); - buffer.writeBytes(three, threeReadIndex, threeReadableBytes); - - return buffer; - } - - long ri = calculateRelativeIndex(index); - index = (int) (ri & Integer.MAX_VALUE); - - switch ((int) ((ri & MASK) >>> 32L)) { - case 0x1: - { - int l = Math.min(oneReadableBytes - index, length); - buffer.writeBytes(one, index, l); - length -= l; - - if (length != 0) { - l = Math.min(twoReadableBytes, length); - buffer.writeBytes(two, twoReadIndex, l); - length -= l; - if (length != 0) { - buffer.writeBytes(three, threeReadIndex, length); - } - } - - return buffer; - } - case 0x2: - { - int l = Math.min(twoReadableBytes - index, length); - buffer.writeBytes(two, index, l); - length -= l; - - if (length != 0) { - buffer.writeBytes(three, threeReadIndex, length); - } - - return buffer; - } - case 0x4: - { - buffer.writeBytes(three, index, length); - - return buffer; - } - default: - throw new IllegalStateException(); - } - } - - @Override - public ByteBuf retainedSlice() { - return new Tuple3ByteBuf( - allocator, - one.retainedSlice(oneReadIndex, oneReadableBytes), - two.retainedSlice(twoReadIndex, twoReadableBytes), - three.retainedSlice(threeReadIndex, threeReadableBytes)); - } - - @Override - public ByteBuf slice(final int readIndex, int length) { - checkIndex(readIndex, length); - - if (readIndex == 0 && length == capacity) { - return new Tuple3ByteBuf( - allocator, - one.slice(oneReadIndex, oneReadableBytes), - two.slice(twoReadIndex, twoReadableBytes), - three.slice(threeReadIndex, threeReadableBytes)); - } - - long ri = calculateRelativeIndex(readIndex); - int index = (int) (ri & Integer.MAX_VALUE); - switch ((int) ((ri & MASK) >>> 32L)) { - case 0x1: - { - ByteBuf oneSlice; - ByteBuf twoSlice; - ByteBuf threeSlice; - - int l = Math.min(oneReadableBytes - index, length); - oneSlice = one.slice(index, l); - length -= l; - if (length != 0) { - l = Math.min(twoReadableBytes, length); - twoSlice = two.slice(twoReadIndex, l); - length -= l; - if (length != 0) { - threeSlice = three.slice(threeReadIndex, length); - return new Tuple3ByteBuf(allocator, oneSlice, twoSlice, threeSlice); - } else { - return new Tuple2ByteBuf(allocator, oneSlice, twoSlice); - } - - } else { - return oneSlice; - } - } - case 0x2: - { - ByteBuf twoSlice; - ByteBuf threeSlice; - - int l = Math.min(twoReadableBytes - index, length); - twoSlice = two.slice(index, l); - length -= l; - if (length != 0) { - threeSlice = three.slice(threeReadIndex, length); - return new Tuple2ByteBuf(allocator, twoSlice, threeSlice); - } else { - return twoSlice; - } - } - case 0x4: - { - return three.slice(index, length); - } - default: - throw new IllegalStateException(); - } - } - - @Override - protected void deallocate() { - if (freed) { - return; - } - - freed = true; - ReferenceCountUtil.safeRelease(one); - ReferenceCountUtil.safeRelease(two); - ReferenceCountUtil.safeRelease(three); - } - - @Override - public String toString(Charset charset) { - StringBuilder builder = new StringBuilder(3); - builder.append(one.toString(charset)); - builder.append(two.toString(charset)); - builder.append(three.toString(charset)); - return builder.toString(); - } - - @Override - public String toString() { - return "Tuple3ByteBuf{" - + "capacity=" - + capacity - + ", one=" - + one - + ", two=" - + two - + ", three=" - + three - + ", allocator=" - + allocator - + ", oneReadIndex=" - + oneReadIndex - + ", twoReadIndex=" - + twoReadIndex - + ", threeReadIndex=" - + threeReadIndex - + ", oneReadableBytes=" - + oneReadableBytes - + ", twoReadableBytes=" - + twoReadableBytes - + ", threeReadableBytes=" - + threeReadableBytes - + ", twoRelativeIndex=" - + twoRelativeIndex - + ", threeRelativeIndex=" - + threeRelativeIndex - + '}'; - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/buffer/TupleByteBuf.java b/rsocket-core/src/main/java/io/rsocket/buffer/TupleByteBuf.java deleted file mode 100644 index 8c8e2e7e4..000000000 --- a/rsocket-core/src/main/java/io/rsocket/buffer/TupleByteBuf.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.rsocket.buffer; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import java.util.Objects; - -public abstract class TupleByteBuf { - - private TupleByteBuf() {} - - public static ByteBuf of(ByteBuf one, ByteBuf two) { - return of(ByteBufAllocator.DEFAULT, one, two); - } - - public static ByteBuf of(ByteBufAllocator allocator, ByteBuf one, ByteBuf two) { - Objects.requireNonNull(allocator); - Objects.requireNonNull(one); - Objects.requireNonNull(two); - - return new Tuple2ByteBuf(allocator, one, two); - } - - public static ByteBuf of(ByteBuf one, ByteBuf two, ByteBuf three) { - return of(ByteBufAllocator.DEFAULT, one, two, three); - } - - public static ByteBuf of(ByteBufAllocator allocator, ByteBuf one, ByteBuf two, ByteBuf three) { - Objects.requireNonNull(allocator); - Objects.requireNonNull(one); - Objects.requireNonNull(two); - Objects.requireNonNull(three); - - return new Tuple3ByteBuf(allocator, one, two, three); - } -} diff --git a/rsocket-core/src/test/java/io/rsocket/buffer/Tuple3ByteBufTest.java b/rsocket-core/src/test/java/io/rsocket/buffer/Tuple3ByteBufTest.java deleted file mode 100644 index 4515fb29b..000000000 --- a/rsocket-core/src/test/java/io/rsocket/buffer/Tuple3ByteBufTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package io.rsocket.buffer; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.ByteBufUtil; -import io.netty.buffer.Unpooled; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.concurrent.ThreadLocalRandom; -import org.junit.Assert; -import org.junit.jupiter.api.Test; - -class Tuple3ByteBufTest { - @Test - void testTupleBufferGet() { - ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; - ByteBuf one = allocator.directBuffer(9); - - byte[] bytes = new byte[9]; - ThreadLocalRandom.current().nextBytes(bytes); - one.writeBytes(bytes); - - bytes = new byte[8]; - ThreadLocalRandom.current().nextBytes(bytes); - ByteBuf two = Unpooled.wrappedBuffer(bytes); - - bytes = new byte[9]; - ThreadLocalRandom.current().nextBytes(bytes); - ByteBuf three = Unpooled.wrappedBuffer(bytes); - - ByteBuf tuple = TupleByteBuf.of(one, two, three); - - int anInt = tuple.getInt(16); - - long aLong = tuple.getLong(15); - - short aShort = tuple.getShort(8); - - int medium = tuple.getMedium(8); - } - - @Test - void testTuple3BufferSlicing() { - ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; - ByteBuf one = allocator.directBuffer(); - ByteBufUtil.writeUtf8(one, "foo"); - - ByteBuf two = allocator.directBuffer(); - ByteBufUtil.writeUtf8(two, "bar"); - - ByteBuf three = allocator.directBuffer(); - ByteBufUtil.writeUtf8(three, "bar"); - - ByteBuf buf = TupleByteBuf.of(one, two, three); - - String s = buf.slice(0, 6).toString(Charset.defaultCharset()); - Assert.assertEquals("foobar", s); - - String s1 = buf.slice(3, 6).toString(Charset.defaultCharset()); - Assert.assertEquals("barbar", s1); - - String s2 = buf.slice(4, 4).toString(Charset.defaultCharset()); - Assert.assertEquals("arba", s2); - } - - @Test - void testTuple3ToNioBuffers() throws Exception { - ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; - ByteBuf one = allocator.directBuffer(); - ByteBufUtil.writeUtf8(one, "one"); - - ByteBuf two = allocator.directBuffer(); - ByteBufUtil.writeUtf8(two, "two"); - - ByteBuf three = allocator.directBuffer(); - ByteBufUtil.writeUtf8(three, "three"); - - ByteBuf buf = TupleByteBuf.of(one, two, three); - ByteBuffer[] byteBuffers = buf.nioBuffers(); - - Assert.assertEquals(3, byteBuffers.length); - - ByteBuffer bb = byteBuffers[0]; - byte[] dst = new byte[bb.remaining()]; - bb.get(dst); - Assert.assertEquals("one", new String(dst, "UTF-8")); - - bb = byteBuffers[1]; - dst = new byte[bb.remaining()]; - bb.get(dst); - Assert.assertEquals("two", new String(dst, "UTF-8")); - - bb = byteBuffers[2]; - dst = new byte[bb.remaining()]; - bb.get(dst); - Assert.assertEquals("three", new String(dst, "UTF-8")); - } -} From d3dc85f32ec12e553c382dda28e8c3ba114f95aa Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 30 Apr 2020 08:52:52 +0100 Subject: [PATCH 42/62] Deprecate AbstractRSocket and provide alternative (#805) --- .../main/java/io/rsocket/AbstractRSocket.java | 35 +-------- .../src/main/java/io/rsocket/RSocket.java | 37 +++++++-- .../main/java/io/rsocket/SocketAcceptor.java | 54 ++++++++++++- .../io/rsocket/core/RSocketConnector.java | 3 +- .../java/io/rsocket/core/RSocketServer.java | 3 +- .../io/rsocket/core/RSocketResponderTest.java | 27 ++++--- .../java/io/rsocket/core/RSocketTest.java | 13 ++-- .../tcp/channel/ChannelEchoClient.java | 31 +++----- .../transport/tcp/duplex/DuplexClient.java | 23 +++--- .../transport/tcp/lease/LeaseExample.java | 19 ++--- .../tcp/requestresponse/HelloWorldClient.java | 33 ++++---- .../tcp/resume/ResumeFileTransfer.java | 50 +++++-------- .../transport/tcp/stream/StreamingClient.java | 23 ++---- .../transport/ws/WebSocketHeadersSample.java | 43 +++++------ .../rsocket/integration/IntegrationTest.java | 5 +- .../integration/InteractionsLoadTest.java | 8 +- .../integration/TcpIntegrationTest.java | 13 ++-- .../rsocket/integration/TestingStreaming.java | 75 ++++++------------- .../rsocket/resume/ResumeIntegrationTest.java | 8 +- .../client/LoadBalancedRSocketMono.java | 18 ++++- .../java/io/rsocket/test/PingHandler.java | 3 +- .../java/io/rsocket/test/TestRSocket.java | 4 +- .../io/rsocket/integration/FragmentTest.java | 9 +-- .../WebSocketTransportIntegrationTest.java | 17 +---- .../WebsocketPingPongIntegrationTest.java | 12 +-- 25 files changed, 264 insertions(+), 302 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/AbstractRSocket.java b/rsocket-core/src/main/java/io/rsocket/AbstractRSocket.java index c099a3120..7f39956dc 100644 --- a/rsocket-core/src/main/java/io/rsocket/AbstractRSocket.java +++ b/rsocket-core/src/main/java/io/rsocket/AbstractRSocket.java @@ -16,48 +16,21 @@ package io.rsocket; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoProcessor; /** * An abstract implementation of {@link RSocket}. All request handling methods emit {@link * UnsupportedOperationException} and hence must be overridden to provide a valid implementation. + * + * @deprecated as of 1.0 in favor of implementing {@link RSocket} directly which has default + * methods. */ +@Deprecated public abstract class AbstractRSocket implements RSocket { private final MonoProcessor onClose = MonoProcessor.create(); - @Override - public Mono fireAndForget(Payload payload) { - payload.release(); - return Mono.error(new UnsupportedOperationException("Fire and forget not implemented.")); - } - - @Override - public Mono requestResponse(Payload payload) { - payload.release(); - return Mono.error(new UnsupportedOperationException("Request-Response not implemented.")); - } - - @Override - public Flux requestStream(Payload payload) { - payload.release(); - return Flux.error(new UnsupportedOperationException("Request-Stream not implemented.")); - } - - @Override - public Flux requestChannel(Publisher payloads) { - return Flux.error(new UnsupportedOperationException("Request-Channel not implemented.")); - } - - @Override - public Mono metadataPush(Payload payload) { - payload.release(); - return Mono.error(new UnsupportedOperationException("Metadata-Push not implemented.")); - } - @Override public void dispose() { onClose.onComplete(); diff --git a/rsocket-core/src/main/java/io/rsocket/RSocket.java b/rsocket-core/src/main/java/io/rsocket/RSocket.java index 5468b4de8..773c93dc2 100644 --- a/rsocket-core/src/main/java/io/rsocket/RSocket.java +++ b/rsocket-core/src/main/java/io/rsocket/RSocket.java @@ -33,7 +33,10 @@ public interface RSocket extends Availability, Closeable { * @return {@code Publisher} that completes when the passed {@code payload} is successfully * handled, otherwise errors. */ - Mono fireAndForget(Payload payload); + default Mono fireAndForget(Payload payload) { + payload.release(); + return Mono.error(new UnsupportedOperationException("Fire-and-Forget not implemented.")); + } /** * Request-Response interaction model of {@code RSocket}. @@ -42,7 +45,10 @@ public interface RSocket extends Availability, Closeable { * @return {@code Publisher} containing at most a single {@code Payload} representing the * response. */ - Mono requestResponse(Payload payload); + default Mono requestResponse(Payload payload) { + payload.release(); + return Mono.error(new UnsupportedOperationException("Request-Response not implemented.")); + } /** * Request-Stream interaction model of {@code RSocket}. @@ -50,7 +56,10 @@ public interface RSocket extends Availability, Closeable { * @param payload Request payload. * @return {@code Publisher} containing the stream of {@code Payload}s representing the response. */ - Flux requestStream(Payload payload); + default Flux requestStream(Payload payload) { + payload.release(); + return Flux.error(new UnsupportedOperationException("Request-Stream not implemented.")); + } /** * Request-Channel interaction model of {@code RSocket}. @@ -58,7 +67,9 @@ public interface RSocket extends Availability, Closeable { * @param payloads Stream of request payloads. * @return Stream of response payloads. */ - Flux requestChannel(Publisher payloads); + default Flux requestChannel(Publisher payloads) { + return Flux.error(new UnsupportedOperationException("Request-Channel not implemented.")); + } /** * Metadata-Push interaction model of {@code RSocket}. @@ -67,10 +78,26 @@ public interface RSocket extends Availability, Closeable { * @return {@code Publisher} that completes when the passed {@code payload} is successfully * handled, otherwise errors. */ - Mono metadataPush(Payload payload); + default Mono metadataPush(Payload payload) { + payload.release(); + return Mono.error(new UnsupportedOperationException("Metadata-Push not implemented.")); + } @Override default double availability() { return isDisposed() ? 0.0 : 1.0; } + + @Override + default void dispose() {} + + @Override + default boolean isDisposed() { + return false; + } + + @Override + default Mono onClose() { + return Mono.never(); + } } diff --git a/rsocket-core/src/main/java/io/rsocket/SocketAcceptor.java b/rsocket-core/src/main/java/io/rsocket/SocketAcceptor.java index 85c731eea..a42626e78 100644 --- a/rsocket-core/src/main/java/io/rsocket/SocketAcceptor.java +++ b/rsocket-core/src/main/java/io/rsocket/SocketAcceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,9 @@ package io.rsocket; import io.rsocket.exceptions.SetupException; +import java.util.function.Function; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** @@ -38,4 +41,53 @@ public interface SocketAcceptor { * @throws SetupException If the acceptor needs to reject the setup of this socket. */ Mono accept(ConnectionSetupPayload setup, RSocket sendingSocket); + + /** Create a {@code SocketAcceptor} that handles requests with the given {@code RSocket}. */ + static SocketAcceptor with(RSocket rsocket) { + return (setup, sendingRSocket) -> Mono.just(rsocket); + } + + /** Create a {@code SocketAcceptor} for fire-and-forget interactions with the given handler. */ + static SocketAcceptor forFireAndForget(Function> handler) { + return with( + new RSocket() { + @Override + public Mono fireAndForget(Payload payload) { + return handler.apply(payload); + } + }); + } + + /** Create a {@code SocketAcceptor} for request-response interactions with the given handler. */ + static SocketAcceptor forRequestResponse(Function> handler) { + return with( + new RSocket() { + @Override + public Mono requestResponse(Payload payload) { + return handler.apply(payload); + } + }); + } + + /** Create a {@code SocketAcceptor} for request-stream interactions with the given handler. */ + static SocketAcceptor forRequestStream(Function> handler) { + return with( + new RSocket() { + @Override + public Flux requestStream(Payload payload) { + return handler.apply(payload); + } + }); + } + + /** Create a {@code SocketAcceptor} for request-channel interactions with the given handler. */ + static SocketAcceptor forRequestChannel(Function, Flux> handler) { + return with( + new RSocket() { + @Override + public Flux requestChannel(Publisher payloads) { + return handler.apply(payloads); + } + }); + } } diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java index 57aebbdf0..a7eed8c76 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java @@ -17,7 +17,6 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import io.rsocket.AbstractRSocket; import io.rsocket.ConnectionSetupPayload; import io.rsocket.DuplexConnection; import io.rsocket.Payload; @@ -56,7 +55,7 @@ public class RSocketConnector { private String metadataMimeType = "application/binary"; private String dataMimeType = "application/binary"; - private SocketAcceptor acceptor = (setup, sendingSocket) -> Mono.just(new AbstractRSocket() {}); + private SocketAcceptor acceptor = SocketAcceptor.with(new RSocket() {}); private InitializingInterceptorRegistry interceptors = new InitializingInterceptorRegistry(); private Duration keepAliveInterval = Duration.ofSeconds(20); diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java index c82a2f40a..19f0c5008 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java @@ -17,7 +17,6 @@ package io.rsocket.core; import io.netty.buffer.ByteBuf; -import io.rsocket.AbstractRSocket; import io.rsocket.Closeable; import io.rsocket.ConnectionSetupPayload; import io.rsocket.DuplexConnection; @@ -45,7 +44,7 @@ public final class RSocketServer { private static final String SERVER_TAG = "server"; private static final int MIN_MTU_SIZE = 64; - private SocketAcceptor acceptor = (setup, sendingSocket) -> Mono.just(new AbstractRSocket() {}); + private SocketAcceptor acceptor = SocketAcceptor.with(new RSocket() {}); private InitializingInterceptorRegistry interceptors = new InitializingInterceptorRegistry(); private int mtu = 0; diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java index c19456548..2dbf6715b 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java @@ -35,7 +35,6 @@ import io.netty.util.CharsetUtil; import io.netty.util.ReferenceCountUtil; import io.netty.util.ReferenceCounted; -import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; import io.rsocket.frame.CancelFrameFlyweight; @@ -164,7 +163,7 @@ public void testCancel() { final int streamId = 4; final AtomicBoolean cancelled = new AtomicBoolean(); rule.setAcceptingSocket( - new AbstractRSocket() { + new RSocket() { @Override public Mono requestResponse(Payload payload) { payload.release(); @@ -193,8 +192,8 @@ public void shouldThrownExceptionIfGivenPayloadIsExitsSizeAllowanceWithNoFragmen ThreadLocalRandom.current().nextBytes(metadata); ThreadLocalRandom.current().nextBytes(data); final Payload payload = DefaultPayload.create(data, metadata); - final AbstractRSocket acceptingSocket = - new AbstractRSocket() { + final RSocket acceptingSocket = + new RSocket() { @Override public Mono requestResponse(Payload p) { p.release(); @@ -256,7 +255,7 @@ public void checkNoLeaksOnRacingCancelFromRequestChannelAndNextFromUpstream() { AssertSubscriber assertSubscriber = AssertSubscriber.create(); rule.setAcceptingSocket( - new AbstractRSocket() { + new RSocket() { @Override public Flux requestChannel(Publisher payloads) { payloads.subscribe(assertSubscriber); @@ -312,7 +311,7 @@ public void checkNoLeaksOnRacingBetweenDownstreamCancelAndOnNextFromRequestChann FluxSink[] sinks = new FluxSink[1]; rule.setAcceptingSocket( - new AbstractRSocket() { + new RSocket() { @Override public Flux requestChannel(Publisher payloads) { ((Flux) payloads) @@ -352,7 +351,7 @@ public void checkNoLeaksOnRacingBetweenDownstreamCancelAndOnNextFromRequestChann FluxSink[] sinks = new FluxSink[1]; rule.setAcceptingSocket( - new AbstractRSocket() { + new RSocket() { @Override public Flux requestChannel(Publisher payloads) { ((Flux) payloads) @@ -397,7 +396,7 @@ public Flux requestChannel(Publisher payloads) { FluxSink[] sinks = new FluxSink[1]; AssertSubscriber assertSubscriber = AssertSubscriber.create(); rule.setAcceptingSocket( - new AbstractRSocket() { + new RSocket() { @Override public Flux requestChannel(Publisher payloads) { payloads.subscribe(assertSubscriber); @@ -466,7 +465,7 @@ public void checkNoLeaksOnRacingBetweenDownstreamCancelAndOnNextFromRequestStrea FluxSink[] sinks = new FluxSink[1]; rule.setAcceptingSocket( - new AbstractRSocket() { + new RSocket() { @Override public Flux requestStream(Payload payload) { payload.release(); @@ -503,7 +502,7 @@ public void checkNoLeaksOnRacingBetweenDownstreamCancelAndOnNextFromRequestRespo Operators.MonoSubscriber[] sources = new Operators.MonoSubscriber[1]; rule.setAcceptingSocket( - new AbstractRSocket() { + new RSocket() { @Override public Mono requestResponse(Payload payload) { payload.release(); @@ -540,7 +539,7 @@ public void simpleDiscardRequestStreamTest() { FluxSink[] sinks = new FluxSink[1]; rule.setAcceptingSocket( - new AbstractRSocket() { + new RSocket() { @Override public Flux requestStream(Payload payload) { payload.release(); @@ -569,7 +568,7 @@ public void simpleDiscardRequestChannelTest() { ByteBufAllocator allocator = rule.alloc(); rule.setAcceptingSocket( - new AbstractRSocket() { + new RSocket() { @Override public Flux requestChannel(Publisher payloads) { return (Flux) payloads; @@ -619,7 +618,7 @@ public void verifiesThatFrameWithNoMetadataHasDecodedCorrectlyIntoPayload( TestPublisher testPublisher = TestPublisher.create(); rule.setAcceptingSocket( - new AbstractRSocket() { + new RSocket() { @Override public Mono fireAndForget(Payload payload) { Mono.just(payload).subscribe(assertSubscriber); @@ -720,7 +719,7 @@ public static class ServerSocketRule extends AbstractSocketRule requestResponse(Payload payload) { return Mono.just(payload); diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java index 4a2c43ef8..02c3dfca8 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java @@ -23,7 +23,6 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; -import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; import io.rsocket.buffer.LeaksTrackingByteBufAllocator; @@ -83,7 +82,7 @@ public void testRequestReplyNoError() { @Test(timeout = 2000) public void testHandlerEmitsError() { rule.setRequestAcceptor( - new AbstractRSocket() { + new RSocket() { @Override public Mono requestResponse(Payload payload) { return Mono.error(new NullPointerException("Deliberate exception.")); @@ -102,7 +101,7 @@ public Mono requestResponse(Payload payload) { @Test(timeout = 2000) public void testHandlerEmitsCustomError() { rule.setRequestAcceptor( - new AbstractRSocket() { + new RSocket() { @Override public Mono requestResponse(Payload payload) { return Mono.error( @@ -129,7 +128,7 @@ public Mono requestResponse(Payload payload) { @Test(timeout = 2000) public void testRequestPropagatesCorrectlyForRequestChannel() { rule.setRequestAcceptor( - new AbstractRSocket() { + new RSocket() { @Override public Flux requestChannel(Publisher payloads) { return Flux.from(payloads) @@ -170,7 +169,7 @@ public void testChannel() throws Exception { public void testErrorPropagatesCorrectly() { AtomicReference error = new AtomicReference<>(); rule.setRequestAcceptor( - new AbstractRSocket() { + new RSocket() { @Override public Flux requestChannel(Publisher payloads) { return Flux.from(payloads).doOnError(error::set); @@ -291,7 +290,7 @@ void initRequestChannelCase( TestPublisher responderPublisher, AssertSubscriber responderSubscriber) { rule.setRequestAcceptor( - new AbstractRSocket() { + new RSocket() { @Override public Flux requestChannel(Publisher payloads) { payloads.subscribe(responderSubscriber); @@ -446,7 +445,7 @@ protected void init() { requestAcceptor = null != requestAcceptor ? requestAcceptor - : new AbstractRSocket() { + : new RSocket() { @Override public Mono requestResponse(Payload payload) { return Mono.just(payload); diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/channel/ChannelEchoClient.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/channel/ChannelEchoClient.java index 71e48790f..b532c0140 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/channel/ChannelEchoClient.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/channel/ChannelEchoClient.java @@ -16,8 +16,6 @@ package io.rsocket.examples.transport.tcp.channel; -import io.rsocket.AbstractRSocket; -import io.rsocket.ConnectionSetupPayload; import io.rsocket.Payload; import io.rsocket.RSocket; import io.rsocket.SocketAcceptor; @@ -27,18 +25,25 @@ import io.rsocket.transport.netty.server.TcpServerTransport; import io.rsocket.util.DefaultPayload; import java.time.Duration; -import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; public final class ChannelEchoClient { private static final Logger logger = LoggerFactory.getLogger(ChannelEchoClient.class); public static void main(String[] args) { - RSocketServer.create(new EchoAcceptor()) + + SocketAcceptor echoAcceptor = + SocketAcceptor.forRequestChannel( + payloads -> + Flux.from(payloads) + .map(Payload::getDataUtf8) + .map(s -> "Echo: " + s) + .map(DefaultPayload::create)); + + RSocketServer.create(echoAcceptor) .bind(TcpServerTransport.create("localhost", 7000)) .subscribe(); @@ -55,20 +60,4 @@ public static void main(String[] args) { .then() .block(); } - - private static class EchoAcceptor implements SocketAcceptor { - @Override - public Mono accept(ConnectionSetupPayload setupPayload, RSocket reactiveSocket) { - return Mono.just( - new AbstractRSocket() { - @Override - public Flux requestChannel(Publisher payloads) { - return Flux.from(payloads) - .map(Payload::getDataUtf8) - .map(s -> "Echo: " + s) - .map(DefaultPayload::create); - } - }); - } - } } diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/duplex/DuplexClient.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/duplex/DuplexClient.java index bfa58bf40..3eba5a800 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/duplex/DuplexClient.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/duplex/DuplexClient.java @@ -16,7 +16,8 @@ package io.rsocket.examples.transport.tcp.duplex; -import io.rsocket.AbstractRSocket; +import static io.rsocket.SocketAcceptor.forRequestStream; + import io.rsocket.Payload; import io.rsocket.RSocket; import io.rsocket.core.RSocketConnector; @@ -31,6 +32,7 @@ public final class DuplexClient { public static void main(String[] args) { + RSocketServer.create( (setup, rsocket) -> { rsocket @@ -39,26 +41,21 @@ public static void main(String[] args) { .log() .subscribe(); - return Mono.just(new AbstractRSocket() {}); + return Mono.just(new RSocket() {}); }) .bind(TcpServerTransport.create("localhost", 7000)) .subscribe(); - RSocket socket = + RSocket rsocket = RSocketConnector.create() .acceptor( - (setup, rsocket) -> - Mono.just( - new AbstractRSocket() { - @Override - public Flux requestStream(Payload payload) { - return Flux.interval(Duration.ofSeconds(1)) - .map(aLong -> DefaultPayload.create("Bi-di Response => " + aLong)); - } - })) + forRequestStream( + payload -> + Flux.interval(Duration.ofSeconds(1)) + .map(aLong -> DefaultPayload.create("Bi-di Response => " + aLong)))) .connect(TcpClientTransport.create("localhost", 7000)) .block(); - socket.onClose().block(); + rsocket.onClose().block(); } } diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/lease/LeaseExample.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/lease/LeaseExample.java index a12c9a170..3eaebd89a 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/lease/LeaseExample.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/lease/LeaseExample.java @@ -18,9 +18,9 @@ import static java.time.Duration.ofSeconds; -import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; +import io.rsocket.SocketAcceptor; import io.rsocket.core.RSocketConnector; import io.rsocket.core.RSocketServer; import io.rsocket.lease.Lease; @@ -45,7 +45,7 @@ public static void main(String[] args) { CloseableChannel server = RSocketServer.create( - (setup, sendingRSocket) -> Mono.just(new ServerAcceptor(sendingRSocket))) + (setup, sendingRSocket) -> Mono.just(new ServerRSocket(sendingRSocket))) .lease( () -> Leases.create() @@ -62,7 +62,9 @@ public static void main(String[] args) { Leases.create() .sender(new LeaseSender(CLIENT_TAG, 3_000, 5)) .receiver(new LeaseReceiver(CLIENT_TAG))) - .acceptor((rSocket, setup) -> Mono.just(new ClientAcceptor())) + .acceptor( + SocketAcceptor.forRequestResponse( + payload -> Mono.just(DefaultPayload.create("Client Response " + new Date())))) .connect(TcpClientTransport.create(server.address())) .block(); @@ -133,17 +135,10 @@ private static class NoopStats implements LeaseStats { public void onEvent(EventType eventType) {} } - private static class ClientAcceptor extends AbstractRSocket { - @Override - public Mono requestResponse(Payload payload) { - return Mono.just(DefaultPayload.create("Client Response " + new Date())); - } - } - - private static class ServerAcceptor extends AbstractRSocket { + private static class ServerRSocket implements RSocket { private final RSocket senderRSocket; - public ServerAcceptor(RSocket senderRSocket) { + public ServerRSocket(RSocket senderRSocket) { this.senderRSocket = senderRSocket; } diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/requestresponse/HelloWorldClient.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/requestresponse/HelloWorldClient.java index 1b9994c2f..85faeee82 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/requestresponse/HelloWorldClient.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/requestresponse/HelloWorldClient.java @@ -16,9 +16,9 @@ package io.rsocket.examples.transport.tcp.requestresponse; -import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; +import io.rsocket.SocketAcceptor; import io.rsocket.core.RSocketConnector; import io.rsocket.core.RSocketServer; import io.rsocket.transport.netty.client.TcpClientTransport; @@ -33,22 +33,23 @@ public final class HelloWorldClient { private static final Logger logger = LoggerFactory.getLogger(HelloWorldClient.class); public static void main(String[] args) { - RSocketServer.create( - (setupPayload, reactiveSocket) -> - Mono.just( - new AbstractRSocket() { - boolean fail = true; - @Override - public Mono requestResponse(Payload p) { - if (fail) { - fail = false; - return Mono.error(new Throwable("Simulated error")); - } else { - return Mono.just(p); - } - } - })) + RSocket rsocket = + new RSocket() { + boolean fail = true; + + @Override + public Mono requestResponse(Payload p) { + if (fail) { + fail = false; + return Mono.error(new Throwable("Simulated error")); + } else { + return Mono.just(p); + } + } + }; + + RSocketServer.create(SocketAcceptor.with(rsocket)) .bind(TcpServerTransport.create("localhost", 7000)) .subscribe(); diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/ResumeFileTransfer.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/ResumeFileTransfer.java index d449dd205..93b54e146 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/ResumeFileTransfer.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/resume/ResumeFileTransfer.java @@ -16,9 +16,9 @@ package io.rsocket.examples.transport.tcp.resume; -import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; +import io.rsocket.SocketAcceptor; import io.rsocket.core.RSocketConnector; import io.rsocket.core.RSocketServer; import io.rsocket.core.Resume; @@ -30,7 +30,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; import reactor.util.retry.Retry; public class ResumeFileTransfer { @@ -40,18 +39,31 @@ public class ResumeFileTransfer { private static final Logger logger = LoggerFactory.getLogger(ResumeFileTransfer.class); public static void main(String[] args) { - RequestCodec requestCodec = new RequestCodec(); + Resume resume = new Resume() .sessionDuration(Duration.ofMinutes(5)) .retry( Retry.fixedDelay(Long.MAX_VALUE, Duration.ofSeconds(1)) - .doBeforeRetry( - retrySignal -> - logger.debug("Disconnected. Trying to resume connection..."))); + .doBeforeRetry(s -> logger.debug("Disconnected. Trying to resume..."))); + + RequestCodec codec = new RequestCodec(); CloseableChannel server = - RSocketServer.create((setup, rSocket) -> Mono.just(new FileServer(requestCodec))) + RSocketServer.create( + SocketAcceptor.forRequestStream( + payload -> { + Request request = codec.decode(payload); + payload.release(); + String fileName = request.getFileName(); + int chunkSize = request.getChunkSize(); + + Flux ticks = Flux.interval(Duration.ofMillis(500)).onBackpressureDrop(); + + return Files.fileSource(fileName, chunkSize) + .map(DefaultPayload::create) + .zipWith(ticks, (p, tick) -> p); + })) .resume(resume) .bind(TcpServerTransport.create("localhost", 8000)) .block(); @@ -63,35 +75,13 @@ public static void main(String[] args) { .block(); client - .requestStream(requestCodec.encode(new Request(16, "lorem.txt"))) + .requestStream(codec.encode(new Request(16, "lorem.txt"))) .doFinally(s -> server.dispose()) .subscribe(Files.fileSink("rsocket-examples/out/lorem_output.txt", PREFETCH_WINDOW_SIZE)); server.onClose().block(); } - private static class FileServer extends AbstractRSocket { - private final RequestCodec requestCodec; - - public FileServer(RequestCodec requestCodec) { - this.requestCodec = requestCodec; - } - - @Override - public Flux requestStream(Payload payload) { - Request request = requestCodec.decode(payload); - payload.release(); - String fileName = request.getFileName(); - int chunkSize = request.getChunkSize(); - - Flux ticks = Flux.interval(Duration.ofMillis(500)).onBackpressureDrop(); - - return Files.fileSource(fileName, chunkSize) - .map(DefaultPayload::create) - .zipWith(ticks, (p, tick) -> p); - } - } - private static class RequestCodec { public Payload encode(Request request) { diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/stream/StreamingClient.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/stream/StreamingClient.java index 1ef2b7a90..6ac329d56 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/stream/StreamingClient.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/stream/StreamingClient.java @@ -16,8 +16,6 @@ package io.rsocket.examples.transport.tcp.stream; -import io.rsocket.AbstractRSocket; -import io.rsocket.ConnectionSetupPayload; import io.rsocket.Payload; import io.rsocket.RSocket; import io.rsocket.SocketAcceptor; @@ -30,14 +28,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; public final class StreamingClient { private static final Logger logger = LoggerFactory.getLogger(StreamingClient.class); public static void main(String[] args) { - RSocketServer.create(new SocketAcceptorImpl()) + RSocketServer.create( + SocketAcceptor.forRequestStream( + payload -> + Flux.interval(Duration.ofMillis(100)) + .map(aLong -> DefaultPayload.create("Interval: " + aLong)))) .bind(TcpServerTransport.create("localhost", 7000)) .subscribe(); @@ -54,18 +55,4 @@ public static void main(String[] args) { .then() .block(); } - - private static class SocketAcceptorImpl implements SocketAcceptor { - @Override - public Mono accept(ConnectionSetupPayload setupPayload, RSocket reactiveSocket) { - return Mono.just( - new AbstractRSocket() { - @Override - public Flux requestStream(Payload payload) { - return Flux.interval(Duration.ofMillis(100)) - .map(aLong -> DefaultPayload.create("Interval: " + aLong)); - } - }); - } - } } diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/ws/WebSocketHeadersSample.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/ws/WebSocketHeadersSample.java index 24f029845..2ab73116d 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/ws/WebSocketHeadersSample.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/ws/WebSocketHeadersSample.java @@ -17,8 +17,6 @@ package io.rsocket.examples.transport.ws; import io.netty.handler.codec.http.HttpResponseStatus; -import io.rsocket.AbstractRSocket; -import io.rsocket.ConnectionSetupPayload; import io.rsocket.DuplexConnection; import io.rsocket.Payload; import io.rsocket.RSocket; @@ -47,9 +45,8 @@ public class WebSocketHeadersSample { public static void main(String[] args) { ServerTransport.ConnectionAcceptor acceptor = - RSocketServer.create() + RSocketServer.create(SocketAcceptor.with(new ServerRSocket())) .payloadDecoder(PayloadDecoder.ZERO_COPY) - .acceptor(new SocketAcceptorImpl()) .asConnectionAcceptor(); DisposableServer disposableServer = @@ -114,29 +111,23 @@ public static void main(String[] args) { rSocket.requestResponse(payload1).block(); } - private static class SocketAcceptorImpl implements SocketAcceptor { + private static class ServerRSocket implements RSocket { + + @Override + public Mono fireAndForget(Payload payload) { + // System.out.println(payload.getDataUtf8()); + payload.release(); + return Mono.empty(); + } + + @Override + public Mono requestResponse(Payload payload) { + return Mono.just(payload); + } + @Override - public Mono accept(ConnectionSetupPayload setupPayload, RSocket reactiveSocket) { - return Mono.just( - new AbstractRSocket() { - - @Override - public Mono fireAndForget(Payload payload) { - // System.out.println(payload.getDataUtf8()); - payload.release(); - return Mono.empty(); - } - - @Override - public Mono requestResponse(Payload payload) { - return Mono.just(payload); - } - - @Override - public Flux requestChannel(Publisher payloads) { - return Flux.from(payloads).subscribeOn(Schedulers.single()); - } - }); + public Flux requestChannel(Publisher payloads) { + return Flux.from(payloads).subscribeOn(Schedulers.single()); } } } diff --git a/rsocket-examples/src/test/java/io/rsocket/integration/IntegrationTest.java b/rsocket-examples/src/test/java/io/rsocket/integration/IntegrationTest.java index 1ef7771cd..e2471f2fc 100644 --- a/rsocket-examples/src/test/java/io/rsocket/integration/IntegrationTest.java +++ b/rsocket-examples/src/test/java/io/rsocket/integration/IntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; import io.rsocket.core.RSocketConnector; @@ -124,7 +123,7 @@ public void startup() { .subscribe(); return Mono.just( - new AbstractRSocket() { + new RSocket() { @Override public Mono requestResponse(Payload payload) { return Mono.just(DefaultPayload.create("RESPONSE", "METADATA")) diff --git a/rsocket-examples/src/test/java/io/rsocket/integration/InteractionsLoadTest.java b/rsocket-examples/src/test/java/io/rsocket/integration/InteractionsLoadTest.java index d24083ea6..48e5baaa7 100644 --- a/rsocket-examples/src/test/java/io/rsocket/integration/InteractionsLoadTest.java +++ b/rsocket-examples/src/test/java/io/rsocket/integration/InteractionsLoadTest.java @@ -1,8 +1,8 @@ package io.rsocket.integration; -import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; +import io.rsocket.SocketAcceptor; import io.rsocket.core.RSocketConnector; import io.rsocket.core.RSocketServer; import io.rsocket.test.SlowTest; @@ -15,7 +15,6 @@ import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; public class InteractionsLoadTest { @@ -23,7 +22,7 @@ public class InteractionsLoadTest { @SlowTest public void channel() { CloseableChannel server = - RSocketServer.create((setup, rsocket) -> Mono.just(new EchoRSocket())) + RSocketServer.create(SocketAcceptor.with(new EchoRSocket())) .bind(TcpServerTransport.create("localhost", 0)) .block(Duration.ofSeconds(10)); @@ -66,7 +65,8 @@ private static Flux input() { return interval; } - private static class EchoRSocket extends AbstractRSocket { + private static class EchoRSocket implements RSocket { + @Override public Flux requestChannel(Publisher payloads) { return Flux.from(payloads) diff --git a/rsocket-examples/src/test/java/io/rsocket/integration/TcpIntegrationTest.java b/rsocket-examples/src/test/java/io/rsocket/integration/TcpIntegrationTest.java index 7133820ca..de27bcb9b 100644 --- a/rsocket-examples/src/test/java/io/rsocket/integration/TcpIntegrationTest.java +++ b/rsocket-examples/src/test/java/io/rsocket/integration/TcpIntegrationTest.java @@ -19,7 +19,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; import io.rsocket.core.RSocketConnector; @@ -41,7 +40,7 @@ import reactor.core.scheduler.Schedulers; public class TcpIntegrationTest { - private AbstractRSocket handler; + private RSocket handler; private CloseableChannel server; @@ -65,7 +64,7 @@ public void cleanup() { @Test(timeout = 15_000L) public void testCompleteWithoutNext() { handler = - new AbstractRSocket() { + new RSocket() { @Override public Flux requestStream(Payload payload) { return Flux.empty(); @@ -81,7 +80,7 @@ public Flux requestStream(Payload payload) { @Test(timeout = 15_000L) public void testSingleStream() { handler = - new AbstractRSocket() { + new RSocket() { @Override public Flux requestStream(Payload payload) { return Flux.just(DefaultPayload.create("RESPONSE", "METADATA")); @@ -98,7 +97,7 @@ public Flux requestStream(Payload payload) { @Test(timeout = 15_000L) public void testZeroPayload() { handler = - new AbstractRSocket() { + new RSocket() { @Override public Flux requestStream(Payload payload) { return Flux.just(EmptyPayload.INSTANCE); @@ -115,7 +114,7 @@ public Flux requestStream(Payload payload) { @Test(timeout = 15_000L) public void testRequestResponseErrors() { handler = - new AbstractRSocket() { + new RSocket() { boolean first = true; @Override @@ -155,7 +154,7 @@ public void testTwoConcurrentStreams() throws InterruptedException { map.put("REQUEST2", processor2); handler = - new AbstractRSocket() { + new RSocket() { @Override public Flux requestStream(Payload payload) { return map.get(payload.getDataUtf8()); diff --git a/rsocket-examples/src/test/java/io/rsocket/integration/TestingStreaming.java b/rsocket-examples/src/test/java/io/rsocket/integration/TestingStreaming.java index 8fe09430a..7d34ba478 100644 --- a/rsocket-examples/src/test/java/io/rsocket/integration/TestingStreaming.java +++ b/rsocket-examples/src/test/java/io/rsocket/integration/TestingStreaming.java @@ -16,9 +16,9 @@ package io.rsocket.integration; -import io.rsocket.AbstractRSocket; import io.rsocket.Closeable; import io.rsocket.Payload; +import io.rsocket.SocketAcceptor; import io.rsocket.core.RSocketConnector; import io.rsocket.core.RSocketServer; import io.rsocket.exceptions.ApplicationErrorException; @@ -29,7 +29,6 @@ import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; public class TestingStreaming { LocalServerTransport serverTransport = LocalServerTransport.create("test"); @@ -40,27 +39,17 @@ public void testRangeButThrowException() { try { server = RSocketServer.create( - (connectionSetupPayload, rSocket) -> - Mono.just( - new AbstractRSocket() { - @Override - public double availability() { - return 1.0; - } - - @Override - public Flux requestStream(Payload payload) { - return Flux.range(1, 1000) - .doOnNext( - i -> { - if (i > 3) { - throw new RuntimeException("BOOM!"); - } - }) - .map(l -> DefaultPayload.create("l -> " + l)) - .cast(Payload.class); - } - })) + SocketAcceptor.forRequestStream( + payload -> + Flux.range(1, 1000) + .doOnNext( + i -> { + if (i > 3) { + throw new RuntimeException("BOOM!"); + } + }) + .map(l -> DefaultPayload.create("l -> " + l)) + .cast(Payload.class))) .bind(serverTransport) .block(); @@ -78,21 +67,11 @@ public void testRangeOfConsumers() { try { server = RSocketServer.create( - (connectionSetupPayload, rSocket) -> - Mono.just( - new AbstractRSocket() { - @Override - public double availability() { - return 1.0; - } - - @Override - public Flux requestStream(Payload payload) { - return Flux.range(1, 1000) - .map(l -> DefaultPayload.create("l -> " + l)) - .cast(Payload.class); - } - })) + SocketAcceptor.forRequestStream( + payload -> + Flux.range(1, 1000) + .map(l -> DefaultPayload.create("l -> " + l)) + .cast(Payload.class))) .bind(serverTransport) .block(); @@ -121,21 +100,11 @@ public void testSingleConsumer() { try { server = RSocketServer.create( - (connectionSetupPayload, rSocket) -> - Mono.just( - new AbstractRSocket() { - @Override - public double availability() { - return 1.0; - } - - @Override - public Flux requestStream(Payload payload) { - return Flux.range(1, 10_000) - .map(l -> DefaultPayload.create("l -> " + l)) - .cast(Payload.class); - } - })) + SocketAcceptor.forRequestStream( + payload -> + Flux.range(1, 10_000) + .map(l -> DefaultPayload.create("l -> " + l)) + .cast(Payload.class))) .bind(serverTransport) .block(); diff --git a/rsocket-examples/src/test/java/io/rsocket/resume/ResumeIntegrationTest.java b/rsocket-examples/src/test/java/io/rsocket/resume/ResumeIntegrationTest.java index bd2db39c7..b2dad0022 100644 --- a/rsocket-examples/src/test/java/io/rsocket/resume/ResumeIntegrationTest.java +++ b/rsocket-examples/src/test/java/io/rsocket/resume/ResumeIntegrationTest.java @@ -16,9 +16,9 @@ package io.rsocket.resume; -import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; +import io.rsocket.SocketAcceptor; import io.rsocket.core.RSocketConnector; import io.rsocket.core.RSocketServer; import io.rsocket.core.Resume; @@ -127,7 +127,7 @@ public void reconnectOnMissingSession() { @Test void serverMissingResume() { CloseableChannel closeableChannel = - RSocketServer.create((setupPayload, rSocket) -> Mono.just(new TestResponderRSocket())) + RSocketServer.create(SocketAcceptor.with(new TestResponderRSocket())) .bind(serverTransport(SERVER_HOST, SERVER_PORT)) .block(); @@ -194,7 +194,7 @@ private static Mono newServerRSocket() { } private static Mono newServerRSocket(int sessionDurationSeconds) { - return RSocketServer.create((setup, rsocket) -> Mono.just(new TestResponderRSocket())) + return RSocketServer.create(SocketAcceptor.with(new TestResponderRSocket())) .resume( new Resume() .sessionDuration(Duration.ofSeconds(sessionDurationSeconds)) @@ -203,7 +203,7 @@ private static Mono newServerRSocket(int sessionDurationSecond .bind(serverTransport(SERVER_HOST, SERVER_PORT)); } - private static class TestResponderRSocket extends AbstractRSocket { + private static class TestResponderRSocket implements RSocket { AtomicInteger counter = new AtomicInteger(); diff --git a/rsocket-load-balancer/src/main/java/io/rsocket/client/LoadBalancedRSocketMono.java b/rsocket-load-balancer/src/main/java/io/rsocket/client/LoadBalancedRSocketMono.java index ed7550233..65ce80934 100644 --- a/rsocket-load-balancer/src/main/java/io/rsocket/client/LoadBalancedRSocketMono.java +++ b/rsocket-load-balancer/src/main/java/io/rsocket/client/LoadBalancedRSocketMono.java @@ -536,7 +536,7 @@ public Mono onClose() { * Wrapper of a RSocket, it computes statistics about the req/resp calls and update availability * accordingly. */ - private class WeightedSocket extends AbstractRSocket implements LoadBalancerSocketMetrics { + private class WeightedSocket implements LoadBalancerSocketMetrics, RSocket { private static final double STARTUP_PENALTY = Long.MAX_VALUE >> 12; private final Quantile lowerQuantile; @@ -554,6 +554,7 @@ private class WeightedSocket extends AbstractRSocket implements LoadBalancerSock private AtomicLong pendingStreams; // number of active streams private volatile double availability = 0.0; + private final MonoProcessor onClose = MonoProcessor.create(); WeightedSocket( RSocketSupplier factory, @@ -791,6 +792,21 @@ public double availability() { return availability; } + @Override + public void dispose() { + onClose.onComplete(); + } + + @Override + public boolean isDisposed() { + return onClose.isDisposed(); + } + + @Override + public Mono onClose() { + return onClose; + } + @Override public String toString() { return "WeightedSocket(" diff --git a/rsocket-test/src/main/java/io/rsocket/test/PingHandler.java b/rsocket-test/src/main/java/io/rsocket/test/PingHandler.java index 902014e7f..47f40a59d 100644 --- a/rsocket-test/src/main/java/io/rsocket/test/PingHandler.java +++ b/rsocket-test/src/main/java/io/rsocket/test/PingHandler.java @@ -16,7 +16,6 @@ package io.rsocket.test; -import io.rsocket.AbstractRSocket; import io.rsocket.ConnectionSetupPayload; import io.rsocket.Payload; import io.rsocket.RSocket; @@ -43,7 +42,7 @@ public PingHandler(byte[] data) { @Override public Mono accept(ConnectionSetupPayload setup, RSocket sendingSocket) { return Mono.just( - new AbstractRSocket() { + new RSocket() { @Override public Mono requestResponse(Payload payload) { payload.release(); diff --git a/rsocket-test/src/main/java/io/rsocket/test/TestRSocket.java b/rsocket-test/src/main/java/io/rsocket/test/TestRSocket.java index 26163d3a6..d48700445 100644 --- a/rsocket-test/src/main/java/io/rsocket/test/TestRSocket.java +++ b/rsocket-test/src/main/java/io/rsocket/test/TestRSocket.java @@ -16,14 +16,14 @@ package io.rsocket.test; -import io.rsocket.AbstractRSocket; import io.rsocket.Payload; +import io.rsocket.RSocket; import io.rsocket.util.DefaultPayload; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -public class TestRSocket extends AbstractRSocket { +public class TestRSocket implements RSocket { private final String data; private final String metadata; diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/integration/FragmentTest.java b/rsocket-transport-netty/src/test/java/io/rsocket/integration/FragmentTest.java index 0ea938af2..23041ec65 100644 --- a/rsocket-transport-netty/src/test/java/io/rsocket/integration/FragmentTest.java +++ b/rsocket-transport-netty/src/test/java/io/rsocket/integration/FragmentTest.java @@ -18,7 +18,6 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; import io.rsocket.core.RSocketConnector; @@ -38,7 +37,7 @@ import reactor.core.publisher.Mono; public class FragmentTest { - private AbstractRSocket handler; + private RSocket handler; private CloseableChannel server; private String message = null; private String metaData = null; @@ -89,7 +88,7 @@ void testFragmentNoMetaData(int clientFrameSize, int serverFrameSize) { System.out.println( "-------------------------------------------------testFragmentNoMetaData-------------------------------------------------"); handler = - new AbstractRSocket() { + new RSocket() { @Override public Flux requestStream(Payload payload) { String request = payload.getDataUtf8(); @@ -119,7 +118,7 @@ void testFragmentRequestMetaDataOnly(int clientFrameSize, int serverFrameSize) { System.out.println( "-------------------------------------------------testFragmentRequestMetaDataOnly-------------------------------------------------"); handler = - new AbstractRSocket() { + new RSocket() { @Override public Flux requestStream(Payload payload) { String request = payload.getDataUtf8(); @@ -150,7 +149,7 @@ void testFragmentBothMetaData(int clientFrameSize, int serverFrameSize) { System.out.println( "-------------------------------------------------testFragmentBothMetaData-------------------------------------------------"); handler = - new AbstractRSocket() { + new RSocket() { @Override public Flux requestStream(Payload payload) { String request = payload.getDataUtf8(); diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebSocketTransportIntegrationTest.java b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebSocketTransportIntegrationTest.java index 7028a3846..c418dea0f 100644 --- a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebSocketTransportIntegrationTest.java +++ b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebSocketTransportIntegrationTest.java @@ -1,8 +1,7 @@ package io.rsocket.transport.netty; -import io.rsocket.AbstractRSocket; -import io.rsocket.Payload; import io.rsocket.RSocket; +import io.rsocket.SocketAcceptor; import io.rsocket.core.RSocketConnector; import io.rsocket.core.RSocketServer; import io.rsocket.transport.ServerTransport; @@ -14,7 +13,6 @@ import java.time.Duration; import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; import reactor.netty.DisposableServer; import reactor.netty.http.server.HttpServer; import reactor.test.StepVerifier; @@ -25,16 +23,9 @@ public class WebSocketTransportIntegrationTest { public void sendStreamOfDataWithExternalHttpServerTest() { ServerTransport.ConnectionAcceptor acceptor = RSocketServer.create( - (setupPayload, sendingRSocket) -> { - return Mono.just( - new AbstractRSocket() { - @Override - public Flux requestStream(Payload payload) { - return Flux.range(0, 10) - .map(i -> DefaultPayload.create(String.valueOf(i))); - } - }); - }) + SocketAcceptor.forRequestStream( + payload -> + Flux.range(0, 10).map(i -> DefaultPayload.create(String.valueOf(i))))) .asConnectionAcceptor(); DisposableServer server = diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketPingPongIntegrationTest.java b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketPingPongIntegrationTest.java index ab6c343de..e2ee9e521 100644 --- a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketPingPongIntegrationTest.java +++ b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketPingPongIntegrationTest.java @@ -8,10 +8,9 @@ import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketFrame; import io.netty.util.ReferenceCountUtil; -import io.rsocket.AbstractRSocket; import io.rsocket.Closeable; -import io.rsocket.Payload; import io.rsocket.RSocket; +import io.rsocket.SocketAcceptor; import io.rsocket.core.RSocketConnector; import io.rsocket.core.RSocketServer; import io.rsocket.transport.ServerTransport; @@ -47,7 +46,7 @@ void tearDown() { @MethodSource("provideServerTransport") void webSocketPingPong(ServerTransport serverTransport) { server = - RSocketServer.create((setup, sendingSocket) -> Mono.just(new EchoRSocket())) + RSocketServer.create(SocketAcceptor.forRequestResponse(Mono::just)) .bind(serverTransport) .block(); @@ -100,13 +99,6 @@ private static Stream provideServerTransport() { HttpServer.create().host(host).port(port), routes -> {}, "/"))); } - private static class EchoRSocket extends AbstractRSocket { - @Override - public Mono requestResponse(Payload payload) { - return Mono.just(payload); - } - } - private static class PingSender extends ChannelInboundHandlerAdapter { private final MonoProcessor channel = MonoProcessor.create(); private final MonoProcessor pong = MonoProcessor.create(); From 31880691d2e582d13f39a576c2b473f95a975b66 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Thu, 30 Apr 2020 16:09:49 +0300 Subject: [PATCH 43/62] reduces maintenance complexity (#806) --- .../io/rsocket/core/RSocketRequester.java | 216 +- .../java/io/rsocket/internal/BitUtil.java | 287 --- .../io/rsocket/internal/CollectionUtil.java | 121 -- .../java/io/rsocket/internal/Hashing.java | 124 -- .../internal/LimitableRequestPublisher.java | 204 -- .../rsocket/internal/SwitchTransformFlux.java | 581 ------ .../io/rsocket/internal/UnicastMonoEmpty.java | 84 - .../internal/UnicastMonoProcessor.java | 509 ----- .../java/io/rsocket/util/DisposableUtils.java | 45 - .../rsocket/util/DuplexConnectionProxy.java | 71 - .../main/java/io/rsocket/util/Function3.java | 22 - .../io/rsocket/util/MonoLifecycleHandler.java | 21 - .../rsocket/util/MultiSubscriberRSocket.java | 54 - .../java/io/rsocket/util/OnceConsumer.java | 33 - .../java/io/rsocket/util/RecyclerFactory.java | 46 - .../core/RSocketRequesterSubscribersTest.java | 18 - .../io/rsocket/core/RSocketRequesterTest.java | 18 - .../LimitableRequestPublisherTest.java | 33 - .../internal/SwitchTransformFluxTest.java | 446 ----- .../internal/UnicastMonoEmptyTest.java | 97 - .../internal/UnicastMonoProcessorTest.java | 1780 ----------------- 21 files changed, 122 insertions(+), 4688 deletions(-) delete mode 100644 rsocket-core/src/main/java/io/rsocket/internal/BitUtil.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/internal/CollectionUtil.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/internal/Hashing.java delete mode 100755 rsocket-core/src/main/java/io/rsocket/internal/LimitableRequestPublisher.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/internal/SwitchTransformFlux.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/internal/UnicastMonoEmpty.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/internal/UnicastMonoProcessor.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/util/DisposableUtils.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/util/DuplexConnectionProxy.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/util/Function3.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/util/MonoLifecycleHandler.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/util/MultiSubscriberRSocket.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/util/OnceConsumer.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/util/RecyclerFactory.java delete mode 100644 rsocket-core/src/test/java/io/rsocket/internal/LimitableRequestPublisherTest.java delete mode 100644 rsocket-core/src/test/java/io/rsocket/internal/SwitchTransformFluxTest.java delete mode 100644 rsocket-core/src/test/java/io/rsocket/internal/UnicastMonoEmptyTest.java delete mode 100644 rsocket-core/src/test/java/io/rsocket/internal/UnicastMonoProcessorTest.java diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index fefb06003..f762bfe99 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -45,21 +45,18 @@ import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.internal.SynchronizedIntObjectHashMap; import io.rsocket.internal.UnboundedProcessor; -import io.rsocket.internal.UnicastMonoEmpty; -import io.rsocket.internal.UnicastMonoProcessor; import io.rsocket.keepalive.KeepAliveFramesAcceptor; import io.rsocket.keepalive.KeepAliveHandler; import io.rsocket.keepalive.KeepAliveSupport; import io.rsocket.lease.RequesterLeaseHandler; -import io.rsocket.util.MonoLifecycleHandler; import java.nio.channels.ClosedChannelException; import java.util.concurrent.CancellationException; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.Consumer; import java.util.function.LongConsumer; import java.util.function.Supplier; -import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.reactivestreams.Processor; import org.reactivestreams.Publisher; @@ -210,15 +207,25 @@ private Mono handleFireAndForget(Payload payload) { return Mono.error(new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE)); } + final AtomicBoolean once = new AtomicBoolean(); final int streamId = streamIdSupplier.nextStreamId(receivers); - return UnicastMonoEmpty.newInstance( + return Mono.defer( () -> { - ByteBuf requestFrame = - RequestFireAndForgetFrameFlyweight.encodeReleasingPayload( - allocator, streamId, payload); + if (once.getAndSet(true)) { + return Mono.error( + new IllegalStateException("FireAndForgetMono allows only a single subscriber")); + } + + return Mono.empty() + .doOnSubscribe( + (__) -> { + ByteBuf requestFrame = + RequestFireAndForgetFrameFlyweight.encodeReleasingPayload( + allocator, streamId, payload); - sendProcessor.onNext(requestFrame); + sendProcessor.onNext(requestFrame); + }); }); } @@ -236,34 +243,37 @@ private Mono handleRequestResponse(final Payload payload) { int streamId = streamIdSupplier.nextStreamId(receivers); final UnboundedProcessor sendProcessor = this.sendProcessor; - - UnicastMonoProcessor receiver = - UnicastMonoProcessor.create( - new MonoLifecycleHandler() { - @Override - public void doOnSubscribe() { - final ByteBuf requestFrame = - RequestResponseFrameFlyweight.encodeReleasingPayload( - allocator, streamId, payload); - - sendProcessor.onNext(requestFrame); - } - - @Override - public void doOnTerminal( - @Nonnull SignalType signalType, - @Nullable Payload element, - @Nullable Throwable e) { - if (signalType == SignalType.CANCEL) { - sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); - } - removeStreamReceiver(streamId); - } - }); + final UnicastProcessor receiver = UnicastProcessor.create(Queues.one().get()); + final AtomicBoolean once = new AtomicBoolean(); receivers.put(streamId, receiver); - return receiver.doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER); + return Mono.defer( + () -> { + if (once.getAndSet(true)) { + return Mono.error( + new IllegalStateException("RequestResponseMono allows only a single subscriber")); + } + + return receiver + .next() + .doOnSubscribe( + (__) -> { + ByteBuf requestFrame = + RequestResponseFrameFlyweight.encodeReleasingPayload( + allocator, streamId, payload); + + sendProcessor.onNext(requestFrame); + }) + .doFinally( + signalType -> { + if (signalType == SignalType.CANCEL) { + sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); + } + removeStreamReceiver(streamId); + }) + .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER); + }); } private Flux handleRequestStream(final Payload payload) { @@ -283,65 +293,76 @@ private Flux handleRequestStream(final Payload payload) { final UnboundedProcessor sendProcessor = this.sendProcessor; final UnicastProcessor receiver = UnicastProcessor.create(); final AtomicInteger wip = new AtomicInteger(0); + final AtomicBoolean once = new AtomicBoolean(); receivers.put(streamId, receiver); - return receiver - .doOnRequest( - new LongConsumer() { - - boolean firstRequest = true; + return Flux.defer( + () -> { + if (once.getAndSet(true)) { + return Flux.error( + new IllegalStateException("RequestStreamFlux allows only a single subscriber")); + } - @Override - public void accept(long n) { - if (firstRequest) { - firstRequest = false; - if (wip.getAndIncrement() != 0) { - // no need to do anything. - // stream was canceled and fist payload has already been discarded - return; - } - int missed = 1; - boolean firstHasBeenSent = false; - for (; ; ) { - if (!firstHasBeenSent) { - sendProcessor.onNext( - RequestStreamFrameFlyweight.encodeReleasingPayload( - allocator, streamId, n, payload)); - firstHasBeenSent = true; - } else { - // if first frame was sent but we cycling again, it means that wip was - // incremented at doOnCancel - sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); - return; + return receiver + .doOnRequest( + new LongConsumer() { + + boolean firstRequest = true; + + @Override + public void accept(long n) { + if (firstRequest) { + firstRequest = false; + if (wip.getAndIncrement() != 0) { + // no need to do anything. + // stream was canceled and fist payload has already been discarded + return; + } + int missed = 1; + boolean firstHasBeenSent = false; + for (; ; ) { + if (!firstHasBeenSent) { + sendProcessor.onNext( + RequestStreamFrameFlyweight.encodeReleasingPayload( + allocator, streamId, n, payload)); + firstHasBeenSent = true; + } else { + // if first frame was sent but we cycling again, it means that wip was + // incremented at doOnCancel + sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); + return; + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + return; + } + } + } else if (!receiver.isDisposed()) { + sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); + } } + }) + .doFinally( + s -> { + if (s == SignalType.CANCEL) { + if (wip.getAndIncrement() != 0) { + return; + } - missed = wip.addAndGet(-missed); - if (missed == 0) { - return; + // check if we need to release payload + // only applicable if the cancel appears earlier than actual request + if (payload.refCnt() > 0) { + payload.release(); + } else { + sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); + } } - } - } else if (!receiver.isDisposed()) { - sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); - } - } - }) - .doOnCancel( - () -> { - if (wip.getAndIncrement() != 0) { - return; - } - - // check if we need to release payload - // only applicable if the cancel appears earlier than actual request - if (payload.refCnt() > 0) { - payload.release(); - } else { - sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); - } - }) - .doFinally(s -> removeStreamReceiver(streamId)) - .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER); + removeStreamReceiver(streamId); + }) + .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER); + }); } private Flux handleChannel(Flux request) { @@ -522,12 +543,23 @@ private Mono handleMetadataPush(Payload payload) { return Mono.error(new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE)); } - return UnicastMonoEmpty.newInstance( + final AtomicBoolean once = new AtomicBoolean(); + + return Mono.defer( () -> { - ByteBuf metadataPushFrame = - MetadataPushFrameFlyweight.encodeReleasingPayload(allocator, payload); + if (once.getAndSet(true)) { + return Mono.error( + new IllegalStateException("MetadataPushMono allows only a single subscriber")); + } + + return Mono.empty() + .doOnSubscribe( + (__) -> { + ByteBuf metadataPushFrame = + MetadataPushFrameFlyweight.encodeReleasingPayload(allocator, payload); - sendProcessor.onNextPrioritized(metadataPushFrame); + sendProcessor.onNextPrioritized(metadataPushFrame); + }); }); } @@ -544,10 +576,6 @@ private Throwable checkAvailable() { return null; } - private boolean contains(int streamId) { - return receivers.containsKey(streamId); - } - private void handleIncomingFrames(ByteBuf frame) { try { int streamId = FrameHeaderFlyweight.streamId(frame); diff --git a/rsocket-core/src/main/java/io/rsocket/internal/BitUtil.java b/rsocket-core/src/main/java/io/rsocket/internal/BitUtil.java deleted file mode 100644 index 79be9ccd5..000000000 --- a/rsocket-core/src/main/java/io/rsocket/internal/BitUtil.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright 2014-2019 Real Logic Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.rsocket.internal; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.util.concurrent.ThreadLocalRandom; - -/** Miscellaneous useful functions for dealing with low level bits and bytes. */ -public class BitUtil { - /** Size of a byte in bytes */ - public static final int SIZE_OF_BYTE = 1; - - /** Size of a boolean in bytes */ - public static final int SIZE_OF_BOOLEAN = 1; - - /** Size of a char in bytes */ - public static final int SIZE_OF_CHAR = 2; - - /** Size of a short in bytes */ - public static final int SIZE_OF_SHORT = 2; - - /** Size of an int in bytes */ - public static final int SIZE_OF_INT = 4; - - /** Size of a float in bytes */ - public static final int SIZE_OF_FLOAT = 4; - - /** Size of a long in bytes */ - public static final int SIZE_OF_LONG = 8; - - /** Size of a double in bytes */ - public static final int SIZE_OF_DOUBLE = 8; - - /** Length of the data blocks used by the CPU cache sub-system in bytes. */ - public static final int CACHE_LINE_LENGTH = 64; - - private static final byte[] HEX_DIGIT_TABLE = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' - }; - - private static final byte[] FROM_HEX_DIGIT_TABLE; - - static { - FROM_HEX_DIGIT_TABLE = new byte[128]; - - FROM_HEX_DIGIT_TABLE['0'] = 0x00; - FROM_HEX_DIGIT_TABLE['1'] = 0x01; - FROM_HEX_DIGIT_TABLE['2'] = 0x02; - FROM_HEX_DIGIT_TABLE['3'] = 0x03; - FROM_HEX_DIGIT_TABLE['4'] = 0x04; - FROM_HEX_DIGIT_TABLE['5'] = 0x05; - FROM_HEX_DIGIT_TABLE['6'] = 0x06; - FROM_HEX_DIGIT_TABLE['7'] = 0x07; - FROM_HEX_DIGIT_TABLE['8'] = 0x08; - FROM_HEX_DIGIT_TABLE['9'] = 0x09; - FROM_HEX_DIGIT_TABLE['a'] = 0x0a; - FROM_HEX_DIGIT_TABLE['A'] = 0x0a; - FROM_HEX_DIGIT_TABLE['b'] = 0x0b; - FROM_HEX_DIGIT_TABLE['B'] = 0x0b; - FROM_HEX_DIGIT_TABLE['c'] = 0x0c; - FROM_HEX_DIGIT_TABLE['C'] = 0x0c; - FROM_HEX_DIGIT_TABLE['d'] = 0x0d; - FROM_HEX_DIGIT_TABLE['D'] = 0x0d; - FROM_HEX_DIGIT_TABLE['e'] = 0x0e; - FROM_HEX_DIGIT_TABLE['E'] = 0x0e; - FROM_HEX_DIGIT_TABLE['f'] = 0x0f; - FROM_HEX_DIGIT_TABLE['F'] = 0x0f; - } - - private static final int LAST_DIGIT_MASK = 0b1; - - /** - * Fast method of finding the next power of 2 greater than or equal to the supplied value. - * - *

    If the value is <= 0 then 1 will be returned. - * - *

    This method is not suitable for {@link Integer#MIN_VALUE} or numbers greater than 2^30. - * - * @param value from which to search for next power of 2 - * @return The next power of 2 or the value itself if it is a power of 2 - */ - public static int findNextPositivePowerOfTwo(final int value) { - return 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(value - 1)); - } - - /** - * Align a value to the next multiple up of alignment. If the value equals an alignment multiple - * then it is returned unchanged. - * - *

    This method executes without branching. This code is designed to be use in the fast path and - * should not be used with negative numbers. Negative numbers will result in undefined behaviour. - * - * @param value to be aligned up. - * @param alignment to be used. - * @return the value aligned to the next boundary. - */ - public static int align(final int value, final int alignment) { - return (value + (alignment - 1)) & -alignment; - } - - /** - * Generate a byte array from the hex representation of the given byte array. - * - * @param buffer to convert from a hex representation (in Big Endian). - * @return new byte array that is decimal representation of the passed array. - */ - public static byte[] fromHexByteArray(final byte[] buffer) { - final byte[] outputBuffer = new byte[buffer.length >> 1]; - - for (int i = 0; i < buffer.length; i += 2) { - final int hi = FROM_HEX_DIGIT_TABLE[buffer[i]] << 4; - final int lo = FROM_HEX_DIGIT_TABLE[buffer[i + 1]]; // lgtm [java/index-out-of-bounds] - outputBuffer[i >> 1] = (byte) (hi | lo); - } - - return outputBuffer; - } - - /** - * Generate a byte array that is a hex representation of a given byte array. - * - * @param buffer to convert to a hex representation. - * @return new byte array that is hex representation (in Big Endian) of the passed array. - */ - public static byte[] toHexByteArray(final byte[] buffer) { - return toHexByteArray(buffer, 0, buffer.length); - } - - /** - * Generate a byte array that is a hex representation of a given byte array. - * - * @param buffer to convert to a hex representation. - * @param offset the offset into the buffer. - * @param length the number of bytes to convert. - * @return new byte array that is hex representation (in Big Endian) of the passed array. - */ - public static byte[] toHexByteArray(final byte[] buffer, final int offset, final int length) { - final byte[] outputBuffer = new byte[length << 1]; - - for (int i = 0; i < (length << 1); i += 2) { - final byte b = buffer[offset + (i >> 1)]; - - outputBuffer[i] = HEX_DIGIT_TABLE[(b >> 4) & 0x0F]; - outputBuffer[i + 1] = HEX_DIGIT_TABLE[b & 0x0F]; - } - - return outputBuffer; - } - - /** - * Generate a byte array from a string that is the hex representation of the given byte array. - * - * @param string to convert from a hex representation (in Big Endian). - * @return new byte array holding the decimal representation of the passed array. - */ - public static byte[] fromHex(final String string) { - return fromHexByteArray(string.getBytes(UTF_8)); - } - - /** - * Generate a string that is the hex representation of a given byte array. - * - * @param buffer to convert to a hex representation. - * @param offset the offset into the buffer. - * @param length the number of bytes to convert. - * @return new String holding the hex representation (in Big Endian) of the passed array. - */ - public static String toHex(final byte[] buffer, final int offset, final int length) { - return new String(toHexByteArray(buffer, offset, length), UTF_8); - } - - /** - * Generate a string that is the hex representation of a given byte array. - * - * @param buffer to convert to a hex representation. - * @return new String holding the hex representation (in Big Endian) of the passed array. - */ - public static String toHex(final byte[] buffer) { - return new String(toHexByteArray(buffer), UTF_8); - } - - /** - * Is a number even. - * - * @param value to check. - * @return true if the number is even otherwise false. - */ - public static boolean isEven(final int value) { - return (value & LAST_DIGIT_MASK) == 0; - } - - /** - * Is a value a positive power of 2. - * - * @param value to be checked. - * @return true if the number is a positive power of 2, otherwise false. - */ - public static boolean isPowerOfTwo(final int value) { - return value > 0 && ((value & (~value + 1)) == value); - } - - /** - * Cycles indices of an array one at a time in a forward fashion - * - * @param current value to be incremented. - * @param max value for the cycle. - * @return the next value, or zero if max is reached. - */ - public static int next(final int current, final int max) { - int next = current + 1; - if (next == max) { - next = 0; - } - - return next; - } - - /** - * Cycles indices of an array one at a time in a backwards fashion - * - * @param current value to be decremented. - * @param max value of the cycle. - * @return the next value, or max - 1 if current is zero. - */ - public static int previous(final int current, final int max) { - if (0 == current) { - return max - 1; - } - - return current - 1; - } - - /** - * Calculate the shift value to scale a number based on how refs are compressed or not. - * - * @param scale of the number reported by Unsafe. - * @return how many times the number needs to be shifted to the left. - */ - public static int calculateShiftForScale(final int scale) { - if (4 == scale) { - return 2; - } else if (8 == scale) { - return 3; - } - - throw new IllegalArgumentException("unknown pointer size for scale=" + scale); - } - - /** - * Generate a randomised integer over [{@link Integer#MIN_VALUE}, {@link Integer#MAX_VALUE}]. - * - * @return randomised integer suitable as an Id. - */ - public static int generateRandomisedId() { - return ThreadLocalRandom.current().nextInt(); - } - - /** - * Is an address aligned on a boundary. - * - * @param address to be tested. - * @param alignment boundary the address is tested against. - * @return true if the address is on the aligned boundary otherwise false. - * @throws IllegalArgumentException if the alignment is not a power of 2. - */ - public static boolean isAligned(final long address, final int alignment) { - if (!BitUtil.isPowerOfTwo(alignment)) { - throw new IllegalArgumentException("alignment must be a power of 2: alignment=" + alignment); - } - - return (address & (alignment - 1)) == 0; - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/internal/CollectionUtil.java b/rsocket-core/src/main/java/io/rsocket/internal/CollectionUtil.java deleted file mode 100644 index 8d4526c36..000000000 --- a/rsocket-core/src/main/java/io/rsocket/internal/CollectionUtil.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2014-2019 Real Logic Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.rsocket.internal; - -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.function.ToIntFunction; - -/** Utility functions for collection objects. */ -public class CollectionUtil { - /** - * A getOrDefault that doesn't create garbage if its suppler is non-capturing. - * - * @param map to perform the lookup on. - * @param key on which the lookup is done. - * @param supplier of the default value if one is not found. - * @param type of the key - * @param type of the value - * @return the value if found or a new default which as been added to the map. - */ - public static V getOrDefault( - final Map map, final K key, final Function supplier) { - V value = map.get(key); - if (value == null) { - value = supplier.apply(key); - map.put(key, value); - } - - return value; - } - - /** - * Garbage free sum function. - * - *

    Note: the list must implement {@link java.util.RandomAccess} to be efficient. - * - * @param values the list of input values - * @param function function that map each value to an int - * @param the value to add up - * @return the sum of all the int values returned for each member of the list. - */ - public static int sum(final List values, final ToIntFunction function) { - int total = 0; - - final int size = values.size(); - for (int i = 0; i < size; i++) { - final V value = values.get(i); - total += function.applyAsInt(value); - } - - return total; - } - - /** - * Validate that a load factor is in the range of 0.1 to 0.9. - * - *

    Load factors in the range 0.5 - 0.7 are recommended for open-addressing with linear probing. - * - * @param loadFactor to be validated. - */ - public static void validateLoadFactor(final float loadFactor) { - if (loadFactor < 0.1f || loadFactor > 0.9f) { - throw new IllegalArgumentException( - "load factor must be in the range of 0.1 to 0.9: " + loadFactor); - } - } - - /** - * Validate that a number is a power of two. - * - * @param value to be validated. - */ - public static void validatePositivePowerOfTwo(final int value) { - if (value > 0 && 1 == (value & (value - 1))) { - throw new IllegalStateException("value must be a positive power of two"); - } - } - - /** - * Remove element from a list if it matches a predicate. - * - *

    Note: the list must implement {@link java.util.RandomAccess} to be efficient. - * - * @param values to be iterated over. - * @param predicate to test the value against - * @param type of the value. - * @return the number of items remove. - */ - public static int removeIf(final List values, final Predicate predicate) { - int size = values.size(); - int total = 0; - - for (int i = 0; i < size; ) { - final T value = values.get(i); - if (predicate.test(value)) { - values.remove(i); - total++; - size--; - } else { - i++; - } - } - - return total; - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/internal/Hashing.java b/rsocket-core/src/main/java/io/rsocket/internal/Hashing.java deleted file mode 100644 index 613dce209..000000000 --- a/rsocket-core/src/main/java/io/rsocket/internal/Hashing.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2014-2019 Real Logic Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.rsocket.internal; - -/** Hashing functions for applying to integers. */ -public class Hashing { - /** Default load factor to be used in open addressing hashed data structures. */ - public static final float DEFAULT_LOAD_FACTOR = 0.55f; - - /** - * Generate a hash for an int value. This is a no op. - * - * @param value to be hashed. - * @return the hashed value. - */ - public static int hash(final int value) { - return value * 31; - } - - /** - * Generate a hash for an long value. - * - * @param value to be hashed. - * @return the hashed value. - */ - public static int hash(final long value) { - long hash = value * 31; - hash = (int) hash ^ (int) (hash >>> 32); - - return (int) hash; - } - - /** - * Generate a hash for a int value. - * - * @param value to be hashed. - * @param mask mask to be applied that must be a power of 2 - 1. - * @return the hash of the value. - */ - public static int hash(final int value, final int mask) { - final int hash = value * 31; - - return hash & mask; - } - - /** - * Generate a hash for a K value. - * - * @param is the type of value - * @param value to be hashed. - * @param mask mask to be applied that must be a power of 2 - 1. - * @return the hash of the value. - */ - public static int hash(final K value, final int mask) { - final int hash = value.hashCode(); - - return hash & mask; - } - - /** - * Generate a hash for a long value. - * - * @param value to be hashed. - * @param mask mask to be applied that must be a power of 2 - 1. - * @return the hash of the value. - */ - public static int hash(final long value, final int mask) { - long hash = value * 31; - hash = (int) hash ^ (int) (hash >>> 32); - - return (int) hash & mask; - } - - /** - * Generate an even hash for a int value. - * - * @param value to be hashed. - * @param mask mask to be applied that must be a power of 2 - 1. - * @return the hash of the value which is always even. - */ - public static int evenHash(final int value, final int mask) { - final int hash = (value << 1) - (value << 8); - - return hash & mask; - } - - /** - * Generate an even hash for a long value. - * - * @param value to be hashed. - * @param mask mask to be applied that must be a power of 2 - 1. - * @return the hash of the value which is always even. - */ - public static int evenHash(final long value, final int mask) { - int hash = (int) value ^ (int) (value >>> 32); - hash = (hash << 1) - (hash << 8); - - return hash & mask; - } - - /** - * Combined two 32 bit keys into a 64-bit compound. - * - * @param keyPartA to make the upper bits - * @param keyPartB to make the lower bits. - * @return the compound key - */ - public static long compoundKey(final int keyPartA, final int keyPartB) { - return ((long) keyPartA << 32) | (keyPartB & 0xFFFF_FFFFL); - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/internal/LimitableRequestPublisher.java b/rsocket-core/src/main/java/io/rsocket/internal/LimitableRequestPublisher.java deleted file mode 100755 index 8adb7542a..000000000 --- a/rsocket-core/src/main/java/io/rsocket/internal/LimitableRequestPublisher.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.internal; - -import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; -import javax.annotation.Nullable; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; -import reactor.core.CoreSubscriber; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Operators; - -/** */ -public class LimitableRequestPublisher extends Flux implements Subscription { - - private static final int NOT_CANCELED_STATE = 0; - private static final int CANCELED_STATE = 1; - - private final Publisher source; - - private volatile int canceled; - private static final AtomicIntegerFieldUpdater CANCELED = - AtomicIntegerFieldUpdater.newUpdater(LimitableRequestPublisher.class, "canceled"); - - private final long prefetch; - - private long internalRequested; - - private long externalRequested; - - private boolean subscribed; - - private @Nullable Subscription internalSubscription; - - private LimitableRequestPublisher(Publisher source, long prefetch) { - this.source = source; - this.prefetch = prefetch; - } - - public static LimitableRequestPublisher wrap(Publisher source, long prefetch) { - return new LimitableRequestPublisher<>(source, prefetch); - } - - public static LimitableRequestPublisher wrap(Publisher source) { - return wrap(source, Long.MAX_VALUE); - } - - @Override - public void subscribe(CoreSubscriber destination) { - synchronized (this) { - if (subscribed) { - throw new IllegalStateException("only one subscriber at a time"); - } - - subscribed = true; - } - final InnerOperator s = new InnerOperator(destination); - - destination.onSubscribe(s); - source.subscribe(s); - increaseInternalLimit(prefetch); - } - - public void increaseInternalLimit(long n) { - synchronized (this) { - long requested = internalRequested; - if (requested == Long.MAX_VALUE) { - return; - } - internalRequested = Operators.addCap(n, requested); - } - - requestN(); - } - - @Override - public void request(long n) { - synchronized (this) { - long requested = externalRequested; - if (requested == Long.MAX_VALUE) { - return; - } - externalRequested = Operators.addCap(n, requested); - } - - requestN(); - } - - private void requestN() { - long r; - final Subscription s; - - synchronized (this) { - s = internalSubscription; - if (s == null) { - return; - } - - long er = externalRequested; - long ir = internalRequested; - - if (er != Long.MAX_VALUE || ir != Long.MAX_VALUE) { - r = Math.min(ir, er); - if (er != Long.MAX_VALUE) { - externalRequested -= r; - } - if (ir != Long.MAX_VALUE) { - internalRequested -= r; - } - } else { - r = Long.MAX_VALUE; - } - } - - if (r > 0) { - s.request(r); - } - } - - public void cancel() { - if (!isCanceled() && CANCELED.compareAndSet(this, NOT_CANCELED_STATE, CANCELED_STATE)) { - Subscription s; - - synchronized (this) { - s = internalSubscription; - internalSubscription = null; - subscribed = false; - } - - if (s != null) { - s.cancel(); - } - } - } - - private boolean isCanceled() { - return canceled == 1; - } - - private class InnerOperator implements CoreSubscriber, Subscription { - final Subscriber destination; - - private InnerOperator(Subscriber destination) { - this.destination = destination; - } - - @Override - public void onSubscribe(Subscription s) { - synchronized (LimitableRequestPublisher.this) { - LimitableRequestPublisher.this.internalSubscription = s; - - if (isCanceled()) { - s.cancel(); - subscribed = false; - LimitableRequestPublisher.this.internalSubscription = null; - } - } - - requestN(); - } - - @Override - public void onNext(T t) { - try { - destination.onNext(t); - } catch (Throwable e) { - onError(e); - } - } - - @Override - public void onError(Throwable t) { - destination.onError(t); - } - - @Override - public void onComplete() { - destination.onComplete(); - } - - @Override - public void request(long n) {} - - @Override - public void cancel() { - LimitableRequestPublisher.this.cancel(); - } - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/internal/SwitchTransformFlux.java b/rsocket-core/src/main/java/io/rsocket/internal/SwitchTransformFlux.java deleted file mode 100644 index 0d2e5988e..000000000 --- a/rsocket-core/src/main/java/io/rsocket/internal/SwitchTransformFlux.java +++ /dev/null @@ -1,581 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.internal; - -import java.util.Objects; -import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; -import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; -import java.util.function.BiFunction; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscription; -import reactor.core.CoreSubscriber; -import reactor.core.Fuseable; -import reactor.core.Scannable; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Operators; -import reactor.util.annotation.Nullable; -import reactor.util.context.Context; - -/** @deprecated in favour of {@link Flux#switchOnFirst(BiFunction)} */ -@Deprecated -public final class SwitchTransformFlux extends Flux { - - final Publisher source; - final BiFunction, Publisher> transformer; - - public SwitchTransformFlux( - Publisher source, BiFunction, Publisher> transformer) { - this.source = Objects.requireNonNull(source, "source"); - this.transformer = Objects.requireNonNull(transformer, "transformer"); - } - - @Override - public int getPrefetch() { - return 1; - } - - @Override - @SuppressWarnings("unchecked") - public void subscribe(CoreSubscriber actual) { - if (actual instanceof Fuseable.ConditionalSubscriber) { - source.subscribe( - new SwitchTransformConditionalOperator<>( - (Fuseable.ConditionalSubscriber) actual, transformer)); - return; - } - source.subscribe(new SwitchTransformOperator<>(actual, transformer)); - } - - static final class SwitchTransformOperator extends Flux - implements CoreSubscriber, Subscription, Scannable { - - final CoreSubscriber outer; - final BiFunction, Publisher> transformer; - - Subscription s; - Throwable throwable; - - volatile boolean done; - volatile T first; - - volatile CoreSubscriber inner; - - @SuppressWarnings("rawtypes") - static final AtomicReferenceFieldUpdater INNER = - AtomicReferenceFieldUpdater.newUpdater( - SwitchTransformOperator.class, CoreSubscriber.class, "inner"); - - volatile int wip; - - @SuppressWarnings("rawtypes") - static final AtomicIntegerFieldUpdater WIP = - AtomicIntegerFieldUpdater.newUpdater(SwitchTransformOperator.class, "wip"); - - volatile int once; - - @SuppressWarnings("rawtypes") - static final AtomicIntegerFieldUpdater ONCE = - AtomicIntegerFieldUpdater.newUpdater(SwitchTransformOperator.class, "once"); - - SwitchTransformOperator( - CoreSubscriber outer, - BiFunction, Publisher> transformer) { - this.outer = outer; - this.transformer = transformer; - } - - @Override - @Nullable - public Object scanUnsafe(Attr key) { - if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); - if (key == Attr.PREFETCH) return 1; - - return null; - } - - @Override - public Context currentContext() { - CoreSubscriber actual = inner; - - if (actual != null) { - return actual.currentContext(); - } - - return outer.currentContext(); - } - - @Override - public void cancel() { - if (s != Operators.cancelledSubscription()) { - Subscription s = this.s; - this.s = Operators.cancelledSubscription(); - - if (WIP.getAndIncrement(this) == 0) { - INNER.lazySet(this, null); - - T f = first; - if (f != null) { - first = null; - Operators.onDiscard(f, currentContext()); - } - } - - s.cancel(); - } - } - - @Override - public void subscribe(CoreSubscriber actual) { - if (once == 0 && ONCE.compareAndSet(this, 0, 1)) { - INNER.lazySet(this, actual); - actual.onSubscribe(this); - } else { - Operators.error( - actual, new IllegalStateException("SwitchTransform allows only one Subscriber")); - } - } - - @Override - public void onSubscribe(Subscription s) { - if (Operators.validate(this.s, s)) { - this.s = s; - s.request(1); - } - } - - @Override - public void onNext(T t) { - if (done) { - Operators.onNextDropped(t, currentContext()); - return; - } - - CoreSubscriber i = inner; - - if (i == null) { - try { - first = t; - Publisher result = - Objects.requireNonNull( - transformer.apply(t, this), "The transformer returned a null value"); - result.subscribe(outer); - return; - } catch (Throwable e) { - onError(Operators.onOperatorError(s, e, t, currentContext())); - return; - } - } - - i.onNext(t); - } - - @Override - public void onError(Throwable t) { - if (done) { - Operators.onErrorDropped(t, currentContext()); - return; - } - - throwable = t; - done = true; - CoreSubscriber i = inner; - - if (i != null) { - if (first == null) { - drainRegular(); - } - } else { - Operators.error(outer, t); - } - } - - @Override - public void onComplete() { - if (done) { - return; - } - - done = true; - CoreSubscriber i = inner; - - if (i != null) { - if (first == null) { - drainRegular(); - } - } else { - Operators.complete(outer); - } - } - - @Override - public void request(long n) { - if (Operators.validate(n)) { - if (first != null && drainRegular() && n != Long.MAX_VALUE) { - if (--n > 0) { - s.request(n); - } - } else { - s.request(n); - } - } - } - - boolean drainRegular() { - if (WIP.getAndIncrement(this) != 0) { - return false; - } - - T f = first; - int m = 1; - boolean sent = false; - Subscription s = this.s; - CoreSubscriber a = inner; - - for (; ; ) { - if (f != null) { - first = null; - - if (s == Operators.cancelledSubscription()) { - Operators.onDiscard(f, a.currentContext()); - return true; - } - - a.onNext(f); - f = null; - sent = true; - } - - if (s == Operators.cancelledSubscription()) { - return sent; - } - - if (done) { - Throwable t = throwable; - if (t != null) { - a.onError(t); - } else { - a.onComplete(); - } - return sent; - } - - m = WIP.addAndGet(this, -m); - - if (m == 0) { - return sent; - } - } - } - } - - static final class SwitchTransformConditionalOperator extends Flux - implements Fuseable.ConditionalSubscriber, Subscription, Scannable { - - final Fuseable.ConditionalSubscriber outer; - final BiFunction, Publisher> transformer; - - Subscription s; - Throwable throwable; - - volatile boolean done; - volatile T first; - - volatile Fuseable.ConditionalSubscriber inner; - - @SuppressWarnings("rawtypes") - static final AtomicReferenceFieldUpdater< - SwitchTransformConditionalOperator, Fuseable.ConditionalSubscriber> - INNER = - AtomicReferenceFieldUpdater.newUpdater( - SwitchTransformConditionalOperator.class, - Fuseable.ConditionalSubscriber.class, - "inner"); - - volatile int wip; - - @SuppressWarnings("rawtypes") - static final AtomicIntegerFieldUpdater WIP = - AtomicIntegerFieldUpdater.newUpdater(SwitchTransformConditionalOperator.class, "wip"); - - volatile int once; - - @SuppressWarnings("rawtypes") - static final AtomicIntegerFieldUpdater ONCE = - AtomicIntegerFieldUpdater.newUpdater(SwitchTransformConditionalOperator.class, "once"); - - SwitchTransformConditionalOperator( - Fuseable.ConditionalSubscriber outer, - BiFunction, Publisher> transformer) { - this.outer = outer; - this.transformer = transformer; - } - - @Override - @Nullable - public Object scanUnsafe(Attr key) { - if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); - if (key == Attr.PREFETCH) return 1; - - return null; - } - - @Override - public Context currentContext() { - CoreSubscriber actual = inner; - - if (actual != null) { - return actual.currentContext(); - } - - return outer.currentContext(); - } - - @Override - public void cancel() { - if (s != Operators.cancelledSubscription()) { - Subscription s = this.s; - this.s = Operators.cancelledSubscription(); - - if (WIP.getAndIncrement(this) == 0) { - INNER.lazySet(this, null); - - T f = first; - if (f != null) { - first = null; - Operators.onDiscard(f, currentContext()); - } - } - - s.cancel(); - } - } - - @Override - @SuppressWarnings("unchecked") - public void subscribe(CoreSubscriber actual) { - if (once == 0 && ONCE.compareAndSet(this, 0, 1)) { - if (actual instanceof Fuseable.ConditionalSubscriber) { - INNER.lazySet(this, (Fuseable.ConditionalSubscriber) actual); - } else { - INNER.lazySet(this, new ConditionalSubscriberAdapter<>(actual)); - } - actual.onSubscribe(this); - } else { - Operators.error( - actual, new IllegalStateException("SwitchTransform allows only one Subscriber")); - } - } - - @Override - public void onSubscribe(Subscription s) { - if (Operators.validate(this.s, s)) { - this.s = s; - s.request(1); - } - } - - @Override - public void onNext(T t) { - if (done) { - Operators.onNextDropped(t, currentContext()); - return; - } - - CoreSubscriber i = inner; - - if (i == null) { - try { - first = t; - Publisher result = - Objects.requireNonNull( - transformer.apply(t, this), "The transformer returned a null value"); - result.subscribe(outer); - return; - } catch (Throwable e) { - onError(Operators.onOperatorError(s, e, t, currentContext())); - return; - } - } - - i.onNext(t); - } - - @Override - public boolean tryOnNext(T t) { - if (done) { - Operators.onNextDropped(t, currentContext()); - return false; - } - - Fuseable.ConditionalSubscriber i = inner; - - if (i == null) { - try { - first = t; - Publisher result = - Objects.requireNonNull( - transformer.apply(t, this), "The transformer returned a null value"); - result.subscribe(outer); - return true; - } catch (Throwable e) { - onError(Operators.onOperatorError(s, e, t, currentContext())); - return false; - } - } - - return i.tryOnNext(t); - } - - @Override - public void onError(Throwable t) { - if (done) { - Operators.onErrorDropped(t, currentContext()); - return; - } - - throwable = t; - done = true; - CoreSubscriber i = inner; - - if (i != null) { - if (first == null) { - drainRegular(); - } - } else { - Operators.error(outer, t); - } - } - - @Override - public void onComplete() { - if (done) { - return; - } - - done = true; - CoreSubscriber i = inner; - - if (i != null) { - if (first == null) { - drainRegular(); - } - } else { - Operators.complete(outer); - } - } - - @Override - public void request(long n) { - if (Operators.validate(n)) { - if (first != null && drainRegular() && n != Long.MAX_VALUE) { - if (--n > 0) { - s.request(n); - } - } else { - s.request(n); - } - } - } - - boolean drainRegular() { - if (WIP.getAndIncrement(this) != 0) { - return false; - } - - T f = first; - int m = 1; - boolean sent = false; - Subscription s = this.s; - CoreSubscriber a = inner; - - for (; ; ) { - if (f != null) { - first = null; - - if (s == Operators.cancelledSubscription()) { - Operators.onDiscard(f, a.currentContext()); - return true; - } - - a.onNext(f); - f = null; - sent = true; - } - - if (s == Operators.cancelledSubscription()) { - return sent; - } - - if (done) { - Throwable t = throwable; - if (t != null) { - a.onError(t); - } else { - a.onComplete(); - } - return sent; - } - - m = WIP.addAndGet(this, -m); - - if (m == 0) { - return sent; - } - } - } - } - - static final class ConditionalSubscriberAdapter implements Fuseable.ConditionalSubscriber { - - final CoreSubscriber delegate; - - ConditionalSubscriberAdapter(CoreSubscriber delegate) { - this.delegate = delegate; - } - - @Override - public Context currentContext() { - return delegate.currentContext(); - } - - @Override - public void onSubscribe(Subscription s) { - delegate.onSubscribe(s); - } - - @Override - public void onNext(T t) { - delegate.onNext(t); - } - - @Override - public void onError(Throwable t) { - delegate.onError(t); - } - - @Override - public void onComplete() { - delegate.onComplete(); - } - - @Override - public boolean tryOnNext(T t) { - delegate.onNext(t); - return true; - } - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/internal/UnicastMonoEmpty.java b/rsocket-core/src/main/java/io/rsocket/internal/UnicastMonoEmpty.java deleted file mode 100644 index 64a7d4422..000000000 --- a/rsocket-core/src/main/java/io/rsocket/internal/UnicastMonoEmpty.java +++ /dev/null @@ -1,84 +0,0 @@ -package io.rsocket.internal; - -import java.time.Duration; -import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; -import reactor.core.CoreSubscriber; -import reactor.core.Scannable; -import reactor.core.publisher.Mono; -import reactor.core.publisher.Operators; -import reactor.util.annotation.Nullable; - -/** - * Represents an empty publisher which only calls onSubscribe and onComplete and allows only a - * single subscriber. - * - * @see Reactive-Streams-Commons - */ -public final class UnicastMonoEmpty extends Mono implements Scannable { - - final Runnable onSubscribe; - - volatile int once; - - @SuppressWarnings("rawtypes") - static final AtomicIntegerFieldUpdater ONCE = - AtomicIntegerFieldUpdater.newUpdater(UnicastMonoEmpty.class, "once"); - - UnicastMonoEmpty(Runnable onSubscribe) { - this.onSubscribe = onSubscribe; - } - - @Override - public void subscribe(CoreSubscriber actual) { - if (once == 0 && ONCE.compareAndSet(this, 0, 1)) { - onSubscribe.run(); - Operators.complete(actual); - } else { - Operators.error( - actual, new IllegalStateException("UnicastMonoEmpty allows only a single Subscriber")); - } - } - - /** - * Returns a properly parametrized instance of this empty Publisher. - * - * @param the output type - * @return a properly parametrized instance of this empty Publisher - */ - @SuppressWarnings("unchecked") - public static Mono newInstance(Runnable onSubscribe) { - return (Mono) new UnicastMonoEmpty(onSubscribe); - } - - @Override - @Nullable - public Object block(Duration m) { - if (once == 0 && ONCE.compareAndSet(this, 0, 1)) { - onSubscribe.run(); - return null; - } else { - throw new IllegalStateException("UnicastMonoEmpty allows only a single Subscriber"); - } - } - - @Override - @Nullable - public Object block() { - if (once == 0 && ONCE.compareAndSet(this, 0, 1)) { - onSubscribe.run(); - return null; - } else { - throw new IllegalStateException("UnicastMonoEmpty allows only a single Subscriber"); - } - } - - @Override - public Object scanUnsafe(Attr key) { - return null; // no particular key to be represented, still useful in hooks - } - - @Override - public String stepName() { - return "source(UnicastMonoEmpty)"; - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/internal/UnicastMonoProcessor.java b/rsocket-core/src/main/java/io/rsocket/internal/UnicastMonoProcessor.java deleted file mode 100644 index c5b06e086..000000000 --- a/rsocket-core/src/main/java/io/rsocket/internal/UnicastMonoProcessor.java +++ /dev/null @@ -1,509 +0,0 @@ -/* - * Copyright 2015-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.internal; - -import io.rsocket.util.MonoLifecycleHandler; -import java.util.Objects; -import java.util.concurrent.CancellationException; -import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; -import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; -import org.reactivestreams.Processor; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; -import reactor.core.CoreSubscriber; -import reactor.core.Disposable; -import reactor.core.Exceptions; -import reactor.core.Scannable; -import reactor.core.publisher.Mono; -import reactor.core.publisher.Operators; -import reactor.core.publisher.SignalType; -import reactor.util.annotation.NonNull; -import reactor.util.annotation.Nullable; -import reactor.util.context.Context; - -public class UnicastMonoProcessor extends Mono - implements Processor, CoreSubscriber, Disposable, Subscription, Scannable { - - static final MonoLifecycleHandler DEFAULT_LIFECYCLE = new MonoLifecycleHandler() {}; - - /** - * Create a {@link UnicastMonoProcessor} that will eagerly request 1 on {@link - * #onSubscribe(Subscription)}, cache and emit the eventual result for a single subscriber. - * - * @param type of the expected value - * @return A {@link UnicastMonoProcessor}. - */ - @SuppressWarnings("unchecked") - public static UnicastMonoProcessor create() { - return new UnicastMonoProcessor(DEFAULT_LIFECYCLE); - } - - /** - * Create a {@link UnicastMonoProcessor} that will eagerly request 1 on {@link - * #onSubscribe(Subscription)}, cache and emit the eventual result for a single subscriber. - * - * @param lifecycleHandler lifecycle handler - * @param type of the expected value - * @return A {@link UnicastMonoProcessor}. - */ - public static UnicastMonoProcessor create(MonoLifecycleHandler lifecycleHandler) { - return new UnicastMonoProcessor<>(lifecycleHandler); - } - - /** Indicates this Subscription has no value and not requested yet. */ - static final int NO_SUBSCRIBER_NO_RESULT = 0; - /** Indicates this Subscription has no value and not requested yet. */ - static final int NO_SUBSCRIBER_HAS_RESULT = 1; - /** Indicates this Subscription has no value and not requested yet. */ - static final int NO_REQUEST_NO_RESULT = 4; - /** Indicates this Subscription has a value but not requested yet. */ - static final int NO_REQUEST_HAS_RESULT = 5; - /** Indicates this Subscription has been requested but there is no value yet. */ - static final int HAS_REQUEST_NO_RESULT = 6; - /** Indicates this Subscription has both request and value. */ - static final int HAS_REQUEST_HAS_RESULT = 7; - /** Indicates the Subscription has been cancelled. */ - static final int CANCELLED = 8; - - volatile int state; - - @SuppressWarnings("rawtypes") - static final AtomicIntegerFieldUpdater STATE = - AtomicIntegerFieldUpdater.newUpdater(UnicastMonoProcessor.class, "state"); - - volatile int once; - - @SuppressWarnings("rawtypes") - static final AtomicIntegerFieldUpdater ONCE = - AtomicIntegerFieldUpdater.newUpdater(UnicastMonoProcessor.class, "once"); - - volatile Subscription subscription; - - @SuppressWarnings("rawtypes") - static final AtomicReferenceFieldUpdater UPSTREAM = - AtomicReferenceFieldUpdater.newUpdater( - UnicastMonoProcessor.class, Subscription.class, "subscription"); - - CoreSubscriber actual; - boolean hasDownstream = false; - - Throwable error; - O value; - - final MonoLifecycleHandler lifecycleHandler; - - UnicastMonoProcessor(MonoLifecycleHandler lifecycleHandler) { - this.lifecycleHandler = lifecycleHandler; - } - - @Override - @NonNull - public Context currentContext() { - final CoreSubscriber a = this.actual; - return a != null ? a.currentContext() : Context.empty(); - } - - @Override - public final void onSubscribe(Subscription subscription) { - if (Operators.setOnce(UPSTREAM, this, subscription)) { - subscription.request(Long.MAX_VALUE); - } - } - - @Override - public final void onComplete() { - onNext(null); - } - - @Override - public final void onError(Throwable cause) { - Objects.requireNonNull(cause, "onError cannot be null"); - - if (UPSTREAM.getAndSet(this, Operators.cancelledSubscription()) - == Operators.cancelledSubscription()) { - Operators.onErrorDropped(cause, currentContext()); - return; - } - - complete(cause); - } - - @Override - public final void onNext(@Nullable O value) { - final Subscription s; - if ((s = UPSTREAM.getAndSet(this, Operators.cancelledSubscription())) - == Operators.cancelledSubscription()) { - if (value != null) { - Operators.onNextDropped(value, currentContext()); - } - return; - } - - if (value == null) { - complete(); - } else { - if (s != null) { - s.cancel(); - } - - complete(value); - } - } - - /** - * Tries to emit the value and complete the underlying subscriber or stores the value away until - * there is a request for it. - * - *

    Make sure this method is called at most once - * - * @param v the value to emit - */ - private void complete(O v) { - for (; ; ) { - int state = this.state; - - // if state is >= HAS_CANCELLED or bit zero is set (*_HAS_VALUE) case, return - if ((state & ~HAS_REQUEST_NO_RESULT) != 0) { - this.value = null; - Operators.onDiscard(v, currentContext()); - return; - } - - if (state == HAS_REQUEST_NO_RESULT) { - if (STATE.compareAndSet(this, HAS_REQUEST_NO_RESULT, HAS_REQUEST_HAS_RESULT)) { - final Subscriber a = actual; - hasDownstream = false; - value = null; - lifecycleHandler.doOnTerminal(SignalType.ON_COMPLETE, v, null); - a.onNext(v); - a.onComplete(); - return; - } - } - setValue(v); - if (state == NO_REQUEST_NO_RESULT - && STATE.compareAndSet(this, NO_REQUEST_NO_RESULT, NO_REQUEST_HAS_RESULT)) { - return; - } - if (state == NO_SUBSCRIBER_NO_RESULT - && STATE.compareAndSet(this, NO_SUBSCRIBER_NO_RESULT, NO_SUBSCRIBER_HAS_RESULT)) { - return; - } - } - } - - /** - * Tries to emit completion the underlying subscriber - * - *

    Make sure this method is called at most once - */ - private void complete() { - for (; ; ) { - int state = this.state; - - // if state is >= HAS_CANCELLED or bit zero is set (*_HAS_VALUE) case, return - if ((state & ~HAS_REQUEST_NO_RESULT) != 0) { - return; - } - - if (state == HAS_REQUEST_NO_RESULT || state == NO_REQUEST_NO_RESULT) { - if (STATE.compareAndSet(this, state, HAS_REQUEST_HAS_RESULT)) { - final Subscriber a = actual; - hasDownstream = false; - lifecycleHandler.doOnTerminal(SignalType.ON_COMPLETE, null, null); - a.onComplete(); - return; - } - } - if (state == NO_SUBSCRIBER_NO_RESULT - && STATE.compareAndSet(this, NO_SUBSCRIBER_NO_RESULT, NO_SUBSCRIBER_HAS_RESULT)) { - return; - } - } - } - - /** - * Tries to emit error the underlying subscriber or stores the value away until there is a request - * for it. - * - *

    Make sure this method is called at most once - * - * @param e the error to emit - */ - private void complete(Throwable e) { - for (; ; ) { - int state = this.state; - - // if state is >= HAS_CANCELLED or bit zero is set (*_HAS_VALUE) case, return - if ((state & ~HAS_REQUEST_NO_RESULT) != 0) { - return; - } - - setError(e); - if (state == HAS_REQUEST_NO_RESULT || state == NO_REQUEST_NO_RESULT) { - if (STATE.compareAndSet(this, state, HAS_REQUEST_HAS_RESULT)) { - final Subscriber a = actual; - hasDownstream = false; - lifecycleHandler.doOnTerminal(SignalType.ON_ERROR, null, e); - a.onError(e); - return; - } - } - if (state == NO_SUBSCRIBER_NO_RESULT - && STATE.compareAndSet(this, NO_SUBSCRIBER_NO_RESULT, NO_SUBSCRIBER_HAS_RESULT)) { - return; - } - } - } - - @Override - public void subscribe(CoreSubscriber actual) { - Objects.requireNonNull(actual, "subscribe"); - - if (once == 0 && ONCE.compareAndSet(this, 0, 1)) { - final MonoLifecycleHandler lh = this.lifecycleHandler; - - lh.doOnSubscribe(); - - this.hasDownstream = true; - this.actual = actual; - - int state = this.state; - - // possible states within the racing between [onNext / onComplete / onError / dispose] and - // setting subscriber - // are NO_SUBSCRIBER_[NO_RESULT or HAS_RESULT] - if (state == NO_SUBSCRIBER_NO_RESULT) { - if (STATE.compareAndSet(this, NO_SUBSCRIBER_NO_RESULT, NO_REQUEST_NO_RESULT)) { - state = NO_REQUEST_NO_RESULT; - } else { - // the possible false position is racing with [onNext / onError / onComplete / dispose] - // which are going to put the state in the NO_REQUEST_HAS_RESULT - STATE.set(this, NO_REQUEST_HAS_RESULT); - state = NO_REQUEST_HAS_RESULT; - } - } else { - STATE.set(this, NO_REQUEST_HAS_RESULT); - state = NO_REQUEST_HAS_RESULT; - } - - // check if state is with a result then there is a chance of immediate termination if there is - // no value - // e.g. [onError / onComplete / dispose] only - if (state == NO_REQUEST_HAS_RESULT && this.value == null) { - this.hasDownstream = false; - Throwable e = this.error; - // barrier to flush changes - STATE.set(this, HAS_REQUEST_HAS_RESULT); - if (e == null) { - lh.doOnTerminal(SignalType.ON_COMPLETE, null, null); - Operators.complete(actual); - } else { - lh.doOnTerminal(SignalType.ON_ERROR, null, e); - Operators.error(actual, e); - } - return; - } - - // call onSubscribe if has value in the result or no result delivered so far - actual.onSubscribe(this); - } else { - Operators.error( - actual, - new IllegalStateException("UnicastMonoProcessor allows only a single Subscriber")); - } - } - - @Override - public final void request(long n) { - if (Operators.validate(n)) { - for (; ; ) { - int s = state; - // if the any bits 1-31 are set, we are either in fusion mode (FUSED_*) - // or request has been called (HAS_REQUEST_*) - if ((s & ~NO_REQUEST_HAS_RESULT) != 0) { - return; - } - if (s == NO_REQUEST_HAS_RESULT) { - if (STATE.compareAndSet(this, NO_REQUEST_HAS_RESULT, HAS_REQUEST_HAS_RESULT)) { - final Subscriber a = actual; - final O v = value; - hasDownstream = false; - value = null; - lifecycleHandler.doOnTerminal(SignalType.ON_COMPLETE, v, null); - a.onNext(v); - a.onComplete(); - return; - } - } - if (STATE.compareAndSet(this, NO_REQUEST_NO_RESULT, HAS_REQUEST_NO_RESULT)) { - return; - } - } - } - } - - @Override - public final void cancel() { - if (STATE.getAndSet(this, CANCELLED) <= HAS_REQUEST_NO_RESULT) { - Operators.onDiscard(value, currentContext()); - value = null; - hasDownstream = false; - lifecycleHandler.doOnTerminal(SignalType.CANCEL, null, null); - final Subscription s = UPSTREAM.getAndSet(this, Operators.cancelledSubscription()); - if (s != null && s != Operators.cancelledSubscription()) { - s.cancel(); - } - } - } - - @Override - public void dispose() { - final Subscription s = UPSTREAM.getAndSet(this, Operators.cancelledSubscription()); - if (s == Operators.cancelledSubscription()) { - return; - } - - if (s != null) { - s.cancel(); - } - - complete(new CancellationException("Disposed")); - } - - /** - * Returns the value that completed this {@link UnicastMonoProcessor}. Returns {@code null} if the - * {@link UnicastMonoProcessor} has not been completed. If the {@link UnicastMonoProcessor} is - * completed with an error a RuntimeException that wraps the error is thrown. - * - * @return the value that completed the {@link UnicastMonoProcessor}, or {@code null} if it has - * not been completed - * @throws RuntimeException if the {@link UnicastMonoProcessor} was completed with an error - */ - @Nullable - public O peek() { - if (isCancelled()) { - return null; - } - - if (value != null) { - return value; - } - - if (error != null) { - RuntimeException re = Exceptions.propagate(error); - re = Exceptions.addSuppressed(re, new Exception("Mono#peek terminated with an error")); - throw re; - } - - return null; - } - - /** - * Set the value internally, without impacting request tracking state. - * - * @param value the new value. - * @see #complete(Object) - */ - private void setValue(O value) { - this.value = value; - } - - /** - * Set the error internally, without impacting request tracking state. - * - * @param throwable the error. - * @see #complete(Object) - */ - private void setError(Throwable throwable) { - this.error = throwable; - } - - /** - * Return the produced {@link Throwable} error if any or null - * - * @return the produced {@link Throwable} error if any or null - */ - @Nullable - public final Throwable getError() { - return isDisposed() ? error : null; - } - - /** - * Indicates whether this {@code UnicastMonoProcessor} has been completed with an error. - * - * @return {@code true} if this {@code UnicastMonoProcessor} was completed with an error, {@code - * false} otherwise. - */ - public final boolean isError() { - return getError() != null; - } - - /** - * Indicates whether this {@code UnicastMonoProcessor} has been interrupted via cancellation. - * - * @return {@code true} if this {@code UnicastMonoProcessor} is cancelled, {@code false} - * otherwise. - */ - public boolean isCancelled() { - return state == CANCELLED; - } - - public final boolean isTerminated() { - int state = this.state; - return (state < CANCELLED && state % 2 == 1); - } - - @Override - public boolean isDisposed() { - int state = this.state; - return state == CANCELLED || (state < CANCELLED && state % 2 == 1); - } - - @Override - @Nullable - public Object scanUnsafe(Attr key) { - // touch guard - int state = this.state; - - if (key == Attr.TERMINATED) { - return (state < CANCELLED && state % 2 == 1); - } - if (key == Attr.PARENT) { - return subscription; - } - if (key == Attr.ERROR) { - return error; - } - if (key == Attr.PREFETCH) { - return Integer.MAX_VALUE; - } - if (key == Attr.CANCELLED) { - return state == CANCELLED; - } - return null; - } - - /** - * Return true if any {@link Subscriber} is actively subscribed - * - * @return true if any {@link Subscriber} is actively subscribed - */ - public final boolean hasDownstream() { - return state > NO_SUBSCRIBER_HAS_RESULT && hasDownstream; - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/util/DisposableUtils.java b/rsocket-core/src/main/java/io/rsocket/util/DisposableUtils.java deleted file mode 100644 index c87a08220..000000000 --- a/rsocket-core/src/main/java/io/rsocket/util/DisposableUtils.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.rsocket.util; - -import java.util.Arrays; -import reactor.core.Disposable; - -/** Utilities for working with the {@link Disposable} type. */ -public final class DisposableUtils { - - private DisposableUtils() {} - - /** - * Calls the {@link Disposable#dispose()} method if the instance is not null. If any exceptions - * are thrown during disposal, suppress them. - * - * @param disposables the {@link Disposable}s to dispose - */ - public static void disposeQuietly(Disposable... disposables) { - Arrays.stream(disposables) - .forEach( - disposable -> { - try { - if (disposable != null) { - disposable.dispose(); - } - } catch (RuntimeException e) { - // Suppress any exceptions during disposal - } - }); - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/util/DuplexConnectionProxy.java b/rsocket-core/src/main/java/io/rsocket/util/DuplexConnectionProxy.java deleted file mode 100644 index 2f5d1da4b..000000000 --- a/rsocket-core/src/main/java/io/rsocket/util/DuplexConnectionProxy.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2015-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.util; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.rsocket.DuplexConnection; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -public class DuplexConnectionProxy implements DuplexConnection { - private final DuplexConnection connection; - - public DuplexConnectionProxy(DuplexConnection connection) { - this.connection = connection; - } - - @Override - public Mono send(Publisher frames) { - return connection.send(frames); - } - - @Override - public Flux receive() { - return connection.receive(); - } - - @Override - public double availability() { - return connection.availability(); - } - - @Override - public ByteBufAllocator alloc() { - return connection.alloc(); - } - - @Override - public Mono onClose() { - return connection.onClose(); - } - - @Override - public void dispose() { - connection.dispose(); - } - - @Override - public boolean isDisposed() { - return connection.isDisposed(); - } - - public DuplexConnection delegate() { - return connection; - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/util/Function3.java b/rsocket-core/src/main/java/io/rsocket/util/Function3.java deleted file mode 100644 index 5783665ae..000000000 --- a/rsocket-core/src/main/java/io/rsocket/util/Function3.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2015-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.util; - -public interface Function3 { - - R apply(T t, U u, V v); -} diff --git a/rsocket-core/src/main/java/io/rsocket/util/MonoLifecycleHandler.java b/rsocket-core/src/main/java/io/rsocket/util/MonoLifecycleHandler.java deleted file mode 100644 index 4d47c03d6..000000000 --- a/rsocket-core/src/main/java/io/rsocket/util/MonoLifecycleHandler.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.rsocket.util; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import reactor.core.publisher.SignalType; - -public interface MonoLifecycleHandler { - - default void doOnSubscribe() {} - - /** - * Handler which is invoked on the terminal activity within a given Monoø - * - * @param signalType a type of signal which explain what happened - * @param element an carried element. May not be present if stream is empty or cancelled or - * errored - * @param e an carried error. May not be present if stream is cancelled or completed successfully - */ - default void doOnTerminal( - @Nonnull SignalType signalType, @Nullable T element, @Nullable Throwable e) {} -} diff --git a/rsocket-core/src/main/java/io/rsocket/util/MultiSubscriberRSocket.java b/rsocket-core/src/main/java/io/rsocket/util/MultiSubscriberRSocket.java deleted file mode 100644 index c2db6c238..000000000 --- a/rsocket-core/src/main/java/io/rsocket/util/MultiSubscriberRSocket.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2015-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.util; - -import io.rsocket.Payload; -import io.rsocket.RSocket; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -public class MultiSubscriberRSocket extends RSocketProxy { - public MultiSubscriberRSocket(RSocket source) { - super(source); - } - - @Override - public Mono fireAndForget(Payload payload) { - return Mono.defer(() -> super.fireAndForget(payload)); - } - - @Override - public Mono requestResponse(Payload payload) { - return Mono.defer(() -> super.requestResponse(payload)); - } - - @Override - public Flux requestStream(Payload payload) { - return Flux.defer(() -> super.requestStream(payload)); - } - - @Override - public Flux requestChannel(Publisher payloads) { - return Flux.defer(() -> super.requestChannel(payloads)); - } - - @Override - public Mono metadataPush(Payload payload) { - return Mono.defer(() -> super.metadataPush(payload)); - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/util/OnceConsumer.java b/rsocket-core/src/main/java/io/rsocket/util/OnceConsumer.java deleted file mode 100644 index af4c038cc..000000000 --- a/rsocket-core/src/main/java/io/rsocket/util/OnceConsumer.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2015-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.util; - -import java.util.function.Consumer; - -public abstract class OnceConsumer implements Consumer { - private boolean isFirst = true; - - @Override - public final void accept(T t) { - if (isFirst) { - isFirst = false; - acceptOnce(t); - } - } - - public abstract void acceptOnce(T t); -} diff --git a/rsocket-core/src/main/java/io/rsocket/util/RecyclerFactory.java b/rsocket-core/src/main/java/io/rsocket/util/RecyclerFactory.java deleted file mode 100644 index 30385195c..000000000 --- a/rsocket-core/src/main/java/io/rsocket/util/RecyclerFactory.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.util; - -import io.netty.util.Recycler; -import io.netty.util.Recycler.Handle; -import java.util.Objects; -import java.util.function.Function; - -/** A factory for creating {@link Recycler}s. */ -public final class RecyclerFactory { - - /** - * Creates a new {@link Recycler}. - * - * @param newObjectCreator the {@link Function} to create a new object - * @param the type being recycled. - * @return the {@link Recycler} - * @throws NullPointerException if {@code newObjectCreator} is {@code null} - */ - public static Recycler createRecycler(Function, T> newObjectCreator) { - Objects.requireNonNull(newObjectCreator, "newObjectCreator must not be null"); - - return new Recycler() { - - @Override - protected T newObject(Handle handle) { - return newObjectCreator.apply(handle); - } - }; - } -} diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java index 01cf99e26..fc87fc721 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java @@ -26,7 +26,6 @@ import io.rsocket.lease.RequesterLeaseHandler; import io.rsocket.test.util.TestDuplexConnection; import io.rsocket.util.DefaultPayload; -import io.rsocket.util.MultiSubscriberRSocket; import java.time.Duration; import java.util.Arrays; import java.util.Collection; @@ -74,23 +73,6 @@ void setUp() { RequesterLeaseHandler.None); } - @ParameterizedTest - @MethodSource("allInteractions") - void multiSubscriber(Function> interaction) { - RSocket multiSubsRSocket = new MultiSubscriberRSocket(rSocketRequester); - Flux response = Flux.from(interaction.apply(multiSubsRSocket)); - StepVerifier.withVirtualTime(() -> response.take(Duration.ofMillis(10))) - .thenAwait(Duration.ofMillis(10)) - .expectComplete() - .verify(Duration.ofSeconds(5)); - StepVerifier.withVirtualTime(() -> response.take(Duration.ofMillis(10))) - .thenAwait(Duration.ofMillis(10)) - .expectComplete() - .verify(Duration.ofSeconds(5)); - - Assertions.assertThat(requestFramesCount(connection.getSent())).isEqualTo(2); - } - @ParameterizedTest @MethodSource("allInteractions") void singleSubscriber(Function> interaction) { diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java index 3b62bc437..b6067cdeb 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java @@ -25,12 +25,10 @@ import static io.rsocket.frame.FrameType.REQUEST_STREAM; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; @@ -61,7 +59,6 @@ import io.rsocket.util.ByteBufPayload; import io.rsocket.util.DefaultPayload; import io.rsocket.util.EmptyPayload; -import io.rsocket.util.MultiSubscriberRSocket; import java.time.Duration; import java.util.ArrayList; import java.util.Iterator; @@ -249,21 +246,6 @@ public void testRequestReplyErrorOnSend() { // verify(responseSub).onError(any(RuntimeException.class)); } - @Test - @Timeout(2_000) - public void testLazyRequestResponse() { - Publisher response = - new MultiSubscriberRSocket(rule.socket).requestResponse(EmptyPayload.INSTANCE); - int streamId = sendRequestResponse(response); - Assertions.assertThat(rule.connection.getSent()).hasSize(1).allMatch(ReferenceCounted::release); - rule.assertHasNoLeaks(); - rule.connection.clearSendReceiveBuffers(); - int streamId2 = sendRequestResponse(response); - assertThat("Stream ID reused.", streamId2, not(equalTo(streamId))); - Assertions.assertThat(rule.connection.getSent()).hasSize(1).allMatch(ReferenceCounted::release); - rule.assertHasNoLeaks(); - } - @Test @Timeout(2_000) public void testChannelRequestCancellation() { diff --git a/rsocket-core/src/test/java/io/rsocket/internal/LimitableRequestPublisherTest.java b/rsocket-core/src/test/java/io/rsocket/internal/LimitableRequestPublisherTest.java deleted file mode 100644 index 8c51c123e..000000000 --- a/rsocket-core/src/test/java/io/rsocket/internal/LimitableRequestPublisherTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.rsocket.internal; - -import java.util.ArrayDeque; -import java.util.Queue; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.RepeatedTest; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.DirectProcessor; -import reactor.test.util.RaceTestUtils; - -class LimitableRequestPublisherTest { - - @Test - @RepeatedTest(2) - public void requestLimitRacingTest() throws InterruptedException { - Queue requests = new ArrayDeque<>(10000); - LimitableRequestPublisher limitableRequestPublisher = - LimitableRequestPublisher.wrap(DirectProcessor.create().doOnRequest(requests::add), 0); - - Runnable request1 = () -> limitableRequestPublisher.request(1); - Runnable request2 = () -> limitableRequestPublisher.increaseInternalLimit(2); - - limitableRequestPublisher.subscribe(); - - for (int i = 0; i < 10000; i++) { - RaceTestUtils.race(request1, request2); - } - - Thread.sleep(1000); - - Assertions.assertThat(requests.stream().mapToLong(l -> l).sum()).isEqualTo(10000); - } -} diff --git a/rsocket-core/src/test/java/io/rsocket/internal/SwitchTransformFluxTest.java b/rsocket-core/src/test/java/io/rsocket/internal/SwitchTransformFluxTest.java deleted file mode 100644 index 07fbf695f..000000000 --- a/rsocket-core/src/test/java/io/rsocket/internal/SwitchTransformFluxTest.java +++ /dev/null @@ -1,446 +0,0 @@ -package io.rsocket.internal; - -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasItem; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; -import org.junit.Assert; -import org.junit.Assume; -import org.junit.Ignore; -import org.junit.Test; -import reactor.core.CoreSubscriber; -import reactor.core.publisher.Flux; -import reactor.core.scheduler.Schedulers; -import reactor.test.StepVerifier; -import reactor.test.publisher.TestPublisher; -import reactor.test.util.RaceTestUtils; -import reactor.util.context.Context; - -@Ignore -public class SwitchTransformFluxTest { - - @Test - public void shouldBeAbleToCancelSubscription() throws InterruptedException { - for (int j = 0; j < 10; j++) { - ArrayList capturedElements = new ArrayList<>(); - ArrayList capturedCompletions = new ArrayList<>(); - for (int i = 0; i < 1000; i++) { - TestPublisher publisher = TestPublisher.createCold(); - AtomicLong captureElement = new AtomicLong(0L); - AtomicBoolean captureCompletion = new AtomicBoolean(false); - AtomicLong requested = new AtomicLong(); - CountDownLatch latch = new CountDownLatch(1); - Flux switchTransformed = - publisher - .flux() - .doOnRequest(requested::addAndGet) - .doOnCancel(latch::countDown) - .transform( - flux -> new SwitchTransformFlux<>(flux, (first, innerFlux) -> innerFlux)); - - publisher.next(1L); - - switchTransformed.subscribe( - captureElement::set, - __ -> {}, - () -> captureCompletion.set(true), - s -> - new Thread( - () -> - RaceTestUtils.race( - publisher::complete, - () -> - RaceTestUtils.race( - s::cancel, () -> s.request(1), Schedulers.parallel()), - Schedulers.parallel())) - .start()); - - Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); - Assert.assertEquals(requested.get(), 1L); - capturedElements.add(captureElement.get()); - capturedCompletions.add(captureCompletion.get()); - } - - Assume.assumeThat(capturedElements, hasItem(equalTo(0L))); - Assume.assumeThat(capturedCompletions, hasItem(equalTo(false))); - } - } - - @Test - public void shouldRequestExpectedAmountOfElements() throws InterruptedException { - TestPublisher publisher = TestPublisher.createCold(); - AtomicLong capture = new AtomicLong(); - AtomicLong requested = new AtomicLong(); - CountDownLatch latch = new CountDownLatch(1); - Flux switchTransformed = - publisher - .flux() - .doOnRequest(requested::addAndGet) - .transform(flux -> new SwitchTransformFlux<>(flux, (first, innerFlux) -> innerFlux)); - - publisher.next(1L); - - switchTransformed.subscribe( - capture::set, - __ -> {}, - latch::countDown, - s -> { - for (int i = 0; i < 10000; i++) { - RaceTestUtils.race(() -> s.request(1), () -> s.request(1)); - } - RaceTestUtils.race(publisher::complete, publisher::complete); - }); - - latch.await(5, TimeUnit.SECONDS); - - Assert.assertEquals(capture.get(), 1L); - Assert.assertEquals(requested.get(), 20000L); - } - - @Test - public void shouldReturnCorrectContextOnEmptySource() { - Flux switchTransformed = - Flux.empty() - .transform(flux -> new SwitchTransformFlux<>(flux, (first, innerFlux) -> innerFlux)) - .subscriberContext(Context.of("a", "c")) - .subscriberContext(Context.of("c", "d")); - - StepVerifier.create(switchTransformed, 0) - .expectSubscription() - .thenRequest(1) - .expectAccessibleContext() - .contains("a", "c") - .contains("c", "d") - .then() - .expectComplete() - .verify(); - } - - @Test - public void shouldNotFailOnIncorrectPublisherBehavior() { - TestPublisher publisher = - TestPublisher.createNoncompliant(TestPublisher.Violation.CLEANUP_ON_TERMINATE); - Flux switchTransformed = - publisher - .flux() - .transform( - flux -> - new SwitchTransformFlux<>( - flux, - (first, innerFlux) -> innerFlux.subscriberContext(Context.of("a", "b")))); - - StepVerifier.create( - new Flux() { - @Override - public void subscribe(CoreSubscriber actual) { - switchTransformed.subscribe(actual); - publisher.next(1L); - } - }, - 0) - .thenRequest(1) - .expectNext(1L) - .thenRequest(1) - .then(() -> publisher.next(2L)) - .expectNext(2L) - .then(() -> publisher.error(new RuntimeException())) - .then(() -> publisher.error(new RuntimeException())) - .then(() -> publisher.error(new RuntimeException())) - .then(() -> publisher.error(new RuntimeException())) - .expectError() - .verifyThenAssertThat() - .hasDroppedErrors(3) - .tookLessThan(Duration.ofSeconds(10)); - - publisher.assertWasRequested(); - publisher.assertNoRequestOverflow(); - } - - // @Test - // public void shouldNotFailOnIncorrePu - - @Test - public void shouldBeAbleToAccessUpstreamContext() { - TestPublisher publisher = TestPublisher.createCold(); - - Flux switchTransformed = - publisher - .flux() - .transform( - flux -> - new SwitchTransformFlux<>( - flux, - (first, innerFlux) -> - innerFlux.map(String::valueOf).subscriberContext(Context.of("a", "b")))) - .subscriberContext(Context.of("a", "c")) - .subscriberContext(Context.of("c", "d")); - - publisher.next(1L); - - StepVerifier.create(switchTransformed, 0) - .thenRequest(1) - .expectNext("1") - .thenRequest(1) - .then(() -> publisher.next(2L)) - .expectNext("2") - .expectAccessibleContext() - .contains("a", "b") - .contains("c", "d") - .then() - .then(publisher::complete) - .expectComplete() - .verify(Duration.ofSeconds(10)); - - publisher.assertWasRequested(); - publisher.assertNoRequestOverflow(); - } - - @Test - public void shouldNotHangWhenOneElementUpstream() { - TestPublisher publisher = TestPublisher.createCold(); - - Flux switchTransformed = - publisher - .flux() - .transform( - flux -> - new SwitchTransformFlux<>( - flux, - (first, innerFlux) -> - innerFlux.map(String::valueOf).subscriberContext(Context.of("a", "b")))) - .subscriberContext(Context.of("a", "c")) - .subscriberContext(Context.of("c", "d")); - - publisher.next(1L); - publisher.complete(); - - StepVerifier.create(switchTransformed, 0) - .thenRequest(1) - .expectNext("1") - .expectComplete() - .verify(Duration.ofSeconds(10)); - - publisher.assertWasRequested(); - publisher.assertNoRequestOverflow(); - } - - @Test - public void backpressureTest() { - TestPublisher publisher = TestPublisher.createCold(); - AtomicLong requested = new AtomicLong(); - - Flux switchTransformed = - publisher - .flux() - .doOnRequest(requested::addAndGet) - .transform( - flux -> - new SwitchTransformFlux<>( - flux, (first, innerFlux) -> innerFlux.map(String::valueOf))); - - publisher.next(1L); - - StepVerifier.create(switchTransformed, 0) - .thenRequest(1) - .expectNext("1") - .thenRequest(1) - .then(() -> publisher.next(2L)) - .expectNext("2") - .then(publisher::complete) - .expectComplete() - .verify(Duration.ofSeconds(10)); - - publisher.assertWasRequested(); - publisher.assertNoRequestOverflow(); - - Assert.assertEquals(2L, requested.get()); - } - - @Test - public void backpressureConditionalTest() { - Flux publisher = Flux.range(0, 10000); - AtomicLong requested = new AtomicLong(); - - Flux switchTransformed = - publisher - .doOnRequest(requested::addAndGet) - .transform( - flux -> - new SwitchTransformFlux<>( - flux, (first, innerFlux) -> innerFlux.map(String::valueOf))) - .filter(e -> false); - - StepVerifier.create(switchTransformed, 0) - .thenRequest(1) - .expectComplete() - .verify(Duration.ofSeconds(10)); - - Assert.assertEquals(2L, requested.get()); - } - - @Test - public void backpressureHiddenConditionalTest() { - Flux publisher = Flux.range(0, 10000); - AtomicLong requested = new AtomicLong(); - - Flux switchTransformed = - publisher - .doOnRequest(requested::addAndGet) - .transform( - flux -> - new SwitchTransformFlux<>( - flux, (first, innerFlux) -> innerFlux.map(String::valueOf).hide())) - .filter(e -> false); - - StepVerifier.create(switchTransformed, 0) - .thenRequest(1) - .expectComplete() - .verify(Duration.ofSeconds(10)); - - Assert.assertEquals(10001L, requested.get()); - } - - @Test - public void backpressureDrawbackOnConditionalInTransformTest() { - Flux publisher = Flux.range(0, 10000); - AtomicLong requested = new AtomicLong(); - - Flux switchTransformed = - publisher - .doOnRequest(requested::addAndGet) - .transform( - flux -> - new SwitchTransformFlux<>( - flux, - (first, innerFlux) -> innerFlux.map(String::valueOf).filter(e -> false))); - - StepVerifier.create(switchTransformed, 0) - .thenRequest(1) - .expectComplete() - .verify(Duration.ofSeconds(10)); - - Assert.assertEquals(10001L, requested.get()); - } - - @Test - public void shouldErrorOnOverflowTest() { - TestPublisher publisher = TestPublisher.createCold(); - - Flux switchTransformed = - publisher - .flux() - .transform( - flux -> - new SwitchTransformFlux<>( - flux, (first, innerFlux) -> innerFlux.map(String::valueOf))); - - publisher.next(1L); - - StepVerifier.create(switchTransformed, 0) - .thenRequest(1) - .expectNext("1") - .then(() -> publisher.next(2L)) - .expectError() - .verify(Duration.ofSeconds(10)); - - publisher.assertWasRequested(); - publisher.assertNoRequestOverflow(); - } - - @Test - public void shouldPropagateonCompleteCorrectly() { - Flux switchTransformed = - Flux.empty() - .transform( - flux -> - new SwitchTransformFlux<>( - flux, (first, innerFlux) -> innerFlux.map(String::valueOf))); - - StepVerifier.create(switchTransformed).expectComplete().verify(Duration.ofSeconds(10)); - } - - @Test - public void shouldPropagateErrorCorrectly() { - Flux switchTransformed = - Flux.error(new RuntimeException("hello")) - .transform( - flux -> - new SwitchTransformFlux<>( - flux, (first, innerFlux) -> innerFlux.map(String::valueOf))); - - StepVerifier.create(switchTransformed) - .expectErrorMessage("hello") - .verify(Duration.ofSeconds(10)); - } - - @Test - public void shouldBeAbleToBeCancelledProperly() { - TestPublisher publisher = TestPublisher.createCold(); - Flux switchTransformed = - publisher - .flux() - .transform( - flux -> - new SwitchTransformFlux<>( - flux, (first, innerFlux) -> innerFlux.map(String::valueOf))); - - publisher.next(1); - - StepVerifier.create(switchTransformed, 0).thenCancel().verify(Duration.ofSeconds(10)); - - publisher.assertCancelled(); - publisher.assertWasRequested(); - } - - @Test - public void shouldBeAbleToCatchDiscardedElement() { - TestPublisher publisher = TestPublisher.createCold(); - Integer[] discarded = new Integer[1]; - Flux switchTransformed = - publisher - .flux() - .transform( - flux -> - new SwitchTransformFlux<>( - flux, (first, innerFlux) -> innerFlux.map(String::valueOf))) - .doOnDiscard(Integer.class, e -> discarded[0] = e); - - publisher.next(1); - - StepVerifier.create(switchTransformed, 0).thenCancel().verify(Duration.ofSeconds(10)); - - publisher.assertCancelled(); - publisher.assertWasRequested(); - - Assert.assertArrayEquals(new Integer[] {1}, discarded); - } - - @Test - public void shouldBeAbleToCatchDiscardedElementInCaseOfConditional() { - TestPublisher publisher = TestPublisher.createCold(); - Integer[] discarded = new Integer[1]; - Flux switchTransformed = - publisher - .flux() - .transform( - flux -> - new SwitchTransformFlux<>( - flux, (first, innerFlux) -> innerFlux.map(String::valueOf))) - .filter(t -> true) - .doOnDiscard(Integer.class, e -> discarded[0] = e); - - publisher.next(1); - - StepVerifier.create(switchTransformed, 0).thenCancel().verify(Duration.ofSeconds(10)); - - publisher.assertCancelled(); - publisher.assertWasRequested(); - - Assert.assertArrayEquals(new Integer[] {1}, discarded); - } -} diff --git a/rsocket-core/src/test/java/io/rsocket/internal/UnicastMonoEmptyTest.java b/rsocket-core/src/test/java/io/rsocket/internal/UnicastMonoEmptyTest.java deleted file mode 100644 index 76bb953a4..000000000 --- a/rsocket-core/src/test/java/io/rsocket/internal/UnicastMonoEmptyTest.java +++ /dev/null @@ -1,97 +0,0 @@ -package io.rsocket.internal; - -import static io.rsocket.internal.SchedulerUtils.warmup; - -import java.time.Duration; -import java.util.concurrent.atomic.AtomicInteger; -import org.assertj.core.api.Assertions; -import org.junit.Test; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; -import reactor.test.util.RaceTestUtils; - -public class UnicastMonoEmptyTest { - - @Test - public void shouldSupportASingleSubscriber() throws InterruptedException { - warmup(Schedulers.single()); - - for (int i = 0; i < 10000; i++) { - AtomicInteger times = new AtomicInteger(); - Mono unicastMono = UnicastMonoEmpty.newInstance(times::incrementAndGet); - - Assertions.assertThatThrownBy( - () -> - RaceTestUtils.race( - unicastMono::subscribe, unicastMono::subscribe, Schedulers.single())) - .hasCause(new IllegalStateException("UnicastMonoEmpty allows only a single Subscriber")); - Assertions.assertThat(times.get()).isEqualTo(1); - } - } - - @Test - public void shouldSupportASingleBlock() throws InterruptedException { - warmup(Schedulers.single()); - - for (int i = 0; i < 10000; i++) { - AtomicInteger times = new AtomicInteger(); - Mono unicastMono = UnicastMonoEmpty.newInstance(times::incrementAndGet); - - Assertions.assertThatThrownBy( - () -> RaceTestUtils.race(unicastMono::block, unicastMono::block, Schedulers.single())) - .hasMessage("UnicastMonoEmpty allows only a single Subscriber"); - Assertions.assertThat(times.get()).isEqualTo(1); - } - } - - @Test - public void shouldSupportASingleBlockWithTimeout() throws InterruptedException { - warmup(Schedulers.single()); - - for (int i = 0; i < 10000; i++) { - AtomicInteger times = new AtomicInteger(); - Mono unicastMono = UnicastMonoEmpty.newInstance(times::incrementAndGet); - - Assertions.assertThatThrownBy( - () -> - RaceTestUtils.race( - () -> unicastMono.block(Duration.ofMinutes(1)), - () -> unicastMono.block(Duration.ofMinutes(1)), - Schedulers.single())) - .hasMessage("UnicastMonoEmpty allows only a single Subscriber"); - Assertions.assertThat(times.get()).isEqualTo(1); - } - } - - @Test - public void shouldSupportASingleSubscribeOrBlock() throws InterruptedException { - warmup(Schedulers.parallel()); - - for (int i = 0; i < 10000; i++) { - AtomicInteger times = new AtomicInteger(); - Mono unicastMono = UnicastMonoEmpty.newInstance(times::incrementAndGet); - - Assertions.assertThatThrownBy( - () -> - RaceTestUtils.race( - unicastMono::subscribe, - () -> - RaceTestUtils.race( - unicastMono::block, - () -> unicastMono.block(Duration.ofMinutes(1)), - Schedulers.parallel()), - Schedulers.parallel())) - .matches( - t -> { - Assertions.assertThat(t.getSuppressed()).hasSize(2); - Assertions.assertThat(t.getSuppressed()[0]) - .hasMessageContaining("UnicastMonoEmpty allows only a single Subscriber"); - Assertions.assertThat(t.getSuppressed()[1]) - .hasMessageContaining("UnicastMonoEmpty allows only a single Subscriber"); - - return true; - }); - Assertions.assertThat(times.get()).isEqualTo(1); - } - } -} diff --git a/rsocket-core/src/test/java/io/rsocket/internal/UnicastMonoProcessorTest.java b/rsocket-core/src/test/java/io/rsocket/internal/UnicastMonoProcessorTest.java deleted file mode 100644 index a836dd509..000000000 --- a/rsocket-core/src/test/java/io/rsocket/internal/UnicastMonoProcessorTest.java +++ /dev/null @@ -1,1780 +0,0 @@ -/* - * Copyright 2015-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.rsocket.internal; - -import static io.rsocket.internal.SchedulerUtils.warmup; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import io.rsocket.internal.subscriber.AssertSubscriber; -import io.rsocket.util.MonoLifecycleHandler; -import java.lang.ref.WeakReference; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Date; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import org.assertj.core.api.Assertions; -import org.junit.Test; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; -import reactor.core.Scannable; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Hooks; -import reactor.core.publisher.Mono; -import reactor.core.publisher.MonoProcessor; -import reactor.core.publisher.Operators; -import reactor.core.publisher.SignalType; -import reactor.core.scheduler.Schedulers; -import reactor.test.StepVerifier; -import reactor.test.publisher.TestPublisher; -import reactor.test.util.RaceTestUtils; -import reactor.util.context.Context; -import reactor.util.function.Tuple2; - -public class UnicastMonoProcessorTest { - - static class VerifyMonoLifecycleHandler implements MonoLifecycleHandler { - private final AtomicInteger onSubscribeCounter = new AtomicInteger(); - private final AtomicInteger onTerminalCounter = new AtomicInteger(); - private final AtomicReference valueReference = new AtomicReference<>(); - private final AtomicReference errorReference = new AtomicReference<>(); - private final AtomicReference signalTypeReference = new AtomicReference<>(); - - @Override - public void doOnSubscribe() { - onSubscribeCounter.incrementAndGet(); - } - - @Override - public void doOnTerminal(SignalType signalType, T element, Throwable e) { - onTerminalCounter.incrementAndGet(); - signalTypeReference.set(signalType); - valueReference.set(element); - errorReference.set(e); - } - - public VerifyMonoLifecycleHandler assertSubscribed() { - assertThat(onSubscribeCounter.get()).isOne(); - return this; - } - - public VerifyMonoLifecycleHandler assertNotSubscribed() { - assertThat(onSubscribeCounter.get()).isZero(); - return this; - } - - public VerifyMonoLifecycleHandler assertTerminated() { - assertThat(onTerminalCounter.get()).describedAs("Expected a single terminal signal").isOne(); - return this; - } - - public VerifyMonoLifecycleHandler assertNotTerminated() { - assertThat(onTerminalCounter.get()).describedAs("Expected zero terminal signals").isZero(); - return this; - } - - public VerifyMonoLifecycleHandler assertCompleted() { - assertTerminated(); - assertThat(signalTypeReference.get()) - .describedAs("Expected ON_COMPLETE signal") - .isEqualTo(SignalType.ON_COMPLETE); - assertThat(errorReference.get()).describedAs("Expected error to be absent").isNull(); - assertThat(valueReference.get()).isNull(); - return this; - } - - public VerifyMonoLifecycleHandler assertCompleted(T value) { - assertTerminated(); - assertThat(signalTypeReference.get()) - .describedAs("Expected ON_COMPLETE signal") - .isEqualTo(SignalType.ON_COMPLETE); - assertThat(errorReference.get()).describedAs("Expected error to be absent").isNull(); - assertThat(valueReference.get()).isEqualTo(value); - return this; - } - - public VerifyMonoLifecycleHandler assertErrored() { - assertTerminated(); - assertThat(signalTypeReference.get()) - .describedAs("Expected ON_ERROR signal") - .isEqualTo(SignalType.ON_ERROR); - assertThat(errorReference.get()).describedAs("Expected error to be present").isNotNull(); - assertThat(valueReference.get()).isNull(); - return this; - } - - public VerifyMonoLifecycleHandler assertCancelled() { - assertTerminated(); - assertThat(signalTypeReference.get()) - .describedAs("Expected ON_ERROR signal") - .isEqualTo(SignalType.CANCEL); - assertThat(errorReference.get()).describedAs("Expected error to be absent").isNull(); - assertThat(valueReference.get()).isNull(); - return this; - } - } - - @Test - public void testUnicast() throws InterruptedException { - warmup(Schedulers.single()); - - for (int i = 0; i < 10000; i++) { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - verifyMonoLifecycleHandler.assertNotSubscribed(); - assertThatThrownBy(() -> RaceTestUtils.race(processor::subscribe, processor::subscribe)) - .hasCause( - new IllegalStateException("UnicastMonoProcessor allows only a single Subscriber")); - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - } - } - - @Test - public void stateFlowTest1_Next() { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = AssertSubscriber.create(0); - - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.onNext(1); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_HAS_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_HAS_RESULT); - - assertSubscriber.assertNoEvents(); - assertSubscriber.request(1); - - verifyMonoLifecycleHandler.assertCompleted(1); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.assertValues(1); - assertSubscriber.assertComplete(); - } - - @Test - public void stateFlowTest1_Complete() { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = AssertSubscriber.create(0); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.onComplete(); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_HAS_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertCompleted(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.assertNoValues(); - assertSubscriber.assertComplete(); - } - - @Test - public void stateFlowTest1_Error() { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = AssertSubscriber.create(0); - RuntimeException testError = new RuntimeException("test"); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.onError(testError); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_HAS_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertErrored(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.assertNoValues(); - assertSubscriber.assertError(RuntimeException.class); - assertSubscriber.assertErrorMessage("test"); - } - - @Test - public void stateFlowTest1_Dispose() { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = AssertSubscriber.create(0); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.dispose(); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_HAS_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertErrored(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.assertNoValues(); - assertSubscriber.assertError(CancellationException.class); - assertSubscriber.assertErrorMessage("Disposed"); - } - - @Test - public void stateFlowTest2_Next() { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = AssertSubscriber.create(0); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_NO_RESULT); - - processor.onNext(1); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_HAS_RESULT); - - assertSubscriber.assertNoEvents(); - assertSubscriber.request(1); - - verifyMonoLifecycleHandler.assertSubscribed().assertCompleted(1); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.assertValues(1); - assertSubscriber.assertComplete(); - } - - @Test - public void stateFlowTest2_Complete() { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = AssertSubscriber.create(0); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_NO_RESULT); - - processor.onComplete(); - - verifyMonoLifecycleHandler.assertSubscribed().assertCompleted(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.assertNoValues(); - assertSubscriber.assertComplete(); - } - - @Test - public void stateFlowTest2_Error() { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = AssertSubscriber.create(0); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_NO_RESULT); - - processor.onError(new RuntimeException("Test")); - - verifyMonoLifecycleHandler.assertSubscribed().assertErrored(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.assertNoValues(); - assertSubscriber.assertError(RuntimeException.class); - assertSubscriber.assertErrorMessage("Test"); - } - - @Test - public void stateFlowTest2_Dispose() { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = AssertSubscriber.create(0); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_NO_RESULT); - - processor.dispose(); - - verifyMonoLifecycleHandler.assertSubscribed().assertErrored(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.assertNoValues(); - assertSubscriber.assertError(CancellationException.class); - assertSubscriber.assertErrorMessage("Disposed"); - } - - @Test - public void stateFlowTest3_Next() { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = AssertSubscriber.create(0); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_NO_RESULT); - - assertSubscriber.assertNoEvents(); - assertSubscriber.request(1); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_NO_RESULT); - - assertSubscriber.assertNoEvents(); - processor.onNext(1); - - verifyMonoLifecycleHandler.assertSubscribed().assertCompleted(1); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.assertValues(1); - assertSubscriber.assertComplete(); - } - - @Test - public void stateFlowTest3_Complete() { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = AssertSubscriber.create(0); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_NO_RESULT); - - assertSubscriber.request(1); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_NO_RESULT); - - processor.onComplete(); - - verifyMonoLifecycleHandler.assertSubscribed().assertCompleted(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.assertNoValues(); - assertSubscriber.assertComplete(); - } - - @Test - public void stateFlowTest3_Error() { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = AssertSubscriber.create(0); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_NO_RESULT); - - assertSubscriber.request(1); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_NO_RESULT); - - processor.onError(new RuntimeException("Test")); - - verifyMonoLifecycleHandler.assertSubscribed().assertErrored(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.assertNoValues(); - assertSubscriber.assertError(RuntimeException.class); - assertSubscriber.assertErrorMessage("Test"); - } - - @Test - public void stateFlowTest3_Dispose() { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = AssertSubscriber.create(0); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_NO_RESULT); - - assertSubscriber.request(1); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_NO_RESULT); - - processor.dispose(); - - verifyMonoLifecycleHandler.assertSubscribed().assertErrored(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.assertNoValues(); - assertSubscriber.assertError(RuntimeException.class); - assertSubscriber.assertErrorMessage("Disposed"); - } - - @Test - public void stateFlowTest4_Next() { - ArrayList discarded = new ArrayList<>(); - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - // Context discardingContext = Operators.enableOnDiscard(null, discarded::add); - Hooks.onNextDropped(discarded::add); - AssertSubscriber assertSubscriber = new AssertSubscriber<>(0); - - try { - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_NO_RESULT); - - assertSubscriber.request(1); - assertSubscriber.assertNoEvents(); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_NO_RESULT); - - assertSubscriber.cancel(); - assertSubscriber.assertNoEvents(); - - verifyMonoLifecycleHandler.assertSubscribed().assertCancelled(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.CANCELLED); - - processor.onNext(1); - - verifyMonoLifecycleHandler.assertSubscribed().assertCancelled(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.CANCELLED); - - assertSubscriber.assertNoEvents(); - assertThat(discarded).containsExactly(1); - } finally { - Hooks.resetOnNextDropped(); - } - } - - @Test - public void stateFlowTest4_Error() { - ArrayList discarded = new ArrayList<>(); - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - // Context discardingContext = Operators.enableOnDiscard(null, discarded::add); - Hooks.onErrorDropped(discarded::add); - AssertSubscriber assertSubscriber = new AssertSubscriber<>(0); - - try { - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_NO_RESULT); - - assertSubscriber.request(1); - assertSubscriber.assertNoEvents(); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_NO_RESULT); - - assertSubscriber.cancel(); - assertSubscriber.assertNoEvents(); - - verifyMonoLifecycleHandler.assertSubscribed().assertCancelled(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.CANCELLED); - - RuntimeException testError = new RuntimeException("test"); - processor.onError(testError); - - verifyMonoLifecycleHandler.assertSubscribed().assertCancelled(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.CANCELLED); - - assertSubscriber.assertNoEvents(); - assertThat(discarded).containsExactly(testError); - - } finally { - Hooks.resetOnErrorDropped(); - } - } - - @Test - public void stateFlowTest4_Dispose() { - ArrayList discarded = new ArrayList<>(); - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - // Context discardingContext = Operators.enableOnDiscard(null, discarded::add); - Hooks.onErrorDropped(discarded::add); - try { - AssertSubscriber assertSubscriber = new AssertSubscriber<>(0); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_NO_RESULT); - - assertSubscriber.request(1); - assertSubscriber.assertNoEvents(); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_NO_RESULT); - - assertSubscriber.cancel(); - assertSubscriber.assertNoEvents(); - - verifyMonoLifecycleHandler.assertSubscribed().assertCancelled(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.CANCELLED); - - processor.dispose(); - - verifyMonoLifecycleHandler.assertSubscribed().assertCancelled(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.CANCELLED); - - assertSubscriber.assertNoEvents(); - assertThat(discarded).isEmpty(); - } finally { - Hooks.resetOnErrorDropped(); - } - } - - @Test - public void stateFlowTest4_Complete() { - ArrayList discarded = new ArrayList<>(); - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - // Context discardingContext = Operators.enableOnDiscard(null, discarded::add); - Hooks.onErrorDropped(discarded::add); - AssertSubscriber assertSubscriber = new AssertSubscriber<>(0); - - try { - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_NO_RESULT); - - assertSubscriber.request(1); - assertSubscriber.assertNoEvents(); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_NO_RESULT); - - assertSubscriber.cancel(); - assertSubscriber.assertNoEvents(); - - verifyMonoLifecycleHandler.assertSubscribed().assertCancelled(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.CANCELLED); - - processor.onComplete(); - - verifyMonoLifecycleHandler.assertSubscribed().assertCancelled(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.CANCELLED); - - assertSubscriber.assertNoEvents(); - assertThat(discarded).isEmpty(); - } finally { - Hooks.resetOnErrorDropped(); - } - } - - @Test - public void stateFlowTest5_Next() { - ArrayList discarded = new ArrayList<>(); - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - Context discardingContext = Operators.enableOnDiscard(null, discarded::add); - AssertSubscriber assertSubscriber = new AssertSubscriber<>(discardingContext, 0); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_NO_RESULT); - - processor.onNext(1); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_HAS_RESULT); - - assertSubscriber.cancel(); - - verifyMonoLifecycleHandler.assertSubscribed().assertCancelled(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.CANCELLED); - - assertSubscriber.request(1); - - verifyMonoLifecycleHandler.assertSubscribed().assertCancelled(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.CANCELLED); - - assertSubscriber.assertNoEvents(); - assertThat(discarded).containsExactly(1); - } - - @Test - public void stateFlowTest5_Complete() { - ArrayList discarded = new ArrayList<>(); - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - Context discardingContext = Operators.enableOnDiscard(null, discarded::add); - AssertSubscriber assertSubscriber = new AssertSubscriber<>(discardingContext, 0); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_NO_RESULT); - - processor.onComplete(); - - verifyMonoLifecycleHandler.assertSubscribed().assertCompleted(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.cancel(); - - verifyMonoLifecycleHandler.assertSubscribed().assertCompleted(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.CANCELLED); - - assertSubscriber.assertComplete(); - assertThat(discarded).isEmpty(); - } - - @Test - public void stateFlowTest5_Error() { - ArrayList discarded = new ArrayList<>(); - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - Context discardingContext = Operators.enableOnDiscard(null, discarded::add); - Hooks.onErrorDropped(discarded::add); - AssertSubscriber assertSubscriber = new AssertSubscriber<>(discardingContext, 0); - - try { - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_NO_RESULT); - - processor.onError(new RuntimeException("test")); - - verifyMonoLifecycleHandler.assertSubscribed().assertErrored(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.cancel(); - - verifyMonoLifecycleHandler.assertSubscribed().assertErrored(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.CANCELLED); - - assertSubscriber.assertError(RuntimeException.class); - assertSubscriber.assertErrorMessage("test"); - assertThat(discarded).isEmpty(); - } finally { - Hooks.resetOnErrorDropped(); - } - } - - @Test - public void stateFlowTest5_Dispose() { - ArrayList discarded = new ArrayList<>(); - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - Context discardingContext = Operators.enableOnDiscard(null, discarded::add); - Hooks.onErrorDropped(discarded::add); - AssertSubscriber assertSubscriber = new AssertSubscriber<>(discardingContext, 0); - - try { - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_NO_RESULT); - - processor.dispose(); - - verifyMonoLifecycleHandler.assertSubscribed().assertErrored(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.cancel(); - - verifyMonoLifecycleHandler.assertSubscribed().assertErrored(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.CANCELLED); - - assertSubscriber.assertError(CancellationException.class); - assertSubscriber.assertErrorMessage("Disposed"); - assertThat(discarded).isEmpty(); - } finally { - Hooks.resetOnErrorDropped(); - } - } - - @Test - public void stateFlowTest6_Next() { - ArrayList discarded = new ArrayList<>(); - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - Context discardingContext = Operators.enableOnDiscard(null, discarded::add); - AssertSubscriber assertSubscriber = new AssertSubscriber<>(discardingContext, 0); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.onNext(1); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_HAS_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_REQUEST_HAS_RESULT); - - assertSubscriber.cancel(); - - verifyMonoLifecycleHandler.assertSubscribed().assertCancelled(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.CANCELLED); - - assertSubscriber.request(1); - - verifyMonoLifecycleHandler.assertSubscribed().assertCancelled(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.CANCELLED); - - assertSubscriber.assertNoEvents(); - assertThat(discarded).containsExactly(1); - } - - @Test - public void stateFlowTest7_Next() throws InterruptedException { - warmup(Schedulers.single()); - - for (int i = 0; i < 10000; i++) { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = new AssertSubscriber<>(); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - RaceTestUtils.race( - () -> processor.onNext(1), - () -> processor.subscribe(assertSubscriber), - Schedulers.single()); - - verifyMonoLifecycleHandler.assertSubscribed().assertCompleted(1); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.assertValues(1); - assertSubscriber.assertComplete(); - } - } - - @Test - public void stateFlowTest7_Complete() throws InterruptedException { - warmup(Schedulers.single()); - - for (int i = 0; i < 10000; i++) { - UnicastMonoProcessor processor = UnicastMonoProcessor.create(); - AssertSubscriber assertSubscriber = new AssertSubscriber<>(0); - - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - RaceTestUtils.race( - processor::onComplete, () -> processor.subscribe(assertSubscriber), Schedulers.single()); - - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.assertNoValues(); - assertSubscriber.assertComplete(); - } - } - - @Test - public void stateFlowTest7_Error() throws InterruptedException { - warmup(Schedulers.single()); - - for (int i = 0; i < 10000; i++) { - UnicastMonoProcessor processor = UnicastMonoProcessor.create(); - AssertSubscriber assertSubscriber = new AssertSubscriber<>(0); - - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - RaceTestUtils.race( - () -> processor.onError(new RuntimeException("test")), - () -> processor.subscribe(assertSubscriber), - Schedulers.single()); - - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.assertNoValues(); - assertSubscriber.assertError(RuntimeException.class); - assertSubscriber.assertErrorMessage("test"); - } - } - - @Test - public void stateFlowTest7_Dispose() throws InterruptedException { - warmup(Schedulers.single()); - - for (int i = 0; i < 10000; i++) { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = new AssertSubscriber<>(0); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - RaceTestUtils.race( - processor::dispose, () -> processor.subscribe(assertSubscriber), Schedulers.single()); - - verifyMonoLifecycleHandler.assertSubscribed().assertErrored(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.assertNoValues(); - assertSubscriber.assertError(CancellationException.class); - assertSubscriber.assertErrorMessage("Disposed"); - } - } - - @Test - public void stateFlowTest8_Next() throws InterruptedException { - warmup(Schedulers.single()); - - for (int i = 0; i < 10000; i++) { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = new AssertSubscriber<>(); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_NO_RESULT); - - RaceTestUtils.race(() -> processor.onNext(1), assertSubscriber::cancel, Schedulers.single()); - - verifyMonoLifecycleHandler.assertSubscribed().assertTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.CANCELLED); - - if (assertSubscriber.values().isEmpty()) { - verifyMonoLifecycleHandler.assertSubscribed().assertCancelled(); - assertSubscriber.assertNoEvents(); - } else { - verifyMonoLifecycleHandler.assertSubscribed().assertCompleted(1); - assertSubscriber.assertValues(1); - assertSubscriber.assertComplete(); - } - } - } - - @Test - public void stateFlowTest9_Next() throws InterruptedException { - warmup(Schedulers.single()); - - for (int i = 0; i < 10000; i++) { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = new AssertSubscriber<>(); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_NO_RESULT); - - RaceTestUtils.race(() -> processor.onNext(1), processor::dispose, Schedulers.single()); - - verifyMonoLifecycleHandler.assertSubscribed().assertTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - if (processor.isError()) { - verifyMonoLifecycleHandler.assertSubscribed().assertErrored(); - assertSubscriber.assertNoValues(); - assertSubscriber.assertErrorMessage("Disposed"); - } else { - verifyMonoLifecycleHandler.assertSubscribed().assertCompleted(1); - assertSubscriber.assertValues(1); - assertSubscriber.assertComplete(); - } - } - } - - @Test - public void stateFlowTest13_Next() throws InterruptedException { - warmup(Schedulers.single()); - - for (int i = 0; i < 10000; i++) { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = new AssertSubscriber<>(0); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.onNext(1); - processor.subscribe(assertSubscriber); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - - RaceTestUtils.race( - () -> assertSubscriber.request(1), - () -> assertSubscriber.request(1), - Schedulers.single()); - - verifyMonoLifecycleHandler.assertSubscribed().assertCompleted(1); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.assertValues(1); - assertSubscriber.assertComplete(); - } - } - - @Test - public void stateFlowTest14_Next() throws InterruptedException { - warmup(Schedulers.single()); - - for (int i = 0; i < 10000; i++) { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = new AssertSubscriber<>(0); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - assertSubscriber.request(1); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - RaceTestUtils.race(() -> processor.onNext(1), () -> processor.onNext(1), Schedulers.single()); - - verifyMonoLifecycleHandler.assertSubscribed().assertCompleted(1); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.HAS_REQUEST_HAS_RESULT); - - assertSubscriber.assertValues(1); - assertSubscriber.assertComplete(); - } - } - - @Test - public void stateFlowTest15_Next() throws InterruptedException { - warmup(Schedulers.single()); - - for (int i = 0; i < 10000; i++) { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = new AssertSubscriber<>(0); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - processor.onNext(1); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - RaceTestUtils.race( - () -> assertSubscriber.request(1), assertSubscriber::cancel, Schedulers.single()); - - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.CANCELLED); - - if (assertSubscriber.values().isEmpty()) { - verifyMonoLifecycleHandler.assertSubscribed().assertCancelled(); - assertSubscriber.assertNoEvents(); - } else { - verifyMonoLifecycleHandler.assertSubscribed().assertCompleted(1); - assertSubscriber.assertValues(1); - assertSubscriber.assertComplete(); - } - } - } - - @Test - public void stateFlowTest16_Next() throws InterruptedException { - warmup(Schedulers.single()); - - for (int i = 0; i < 10000; i++) { - VerifyMonoLifecycleHandler verifyMonoLifecycleHandler = - new VerifyMonoLifecycleHandler<>(); - UnicastMonoProcessor processor = - UnicastMonoProcessor.create(verifyMonoLifecycleHandler); - AssertSubscriber assertSubscriber = new AssertSubscriber<>(0); - - verifyMonoLifecycleHandler.assertNotSubscribed().assertNotTerminated(); - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.NO_SUBSCRIBER_NO_RESULT); - - processor.subscribe(assertSubscriber); - processor.onNext(1); - - verifyMonoLifecycleHandler.assertSubscribed().assertNotTerminated(); - RaceTestUtils.race(assertSubscriber::cancel, assertSubscriber::cancel, Schedulers.single()); - - assertThat(processor.state).isEqualTo(UnicastMonoProcessor.CANCELLED); - - verifyMonoLifecycleHandler.assertSubscribed().assertCancelled(); - assertSubscriber.assertNoEvents(); - } - } - - @Test - public void noRetentionOnTermination() throws InterruptedException { - Date date = new Date(); - CompletableFuture future = new CompletableFuture<>(); - - WeakReference refDate = new WeakReference<>(date); - WeakReference> refFuture = new WeakReference<>(future); - - Mono source = Mono.fromFuture(future); - Mono data = - source.map(Date::toString).log().subscribeWith(UnicastMonoProcessor.create()).log(); - - future.complete(date); - assertThat(data.block()).isEqualTo(date.toString()); - - date = null; - future = null; - source = null; - System.gc(); - - int cycles; - for (cycles = 10; cycles > 0; cycles--) { - if (refDate.get() == null && refFuture.get() == null) break; - Thread.sleep(100); - } - - assertThat(refFuture.get()).isNull(); - assertThat(refDate.get()).isNull(); - assertThat(cycles).isNotZero().isPositive(); - } - - @Test - public void noRetentionOnTerminationError() throws InterruptedException { - CompletableFuture future = new CompletableFuture<>(); - - WeakReference> refFuture = new WeakReference<>(future); - UnicastMonoProcessor processor = UnicastMonoProcessor.create(); - - Mono source = Mono.fromFuture(future); - Mono data = source.map(Date::toString).subscribeWith(processor); - - future.completeExceptionally(new IllegalStateException()); - - assertThatExceptionOfType(IllegalStateException.class).isThrownBy(data::block); - - future = null; - source = null; - System.gc(); - - int cycles; - for (cycles = 10; cycles > 0; cycles--) { - if (refFuture.get() == null) break; - Thread.sleep(100); - } - - assertThat(refFuture.get()).isNull(); - assertThat(cycles).isNotZero().isPositive(); - } - - @Test - public void noRetentionOnTerminationCancel() throws InterruptedException { - CompletableFuture future = new CompletableFuture<>(); - - WeakReference> refFuture = new WeakReference<>(future); - UnicastMonoProcessor processor = UnicastMonoProcessor.create(); - - Mono source = Mono.fromFuture(future); - Mono data = - source.map(Date::toString).transformDeferred((s) -> s.subscribeWith(processor)); - - future = null; - source = null; - - data.subscribe().dispose(); - processor.dispose(); - - data = null; - - System.gc(); - - int cycles; - for (cycles = 10; cycles > 0; cycles--) { - if (refFuture.get() == null) break; - Thread.sleep(100); - } - - assertThat(refFuture.get()).isNull(); - assertThat(cycles).isNotZero().isPositive(); - } - - @Test(expected = IllegalStateException.class) - public void MonoProcessorResultNotAvailable() { - MonoProcessor mp = MonoProcessor.create(); - mp.block(Duration.ofMillis(1)); - } - - @Test - public void MonoProcessorRejectedDoOnSuccessOrError() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - AtomicReference ref = new AtomicReference<>(); - - mp.doOnSuccessOrError((s, f) -> ref.set(f)).subscribe(); - mp.onError(new Exception("test")); - - assertThat(ref.get()).hasMessage("test"); - assertThat(mp.isError()).isTrue(); - } - - @Test - public void MonoProcessorRejectedDoOnTerminate() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - AtomicInteger invoked = new AtomicInteger(); - - mp.doOnTerminate(invoked::incrementAndGet).subscribe(); - mp.onError(new Exception("test")); - - assertThat(invoked.get()).isEqualTo(1); - assertThat(mp.isError()).isTrue(); - } - - @Test - public void MonoProcessorRejectedSubscribeCallback() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - AtomicReference ref = new AtomicReference<>(); - - mp.subscribe(v -> {}, ref::set); - mp.onError(new Exception("test")); - - assertThat(ref.get()).hasMessage("test"); - assertThat(mp.isError()).isTrue(); - } - - @Test - public void MonoProcessorSuccessDoOnSuccessOrError() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - AtomicReference ref = new AtomicReference<>(); - - mp.doOnSuccessOrError((s, f) -> ref.set(s)).subscribe(); - mp.onNext("test"); - - assertThat(ref.get()).isEqualToIgnoringCase("test"); - assertThat(mp.isDisposed()).isTrue(); - assertThat(mp.isError()).isFalse(); - } - - @Test - public void MonoProcessorSuccessDoOnTerminate() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - AtomicInteger invoked = new AtomicInteger(); - - mp.doOnTerminate(invoked::incrementAndGet).subscribe(); - mp.onNext("test"); - - assertThat(invoked.get()).isEqualTo(1); - assertThat(mp.isDisposed()).isTrue(); - assertThat(mp.isError()).isFalse(); - } - - @Test - public void MonoProcessorSuccessSubscribeCallback() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - AtomicReference ref = new AtomicReference<>(); - - mp.subscribe(ref::set); - mp.onNext("test"); - - assertThat(ref.get()).isEqualToIgnoringCase("test"); - assertThat(mp.isDisposed()).isTrue(); - assertThat(mp.isError()).isFalse(); - } - - @Test - public void MonoProcessorRejectedDoOnError() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - AtomicReference ref = new AtomicReference<>(); - - mp.doOnError(ref::set).subscribe(); - mp.onError(new Exception("test")); - - assertThat(ref.get()).hasMessage("test"); - assertThat(mp.isError()).isTrue(); - } - - @Test(expected = NullPointerException.class) - public void MonoProcessorRejectedSubscribeCallbackNull() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - - mp.subscribe((Subscriber) null); - } - - @Test - public void MonoProcessorSuccessDoOnSuccess() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - AtomicReference ref = new AtomicReference<>(); - - mp.doOnSuccess(ref::set).subscribe(); - mp.onNext("test"); - - assertThat(ref.get()).isEqualToIgnoringCase("test"); - assertThat(mp.isDisposed()).isTrue(); - assertThat(mp.isError()).isFalse(); - } - - @Test - public void MonoProcessorSuccessChainTogether() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - UnicastMonoProcessor mp2 = UnicastMonoProcessor.create(); - mp.subscribe(mp2); - - mp.onNext("test"); - - assertThat(mp2.peek()).isEqualToIgnoringCase("test"); - assertThat(mp.isDisposed()).isTrue(); - assertThat(mp.isError()).isFalse(); - } - - @Test - public void MonoProcessorRejectedChainTogether() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - UnicastMonoProcessor mp2 = UnicastMonoProcessor.create(); - mp.subscribe(mp2); - - mp.onError(new Exception("test")); - - assertThat(mp2.getError()).hasMessage("test"); - assertThat(mp.isError()).isTrue(); - } - - @Test - public void MonoProcessorDoubleFulfill() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - - StepVerifier.create(mp) - .then( - () -> { - mp.onNext("test1"); - mp.onNext("test2"); - }) - .expectNext("test1") - .expectComplete() - .verifyThenAssertThat() - .hasDroppedExactly("test2"); - } - - @Test - public void MonoProcessorNullFulfill() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - - mp.onNext(null); - - assertThat(mp.isDisposed()).isTrue(); - assertThat(mp.peek()).isNull(); - } - - @Test - public void MonoProcessorMapFulfill() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - - mp.onNext(1); - - UnicastMonoProcessor mp2 = - mp.map(s -> s * 2).subscribeWith(UnicastMonoProcessor.create()); - - assertThat(mp2.isDisposed()).isTrue(); - assertThat(mp2.isTerminated()).isTrue(); - assertThat(mp2.isCancelled()).isFalse(); - assertThat(mp2.peek()).isEqualTo(2); - - mp2.subscribe(); - } - - @Test - public void MonoProcessorThenFulfill() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - - mp.onNext(1); - - UnicastMonoProcessor mp2 = - mp.flatMap(s -> Mono.just(s * 2)).subscribeWith(UnicastMonoProcessor.create()); - - assertThat(mp2.isDisposed()).isTrue(); - assertThat(mp2.isTerminated()).isTrue(); - assertThat(mp2.isCancelled()).isFalse(); - assertThat(mp2.peek()).isEqualTo(2); - - mp2.subscribe(); - } - - @Test - public void MonoProcessorMapError() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - - mp.onNext(1); - - UnicastMonoProcessor mp2 = UnicastMonoProcessor.create(); - - StepVerifier.create( - mp.map( - s -> { - throw new RuntimeException("test"); - }) - .subscribeWith(mp2), - 0) - .thenRequest(1) - .then( - () -> { - assertThat(mp2.isDisposed()).isTrue(); - assertThat(mp2.getError()).hasMessage("test"); - }) - .verifyErrorMessage("test"); - } - - @Test(expected = Exception.class) - public void MonoProcessorDoubleError() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - - mp.onError(new Exception("test")); - mp.onError(new Exception("test")); - } - - @Test(expected = Exception.class) - public void MonoProcessorDoubleSignal() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - - mp.onNext("test"); - mp.onError(new Exception("test")); - } - - @Test - public void zipMonoProcessor() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - UnicastMonoProcessor mp2 = UnicastMonoProcessor.create(); - UnicastMonoProcessor> mp3 = UnicastMonoProcessor.create(); - - StepVerifier.create(Mono.zip(mp, mp2).subscribeWith(mp3), 0) - .then(() -> assertThat(mp3.isDisposed()).isFalse()) - .then(() -> mp.onNext(1)) - .then(() -> assertThat(mp3.isDisposed()).isFalse()) - .then(() -> mp2.onNext(2)) - .then( - () -> { - assertThat(mp3.isDisposed()).isTrue(); - assertThat(mp3.peek().getT1()).isEqualTo(1); - assertThat(mp3.peek().getT2()).isEqualTo(2); - }) - .thenRequest(1) - .expectNextMatches(t -> t.getT1() == 1 && t.getT2() == 2) - .verifyComplete(); - } - - @Test - public void zipMonoProcessor2() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - UnicastMonoProcessor mp3 = UnicastMonoProcessor.create(); - - StepVerifier.create(Mono.zip(d -> (Integer) d[0], mp).subscribeWith(mp3), 0) - .then(() -> assertThat(mp3.isDisposed()).isFalse()) - .then(() -> mp.onNext(1)) - .then( - () -> { - assertThat(mp3.isDisposed()).isTrue(); - assertThat(mp3.peek()).isEqualTo(1); - }) - .thenRequest(1) - .expectNext(1) - .verifyComplete(); - } - - @Test - public void zipMonoProcessorRejected() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - UnicastMonoProcessor mp2 = UnicastMonoProcessor.create(); - UnicastMonoProcessor> mp3 = UnicastMonoProcessor.create(); - - StepVerifier.create(Mono.zip(mp, mp2).subscribeWith(mp3)) - .then(() -> assertThat(mp3.isDisposed()).isFalse()) - .then(() -> mp.onError(new Exception("test"))) - .then( - () -> { - assertThat(mp3.isDisposed()).isTrue(); - assertThat(mp3.getError()).hasMessage("test"); - }) - .verifyErrorMessage("test"); - } - - @Test - public void filterMonoProcessor() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - UnicastMonoProcessor mp2 = UnicastMonoProcessor.create(); - StepVerifier.create(mp.filter(s -> s % 2 == 0).subscribeWith(mp2), 0) - .then(() -> mp.onNext(2)) - .then(() -> assertThat(mp2.isError()).isFalse()) - .then(() -> assertThat(mp2.isDisposed()).isTrue()) - .then(() -> assertThat(mp2.peek()).isEqualTo(2)) - .then(() -> assertThat(mp2.isDisposed()).isTrue()) - .thenRequest(1) - .expectNext(2) - .verifyComplete(); - } - - @Test - public void filterMonoProcessorNot() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - UnicastMonoProcessor mp2 = UnicastMonoProcessor.create(); - StepVerifier.create(mp.filter(s -> s % 2 == 0).subscribeWith(mp2)) - .then(() -> mp.onNext(1)) - .then(() -> assertThat(mp2.isError()).isFalse()) - .then(() -> assertThat(mp2.isDisposed()).isTrue()) - .then(() -> assertThat(mp2.peek()).isNull()) - .then(() -> assertThat(mp2.isDisposed()).isTrue()) - .verifyComplete(); - } - - @Test - public void filterMonoProcessorError() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - UnicastMonoProcessor mp2 = UnicastMonoProcessor.create(); - StepVerifier.create( - mp.filter( - s -> { - throw new RuntimeException("test"); - }) - .subscribeWith(mp2)) - .then(() -> mp.onNext(2)) - .then(() -> assertThat(mp2.isError()).isTrue()) - .then(() -> assertThat(mp2.isDisposed()).isTrue()) - .then(() -> assertThat(mp2.getError()).hasMessage("test")) - .then(() -> assertThat(mp2.isDisposed()).isTrue()) - .verifyErrorMessage("test"); - } - - @Test - public void doOnSuccessMonoProcessorError() { - UnicastMonoProcessor mp = UnicastMonoProcessor.create(); - UnicastMonoProcessor mp2 = UnicastMonoProcessor.create(); - AtomicReference ref = new AtomicReference<>(); - - StepVerifier.create( - mp.doOnSuccess( - s -> { - throw new RuntimeException("test"); - }) - .doOnError(ref::set) - .subscribeWith(mp2)) - .then(() -> mp.onNext(2)) - .then(() -> assertThat(mp2.isError()).isTrue()) - .then(() -> assertThat(ref.get()).hasMessage("test")) - .then(() -> assertThat(mp2.isDisposed()).isTrue()) - .then(() -> assertThat(mp2.getError()).hasMessage("test")) - .then(() -> assertThat(mp2.isDisposed()).isTrue()) - .verifyErrorMessage("test"); - } - - @Test - public void fluxCancelledByMonoProcessor() { - AtomicLong cancelCounter = new AtomicLong(); - Flux.range(1, 10) - .doOnCancel(cancelCounter::incrementAndGet) - .transformDeferred((s) -> s.subscribeWith(UnicastMonoProcessor.create())) - .subscribe(); - - assertThat(cancelCounter.get()).isEqualTo(1); - } - - @Test - public void cancelledByMonoProcessor() { - AtomicLong cancelCounter = new AtomicLong(); - UnicastMonoProcessor monoProcessor = - Mono.just("foo") - .doOnCancel(cancelCounter::incrementAndGet) - .subscribeWith(UnicastMonoProcessor.create()); - monoProcessor.subscribe(); - - assertThat(cancelCounter.get()).isEqualTo(1); - } - - @Test - public void scanProcessor() { - UnicastMonoProcessor test = UnicastMonoProcessor.create(); - Subscription subscription = Operators.emptySubscription(); - test.onSubscribe(subscription); - - assertThat(test.scan(Scannable.Attr.PREFETCH)).isEqualTo(Integer.MAX_VALUE); - assertThat(test.scan(Scannable.Attr.TERMINATED)).isFalse(); - assertThat(test.scan(Scannable.Attr.CANCELLED)).isFalse(); - - test.onComplete(); - assertThat(test.scan(Scannable.Attr.TERMINATED)).isTrue(); - assertThat(test.scan(Scannable.Attr.CANCELLED)).isFalse(); - } - - @Test - public void scanProcessorCancelled() { - UnicastMonoProcessor test = UnicastMonoProcessor.create(); - Subscription subscription = Operators.emptySubscription(); - test.onSubscribe(subscription); - - assertThat(test.scan(Scannable.Attr.PREFETCH)).isEqualTo(Integer.MAX_VALUE); - assertThat(test.scan(Scannable.Attr.TERMINATED)).isFalse(); - assertThat(test.scan(Scannable.Attr.CANCELLED)).isFalse(); - - test.cancel(); - assertThat(test.scan(Scannable.Attr.TERMINATED)).isFalse(); - assertThat(test.scan(Scannable.Attr.CANCELLED)).isTrue(); - } - - @Test - public void scanProcessorSubscription() { - UnicastMonoProcessor test = UnicastMonoProcessor.create(); - Subscription subscription = Operators.emptySubscription(); - test.onSubscribe(subscription); - - assertThat(test.scan(Scannable.Attr.ACTUAL)).isNull(); - assertThat(test.scan(Scannable.Attr.PARENT)).isSameAs(subscription); - } - - @Test - public void scanProcessorError() { - UnicastMonoProcessor test = UnicastMonoProcessor.create(); - Subscription subscription = Operators.emptySubscription(); - test.onSubscribe(subscription); - - test.onError(new IllegalStateException("boom")); - - assertThat(test.scan(Scannable.Attr.ERROR)).hasMessage("boom"); - } - - @Test - public void monoToProcessorConnects() { - TestPublisher tp = TestPublisher.create(); - UnicastMonoProcessor connectedProcessor = - tp.mono().subscribeWith(UnicastMonoProcessor.create()); - - assertThat(connectedProcessor.subscription).isNotNull(); - } - - @Test - public void monoToProcessorChain() { - StepVerifier.withVirtualTime( - () -> - Mono.just("foo") - .subscribeWith(UnicastMonoProcessor.create()) - .delayElement(Duration.ofMillis(500))) - .expectSubscription() - .expectNoEvent(Duration.ofMillis(500)) - .expectNext("foo") - .verifyComplete(); - } - - @Test - public void monoToProcessorChainColdToHot() { - AtomicInteger subscriptionCount = new AtomicInteger(); - Mono coldToHot = - Mono.just("foo") - .doOnSubscribe(sub -> subscriptionCount.incrementAndGet()) - .transformDeferred(s -> s.subscribeWith(UnicastMonoProcessor.create())) - .subscribeWith(UnicastMonoProcessor.create()) // this actually subscribes - .filter(s -> s.length() < 4); - - assertThat(subscriptionCount.get()).isEqualTo(1); - - coldToHot.block(); - assertThatThrownBy(coldToHot::block) - .hasMessage("UnicastMonoProcessor allows only a single Subscriber"); - assertThatThrownBy(coldToHot::block) - .hasMessage("UnicastMonoProcessor allows only a single Subscriber"); - - assertThat(subscriptionCount.get()).isEqualTo(1); - } - - @Test - public void monoProcessorBlockIsUnbounded() { - long start = System.nanoTime(); - - String result = - Mono.just("foo") - .delayElement(Duration.ofMillis(500)) - .subscribeWith(UnicastMonoProcessor.create()) - .block(); - - assertThat(result).isEqualTo("foo"); - assertThat(Duration.ofNanos(System.nanoTime() - start)) - .isGreaterThanOrEqualTo(Duration.ofMillis(500)); - } - - @Test - public void monoProcessorBlockNegativeIsImmediateTimeout() { - long start = System.nanoTime(); - - assertThatExceptionOfType(IllegalStateException.class) - .isThrownBy( - () -> - Mono.just("foo") - .delayElement(Duration.ofMillis(500)) - .subscribeWith(UnicastMonoProcessor.create()) - .block(Duration.ofSeconds(-1))) - .withMessage("Timeout on blocking read for -1000 MILLISECONDS"); - - assertThat(Duration.ofNanos(System.nanoTime() - start)).isLessThan(Duration.ofMillis(500)); - } - - @Test - public void monoProcessorBlockZeroIsImmediateTimeout() { - long start = System.nanoTime(); - - assertThatExceptionOfType(IllegalStateException.class) - .isThrownBy( - () -> - Mono.just("foo") - .delayElement(Duration.ofMillis(500)) - .subscribeWith(UnicastMonoProcessor.create()) - .block(Duration.ZERO)) - .withMessage("Timeout on blocking read for 0 MILLISECONDS"); - - assertThat(Duration.ofNanos(System.nanoTime() - start)).isLessThan(Duration.ofMillis(500)); - } - - @Test - public void disposeBeforeValueSendsCancellationException() { - UnicastMonoProcessor processor = UnicastMonoProcessor.create(); - AtomicReference e1 = new AtomicReference<>(); - AtomicReference e2 = new AtomicReference<>(); - AtomicReference e3 = new AtomicReference<>(); - AtomicReference late = new AtomicReference<>(); - - processor.subscribe(v -> Assertions.fail("expected first subscriber to error"), e1::set); - processor.subscribe(v -> Assertions.fail("expected second subscriber to error"), e2::set); - processor.subscribe(v -> Assertions.fail("expected third subscriber to error"), e3::set); - - processor.dispose(); - - assertThat(e1.get()).isInstanceOf(CancellationException.class); - assertThat(e2.get()).isInstanceOf(IllegalStateException.class); - assertThat(e3.get()).isInstanceOf(IllegalStateException.class); - - processor.subscribe(v -> Assertions.fail("expected late subscriber to error"), late::set); - assertThat(late.get()).isInstanceOf(IllegalStateException.class); - } -} From 388775c6bc36682e2fb45d68061e63bc8a3a42e1 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Thu, 30 Apr 2020 17:31:54 +0300 Subject: [PATCH 44/62] adds generated JMH files cleanup --- benchmarks/build.gradle | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index fa1d6e04b..f07f7c6f5 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -40,6 +40,10 @@ task jmhBaseline(type: JmhExecTask, description: 'Executing JMH baseline benchma classpath = sourceSets.main.runtimeClasspath + configurations.baseline } +clean { + delete "${projectDir}/src/main/generated" +} + class JmhExecTask extends JavaExec { private String include; @@ -160,4 +164,4 @@ class JmhExecTask extends JavaExec { super.exec(); } -} \ No newline at end of file +} From d97fd8d5fc1d74f9fb72340b6f1023e1d780f1a8 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Thu, 30 Apr 2020 20:50:55 +0300 Subject: [PATCH 45/62] adds missing NPE catch in order to handle rare racing cases (#808) --- .../src/main/java/io/rsocket/core/RSocketRequester.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index f762bfe99..f9d4b2afe 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -478,7 +478,7 @@ public void accept(long n) { frame = RequestChannelFrameFlyweight.encodeReleasingPayload( allocator, streamId, false, n, initialPayload); - } catch (IllegalReferenceCountException e) { + } catch (IllegalReferenceCountException | NullPointerException e) { return; } From 12fd30140d23f161ad59c29c6385860af1d19a09 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Fri, 1 May 2020 10:51:20 +0300 Subject: [PATCH 46/62] provides supportive class to hide concurrency complexity (#807) --- .../io/rsocket/core/RSocketRequester.java | 417 ++++++++---------- .../java/io/rsocket/core/RequestOperator.java | 188 ++++++++ .../core/RSocketRequesterSubscribersTest.java | 30 ++ .../io/rsocket/core/RSocketRequesterTest.java | 155 ++++++- .../io/rsocket/core/SetupRejectionTest.java | 2 + 5 files changed, 566 insertions(+), 226 deletions(-) create mode 100644 rsocket-core/src/main/java/io/rsocket/core/RequestOperator.java diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index f9d4b2afe..fabea217b 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -52,10 +52,8 @@ import java.nio.channels.ClosedChannelException; import java.util.concurrent.CancellationException; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.Consumer; -import java.util.function.LongConsumer; import java.util.function.Supplier; import javax.annotation.Nullable; import org.reactivestreams.Processor; @@ -66,6 +64,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoProcessor; +import reactor.core.publisher.Operators; import reactor.core.publisher.SignalType; import reactor.core.publisher.UnicastProcessor; import reactor.util.concurrent.Queues; @@ -208,7 +207,6 @@ private Mono handleFireAndForget(Payload payload) { } final AtomicBoolean once = new AtomicBoolean(); - final int streamId = streamIdSupplier.nextStreamId(receivers); return Mono.defer( () -> { @@ -217,15 +215,14 @@ private Mono handleFireAndForget(Payload payload) { new IllegalStateException("FireAndForgetMono allows only a single subscriber")); } - return Mono.empty() - .doOnSubscribe( - (__) -> { - ByteBuf requestFrame = - RequestFireAndForgetFrameFlyweight.encodeReleasingPayload( - allocator, streamId, payload); + final int streamId = streamIdSupplier.nextStreamId(receivers); + final ByteBuf requestFrame = + RequestFireAndForgetFrameFlyweight.encodeReleasingPayload( + allocator, streamId, payload); - sendProcessor.onNext(requestFrame); - }); + sendProcessor.onNext(requestFrame); + + return Mono.empty(); }); } @@ -241,13 +238,10 @@ private Mono handleRequestResponse(final Payload payload) { return Mono.error(new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE)); } - int streamId = streamIdSupplier.nextStreamId(receivers); final UnboundedProcessor sendProcessor = this.sendProcessor; final UnicastProcessor receiver = UnicastProcessor.create(Queues.one().get()); final AtomicBoolean once = new AtomicBoolean(); - receivers.put(streamId, receiver); - return Mono.defer( () -> { if (once.getAndSet(true)) { @@ -257,21 +251,39 @@ private Mono handleRequestResponse(final Payload payload) { return receiver .next() - .doOnSubscribe( - (__) -> { - ByteBuf requestFrame = - RequestResponseFrameFlyweight.encodeReleasingPayload( - allocator, streamId, payload); - - sendProcessor.onNext(requestFrame); - }) - .doFinally( - signalType -> { - if (signalType == SignalType.CANCEL) { - sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); - } - removeStreamReceiver(streamId); - }) + .transform( + Operators.lift( + (s, actual) -> + new RequestOperator(actual) { + + @Override + void hookOnFirstRequest(long n) { + int streamId = streamIdSupplier.nextStreamId(receivers); + this.streamId = streamId; + + ByteBuf requestResponseFrame = + RequestResponseFrameFlyweight.encodeReleasingPayload( + allocator, streamId, payload); + + receivers.put(streamId, receiver); + sendProcessor.onNext(requestResponseFrame); + } + + @Override + void hookOnCancel() { + if (receivers.remove(streamId, receiver)) { + sendProcessor.onNext( + CancelFrameFlyweight.encode(allocator, streamId)); + } else { + payload.release(); + } + } + + @Override + public void hookOnTerminal(SignalType signalType) { + receivers.remove(streamId, receiver); + } + })) .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER); }); } @@ -288,15 +300,10 @@ private Flux handleRequestStream(final Payload payload) { return Flux.error(new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE)); } - int streamId = streamIdSupplier.nextStreamId(receivers); - final UnboundedProcessor sendProcessor = this.sendProcessor; final UnicastProcessor receiver = UnicastProcessor.create(); - final AtomicInteger wip = new AtomicInteger(0); final AtomicBoolean once = new AtomicBoolean(); - receivers.put(streamId, receiver); - return Flux.defer( () -> { if (once.getAndSet(true)) { @@ -305,62 +312,50 @@ private Flux handleRequestStream(final Payload payload) { } return receiver - .doOnRequest( - new LongConsumer() { - - boolean firstRequest = true; - - @Override - public void accept(long n) { - if (firstRequest) { - firstRequest = false; - if (wip.getAndIncrement() != 0) { - // no need to do anything. - // stream was canceled and fist payload has already been discarded - return; - } - int missed = 1; - boolean firstHasBeenSent = false; - for (; ; ) { - if (!firstHasBeenSent) { - sendProcessor.onNext( - RequestStreamFrameFlyweight.encodeReleasingPayload( - allocator, streamId, n, payload)); - firstHasBeenSent = true; - } else { - // if first frame was sent but we cycling again, it means that wip was - // incremented at doOnCancel - sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); - return; - } - - missed = wip.addAndGet(-missed); - if (missed == 0) { - return; - } - } - } else if (!receiver.isDisposed()) { - sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); - } - } - }) - .doFinally( - s -> { - if (s == SignalType.CANCEL) { - if (wip.getAndIncrement() != 0) { - return; - } - - // check if we need to release payload - // only applicable if the cancel appears earlier than actual request - if (payload.refCnt() > 0) { - payload.release(); - } else { - sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); - } - } - removeStreamReceiver(streamId); - }) + .transform( + Operators.lift( + (s, actual) -> + new RequestOperator(actual) { + + @Override + void hookOnFirstRequest(long n) { + int streamId = streamIdSupplier.nextStreamId(receivers); + this.streamId = streamId; + + ByteBuf requestStreamFrame = + RequestStreamFrameFlyweight.encodeReleasingPayload( + allocator, streamId, n, payload); + + receivers.put(streamId, receiver); + + sendProcessor.onNext(requestStreamFrame); + } + + @Override + void hookOnRemainingRequests(long n) { + if (receiver.isDisposed()) { + return; + } + + sendProcessor.onNext( + RequestNFrameFlyweight.encode(allocator, streamId, n)); + } + + @Override + void hookOnCancel() { + if (receivers.remove(streamId, receiver)) { + sendProcessor.onNext( + CancelFrameFlyweight.encode(allocator, streamId)); + } else { + payload.release(); + } + } + + @Override + void hookOnTerminal(SignalType signalType) { + receivers.remove(streamId); + } + })) .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER); }); } @@ -394,141 +389,123 @@ private Flux handleChannel(Flux request) { private Flux handleChannel(Payload initialPayload, Flux inboundFlux) { final UnboundedProcessor sendProcessor = this.sendProcessor; - final int streamId = streamIdSupplier.nextStreamId(receivers); - final AtomicInteger wip = new AtomicInteger(0); final UnicastProcessor receiver = UnicastProcessor.create(); - final BaseSubscriber upstreamSubscriber = - new BaseSubscriber() { - boolean first = true; + return receiver.transform( + Operators.lift( + (s, actual) -> + new RequestOperator(actual) { - @Override - protected void hookOnSubscribe(Subscription subscription) { - // noops - } + final BaseSubscriber upstreamSubscriber = + new BaseSubscriber() { - @Override - protected void hookOnNext(Payload payload) { - if (first) { - // need to skip first since we have already sent it - // no need to release it since it was released earlier on the request establishment - // phase - first = false; - request(1); - return; - } - if (!PayloadValidationUtils.isValid(mtu, payload)) { - payload.release(); - cancel(); - final IllegalArgumentException t = - new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE); - errorConsumer.accept(t); - // no need to send any errors. - sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); - receiver.onError(t); - return; - } - final ByteBuf frame = - PayloadFrameFlyweight.encodeNextReleasingPayload(allocator, streamId, payload); - - sendProcessor.onNext(frame); - } + boolean first = true; - @Override - protected void hookOnComplete() { - ByteBuf frame = PayloadFrameFlyweight.encodeComplete(allocator, streamId); - sendProcessor.onNext(frame); - } + @Override + protected void hookOnSubscribe(Subscription subscription) { + // noops + } - @Override - protected void hookOnError(Throwable t) { - ByteBuf frame = ErrorFrameFlyweight.encode(allocator, streamId, t); - sendProcessor.onNext(frame); - receiver.onError(t); - } + @Override + protected void hookOnNext(Payload payload) { + if (first) { + // need to skip first since we have already sent it + // no need to release it since it was released earlier on the request + // establishment + // phase + first = false; + request(1); + return; + } + if (!PayloadValidationUtils.isValid(mtu, payload)) { + payload.release(); + cancel(); + final IllegalArgumentException t = + new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE); + errorConsumer.accept(t); + // no need to send any errors. + sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); + receiver.onError(t); + return; + } + final ByteBuf frame = + PayloadFrameFlyweight.encodeNextReleasingPayload( + allocator, streamId, payload); - @Override - protected void hookFinally(SignalType type) { - senders.remove(streamId, this); - } - }; - - return receiver - .doOnRequest( - new LongConsumer() { - - boolean firstRequest = true; - - @Override - public void accept(long n) { - if (firstRequest) { - firstRequest = false; - if (wip.getAndIncrement() != 0) { - // no need to do anything. - // stream was canceled and fist payload has already been discarded - return; + sendProcessor.onNext(frame); + } + + @Override + protected void hookOnComplete() { + ByteBuf frame = PayloadFrameFlyweight.encodeComplete(allocator, streamId); + sendProcessor.onNext(frame); + } + + @Override + protected void hookOnError(Throwable t) { + ByteBuf frame = ErrorFrameFlyweight.encode(allocator, streamId, t); + sendProcessor.onNext(frame); + receiver.onError(t); + } + + @Override + protected void hookFinally(SignalType type) { + senders.remove(streamId, this); + } + }; + + @Override + void hookOnFirstRequest(long n) { + final int streamId = streamIdSupplier.nextStreamId(receivers); + this.streamId = streamId; + + final ByteBuf frame = + RequestChannelFrameFlyweight.encodeReleasingPayload( + allocator, streamId, false, n, initialPayload); + + senders.put(streamId, upstreamSubscriber); + receivers.put(streamId, receiver); + + inboundFlux + .limitRate(Queues.SMALL_BUFFER_SIZE) + .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER) + .subscribe(upstreamSubscriber); + + sendProcessor.onNext(frame); } - int missed = 1; - boolean firstHasBeenSent = false; - for (; ; ) { - if (!firstHasBeenSent) { - ByteBuf frame; - try { - frame = - RequestChannelFrameFlyweight.encodeReleasingPayload( - allocator, streamId, false, n, initialPayload); - } catch (IllegalReferenceCountException | NullPointerException e) { - return; - } - - senders.put(streamId, upstreamSubscriber); - receivers.put(streamId, receiver); - - inboundFlux - .limitRate(Queues.SMALL_BUFFER_SIZE) - .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER) - .subscribe(upstreamSubscriber); - - sendProcessor.onNext(frame); - firstHasBeenSent = true; - } else { - // if first frame was sent but we cycling again, it means that wip was - // incremented at doOnCancel - senders.remove(streamId, upstreamSubscriber); - receivers.remove(streamId, receiver); - sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); + + @Override + void hookOnRemainingRequests(long n) { + if (receiver.isDisposed()) { return; } - missed = wip.addAndGet(-missed); - if (missed == 0) { - return; + sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); + } + + @Override + void hookOnCancel() { + senders.remove(streamId, upstreamSubscriber); + if (receivers.remove(streamId, receiver)) { + sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); } } - } else { - sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); - } - } - }) - .doOnError( - t -> { - upstreamSubscriber.cancel(); - receivers.remove(streamId, receiver); - }) - .doOnComplete(() -> receivers.remove(streamId, receiver)) - .doOnCancel( - () -> { - upstreamSubscriber.cancel(); - if (wip.getAndIncrement() != 0) { - return; - } - // need to send frame only if RequestChannelFrame was sent - if (receivers.remove(streamId, receiver)) { - sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); - } - }); + @Override + void hookOnTerminal(SignalType signalType) { + if (signalType == SignalType.ON_ERROR) { + upstreamSubscriber.cancel(); + } + receivers.remove(streamId, receiver); + } + + @Override + public void cancel() { + upstreamSubscriber.cancel(); + super.cancel(); + } + })); } private Mono handleMetadataPush(Payload payload) { @@ -552,14 +529,12 @@ private Mono handleMetadataPush(Payload payload) { new IllegalStateException("MetadataPushMono allows only a single subscriber")); } - return Mono.empty() - .doOnSubscribe( - (__) -> { - ByteBuf metadataPushFrame = - MetadataPushFrameFlyweight.encodeReleasingPayload(allocator, payload); + ByteBuf metadataPushFrame = + MetadataPushFrameFlyweight.encodeReleasingPayload(allocator, payload); - sendProcessor.onNextPrioritized(metadataPushFrame); - }); + sendProcessor.onNextPrioritized(metadataPushFrame); + + return Mono.empty(); }); } @@ -757,14 +732,6 @@ private void terminate(Throwable e) { onClose.onError(e); } - private void removeStreamReceiver(int streamId) { - /*on termination receivers are explicitly cleared to avoid removing from map while iterating over one - of its views*/ - if (terminationError == null) { - receivers.remove(streamId); - } - } - private void handleSendProcessorError(Throwable t) { connection.dispose(); } diff --git a/rsocket-core/src/main/java/io/rsocket/core/RequestOperator.java b/rsocket-core/src/main/java/io/rsocket/core/RequestOperator.java new file mode 100644 index 000000000..05f8d6b3c --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/core/RequestOperator.java @@ -0,0 +1,188 @@ +package io.rsocket.core; + +import io.rsocket.Payload; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.publisher.Operators; +import reactor.core.publisher.SignalType; +import reactor.util.context.Context; + +/** + * This is a support class for handling of request input, intended for use with {@link + * Operators#lift}. It ensures serial execution of cancellation vs first request signals and also + * provides hooks for separate handling of first vs subsequent {@link Subscription#request} + * invocations. + */ +abstract class RequestOperator + implements CoreSubscriber, Fuseable.QueueSubscription { + + final CoreSubscriber actual; + + Subscription s; + Fuseable.QueueSubscription qs; + + int streamId; + boolean firstRequest = true; + + volatile int wip; + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(RequestOperator.class, "wip"); + + RequestOperator(CoreSubscriber actual) { + this.actual = actual; + } + + /** + * Optional hook executed exactly once on the first {@link Subscription#request) invocation + * and right after the {@link Subscription#request} was propagated to the upstream subscription. + * + *

    Note: this hook may not be invoked if cancellation happened before this invocation + */ + void hookOnFirstRequest(long n) {} + + /** + * Optional hook executed after the {@link Subscription#request} was propagated to the upstream + * subscription and excludes the first {@link Subscription#request} invocation. + */ + void hookOnRemainingRequests(long n) {} + + /** Optional hook executed after this {@link Subscription} cancelling. */ + void hookOnCancel() {} + + /** + * Optional hook executed after {@link org.reactivestreams.Subscriber} termination events + * (onError, onComplete). + * + * @param signalType the type of termination event that triggered the hook ({@link + * SignalType#ON_ERROR} or {@link SignalType#ON_COMPLETE}) + */ + void hookOnTerminal(SignalType signalType) {} + + @Override + public Context currentContext() { + return actual.currentContext(); + } + + @Override + public void request(long n) { + this.s.request(n); + if (!firstRequest) { + try { + this.hookOnRemainingRequests(n); + } catch (Throwable throwable) { + onError(throwable); + } + return; + } + this.firstRequest = false; + + if (WIP.getAndIncrement(this) != 0) { + return; + } + int missed = 1; + + boolean firstLoop = true; + for (; ; ) { + if (firstLoop) { + firstLoop = false; + try { + this.hookOnFirstRequest(n); + } catch (Throwable throwable) { + onError(throwable); + return; + } + } else { + try { + this.hookOnCancel(); + } catch (Throwable throwable) { + onError(throwable); + } + return; + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + return; + } + } + } + + @Override + public void cancel() { + this.s.cancel(); + + if (WIP.getAndIncrement(this) != 0) { + return; + } + + hookOnCancel(); + } + + @Override + @SuppressWarnings("unchecked") + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + if (s instanceof Fuseable.QueueSubscription) { + this.qs = (Fuseable.QueueSubscription) s; + } + this.actual.onSubscribe(this); + } + } + + @Override + public void onNext(Payload t) { + this.actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + this.actual.onError(t); + try { + this.hookOnTerminal(SignalType.ON_ERROR); + } catch (Throwable throwable) { + Operators.onErrorDropped(throwable, currentContext()); + } + } + + @Override + public void onComplete() { + this.actual.onComplete(); + try { + this.hookOnTerminal(SignalType.ON_COMPLETE); + } catch (Throwable throwable) { + Operators.onErrorDropped(throwable, currentContext()); + } + } + + @Override + public int requestFusion(int requestedMode) { + if (this.qs != null) { + return this.qs.requestFusion(requestedMode); + } else { + return Fuseable.NONE; + } + } + + @Override + public Payload poll() { + return this.qs.poll(); + } + + @Override + public int size() { + return this.qs.size(); + } + + @Override + public boolean isEmpty() { + return this.qs.isEmpty(); + } + + @Override + public void clear() { + this.qs.clear(); + } +} diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java index fc87fc721..4a9d907fa 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java @@ -22,7 +22,9 @@ import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.frame.FrameHeaderFlyweight; import io.rsocket.frame.FrameType; +import io.rsocket.frame.PayloadFrameFlyweight; import io.rsocket.frame.decoder.PayloadDecoder; +import io.rsocket.internal.subscriber.AssertSubscriber; import io.rsocket.lease.RequesterLeaseHandler; import io.rsocket.test.util.TestDuplexConnection; import io.rsocket.util.DefaultPayload; @@ -40,6 +42,7 @@ import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.test.StepVerifier; +import reactor.test.util.RaceTestUtils; class RSocketRequesterSubscribersTest { @@ -89,6 +92,33 @@ void singleSubscriber(Function> interaction) { Assertions.assertThat(requestFramesCount(connection.getSent())).isEqualTo(1); } + @ParameterizedTest + @MethodSource("allInteractions") + void singleSubscriberInCaseOfRacing(Function> interaction) { + for (int i = 1; i < 20000; i += 2) { + Flux response = Flux.from(interaction.apply(rSocketRequester)); + AssertSubscriber assertSubscriberA = AssertSubscriber.create(); + AssertSubscriber assertSubscriberB = AssertSubscriber.create(); + + RaceTestUtils.race( + () -> response.subscribe(assertSubscriberA), () -> response.subscribe(assertSubscriberB)); + + connection.addToReceivedBuffer(PayloadFrameFlyweight.encodeComplete(connection.alloc(), i)); + + assertSubscriberA.assertTerminated(); + assertSubscriberB.assertTerminated(); + + Assertions.assertThat(new AssertSubscriber[] {assertSubscriberA, assertSubscriberB}) + .anySatisfy(as -> as.assertError(IllegalStateException.class)); + + Assertions.assertThat(connection.getSent()) + .hasSize(1) + .first() + .matches(bb -> REQUEST_TYPES.contains(FrameHeaderFlyweight.frameType(bb))); + connection.clearSendReceiveBuffers(); + } + } + @ParameterizedTest @MethodSource("allInteractions") void singleSubscriberInteractionsAreLazy(Function> interaction) { diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java index b6067cdeb..d7cd8c24b 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java @@ -34,6 +34,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.util.CharsetUtil; import io.netty.util.ReferenceCountUtil; @@ -50,7 +51,9 @@ import io.rsocket.frame.FrameType; import io.rsocket.frame.PayloadFrameFlyweight; import io.rsocket.frame.RequestChannelFrameFlyweight; +import io.rsocket.frame.RequestFireAndForgetFrameFlyweight; import io.rsocket.frame.RequestNFrameFlyweight; +import io.rsocket.frame.RequestResponseFrameFlyweight; import io.rsocket.frame.RequestStreamFrameFlyweight; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.internal.subscriber.AssertSubscriber; @@ -534,7 +537,8 @@ private static Stream racingCases() { RaceTestUtils.race(() -> as.request(1), as::cancel); // ensures proper frames order if (rule.connection.getSent().size() > 0) { - Assertions.assertThat(rule.connection.getSent()).hasSize(2); + // + // Assertions.assertThat(rule.connection.getSent()).hasSize(2); Assertions.assertThat(rule.connection.getSent()) .element(0) .matches( @@ -771,6 +775,155 @@ static Stream encodeDecodePayloadCases() { Arguments.of(REQUEST_CHANNEL, 5, 5)); } + @Test + public void ensuresThatNoOpsMustHappenUntilSubscriptionInCaseOfFnfCall() { + Payload payload1 = ByteBufPayload.create("abc1"); + Mono fnf1 = rule.socket.fireAndForget(payload1); + + Payload payload2 = ByteBufPayload.create("abc2"); + Mono fnf2 = rule.socket.fireAndForget(payload2); + + Assertions.assertThat(rule.connection.getSent()).isEmpty(); + + // checks that fnf2 should have id 1 even though it was generated later than fnf1 + AssertSubscriber voidAssertSubscriber2 = fnf2.subscribeWith(AssertSubscriber.create(0)); + voidAssertSubscriber2.assertTerminated().assertNoError(); + Assertions.assertThat(rule.connection.getSent()) + .hasSize(1) + .first() + .matches(bb -> frameType(bb) == REQUEST_FNF) + .matches(bb -> FrameHeaderFlyweight.streamId(bb) == 1) + // ensures that this is fnf1 with abc2 data + .matches( + bb -> + ByteBufUtil.equals( + RequestFireAndForgetFrameFlyweight.data(bb), + Unpooled.wrappedBuffer("abc2".getBytes()))) + .matches(ReferenceCounted::release); + + rule.connection.clearSendReceiveBuffers(); + + // checks that fnf1 should have id 3 even though it was generated earlier + AssertSubscriber voidAssertSubscriber1 = fnf1.subscribeWith(AssertSubscriber.create(0)); + voidAssertSubscriber1.assertTerminated().assertNoError(); + Assertions.assertThat(rule.connection.getSent()) + .hasSize(1) + .first() + .matches(bb -> frameType(bb) == REQUEST_FNF) + .matches(bb -> FrameHeaderFlyweight.streamId(bb) == 3) + // ensures that this is fnf1 with abc1 data + .matches( + bb -> + ByteBufUtil.equals( + RequestFireAndForgetFrameFlyweight.data(bb), + Unpooled.wrappedBuffer("abc1".getBytes()))) + .matches(ReferenceCounted::release); + } + + @ParameterizedTest + @MethodSource("requestNInteractions") + public void ensuresThatNoOpsMustHappenUntilFirstRequestN( + FrameType frameType, BiFunction> interaction) { + Payload payload1 = ByteBufPayload.create("abc1"); + Publisher interaction1 = interaction.apply(rule, payload1); + + Payload payload2 = ByteBufPayload.create("abc2"); + Publisher interaction2 = interaction.apply(rule, payload2); + + Assertions.assertThat(rule.connection.getSent()).isEmpty(); + + AssertSubscriber assertSubscriber1 = AssertSubscriber.create(0); + interaction1.subscribe(assertSubscriber1); + AssertSubscriber assertSubscriber2 = AssertSubscriber.create(0); + interaction2.subscribe(assertSubscriber2); + assertSubscriber1.assertNotTerminated().assertNoError(); + assertSubscriber2.assertNotTerminated().assertNoError(); + // even though we subscribed, nothing should happen until the first requestN + Assertions.assertThat(rule.connection.getSent()).isEmpty(); + + // first request on the second interaction to ensure that stream id issuing on the first request + assertSubscriber2.request(1); + + Assertions.assertThat(rule.connection.getSent()) + .hasSize(1) + .first() + .matches(bb -> frameType(bb) == frameType) + .matches( + bb -> FrameHeaderFlyweight.streamId(bb) == 1, + "Expected to have stream ID {1} but got {" + + FrameHeaderFlyweight.streamId(rule.connection.getSent().iterator().next()) + + "}") + .matches( + bb -> { + switch (frameType) { + case REQUEST_RESPONSE: + return ByteBufUtil.equals( + RequestResponseFrameFlyweight.data(bb), + Unpooled.wrappedBuffer("abc2".getBytes())); + case REQUEST_STREAM: + return ByteBufUtil.equals( + RequestStreamFrameFlyweight.data(bb), + Unpooled.wrappedBuffer("abc2".getBytes())); + case REQUEST_CHANNEL: + return ByteBufUtil.equals( + RequestChannelFrameFlyweight.data(bb), + Unpooled.wrappedBuffer("abc2".getBytes())); + } + + return false; + }) + .matches(ReferenceCounted::release); + + rule.connection.clearSendReceiveBuffers(); + + assertSubscriber1.request(1); + Assertions.assertThat(rule.connection.getSent()) + .hasSize(1) + .first() + .matches(bb -> frameType(bb) == frameType) + .matches( + bb -> FrameHeaderFlyweight.streamId(bb) == 3, + "Expected to have stream ID {1} but got {" + + FrameHeaderFlyweight.streamId(rule.connection.getSent().iterator().next()) + + "}") + .matches( + bb -> { + switch (frameType) { + case REQUEST_RESPONSE: + return ByteBufUtil.equals( + RequestResponseFrameFlyweight.data(bb), + Unpooled.wrappedBuffer("abc1".getBytes())); + case REQUEST_STREAM: + return ByteBufUtil.equals( + RequestStreamFrameFlyweight.data(bb), + Unpooled.wrappedBuffer("abc1".getBytes())); + case REQUEST_CHANNEL: + return ByteBufUtil.equals( + RequestChannelFrameFlyweight.data(bb), + Unpooled.wrappedBuffer("abc1".getBytes())); + } + + return false; + }) + .matches(ReferenceCounted::release); + } + + private static Stream requestNInteractions() { + return Stream.of( + Arguments.of( + REQUEST_RESPONSE, + (BiFunction>) + (rule, payload) -> rule.socket.requestResponse(payload)), + Arguments.of( + REQUEST_STREAM, + (BiFunction>) + (rule, payload) -> rule.socket.requestStream(payload)), + Arguments.of( + REQUEST_CHANNEL, + (BiFunction>) + (rule, payload) -> rule.socket.requestChannel(Flux.just(payload)))); + } + public int sendRequestResponse(Publisher response) { Subscriber sub = TestSubscriber.create(); response.subscribe(sub); diff --git a/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java b/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java index db72c7775..4d5cdc0d5 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java @@ -20,6 +20,7 @@ import java.time.Duration; import java.util.ArrayList; import java.util.List; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; import reactor.core.publisher.UnicastProcessor; @@ -47,6 +48,7 @@ void responderRejectSetup() { } @Test + @Disabled("FIXME: needs to be revised") void requesterStreamsTerminatedOnZeroErrorFrame() { LeaksTrackingByteBufAllocator allocator = LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); From b60020e7d5b451313c6a55522ec6029df2d3bb89 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Mon, 4 May 2020 11:00:23 +0300 Subject: [PATCH 47/62] provides `Payload` `refCnt` verification (#809) --- .../io/rsocket/core/RSocketRequester.java | 20 ++++++++ .../io/rsocket/core/RSocketResponder.java | 37 +++++++++++---- .../io/rsocket/core/RSocketRequesterTest.java | 25 ++++++++++ .../io/rsocket/core/RSocketResponderTest.java | 46 +++++++++++++++++++ 4 files changed, 118 insertions(+), 10 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index fabea217b..ced250620 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -195,6 +195,10 @@ public Mono onClose() { } private Mono handleFireAndForget(Payload payload) { + if (payload.refCnt() <= 0) { + return Mono.error(new IllegalReferenceCountException()); + } + Throwable err = checkAvailable(); if (err != null) { payload.release(); @@ -227,6 +231,10 @@ private Mono handleFireAndForget(Payload payload) { } private Mono handleRequestResponse(final Payload payload) { + if (payload.refCnt() <= 0) { + return Mono.error(new IllegalReferenceCountException()); + } + Throwable err = checkAvailable(); if (err != null) { payload.release(); @@ -289,6 +297,10 @@ public void hookOnTerminal(SignalType signalType) { } private Flux handleRequestStream(final Payload payload) { + if (payload.refCnt() <= 0) { + return Flux.error(new IllegalReferenceCountException()); + } + Throwable err = checkAvailable(); if (err != null) { payload.release(); @@ -371,6 +383,10 @@ private Flux handleChannel(Flux request) { (s, flux) -> { Payload payload = s.get(); if (payload != null) { + if (payload.refCnt() <= 0) { + return Mono.error(new IllegalReferenceCountException()); + } + if (!PayloadValidationUtils.isValid(mtu, payload)) { payload.release(); final IllegalArgumentException t = @@ -509,6 +525,10 @@ public void cancel() { } private Mono handleMetadataPush(Payload payload) { + if (payload.refCnt() <= 0) { + return Mono.error(new IllegalReferenceCountException()); + } + Throwable err = this.terminationError; if (err != null) { payload.release(); diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java index f5c4aecec..2f073ba8a 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java @@ -450,8 +450,32 @@ protected void hookOnSubscribe(Subscription s) { @Override protected void hookOnNext(Payload payload) { - if (!PayloadValidationUtils.isValid(mtu, payload)) { - payload.release(); + try { + if (!PayloadValidationUtils.isValid(mtu, payload)) { + payload.release(); + // specifically for requestChannel case so when Payload is invalid we will not be + // sending CancelFrame and ErrorFrame + // Note: CancelFrame is redundant and due to spec + // (https://github.com/rsocket/rsocket/blob/master/Protocol.md#request-channel) + // Upon receiving an ERROR[APPLICATION_ERROR|REJECTED|CANCELED|INVALID], the stream + // is + // terminated on both Requester and Responder. + // Upon sending an ERROR[APPLICATION_ERROR|REJECTED|CANCELED|INVALID], the stream is + // terminated on both the Requester and Responder. + if (requestChannel != null) { + channelProcessors.remove(streamId, requestChannel); + } + cancel(); + final IllegalArgumentException t = + new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE); + handleError(streamId, t); + return; + } + + ByteBuf byteBuf = + PayloadFrameFlyweight.encodeNextReleasingPayload(allocator, streamId, payload); + sendProcessor.onNext(byteBuf); + } catch (Throwable e) { // specifically for requestChannel case so when Payload is invalid we will not be // sending CancelFrame and ErrorFrame // Note: CancelFrame is redundant and due to spec @@ -464,15 +488,8 @@ protected void hookOnNext(Payload payload) { channelProcessors.remove(streamId, requestChannel); } cancel(); - final IllegalArgumentException t = - new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE); - handleError(streamId, t); - return; + handleError(streamId, e); } - - ByteBuf byteBuf = - PayloadFrameFlyweight.encodeNextReleasingPayload(allocator, streamId, payload); - sendProcessor.onNext(byteBuf); } @Override diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java index d7cd8c24b..2117e195d 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java @@ -37,6 +37,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.util.CharsetUtil; +import io.netty.util.IllegalReferenceCountException; import io.netty.util.ReferenceCountUtil; import io.netty.util.ReferenceCounted; import io.rsocket.Payload; @@ -775,6 +776,30 @@ static Stream encodeDecodePayloadCases() { Arguments.of(REQUEST_CHANNEL, 5, 5)); } + @ParameterizedTest + @MethodSource("refCntCases") + public void ensureSendsErrorOnIllegalRefCntPayload( + BiFunction> sourceProducer) { + Payload invalidPayload = ByteBufPayload.create("test", "test"); + invalidPayload.release(); + + Publisher source = sourceProducer.apply(invalidPayload, rule.socket); + + StepVerifier.create(source, 0) + .expectError(IllegalReferenceCountException.class) + .verify(Duration.ofMillis(100)); + } + + private static Stream>> refCntCases() { + return Stream.of( + (p, r) -> r.fireAndForget(p), + (p, r) -> r.requestResponse(p), + (p, r) -> r.requestStream(p), + (p, r) -> r.requestChannel(Mono.just(p)), + (p, r) -> + r.requestChannel(Flux.just(EmptyPayload.INSTANCE, p).doOnSubscribe(s -> s.request(1)))); + } + @Test public void ensuresThatNoOpsMustHappenUntilSubscriptionInCaseOfFnfCall() { Payload payload1 = ByteBufPayload.create("abc1"); diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java index 2dbf6715b..9ec2a2df1 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java @@ -18,6 +18,7 @@ import static io.rsocket.core.PayloadValidationUtils.INVALID_PAYLOAD_ERROR_MESSAGE; import static io.rsocket.frame.FrameHeaderFlyweight.frameType; +import static io.rsocket.frame.FrameType.ERROR; import static io.rsocket.frame.FrameType.REQUEST_CHANNEL; import static io.rsocket.frame.FrameType.REQUEST_FNF; import static io.rsocket.frame.FrameType.REQUEST_N; @@ -711,6 +712,51 @@ static Stream encodeDecodePayloadCases() { Arguments.of(REQUEST_CHANNEL, 5, 5)); } + @ParameterizedTest + @MethodSource("refCntCases") + public void ensureSendsErrorOnIllegalRefCntPayload(FrameType frameType) { + rule.setAcceptingSocket( + new RSocket() { + @Override + public Mono requestResponse(Payload payload) { + Payload invalidPayload = ByteBufPayload.create("test", "test"); + invalidPayload.release(); + return Mono.just(invalidPayload); + } + + @Override + public Flux requestStream(Payload payload) { + Payload invalidPayload = ByteBufPayload.create("test", "test"); + invalidPayload.release(); + return Flux.just(invalidPayload); + } + + @Override + public Flux requestChannel(Publisher payloads) { + Payload invalidPayload = ByteBufPayload.create("test", "test"); + invalidPayload.release(); + return Flux.just(invalidPayload); + } + }); + + rule.sendRequest(1, frameType); + + Assertions.assertThat(rule.connection.getSent()) + .hasSize(1) + .first() + .matches( + bb -> frameType(bb) == ERROR, + "Expect frame type to be {" + + ERROR + + "} but was {" + + frameType(rule.connection.getSent().iterator().next()) + + "}"); + } + + private static Stream refCntCases() { + return Stream.of(REQUEST_RESPONSE, REQUEST_STREAM, REQUEST_CHANNEL); + } + public static class ServerSocketRule extends AbstractSocketRule { private RSocket acceptingSocket; From 202e27f9d8c9e3b74c154f3d7cb6f3f1d45036d9 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Mon, 4 May 2020 13:58:54 +0300 Subject: [PATCH 48/62] provides ordered stream id issuing (#811) --- .../io/rsocket/core/RSocketConnector.java | 4 +- .../io/rsocket/core/RSocketRequester.java | 248 +++++++++--------- .../java/io/rsocket/core/RSocketServer.java | 4 +- .../test/java/io/rsocket/TestScheduler.java | 80 ++++++ .../java/io/rsocket/core/KeepAliveTest.java | 7 +- .../io/rsocket/core/RSocketLeaseTest.java | 4 +- .../core/RSocketRequesterSubscribersTest.java | 25 +- .../io/rsocket/core/RSocketRequesterTest.java | 48 +++- .../java/io/rsocket/core/RSocketTest.java | 4 +- .../io/rsocket/core/SetupRejectionTest.java | 6 +- 10 files changed, 292 insertions(+), 138 deletions(-) create mode 100644 rsocket-core/src/test/java/io/rsocket/TestScheduler.java diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java index a7eed8c76..4e47109cf 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java @@ -42,6 +42,7 @@ import java.util.function.Supplier; import reactor.core.Disposable; import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; import reactor.util.retry.Retry; public class RSocketConnector { @@ -293,7 +294,8 @@ public Mono connect(Supplier transportSupplier) { (int) keepAliveInterval.toMillis(), (int) keepAliveMaxLifeTime.toMillis(), keepAliveHandler, - requesterLeaseHandler); + requesterLeaseHandler, + Schedulers.single(Schedulers.parallel())); RSocket wrappedRSocketRequester = interceptors.initRequester(rSocketRequester); diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index ced250620..a2bd3d9fd 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -67,6 +67,7 @@ import reactor.core.publisher.Operators; import reactor.core.publisher.SignalType; import reactor.core.publisher.UnicastProcessor; +import reactor.core.scheduler.Scheduler; import reactor.util.concurrent.Queues; /** @@ -105,6 +106,7 @@ class RSocketRequester implements RSocket { private final KeepAliveFramesAcceptor keepAliveFramesAcceptor; private volatile Throwable terminationError; private final MonoProcessor onClose; + private final Scheduler serialScheduler; RSocketRequester( DuplexConnection connection, @@ -115,7 +117,8 @@ class RSocketRequester implements RSocket { int keepAliveTickPeriod, int keepAliveAckTimeout, @Nullable KeepAliveHandler keepAliveHandler, - RequesterLeaseHandler leaseHandler) { + RequesterLeaseHandler leaseHandler, + Scheduler serialScheduler) { this.connection = connection; this.allocator = connection.alloc(); this.payloadDecoder = payloadDecoder; @@ -126,6 +129,7 @@ class RSocketRequester implements RSocket { this.senders = new SynchronizedIntObjectHashMap<>(); this.receivers = new SynchronizedIntObjectHashMap<>(); this.onClose = MonoProcessor.create(); + this.serialScheduler = serialScheduler; // DO NOT Change the order here. The Send processor must be subscribed to before receiving this.sendProcessor = new UnboundedProcessor<>(); @@ -212,22 +216,23 @@ private Mono handleFireAndForget(Payload payload) { final AtomicBoolean once = new AtomicBoolean(); - return Mono.defer( - () -> { - if (once.getAndSet(true)) { - return Mono.error( - new IllegalStateException("FireAndForgetMono allows only a single subscriber")); - } + return Mono.defer( + () -> { + if (once.getAndSet(true)) { + return Mono.error( + new IllegalStateException("FireAndForgetMono allows only a single subscriber")); + } - final int streamId = streamIdSupplier.nextStreamId(receivers); - final ByteBuf requestFrame = - RequestFireAndForgetFrameFlyweight.encodeReleasingPayload( - allocator, streamId, payload); + final int streamId = streamIdSupplier.nextStreamId(receivers); + final ByteBuf requestFrame = + RequestFireAndForgetFrameFlyweight.encodeReleasingPayload( + allocator, streamId, payload); - sendProcessor.onNext(requestFrame); + sendProcessor.onNext(requestFrame); - return Mono.empty(); - }); + return Mono.empty(); + }) + .subscribeOn(serialScheduler); } private Mono handleRequestResponse(final Payload payload) { @@ -292,6 +297,7 @@ public void hookOnTerminal(SignalType signalType) { receivers.remove(streamId, receiver); } })) + .subscribeOn(serialScheduler) .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER); }); } @@ -368,6 +374,7 @@ void hookOnTerminal(SignalType signalType) { receivers.remove(streamId); } })) + .subscribeOn(serialScheduler, false) .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER); }); } @@ -408,120 +415,125 @@ private Flux handleChannel(Payload initialPayload, Flux receiver = UnicastProcessor.create(); - return receiver.transform( - Operators.lift( - (s, actual) -> - new RequestOperator(actual) { + return receiver + .transform( + Operators.lift( + (s, actual) -> + new RequestOperator(actual) { - final BaseSubscriber upstreamSubscriber = - new BaseSubscriber() { + final BaseSubscriber upstreamSubscriber = + new BaseSubscriber() { - boolean first = true; + boolean first = true; - @Override - protected void hookOnSubscribe(Subscription subscription) { - // noops - } + @Override + protected void hookOnSubscribe(Subscription subscription) { + // noops + } - @Override - protected void hookOnNext(Payload payload) { - if (first) { - // need to skip first since we have already sent it - // no need to release it since it was released earlier on the request - // establishment - // phase - first = false; - request(1); - return; - } - if (!PayloadValidationUtils.isValid(mtu, payload)) { - payload.release(); - cancel(); - final IllegalArgumentException t = - new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE); - errorConsumer.accept(t); - // no need to send any errors. - sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); - receiver.onError(t); - return; - } - final ByteBuf frame = - PayloadFrameFlyweight.encodeNextReleasingPayload( - allocator, streamId, payload); - - sendProcessor.onNext(frame); - } + @Override + protected void hookOnNext(Payload payload) { + if (first) { + // need to skip first since we have already sent it + // no need to release it since it was released earlier on the + // request + // establishment + // phase + first = false; + request(1); + return; + } + if (!PayloadValidationUtils.isValid(mtu, payload)) { + payload.release(); + cancel(); + final IllegalArgumentException t = + new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE); + errorConsumer.accept(t); + // no need to send any errors. + sendProcessor.onNext( + CancelFrameFlyweight.encode(allocator, streamId)); + receiver.onError(t); + return; + } + final ByteBuf frame = + PayloadFrameFlyweight.encodeNextReleasingPayload( + allocator, streamId, payload); + + sendProcessor.onNext(frame); + } + + @Override + protected void hookOnComplete() { + ByteBuf frame = + PayloadFrameFlyweight.encodeComplete(allocator, streamId); + sendProcessor.onNext(frame); + } + + @Override + protected void hookOnError(Throwable t) { + ByteBuf frame = ErrorFrameFlyweight.encode(allocator, streamId, t); + sendProcessor.onNext(frame); + receiver.onError(t); + } - @Override - protected void hookOnComplete() { - ByteBuf frame = PayloadFrameFlyweight.encodeComplete(allocator, streamId); - sendProcessor.onNext(frame); + @Override + protected void hookFinally(SignalType type) { + senders.remove(streamId, this); + } + }; + + @Override + void hookOnFirstRequest(long n) { + final int streamId = streamIdSupplier.nextStreamId(receivers); + this.streamId = streamId; + + final ByteBuf frame = + RequestChannelFrameFlyweight.encodeReleasingPayload( + allocator, streamId, false, n, initialPayload); + + senders.put(streamId, upstreamSubscriber); + receivers.put(streamId, receiver); + + inboundFlux + .limitRate(Queues.SMALL_BUFFER_SIZE) + .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER) + .subscribe(upstreamSubscriber); + + sendProcessor.onNext(frame); + } + + @Override + void hookOnRemainingRequests(long n) { + if (receiver.isDisposed()) { + return; } - @Override - protected void hookOnError(Throwable t) { - ByteBuf frame = ErrorFrameFlyweight.encode(allocator, streamId, t); - sendProcessor.onNext(frame); - receiver.onError(t); + sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); + } + + @Override + void hookOnCancel() { + senders.remove(streamId, upstreamSubscriber); + if (receivers.remove(streamId, receiver)) { + sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); } + } - @Override - protected void hookFinally(SignalType type) { - senders.remove(streamId, this); + @Override + void hookOnTerminal(SignalType signalType) { + if (signalType == SignalType.ON_ERROR) { + upstreamSubscriber.cancel(); } - }; - - @Override - void hookOnFirstRequest(long n) { - final int streamId = streamIdSupplier.nextStreamId(receivers); - this.streamId = streamId; - - final ByteBuf frame = - RequestChannelFrameFlyweight.encodeReleasingPayload( - allocator, streamId, false, n, initialPayload); - - senders.put(streamId, upstreamSubscriber); - receivers.put(streamId, receiver); - - inboundFlux - .limitRate(Queues.SMALL_BUFFER_SIZE) - .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER) - .subscribe(upstreamSubscriber); - - sendProcessor.onNext(frame); - } - - @Override - void hookOnRemainingRequests(long n) { - if (receiver.isDisposed()) { - return; - } - - sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); - } - - @Override - void hookOnCancel() { - senders.remove(streamId, upstreamSubscriber); - if (receivers.remove(streamId, receiver)) { - sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); - } - } - - @Override - void hookOnTerminal(SignalType signalType) { - if (signalType == SignalType.ON_ERROR) { - upstreamSubscriber.cancel(); - } - receivers.remove(streamId, receiver); - } - - @Override - public void cancel() { - upstreamSubscriber.cancel(); - super.cancel(); - } - })); + receivers.remove(streamId, receiver); + } + + @Override + public void cancel() { + upstreamSubscriber.cancel(); + super.cancel(); + } + })) + .subscribeOn(serialScheduler, false); } private Mono handleMetadataPush(Payload payload) { diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java index 19f0c5008..d5d8cee0f 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java @@ -39,6 +39,7 @@ import java.util.function.Consumer; import java.util.function.Supplier; import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; public final class RSocketServer { private static final String SERVER_TAG = "server"; @@ -222,7 +223,8 @@ private Mono acceptSetup( setupPayload.keepAliveInterval(), setupPayload.keepAliveMaxLifetime(), keepAliveHandler, - requesterLeaseHandler); + requesterLeaseHandler, + Schedulers.single(Schedulers.parallel())); RSocket wrappedRSocketRequester = interceptors.initRequester(rSocketRequester); diff --git a/rsocket-core/src/test/java/io/rsocket/TestScheduler.java b/rsocket-core/src/test/java/io/rsocket/TestScheduler.java new file mode 100644 index 000000000..7bc98d45d --- /dev/null +++ b/rsocket-core/src/test/java/io/rsocket/TestScheduler.java @@ -0,0 +1,80 @@ +package io.rsocket; + +import java.util.Queue; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import reactor.core.Disposable; +import reactor.core.Disposables; +import reactor.core.Exceptions; +import reactor.core.scheduler.Scheduler; +import reactor.util.concurrent.Queues; + +/** + * This is an implementation of scheduler which allows task execution on the caller thread or + * scheduling it for thread which are currently working (with "work stealing" behaviour) + */ +public final class TestScheduler implements Scheduler { + + public static final Scheduler INSTANCE = new TestScheduler(); + + volatile int wip; + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(TestScheduler.class, "wip"); + + final Worker sharedWorker = new TestWorker(this); + final Queue tasks = Queues.unboundedMultiproducer().get(); + + private TestScheduler() {} + + @Override + public Disposable schedule(Runnable task) { + tasks.offer(task); + if (WIP.getAndIncrement(this) != 0) { + return Disposables.never(); + } + + int missed = 1; + + for (; ; ) { + for (; ; ) { + Runnable runnable = tasks.poll(); + + if (runnable == null) { + break; + } + + try { + runnable.run(); + } catch (Throwable t) { + Exceptions.throwIfFatal(t); + } + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + return Disposables.never(); + } + } + } + + @Override + public Worker createWorker() { + return sharedWorker; + } + + static class TestWorker implements Worker { + + final TestScheduler parent; + + TestWorker(TestScheduler parent) { + this.parent = parent; + } + + @Override + public Disposable schedule(Runnable task) { + return parent.schedule(task); + } + + @Override + public void dispose() {} + } +} diff --git a/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java b/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java index e8f3f4190..7e465db08 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java @@ -23,6 +23,7 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import io.rsocket.RSocket; +import io.rsocket.TestScheduler; import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.exceptions.ConnectionErrorException; import io.rsocket.frame.FrameHeaderFlyweight; @@ -67,7 +68,8 @@ static RSocketState requester(int tickPeriod, int timeout) { tickPeriod, timeout, new DefaultKeepAliveHandler(connection), - RequesterLeaseHandler.None); + RequesterLeaseHandler.None, + TestScheduler.INSTANCE); return new RSocketState(rSocket, errors, allocator, connection); } @@ -94,7 +96,8 @@ static ResumableRSocketState resumableRequester(int tickPeriod, int timeout) { tickPeriod, timeout, new ResumableKeepAliveHandler(resumableConnection), - RequesterLeaseHandler.None); + RequesterLeaseHandler.None, + TestScheduler.INSTANCE); return new ResumableRSocketState(rSocket, errors, connection, resumableConnection, allocator); } diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java index 51f5afc24..04d5fe174 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java @@ -28,6 +28,7 @@ import io.netty.buffer.Unpooled; import io.rsocket.Payload; import io.rsocket.RSocket; +import io.rsocket.TestScheduler; import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.exceptions.Exceptions; import io.rsocket.frame.FrameHeaderFlyweight; @@ -98,7 +99,8 @@ void setUp() { 0, 0, null, - requesterLeaseHandler); + requesterLeaseHandler, + TestScheduler.INSTANCE); RSocket mockRSocketHandler = mock(RSocket.class); when(mockRSocketHandler.metadataPush(any())).thenReturn(Mono.empty()); diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java index 4a9d907fa..3e7479af3 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java @@ -19,6 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.rsocket.RSocket; +import io.rsocket.TestScheduler; import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.frame.FrameHeaderFlyweight; import io.rsocket.frame.FrameType; @@ -28,7 +29,6 @@ import io.rsocket.lease.RequesterLeaseHandler; import io.rsocket.test.util.TestDuplexConnection; import io.rsocket.util.DefaultPayload; -import java.time.Duration; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; @@ -41,7 +41,6 @@ import org.junit.jupiter.params.provider.MethodSource; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; -import reactor.test.StepVerifier; import reactor.test.util.RaceTestUtils; class RSocketRequesterSubscribersTest { @@ -73,21 +72,25 @@ void setUp() { 0, 0, null, - RequesterLeaseHandler.None); + RequesterLeaseHandler.None, + TestScheduler.INSTANCE); } @ParameterizedTest @MethodSource("allInteractions") void singleSubscriber(Function> interaction) { Flux response = Flux.from(interaction.apply(rSocketRequester)); - StepVerifier.withVirtualTime(() -> response.take(Duration.ofMillis(10))) - .thenAwait(Duration.ofMillis(10)) - .expectComplete() - .verify(Duration.ofSeconds(5)); - StepVerifier.withVirtualTime(() -> response.take(Duration.ofMillis(10))) - .thenAwait(Duration.ofMillis(10)) - .expectError(IllegalStateException.class) - .verify(Duration.ofSeconds(5)); + + AssertSubscriber assertSubscriberA = AssertSubscriber.create(); + AssertSubscriber assertSubscriberB = AssertSubscriber.create(); + + response.subscribe(assertSubscriberA); + response.subscribe(assertSubscriberB); + + connection.addToReceivedBuffer(PayloadFrameFlyweight.encodeComplete(connection.alloc(), 1)); + + assertSubscriberA.assertTerminated(); + assertSubscriberB.assertTerminated(); Assertions.assertThat(requestFramesCount(connection.getSent())).isEqualTo(1); } diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java index 2117e195d..56e6c9342 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java @@ -42,6 +42,7 @@ import io.netty.util.ReferenceCounted; import io.rsocket.Payload; import io.rsocket.RSocket; +import io.rsocket.TestScheduler; import io.rsocket.exceptions.ApplicationErrorException; import io.rsocket.exceptions.CustomRSocketException; import io.rsocket.exceptions.RejectedSetupException; @@ -949,6 +950,50 @@ private static Stream requestNInteractions() { (rule, payload) -> rule.socket.requestChannel(Flux.just(payload)))); } + @ParameterizedTest + @MethodSource("streamIdRacingCases") + public void ensuresCorrectOrderOfStreamIdIssuingInCaseOfRacing( + BiFunction> interaction1, + BiFunction> interaction2) { + for (int i = 1; i < 10000; i += 4) { + Payload payload = DefaultPayload.create("test"); + Publisher publisher1 = interaction1.apply(rule, payload); + Publisher publisher2 = interaction2.apply(rule, payload); + RaceTestUtils.race( + () -> publisher1.subscribe(AssertSubscriber.create()), + () -> publisher2.subscribe(AssertSubscriber.create())); + + Assertions.assertThat(rule.connection.getSent()) + .extracting(FrameHeaderFlyweight::streamId) + .containsExactly(i, i + 2); + rule.connection.getSent().clear(); + } + } + + public static Stream streamIdRacingCases() { + return Stream.of( + Arguments.of( + (BiFunction>) + (r, p) -> r.socket.fireAndForget(p), + (BiFunction>) + (r, p) -> r.socket.requestResponse(p)), + Arguments.of( + (BiFunction>) + (r, p) -> r.socket.requestResponse(p), + (BiFunction>) + (r, p) -> r.socket.requestStream(p)), + Arguments.of( + (BiFunction>) + (r, p) -> r.socket.requestStream(p), + (BiFunction>) + (r, p) -> r.socket.requestChannel(Flux.just(p))), + Arguments.of( + (BiFunction>) + (r, p) -> r.socket.requestChannel(Flux.just(p)), + (BiFunction>) + (r, p) -> r.socket.fireAndForget(p))); + } + public int sendRequestResponse(Publisher response) { Subscriber sub = TestSubscriber.create(); response.subscribe(sub); @@ -973,7 +1018,8 @@ protected RSocketRequester newRSocket() { 0, 0, null, - RequesterLeaseHandler.None); + RequesterLeaseHandler.None, + TestScheduler.INSTANCE); } public int getStreamIdForRequestType(FrameType expectedFrameType) { diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java index 02c3dfca8..48ce150d6 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java @@ -25,6 +25,7 @@ import io.netty.buffer.ByteBufAllocator; import io.rsocket.Payload; import io.rsocket.RSocket; +import io.rsocket.TestScheduler; import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.exceptions.ApplicationErrorException; import io.rsocket.exceptions.CustomRSocketException; @@ -492,7 +493,8 @@ public Flux requestChannel(Publisher payloads) { 0, 0, null, - RequesterLeaseHandler.None); + RequesterLeaseHandler.None, + TestScheduler.INSTANCE); } public void setRequestAcceptor(RSocket requestAcceptor) { diff --git a/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java b/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java index 4d5cdc0d5..388bfffeb 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java @@ -64,7 +64,8 @@ void requesterStreamsTerminatedOnZeroErrorFrame() { 0, 0, null, - RequesterLeaseHandler.None); + RequesterLeaseHandler.None, + TestScheduler.INSTANCE); String errorMsg = "error"; @@ -101,7 +102,8 @@ void requesterNewStreamsTerminatedAfterZeroErrorFrame() { 0, 0, null, - RequesterLeaseHandler.None); + RequesterLeaseHandler.None, + TestScheduler.INSTANCE); conn.addToReceivedBuffer( ErrorFrameFlyweight.encode( From 4fa73124212c02ffa31fb4d0589cd289b1260446 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 4 May 2020 13:31:26 +0100 Subject: [PATCH 49/62] Add Javadoc to RSocketConnector and RSocketServer (#813) --- .../io/rsocket/core/RSocketConnector.java | 371 ++++++++++++++---- .../java/io/rsocket/core/RSocketServer.java | 228 +++++++++-- .../src/main/java/io/rsocket/core/Resume.java | 105 ++++- .../FragmentationDuplexConnection.java | 2 +- .../core/RSocketServerFragmentationTest.java | 6 +- 5 files changed, 598 insertions(+), 114 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java index 4e47109cf..3d9345c4b 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java @@ -22,6 +22,7 @@ import io.rsocket.Payload; import io.rsocket.RSocket; import io.rsocket.SocketAcceptor; +import io.rsocket.fragmentation.FragmentationDuplexConnection; import io.rsocket.frame.SetupFrameFlyweight; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.internal.ClientServerInputMultiplexer; @@ -40,14 +41,36 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Supplier; +import javax.annotation.Nullable; import reactor.core.Disposable; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; import reactor.util.retry.Retry; +/** + * The main class to use to establish a connection to an RSocket server. + * + *

    To connect over TCP using default settings: + * + *

    {@code
    + * import io.rsocket.transport.netty.client.TcpClientTransport;
    + *
    + * Mono rocketMono =
    + *         RSocketConnector.connectWith(TcpClientTransport.create("localhost", 7000));
    + * }
    + * + *

    To customize connection settings before connecting: + * + *

    {@code
    + * Mono rocketMono =
    + *         RSocketConnector.create()
    + *                 .metadataMimeType("message/x.rsocket.composite-metadata.v0")
    + *                 .dataMimeType("application/cbor")
    + *                 .connect(TcpClientTransport.create("localhost", 7000));
    + * }
    + */ public class RSocketConnector { private static final String CLIENT_TAG = "client"; - private static final int MIN_MTU_SIZE = 64; private static final BiConsumer INVALIDATE_FUNCTION = (r, i) -> r.onClose().subscribe(null, __ -> i.invalidate(), i::invalidate); @@ -55,13 +78,12 @@ public class RSocketConnector { private Payload setupPayload = EmptyPayload.INSTANCE; private String metadataMimeType = "application/binary"; private String dataMimeType = "application/binary"; - - private SocketAcceptor acceptor = SocketAcceptor.with(new RSocket() {}); - private InitializingInterceptorRegistry interceptors = new InitializingInterceptorRegistry(); - private Duration keepAliveInterval = Duration.ofSeconds(20); private Duration keepAliveMaxLifeTime = Duration.ofSeconds(90); + @Nullable private SocketAcceptor acceptor; + private InitializingInterceptorRegistry interceptors = new InitializingInterceptorRegistry(); + private Retry retrySpec; private Resume resume; private Supplier> leasesSupplier; @@ -73,53 +95,109 @@ public class RSocketConnector { private RSocketConnector() {} + /** + * Static factory method to create an {@code RSocketConnector} instance and customize default + * settings before connecting. To connect only, use {@link #connectWith(ClientTransport)}. + */ public static RSocketConnector create() { return new RSocketConnector(); } + /** + * Static factory method to connect with default settings, effectively a shortcut for: + * + *
    +   * RSocketConnector.create().connectWith(transport);
    +   * 
    + * + * @param transport the transport of choice to connect with + * @return a {@code Mono} with the connected RSocket + */ public static Mono connectWith(ClientTransport transport) { return RSocketConnector.create().connect(() -> transport); } + /** + * Provide a {@code Payload} with data and/or metadata for the initial {@code SETUP} frame. Data + * and metadata should be formatted according to the MIME types specified via {@link + * #dataMimeType(String)} and {@link #metadataMimeType(String)}. + * + * @param payload the payload containing data and/or metadata for the {@code SETUP} frame + * @return the same instance for method chaining + * @see SETUP + * Frame + */ public RSocketConnector setupPayload(Payload payload) { - this.setupPayload = payload; + this.setupPayload = Objects.requireNonNull(payload); return this; } + /** + * Set the MIME type to use for formatting payload data on the established connection. This is set + * in the initial {@code SETUP} frame sent to the server. + * + *

    By default this is set to {@code "application/binary"}. + * + * @param dataMimeType the MIME type to be used for payload data + * @return the same instance for method chaining + * @see SETUP + * Frame + */ public RSocketConnector dataMimeType(String dataMimeType) { - this.dataMimeType = dataMimeType; + this.dataMimeType = Objects.requireNonNull(dataMimeType); return this; } + /** + * Set the MIME type to use for formatting payload metadata on the established connection. This is + * set in the initial {@code SETUP} frame sent to the server. + * + *

    For metadata encoding, consider using one of the following encoders: + * + *

      + *
    • {@link io.rsocket.metadata.CompositeMetadataFlyweight Composite Metadata} + *
    • {@link io.rsocket.metadata.TaggingMetadataFlyweight Routing} + *
    • {@link io.rsocket.metadata.security.AuthMetadataFlyweight Authentication} + *
    + * + *

    For more on the above metadata formats, see the corresponding protocol extensions + * + *

    By default this is set to {@code "application/binary"}. + * + * @param metadataMimeType the MIME type to be used for payload metadata + * @return the same instance for method chaining + * @see SETUP + * Frame + */ public RSocketConnector metadataMimeType(String metadataMimeType) { - this.metadataMimeType = metadataMimeType; - return this; - } - - public RSocketConnector interceptors(Consumer consumer) { - consumer.accept(this.interceptors); - return this; - } - - public RSocketConnector acceptor(SocketAcceptor acceptor) { - this.acceptor = acceptor; + this.metadataMimeType = Objects.requireNonNull(metadataMimeType); return this; } /** - * Set the time {@code interval} between KEEPALIVE frames sent by this client, and the {@code - * maxLifeTime} that this client will allow between KEEPALIVE frames from the server before - * assuming it is dead. + * Set the "Time Between {@code KEEPALIVE} Frames" which is how frequently {@code KEEPALIVE} + * frames should be emitted, and the "Max Lifetime" which is how long to allow between {@code + * KEEPALIVE} frames from the remote end before concluding that connectivity is lost. Both + * settings are specified in the initial {@code SETUP} frame sent to the server. The spec mentions + * the following: * - *

    Note that reasonable values for the time interval may vary significantly. For - * server-to-server connections the spec suggests 500ms, while for for mobile-to-server - * connections it suggests 30+ seconds. In addition {@code maxLifeTime} should allow plenty of - * room for multiple missed ticks from the server. + *

      + *
    • For server-to-server connections, a reasonable time interval between client {@code + * KEEPALIVE} frames is 500ms. + *
    • For mobile-to-server connections, the time interval between client {@code KEEPALIVE} + * frames is often > 30,000ms. + *
    * - *

    By default {@code interval} is set to 20 seconds and {@code maxLifeTime} to 90 seconds. + *

    By default these are set to 20 seconds and 90 seconds respectively. * - * @param interval the time between KEEPALIVE frames sent, must be greater than 0. - * @param maxLifeTime the max time between KEEPALIVE frames received, must be greater than 0. + * @param interval how frequently to emit KEEPALIVE frames + * @param maxLifeTime how long to allow between {@code KEEPALIVE} frames from the remote end + * before assuming that connectivity is lost; the value should be generous and allow for + * multiple missed {@code KEEPALIVE} frames. + * @return the same instance for method chaining + * @see SETUP + * Frame */ public RSocketConnector keepAlive(Duration interval, Duration maxLifeTime) { if (!interval.negated().isNegative()) { @@ -134,97 +212,227 @@ public RSocketConnector keepAlive(Duration interval, Duration maxLifeTime) { } /** - * Enables a reconnectable, shared instance of {@code Mono} so every subscriber will - * observe the same RSocket instance up on connection establishment.
    - * For example: + * Configure interception at one of the following levels: + * + *

      + *
    • Transport level + *
    • At the level of accepting new connections + *
    • Performing requests + *
    • Responding to requests + *
    + * + * @param configurer a configurer to customize interception with. + * @return the same instance for method chaining + */ + public RSocketConnector interceptors(Consumer configurer) { + configurer.accept(this.interceptors); + return this; + } + + /** + * Configure a client-side {@link SocketAcceptor} for responding to requests from the server. + * + *

    A full-form example with access to the {@code SETUP} frame and the "sending" RSocket (the + * same as the one returned from {@link #connect(ClientTransport)}): * *

    {@code
    -   * Mono sharedRSocketMono =
    -   *   RSocketConnector.create()
    -   *           .reconnect(Retry.fixedDelay(3, Duration.ofSeconds(1)))
    -   *           .connect(transport);
    +   * Mono rsocketMono =
    +   *     RSocketConnector.create()
    +   *             .acceptor((setup, sendingRSocket) -> Mono.just(new RSocket() {...}))
    +   *             .connect(transport);
    +   * }
    * - * RSocket r1 = sharedRSocketMono.block(); - * RSocket r2 = sharedRSocketMono.block(); + *

    A shortcut example with just the handling RSocket: * - * assert r1 == r2; + *

    {@code
    +   * Mono rsocketMono =
    +   *     RSocketConnector.create()
    +   *             .acceptor(SocketAcceptor.with(new RSocket() {...})))
    +   *             .connect(transport);
    +   * }
    * + *

    A shortcut example handling only request-response: + * + *

    {@code
    +   * Mono rsocketMono =
    +   *     RSocketConnector.create()
    +   *             .acceptor(SocketAcceptor.forRequestResponse(payload -> ...))
    +   *             .connect(transport);
        * }
    * - * Apart of the shared behavior, if the connection is lost, the same {@code Mono} - * instance will transparently re-establish the connection for subsequent subscribers.
    - * For example: + *

    By default, {@code new RSocket(){}} is used which rejects all requests from the server with + * {@link UnsupportedOperationException}. + * + * @param acceptor the acceptor to use for responding to server requests + * @return the same instance for method chaining + */ + public RSocketConnector acceptor(SocketAcceptor acceptor) { + this.acceptor = acceptor; + return this; + } + + /** + * When this is enabled, the connect methods of this class return a special {@code Mono} + * that maintains a single, shared {@code RSocket} for all subscribers: * *

    {@code
    -   * Mono sharedRSocketMono =
    +   * Mono rsocketMono =
        *   RSocketConnector.create()
        *           .reconnect(Retry.fixedDelay(3, Duration.ofSeconds(1)))
        *           .connect(transport);
        *
    -   *  RSocket r1 = sharedRSocketMono.block();
    -   *  RSocket r2 = sharedRSocketMono.block();
    +   *  RSocket r1 = rsocketMono.block();
    +   *  RSocket r2 = rsocketMono.block();
        *
        *  assert r1 == r2;
    +   * }
    * - * r1.dispose() + *

    The {@code RSocket} remains cached until the connection is lost and after that, new attempts + * to subscribe or re-subscribe trigger a reconnect and result in a new shared {@code RSocket}: * - * assert r2.isDisposed() + *

    {@code
    +   * Mono rsocketMono =
    +   *   RSocketConnector.create()
    +   *           .reconnect(Retry.fixedDelay(3, Duration.ofSeconds(1)))
    +   *           .connect(transport);
    +   *
    +   *  RSocket r1 = rsocketMono.block();
    +   *  RSocket r2 = rsocketMono.block();
        *
    -   *  RSocket r3 = sharedRSocketMono.block();
    -   *  RSocket r4 = sharedRSocketMono.block();
    +   *  r1.dispose();
        *
    +   *  RSocket r3 = rsocketMono.block();
    +   *  RSocket r4 = rsocketMono.block();
    +   *
    +   *  assert r1 == r2;
    +   *  assert r3 == r4;
        *  assert r1 != r3;
    -   *  assert r4 == r3;
        *
        * }
    * - * Note, having reconnect() enabled does not eliminate the need to accompany each - * individual request with the corresponding retry logic.
    - * For example: + *

    Downstream subscribers for individual requests still need their own retry logic to determine + * if or when failed requests should be retried which in turn triggers the shared reconnect: * *

    {@code
    -   * Mono sharedRSocketMono =
    +   * Mono rocketMono =
        *   RSocketConnector.create()
        *           .reconnect(Retry.fixedDelay(3, Duration.ofSeconds(1)))
        *           .connect(transport);
        *
    -   *  sharedRSocket.flatMap(rSocket -> rSocket.requestResponse(...))
    -   *               .retryWhen(ownRetry)
    -   *               .subscribe()
    -   *
    +   *  rsocketMono.flatMap(rsocket -> rsocket.requestResponse(...))
    +   *           .retryWhen(Retry.fixedDelay(1, Duration.ofSeconds(5)))
    +   *           .subscribe()
        * }
    * - * @param retrySpec a retry factory applied for {@link Mono#retryWhen(Retry)} - * @return a shared instance of {@code Mono}. + *

    Note: this feature is mutually exclusive with {@link #resume(Resume)}. If + * both are enabled, "resume" takes precedence. Consider using "reconnect" when the server does + * not have "resume" enabled or supported, or when you don't need to incur the overhead of saving + * in-flight frames to be potentially replayed after a reconnect. + * + *

    By default this is not enabled in which case a new connection is obtained per subscriber. + * + * @param retry a retry spec that declares the rules for reconnecting + * @return the same instance for method chaining */ - public RSocketConnector reconnect(Retry retrySpec) { - this.retrySpec = Objects.requireNonNull(retrySpec); + public RSocketConnector reconnect(Retry retry) { + this.retrySpec = Objects.requireNonNull(retry); return this; } + /** + * Enables the Resume capability of the RSocket protocol where if the client gets disconnected, + * the connection is re-acquired and any interrupted streams are resumed automatically. For this + * to work the server must also support and have the Resume capability enabled. + * + *

    See {@link Resume} for settings to customize the Resume capability. + * + *

    Note: this feature is mutually exclusive with {@link #reconnect(Retry)}. If + * both are enabled, "resume" takes precedence. Consider using "reconnect" when the server does + * not have "resume" enabled or supported, or when you don't need to incur the overhead of saving + * in-flight frames to be potentially replayed after a reconnect. + * + *

    By default this is not enabled. + * + * @param resume configuration for the Resume capability + * @return the same instance for method chaining + * @see Resuming + * Operation + */ public RSocketConnector resume(Resume resume) { this.resume = resume; return this; } + /** + * Enables the Lease feature of the RSocket protocol where the number of requests that can be + * performed from either side are rationed via {@code LEASE} frames from the responder side. + * + *

    Example usage: + * + *

    {@code
    +   * Mono rocketMono =
    +   *         RSocketConnector.create().lease(Leases::new).connect(transport);
    +   * }
    + * + *

    By default this is not enabled. + * + * @param supplier supplier for a {@link Leases} + * @return the same instance for method chaining Lease + * Semantics + */ public RSocketConnector lease(Supplier> supplier) { this.leasesSupplier = supplier; return this; } + /** + * When this is set, frames larger than the given maximum transmission unit (mtu) size value are + * broken down into fragments to fit that size. + * + *

    By default this is not set in which case payloads are sent whole up to the maximum frame + * size of 16,777,215 bytes. + * + * @param mtu the threshold size for fragmentation, must be no less than 64 + * @return the same instance for method chaining + * @see Fragmentation + * and Reassembly + */ public RSocketConnector fragment(int mtu) { - if (mtu > 0 && mtu < MIN_MTU_SIZE || mtu < 0) { + if (mtu > 0 && mtu < FragmentationDuplexConnection.MIN_MTU_SIZE || mtu < 0) { String msg = - String.format("smallest allowed mtu size is %d bytes, provided: %d", MIN_MTU_SIZE, mtu); + String.format( + "The smallest allowed mtu size is %d bytes, provided: %d", + FragmentationDuplexConnection.MIN_MTU_SIZE, mtu); throw new IllegalArgumentException(msg); } this.mtu = mtu; return this; } - public RSocketConnector payloadDecoder(PayloadDecoder payloadDecoder) { - Objects.requireNonNull(payloadDecoder); - this.payloadDecoder = payloadDecoder; + /** + * Configure the {@code PayloadDecoder} used to create {@link Payload}'s from incoming raw frame + * buffers. The following decoders are available: + * + *

      + *
    • {@link PayloadDecoder#DEFAULT} -- the data and metadata are independent copies of the + * underlying frame {@link ByteBuf} + *
    • {@link PayloadDecoder#ZERO_COPY} -- the data and metadata are retained slices of the + * underlying {@link ByteBuf}. That's more efficient but requires careful tracking and + * {@link Payload#release() release} of the payload when no longer needed. + *
    + * + *

    By default this is set to {@link PayloadDecoder#DEFAULT} in which case data and metadata are + * copied and do not need to be tracked and released. + * + * @param decoder the decoder to use + * @return the same instance for method chaining + */ + public RSocketConnector payloadDecoder(PayloadDecoder decoder) { + Objects.requireNonNull(decoder); + this.payloadDecoder = decoder; return this; } @@ -239,10 +447,38 @@ public RSocketConnector errorConsumer(Consumer errorConsumer) { return this; } + /** + * The final step to connect with the transport to use as input and the resulting {@code + * Mono} as output. Each subscriber to the returned {@code Mono} starts a new connection + * if neither {@link #reconnect(Retry) reconnect} nor {@link #resume(Resume)} are enabled. + * + *

    The following transports are available (via additional RSocket Java modules): + * + *

      + *
    • {@link io.rsocket.transport.netty.client.TcpClientTransport TcpClientTransport} via + * {@code rsocket-transport-netty}. + *
    • {@link io.rsocket.transport.netty.client.WebsocketClientTransport + * WebsocketClientTransport} via {@code rsocket-transport-netty}. + *
    • {@link io.rsocket.transport.local.LocalClientTransport LocalClientTransport} via {@code + * rsocket-transport-local} + *
    + * + * @param transport the transport of choice to connect with + * @return a {@code Mono} with the connected RSocket + */ public Mono connect(ClientTransport transport) { return connect(() -> transport); } + /** + * Variant of {@link #connect(ClientTransport)} with a {@link Supplier} for the {@code + * ClientTransport}. + * + *

    // TODO: when to use? + * + * @param transportSupplier supplier for the transport to connect with + * @return a {@code Mono} with the connected RSocket + */ public Mono connect(Supplier transportSupplier) { Mono connectionMono = Mono.fromSupplier(transportSupplier).flatMap(t -> t.connect(mtu)); @@ -310,6 +546,9 @@ public Mono connect(Supplier transportSupplier) { dataMimeType, setupPayload); + SocketAcceptor acceptor = + this.acceptor != null ? this.acceptor : SocketAcceptor.with(new RSocket() {}); + ConnectionSetupPayload setup = new DefaultConnectionSetupPayload(setupFrame); return interceptors diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java index d5d8cee0f..036900be1 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java @@ -20,10 +20,12 @@ import io.rsocket.Closeable; import io.rsocket.ConnectionSetupPayload; import io.rsocket.DuplexConnection; +import io.rsocket.Payload; import io.rsocket.RSocket; import io.rsocket.SocketAcceptor; import io.rsocket.exceptions.InvalidSetupException; import io.rsocket.exceptions.RejectedSetupException; +import io.rsocket.fragmentation.FragmentationDuplexConnection; import io.rsocket.frame.FrameHeaderFlyweight; import io.rsocket.frame.SetupFrameFlyweight; import io.rsocket.frame.decoder.PayloadDecoder; @@ -41,64 +43,207 @@ import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; +/** + * The main class for starting an RSocket server. + * + *

    For example: + * + *

    {@code
    + * CloseableChannel closeable =
    + *         RSocketServer.create(SocketAcceptor.with(new RSocket() {...}))
    + *                 .bind(TcpServerTransport.create("localhost", 7000))
    + *                 .block();
    + * }
    + */ public final class RSocketServer { private static final String SERVER_TAG = "server"; - private static final int MIN_MTU_SIZE = 64; private SocketAcceptor acceptor = SocketAcceptor.with(new RSocket() {}); private InitializingInterceptorRegistry interceptors = new InitializingInterceptorRegistry(); - private int mtu = 0; private Resume resume; private Supplier> leasesSupplier = null; - private Consumer errorConsumer = ex -> {}; + private int mtu = 0; private PayloadDecoder payloadDecoder = PayloadDecoder.DEFAULT; + private Consumer errorConsumer = ex -> {}; + private RSocketServer() {} + /** Static factory method to create an {@code RSocketServer}. */ public static RSocketServer create() { return new RSocketServer(); } + /** + * Static factory method to create an {@code RSocketServer} instance with the given {@code + * SocketAcceptor}. Effectively a shortcut for: + * + *
    +   * RSocketServer.create().acceptor(...);
    +   * 
    + * + * @param acceptor the acceptor to handle connections with + * @return the same instance for method chaining + * @see #acceptor(SocketAcceptor) + */ public static RSocketServer create(SocketAcceptor acceptor) { return RSocketServer.create().acceptor(acceptor); } + /** + * Set the acceptor to handle incoming connections and handle requests. + * + *

    An example with access to the {@code SETUP} frame and sending RSocket for performing + * requests back to the client if needed: + * + *

    {@code
    +   * RSocketServer.create((setup, sendingRSocket) -> Mono.just(new RSocket() {...}))
    +   *         .bind(TcpServerTransport.create("localhost", 7000))
    +   *         .subscribe();
    +   * }
    + * + *

    A shortcut to provide the handling RSocket only: + * + *

    {@code
    +   * RSocketServer.create(SocketAcceptor.with(new RSocket() {...}))
    +   *         .bind(TcpServerTransport.create("localhost", 7000))
    +   *         .subscribe();
    +   * }
    + * + *

    A shortcut to handle request-response interactions only: + * + *

    {@code
    +   * RSocketServer.create(SocketAcceptor.forRequestResponse(payload -> ...))
    +   *         .bind(TcpServerTransport.create("localhost", 7000))
    +   *         .subscribe();
    +   * }
    + * + *

    By default, {@code new RSocket(){}} is used for handling which rejects requests from the + * client with {@link UnsupportedOperationException}. + * + * @param acceptor the acceptor to handle incoming connections and requests with + * @return the same instance for method chaining + */ public RSocketServer acceptor(SocketAcceptor acceptor) { Objects.requireNonNull(acceptor); this.acceptor = acceptor; return this; } - public RSocketServer interceptors(Consumer consumer) { - consumer.accept(this.interceptors); - return this; - } - - public RSocketServer fragment(int mtu) { - if (mtu > 0 && mtu < MIN_MTU_SIZE || mtu < 0) { - String msg = - String.format("smallest allowed mtu size is %d bytes, provided: %d", MIN_MTU_SIZE, mtu); - throw new IllegalArgumentException(msg); - } - this.mtu = mtu; + /** + * Configure interception at one of the following levels: + * + *

      + *
    • Transport level + *
    • At the level of accepting new connections + *
    • Performing requests + *
    • Responding to requests + *
    + * + * @param configurer a configurer to customize interception with. + * @return the same instance for method chaining + */ + public RSocketServer interceptors(Consumer configurer) { + configurer.accept(this.interceptors); return this; } + /** + * Enables the Resume capability of the RSocket protocol where if the client gets disconnected, + * the connection is re-acquired and any interrupted streams are transparently resumed. For this + * to work clients must also support and request to enable this when connecting. + * + *

    Use the {@link Resume} argument to customize the Resume session duration, storage, retry + * logic, and others. + * + *

    By default this is not enabled. + * + * @param resume configuration for the Resume capability + * @return the same instance for method chaining + * @see Resuming + * Operation + */ public RSocketServer resume(Resume resume) { this.resume = resume; return this; } + /** + * Enables the Lease feature of the RSocket protocol where the number of requests that can be + * performed from either side are rationed via {@code LEASE} frames from the responder side. For + * this to work clients must also support and request to enable this when connecting. + * + *

    Example usage: + * + *

    {@code
    +   * RSocketServer.create(SocketAcceptor.with(new RSocket() {...}))
    +   *         .lease(Leases::new)
    +   *         .bind(TcpServerTransport.create("localhost", 7000))
    +   *         .subscribe();
    +   * }
    + * + *

    By default this is not enabled. + * + * @param supplier supplier for a {@link Leases} + * @return the same instance for method chaining + * @return the same instance for method chaining Lease + * Semantics + */ public RSocketServer lease(Supplier> supplier) { this.leasesSupplier = supplier; return this; } - public RSocketServer payloadDecoder(PayloadDecoder payloadDecoder) { - Objects.requireNonNull(payloadDecoder); - this.payloadDecoder = payloadDecoder; + /** + * When this is set, frames larger than the given maximum transmission unit (mtu) size value are + * fragmented. + * + *

    By default this is not set in which case payloads are sent whole up to the maximum frame + * size of 16,777,215 bytes. + * + * @param mtu the threshold size for fragmentation, must be no less than 64 + * @return the same instance for method chaining + * @see Fragmentation + * and Reassembly + */ + public RSocketServer fragment(int mtu) { + if (mtu > 0 && mtu < FragmentationDuplexConnection.MIN_MTU_SIZE || mtu < 0) { + String msg = + String.format( + "The smallest allowed mtu size is %d bytes, provided: %d", + FragmentationDuplexConnection.MIN_MTU_SIZE, mtu); + throw new IllegalArgumentException(msg); + } + this.mtu = mtu; + return this; + } + + /** + * Configure the {@code PayloadDecoder} used to create {@link Payload}'s from incoming raw frame + * buffers. The following decoders are available: + * + *

      + *
    • {@link PayloadDecoder#DEFAULT} -- the data and metadata are independent copies of the + * underlying frame {@link ByteBuf} + *
    • {@link PayloadDecoder#ZERO_COPY} -- the data and metadata are retained slices of the + * underlying {@link ByteBuf}. That's more efficient but requires careful tracking and + * {@link Payload#release() release} of the payload when no longer needed. + *
    + * + *

    By default this is set to {@link PayloadDecoder#DEFAULT} in which case data and metadata are + * copied and do not need to be tracked and released. + * + * @param decoder the decoder to use + * @return the same instance for method chaining + */ + public RSocketServer payloadDecoder(PayloadDecoder decoder) { + Objects.requireNonNull(decoder); + this.payloadDecoder = decoder; return this; } @@ -112,17 +257,25 @@ public RSocketServer errorConsumer(Consumer errorConsumer) { return this; } - public ServerTransport.ConnectionAcceptor asConnectionAcceptor() { - return new ServerTransport.ConnectionAcceptor() { - private final ServerSetup serverSetup = serverSetup(); - - @Override - public Mono apply(DuplexConnection connection) { - return acceptor(serverSetup, connection); - } - }; - } - + /** + * Start the server on the given transport. + * + *

    The following transports are available from additional RSocket Java modules: + * + *

      + *
    • {@link io.rsocket.transport.netty.client.TcpServerTransport TcpServerTransport} via + * {@code rsocket-transport-netty}. + *
    • {@link io.rsocket.transport.netty.client.WebsocketServerTransport + * WebsocketServerTransport} via {@code rsocket-transport-netty}. + *
    • {@link io.rsocket.transport.local.LocalServerTransport LocalServerTransport} via {@code + * rsocket-transport-local} + *
    + * + * @param transport the transport of choice to connect with + * @param the type of {@code Closeable} for the given transport + * @return a {@code Mono} with a {@code Closeable} that can be used to obtain information about + * the server, stop it, or be notified of when it is stopped. + */ public Mono bind(ServerTransport transport) { return Mono.defer( new Supplier>() { @@ -137,6 +290,23 @@ public Mono get() { }); } + /** + * An alternative to {@link #bind(ServerTransport)} that is useful for installing RSocket on a + * server that is started independently. + * + * @see io.rsocket.examples.transport.ws.WebSocketHeadersSample + */ + public ServerTransport.ConnectionAcceptor asConnectionAcceptor() { + return new ServerTransport.ConnectionAcceptor() { + private final ServerSetup serverSetup = serverSetup(); + + @Override + public Mono apply(DuplexConnection connection) { + return acceptor(serverSetup, connection); + } + }; + } + private Mono acceptor(ServerSetup serverSetup, DuplexConnection connection) { ClientServerInputMultiplexer multiplexer = new ClientServerInputMultiplexer(connection, interceptors, false); diff --git a/rsocket-core/src/main/java/io/rsocket/core/Resume.java b/rsocket-core/src/main/java/io/rsocket/core/Resume.java index aedcc9e5e..04221154f 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/Resume.java +++ b/rsocket-core/src/main/java/io/rsocket/core/Resume.java @@ -20,20 +20,29 @@ import io.rsocket.resume.InMemoryResumableFramesStore; import io.rsocket.resume.ResumableFramesStore; import java.time.Duration; +import java.util.Objects; import java.util.function.Function; import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.util.retry.Retry; +/** + * Simple holder of configuration settings for the RSocket Resume capability. This can be used to + * configure an {@link RSocketConnector} or an {@link RSocketServer} except for {@link + * #retry(Retry)} and {@link #token(Supplier)} which apply only to the client side. + */ public class Resume { private static final Logger logger = LoggerFactory.getLogger(Resume.class); private Duration sessionDuration = Duration.ofMinutes(2); - private Duration streamTimeout = Duration.ofSeconds(10); + + /* Storage */ private boolean cleanupStoreOnKeepAlive; private Function storeFactory; + private Duration streamTimeout = Duration.ofSeconds(10); + /* Client only */ private Supplier tokenSupplier = ResumeFrameFlyweight::generateResumeToken; private Retry retry = Retry.backoff(Long.MAX_VALUE, Duration.ofSeconds(1)) @@ -43,43 +52,105 @@ public class Resume { public Resume() {} + /** + * The maximum time for a client to keep trying to reconnect. During this time client and server + * continue to store unsent frames to keep the session warm and ready to resume. + * + *

    By default this is set to 2 minutes. + * + * @param sessionDuration the max duration for a session + * @return the same instance for method chaining + */ public Resume sessionDuration(Duration sessionDuration) { - this.sessionDuration = sessionDuration; - return this; - } - - public Resume streamTimeout(Duration streamTimeout) { - this.streamTimeout = streamTimeout; + this.sessionDuration = Objects.requireNonNull(sessionDuration); return this; } + /** + * When this property is enabled, hints from {@code KEEPALIVE} frames about how much data has been + * received by the other side, is used to proactively clean frames from the {@link + * #storeFactory(Function) store}. + * + *

    By default this is set to {@code false} in which case information from {@code KEEPALIVE} is + * ignored and old frames from the store are removed only when the store runs out of space. + * + * @return the same instance for method chaining + */ public Resume cleanupStoreOnKeepAlive() { this.cleanupStoreOnKeepAlive = true; return this; } + /** + * Configure a factory to create the storage for buffering (or persisting) a window of frames that + * may need to be sent again to resume after a dropped connection. + * + *

    By default {@link InMemoryResumableFramesStore} is used with its cache size set to 100,000 + * bytes. When the cache fills up, the oldest frames are gradually removed to create space for new + * ones. + * + * @param storeFactory the factory to use to create the store + * @return the same instance for method chaining + */ public Resume storeFactory( Function storeFactory) { this.storeFactory = storeFactory; return this; } - public Resume token(Supplier supplier) { - this.tokenSupplier = supplier; + /** + * A {@link reactor.core.publisher.Flux#timeout(Duration) timeout} value to apply to the resumed + * session stream obtained from the {@link #storeFactory(Function) store} after a reconnect. The + * resume stream must not take longer than the specified time to emit each frame. + * + *

    By default this is set to 10 seconds. + * + * @param streamTimeout the timeout value for resuming a session stream + * @return the same instance for method chaining + */ + public Resume streamTimeout(Duration streamTimeout) { + this.streamTimeout = Objects.requireNonNull(streamTimeout); return this; } + /** + * Configure the logic for reconnecting. This setting is for use with {@link + * RSocketConnector#resume(Resume)} on the client side only. + * + *

    By default this is set to: + * + *

    {@code
    +   * Retry.backoff(Long.MAX_VALUE, Duration.ofSeconds(1))
    +   *     .maxBackoff(Duration.ofSeconds(16))
    +   *     .jitter(1.0)
    +   * }
    + * + * @param retry the {@code Retry} spec to use when attempting to reconnect + * @return the same instance for method chaining + */ public Resume retry(Retry retry) { this.retry = retry; return this; } - Duration getSessionDuration() { - return sessionDuration; + /** + * Customize the generation of the resume identification token used to resume. This setting is for + * use with {@link RSocketConnector#resume(Resume)} on the client side only. + * + *

    By default this is {@code ResumeFrameFlyweight::generateResumeToken}. + * + * @param supplier a custom generator for a resume identification token + * @return the same instance for method chaining + */ + public Resume token(Supplier supplier) { + this.tokenSupplier = supplier; + return this; } - Duration getStreamTimeout() { - return streamTimeout; + // Package private accessors + + Duration getSessionDuration() { + return sessionDuration; } boolean isCleanupStoreOnKeepAlive() { @@ -92,11 +163,15 @@ boolean isCleanupStoreOnKeepAlive() { : token -> new InMemoryResumableFramesStore(tag, 100_000); } - Supplier getTokenSupplier() { - return tokenSupplier; + Duration getStreamTimeout() { + return streamTimeout; } Retry getRetry() { return retry; } + + Supplier getTokenSupplier() { + return tokenSupplier; + } } diff --git a/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java b/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java index 316643e10..24b360755 100644 --- a/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java +++ b/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java @@ -41,7 +41,7 @@ */ public final class FragmentationDuplexConnection extends ReassemblyDuplexConnection implements DuplexConnection { - private static final int MIN_MTU_SIZE = 64; + public static final int MIN_MTU_SIZE = 64; private static final Logger logger = LoggerFactory.getLogger(FragmentationDuplexConnection.class); private final DuplexConnection delegate; private final int mtu; diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketServerFragmentationTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketServerFragmentationTest.java index 9d105a8c9..073ebfd06 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketServerFragmentationTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketServerFragmentationTest.java @@ -11,7 +11,7 @@ public class RSocketServerFragmentationTest { public void serverErrorsWithEnabledFragmentationOnInsufficientMtu() { Assertions.assertThatIllegalArgumentException() .isThrownBy(() -> RSocketServer.create().fragment(2)) - .withMessage("smallest allowed mtu size is 64 bytes, provided: 2"); + .withMessage("The smallest allowed mtu size is 64 bytes, provided: 2"); } @Test @@ -28,12 +28,12 @@ public void serverSucceedsWithDisabledFragmentation() { public void clientErrorsWithEnabledFragmentationOnInsufficientMtu() { Assertions.assertThatIllegalArgumentException() .isThrownBy(() -> RSocketConnector.create().fragment(2)) - .withMessage("smallest allowed mtu size is 64 bytes, provided: 2"); + .withMessage("The smallest allowed mtu size is 64 bytes, provided: 2"); } @Test public void clientSucceedsWithEnabledFragmentationOnSufficientMtu() { - RSocketConnector.create().fragment(100).connect(TestClientTransport::new).block(); + RSocketConnector.create().fragment(100).connect(new TestClientTransport()).block(); } @Test From c9a4f064ca1fe8fb06b67f7ac09ba2777bbb5634 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Mon, 4 May 2020 15:32:20 +0300 Subject: [PATCH 50/62] relaxed stream id supplement (#814) --- .../java/io/rsocket/core/StreamIdSupplier.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/core/StreamIdSupplier.java b/rsocket-core/src/main/java/io/rsocket/core/StreamIdSupplier.java index 70734b8c0..7f4d7b7b3 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/StreamIdSupplier.java +++ b/rsocket-core/src/main/java/io/rsocket/core/StreamIdSupplier.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,11 @@ package io.rsocket.core; import io.netty.util.collection.IntObjectMap; -import java.util.concurrent.atomic.AtomicLongFieldUpdater; final class StreamIdSupplier { private static final int MASK = 0x7FFFFFFF; - private static final AtomicLongFieldUpdater STREAM_ID = - AtomicLongFieldUpdater.newUpdater(StreamIdSupplier.class, "streamId"); - private volatile long streamId; + private long streamId; // Visible for testing StreamIdSupplier(int streamId) { @@ -38,10 +35,18 @@ static StreamIdSupplier serverSupplier() { return new StreamIdSupplier(0); } + /** + * This methods provides new stream id and ensures there is no intersections with already running + * streams. This methods is not thread-safe. + * + * @param streamIds currently running streams store + * @return next stream id + */ int nextStreamId(IntObjectMap streamIds) { int streamId; do { - streamId = (int) STREAM_ID.addAndGet(this, 2) & MASK; + this.streamId += 2; + streamId = (int) (this.streamId & MASK); } while (streamId == 0 || streamIds.containsKey(streamId)); return streamId; } From be382d7dba2464ae7421bc7b720f70aab71d518c Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 6 May 2020 18:26:15 +0100 Subject: [PATCH 51/62] Add missing package-infos and Javadoc in plugins package (#817) --- .../io/rsocket/core/RSocketConnector.java | 4 +- .../java/io/rsocket/core/RSocketServer.java | 4 +- .../java/io/rsocket/core/package-info.java | 8 ++- .../io/rsocket/exceptions/package-info.java | 4 +- .../java/io/rsocket/frame/package-info.java | 21 +++++++ .../io/rsocket/keepalive/package-info.java | 19 ++++++ .../java/io/rsocket/lease/package-info.java | 9 ++- .../io/rsocket/metadata/package-info.java | 22 +++++++ .../main/java/io/rsocket/package-info.java | 11 +++- .../plugins/DuplexConnectionInterceptor.java | 8 ++- .../InitializingInterceptorRegistry.java | 4 ++ .../rsocket/plugins/InterceptorRegistry.java | 63 +++++++++++++++---- .../rsocket/plugins/RSocketInterceptor.java | 9 ++- .../plugins/SocketAcceptorInterceptor.java | 6 +- .../java/io/rsocket/plugins/package-info.java | 18 ++++++ .../java/io/rsocket/resume/package-info.java | 7 +++ .../io/rsocket/transport/package-info.java | 3 +- .../java/io/rsocket/util/package-info.java | 3 +- 18 files changed, 191 insertions(+), 32 deletions(-) create mode 100644 rsocket-core/src/main/java/io/rsocket/frame/package-info.java create mode 100644 rsocket-core/src/main/java/io/rsocket/keepalive/package-info.java create mode 100644 rsocket-core/src/main/java/io/rsocket/metadata/package-info.java create mode 100644 rsocket-core/src/main/java/io/rsocket/plugins/package-info.java diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java index 3d9345c4b..45d16a665 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java @@ -378,8 +378,8 @@ public RSocketConnector resume(Resume resume) { *

    By default this is not enabled. * * @param supplier supplier for a {@link Leases} - * @return the same instance for method chaining Lease + * @return the same instance for method chaining + * @see Lease * Semantics */ public RSocketConnector lease(Supplier> supplier) { diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java index 036900be1..5960e33d4 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java @@ -189,8 +189,8 @@ public RSocketServer resume(Resume resume) { * * @param supplier supplier for a {@link Leases} * @return the same instance for method chaining - * @return the same instance for method chaining Lease + * @return the same instance for method chaining + * @see Lease * Semantics */ public RSocketServer lease(Supplier> supplier) { diff --git a/rsocket-core/src/main/java/io/rsocket/core/package-info.java b/rsocket-core/src/main/java/io/rsocket/core/package-info.java index a70bb3b16..29db3f205 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/package-info.java +++ b/rsocket-core/src/main/java/io/rsocket/core/package-info.java @@ -15,8 +15,12 @@ */ /** - * Contains core RSocket protocol, client and server implementation classes, including factories to - * create and configure them. + * Contains {@link io.rsocket.core.RSocketConnector RSocketConnector} and {@link + * io.rsocket.core.RSocketServer RSocketServer}, the main classes for connecting to or starting an + * RSocket server. + * + *

    This package also contains a package private classes that implement support for the main + * RSocket interactions. */ @NonNullApi package io.rsocket.core; diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/package-info.java b/rsocket-core/src/main/java/io/rsocket/exceptions/package-info.java index babf8194e..969aedded 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/package-info.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ /** - * The hierarchy of exceptions that can be returned by the API + * A hierarchy of exceptions that represent RSocket protocol error codes. * * @see Error * Codes diff --git a/rsocket-core/src/main/java/io/rsocket/frame/package-info.java b/rsocket-core/src/main/java/io/rsocket/frame/package-info.java new file mode 100644 index 000000000..1d02ebca0 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/frame/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Support for encoding and decoding of RSocket frames to and from {@link io.rsocket.Payload + * Payload}. + */ +package io.rsocket.frame; diff --git a/rsocket-core/src/main/java/io/rsocket/keepalive/package-info.java b/rsocket-core/src/main/java/io/rsocket/keepalive/package-info.java new file mode 100644 index 000000000..ce8a2f3fb --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/keepalive/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** Support classes for sending and keeping track of KEEPALIVE frames from the remote. */ +@javax.annotation.ParametersAreNonnullByDefault +package io.rsocket.keepalive; diff --git a/rsocket-core/src/main/java/io/rsocket/lease/package-info.java b/rsocket-core/src/main/java/io/rsocket/lease/package-info.java index 6700c10d9..ce1956628 100644 --- a/rsocket-core/src/main/java/io/rsocket/lease/package-info.java +++ b/rsocket-core/src/main/java/io/rsocket/lease/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,5 +14,12 @@ * limitations under the License. */ +/** + * Contains support classes for the Lease feature of the RSocket protocol. + * + * @see Resuming + * Operation + */ @javax.annotation.ParametersAreNonnullByDefault package io.rsocket.lease; diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/package-info.java b/rsocket-core/src/main/java/io/rsocket/metadata/package-info.java new file mode 100644 index 000000000..b1bc45ff0 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/metadata/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Contains implementations of RSocket protocol extensions related + * to the use of metadata. + */ +package io.rsocket.metadata; diff --git a/rsocket-core/src/main/java/io/rsocket/package-info.java b/rsocket-core/src/main/java/io/rsocket/package-info.java index 243c1ab52..878a56301 100644 --- a/rsocket-core/src/main/java/io/rsocket/package-info.java +++ b/rsocket-core/src/main/java/io/rsocket/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,4 +14,13 @@ * limitations under the License. */ +/** + * Contains key contracts of the RSocket programming model including {@link io.rsocket.RSocket + * RSocket} for performing or handling RSocket interactions, {@link io.rsocket.SocketAcceptor + * SocketAcceptor} for declaring responders, {@link io.rsocket.Payload Payload} for access to the + * content of a payload, and others. + * + *

    To connect to or start a server see {@link io.rsocket.core.RSocketConnector RSocketConnector} + * and {@link io.rsocket.core.RSocketServer RSocketServer} in {@link io.rsocket.core}. + */ package io.rsocket; diff --git a/rsocket-core/src/main/java/io/rsocket/plugins/DuplexConnectionInterceptor.java b/rsocket-core/src/main/java/io/rsocket/plugins/DuplexConnectionInterceptor.java index 056ded0cd..6b2a7a71b 100644 --- a/rsocket-core/src/main/java/io/rsocket/plugins/DuplexConnectionInterceptor.java +++ b/rsocket-core/src/main/java/io/rsocket/plugins/DuplexConnectionInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,9 +19,13 @@ import io.rsocket.DuplexConnection; import java.util.function.BiFunction; -/** */ +/** + * Contract to decorate a {@link DuplexConnection} and intercept the sending and receiving of + * RSocket frames at the transport level. + */ public @FunctionalInterface interface DuplexConnectionInterceptor extends BiFunction { + enum Type { SETUP, CLIENT, diff --git a/rsocket-core/src/main/java/io/rsocket/plugins/InitializingInterceptorRegistry.java b/rsocket-core/src/main/java/io/rsocket/plugins/InitializingInterceptorRegistry.java index cf911b954..fc032847c 100644 --- a/rsocket-core/src/main/java/io/rsocket/plugins/InitializingInterceptorRegistry.java +++ b/rsocket-core/src/main/java/io/rsocket/plugins/InitializingInterceptorRegistry.java @@ -19,6 +19,10 @@ import io.rsocket.RSocket; import io.rsocket.SocketAcceptor; +/** + * Extends {@link InterceptorRegistry} with methods for building a chain of registered interceptors. + * This is not intended for direct use by applications. + */ public class InitializingInterceptorRegistry extends InterceptorRegistry { public DuplexConnection initConnection( diff --git a/rsocket-core/src/main/java/io/rsocket/plugins/InterceptorRegistry.java b/rsocket-core/src/main/java/io/rsocket/plugins/InterceptorRegistry.java index f9ee151a8..427fa15ae 100644 --- a/rsocket-core/src/main/java/io/rsocket/plugins/InterceptorRegistry.java +++ b/rsocket-core/src/main/java/io/rsocket/plugins/InterceptorRegistry.java @@ -19,54 +19,87 @@ import java.util.List; import java.util.function.Consumer; +/** + * Provides support for registering interceptors at the following levels: + * + *

      + *
    • {@link #forConnection(DuplexConnectionInterceptor)} -- transport level + *
    • {@link #forSocketAcceptor(SocketAcceptorInterceptor)} -- for accepting new connections + *
    • {@link #forRequester(RSocketInterceptor)} -- for performing of requests + *
    • {@link #forResponder(RSocketInterceptor)} -- for responding to requests + *
    + */ public class InterceptorRegistry { - private List connectionInterceptors = new ArrayList<>(); private List requesterInteceptors = new ArrayList<>(); private List responderInterceptors = new ArrayList<>(); private List socketAcceptorInterceptors = new ArrayList<>(); + private List connectionInterceptors = new ArrayList<>(); - public InterceptorRegistry forConnection(DuplexConnectionInterceptor interceptor) { - connectionInterceptors.add(interceptor); - return this; - } - - public InterceptorRegistry forConnection(Consumer> consumer) { - consumer.accept(connectionInterceptors); - return this; - } - + /** + * Add an {@link RSocketInterceptor} that will decorate the RSocket used for performing requests. + */ public InterceptorRegistry forRequester(RSocketInterceptor interceptor) { requesterInteceptors.add(interceptor); return this; } + /** + * Variant of {@link #forRequester(RSocketInterceptor)} with access to the list of existing + * registrations. + */ public InterceptorRegistry forRequester(Consumer> consumer) { consumer.accept(requesterInteceptors); return this; } + /** + * Add an {@link RSocketInterceptor} that will decorate the RSocket used for resonding to + * requests. + */ public InterceptorRegistry forResponder(RSocketInterceptor interceptor) { responderInterceptors.add(interceptor); return this; } + /** + * Variant of {@link #forResponder(RSocketInterceptor)} with access to the list of existing + * registrations. + */ public InterceptorRegistry forResponder(Consumer> consumer) { consumer.accept(responderInterceptors); return this; } + /** + * Add a {@link SocketAcceptorInterceptor} that will intercept the accepting of new connections. + */ public InterceptorRegistry forSocketAcceptor(SocketAcceptorInterceptor interceptor) { socketAcceptorInterceptors.add(interceptor); return this; } + /** + * Variant of {@link #forSocketAcceptor(SocketAcceptorInterceptor)} with access to the list of + * existing registrations. + */ public InterceptorRegistry forSocketAcceptor(Consumer> consumer) { consumer.accept(socketAcceptorInterceptors); return this; } - List getConnectionInterceptors() { - return connectionInterceptors; + /** Add a {@link DuplexConnectionInterceptor}. */ + public InterceptorRegistry forConnection(DuplexConnectionInterceptor interceptor) { + connectionInterceptors.add(interceptor); + return this; + } + + /** + * Variant of {@link #forConnection(DuplexConnectionInterceptor)} with access to the list of + * existing registrations. + */ + public InterceptorRegistry forConnection(Consumer> consumer) { + consumer.accept(connectionInterceptors); + return this; } List getRequesterInteceptors() { @@ -77,6 +110,10 @@ List getResponderInterceptors() { return responderInterceptors; } + List getConnectionInterceptors() { + return connectionInterceptors; + } + List getSocketAcceptorInterceptors() { return socketAcceptorInterceptors; } diff --git a/rsocket-core/src/main/java/io/rsocket/plugins/RSocketInterceptor.java b/rsocket-core/src/main/java/io/rsocket/plugins/RSocketInterceptor.java index 0bad0faed..0cd4bb8f6 100644 --- a/rsocket-core/src/main/java/io/rsocket/plugins/RSocketInterceptor.java +++ b/rsocket-core/src/main/java/io/rsocket/plugins/RSocketInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,5 +19,10 @@ import io.rsocket.RSocket; import java.util.function.Function; -/** */ +/** + * Contract to decorate an {@link RSocket}, providing a way to intercept interactions. This can be + * applied to a {@link InterceptorRegistry#forRequester(RSocketInterceptor) requester} or {@link + * InterceptorRegistry#forResponder(RSocketInterceptor) responder} {@code RSocket} of a client or + * server. + */ public @FunctionalInterface interface RSocketInterceptor extends Function {} diff --git a/rsocket-core/src/main/java/io/rsocket/plugins/SocketAcceptorInterceptor.java b/rsocket-core/src/main/java/io/rsocket/plugins/SocketAcceptorInterceptor.java index 0cb9d92d2..6dd850ba9 100644 --- a/rsocket-core/src/main/java/io/rsocket/plugins/SocketAcceptorInterceptor.java +++ b/rsocket-core/src/main/java/io/rsocket/plugins/SocketAcceptorInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2019 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,8 +22,8 @@ * Contract to decorate a {@link SocketAcceptor}, providing access to connection {@code setup} * information and the ability to also decorate the sockets for requesting and responding. * - *

    This can be used as an alternative to individual requester and responder {@link - * RSocketInterceptor} plugins. + *

    This could be used as an alternative to registering an individual "requester" {@code + * RSocketInterceptor} and "responder" {@code RSocketInterceptor}. */ public @FunctionalInterface interface SocketAcceptorInterceptor extends Function {} diff --git a/rsocket-core/src/main/java/io/rsocket/plugins/package-info.java b/rsocket-core/src/main/java/io/rsocket/plugins/package-info.java new file mode 100644 index 000000000..743e3a8a4 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/plugins/package-info.java @@ -0,0 +1,18 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** Contracts for interception of transports, connections, and requests in in RSocket Java. */ +package io.rsocket.plugins; diff --git a/rsocket-core/src/main/java/io/rsocket/resume/package-info.java b/rsocket-core/src/main/java/io/rsocket/resume/package-info.java index 57027bee2..aaaa3ee9f 100644 --- a/rsocket-core/src/main/java/io/rsocket/resume/package-info.java +++ b/rsocket-core/src/main/java/io/rsocket/resume/package-info.java @@ -14,5 +14,12 @@ * limitations under the License. */ +/** + * Contains support classes for the RSocket resume capability. + * + * @see Resuming + * Operation + */ @javax.annotation.ParametersAreNonnullByDefault package io.rsocket.resume; diff --git a/rsocket-core/src/main/java/io/rsocket/transport/package-info.java b/rsocket-core/src/main/java/io/rsocket/transport/package-info.java index 86e7c311a..153676324 100644 --- a/rsocket-core/src/main/java/io/rsocket/transport/package-info.java +++ b/rsocket-core/src/main/java/io/rsocket/transport/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,5 +14,6 @@ * limitations under the License. */ +/** Client and server transport contracts for pluggable transports. */ @javax.annotation.ParametersAreNonnullByDefault package io.rsocket.transport; diff --git a/rsocket-core/src/main/java/io/rsocket/util/package-info.java b/rsocket-core/src/main/java/io/rsocket/util/package-info.java index 79123d3b2..e034672f1 100644 --- a/rsocket-core/src/main/java/io/rsocket/util/package-info.java +++ b/rsocket-core/src/main/java/io/rsocket/util/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2018 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,5 +14,6 @@ * limitations under the License. */ +/** Shared utility classes and {@link io.rsocket.Payload} implementations. */ @javax.annotation.ParametersAreNonnullByDefault package io.rsocket.util; From 79d2ee66103a73bc130c88d22c75acf1ba54df2a Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Thu, 7 May 2020 10:51:08 +0300 Subject: [PATCH 52/62] Renames Flyweight classes to Codec (#820) --- .../core/DefaultConnectionSetupPayload.java | 26 +- .../rsocket/core/PayloadValidationUtils.java | 18 +- .../io/rsocket/core/RSocketConnector.java | 4 +- .../io/rsocket/core/RSocketRequester.java | 60 ++- .../io/rsocket/core/RSocketResponder.java | 27 +- .../java/io/rsocket/core/RSocketServer.java | 14 +- .../src/main/java/io/rsocket/core/Resume.java | 4 +- .../java/io/rsocket/core/ServerSetup.java | 16 +- .../exceptions/ApplicationErrorException.java | 4 +- .../rsocket/exceptions/CanceledException.java | 4 +- .../exceptions/ConnectionCloseException.java | 4 +- .../exceptions/ConnectionErrorException.java | 4 +- .../exceptions/CustomRSocketException.java | 6 +- .../io/rsocket/exceptions/Exceptions.java | 30 +- .../rsocket/exceptions/InvalidException.java | 4 +- .../exceptions/InvalidSetupException.java | 4 +- .../rsocket/exceptions/RSocketException.java | 4 +- .../rsocket/exceptions/RejectedException.java | 4 +- .../exceptions/RejectedResumeException.java | 4 +- .../exceptions/RejectedSetupException.java | 4 +- .../io/rsocket/exceptions/SetupException.java | 4 +- .../exceptions/UnsupportedSetupException.java | 4 +- .../FragmentationDuplexConnection.java | 14 +- .../fragmentation/FrameFragmenter.java | 66 +-- .../fragmentation/FrameReassembler.java | 44 +- .../ReassemblyDuplexConnection.java | 4 +- ...meFlyweight.java => CancelFrameCodec.java} | 6 +- ...ameFlyweight.java => ErrorFrameCodec.java} | 8 +- .../main/java/io/rsocket/frame/ErrorType.java | 85 ---- .../io/rsocket/frame/ExtensionFrameCodec.java | 66 +++ .../frame/ExtensionFrameFlyweight.java | 66 --- ...Flyweight.java => FragmentationCodec.java} | 4 +- ...dataFlyweight.java => FrameBodyCodec.java} | 4 +- ...erFlyweight.java => FrameHeaderCodec.java} | 4 +- ...thFlyweight.java => FrameLengthCodec.java} | 4 +- .../main/java/io/rsocket/frame/FrameUtil.java | 44 +- .../io/rsocket/frame/GenericFrameCodec.java | 157 +++++++ ...lyweight.java => KeepAliveFrameCodec.java} | 20 +- ...ameFlyweight.java => LeaseFrameCodec.java} | 20 +- ...eight.java => MetadataPushFrameCodec.java} | 8 +- .../io/rsocket/frame/PayloadFrameCodec.java | 54 +++ .../rsocket/frame/PayloadFrameFlyweight.java | 79 ---- .../frame/RequestChannelFrameCodec.java | 67 +++ .../frame/RequestChannelFrameFlyweight.java | 81 ---- .../frame/RequestFireAndForgetFrameCodec.java | 36 ++ .../RequestFireAndForgetFrameFlyweight.java | 63 --- .../io/rsocket/frame/RequestFlyweight.java | 110 ----- ...Flyweight.java => RequestNFrameCodec.java} | 10 +- .../frame/RequestResponseFrameCodec.java | 35 ++ .../frame/RequestResponseFrameFlyweight.java | 62 --- .../frame/RequestStreamFrameCodec.java | 62 +++ .../frame/RequestStreamFrameFlyweight.java | 76 ---- ...meFlyweight.java => ResumeFrameCodec.java} | 22 +- ...Flyweight.java => ResumeOkFrameCodec.java} | 8 +- ...ameFlyweight.java => SetupFrameCodec.java} | 32 +- ...ersionFlyweight.java => VersionCodec.java} | 2 +- .../frame/decoder/DefaultPayloadDecoder.java | 38 +- .../frame/decoder/ZeroCopyPayloadDecoder.java | 38 +- .../ClientServerInputMultiplexer.java | 6 +- .../rsocket/keepalive/KeepAliveSupport.java | 12 +- .../rsocket/lease/RequesterLeaseHandler.java | 8 +- .../rsocket/lease/ResponderLeaseHandler.java | 4 +- .../rsocket/metadata/AuthMetadataCodec.java | 335 +++++++++++++++ .../metadata/CompositeMetadataCodec.java | 385 ++++++++++++++++++ .../metadata/CompositeMetadataFlyweight.java | 169 ++------ .../metadata/TaggingMetadataCodec.java | 76 ++++ .../metadata/TaggingMetadataFlyweight.java | 26 +- .../rsocket/metadata/WellKnownAuthType.java | 121 ++++++ .../security/AuthMetadataFlyweight.java | 175 +------- .../metadata/security/WellKnownAuthType.java | 26 ++ .../rsocket/resume/ClientRSocketSession.java | 12 +- .../resume/ResumableDuplexConnection.java | 4 +- .../rsocket/resume/ServerRSocketSession.java | 16 +- .../core/ConnectionSetupPayloadTest.java | 8 +- .../java/io/rsocket/core/KeepAliveTest.java | 14 +- .../core/PayloadValidationUtilsTest.java | 34 +- .../io/rsocket/core/RSocketLeaseTest.java | 24 +- .../core/RSocketRequesterSubscribersTest.java | 12 +- .../io/rsocket/core/RSocketRequesterTest.java | 112 +++-- .../io/rsocket/core/RSocketResponderTest.java | 90 ++-- .../io/rsocket/core/SetupRejectionTest.java | 16 +- .../io/rsocket/exceptions/ExceptionsTest.java | 28 +- .../FragmentationDuplexConnectionTest.java | 6 +- .../FragmentationIntegrationTest.java | 13 +- .../fragmentation/FrameFragmenterTest.java | 131 +++--- .../fragmentation/FrameReassemblerTest.java | 88 ++-- .../ReassembleDuplexConnectionTest.java | 55 ++- ...ightTest.java => ErrorFrameCodecTest.java} | 6 +- .../frame/ExtensionFrameCodecTest.java | 62 +++ .../frame/ExtensionFrameFlyweightTest.java | 62 --- ...ghtTest.java => FrameHeaderCodecTest.java} | 16 +- ...htTest.java => GenericFrameCodecTest.java} | 96 +++-- .../frame/KeepaliveFrameFlyweightTest.java | 10 +- .../io/rsocket/frame/LeaseFrameCodecTest.java | 42 ++ .../frame/LeaseFrameFlyweightTest.java | 43 -- .../rsocket/frame/PayloadFlyweightTest.java | 39 +- ...tTest.java => RequestNFrameCodecTest.java} | 6 +- ...ghtTest.java => ResumeFrameCodecTest.java} | 13 +- ...tTest.java => ResumeOkFrameCodecTest.java} | 6 +- .../io/rsocket/frame/SetupFrameCodecTest.java | 57 +++ .../frame/SetupFrameFlyweightTest.java | 57 --- ...yweightTest.java => VersionCodecTest.java} | 24 +- .../ClientServerInputMultiplexerTest.java | 14 +- .../MicrometerDuplexConnection.java | 4 +- .../main/java/io/rsocket/test/TestFrames.java | 28 +- .../transport/netty/RSocketLengthCodec.java | 4 +- .../transport/netty/TcpDuplexConnection.java | 6 +- .../client/WebsocketClientTransport.java | 2 +- .../netty/server/WebsocketRouteTransport.java | 2 +- .../server/WebsocketServerTransport.java | 2 +- .../client/WebsocketClientTransportTest.java | 2 +- .../server/WebsocketServerTransportTest.java | 2 +- 112 files changed, 2422 insertions(+), 1941 deletions(-) rename rsocket-core/src/main/java/io/rsocket/frame/{CancelFrameFlyweight.java => CancelFrameCodec.java} (55%) rename rsocket-core/src/main/java/io/rsocket/frame/{ErrorFrameFlyweight.java => ErrorFrameCodec.java} (89%) delete mode 100644 rsocket-core/src/main/java/io/rsocket/frame/ErrorType.java create mode 100644 rsocket-core/src/main/java/io/rsocket/frame/ExtensionFrameCodec.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/frame/ExtensionFrameFlyweight.java rename rsocket-core/src/main/java/io/rsocket/frame/{FragmentationFlyweight.java => FragmentationCodec.java} (80%) rename rsocket-core/src/main/java/io/rsocket/frame/{DataAndMetadataFlyweight.java => FrameBodyCodec.java} (97%) rename rsocket-core/src/main/java/io/rsocket/frame/{FrameHeaderFlyweight.java => FrameHeaderCodec.java} (98%) rename rsocket-core/src/main/java/io/rsocket/frame/{FrameLengthFlyweight.java => FrameLengthCodec.java} (95%) create mode 100644 rsocket-core/src/main/java/io/rsocket/frame/GenericFrameCodec.java rename rsocket-core/src/main/java/io/rsocket/frame/{KeepAliveFrameFlyweight.java => KeepAliveFrameCodec.java} (61%) rename rsocket-core/src/main/java/io/rsocket/frame/{LeaseFrameFlyweight.java => LeaseFrameCodec.java} (74%) rename rsocket-core/src/main/java/io/rsocket/frame/{MetadataPushFrameFlyweight.java => MetadataPushFrameCodec.java} (83%) create mode 100644 rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameCodec.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameFlyweight.java create mode 100644 rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameCodec.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameFlyweight.java create mode 100644 rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameCodec.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameFlyweight.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/frame/RequestFlyweight.java rename rsocket-core/src/main/java/io/rsocket/frame/{RequestNFrameFlyweight.java => RequestNFrameCodec.java} (68%) create mode 100644 rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameCodec.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameFlyweight.java create mode 100644 rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameCodec.java delete mode 100644 rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameFlyweight.java rename rsocket-core/src/main/java/io/rsocket/frame/{ResumeFrameFlyweight.java => ResumeFrameCodec.java} (79%) rename rsocket-core/src/main/java/io/rsocket/frame/{ResumeOkFrameFlyweight.java => ResumeOkFrameCodec.java} (67%) rename rsocket-core/src/main/java/io/rsocket/frame/{SetupFrameFlyweight.java => SetupFrameCodec.java} (83%) rename rsocket-core/src/main/java/io/rsocket/frame/{VersionFlyweight.java => VersionCodec.java} (96%) create mode 100644 rsocket-core/src/main/java/io/rsocket/metadata/AuthMetadataCodec.java create mode 100644 rsocket-core/src/main/java/io/rsocket/metadata/CompositeMetadataCodec.java create mode 100644 rsocket-core/src/main/java/io/rsocket/metadata/TaggingMetadataCodec.java create mode 100644 rsocket-core/src/main/java/io/rsocket/metadata/WellKnownAuthType.java rename rsocket-core/src/test/java/io/rsocket/frame/{ErrorFrameFlyweightTest.java => ErrorFrameCodecTest.java} (65%) create mode 100644 rsocket-core/src/test/java/io/rsocket/frame/ExtensionFrameCodecTest.java delete mode 100644 rsocket-core/src/test/java/io/rsocket/frame/ExtensionFrameFlyweightTest.java rename rsocket-core/src/test/java/io/rsocket/frame/{FrameHeaderFlyweightTest.java => FrameHeaderCodecTest.java} (52%) rename rsocket-core/src/test/java/io/rsocket/frame/{RequestFlyweightTest.java => GenericFrameCodecTest.java} (65%) create mode 100644 rsocket-core/src/test/java/io/rsocket/frame/LeaseFrameCodecTest.java delete mode 100644 rsocket-core/src/test/java/io/rsocket/frame/LeaseFrameFlyweightTest.java rename rsocket-core/src/test/java/io/rsocket/frame/{RequestNFrameFlyweightTest.java => RequestNFrameCodecTest.java} (63%) rename rsocket-core/src/test/java/io/rsocket/frame/{ResumeFrameFlyweightTest.java => ResumeFrameCodecTest.java} (68%) rename rsocket-core/src/test/java/io/rsocket/frame/{ResumeOkFrameFlyweightTest.java => ResumeOkFrameCodecTest.java} (51%) create mode 100644 rsocket-core/src/test/java/io/rsocket/frame/SetupFrameCodecTest.java delete mode 100644 rsocket-core/src/test/java/io/rsocket/frame/SetupFrameFlyweightTest.java rename rsocket-core/src/test/java/io/rsocket/frame/{VersionFlyweightTest.java => VersionCodecTest.java} (58%) diff --git a/rsocket-core/src/main/java/io/rsocket/core/DefaultConnectionSetupPayload.java b/rsocket-core/src/main/java/io/rsocket/core/DefaultConnectionSetupPayload.java index feeb5c481..9b5647c6f 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/DefaultConnectionSetupPayload.java +++ b/rsocket-core/src/main/java/io/rsocket/core/DefaultConnectionSetupPayload.java @@ -19,8 +19,8 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.rsocket.ConnectionSetupPayload; -import io.rsocket.frame.FrameHeaderFlyweight; -import io.rsocket.frame.SetupFrameFlyweight; +import io.rsocket.frame.FrameHeaderCodec; +import io.rsocket.frame.SetupFrameCodec; /** * Default implementation of {@link ConnectionSetupPayload}. Primarily for internal use within @@ -36,18 +36,18 @@ public DefaultConnectionSetupPayload(ByteBuf setupFrame) { @Override public boolean hasMetadata() { - return FrameHeaderFlyweight.hasMetadata(setupFrame); + return FrameHeaderCodec.hasMetadata(setupFrame); } @Override public ByteBuf sliceMetadata() { - final ByteBuf metadata = SetupFrameFlyweight.metadata(setupFrame); + final ByteBuf metadata = SetupFrameCodec.metadata(setupFrame); return metadata == null ? Unpooled.EMPTY_BUFFER : metadata; } @Override public ByteBuf sliceData() { - return SetupFrameFlyweight.data(setupFrame); + return SetupFrameCodec.data(setupFrame); } @Override @@ -62,42 +62,42 @@ public ByteBuf metadata() { @Override public String metadataMimeType() { - return SetupFrameFlyweight.metadataMimeType(setupFrame); + return SetupFrameCodec.metadataMimeType(setupFrame); } @Override public String dataMimeType() { - return SetupFrameFlyweight.dataMimeType(setupFrame); + return SetupFrameCodec.dataMimeType(setupFrame); } @Override public int keepAliveInterval() { - return SetupFrameFlyweight.keepAliveInterval(setupFrame); + return SetupFrameCodec.keepAliveInterval(setupFrame); } @Override public int keepAliveMaxLifetime() { - return SetupFrameFlyweight.keepAliveMaxLifetime(setupFrame); + return SetupFrameCodec.keepAliveMaxLifetime(setupFrame); } @Override public int getFlags() { - return FrameHeaderFlyweight.flags(setupFrame); + return FrameHeaderCodec.flags(setupFrame); } @Override public boolean willClientHonorLease() { - return SetupFrameFlyweight.honorLease(setupFrame); + return SetupFrameCodec.honorLease(setupFrame); } @Override public boolean isResumeEnabled() { - return SetupFrameFlyweight.resumeEnabled(setupFrame); + return SetupFrameCodec.resumeEnabled(setupFrame); } @Override public ByteBuf resumeToken() { - return SetupFrameFlyweight.resumeToken(setupFrame); + return SetupFrameCodec.resumeToken(setupFrame); } @Override diff --git a/rsocket-core/src/main/java/io/rsocket/core/PayloadValidationUtils.java b/rsocket-core/src/main/java/io/rsocket/core/PayloadValidationUtils.java index 3b6b375d1..2d2b96f7e 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/PayloadValidationUtils.java +++ b/rsocket-core/src/main/java/io/rsocket/core/PayloadValidationUtils.java @@ -1,8 +1,8 @@ package io.rsocket.core; import io.rsocket.Payload; -import io.rsocket.frame.FrameHeaderFlyweight; -import io.rsocket.frame.FrameLengthFlyweight; +import io.rsocket.frame.FrameHeaderCodec; +import io.rsocket.frame.FrameLengthCodec; final class PayloadValidationUtils { static final String INVALID_PAYLOAD_ERROR_MESSAGE = @@ -14,18 +14,18 @@ static boolean isValid(int mtu, Payload payload) { } if (payload.hasMetadata()) { - return (((FrameHeaderFlyweight.size() - + FrameLengthFlyweight.FRAME_LENGTH_SIZE - + FrameHeaderFlyweight.size() + return (((FrameHeaderCodec.size() + + FrameLengthCodec.FRAME_LENGTH_SIZE + + FrameHeaderCodec.size() + payload.data().readableBytes() + payload.metadata().readableBytes()) - & ~FrameLengthFlyweight.FRAME_LENGTH_MASK) + & ~FrameLengthCodec.FRAME_LENGTH_MASK) == 0); } else { - return (((FrameHeaderFlyweight.size() + return (((FrameHeaderCodec.size() + payload.data().readableBytes() - + FrameLengthFlyweight.FRAME_LENGTH_SIZE) - & ~FrameLengthFlyweight.FRAME_LENGTH_MASK) + + FrameLengthCodec.FRAME_LENGTH_SIZE) + & ~FrameLengthCodec.FRAME_LENGTH_MASK) == 0); } } diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java index 45d16a665..b69610f3f 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java @@ -23,7 +23,7 @@ import io.rsocket.RSocket; import io.rsocket.SocketAcceptor; import io.rsocket.fragmentation.FragmentationDuplexConnection; -import io.rsocket.frame.SetupFrameFlyweight; +import io.rsocket.frame.SetupFrameCodec; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.internal.ClientServerInputMultiplexer; import io.rsocket.keepalive.KeepAliveHandler; @@ -536,7 +536,7 @@ public Mono connect(Supplier transportSupplier) { RSocket wrappedRSocketRequester = interceptors.initRequester(rSocketRequester); ByteBuf setupFrame = - SetupFrameFlyweight.encode( + SetupFrameCodec.encode( wrappedConnection.alloc(), leaseEnabled, (int) keepAliveInterval.toMillis(), diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index a2bd3d9fd..846eaa922 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -31,17 +31,17 @@ import io.rsocket.RSocket; import io.rsocket.exceptions.ConnectionErrorException; import io.rsocket.exceptions.Exceptions; -import io.rsocket.frame.CancelFrameFlyweight; -import io.rsocket.frame.ErrorFrameFlyweight; -import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.CancelFrameCodec; +import io.rsocket.frame.ErrorFrameCodec; +import io.rsocket.frame.FrameHeaderCodec; import io.rsocket.frame.FrameType; -import io.rsocket.frame.MetadataPushFrameFlyweight; -import io.rsocket.frame.PayloadFrameFlyweight; -import io.rsocket.frame.RequestChannelFrameFlyweight; -import io.rsocket.frame.RequestFireAndForgetFrameFlyweight; -import io.rsocket.frame.RequestNFrameFlyweight; -import io.rsocket.frame.RequestResponseFrameFlyweight; -import io.rsocket.frame.RequestStreamFrameFlyweight; +import io.rsocket.frame.MetadataPushFrameCodec; +import io.rsocket.frame.PayloadFrameCodec; +import io.rsocket.frame.RequestChannelFrameCodec; +import io.rsocket.frame.RequestFireAndForgetFrameCodec; +import io.rsocket.frame.RequestNFrameCodec; +import io.rsocket.frame.RequestResponseFrameCodec; +import io.rsocket.frame.RequestStreamFrameCodec; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.internal.SynchronizedIntObjectHashMap; import io.rsocket.internal.UnboundedProcessor; @@ -225,7 +225,7 @@ private Mono handleFireAndForget(Payload payload) { final int streamId = streamIdSupplier.nextStreamId(receivers); final ByteBuf requestFrame = - RequestFireAndForgetFrameFlyweight.encodeReleasingPayload( + RequestFireAndForgetFrameCodec.encodeReleasingPayload( allocator, streamId, payload); sendProcessor.onNext(requestFrame); @@ -275,7 +275,7 @@ void hookOnFirstRequest(long n) { this.streamId = streamId; ByteBuf requestResponseFrame = - RequestResponseFrameFlyweight.encodeReleasingPayload( + RequestResponseFrameCodec.encodeReleasingPayload( allocator, streamId, payload); receivers.put(streamId, receiver); @@ -285,8 +285,7 @@ void hookOnFirstRequest(long n) { @Override void hookOnCancel() { if (receivers.remove(streamId, receiver)) { - sendProcessor.onNext( - CancelFrameFlyweight.encode(allocator, streamId)); + sendProcessor.onNext(CancelFrameCodec.encode(allocator, streamId)); } else { payload.release(); } @@ -341,7 +340,7 @@ void hookOnFirstRequest(long n) { this.streamId = streamId; ByteBuf requestStreamFrame = - RequestStreamFrameFlyweight.encodeReleasingPayload( + RequestStreamFrameCodec.encodeReleasingPayload( allocator, streamId, n, payload); receivers.put(streamId, receiver); @@ -356,14 +355,13 @@ void hookOnRemainingRequests(long n) { } sendProcessor.onNext( - RequestNFrameFlyweight.encode(allocator, streamId, n)); + RequestNFrameCodec.encode(allocator, streamId, n)); } @Override void hookOnCancel() { if (receivers.remove(streamId, receiver)) { - sendProcessor.onNext( - CancelFrameFlyweight.encode(allocator, streamId)); + sendProcessor.onNext(CancelFrameCodec.encode(allocator, streamId)); } else { payload.release(); } @@ -450,13 +448,12 @@ protected void hookOnNext(Payload payload) { new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE); errorConsumer.accept(t); // no need to send any errors. - sendProcessor.onNext( - CancelFrameFlyweight.encode(allocator, streamId)); + sendProcessor.onNext(CancelFrameCodec.encode(allocator, streamId)); receiver.onError(t); return; } final ByteBuf frame = - PayloadFrameFlyweight.encodeNextReleasingPayload( + PayloadFrameCodec.encodeNextReleasingPayload( allocator, streamId, payload); sendProcessor.onNext(frame); @@ -464,14 +461,13 @@ protected void hookOnNext(Payload payload) { @Override protected void hookOnComplete() { - ByteBuf frame = - PayloadFrameFlyweight.encodeComplete(allocator, streamId); + ByteBuf frame = PayloadFrameCodec.encodeComplete(allocator, streamId); sendProcessor.onNext(frame); } @Override protected void hookOnError(Throwable t) { - ByteBuf frame = ErrorFrameFlyweight.encode(allocator, streamId, t); + ByteBuf frame = ErrorFrameCodec.encode(allocator, streamId, t); sendProcessor.onNext(frame); receiver.onError(t); } @@ -488,7 +484,7 @@ void hookOnFirstRequest(long n) { this.streamId = streamId; final ByteBuf frame = - RequestChannelFrameFlyweight.encodeReleasingPayload( + RequestChannelFrameCodec.encodeReleasingPayload( allocator, streamId, false, n, initialPayload); senders.put(streamId, upstreamSubscriber); @@ -508,14 +504,14 @@ void hookOnRemainingRequests(long n) { return; } - sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); + sendProcessor.onNext(RequestNFrameCodec.encode(allocator, streamId, n)); } @Override void hookOnCancel() { senders.remove(streamId, upstreamSubscriber); if (receivers.remove(streamId, receiver)) { - sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); + sendProcessor.onNext(CancelFrameCodec.encode(allocator, streamId)); } } @@ -562,7 +558,7 @@ private Mono handleMetadataPush(Payload payload) { } ByteBuf metadataPushFrame = - MetadataPushFrameFlyweight.encodeReleasingPayload(allocator, payload); + MetadataPushFrameCodec.encodeReleasingPayload(allocator, payload); sendProcessor.onNextPrioritized(metadataPushFrame); @@ -585,8 +581,8 @@ private Throwable checkAvailable() { private void handleIncomingFrames(ByteBuf frame) { try { - int streamId = FrameHeaderFlyweight.streamId(frame); - FrameType type = FrameHeaderFlyweight.frameType(frame); + int streamId = FrameHeaderCodec.streamId(frame); + FrameType type = FrameHeaderCodec.frameType(frame); if (streamId == 0) { handleStreamZero(type, frame); } else { @@ -666,7 +662,7 @@ private void handleFrame(int streamId, FrameType type, ByteBuf frame) { { Subscription sender = senders.get(streamId); if (sender != null) { - long n = RequestNFrameFlyweight.requestN(frame); + long n = RequestNFrameCodec.requestN(frame); sender.request(n); } break; @@ -682,7 +678,7 @@ private void handleMissingResponseProcessor(int streamId, FrameType type, ByteBu if (type == FrameType.ERROR) { // message for stream that has never existed, we have a problem with // the overall connection and must tear down - String errorMessage = ErrorFrameFlyweight.dataUtf8(frame); + String errorMessage = ErrorFrameCodec.dataUtf8(frame); throw new IllegalStateException( "Client received error for non-existent stream: " diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java index 2f073ba8a..b9d3ea794 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java @@ -292,9 +292,9 @@ private synchronized void cleanUpChannelProcessors(Throwable e) { private void handleFrame(ByteBuf frame) { try { - int streamId = FrameHeaderFlyweight.streamId(frame); + int streamId = FrameHeaderCodec.streamId(frame); Subscriber receiver; - FrameType frameType = FrameHeaderFlyweight.frameType(frame); + FrameType frameType = FrameHeaderCodec.frameType(frame); switch (frameType) { case REQUEST_FNF: handleFireAndForget(streamId, fireAndForget(payloadDecoder.apply(frame))); @@ -309,12 +309,12 @@ private void handleFrame(ByteBuf frame) { handleRequestN(streamId, frame); break; case REQUEST_STREAM: - long streamInitialRequestN = RequestStreamFrameFlyweight.initialRequestN(frame); + long streamInitialRequestN = RequestStreamFrameCodec.initialRequestN(frame); Payload streamPayload = payloadDecoder.apply(frame); handleStream(streamId, requestStream(streamPayload), streamInitialRequestN, null); break; case REQUEST_CHANNEL: - long channelInitialRequestN = RequestChannelFrameFlyweight.initialRequestN(frame); + long channelInitialRequestN = RequestChannelFrameCodec.initialRequestN(frame); Payload channelPayload = payloadDecoder.apply(frame); handleChannel(streamId, channelPayload, channelInitialRequestN); break; @@ -339,7 +339,7 @@ private void handleFrame(ByteBuf frame) { case ERROR: receiver = channelProcessors.get(streamId); if (receiver != null) { - receiver.onError(new ApplicationErrorException(ErrorFrameFlyweight.dataUtf8(frame))); + receiver.onError(new ApplicationErrorException(ErrorFrameCodec.dataUtf8(frame))); } break; case NEXT_COMPLETE: @@ -408,8 +408,7 @@ protected void hookOnNext(Payload payload) { } ByteBuf byteBuf = - PayloadFrameFlyweight.encodeNextCompleteReleasingPayload( - allocator, streamId, payload); + PayloadFrameCodec.encodeNextCompleteReleasingPayload(allocator, streamId, payload); sendProcessor.onNext(byteBuf); } @@ -421,7 +420,7 @@ protected void hookOnError(Throwable throwable) { @Override protected void hookOnComplete() { if (isEmpty) { - sendProcessor.onNext(PayloadFrameFlyweight.encodeComplete(allocator, streamId)); + sendProcessor.onNext(PayloadFrameCodec.encodeComplete(allocator, streamId)); } } @@ -473,7 +472,7 @@ protected void hookOnNext(Payload payload) { } ByteBuf byteBuf = - PayloadFrameFlyweight.encodeNextReleasingPayload(allocator, streamId, payload); + PayloadFrameCodec.encodeNextReleasingPayload(allocator, streamId, payload); sendProcessor.onNext(byteBuf); } catch (Throwable e) { // specifically for requestChannel case so when Payload is invalid we will not be @@ -494,7 +493,7 @@ protected void hookOnNext(Payload payload) { @Override protected void hookOnComplete() { - sendProcessor.onNext(PayloadFrameFlyweight.encodeComplete(allocator, streamId)); + sendProcessor.onNext(PayloadFrameCodec.encodeComplete(allocator, streamId)); } @Override @@ -553,7 +552,7 @@ public void accept(long l) { n = l; } if (n > 0) { - sendProcessor.onNext(RequestNFrameFlyweight.encode(allocator, streamId, n)); + sendProcessor.onNext(RequestNFrameCodec.encode(allocator, streamId, n)); } } }) @@ -561,7 +560,7 @@ public void accept(long l) { signalType -> { if (channelProcessors.remove(streamId, frames)) { if (signalType == SignalType.CANCEL) { - sendProcessor.onNext(CancelFrameFlyweight.encode(allocator, streamId)); + sendProcessor.onNext(CancelFrameCodec.encode(allocator, streamId)); } } }) @@ -605,14 +604,14 @@ private void handleCancelFrame(int streamId) { private void handleError(int streamId, Throwable t) { errorConsumer.accept(t); - sendProcessor.onNext(ErrorFrameFlyweight.encode(allocator, streamId, t)); + sendProcessor.onNext(ErrorFrameCodec.encode(allocator, streamId, t)); } private void handleRequestN(int streamId, ByteBuf frame) { Subscription subscription = sendingSubscriptions.get(streamId); if (subscription != null) { - long n = RequestNFrameFlyweight.requestN(frame); + long n = RequestNFrameCodec.requestN(frame); subscription.request(n); } } diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java index 5960e33d4..66e2249b5 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java @@ -26,8 +26,8 @@ import io.rsocket.exceptions.InvalidSetupException; import io.rsocket.exceptions.RejectedSetupException; import io.rsocket.fragmentation.FragmentationDuplexConnection; -import io.rsocket.frame.FrameHeaderFlyweight; -import io.rsocket.frame.SetupFrameFlyweight; +import io.rsocket.frame.FrameHeaderCodec; +import io.rsocket.frame.SetupFrameCodec; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.internal.ClientServerInputMultiplexer; import io.rsocket.lease.Leases; @@ -325,7 +325,7 @@ private Mono acceptResume( private Mono accept( ServerSetup serverSetup, ByteBuf startFrame, ClientServerInputMultiplexer multiplexer) { - switch (FrameHeaderFlyweight.frameType(startFrame)) { + switch (FrameHeaderCodec.frameType(startFrame)) { case SETUP: return acceptSetup(serverSetup, startFrame, multiplexer); case RESUME: @@ -335,7 +335,7 @@ private Mono accept( .sendError( multiplexer, new InvalidSetupException( - "invalid setup frame: " + FrameHeaderFlyweight.frameType(startFrame))) + "invalid setup frame: " + FrameHeaderCodec.frameType(startFrame))) .doFinally( signalType -> { startFrame.release(); @@ -347,12 +347,12 @@ private Mono accept( private Mono acceptSetup( ServerSetup serverSetup, ByteBuf setupFrame, ClientServerInputMultiplexer multiplexer) { - if (!SetupFrameFlyweight.isSupportedVersion(setupFrame)) { + if (!SetupFrameCodec.isSupportedVersion(setupFrame)) { return serverSetup .sendError( multiplexer, new InvalidSetupException( - "Unsupported version: " + SetupFrameFlyweight.humanReadableVersion(setupFrame))) + "Unsupported version: " + SetupFrameCodec.humanReadableVersion(setupFrame))) .doFinally( signalType -> { setupFrame.release(); @@ -361,7 +361,7 @@ private Mono acceptSetup( } boolean leaseEnabled = leasesSupplier != null; - if (SetupFrameFlyweight.honorLease(setupFrame) && !leaseEnabled) { + if (SetupFrameCodec.honorLease(setupFrame) && !leaseEnabled) { return serverSetup .sendError(multiplexer, new InvalidSetupException("lease is not supported")) .doFinally( diff --git a/rsocket-core/src/main/java/io/rsocket/core/Resume.java b/rsocket-core/src/main/java/io/rsocket/core/Resume.java index 04221154f..48133af98 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/Resume.java +++ b/rsocket-core/src/main/java/io/rsocket/core/Resume.java @@ -16,7 +16,7 @@ package io.rsocket.core; import io.netty.buffer.ByteBuf; -import io.rsocket.frame.ResumeFrameFlyweight; +import io.rsocket.frame.ResumeFrameCodec; import io.rsocket.resume.InMemoryResumableFramesStore; import io.rsocket.resume.ResumableFramesStore; import java.time.Duration; @@ -43,7 +43,7 @@ public class Resume { private Duration streamTimeout = Duration.ofSeconds(10); /* Client only */ - private Supplier tokenSupplier = ResumeFrameFlyweight::generateResumeToken; + private Supplier tokenSupplier = ResumeFrameCodec::generateResumeToken; private Retry retry = Retry.backoff(Long.MAX_VALUE, Duration.ofSeconds(1)) .maxBackoff(Duration.ofSeconds(16)) diff --git a/rsocket-core/src/main/java/io/rsocket/core/ServerSetup.java b/rsocket-core/src/main/java/io/rsocket/core/ServerSetup.java index 3e20d3c60..337d17c64 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/ServerSetup.java +++ b/rsocket-core/src/main/java/io/rsocket/core/ServerSetup.java @@ -22,9 +22,9 @@ import io.rsocket.DuplexConnection; import io.rsocket.exceptions.RejectedResumeException; import io.rsocket.exceptions.UnsupportedSetupException; -import io.rsocket.frame.ErrorFrameFlyweight; -import io.rsocket.frame.ResumeFrameFlyweight; -import io.rsocket.frame.SetupFrameFlyweight; +import io.rsocket.frame.ErrorFrameCodec; +import io.rsocket.frame.ResumeFrameCodec; +import io.rsocket.frame.SetupFrameCodec; import io.rsocket.internal.ClientServerInputMultiplexer; import io.rsocket.keepalive.KeepAliveHandler; import io.rsocket.resume.*; @@ -47,7 +47,7 @@ void dispose() {} Mono sendError(ClientServerInputMultiplexer multiplexer, Exception exception) { DuplexConnection duplexConnection = multiplexer.asSetupConnection(); return duplexConnection - .sendOne(ErrorFrameFlyweight.encode(duplexConnection.alloc(), 0, exception)) + .sendOne(ErrorFrameCodec.encode(duplexConnection.alloc(), 0, exception)) .onErrorResume(err -> Mono.empty()); } @@ -59,7 +59,7 @@ public Mono acceptRSocketSetup( ClientServerInputMultiplexer multiplexer, BiFunction> then) { - if (SetupFrameFlyweight.resumeEnabled(frame)) { + if (SetupFrameCodec.resumeEnabled(frame)) { return sendError(multiplexer, new UnsupportedSetupException("resume not supported")) .doFinally( signalType -> { @@ -109,8 +109,8 @@ public Mono acceptRSocketSetup( ClientServerInputMultiplexer multiplexer, BiFunction> then) { - if (SetupFrameFlyweight.resumeEnabled(frame)) { - ByteBuf resumeToken = SetupFrameFlyweight.resumeToken(frame); + if (SetupFrameCodec.resumeEnabled(frame)) { + ByteBuf resumeToken = SetupFrameCodec.resumeToken(frame); ResumableDuplexConnection connection = sessionManager @@ -133,7 +133,7 @@ public Mono acceptRSocketSetup( @Override public Mono acceptRSocketResume(ByteBuf frame, ClientServerInputMultiplexer multiplexer) { - ServerRSocketSession session = sessionManager.get(ResumeFrameFlyweight.token(frame)); + ServerRSocketSession session = sessionManager.get(ResumeFrameCodec.token(frame)); if (session != null) { return session .continueWith(multiplexer.asClientServerConnection()) diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/ApplicationErrorException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/ApplicationErrorException.java index 351e045a3..e617b82d8 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/ApplicationErrorException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/ApplicationErrorException.java @@ -16,7 +16,7 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorFrameFlyweight; +import io.rsocket.frame.ErrorFrameCodec; import javax.annotation.Nullable; /** @@ -45,6 +45,6 @@ public ApplicationErrorException(String message) { * @param cause the cause of this exception */ public ApplicationErrorException(String message, @Nullable Throwable cause) { - super(ErrorFrameFlyweight.APPLICATION_ERROR, message, cause); + super(ErrorFrameCodec.APPLICATION_ERROR, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/CanceledException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/CanceledException.java index 537cf2bf2..3c5fc7420 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/CanceledException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/CanceledException.java @@ -16,7 +16,7 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorFrameFlyweight; +import io.rsocket.frame.ErrorFrameCodec; import javax.annotation.Nullable; /** @@ -46,6 +46,6 @@ public CanceledException(String message) { * @param cause the cause of this exception */ public CanceledException(String message, @Nullable Throwable cause) { - super(ErrorFrameFlyweight.CANCELED, message, cause); + super(ErrorFrameCodec.CANCELED, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionCloseException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionCloseException.java index f1f1a47d8..5cff2c821 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionCloseException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionCloseException.java @@ -16,7 +16,7 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorFrameFlyweight; +import io.rsocket.frame.ErrorFrameCodec; import javax.annotation.Nullable; /** @@ -46,6 +46,6 @@ public ConnectionCloseException(String message) { * @param cause the cause of this exception */ public ConnectionCloseException(String message, @Nullable Throwable cause) { - super(ErrorFrameFlyweight.CONNECTION_CLOSE, message, cause); + super(ErrorFrameCodec.CONNECTION_CLOSE, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionErrorException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionErrorException.java index 9581cfc97..3fcb8f5de 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionErrorException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionErrorException.java @@ -16,7 +16,7 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorFrameFlyweight; +import io.rsocket.frame.ErrorFrameCodec; import javax.annotation.Nullable; /** @@ -46,6 +46,6 @@ public ConnectionErrorException(String message) { * @param cause the cause of this exception */ public ConnectionErrorException(String message, @Nullable Throwable cause) { - super(ErrorFrameFlyweight.CONNECTION_ERROR, message, cause); + super(ErrorFrameCodec.CONNECTION_ERROR, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/CustomRSocketException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/CustomRSocketException.java index 5c1154ebd..18f488ba0 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/CustomRSocketException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/CustomRSocketException.java @@ -16,7 +16,7 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorFrameFlyweight; +import io.rsocket.frame.ErrorFrameCodec; import javax.annotation.Nullable; public class CustomRSocketException extends RSocketException { @@ -43,8 +43,8 @@ public CustomRSocketException(int errorCode, String message) { */ public CustomRSocketException(int errorCode, String message, @Nullable Throwable cause) { super(errorCode, message, cause); - if (errorCode > ErrorFrameFlyweight.MAX_USER_ALLOWED_ERROR_CODE - && errorCode < ErrorFrameFlyweight.MIN_USER_ALLOWED_ERROR_CODE) { + if (errorCode > ErrorFrameCodec.MAX_USER_ALLOWED_ERROR_CODE + && errorCode < ErrorFrameCodec.MIN_USER_ALLOWED_ERROR_CODE) { throw new IllegalArgumentException( "Allowed errorCode value should be in range [0x00000301-0xFFFFFFFE]", this); } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/Exceptions.java b/rsocket-core/src/main/java/io/rsocket/exceptions/Exceptions.java index fe2d304f5..5c6eee614 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/Exceptions.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/Exceptions.java @@ -16,22 +16,22 @@ package io.rsocket.exceptions; -import static io.rsocket.frame.ErrorFrameFlyweight.APPLICATION_ERROR; -import static io.rsocket.frame.ErrorFrameFlyweight.CANCELED; -import static io.rsocket.frame.ErrorFrameFlyweight.CONNECTION_CLOSE; -import static io.rsocket.frame.ErrorFrameFlyweight.CONNECTION_ERROR; -import static io.rsocket.frame.ErrorFrameFlyweight.INVALID; -import static io.rsocket.frame.ErrorFrameFlyweight.INVALID_SETUP; -import static io.rsocket.frame.ErrorFrameFlyweight.MAX_USER_ALLOWED_ERROR_CODE; -import static io.rsocket.frame.ErrorFrameFlyweight.MIN_USER_ALLOWED_ERROR_CODE; -import static io.rsocket.frame.ErrorFrameFlyweight.REJECTED; -import static io.rsocket.frame.ErrorFrameFlyweight.REJECTED_RESUME; -import static io.rsocket.frame.ErrorFrameFlyweight.REJECTED_SETUP; -import static io.rsocket.frame.ErrorFrameFlyweight.UNSUPPORTED_SETUP; +import static io.rsocket.frame.ErrorFrameCodec.APPLICATION_ERROR; +import static io.rsocket.frame.ErrorFrameCodec.CANCELED; +import static io.rsocket.frame.ErrorFrameCodec.CONNECTION_CLOSE; +import static io.rsocket.frame.ErrorFrameCodec.CONNECTION_ERROR; +import static io.rsocket.frame.ErrorFrameCodec.INVALID; +import static io.rsocket.frame.ErrorFrameCodec.INVALID_SETUP; +import static io.rsocket.frame.ErrorFrameCodec.MAX_USER_ALLOWED_ERROR_CODE; +import static io.rsocket.frame.ErrorFrameCodec.MIN_USER_ALLOWED_ERROR_CODE; +import static io.rsocket.frame.ErrorFrameCodec.REJECTED; +import static io.rsocket.frame.ErrorFrameCodec.REJECTED_RESUME; +import static io.rsocket.frame.ErrorFrameCodec.REJECTED_SETUP; +import static io.rsocket.frame.ErrorFrameCodec.UNSUPPORTED_SETUP; import io.netty.buffer.ByteBuf; import io.rsocket.RSocketErrorException; -import io.rsocket.frame.ErrorFrameFlyweight; +import io.rsocket.frame.ErrorFrameCodec; import java.util.Objects; /** Utility class that generates an exception from a frame. */ @@ -49,8 +49,8 @@ private Exceptions() {} public static RuntimeException from(int streamId, ByteBuf frame) { Objects.requireNonNull(frame, "frame must not be null"); - int errorCode = ErrorFrameFlyweight.errorCode(frame); - String message = ErrorFrameFlyweight.dataUtf8(frame); + int errorCode = ErrorFrameCodec.errorCode(frame); + String message = ErrorFrameCodec.dataUtf8(frame); if (streamId == 0) { switch (errorCode) { diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidException.java index a4b28659f..2428d1e7e 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidException.java @@ -16,7 +16,7 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorFrameFlyweight; +import io.rsocket.frame.ErrorFrameCodec; import javax.annotation.Nullable; /** @@ -45,6 +45,6 @@ public InvalidException(String message) { * @param cause the cause of this exception */ public InvalidException(String message, @Nullable Throwable cause) { - super(ErrorFrameFlyweight.INVALID, message, cause); + super(ErrorFrameCodec.INVALID, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidSetupException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidSetupException.java index 1ff53d51d..57da19bb6 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidSetupException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidSetupException.java @@ -16,7 +16,7 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorFrameFlyweight; +import io.rsocket.frame.ErrorFrameCodec; import javax.annotation.Nullable; /** @@ -46,6 +46,6 @@ public InvalidSetupException(String message) { * @param cause the cause of this exception */ public InvalidSetupException(String message, @Nullable Throwable cause) { - super(ErrorFrameFlyweight.INVALID_SETUP, message, cause); + super(ErrorFrameCodec.INVALID_SETUP, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/RSocketException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/RSocketException.java index 93c49d5e2..2b137282f 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/RSocketException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/RSocketException.java @@ -17,7 +17,7 @@ package io.rsocket.exceptions; import io.rsocket.RSocketErrorException; -import io.rsocket.frame.ErrorFrameFlyweight; +import io.rsocket.frame.ErrorFrameCodec; import reactor.util.annotation.Nullable; /** @@ -47,7 +47,7 @@ public RSocketException(String message) { * @param cause the cause of this exception */ public RSocketException(String message, @Nullable Throwable cause) { - super(ErrorFrameFlyweight.APPLICATION_ERROR, message, cause); + super(ErrorFrameCodec.APPLICATION_ERROR, message, cause); } /** diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedException.java index 3fad3f396..c87a60243 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedException.java @@ -16,7 +16,7 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorFrameFlyweight; +import io.rsocket.frame.ErrorFrameCodec; import javax.annotation.Nullable; /** @@ -47,6 +47,6 @@ public RejectedException(String message) { * @param cause the cause of this exception */ public RejectedException(String message, @Nullable Throwable cause) { - super(ErrorFrameFlyweight.REJECTED, message, cause); + super(ErrorFrameCodec.REJECTED, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedResumeException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedResumeException.java index a10eb4197..8a6ea2244 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedResumeException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedResumeException.java @@ -16,7 +16,7 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorFrameFlyweight; +import io.rsocket.frame.ErrorFrameCodec; import javax.annotation.Nullable; /** @@ -45,6 +45,6 @@ public RejectedResumeException(String message) { * @param cause the cause of this exception */ public RejectedResumeException(String message, @Nullable Throwable cause) { - super(ErrorFrameFlyweight.REJECTED_RESUME, message, cause); + super(ErrorFrameCodec.REJECTED_RESUME, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedSetupException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedSetupException.java index 6b5dc0f8b..972b430a7 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedSetupException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedSetupException.java @@ -16,7 +16,7 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorFrameFlyweight; +import io.rsocket.frame.ErrorFrameCodec; import javax.annotation.Nullable; /** @@ -45,6 +45,6 @@ public RejectedSetupException(String message) { * @param cause the cause of this exception */ public RejectedSetupException(String message, @Nullable Throwable cause) { - super(ErrorFrameFlyweight.REJECTED_SETUP, message, cause); + super(ErrorFrameCodec.REJECTED_SETUP, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/SetupException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/SetupException.java index 712508f0b..158e5410d 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/SetupException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/SetupException.java @@ -16,7 +16,7 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorFrameFlyweight; +import io.rsocket.frame.ErrorFrameCodec; import javax.annotation.Nullable; /** The root of the setup exception hierarchy. */ @@ -44,7 +44,7 @@ public SetupException(String message) { */ @Deprecated public SetupException(String message, @Nullable Throwable cause) { - this(ErrorFrameFlyweight.INVALID_SETUP, message, cause); + this(ErrorFrameCodec.INVALID_SETUP, message, cause); } /** diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/UnsupportedSetupException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/UnsupportedSetupException.java index b112b95be..3282c9750 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/UnsupportedSetupException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/UnsupportedSetupException.java @@ -16,7 +16,7 @@ package io.rsocket.exceptions; -import io.rsocket.frame.ErrorFrameFlyweight; +import io.rsocket.frame.ErrorFrameCodec; import javax.annotation.Nullable; /** @@ -45,6 +45,6 @@ public UnsupportedSetupException(String message) { * @param cause the cause of this exception */ public UnsupportedSetupException(String message, @Nullable Throwable cause) { - super(ErrorFrameFlyweight.UNSUPPORTED_SETUP, message, cause); + super(ErrorFrameCodec.UNSUPPORTED_SETUP, message, cause); } } diff --git a/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java b/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java index 24b360755..5192ffead 100644 --- a/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java +++ b/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java @@ -21,8 +21,8 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.rsocket.DuplexConnection; -import io.rsocket.frame.FrameHeaderFlyweight; -import io.rsocket.frame.FrameLengthFlyweight; +import io.rsocket.frame.FrameHeaderCodec; +import io.rsocket.frame.FrameLengthCodec; import io.rsocket.frame.FrameType; import java.util.Objects; import javax.annotation.Nullable; @@ -100,7 +100,7 @@ public Mono send(Publisher frames) { @Override public Mono sendOne(ByteBuf frame) { - FrameType frameType = FrameHeaderFlyweight.frameType(frame); + FrameType frameType = FrameHeaderCodec.frameType(frame); int readableBytes = frame.readableBytes(); if (shouldFragment(frameType, readableBytes)) { if (logger.isDebugEnabled()) { @@ -108,12 +108,12 @@ public Mono sendOne(ByteBuf frame) { Flux.from(fragmentFrame(alloc(), mtu, frame, frameType, encodeLength)) .doOnNext( byteBuf -> { - ByteBuf f = encodeLength ? FrameLengthFlyweight.frame(byteBuf) : byteBuf; + ByteBuf f = encodeLength ? FrameLengthCodec.frame(byteBuf) : byteBuf; logger.debug( "{} - stream id {} - frame type {} - \n {}", type, - FrameHeaderFlyweight.streamId(f), - FrameHeaderFlyweight.frameType(f), + FrameHeaderCodec.streamId(f), + FrameHeaderCodec.frameType(f), ByteBufUtil.prettyHexDump(f)); })); } else { @@ -127,7 +127,7 @@ public Mono sendOne(ByteBuf frame) { private ByteBuf encode(ByteBuf frame) { if (encodeLength) { - return FrameLengthFlyweight.encode(alloc(), frame.readableBytes(), frame); + return FrameLengthCodec.encode(alloc(), frame.readableBytes(), frame); } else { return frame; } diff --git a/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameFragmenter.java b/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameFragmenter.java index 8593d2be7..4b8fd36e9 100644 --- a/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameFragmenter.java +++ b/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameFragmenter.java @@ -20,14 +20,14 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; import io.netty.util.ReferenceCountUtil; -import io.rsocket.frame.FrameHeaderFlyweight; -import io.rsocket.frame.FrameLengthFlyweight; +import io.rsocket.frame.FrameHeaderCodec; +import io.rsocket.frame.FrameLengthCodec; import io.rsocket.frame.FrameType; -import io.rsocket.frame.PayloadFrameFlyweight; -import io.rsocket.frame.RequestChannelFrameFlyweight; -import io.rsocket.frame.RequestFireAndForgetFrameFlyweight; -import io.rsocket.frame.RequestResponseFrameFlyweight; -import io.rsocket.frame.RequestStreamFrameFlyweight; +import io.rsocket.frame.PayloadFrameCodec; +import io.rsocket.frame.RequestChannelFrameCodec; +import io.rsocket.frame.RequestFireAndForgetFrameCodec; +import io.rsocket.frame.RequestResponseFrameCodec; +import io.rsocket.frame.RequestStreamFrameCodec; import java.util.function.Consumer; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; @@ -49,7 +49,7 @@ static Publisher fragmentFrame( boolean encodeLength) { ByteBuf metadata = getMetadata(frame, frameType); ByteBuf data = getData(frame, frameType); - int streamId = FrameHeaderFlyweight.streamId(frame); + int streamId = FrameHeaderCodec.streamId(frame); return Flux.generate( new Consumer>() { boolean first = true; @@ -84,7 +84,7 @@ static ByteBuf encodeFirstFragment( ByteBuf metadata, ByteBuf data) { // subtract the header bytes - int remaining = mtu - FrameHeaderFlyweight.size(); + int remaining = mtu - FrameHeaderCodec.size(); // substract the initial request n switch (frameType) { @@ -112,40 +112,40 @@ static ByteBuf encodeFirstFragment( switch (frameType) { case REQUEST_FNF: - return RequestFireAndForgetFrameFlyweight.encode( + return RequestFireAndForgetFrameCodec.encode( allocator, streamId, true, metadataFragment, dataFragment); case REQUEST_STREAM: - return RequestStreamFrameFlyweight.encode( + return RequestStreamFrameCodec.encode( allocator, streamId, true, - RequestStreamFrameFlyweight.initialRequestN(frame), + RequestStreamFrameCodec.initialRequestN(frame), metadataFragment, dataFragment); case REQUEST_RESPONSE: - return RequestResponseFrameFlyweight.encode( + return RequestResponseFrameCodec.encode( allocator, streamId, true, metadataFragment, dataFragment); case REQUEST_CHANNEL: - return RequestChannelFrameFlyweight.encode( + return RequestChannelFrameCodec.encode( allocator, streamId, true, false, - RequestChannelFrameFlyweight.initialRequestN(frame), + RequestChannelFrameCodec.initialRequestN(frame), metadataFragment, dataFragment); // Payload and synthetic types case PAYLOAD: - return PayloadFrameFlyweight.encode( + return PayloadFrameCodec.encode( allocator, streamId, true, false, false, metadataFragment, dataFragment); case NEXT: - return PayloadFrameFlyweight.encode( + return PayloadFrameCodec.encode( allocator, streamId, true, false, true, metadataFragment, dataFragment); case NEXT_COMPLETE: - return PayloadFrameFlyweight.encode( + return PayloadFrameCodec.encode( allocator, streamId, true, true, true, metadataFragment, dataFragment); case COMPLETE: - return PayloadFrameFlyweight.encode( + return PayloadFrameCodec.encode( allocator, streamId, true, true, false, metadataFragment, dataFragment); default: throw new IllegalStateException("unsupported fragment type: " + frameType); @@ -155,7 +155,7 @@ static ByteBuf encodeFirstFragment( static ByteBuf encodeFollowsFragment( ByteBufAllocator allocator, int mtu, int streamId, ByteBuf metadata, ByteBuf data) { // subtract the header bytes - int remaining = mtu - FrameHeaderFlyweight.size(); + int remaining = mtu - FrameHeaderCodec.size(); ByteBuf metadataFragment = null; if (metadata.isReadable()) { @@ -173,33 +173,33 @@ static ByteBuf encodeFollowsFragment( } boolean follows = data.isReadable() || metadata.isReadable(); - return PayloadFrameFlyweight.encode( + return PayloadFrameCodec.encode( allocator, streamId, follows, false, true, metadataFragment, dataFragment); } static ByteBuf getMetadata(ByteBuf frame, FrameType frameType) { - boolean hasMetadata = FrameHeaderFlyweight.hasMetadata(frame); + boolean hasMetadata = FrameHeaderCodec.hasMetadata(frame); if (hasMetadata) { ByteBuf metadata; switch (frameType) { case REQUEST_FNF: - metadata = RequestFireAndForgetFrameFlyweight.metadata(frame); + metadata = RequestFireAndForgetFrameCodec.metadata(frame); break; case REQUEST_STREAM: - metadata = RequestStreamFrameFlyweight.metadata(frame); + metadata = RequestStreamFrameCodec.metadata(frame); break; case REQUEST_RESPONSE: - metadata = RequestResponseFrameFlyweight.metadata(frame); + metadata = RequestResponseFrameCodec.metadata(frame); break; case REQUEST_CHANNEL: - metadata = RequestChannelFrameFlyweight.metadata(frame); + metadata = RequestChannelFrameCodec.metadata(frame); break; // Payload and synthetic types case PAYLOAD: case NEXT: case NEXT_COMPLETE: case COMPLETE: - metadata = PayloadFrameFlyweight.metadata(frame); + metadata = PayloadFrameCodec.metadata(frame); break; default: throw new IllegalStateException("unsupported fragment type"); @@ -214,23 +214,23 @@ static ByteBuf getData(ByteBuf frame, FrameType frameType) { ByteBuf data; switch (frameType) { case REQUEST_FNF: - data = RequestFireAndForgetFrameFlyweight.data(frame); + data = RequestFireAndForgetFrameCodec.data(frame); break; case REQUEST_STREAM: - data = RequestStreamFrameFlyweight.data(frame); + data = RequestStreamFrameCodec.data(frame); break; case REQUEST_RESPONSE: - data = RequestResponseFrameFlyweight.data(frame); + data = RequestResponseFrameCodec.data(frame); break; case REQUEST_CHANNEL: - data = RequestChannelFrameFlyweight.data(frame); + data = RequestChannelFrameCodec.data(frame); break; // Payload and synthetic types case PAYLOAD: case NEXT: case NEXT_COMPLETE: case COMPLETE: - data = PayloadFrameFlyweight.data(frame); + data = PayloadFrameCodec.data(frame); break; default: throw new IllegalStateException("unsupported fragment type"); @@ -240,7 +240,7 @@ static ByteBuf getData(ByteBuf frame, FrameType frameType) { static ByteBuf encode(ByteBufAllocator allocator, ByteBuf frame, boolean encodeLength) { if (encodeLength) { - return FrameLengthFlyweight.encode(allocator, frame.readableBytes(), frame); + return FrameLengthCodec.encode(allocator, frame.readableBytes(), frame); } else { return frame; } diff --git a/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameReassembler.java b/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameReassembler.java index 1a8d242b2..1e96bd1fc 100644 --- a/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameReassembler.java +++ b/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameReassembler.java @@ -146,12 +146,12 @@ void cancelAssemble(int streamId) { void handleNoFollowsFlag(ByteBuf frame, SynchronousSink sink, int streamId) { ByteBuf header = removeHeader(streamId); if (header != null) { - if (FrameHeaderFlyweight.hasMetadata(header)) { + if (FrameHeaderCodec.hasMetadata(header)) { ByteBuf assembledFrame = assembleFrameWithMetadata(frame, streamId, header); sink.next(assembledFrame); } else { ByteBuf data = assembleData(frame, streamId); - ByteBuf assembledFrame = FragmentationFlyweight.encode(allocator, header, data); + ByteBuf assembledFrame = FragmentationCodec.encode(allocator, header, data); sink.next(assembledFrame); } frame.release(); @@ -163,36 +163,36 @@ void handleNoFollowsFlag(ByteBuf frame, SynchronousSink sink, int strea void handleFollowsFlag(ByteBuf frame, int streamId, FrameType frameType) { ByteBuf header = getHeader(streamId); if (header == null) { - header = frame.copy(frame.readerIndex(), FrameHeaderFlyweight.size()); + header = frame.copy(frame.readerIndex(), FrameHeaderCodec.size()); if (frameType == FrameType.REQUEST_CHANNEL || frameType == FrameType.REQUEST_STREAM) { - long i = RequestChannelFrameFlyweight.initialRequestN(frame); + long i = RequestChannelFrameCodec.initialRequestN(frame); header.writeInt(i > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) i); } putHeader(streamId, header); } - if (FrameHeaderFlyweight.hasMetadata(frame)) { + if (FrameHeaderCodec.hasMetadata(frame)) { CompositeByteBuf metadata = getMetadata(streamId); switch (frameType) { case REQUEST_FNF: - metadata.addComponents(true, RequestFireAndForgetFrameFlyweight.metadata(frame).retain()); + metadata.addComponents(true, RequestFireAndForgetFrameCodec.metadata(frame).retain()); break; case REQUEST_STREAM: - metadata.addComponents(true, RequestStreamFrameFlyweight.metadata(frame).retain()); + metadata.addComponents(true, RequestStreamFrameCodec.metadata(frame).retain()); break; case REQUEST_RESPONSE: - metadata.addComponents(true, RequestResponseFrameFlyweight.metadata(frame).retain()); + metadata.addComponents(true, RequestResponseFrameCodec.metadata(frame).retain()); break; case REQUEST_CHANNEL: - metadata.addComponents(true, RequestChannelFrameFlyweight.metadata(frame).retain()); + metadata.addComponents(true, RequestChannelFrameCodec.metadata(frame).retain()); break; // Payload and synthetic types case PAYLOAD: case NEXT: case NEXT_COMPLETE: case COMPLETE: - metadata.addComponents(true, PayloadFrameFlyweight.metadata(frame).retain()); + metadata.addComponents(true, PayloadFrameCodec.metadata(frame).retain()); break; default: throw new IllegalStateException("unsupported fragment type"); @@ -202,23 +202,23 @@ void handleFollowsFlag(ByteBuf frame, int streamId, FrameType frameType) { ByteBuf data; switch (frameType) { case REQUEST_FNF: - data = RequestFireAndForgetFrameFlyweight.data(frame).retain(); + data = RequestFireAndForgetFrameCodec.data(frame).retain(); break; case REQUEST_STREAM: - data = RequestStreamFrameFlyweight.data(frame).retain(); + data = RequestStreamFrameCodec.data(frame).retain(); break; case REQUEST_RESPONSE: - data = RequestResponseFrameFlyweight.data(frame).retain(); + data = RequestResponseFrameCodec.data(frame).retain(); break; case REQUEST_CHANNEL: - data = RequestChannelFrameFlyweight.data(frame).retain(); + data = RequestChannelFrameCodec.data(frame).retain(); break; // Payload and synthetic types case PAYLOAD: case NEXT: case NEXT_COMPLETE: case COMPLETE: - data = PayloadFrameFlyweight.data(frame).retain(); + data = PayloadFrameCodec.data(frame).retain(); break; default: throw new IllegalStateException("unsupported fragment type"); @@ -230,8 +230,8 @@ void handleFollowsFlag(ByteBuf frame, int streamId, FrameType frameType) { void reassembleFrame(ByteBuf frame, SynchronousSink sink) { try { - FrameType frameType = FrameHeaderFlyweight.frameType(frame); - int streamId = FrameHeaderFlyweight.streamId(frame); + FrameType frameType = FrameHeaderCodec.frameType(frame); + int streamId = FrameHeaderCodec.streamId(frame); switch (frameType) { case CANCEL: case ERROR: @@ -244,7 +244,7 @@ void reassembleFrame(ByteBuf frame, SynchronousSink sink) { return; } - boolean hasFollows = FrameHeaderFlyweight.hasFollows(frame); + boolean hasFollows = FrameHeaderCodec.hasFollows(frame); if (hasFollows) { handleFollowsFlag(frame, streamId, frameType); @@ -262,12 +262,12 @@ private ByteBuf assembleFrameWithMetadata(ByteBuf frame, int streamId, ByteBuf h ByteBuf metadata; CompositeByteBuf cm = removeMetadata(streamId); - ByteBuf decodedMetadata = PayloadFrameFlyweight.metadata(frame); + ByteBuf decodedMetadata = PayloadFrameCodec.metadata(frame); if (decodedMetadata != null) { if (cm != null) { metadata = cm.addComponents(true, decodedMetadata.retain()); } else { - metadata = PayloadFrameFlyweight.metadata(frame).retain(); + metadata = PayloadFrameCodec.metadata(frame).retain(); } } else { metadata = cm != null ? cm : null; @@ -275,14 +275,14 @@ private ByteBuf assembleFrameWithMetadata(ByteBuf frame, int streamId, ByteBuf h ByteBuf data = assembleData(frame, streamId); - return FragmentationFlyweight.encode(allocator, header, metadata, data); + return FragmentationCodec.encode(allocator, header, metadata, data); } private ByteBuf assembleData(ByteBuf frame, int streamId) { ByteBuf data; CompositeByteBuf cd = removeData(streamId); if (cd != null) { - cd.addComponents(true, PayloadFrameFlyweight.data(frame).retain()); + cd.addComponents(true, PayloadFrameCodec.data(frame).retain()); data = cd; } else { data = Unpooled.EMPTY_BUFFER; diff --git a/rsocket-core/src/main/java/io/rsocket/fragmentation/ReassemblyDuplexConnection.java b/rsocket-core/src/main/java/io/rsocket/fragmentation/ReassemblyDuplexConnection.java index 933755bb2..6060c0c20 100644 --- a/rsocket-core/src/main/java/io/rsocket/fragmentation/ReassemblyDuplexConnection.java +++ b/rsocket-core/src/main/java/io/rsocket/fragmentation/ReassemblyDuplexConnection.java @@ -19,7 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; -import io.rsocket.frame.FrameLengthFlyweight; +import io.rsocket.frame.FrameLengthCodec; import java.util.Objects; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; @@ -58,7 +58,7 @@ public Mono sendOne(ByteBuf frame) { private ByteBuf decode(ByteBuf frame) { if (decodeLength) { - return FrameLengthFlyweight.frame(frame).retain(); + return FrameLengthCodec.frame(frame).retain(); } else { return frame; } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/CancelFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/CancelFrameCodec.java similarity index 55% rename from rsocket-core/src/main/java/io/rsocket/frame/CancelFrameFlyweight.java rename to rsocket-core/src/main/java/io/rsocket/frame/CancelFrameCodec.java index 349a43c3a..d0d929f0f 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/CancelFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/CancelFrameCodec.java @@ -3,10 +3,10 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; -public class CancelFrameFlyweight { - private CancelFrameFlyweight() {} +public class CancelFrameCodec { + private CancelFrameCodec() {} public static ByteBuf encode(final ByteBufAllocator allocator, final int streamId) { - return FrameHeaderFlyweight.encode(allocator, streamId, FrameType.CANCEL, 0); + return FrameHeaderCodec.encode(allocator, streamId, FrameType.CANCEL, 0); } } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/ErrorFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/ErrorFrameCodec.java similarity index 89% rename from rsocket-core/src/main/java/io/rsocket/frame/ErrorFrameFlyweight.java rename to rsocket-core/src/main/java/io/rsocket/frame/ErrorFrameCodec.java index ab26233f1..dcacb57dc 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/ErrorFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/ErrorFrameCodec.java @@ -6,7 +6,7 @@ import io.rsocket.RSocketErrorException; import java.nio.charset.StandardCharsets; -public class ErrorFrameFlyweight { +public class ErrorFrameCodec { // defined zero stream id error codes public static final int INVALID_SETUP = 0x00000001; @@ -26,7 +26,7 @@ public class ErrorFrameFlyweight { public static ByteBuf encode( ByteBufAllocator allocator, int streamId, Throwable t, ByteBuf data) { - ByteBuf header = FrameHeaderFlyweight.encode(allocator, streamId, FrameType.ERROR, 0); + ByteBuf header = FrameHeaderCodec.encode(allocator, streamId, FrameType.ERROR, 0); int errorCode = t instanceof RSocketErrorException @@ -46,7 +46,7 @@ public static ByteBuf encode(ByteBufAllocator allocator, int streamId, Throwable public static int errorCode(ByteBuf byteBuf) { byteBuf.markReaderIndex(); - byteBuf.skipBytes(FrameHeaderFlyweight.size()); + byteBuf.skipBytes(FrameHeaderCodec.size()); int i = byteBuf.readInt(); byteBuf.resetReaderIndex(); return i; @@ -54,7 +54,7 @@ public static int errorCode(ByteBuf byteBuf) { public static ByteBuf data(ByteBuf byteBuf) { byteBuf.markReaderIndex(); - byteBuf.skipBytes(FrameHeaderFlyweight.size() + Integer.BYTES); + byteBuf.skipBytes(FrameHeaderCodec.size() + Integer.BYTES); ByteBuf slice = byteBuf.slice(); byteBuf.resetReaderIndex(); return slice; diff --git a/rsocket-core/src/main/java/io/rsocket/frame/ErrorType.java b/rsocket-core/src/main/java/io/rsocket/frame/ErrorType.java deleted file mode 100644 index b41a5d59e..000000000 --- a/rsocket-core/src/main/java/io/rsocket/frame/ErrorType.java +++ /dev/null @@ -1,85 +0,0 @@ -package io.rsocket.frame; - -/** - * The types of {@link Error} that can be set. - * - * @see Error - * Codes - * @deprecated please use constants in {@link ErrorFrameFlyweight}. - */ -@Deprecated -public final class ErrorType { - - /** - * Application layer logic generating a Reactive Streams onError event. Stream ID MUST be > 0. - */ - public static final int APPLICATION_ERROR = 0x00000201; - - /** - * The Responder canceled the request but may have started processing it (similar to REJECTED but - * doesn't guarantee lack of side-effects). Stream ID MUST be > 0. - */ - public static final int CANCELED = 0x00000203; - - /** - * The connection is being terminated. Stream ID MUST be 0. Sender or Receiver of this frame MUST - * wait for outstanding streams to terminate before closing the connection. New requests MAY not - * be accepted. - */ - public static final int CONNECTION_CLOSE = 0x00000102; - - /** - * The connection is being terminated. Stream ID MUST be 0. Sender or Receiver of this frame MAY - * close the connection immediately without waiting for outstanding streams to terminate. - */ - public static final int CONNECTION_ERROR = 0x00000101; - - /** The request is invalid. Stream ID MUST be > 0. */ - public static final int INVALID = 0x00000204; - - /** - * The Setup frame is invalid for the server (it could be that the client is too recent for the - * old server). Stream ID MUST be 0. - */ - public static final int INVALID_SETUP = 0x00000001; - - /** - * Despite being a valid request, the Responder decided to reject it. The Responder guarantees - * that it didn't process the request. The reason for the rejection is explained in the Error Data - * section. Stream ID MUST be > 0. - */ - public static final int REJECTED = 0x00000202; - - /** - * The server rejected the resume, it can specify the reason in the payload. Stream ID MUST be 0. - */ - public static final int REJECTED_RESUME = 0x00000004; - - /** - * The server rejected the setup, it can specify the reason in the payload. Stream ID MUST be 0. - */ - public static final int REJECTED_SETUP = 0x00000003; - - /** Reserved. */ - public static final int RESERVED = 0x00000000; - - /** Reserved for Extension Use. */ - public static final int RESERVED_FOR_EXTENSION = 0xFFFFFFFF; - - /** - * Some (or all) of the parameters specified by the client are unsupported by the server. Stream - * ID MUST be 0. - */ - public static final int UNSUPPORTED_SETUP = 0x00000002; - - /** Minimum allowed user defined error code value */ - public static final int MIN_USER_ALLOWED_ERROR_CODE = 0x00000301; - - /** - * Maximum allowed user defined error code value. Note, the value is above signed integer maximum, - * so it will be negative after overflow. - */ - public static final int MAX_USER_ALLOWED_ERROR_CODE = 0xFFFFFFFE; - - private ErrorType() {} -} diff --git a/rsocket-core/src/main/java/io/rsocket/frame/ExtensionFrameCodec.java b/rsocket-core/src/main/java/io/rsocket/frame/ExtensionFrameCodec.java new file mode 100644 index 000000000..bf30b9556 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/frame/ExtensionFrameCodec.java @@ -0,0 +1,66 @@ +package io.rsocket.frame; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import javax.annotation.Nullable; + +public class ExtensionFrameCodec { + private ExtensionFrameCodec() {} + + public static ByteBuf encode( + ByteBufAllocator allocator, + int streamId, + int extendedType, + @Nullable ByteBuf metadata, + ByteBuf data) { + + final boolean hasMetadata = metadata != null; + + int flags = FrameHeaderCodec.FLAGS_I; + + if (hasMetadata) { + flags |= FrameHeaderCodec.FLAGS_M; + } + + final ByteBuf header = FrameHeaderCodec.encode(allocator, streamId, FrameType.EXT, flags); + header.writeInt(extendedType); + + return FrameBodyCodec.encode(allocator, header, metadata, hasMetadata, data); + } + + public static int extendedType(ByteBuf byteBuf) { + FrameHeaderCodec.ensureFrameType(FrameType.EXT, byteBuf); + byteBuf.markReaderIndex(); + byteBuf.skipBytes(FrameHeaderCodec.size()); + int i = byteBuf.readInt(); + byteBuf.resetReaderIndex(); + return i; + } + + public static ByteBuf data(ByteBuf byteBuf) { + FrameHeaderCodec.ensureFrameType(FrameType.EXT, byteBuf); + + boolean hasMetadata = FrameHeaderCodec.hasMetadata(byteBuf); + byteBuf.markReaderIndex(); + // Extended type + byteBuf.skipBytes(FrameHeaderCodec.size() + Integer.BYTES); + ByteBuf data = FrameBodyCodec.dataWithoutMarking(byteBuf, hasMetadata); + byteBuf.resetReaderIndex(); + return data; + } + + public static ByteBuf metadata(ByteBuf byteBuf) { + FrameHeaderCodec.ensureFrameType(FrameType.EXT, byteBuf); + + boolean hasMetadata = FrameHeaderCodec.hasMetadata(byteBuf); + if (!hasMetadata) { + return null; + } + byteBuf.markReaderIndex(); + // Extended type + byteBuf.skipBytes(FrameHeaderCodec.size() + Integer.BYTES); + ByteBuf metadata = FrameBodyCodec.metadataWithoutMarking(byteBuf); + byteBuf.resetReaderIndex(); + return metadata; + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/frame/ExtensionFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/ExtensionFrameFlyweight.java deleted file mode 100644 index 8cb01b08f..000000000 --- a/rsocket-core/src/main/java/io/rsocket/frame/ExtensionFrameFlyweight.java +++ /dev/null @@ -1,66 +0,0 @@ -package io.rsocket.frame; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import javax.annotation.Nullable; - -public class ExtensionFrameFlyweight { - private ExtensionFrameFlyweight() {} - - public static ByteBuf encode( - ByteBufAllocator allocator, - int streamId, - int extendedType, - @Nullable ByteBuf metadata, - ByteBuf data) { - - final boolean hasMetadata = metadata != null; - - int flags = FrameHeaderFlyweight.FLAGS_I; - - if (hasMetadata) { - flags |= FrameHeaderFlyweight.FLAGS_M; - } - - final ByteBuf header = FrameHeaderFlyweight.encode(allocator, streamId, FrameType.EXT, flags); - header.writeInt(extendedType); - - return DataAndMetadataFlyweight.encode(allocator, header, metadata, hasMetadata, data); - } - - public static int extendedType(ByteBuf byteBuf) { - FrameHeaderFlyweight.ensureFrameType(FrameType.EXT, byteBuf); - byteBuf.markReaderIndex(); - byteBuf.skipBytes(FrameHeaderFlyweight.size()); - int i = byteBuf.readInt(); - byteBuf.resetReaderIndex(); - return i; - } - - public static ByteBuf data(ByteBuf byteBuf) { - FrameHeaderFlyweight.ensureFrameType(FrameType.EXT, byteBuf); - - boolean hasMetadata = FrameHeaderFlyweight.hasMetadata(byteBuf); - byteBuf.markReaderIndex(); - // Extended type - byteBuf.skipBytes(FrameHeaderFlyweight.size() + Integer.BYTES); - ByteBuf data = DataAndMetadataFlyweight.dataWithoutMarking(byteBuf, hasMetadata); - byteBuf.resetReaderIndex(); - return data; - } - - public static ByteBuf metadata(ByteBuf byteBuf) { - FrameHeaderFlyweight.ensureFrameType(FrameType.EXT, byteBuf); - - boolean hasMetadata = FrameHeaderFlyweight.hasMetadata(byteBuf); - if (!hasMetadata) { - return null; - } - byteBuf.markReaderIndex(); - // Extended type - byteBuf.skipBytes(FrameHeaderFlyweight.size() + Integer.BYTES); - ByteBuf metadata = DataAndMetadataFlyweight.metadataWithoutMarking(byteBuf); - byteBuf.resetReaderIndex(); - return metadata; - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/frame/FragmentationFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/FragmentationCodec.java similarity index 80% rename from rsocket-core/src/main/java/io/rsocket/frame/FragmentationFlyweight.java rename to rsocket-core/src/main/java/io/rsocket/frame/FragmentationCodec.java index a91d52782..de228b271 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/FragmentationFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/FragmentationCodec.java @@ -5,7 +5,7 @@ import reactor.util.annotation.Nullable; /** FragmentationFlyweight is used to re-assemble frames */ -public class FragmentationFlyweight { +public class FragmentationCodec { public static ByteBuf encode(final ByteBufAllocator allocator, ByteBuf header, ByteBuf data) { return encode(allocator, header, null, data); } @@ -14,6 +14,6 @@ public static ByteBuf encode( final ByteBufAllocator allocator, ByteBuf header, @Nullable ByteBuf metadata, ByteBuf data) { final boolean hasMetadata = metadata != null; - return DataAndMetadataFlyweight.encode(allocator, header, metadata, hasMetadata, data); + return FrameBodyCodec.encode(allocator, header, metadata, hasMetadata, data); } } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/DataAndMetadataFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/FrameBodyCodec.java similarity index 97% rename from rsocket-core/src/main/java/io/rsocket/frame/DataAndMetadataFlyweight.java rename to rsocket-core/src/main/java/io/rsocket/frame/FrameBodyCodec.java index 73bfd38f1..3256d4426 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/DataAndMetadataFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/FrameBodyCodec.java @@ -4,10 +4,10 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; -class DataAndMetadataFlyweight { +class FrameBodyCodec { public static final int FRAME_LENGTH_MASK = 0xFFFFFF; - private DataAndMetadataFlyweight() {} + private FrameBodyCodec() {} private static void encodeLength(final ByteBuf byteBuf, final int length) { if ((length & ~FRAME_LENGTH_MASK) != 0) { diff --git a/rsocket-core/src/main/java/io/rsocket/frame/FrameHeaderFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/FrameHeaderCodec.java similarity index 98% rename from rsocket-core/src/main/java/io/rsocket/frame/FrameHeaderFlyweight.java rename to rsocket-core/src/main/java/io/rsocket/frame/FrameHeaderCodec.java index cbc677444..28f39459d 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/FrameHeaderFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/FrameHeaderCodec.java @@ -12,7 +12,7 @@ * *

    Not thread-safe. Assumed to be used single-threaded */ -public final class FrameHeaderFlyweight { +public final class FrameHeaderCodec { /** (I)gnore flag: a value of 0 indicates the protocol can't ignore this frame */ public static final int FLAGS_I = 0b10_0000_0000; /** (M)etadata flag: a value of 1 indicates the frame contains metadata */ @@ -38,7 +38,7 @@ public final class FrameHeaderFlyweight { disableFrameTypeCheck = Boolean.getBoolean(DISABLE_FRAME_TYPE_CHECK); } - private FrameHeaderFlyweight() {} + private FrameHeaderCodec() {} static ByteBuf encodeStreamZero( final ByteBufAllocator allocator, final FrameType frameType, int flags) { diff --git a/rsocket-core/src/main/java/io/rsocket/frame/FrameLengthFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/FrameLengthCodec.java similarity index 95% rename from rsocket-core/src/main/java/io/rsocket/frame/FrameLengthFlyweight.java rename to rsocket-core/src/main/java/io/rsocket/frame/FrameLengthCodec.java index 622160061..f6c19c8ee 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/FrameLengthFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/FrameLengthCodec.java @@ -7,11 +7,11 @@ * Some transports like TCP aren't framed, and require a length. This is used by DuplexConnections * for transports that need to send length */ -public class FrameLengthFlyweight { +public class FrameLengthCodec { public static final int FRAME_LENGTH_MASK = 0xFFFFFF; public static final int FRAME_LENGTH_SIZE = 3; - private FrameLengthFlyweight() {} + private FrameLengthCodec() {} private static void encodeLength(final ByteBuf byteBuf, final int length) { if ((length & ~FRAME_LENGTH_MASK) != 0) { diff --git a/rsocket-core/src/main/java/io/rsocket/frame/FrameUtil.java b/rsocket-core/src/main/java/io/rsocket/frame/FrameUtil.java index 6662d34af..66d18c8a7 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/FrameUtil.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/FrameUtil.java @@ -9,8 +9,8 @@ public class FrameUtil { private FrameUtil() {} public static String toString(ByteBuf frame) { - FrameType frameType = FrameHeaderFlyweight.frameType(frame); - int streamId = FrameHeaderFlyweight.streamId(frame); + FrameType frameType = FrameHeaderCodec.frameType(frame); + int streamId = FrameHeaderCodec.streamId(frame); StringBuilder payload = new StringBuilder(); payload @@ -19,20 +19,18 @@ public static String toString(ByteBuf frame) { .append(" Type: ") .append(frameType) .append(" Flags: 0b") - .append(Integer.toBinaryString(FrameHeaderFlyweight.flags(frame))) + .append(Integer.toBinaryString(FrameHeaderCodec.flags(frame))) .append(" Length: " + frame.readableBytes()); if (frameType.hasInitialRequestN()) { - payload - .append(" InitialRequestN: ") - .append(RequestStreamFrameFlyweight.initialRequestN(frame)); + payload.append(" InitialRequestN: ").append(RequestStreamFrameCodec.initialRequestN(frame)); } if (frameType == FrameType.REQUEST_N) { - payload.append(" RequestN: ").append(RequestNFrameFlyweight.requestN(frame)); + payload.append(" RequestN: ").append(RequestNFrameCodec.requestN(frame)); } - if (FrameHeaderFlyweight.hasMetadata(frame)) { + if (FrameHeaderCodec.hasMetadata(frame)) { payload.append("\nMetadata:\n"); ByteBufUtil.appendPrettyHexDump(payload, getMetadata(frame, frameType)); @@ -45,37 +43,37 @@ public static String toString(ByteBuf frame) { } private static ByteBuf getMetadata(ByteBuf frame, FrameType frameType) { - boolean hasMetadata = FrameHeaderFlyweight.hasMetadata(frame); + boolean hasMetadata = FrameHeaderCodec.hasMetadata(frame); if (hasMetadata) { ByteBuf metadata; switch (frameType) { case REQUEST_FNF: - metadata = RequestFireAndForgetFrameFlyweight.metadata(frame); + metadata = RequestFireAndForgetFrameCodec.metadata(frame); break; case REQUEST_STREAM: - metadata = RequestStreamFrameFlyweight.metadata(frame); + metadata = RequestStreamFrameCodec.metadata(frame); break; case REQUEST_RESPONSE: - metadata = RequestResponseFrameFlyweight.metadata(frame); + metadata = RequestResponseFrameCodec.metadata(frame); break; case REQUEST_CHANNEL: - metadata = RequestChannelFrameFlyweight.metadata(frame); + metadata = RequestChannelFrameCodec.metadata(frame); break; // Payload and synthetic types case PAYLOAD: case NEXT: case NEXT_COMPLETE: case COMPLETE: - metadata = PayloadFrameFlyweight.metadata(frame); + metadata = PayloadFrameCodec.metadata(frame); break; case METADATA_PUSH: - metadata = MetadataPushFrameFlyweight.metadata(frame); + metadata = MetadataPushFrameCodec.metadata(frame); break; case SETUP: - metadata = SetupFrameFlyweight.metadata(frame); + metadata = SetupFrameCodec.metadata(frame); break; case LEASE: - metadata = LeaseFrameFlyweight.metadata(frame); + metadata = LeaseFrameCodec.metadata(frame); break; default: return Unpooled.EMPTY_BUFFER; @@ -90,26 +88,26 @@ private static ByteBuf getData(ByteBuf frame, FrameType frameType) { ByteBuf data; switch (frameType) { case REQUEST_FNF: - data = RequestFireAndForgetFrameFlyweight.data(frame); + data = RequestFireAndForgetFrameCodec.data(frame); break; case REQUEST_STREAM: - data = RequestStreamFrameFlyweight.data(frame); + data = RequestStreamFrameCodec.data(frame); break; case REQUEST_RESPONSE: - data = RequestResponseFrameFlyweight.data(frame); + data = RequestResponseFrameCodec.data(frame); break; case REQUEST_CHANNEL: - data = RequestChannelFrameFlyweight.data(frame); + data = RequestChannelFrameCodec.data(frame); break; // Payload and synthetic types case PAYLOAD: case NEXT: case NEXT_COMPLETE: case COMPLETE: - data = PayloadFrameFlyweight.data(frame); + data = PayloadFrameCodec.data(frame); break; case SETUP: - data = SetupFrameFlyweight.data(frame); + data = SetupFrameCodec.data(frame); break; default: return Unpooled.EMPTY_BUFFER; diff --git a/rsocket-core/src/main/java/io/rsocket/frame/GenericFrameCodec.java b/rsocket-core/src/main/java/io/rsocket/frame/GenericFrameCodec.java new file mode 100644 index 000000000..65e7eeeea --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/frame/GenericFrameCodec.java @@ -0,0 +1,157 @@ +package io.rsocket.frame; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.util.IllegalReferenceCountException; +import io.rsocket.Payload; +import javax.annotation.Nullable; + +class GenericFrameCodec { + + static ByteBuf encodeReleasingPayload( + final ByteBufAllocator allocator, + final FrameType frameType, + final int streamId, + boolean complete, + boolean next, + final Payload payload) { + return encodeReleasingPayload(allocator, frameType, streamId, complete, next, 0, payload); + } + + static ByteBuf encodeReleasingPayload( + final ByteBufAllocator allocator, + final FrameType frameType, + final int streamId, + boolean complete, + boolean next, + int requestN, + final Payload payload) { + + // if refCnt exceptions throws here it is safe to do no-op + boolean hasMetadata = payload.hasMetadata(); + // if refCnt exceptions throws here it is safe to do no-op still + final ByteBuf metadata = hasMetadata ? payload.metadata().retain() : null; + final ByteBuf data; + // retaining data safely. May throw either NPE or RefCntE + try { + data = payload.data().retain(); + } catch (IllegalReferenceCountException | NullPointerException e) { + if (hasMetadata) { + metadata.release(); + } + throw e; + } + // releasing payload safely since it can be already released wheres we have to release retained + // data and metadata as well + try { + payload.release(); + } catch (IllegalReferenceCountException e) { + data.release(); + if (hasMetadata) { + metadata.release(); + } + throw e; + } + + return encode(allocator, frameType, streamId, false, complete, next, requestN, metadata, data); + } + + static ByteBuf encode( + final ByteBufAllocator allocator, + final FrameType frameType, + final int streamId, + boolean fragmentFollows, + @Nullable ByteBuf metadata, + ByteBuf data) { + return encode(allocator, frameType, streamId, fragmentFollows, false, false, 0, metadata, data); + } + + static ByteBuf encode( + final ByteBufAllocator allocator, + final FrameType frameType, + final int streamId, + boolean fragmentFollows, + boolean complete, + boolean next, + int requestN, + @Nullable ByteBuf metadata, + ByteBuf data) { + + final boolean hasMetadata = metadata != null; + + int flags = 0; + + if (hasMetadata) { + flags |= FrameHeaderCodec.FLAGS_M; + } + + if (fragmentFollows) { + flags |= FrameHeaderCodec.FLAGS_F; + } + + if (complete) { + flags |= FrameHeaderCodec.FLAGS_C; + } + + if (next) { + flags |= FrameHeaderCodec.FLAGS_N; + } + + final ByteBuf header = FrameHeaderCodec.encode(allocator, streamId, frameType, flags); + + if (requestN > 0) { + header.writeInt(requestN); + } + + return FrameBodyCodec.encode(allocator, header, metadata, hasMetadata, data); + } + + static ByteBuf data(ByteBuf byteBuf) { + boolean hasMetadata = FrameHeaderCodec.hasMetadata(byteBuf); + int idx = byteBuf.readerIndex(); + byteBuf.skipBytes(FrameHeaderCodec.size()); + ByteBuf data = FrameBodyCodec.dataWithoutMarking(byteBuf, hasMetadata); + byteBuf.readerIndex(idx); + return data; + } + + static ByteBuf metadata(ByteBuf byteBuf) { + boolean hasMetadata = FrameHeaderCodec.hasMetadata(byteBuf); + if (!hasMetadata) { + return null; + } + byteBuf.markReaderIndex(); + byteBuf.skipBytes(FrameHeaderCodec.size()); + ByteBuf metadata = FrameBodyCodec.metadataWithoutMarking(byteBuf); + byteBuf.resetReaderIndex(); + return metadata; + } + + static ByteBuf dataWithRequestN(ByteBuf byteBuf) { + boolean hasMetadata = FrameHeaderCodec.hasMetadata(byteBuf); + byteBuf.markReaderIndex(); + byteBuf.skipBytes(FrameHeaderCodec.size() + Integer.BYTES); + ByteBuf data = FrameBodyCodec.dataWithoutMarking(byteBuf, hasMetadata); + byteBuf.resetReaderIndex(); + return data; + } + + static ByteBuf metadataWithRequestN(ByteBuf byteBuf) { + boolean hasMetadata = FrameHeaderCodec.hasMetadata(byteBuf); + if (!hasMetadata) { + return null; + } + byteBuf.markReaderIndex(); + byteBuf.skipBytes(FrameHeaderCodec.size() + Integer.BYTES); + ByteBuf metadata = FrameBodyCodec.metadataWithoutMarking(byteBuf); + byteBuf.resetReaderIndex(); + return metadata; + } + + static int initialRequestN(ByteBuf byteBuf) { + byteBuf.markReaderIndex(); + int i = byteBuf.skipBytes(FrameHeaderCodec.size()).readInt(); + byteBuf.resetReaderIndex(); + return i; + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/frame/KeepAliveFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/KeepAliveFrameCodec.java similarity index 61% rename from rsocket-core/src/main/java/io/rsocket/frame/KeepAliveFrameFlyweight.java rename to rsocket-core/src/main/java/io/rsocket/frame/KeepAliveFrameCodec.java index b591412a6..752d5b3eb 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/KeepAliveFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/KeepAliveFrameCodec.java @@ -3,7 +3,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; -public class KeepAliveFrameFlyweight { +public class KeepAliveFrameCodec { /** * (R)espond: Set by the sender of the KEEPALIVE, to which the responder MUST reply with a * KEEPALIVE without the R flag set @@ -12,7 +12,7 @@ public class KeepAliveFrameFlyweight { public static final long LAST_POSITION_MASK = 0x8000000000000000L; - private KeepAliveFrameFlyweight() {} + private KeepAliveFrameCodec() {} public static ByteBuf encode( final ByteBufAllocator allocator, @@ -20,7 +20,7 @@ public static ByteBuf encode( final long lastPosition, final ByteBuf data) { final int flags = respond ? FLAGS_KEEPALIVE_R : 0; - ByteBuf header = FrameHeaderFlyweight.encodeStreamZero(allocator, FrameType.KEEPALIVE, flags); + ByteBuf header = FrameHeaderCodec.encodeStreamZero(allocator, FrameType.KEEPALIVE, flags); long lp = 0; if (lastPosition > 0) { @@ -29,27 +29,27 @@ public static ByteBuf encode( header.writeLong(lp); - return DataAndMetadataFlyweight.encode(allocator, header, null, false, data); + return FrameBodyCodec.encode(allocator, header, null, false, data); } public static boolean respondFlag(ByteBuf byteBuf) { - FrameHeaderFlyweight.ensureFrameType(FrameType.KEEPALIVE, byteBuf); - int flags = FrameHeaderFlyweight.flags(byteBuf); + FrameHeaderCodec.ensureFrameType(FrameType.KEEPALIVE, byteBuf); + int flags = FrameHeaderCodec.flags(byteBuf); return (flags & FLAGS_KEEPALIVE_R) == FLAGS_KEEPALIVE_R; } public static long lastPosition(ByteBuf byteBuf) { - FrameHeaderFlyweight.ensureFrameType(FrameType.KEEPALIVE, byteBuf); + FrameHeaderCodec.ensureFrameType(FrameType.KEEPALIVE, byteBuf); byteBuf.markReaderIndex(); - long l = byteBuf.skipBytes(FrameHeaderFlyweight.size()).readLong(); + long l = byteBuf.skipBytes(FrameHeaderCodec.size()).readLong(); byteBuf.resetReaderIndex(); return l; } public static ByteBuf data(ByteBuf byteBuf) { - FrameHeaderFlyweight.ensureFrameType(FrameType.KEEPALIVE, byteBuf); + FrameHeaderCodec.ensureFrameType(FrameType.KEEPALIVE, byteBuf); byteBuf.markReaderIndex(); - ByteBuf slice = byteBuf.skipBytes(FrameHeaderFlyweight.size() + Long.BYTES).slice(); + ByteBuf slice = byteBuf.skipBytes(FrameHeaderCodec.size() + Long.BYTES).slice(); byteBuf.resetReaderIndex(); return slice; } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/LeaseFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/LeaseFrameCodec.java similarity index 74% rename from rsocket-core/src/main/java/io/rsocket/frame/LeaseFrameFlyweight.java rename to rsocket-core/src/main/java/io/rsocket/frame/LeaseFrameCodec.java index 32f086a15..cafd80104 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/LeaseFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/LeaseFrameCodec.java @@ -5,7 +5,7 @@ import io.netty.buffer.Unpooled; import javax.annotation.Nullable; -public class LeaseFrameFlyweight { +public class LeaseFrameCodec { public static ByteBuf encode( final ByteBufAllocator allocator, @@ -18,11 +18,11 @@ public static ByteBuf encode( int flags = 0; if (hasMetadata) { - flags |= FrameHeaderFlyweight.FLAGS_M; + flags |= FrameHeaderCodec.FLAGS_M; } final ByteBuf header = - FrameHeaderFlyweight.encodeStreamZero(allocator, FrameType.LEASE, flags) + FrameHeaderCodec.encodeStreamZero(allocator, FrameType.LEASE, flags) .writeInt(ttl) .writeInt(numRequests); @@ -49,30 +49,30 @@ public static ByteBuf encode( } public static int ttl(final ByteBuf byteBuf) { - FrameHeaderFlyweight.ensureFrameType(FrameType.LEASE, byteBuf); + FrameHeaderCodec.ensureFrameType(FrameType.LEASE, byteBuf); byteBuf.markReaderIndex(); - byteBuf.skipBytes(FrameHeaderFlyweight.size()); + byteBuf.skipBytes(FrameHeaderCodec.size()); int ttl = byteBuf.readInt(); byteBuf.resetReaderIndex(); return ttl; } public static int numRequests(final ByteBuf byteBuf) { - FrameHeaderFlyweight.ensureFrameType(FrameType.LEASE, byteBuf); + FrameHeaderCodec.ensureFrameType(FrameType.LEASE, byteBuf); byteBuf.markReaderIndex(); // Ttl - byteBuf.skipBytes(FrameHeaderFlyweight.size() + Integer.BYTES); + byteBuf.skipBytes(FrameHeaderCodec.size() + Integer.BYTES); int numRequests = byteBuf.readInt(); byteBuf.resetReaderIndex(); return numRequests; } public static ByteBuf metadata(final ByteBuf byteBuf) { - FrameHeaderFlyweight.ensureFrameType(FrameType.LEASE, byteBuf); - if (FrameHeaderFlyweight.hasMetadata(byteBuf)) { + FrameHeaderCodec.ensureFrameType(FrameType.LEASE, byteBuf); + if (FrameHeaderCodec.hasMetadata(byteBuf)) { byteBuf.markReaderIndex(); // Ttl + Num of requests - byteBuf.skipBytes(FrameHeaderFlyweight.size() + Integer.BYTES * 2); + byteBuf.skipBytes(FrameHeaderCodec.size() + Integer.BYTES * 2); ByteBuf metadata = byteBuf.slice(); byteBuf.resetReaderIndex(); return metadata; diff --git a/rsocket-core/src/main/java/io/rsocket/frame/MetadataPushFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/MetadataPushFrameCodec.java similarity index 83% rename from rsocket-core/src/main/java/io/rsocket/frame/MetadataPushFrameFlyweight.java rename to rsocket-core/src/main/java/io/rsocket/frame/MetadataPushFrameCodec.java index a39acef92..62c8a17dc 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/MetadataPushFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/MetadataPushFrameCodec.java @@ -5,7 +5,7 @@ import io.netty.util.IllegalReferenceCountException; import io.rsocket.Payload; -public class MetadataPushFrameFlyweight { +public class MetadataPushFrameCodec { public static ByteBuf encodeReleasingPayload(ByteBufAllocator allocator, Payload payload) { final ByteBuf metadata = payload.metadata().retain(); @@ -22,14 +22,14 @@ public static ByteBuf encodeReleasingPayload(ByteBufAllocator allocator, Payload public static ByteBuf encode(ByteBufAllocator allocator, ByteBuf metadata) { ByteBuf header = - FrameHeaderFlyweight.encodeStreamZero( - allocator, FrameType.METADATA_PUSH, FrameHeaderFlyweight.FLAGS_M); + FrameHeaderCodec.encodeStreamZero( + allocator, FrameType.METADATA_PUSH, FrameHeaderCodec.FLAGS_M); return allocator.compositeBuffer(2).addComponents(true, header, metadata); } public static ByteBuf metadata(ByteBuf byteBuf) { byteBuf.markReaderIndex(); - int headerSize = FrameHeaderFlyweight.size(); + int headerSize = FrameHeaderCodec.size(); int metadataLength = byteBuf.readableBytes() - headerSize; byteBuf.skipBytes(headerSize); ByteBuf metadata = byteBuf.readSlice(metadataLength); diff --git a/rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameCodec.java b/rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameCodec.java new file mode 100644 index 000000000..8a7e6427a --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameCodec.java @@ -0,0 +1,54 @@ +package io.rsocket.frame; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.rsocket.Payload; + +public class PayloadFrameCodec { + + private PayloadFrameCodec() {} + + public static ByteBuf encodeNextReleasingPayload( + ByteBufAllocator allocator, int streamId, Payload payload) { + + return encodeReleasingPayload(allocator, streamId, false, payload); + } + + public static ByteBuf encodeNextCompleteReleasingPayload( + ByteBufAllocator allocator, int streamId, Payload payload) { + + return encodeReleasingPayload(allocator, streamId, true, payload); + } + + static ByteBuf encodeReleasingPayload( + ByteBufAllocator allocator, int streamId, boolean complete, Payload payload) { + + return GenericFrameCodec.encodeReleasingPayload( + allocator, FrameType.PAYLOAD, streamId, complete, true, payload); + } + + public static ByteBuf encodeComplete(ByteBufAllocator allocator, int streamId) { + return encode(allocator, streamId, false, true, false, null, null); + } + + public static ByteBuf encode( + ByteBufAllocator allocator, + int streamId, + boolean fragmentFollows, + boolean complete, + boolean next, + ByteBuf metadata, + ByteBuf data) { + + return GenericFrameCodec.encode( + allocator, FrameType.PAYLOAD, streamId, fragmentFollows, complete, next, 0, metadata, data); + } + + public static ByteBuf data(ByteBuf byteBuf) { + return GenericFrameCodec.data(byteBuf); + } + + public static ByteBuf metadata(ByteBuf byteBuf) { + return GenericFrameCodec.metadata(byteBuf); + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameFlyweight.java deleted file mode 100644 index 53ac6150b..000000000 --- a/rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameFlyweight.java +++ /dev/null @@ -1,79 +0,0 @@ -package io.rsocket.frame; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.util.IllegalReferenceCountException; -import io.rsocket.Payload; - -public class PayloadFrameFlyweight { - private static final RequestFlyweight FLYWEIGHT = new RequestFlyweight(FrameType.PAYLOAD); - - private PayloadFrameFlyweight() {} - - public static ByteBuf encodeNextReleasingPayload( - ByteBufAllocator allocator, int streamId, Payload payload) { - return encodeReleasingPayload(allocator, streamId, false, payload); - } - - public static ByteBuf encodeNextCompleteReleasingPayload( - ByteBufAllocator allocator, int streamId, Payload payload) { - - return encodeReleasingPayload(allocator, streamId, true, payload); - } - - static ByteBuf encodeReleasingPayload( - ByteBufAllocator allocator, int streamId, boolean complete, Payload payload) { - - // if refCnt exceptions throws here it is safe to do no-op - boolean hasMetadata = payload.hasMetadata(); - // if refCnt exceptions throws here it is safe to do no-op still - final ByteBuf metadata = hasMetadata ? payload.metadata().retain() : null; - final ByteBuf data; - // retaining data safely. May throw either NPE or RefCntE - try { - data = payload.data().retain(); - } catch (IllegalReferenceCountException | NullPointerException e) { - if (hasMetadata) { - metadata.release(); - } - throw e; - } - // releasing payload safely since it can be already released wheres we have to release retained - // data and metadata as well - try { - payload.release(); - } catch (IllegalReferenceCountException e) { - data.release(); - if (hasMetadata) { - metadata.release(); - } - throw e; - } - - return encode(allocator, streamId, false, complete, true, metadata, data); - } - - public static ByteBuf encodeComplete(ByteBufAllocator allocator, int streamId) { - return encode(allocator, streamId, false, true, false, null, null); - } - - public static ByteBuf encode( - ByteBufAllocator allocator, - int streamId, - boolean fragmentFollows, - boolean complete, - boolean next, - ByteBuf metadata, - ByteBuf data) { - return FLYWEIGHT.encode( - allocator, streamId, fragmentFollows, complete, next, 0, metadata, data); - } - - public static ByteBuf data(ByteBuf byteBuf) { - return FLYWEIGHT.data(byteBuf); - } - - public static ByteBuf metadata(ByteBuf byteBuf) { - return FLYWEIGHT.metadata(byteBuf); - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameCodec.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameCodec.java new file mode 100644 index 000000000..2ff887043 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameCodec.java @@ -0,0 +1,67 @@ +package io.rsocket.frame; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.rsocket.Payload; + +public class RequestChannelFrameCodec { + + private RequestChannelFrameCodec() {} + + public static ByteBuf encodeReleasingPayload( + ByteBufAllocator allocator, + int streamId, + boolean complete, + long initialRequestN, + Payload payload) { + + if (initialRequestN < 1) { + throw new IllegalArgumentException("request n is less than 1"); + } + + int reqN = initialRequestN > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) initialRequestN; + + return GenericFrameCodec.encodeReleasingPayload( + allocator, FrameType.REQUEST_CHANNEL, streamId, complete, false, reqN, payload); + } + + public static ByteBuf encode( + ByteBufAllocator allocator, + int streamId, + boolean fragmentFollows, + boolean complete, + long initialRequestN, + ByteBuf metadata, + ByteBuf data) { + + if (initialRequestN < 1) { + throw new IllegalArgumentException("request n is less than 1"); + } + + int reqN = initialRequestN > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) initialRequestN; + + return GenericFrameCodec.encode( + allocator, + FrameType.REQUEST_CHANNEL, + streamId, + fragmentFollows, + complete, + false, + reqN, + metadata, + data); + } + + public static ByteBuf data(ByteBuf byteBuf) { + return GenericFrameCodec.dataWithRequestN(byteBuf); + } + + public static ByteBuf metadata(ByteBuf byteBuf) { + return GenericFrameCodec.metadataWithRequestN(byteBuf); + } + + public static long initialRequestN(ByteBuf byteBuf) { + int requestN = GenericFrameCodec.initialRequestN(byteBuf); + return requestN == Integer.MAX_VALUE ? Long.MAX_VALUE : requestN; + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameFlyweight.java deleted file mode 100644 index c0db21170..000000000 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameFlyweight.java +++ /dev/null @@ -1,81 +0,0 @@ -package io.rsocket.frame; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.util.IllegalReferenceCountException; -import io.rsocket.Payload; - -public class RequestChannelFrameFlyweight { - - private static final RequestFlyweight FLYWEIGHT = new RequestFlyweight(FrameType.REQUEST_CHANNEL); - - private RequestChannelFrameFlyweight() {} - - public static ByteBuf encodeReleasingPayload( - ByteBufAllocator allocator, - int streamId, - boolean complete, - long initialRequestN, - Payload payload) { - - // if refCnt exceptions throws here it is safe to do no-op - boolean hasMetadata = payload.hasMetadata(); - // if refCnt exceptions throws here it is safe to do no-op still - final ByteBuf metadata = hasMetadata ? payload.metadata().retain() : null; - final ByteBuf data; - // retaining data safely. May throw either NPE or RefCntE - try { - data = payload.data().retain(); - } catch (IllegalReferenceCountException | NullPointerException e) { - if (hasMetadata) { - metadata.release(); - } - throw e; - } - // releasing payload safely since it can be already released wheres we have to release retained - // data and metadata as well - try { - payload.release(); - } catch (IllegalReferenceCountException e) { - data.release(); - if (hasMetadata) { - metadata.release(); - } - throw e; - } - - return encode(allocator, streamId, false, complete, initialRequestN, metadata, data); - } - - public static ByteBuf encode( - ByteBufAllocator allocator, - int streamId, - boolean fragmentFollows, - boolean complete, - long initialRequestN, - ByteBuf metadata, - ByteBuf data) { - - if (initialRequestN < 1) { - throw new IllegalArgumentException("request n is less than 1"); - } - - int reqN = initialRequestN > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) initialRequestN; - - return FLYWEIGHT.encode( - allocator, streamId, fragmentFollows, complete, false, reqN, metadata, data); - } - - public static ByteBuf data(ByteBuf byteBuf) { - return FLYWEIGHT.dataWithRequestN(byteBuf); - } - - public static ByteBuf metadata(ByteBuf byteBuf) { - return FLYWEIGHT.metadataWithRequestN(byteBuf); - } - - public static long initialRequestN(ByteBuf byteBuf) { - int requestN = FLYWEIGHT.initialRequestN(byteBuf); - return requestN == Integer.MAX_VALUE ? Long.MAX_VALUE : requestN; - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameCodec.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameCodec.java new file mode 100644 index 000000000..ddb5bc5d7 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameCodec.java @@ -0,0 +1,36 @@ +package io.rsocket.frame; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.rsocket.Payload; + +public class RequestFireAndForgetFrameCodec { + + private RequestFireAndForgetFrameCodec() {} + + public static ByteBuf encodeReleasingPayload( + ByteBufAllocator allocator, int streamId, Payload payload) { + + return GenericFrameCodec.encodeReleasingPayload( + allocator, FrameType.REQUEST_FNF, streamId, false, false, payload); + } + + public static ByteBuf encode( + ByteBufAllocator allocator, + int streamId, + boolean fragmentFollows, + ByteBuf metadata, + ByteBuf data) { + + return GenericFrameCodec.encode( + allocator, FrameType.REQUEST_FNF, streamId, fragmentFollows, metadata, data); + } + + public static ByteBuf data(ByteBuf byteBuf) { + return GenericFrameCodec.data(byteBuf); + } + + public static ByteBuf metadata(ByteBuf byteBuf) { + return GenericFrameCodec.metadata(byteBuf); + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameFlyweight.java deleted file mode 100644 index e091edcc3..000000000 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameFlyweight.java +++ /dev/null @@ -1,63 +0,0 @@ -package io.rsocket.frame; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.util.IllegalReferenceCountException; -import io.rsocket.Payload; - -public class RequestFireAndForgetFrameFlyweight { - - private static final RequestFlyweight FLYWEIGHT = new RequestFlyweight(FrameType.REQUEST_FNF); - - private RequestFireAndForgetFrameFlyweight() {} - - public static ByteBuf encodeReleasingPayload( - ByteBufAllocator allocator, int streamId, Payload payload) { - - // if refCnt exceptions throws here it is safe to do no-op - boolean hasMetadata = payload.hasMetadata(); - // if refCnt exceptions throws here it is safe to do no-op still - final ByteBuf metadata = hasMetadata ? payload.metadata().retain() : null; - final ByteBuf data; - // retaining data safely. May throw either NPE or RefCntE - try { - data = payload.data().retain(); - } catch (IllegalReferenceCountException | NullPointerException e) { - if (hasMetadata) { - metadata.release(); - } - throw e; - } - // releasing payload safely since it can be already released wheres we have to release retained - // data and metadata as well - try { - payload.release(); - } catch (IllegalReferenceCountException e) { - data.release(); - if (hasMetadata) { - metadata.release(); - } - throw e; - } - - return FLYWEIGHT.encode(allocator, streamId, false, metadata, data); - } - - public static ByteBuf encode( - ByteBufAllocator allocator, - int streamId, - boolean fragmentFollows, - ByteBuf metadata, - ByteBuf data) { - - return FLYWEIGHT.encode(allocator, streamId, fragmentFollows, metadata, data); - } - - public static ByteBuf data(ByteBuf byteBuf) { - return FLYWEIGHT.data(byteBuf); - } - - public static ByteBuf metadata(ByteBuf byteBuf) { - return FLYWEIGHT.metadata(byteBuf); - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestFlyweight.java deleted file mode 100644 index 15fac9f55..000000000 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestFlyweight.java +++ /dev/null @@ -1,110 +0,0 @@ -package io.rsocket.frame; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import javax.annotation.Nullable; - -class RequestFlyweight { - FrameType frameType; - - RequestFlyweight(FrameType frameType) { - this.frameType = frameType; - } - - ByteBuf encode( - final ByteBufAllocator allocator, - final int streamId, - boolean fragmentFollows, - @Nullable ByteBuf metadata, - ByteBuf data) { - return encode(allocator, streamId, fragmentFollows, false, false, 0, metadata, data); - } - - ByteBuf encode( - final ByteBufAllocator allocator, - final int streamId, - boolean fragmentFollows, - boolean complete, - boolean next, - int requestN, - @Nullable ByteBuf metadata, - ByteBuf data) { - - final boolean hasMetadata = metadata != null; - - int flags = 0; - - if (hasMetadata) { - flags |= FrameHeaderFlyweight.FLAGS_M; - } - - if (fragmentFollows) { - flags |= FrameHeaderFlyweight.FLAGS_F; - } - - if (complete) { - flags |= FrameHeaderFlyweight.FLAGS_C; - } - - if (next) { - flags |= FrameHeaderFlyweight.FLAGS_N; - } - - final ByteBuf header = FrameHeaderFlyweight.encode(allocator, streamId, frameType, flags); - - if (requestN > 0) { - header.writeInt(requestN); - } - - return DataAndMetadataFlyweight.encode(allocator, header, metadata, hasMetadata, data); - } - - ByteBuf data(ByteBuf byteBuf) { - boolean hasMetadata = FrameHeaderFlyweight.hasMetadata(byteBuf); - int idx = byteBuf.readerIndex(); - byteBuf.skipBytes(FrameHeaderFlyweight.size()); - ByteBuf data = DataAndMetadataFlyweight.dataWithoutMarking(byteBuf, hasMetadata); - byteBuf.readerIndex(idx); - return data; - } - - ByteBuf metadata(ByteBuf byteBuf) { - boolean hasMetadata = FrameHeaderFlyweight.hasMetadata(byteBuf); - if (!hasMetadata) { - return null; - } - byteBuf.markReaderIndex(); - byteBuf.skipBytes(FrameHeaderFlyweight.size()); - ByteBuf metadata = DataAndMetadataFlyweight.metadataWithoutMarking(byteBuf); - byteBuf.resetReaderIndex(); - return metadata; - } - - ByteBuf dataWithRequestN(ByteBuf byteBuf) { - boolean hasMetadata = FrameHeaderFlyweight.hasMetadata(byteBuf); - byteBuf.markReaderIndex(); - byteBuf.skipBytes(FrameHeaderFlyweight.size() + Integer.BYTES); - ByteBuf data = DataAndMetadataFlyweight.dataWithoutMarking(byteBuf, hasMetadata); - byteBuf.resetReaderIndex(); - return data; - } - - ByteBuf metadataWithRequestN(ByteBuf byteBuf) { - boolean hasMetadata = FrameHeaderFlyweight.hasMetadata(byteBuf); - if (!hasMetadata) { - return null; - } - byteBuf.markReaderIndex(); - byteBuf.skipBytes(FrameHeaderFlyweight.size() + Integer.BYTES); - ByteBuf metadata = DataAndMetadataFlyweight.metadataWithoutMarking(byteBuf); - byteBuf.resetReaderIndex(); - return metadata; - } - - int initialRequestN(ByteBuf byteBuf) { - byteBuf.markReaderIndex(); - int i = byteBuf.skipBytes(FrameHeaderFlyweight.size()).readInt(); - byteBuf.resetReaderIndex(); - return i; - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestNFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestNFrameCodec.java similarity index 68% rename from rsocket-core/src/main/java/io/rsocket/frame/RequestNFrameFlyweight.java rename to rsocket-core/src/main/java/io/rsocket/frame/RequestNFrameCodec.java index fe2c752cf..66bdd46f4 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestNFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/RequestNFrameCodec.java @@ -3,8 +3,8 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; -public class RequestNFrameFlyweight { - private RequestNFrameFlyweight() {} +public class RequestNFrameCodec { + private RequestNFrameCodec() {} public static ByteBuf encode( final ByteBufAllocator allocator, final int streamId, long requestN) { @@ -15,14 +15,14 @@ public static ByteBuf encode( int reqN = requestN > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) requestN; - ByteBuf header = FrameHeaderFlyweight.encode(allocator, streamId, FrameType.REQUEST_N, 0); + ByteBuf header = FrameHeaderCodec.encode(allocator, streamId, FrameType.REQUEST_N, 0); return header.writeInt(reqN); } public static long requestN(ByteBuf byteBuf) { - FrameHeaderFlyweight.ensureFrameType(FrameType.REQUEST_N, byteBuf); + FrameHeaderCodec.ensureFrameType(FrameType.REQUEST_N, byteBuf); byteBuf.markReaderIndex(); - byteBuf.skipBytes(FrameHeaderFlyweight.size()); + byteBuf.skipBytes(FrameHeaderCodec.size()); int i = byteBuf.readInt(); byteBuf.resetReaderIndex(); return i == Integer.MAX_VALUE ? Long.MAX_VALUE : i; diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameCodec.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameCodec.java new file mode 100644 index 000000000..884a8293b --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameCodec.java @@ -0,0 +1,35 @@ +package io.rsocket.frame; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.rsocket.Payload; + +public class RequestResponseFrameCodec { + + private RequestResponseFrameCodec() {} + + public static ByteBuf encodeReleasingPayload( + ByteBufAllocator allocator, int streamId, Payload payload) { + + return GenericFrameCodec.encodeReleasingPayload( + allocator, FrameType.REQUEST_RESPONSE, streamId, false, false, payload); + } + + public static ByteBuf encode( + ByteBufAllocator allocator, + int streamId, + boolean fragmentFollows, + ByteBuf metadata, + ByteBuf data) { + return GenericFrameCodec.encode( + allocator, FrameType.REQUEST_RESPONSE, streamId, fragmentFollows, metadata, data); + } + + public static ByteBuf data(ByteBuf byteBuf) { + return GenericFrameCodec.data(byteBuf); + } + + public static ByteBuf metadata(ByteBuf byteBuf) { + return GenericFrameCodec.metadata(byteBuf); + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameFlyweight.java deleted file mode 100644 index 782c70965..000000000 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameFlyweight.java +++ /dev/null @@ -1,62 +0,0 @@ -package io.rsocket.frame; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.util.IllegalReferenceCountException; -import io.rsocket.Payload; - -public class RequestResponseFrameFlyweight { - private static final RequestFlyweight FLYWEIGHT = - new RequestFlyweight(FrameType.REQUEST_RESPONSE); - - private RequestResponseFrameFlyweight() {} - - public static ByteBuf encodeReleasingPayload( - ByteBufAllocator allocator, int streamId, Payload payload) { - - // if refCnt exceptions throws here it is safe to do no-op - boolean hasMetadata = payload.hasMetadata(); - // if refCnt exceptions throws here it is safe to do no-op still - final ByteBuf metadata = hasMetadata ? payload.metadata().retain() : null; - final ByteBuf data; - // retaining data safely. May throw either NPE or RefCntE - try { - data = payload.data().retain(); - } catch (IllegalReferenceCountException | NullPointerException e) { - if (hasMetadata) { - metadata.release(); - } - throw e; - } - // releasing payload safely since it can be already released wheres we have to release retained - // data and metadata as well - try { - payload.release(); - } catch (IllegalReferenceCountException e) { - data.release(); - if (hasMetadata) { - metadata.release(); - } - throw e; - } - - return encode(allocator, streamId, false, metadata, data); - } - - public static ByteBuf encode( - ByteBufAllocator allocator, - int streamId, - boolean fragmentFollows, - ByteBuf metadata, - ByteBuf data) { - return FLYWEIGHT.encode(allocator, streamId, fragmentFollows, metadata, data); - } - - public static ByteBuf data(ByteBuf byteBuf) { - return FLYWEIGHT.data(byteBuf); - } - - public static ByteBuf metadata(ByteBuf byteBuf) { - return FLYWEIGHT.metadata(byteBuf); - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameCodec.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameCodec.java new file mode 100644 index 000000000..9853a8b54 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameCodec.java @@ -0,0 +1,62 @@ +package io.rsocket.frame; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.rsocket.Payload; + +public class RequestStreamFrameCodec { + + private RequestStreamFrameCodec() {} + + public static ByteBuf encodeReleasingPayload( + ByteBufAllocator allocator, int streamId, long initialRequestN, Payload payload) { + + if (initialRequestN < 1) { + throw new IllegalArgumentException("request n is less than 1"); + } + + int reqN = initialRequestN > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) initialRequestN; + + return GenericFrameCodec.encodeReleasingPayload( + allocator, FrameType.REQUEST_STREAM, streamId, false, false, reqN, payload); + } + + public static ByteBuf encode( + ByteBufAllocator allocator, + int streamId, + boolean fragmentFollows, + long initialRequestN, + ByteBuf metadata, + ByteBuf data) { + + if (initialRequestN < 1) { + throw new IllegalArgumentException("request n is less than 1"); + } + + int reqN = initialRequestN > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) initialRequestN; + + return GenericFrameCodec.encode( + allocator, + FrameType.REQUEST_STREAM, + streamId, + fragmentFollows, + false, + false, + reqN, + metadata, + data); + } + + public static ByteBuf data(ByteBuf byteBuf) { + return GenericFrameCodec.dataWithRequestN(byteBuf); + } + + public static ByteBuf metadata(ByteBuf byteBuf) { + return GenericFrameCodec.metadataWithRequestN(byteBuf); + } + + public static long initialRequestN(ByteBuf byteBuf) { + int requestN = GenericFrameCodec.initialRequestN(byteBuf); + return requestN == Integer.MAX_VALUE ? Long.MAX_VALUE : requestN; + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameFlyweight.java deleted file mode 100644 index 2fb209ffb..000000000 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameFlyweight.java +++ /dev/null @@ -1,76 +0,0 @@ -package io.rsocket.frame; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.util.IllegalReferenceCountException; -import io.rsocket.Payload; - -public class RequestStreamFrameFlyweight { - - private static final RequestFlyweight FLYWEIGHT = new RequestFlyweight(FrameType.REQUEST_STREAM); - - private RequestStreamFrameFlyweight() {} - - public static ByteBuf encodeReleasingPayload( - ByteBufAllocator allocator, int streamId, long initialRequestN, Payload payload) { - - // if refCnt exceptions throws here it is safe to do no-op - boolean hasMetadata = payload.hasMetadata(); - // if refCnt exceptions throws here it is safe to do no-op still - final ByteBuf metadata = hasMetadata ? payload.metadata().retain() : null; - final ByteBuf data; - // retaining data safely. May throw either NPE or RefCntE - try { - data = payload.data().retain(); - } catch (IllegalReferenceCountException | NullPointerException e) { - if (hasMetadata) { - metadata.release(); - } - throw e; - } - // releasing payload safely since it can be already released wheres we have to release retained - // data and metadata as well - try { - payload.release(); - } catch (IllegalReferenceCountException e) { - data.release(); - if (hasMetadata) { - metadata.release(); - } - throw e; - } - - return encode(allocator, streamId, false, initialRequestN, metadata, data); - } - - public static ByteBuf encode( - ByteBufAllocator allocator, - int streamId, - boolean fragmentFollows, - long initialRequestN, - ByteBuf metadata, - ByteBuf data) { - - if (initialRequestN < 1) { - throw new IllegalArgumentException("request n is less than 1"); - } - - int reqN = initialRequestN > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) initialRequestN; - - return FLYWEIGHT.encode( - allocator, streamId, fragmentFollows, false, false, reqN, metadata, data); - } - - public static ByteBuf data(ByteBuf byteBuf) { - return FLYWEIGHT.dataWithRequestN(byteBuf); - } - - public static ByteBuf metadata(ByteBuf byteBuf) { - return FLYWEIGHT.metadataWithRequestN(byteBuf); - } - - public static long initialRequestN(ByteBuf byteBuf) { - int requestN = FLYWEIGHT.initialRequestN(byteBuf); - return requestN == Integer.MAX_VALUE ? Long.MAX_VALUE : requestN; - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/frame/ResumeFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/ResumeFrameCodec.java similarity index 79% rename from rsocket-core/src/main/java/io/rsocket/frame/ResumeFrameFlyweight.java rename to rsocket-core/src/main/java/io/rsocket/frame/ResumeFrameCodec.java index 06c9fc38c..aae89f7ab 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/ResumeFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/ResumeFrameCodec.java @@ -21,8 +21,8 @@ import io.netty.buffer.Unpooled; import java.util.UUID; -public class ResumeFrameFlyweight { - static final int CURRENT_VERSION = SetupFrameFlyweight.CURRENT_VERSION; +public class ResumeFrameCodec { + static final int CURRENT_VERSION = SetupFrameCodec.CURRENT_VERSION; public static ByteBuf encode( ByteBufAllocator allocator, @@ -30,7 +30,7 @@ public static ByteBuf encode( long lastReceivedServerPos, long firstAvailableClientPos) { - ByteBuf byteBuf = FrameHeaderFlyweight.encodeStreamZero(allocator, FrameType.RESUME, 0); + ByteBuf byteBuf = FrameHeaderCodec.encodeStreamZero(allocator, FrameType.RESUME, 0); byteBuf.writeInt(CURRENT_VERSION); token.markReaderIndex(); byteBuf.writeShort(token.readableBytes()); @@ -43,10 +43,10 @@ public static ByteBuf encode( } public static int version(ByteBuf byteBuf) { - FrameHeaderFlyweight.ensureFrameType(FrameType.RESUME, byteBuf); + FrameHeaderCodec.ensureFrameType(FrameType.RESUME, byteBuf); byteBuf.markReaderIndex(); - byteBuf.skipBytes(FrameHeaderFlyweight.size()); + byteBuf.skipBytes(FrameHeaderCodec.size()); int version = byteBuf.readInt(); byteBuf.resetReaderIndex(); @@ -54,11 +54,11 @@ public static int version(ByteBuf byteBuf) { } public static ByteBuf token(ByteBuf byteBuf) { - FrameHeaderFlyweight.ensureFrameType(FrameType.RESUME, byteBuf); + FrameHeaderCodec.ensureFrameType(FrameType.RESUME, byteBuf); byteBuf.markReaderIndex(); // header + version - int tokenPos = FrameHeaderFlyweight.size() + Integer.BYTES; + int tokenPos = FrameHeaderCodec.size() + Integer.BYTES; byteBuf.skipBytes(tokenPos); // token int tokenLength = byteBuf.readShort() & 0xFFFF; @@ -69,11 +69,11 @@ public static ByteBuf token(ByteBuf byteBuf) { } public static long lastReceivedServerPos(ByteBuf byteBuf) { - FrameHeaderFlyweight.ensureFrameType(FrameType.RESUME, byteBuf); + FrameHeaderCodec.ensureFrameType(FrameType.RESUME, byteBuf); byteBuf.markReaderIndex(); // header + version - int tokenPos = FrameHeaderFlyweight.size() + Integer.BYTES; + int tokenPos = FrameHeaderCodec.size() + Integer.BYTES; byteBuf.skipBytes(tokenPos); // token int tokenLength = byteBuf.readShort() & 0xFFFF; @@ -85,11 +85,11 @@ public static long lastReceivedServerPos(ByteBuf byteBuf) { } public static long firstAvailableClientPos(ByteBuf byteBuf) { - FrameHeaderFlyweight.ensureFrameType(FrameType.RESUME, byteBuf); + FrameHeaderCodec.ensureFrameType(FrameType.RESUME, byteBuf); byteBuf.markReaderIndex(); // header + version - int tokenPos = FrameHeaderFlyweight.size() + Integer.BYTES; + int tokenPos = FrameHeaderCodec.size() + Integer.BYTES; byteBuf.skipBytes(tokenPos); // token int tokenLength = byteBuf.readShort() & 0xFFFF; diff --git a/rsocket-core/src/main/java/io/rsocket/frame/ResumeOkFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/ResumeOkFrameCodec.java similarity index 67% rename from rsocket-core/src/main/java/io/rsocket/frame/ResumeOkFrameFlyweight.java rename to rsocket-core/src/main/java/io/rsocket/frame/ResumeOkFrameCodec.java index dd1971603..2b6951e49 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/ResumeOkFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/ResumeOkFrameCodec.java @@ -3,18 +3,18 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; -public class ResumeOkFrameFlyweight { +public class ResumeOkFrameCodec { public static ByteBuf encode(final ByteBufAllocator allocator, long lastReceivedClientPos) { - ByteBuf byteBuf = FrameHeaderFlyweight.encodeStreamZero(allocator, FrameType.RESUME_OK, 0); + ByteBuf byteBuf = FrameHeaderCodec.encodeStreamZero(allocator, FrameType.RESUME_OK, 0); byteBuf.writeLong(lastReceivedClientPos); return byteBuf; } public static long lastReceivedClientPos(ByteBuf byteBuf) { - FrameHeaderFlyweight.ensureFrameType(FrameType.RESUME_OK, byteBuf); + FrameHeaderCodec.ensureFrameType(FrameType.RESUME_OK, byteBuf); byteBuf.markReaderIndex(); - long lastReceivedClientPosition = byteBuf.skipBytes(FrameHeaderFlyweight.size()).readLong(); + long lastReceivedClientPosition = byteBuf.skipBytes(FrameHeaderCodec.size()).readLong(); byteBuf.resetReaderIndex(); return lastReceivedClientPosition; diff --git a/rsocket-core/src/main/java/io/rsocket/frame/SetupFrameFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/SetupFrameCodec.java similarity index 83% rename from rsocket-core/src/main/java/io/rsocket/frame/SetupFrameFlyweight.java rename to rsocket-core/src/main/java/io/rsocket/frame/SetupFrameCodec.java index bfb73fe22..d6f7431e4 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/SetupFrameFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/SetupFrameCodec.java @@ -7,7 +7,7 @@ import io.rsocket.Payload; import java.nio.charset.StandardCharsets; -public class SetupFrameFlyweight { +public class SetupFrameCodec { /** * A flag used to indicate that the client requires connection resumption, if possible (the frame * contains a Resume Identification Token) @@ -17,9 +17,9 @@ public class SetupFrameFlyweight { /** A flag used to indicate that the client will honor LEASE sent by the server */ public static final int FLAGS_WILL_HONOR_LEASE = 0b00_0100_0000; - public static final int CURRENT_VERSION = VersionFlyweight.encode(1, 0); + public static final int CURRENT_VERSION = VersionCodec.encode(1, 0); - private static final int VERSION_FIELD_OFFSET = FrameHeaderFlyweight.size(); + private static final int VERSION_FIELD_OFFSET = FrameHeaderCodec.size(); private static final int KEEPALIVE_INTERVAL_FIELD_OFFSET = VERSION_FIELD_OFFSET + Integer.BYTES; private static final int KEEPALIVE_MAX_LIFETIME_FIELD_OFFSET = KEEPALIVE_INTERVAL_FIELD_OFFSET + Integer.BYTES; @@ -70,10 +70,10 @@ public static ByteBuf encode( } if (hasMetadata) { - flags |= FrameHeaderFlyweight.FLAGS_M; + flags |= FrameHeaderCodec.FLAGS_M; } - final ByteBuf header = FrameHeaderFlyweight.encodeStreamZero(allocator, FrameType.SETUP, flags); + final ByteBuf header = FrameHeaderCodec.encodeStreamZero(allocator, FrameType.SETUP, flags); header.writeInt(CURRENT_VERSION).writeInt(keepaliveInterval).writeInt(maxLifetime); @@ -93,11 +93,11 @@ public static ByteBuf encode( header.writeByte(length); ByteBufUtil.writeUtf8(header, dataMimeType); - return DataAndMetadataFlyweight.encode(allocator, header, metadata, hasMetadata, data); + return FrameBodyCodec.encode(allocator, header, metadata, hasMetadata, data); } public static int version(ByteBuf byteBuf) { - FrameHeaderFlyweight.ensureFrameType(FrameType.SETUP, byteBuf); + FrameHeaderCodec.ensureFrameType(FrameType.SETUP, byteBuf); byteBuf.markReaderIndex(); int version = byteBuf.skipBytes(VERSION_FIELD_OFFSET).readInt(); byteBuf.resetReaderIndex(); @@ -106,7 +106,7 @@ public static int version(ByteBuf byteBuf) { public static String humanReadableVersion(ByteBuf byteBuf) { int encodedVersion = version(byteBuf); - return VersionFlyweight.major(encodedVersion) + "." + VersionFlyweight.minor(encodedVersion); + return VersionCodec.major(encodedVersion) + "." + VersionCodec.minor(encodedVersion); } public static boolean isSupportedVersion(ByteBuf byteBuf) { @@ -135,11 +135,11 @@ public static int keepAliveMaxLifetime(ByteBuf byteBuf) { } public static boolean honorLease(ByteBuf byteBuf) { - return (FLAGS_WILL_HONOR_LEASE & FrameHeaderFlyweight.flags(byteBuf)) == FLAGS_WILL_HONOR_LEASE; + return (FLAGS_WILL_HONOR_LEASE & FrameHeaderCodec.flags(byteBuf)) == FLAGS_WILL_HONOR_LEASE; } public static boolean resumeEnabled(ByteBuf byteBuf) { - return (FLAGS_RESUME_ENABLE & FrameHeaderFlyweight.flags(byteBuf)) == FLAGS_RESUME_ENABLE; + return (FLAGS_RESUME_ENABLE & FrameHeaderCodec.flags(byteBuf)) == FLAGS_RESUME_ENABLE; } public static ByteBuf resumeToken(ByteBuf byteBuf) { @@ -147,7 +147,7 @@ public static ByteBuf resumeToken(ByteBuf byteBuf) { byteBuf.markReaderIndex(); // header int resumePos = - FrameHeaderFlyweight.size() + FrameHeaderCodec.size() + // version Integer.BYTES @@ -187,29 +187,29 @@ public static String dataMimeType(ByteBuf byteBuf) { } public static ByteBuf metadata(ByteBuf byteBuf) { - boolean hasMetadata = FrameHeaderFlyweight.hasMetadata(byteBuf); + boolean hasMetadata = FrameHeaderCodec.hasMetadata(byteBuf); if (!hasMetadata) { return null; } byteBuf.markReaderIndex(); skipToPayload(byteBuf); - ByteBuf metadata = DataAndMetadataFlyweight.metadataWithoutMarking(byteBuf); + ByteBuf metadata = FrameBodyCodec.metadataWithoutMarking(byteBuf); byteBuf.resetReaderIndex(); return metadata; } public static ByteBuf data(ByteBuf byteBuf) { - boolean hasMetadata = FrameHeaderFlyweight.hasMetadata(byteBuf); + boolean hasMetadata = FrameHeaderCodec.hasMetadata(byteBuf); byteBuf.markReaderIndex(); skipToPayload(byteBuf); - ByteBuf data = DataAndMetadataFlyweight.dataWithoutMarking(byteBuf, hasMetadata); + ByteBuf data = FrameBodyCodec.dataWithoutMarking(byteBuf, hasMetadata); byteBuf.resetReaderIndex(); return data; } private static int bytesToSkipToMimeType(ByteBuf byteBuf) { int bytesToSkip = VARIABLE_DATA_OFFSET; - if ((FLAGS_RESUME_ENABLE & FrameHeaderFlyweight.flags(byteBuf)) == FLAGS_RESUME_ENABLE) { + if ((FLAGS_RESUME_ENABLE & FrameHeaderCodec.flags(byteBuf)) == FLAGS_RESUME_ENABLE) { bytesToSkip += resumeTokenLength(byteBuf) + Short.BYTES; } return bytesToSkip; diff --git a/rsocket-core/src/main/java/io/rsocket/frame/VersionFlyweight.java b/rsocket-core/src/main/java/io/rsocket/frame/VersionCodec.java similarity index 96% rename from rsocket-core/src/main/java/io/rsocket/frame/VersionFlyweight.java rename to rsocket-core/src/main/java/io/rsocket/frame/VersionCodec.java index e238b3fe2..35e4aa86a 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/VersionFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/VersionCodec.java @@ -16,7 +16,7 @@ package io.rsocket.frame; -public class VersionFlyweight { +public class VersionCodec { public static int encode(int major, int minor) { return (major << 16) | (minor & 0xFFFF); diff --git a/rsocket-core/src/main/java/io/rsocket/frame/decoder/DefaultPayloadDecoder.java b/rsocket-core/src/main/java/io/rsocket/frame/decoder/DefaultPayloadDecoder.java index 0a77e3820..e6874c097 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/decoder/DefaultPayloadDecoder.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/decoder/DefaultPayloadDecoder.java @@ -3,14 +3,14 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.rsocket.Payload; -import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.FrameHeaderCodec; import io.rsocket.frame.FrameType; -import io.rsocket.frame.MetadataPushFrameFlyweight; -import io.rsocket.frame.PayloadFrameFlyweight; -import io.rsocket.frame.RequestChannelFrameFlyweight; -import io.rsocket.frame.RequestFireAndForgetFrameFlyweight; -import io.rsocket.frame.RequestResponseFrameFlyweight; -import io.rsocket.frame.RequestStreamFrameFlyweight; +import io.rsocket.frame.MetadataPushFrameCodec; +import io.rsocket.frame.PayloadFrameCodec; +import io.rsocket.frame.RequestChannelFrameCodec; +import io.rsocket.frame.RequestFireAndForgetFrameCodec; +import io.rsocket.frame.RequestResponseFrameCodec; +import io.rsocket.frame.RequestStreamFrameCodec; import io.rsocket.util.DefaultPayload; import java.nio.ByteBuffer; @@ -21,32 +21,32 @@ class DefaultPayloadDecoder implements PayloadDecoder { public Payload apply(ByteBuf byteBuf) { ByteBuf m; ByteBuf d; - FrameType type = FrameHeaderFlyweight.frameType(byteBuf); + FrameType type = FrameHeaderCodec.frameType(byteBuf); switch (type) { case REQUEST_FNF: - d = RequestFireAndForgetFrameFlyweight.data(byteBuf); - m = RequestFireAndForgetFrameFlyweight.metadata(byteBuf); + d = RequestFireAndForgetFrameCodec.data(byteBuf); + m = RequestFireAndForgetFrameCodec.metadata(byteBuf); break; case REQUEST_RESPONSE: - d = RequestResponseFrameFlyweight.data(byteBuf); - m = RequestResponseFrameFlyweight.metadata(byteBuf); + d = RequestResponseFrameCodec.data(byteBuf); + m = RequestResponseFrameCodec.metadata(byteBuf); break; case REQUEST_STREAM: - d = RequestStreamFrameFlyweight.data(byteBuf); - m = RequestStreamFrameFlyweight.metadata(byteBuf); + d = RequestStreamFrameCodec.data(byteBuf); + m = RequestStreamFrameCodec.metadata(byteBuf); break; case REQUEST_CHANNEL: - d = RequestChannelFrameFlyweight.data(byteBuf); - m = RequestChannelFrameFlyweight.metadata(byteBuf); + d = RequestChannelFrameCodec.data(byteBuf); + m = RequestChannelFrameCodec.metadata(byteBuf); break; case NEXT: case NEXT_COMPLETE: - d = PayloadFrameFlyweight.data(byteBuf); - m = PayloadFrameFlyweight.metadata(byteBuf); + d = PayloadFrameCodec.data(byteBuf); + m = PayloadFrameCodec.metadata(byteBuf); break; case METADATA_PUSH: d = Unpooled.EMPTY_BUFFER; - m = MetadataPushFrameFlyweight.metadata(byteBuf); + m = MetadataPushFrameCodec.metadata(byteBuf); break; default: throw new IllegalArgumentException("unsupported frame type: " + type); diff --git a/rsocket-core/src/main/java/io/rsocket/frame/decoder/ZeroCopyPayloadDecoder.java b/rsocket-core/src/main/java/io/rsocket/frame/decoder/ZeroCopyPayloadDecoder.java index c92f82428..3a0dc7bb5 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/decoder/ZeroCopyPayloadDecoder.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/decoder/ZeroCopyPayloadDecoder.java @@ -3,14 +3,14 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.rsocket.Payload; -import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.FrameHeaderCodec; import io.rsocket.frame.FrameType; -import io.rsocket.frame.MetadataPushFrameFlyweight; -import io.rsocket.frame.PayloadFrameFlyweight; -import io.rsocket.frame.RequestChannelFrameFlyweight; -import io.rsocket.frame.RequestFireAndForgetFrameFlyweight; -import io.rsocket.frame.RequestResponseFrameFlyweight; -import io.rsocket.frame.RequestStreamFrameFlyweight; +import io.rsocket.frame.MetadataPushFrameCodec; +import io.rsocket.frame.PayloadFrameCodec; +import io.rsocket.frame.RequestChannelFrameCodec; +import io.rsocket.frame.RequestFireAndForgetFrameCodec; +import io.rsocket.frame.RequestResponseFrameCodec; +import io.rsocket.frame.RequestStreamFrameCodec; import io.rsocket.util.ByteBufPayload; /** @@ -22,32 +22,32 @@ public class ZeroCopyPayloadDecoder implements PayloadDecoder { public Payload apply(ByteBuf byteBuf) { ByteBuf m; ByteBuf d; - FrameType type = FrameHeaderFlyweight.frameType(byteBuf); + FrameType type = FrameHeaderCodec.frameType(byteBuf); switch (type) { case REQUEST_FNF: - d = RequestFireAndForgetFrameFlyweight.data(byteBuf); - m = RequestFireAndForgetFrameFlyweight.metadata(byteBuf); + d = RequestFireAndForgetFrameCodec.data(byteBuf); + m = RequestFireAndForgetFrameCodec.metadata(byteBuf); break; case REQUEST_RESPONSE: - d = RequestResponseFrameFlyweight.data(byteBuf); - m = RequestResponseFrameFlyweight.metadata(byteBuf); + d = RequestResponseFrameCodec.data(byteBuf); + m = RequestResponseFrameCodec.metadata(byteBuf); break; case REQUEST_STREAM: - d = RequestStreamFrameFlyweight.data(byteBuf); - m = RequestStreamFrameFlyweight.metadata(byteBuf); + d = RequestStreamFrameCodec.data(byteBuf); + m = RequestStreamFrameCodec.metadata(byteBuf); break; case REQUEST_CHANNEL: - d = RequestChannelFrameFlyweight.data(byteBuf); - m = RequestChannelFrameFlyweight.metadata(byteBuf); + d = RequestChannelFrameCodec.data(byteBuf); + m = RequestChannelFrameCodec.metadata(byteBuf); break; case NEXT: case NEXT_COMPLETE: - d = PayloadFrameFlyweight.data(byteBuf); - m = PayloadFrameFlyweight.metadata(byteBuf); + d = PayloadFrameCodec.data(byteBuf); + m = PayloadFrameCodec.metadata(byteBuf); break; case METADATA_PUSH: d = Unpooled.EMPTY_BUFFER; - m = MetadataPushFrameFlyweight.metadata(byteBuf); + m = MetadataPushFrameCodec.metadata(byteBuf); break; default: throw new IllegalArgumentException("unsupported frame type: " + type); diff --git a/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java b/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java index cf3eeb120..c294d6539 100644 --- a/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java +++ b/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufAllocator; import io.rsocket.Closeable; import io.rsocket.DuplexConnection; -import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.FrameHeaderCodec; import io.rsocket.frame.FrameUtil; import io.rsocket.plugins.DuplexConnectionInterceptor.Type; import io.rsocket.plugins.InitializingInterceptorRegistry; @@ -79,10 +79,10 @@ public ClientServerInputMultiplexer( .receive() .groupBy( frame -> { - int streamId = FrameHeaderFlyweight.streamId(frame); + int streamId = FrameHeaderCodec.streamId(frame); final Type type; if (streamId == 0) { - switch (FrameHeaderFlyweight.frameType(frame)) { + switch (FrameHeaderCodec.frameType(frame)) { case SETUP: case RESUME: case RESUME_OK: diff --git a/rsocket-core/src/main/java/io/rsocket/keepalive/KeepAliveSupport.java b/rsocket-core/src/main/java/io/rsocket/keepalive/KeepAliveSupport.java index ea8a0de22..db29d8030 100644 --- a/rsocket-core/src/main/java/io/rsocket/keepalive/KeepAliveSupport.java +++ b/rsocket-core/src/main/java/io/rsocket/keepalive/KeepAliveSupport.java @@ -19,7 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; -import io.rsocket.frame.KeepAliveFrameFlyweight; +import io.rsocket.frame.KeepAliveFrameCodec; import io.rsocket.resume.ResumeStateHolder; import java.time.Duration; import java.util.concurrent.atomic.AtomicBoolean; @@ -69,14 +69,14 @@ public void receive(ByteBuf keepAliveFrame) { long remoteLastReceivedPos = remoteLastReceivedPosition(keepAliveFrame); resumeStateHolder.onImpliedPosition(remoteLastReceivedPos); } - if (KeepAliveFrameFlyweight.respondFlag(keepAliveFrame)) { + if (KeepAliveFrameCodec.respondFlag(keepAliveFrame)) { long localLastReceivedPos = localLastReceivedPosition(); send( - KeepAliveFrameFlyweight.encode( + KeepAliveFrameCodec.encode( allocator, false, localLastReceivedPos, - KeepAliveFrameFlyweight.data(keepAliveFrame).retain())); + KeepAliveFrameCodec.data(keepAliveFrame).retain())); } } @@ -118,7 +118,7 @@ long localLastReceivedPosition() { } long remoteLastReceivedPosition(ByteBuf keepAliveFrame) { - return KeepAliveFrameFlyweight.lastPosition(keepAliveFrame); + return KeepAliveFrameCodec.lastPosition(keepAliveFrame); } public static final class ServerKeepAliveSupport extends KeepAliveSupport { @@ -145,7 +145,7 @@ public ClientKeepAliveSupport( void onIntervalTick() { tryTimeout(); send( - KeepAliveFrameFlyweight.encode( + KeepAliveFrameCodec.encode( allocator, true, localLastReceivedPosition(), Unpooled.EMPTY_BUFFER)); } } diff --git a/rsocket-core/src/main/java/io/rsocket/lease/RequesterLeaseHandler.java b/rsocket-core/src/main/java/io/rsocket/lease/RequesterLeaseHandler.java index dd4247090..fd569a2c8 100644 --- a/rsocket-core/src/main/java/io/rsocket/lease/RequesterLeaseHandler.java +++ b/rsocket-core/src/main/java/io/rsocket/lease/RequesterLeaseHandler.java @@ -18,7 +18,7 @@ import io.netty.buffer.ByteBuf; import io.rsocket.Availability; -import io.rsocket.frame.LeaseFrameFlyweight; +import io.rsocket.frame.LeaseFrameCodec; import java.util.function.Consumer; import reactor.core.Disposable; import reactor.core.publisher.Flux; @@ -63,9 +63,9 @@ public Exception leaseError() { @Override public void receive(ByteBuf leaseFrame) { - int numberOfRequests = LeaseFrameFlyweight.numRequests(leaseFrame); - int timeToLiveMillis = LeaseFrameFlyweight.ttl(leaseFrame); - ByteBuf metadata = LeaseFrameFlyweight.metadata(leaseFrame); + int numberOfRequests = LeaseFrameCodec.numRequests(leaseFrame); + int timeToLiveMillis = LeaseFrameCodec.ttl(leaseFrame); + ByteBuf metadata = LeaseFrameCodec.metadata(leaseFrame); LeaseImpl lease = LeaseImpl.create(timeToLiveMillis, numberOfRequests, metadata); currentLease = lease; receivedLease.onNext(lease); diff --git a/rsocket-core/src/main/java/io/rsocket/lease/ResponderLeaseHandler.java b/rsocket-core/src/main/java/io/rsocket/lease/ResponderLeaseHandler.java index 5ca745ee7..5f000cb30 100644 --- a/rsocket-core/src/main/java/io/rsocket/lease/ResponderLeaseHandler.java +++ b/rsocket-core/src/main/java/io/rsocket/lease/ResponderLeaseHandler.java @@ -19,7 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.rsocket.Availability; -import io.rsocket.frame.LeaseFrameFlyweight; +import io.rsocket.frame.LeaseFrameCodec; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; @@ -96,7 +96,7 @@ public double availability() { } private ByteBuf createLeaseFrame(Lease lease) { - return LeaseFrameFlyweight.encode( + return LeaseFrameCodec.encode( allocator, lease.getTimeToLiveMillis(), lease.getAllowedRequests(), lease.getMetadata()); } diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/AuthMetadataCodec.java b/rsocket-core/src/main/java/io/rsocket/metadata/AuthMetadataCodec.java new file mode 100644 index 000000000..41dafb33d --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/metadata/AuthMetadataCodec.java @@ -0,0 +1,335 @@ +package io.rsocket.metadata; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.util.CharsetUtil; +import io.rsocket.util.CharByteBufUtil; + +public class AuthMetadataCodec { + + static final int STREAM_METADATA_KNOWN_MASK = 0x80; // 1000 0000 + static final byte STREAM_METADATA_LENGTH_MASK = 0x7F; // 0111 1111 + + static final int USERNAME_BYTES_LENGTH = 1; + static final int AUTH_TYPE_ID_LENGTH = 1; + + static final char[] EMPTY_CHARS_ARRAY = new char[0]; + + private AuthMetadataCodec() {} + + /** + * Encode a Authentication CompositeMetadata payload using custom authentication type + * + * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed. + * @param customAuthType the custom mime type to encode. + * @param metadata the metadata value to encode. + * @throws IllegalArgumentException in case of {@code customAuthType} is non US_ASCII string or + * empty string or its length is greater than 128 bytes + */ + public static ByteBuf encodeMetadata( + ByteBufAllocator allocator, String customAuthType, ByteBuf metadata) { + + int actualASCIILength = ByteBufUtil.utf8Bytes(customAuthType); + if (actualASCIILength != customAuthType.length()) { + throw new IllegalArgumentException("custom auth type must be US_ASCII characters only"); + } + if (actualASCIILength < 1 || actualASCIILength > 128) { + throw new IllegalArgumentException( + "custom auth type must have a strictly positive length that fits on 7 unsigned bits, ie 1-128"); + } + + int capacity = 1 + actualASCIILength; + ByteBuf headerBuffer = allocator.buffer(capacity, capacity); + // encoded length is one less than actual length, since 0 is never a valid length, which gives + // wider representation range + headerBuffer.writeByte(actualASCIILength - 1); + + ByteBufUtil.reserveAndWriteUtf8(headerBuffer, customAuthType, actualASCIILength); + + return allocator.compositeBuffer(2).addComponents(true, headerBuffer, metadata); + } + + /** + * Encode a Authentication CompositeMetadata payload using custom authentication type + * + * @param allocator the {@link ByteBufAllocator} to create intermediate buffers as needed. + * @param authType the well-known mime type to encode. + * @param metadata the metadata value to encode. + * @throws IllegalArgumentException in case of {@code authType} is {@link + * WellKnownAuthType#UNPARSEABLE_AUTH_TYPE} or {@link + * WellKnownAuthType#UNKNOWN_RESERVED_AUTH_TYPE} + */ + public static ByteBuf encodeMetadata( + ByteBufAllocator allocator, WellKnownAuthType authType, ByteBuf metadata) { + + if (authType == WellKnownAuthType.UNPARSEABLE_AUTH_TYPE + || authType == WellKnownAuthType.UNKNOWN_RESERVED_AUTH_TYPE) { + throw new IllegalArgumentException("only allowed AuthType should be used"); + } + + int capacity = AUTH_TYPE_ID_LENGTH; + ByteBuf headerBuffer = + allocator + .buffer(capacity, capacity) + .writeByte(authType.getIdentifier() | STREAM_METADATA_KNOWN_MASK); + + return allocator.compositeBuffer(2).addComponents(true, headerBuffer, metadata); + } + + /** + * Encode a Authentication CompositeMetadata payload using Simple Authentication format + * + * @throws IllegalArgumentException if the username length is greater than 255 + * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed. + * @param username the char sequence which represents user name. + * @param password the char sequence which represents user password. + */ + public static ByteBuf encodeSimpleMetadata( + ByteBufAllocator allocator, char[] username, char[] password) { + + int usernameLength = CharByteBufUtil.utf8Bytes(username); + if (usernameLength > 255) { + throw new IllegalArgumentException( + "Username should be shorter than or equal to 255 bytes length in UTF-8 encoding"); + } + + int passwordLength = CharByteBufUtil.utf8Bytes(password); + int capacity = AUTH_TYPE_ID_LENGTH + USERNAME_BYTES_LENGTH + usernameLength + passwordLength; + final ByteBuf buffer = + allocator + .buffer(capacity, capacity) + .writeByte(WellKnownAuthType.SIMPLE.getIdentifier() | STREAM_METADATA_KNOWN_MASK) + .writeByte(usernameLength); + + CharByteBufUtil.writeUtf8(buffer, username); + CharByteBufUtil.writeUtf8(buffer, password); + + return buffer; + } + + /** + * Encode a Authentication CompositeMetadata payload using Bearer Authentication format + * + * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed. + * @param token the char sequence which represents BEARER token. + */ + public static ByteBuf encodeBearerMetadata(ByteBufAllocator allocator, char[] token) { + + int tokenLength = CharByteBufUtil.utf8Bytes(token); + int capacity = AUTH_TYPE_ID_LENGTH + tokenLength; + final ByteBuf buffer = + allocator + .buffer(capacity, capacity) + .writeByte(WellKnownAuthType.BEARER.getIdentifier() | STREAM_METADATA_KNOWN_MASK); + + CharByteBufUtil.writeUtf8(buffer, token); + + return buffer; + } + + /** + * Encode a new Authentication Metadata payload information, first verifying if the passed {@link + * String} matches a {@link WellKnownAuthType} (in which case it will be encoded in a compressed + * fashion using the mime id of that type). + * + *

    Prefer using {@link #encodeMetadata(ByteBufAllocator, String, ByteBuf)} if you already know + * that the mime type is not a {@link WellKnownAuthType}. + * + * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed. + * @param authType the mime type to encode, as a {@link String}. well known mime types are + * compressed. + * @param metadata the metadata value to encode. + * @see #encodeMetadata(ByteBufAllocator, WellKnownAuthType, ByteBuf) + * @see #encodeMetadata(ByteBufAllocator, String, ByteBuf) + */ + public static ByteBuf encodeMetadataWithCompression( + ByteBufAllocator allocator, String authType, ByteBuf metadata) { + WellKnownAuthType wkn = WellKnownAuthType.fromString(authType); + if (wkn == WellKnownAuthType.UNPARSEABLE_AUTH_TYPE) { + return AuthMetadataCodec.encodeMetadata(allocator, authType, metadata); + } else { + return AuthMetadataCodec.encodeMetadata(allocator, wkn, metadata); + } + } + + /** + * Get the first {@code byte} from a {@link ByteBuf} and check whether it is length or {@link + * WellKnownAuthType}. Assuming said buffer properly contains such a {@code byte} + * + * @param metadata byteBuf used to get information from + */ + public static boolean isWellKnownAuthType(ByteBuf metadata) { + byte lengthOrId = metadata.getByte(0); + return (lengthOrId & STREAM_METADATA_LENGTH_MASK) != lengthOrId; + } + + /** + * Read first byte from the given {@code metadata} and tries to convert it's value to {@link + * WellKnownAuthType}. + * + * @param metadata given metadata buffer to read from + * @return Return on of the know Auth types or {@link WellKnownAuthType#UNPARSEABLE_AUTH_TYPE} if + * field's value is length or unknown auth type + * @throws IllegalStateException if not enough readable bytes in the given {@link ByteBuf} + */ + public static WellKnownAuthType decodeWellKnownAuthType(ByteBuf metadata) { + if (metadata.readableBytes() < 1) { + throw new IllegalStateException( + "Unable to decode Well Know Auth type. Not enough readable bytes"); + } + byte lengthOrId = metadata.readByte(); + int normalizedId = (byte) (lengthOrId & STREAM_METADATA_LENGTH_MASK); + + if (normalizedId != lengthOrId) { + return WellKnownAuthType.fromIdentifier(normalizedId); + } + + return WellKnownAuthType.UNPARSEABLE_AUTH_TYPE; + } + + /** + * Read up to 129 bytes from the given metadata in order to get the custom Auth Type + * + * @param metadata + * @return + */ + public static CharSequence decodeCustomAuthType(ByteBuf metadata) { + if (metadata.readableBytes() < 2) { + throw new IllegalStateException( + "Unable to decode custom Auth type. Not enough readable bytes"); + } + + byte encodedLength = metadata.readByte(); + if (encodedLength < 0) { + throw new IllegalStateException( + "Unable to decode custom Auth type. Incorrect auth type length"); + } + + // encoded length is realLength - 1 in order to avoid intersection with 0x00 authtype + int realLength = encodedLength + 1; + if (metadata.readableBytes() < realLength) { + throw new IllegalArgumentException( + "Unable to decode custom Auth type. Malformed length or auth type string"); + } + + return metadata.readCharSequence(realLength, CharsetUtil.US_ASCII); + } + + /** + * Read all remaining {@code bytes} from the given {@link ByteBuf} and return sliced + * representation of a payload + * + * @param metadata metadata to get payload from. Please note, the {@code metadata#readIndex} + * should be set to the beginning of the payload bytes + * @return sliced {@link ByteBuf} or {@link Unpooled#EMPTY_BUFFER} if no bytes readable in the + * given one + */ + public static ByteBuf decodePayload(ByteBuf metadata) { + if (metadata.readableBytes() == 0) { + return Unpooled.EMPTY_BUFFER; + } + + return metadata.readSlice(metadata.readableBytes()); + } + + /** + * Read up to 257 {@code bytes} from the given {@link ByteBuf} where the first byte is username + * length and the subsequent number of bytes equal to decoded length + * + * @param simpleAuthMetadata the given metadata to read username from. Please note, the {@code + * simpleAuthMetadata#readIndex} should be set to the username length byte + * @return sliced {@link ByteBuf} or {@link Unpooled#EMPTY_BUFFER} if username length is zero + */ + public static ByteBuf decodeUsername(ByteBuf simpleAuthMetadata) { + short usernameLength = decodeUsernameLength(simpleAuthMetadata); + + if (usernameLength == 0) { + return Unpooled.EMPTY_BUFFER; + } + + return simpleAuthMetadata.readSlice(usernameLength); + } + + /** + * Read all the remaining {@code byte}s from the given {@link ByteBuf} which represents user's + * password + * + * @param simpleAuthMetadata the given metadata to read password from. Please note, the {@code + * simpleAuthMetadata#readIndex} should be set to the beginning of the password bytes + * @return sliced {@link ByteBuf} or {@link Unpooled#EMPTY_BUFFER} if password length is zero + */ + public static ByteBuf decodePassword(ByteBuf simpleAuthMetadata) { + if (simpleAuthMetadata.readableBytes() == 0) { + return Unpooled.EMPTY_BUFFER; + } + + return simpleAuthMetadata.readSlice(simpleAuthMetadata.readableBytes()); + } + /** + * Read up to 257 {@code bytes} from the given {@link ByteBuf} where the first byte is username + * length and the subsequent number of bytes equal to decoded length + * + * @param simpleAuthMetadata the given metadata to read username from. Please note, the {@code + * simpleAuthMetadata#readIndex} should be set to the username length byte + * @return {@code char[]} which represents UTF-8 username + */ + public static char[] decodeUsernameAsCharArray(ByteBuf simpleAuthMetadata) { + short usernameLength = decodeUsernameLength(simpleAuthMetadata); + + if (usernameLength == 0) { + return EMPTY_CHARS_ARRAY; + } + + return CharByteBufUtil.readUtf8(simpleAuthMetadata, usernameLength); + } + + /** + * Read all the remaining {@code byte}s from the given {@link ByteBuf} which represents user's + * password + * + * @param simpleAuthMetadata the given metadata to read username from. Please note, the {@code + * simpleAuthMetadata#readIndex} should be set to the beginning of the password bytes + * @return {@code char[]} which represents UTF-8 password + */ + public static char[] decodePasswordAsCharArray(ByteBuf simpleAuthMetadata) { + if (simpleAuthMetadata.readableBytes() == 0) { + return EMPTY_CHARS_ARRAY; + } + + return CharByteBufUtil.readUtf8(simpleAuthMetadata, simpleAuthMetadata.readableBytes()); + } + + /** + * Read all the remaining {@code bytes} from the given {@link ByteBuf} where the first byte is + * username length and the subsequent number of bytes equal to decoded length + * + * @param bearerAuthMetadata the given metadata to read username from. Please note, the {@code + * simpleAuthMetadata#readIndex} should be set to the beginning of the password bytes + * @return {@code char[]} which represents UTF-8 password + */ + public static char[] decodeBearerTokenAsCharArray(ByteBuf bearerAuthMetadata) { + if (bearerAuthMetadata.readableBytes() == 0) { + return EMPTY_CHARS_ARRAY; + } + + return CharByteBufUtil.readUtf8(bearerAuthMetadata, bearerAuthMetadata.readableBytes()); + } + + private static short decodeUsernameLength(ByteBuf simpleAuthMetadata) { + if (simpleAuthMetadata.readableBytes() < 1) { + throw new IllegalStateException( + "Unable to decode custom username. Not enough readable bytes"); + } + + short usernameLength = simpleAuthMetadata.readUnsignedByte(); + + if (simpleAuthMetadata.readableBytes() < usernameLength) { + throw new IllegalArgumentException( + "Unable to decode username. Malformed username length or content"); + } + + return usernameLength; + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/CompositeMetadataCodec.java b/rsocket-core/src/main/java/io/rsocket/metadata/CompositeMetadataCodec.java new file mode 100644 index 000000000..5e00abba8 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/metadata/CompositeMetadataCodec.java @@ -0,0 +1,385 @@ +/* + * Copyright 2015-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.rsocket.metadata; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.CompositeByteBuf; +import io.netty.util.CharsetUtil; +import io.rsocket.util.NumberUtils; +import reactor.util.annotation.Nullable; + +/** + * A flyweight class that can be used to encode/decode composite metadata information to/from {@link + * ByteBuf}. This is intended for low-level efficient manipulation of such buffers. See {@link + * CompositeMetadata} for an Iterator-like approach to decoding entries. + */ +public class CompositeMetadataCodec { + + static final int STREAM_METADATA_KNOWN_MASK = 0x80; // 1000 0000 + + static final byte STREAM_METADATA_LENGTH_MASK = 0x7F; // 0111 1111 + + private CompositeMetadataCodec() {} + + public static int computeNextEntryIndex( + int currentEntryIndex, ByteBuf headerSlice, ByteBuf contentSlice) { + return currentEntryIndex + + headerSlice.readableBytes() // this includes the mime length byte + + 3 // 3 bytes of the content length, which are excluded from the slice + + contentSlice.readableBytes(); + } + + /** + * Decode the next metadata entry (a mime header + content pair of {@link ByteBuf}) from a {@link + * ByteBuf} that contains at least enough bytes for one more such entry. These buffers are + * actually slices of the full metadata buffer, and this method doesn't move the full metadata + * buffer's {@link ByteBuf#readerIndex()}. As such, it requires the user to provide an {@code + * index} to read from. The next index is computed by calling {@link #computeNextEntryIndex(int, + * ByteBuf, ByteBuf)}. Size of the first buffer (the "header buffer") drives which decoding method + * should be further applied to it. + * + *

    The header buffer is either: + * + *

      + *
    • made up of a single byte: this represents an encoded mime id, which can be further + * decoded using {@link #decodeMimeIdFromMimeBuffer(ByteBuf)} + *
    • made up of 2 or more bytes: this represents an encoded mime String + its length, which + * can be further decoded using {@link #decodeMimeTypeFromMimeBuffer(ByteBuf)}. Note the + * encoded length, in the first byte, is skipped by this decoding method because the + * remaining length of the buffer is that of the mime string. + *
    + * + * @param compositeMetadata the source {@link ByteBuf} that originally contains one or more + * metadata entries + * @param entryIndex the {@link ByteBuf#readerIndex()} to start decoding from. original reader + * index is kept on the source buffer + * @param retainSlices should produced metadata entry buffers {@link ByteBuf#slice() slices} be + * {@link ByteBuf#retainedSlice() retained}? + * @return a {@link ByteBuf} array of length 2 containing the mime header buffer + * slice and the content buffer slice, or one of the + * zero-length error constant arrays + */ + public static ByteBuf[] decodeMimeAndContentBuffersSlices( + ByteBuf compositeMetadata, int entryIndex, boolean retainSlices) { + compositeMetadata.markReaderIndex(); + compositeMetadata.readerIndex(entryIndex); + + if (compositeMetadata.isReadable()) { + ByteBuf mime; + int ridx = compositeMetadata.readerIndex(); + byte mimeIdOrLength = compositeMetadata.readByte(); + if ((mimeIdOrLength & STREAM_METADATA_KNOWN_MASK) == STREAM_METADATA_KNOWN_MASK) { + mime = + retainSlices + ? compositeMetadata.retainedSlice(ridx, 1) + : compositeMetadata.slice(ridx, 1); + } else { + // M flag unset, remaining 7 bits are the length of the mime + int mimeLength = Byte.toUnsignedInt(mimeIdOrLength) + 1; + + if (compositeMetadata.isReadable( + mimeLength)) { // need to be able to read an extra mimeLength bytes + // here we need a way for the returned ByteBuf to differentiate between a + // 1-byte length mime type and a 1 byte encoded mime id, preferably without + // re-applying the byte mask. The easiest way is to include the initial byte + // and have further decoding ignore the first byte. 1 byte buffer == id, 2+ byte + // buffer == full mime string. + mime = + retainSlices + ? + // we accommodate that we don't read from current readerIndex, but + // readerIndex - 1 ("0"), for a total slice size of mimeLength + 1 + compositeMetadata.retainedSlice(ridx, mimeLength + 1) + : compositeMetadata.slice(ridx, mimeLength + 1); + // we thus need to skip the bytes we just sliced, but not the flag/length byte + // which was already skipped in initial read + compositeMetadata.skipBytes(mimeLength); + } else { + compositeMetadata.resetReaderIndex(); + throw new IllegalStateException("metadata is malformed"); + } + } + + if (compositeMetadata.isReadable(3)) { + // ensures the length medium can be read + final int metadataLength = compositeMetadata.readUnsignedMedium(); + if (compositeMetadata.isReadable(metadataLength)) { + ByteBuf metadata = + retainSlices + ? compositeMetadata.readRetainedSlice(metadataLength) + : compositeMetadata.readSlice(metadataLength); + compositeMetadata.resetReaderIndex(); + return new ByteBuf[] {mime, metadata}; + } else { + compositeMetadata.resetReaderIndex(); + throw new IllegalStateException("metadata is malformed"); + } + } else { + compositeMetadata.resetReaderIndex(); + throw new IllegalStateException("metadata is malformed"); + } + } + compositeMetadata.resetReaderIndex(); + throw new IllegalArgumentException( + String.format("entry index %d is larger than buffer size", entryIndex)); + } + + /** + * Decode a {@code byte} compressed mime id from a {@link ByteBuf}, assuming said buffer properly + * contains such an id. + * + *

    The buffer must have exactly one readable byte, which is assumed to have been tested for + * mime id encoding via the {@link #STREAM_METADATA_KNOWN_MASK} mask ({@code firstByte & + * STREAM_METADATA_KNOWN_MASK) == STREAM_METADATA_KNOWN_MASK}). + * + *

    If there is no readable byte, the negative identifier of {@link + * WellKnownMimeType#UNPARSEABLE_MIME_TYPE} is returned. + * + * @param mimeBuffer the buffer that should next contain the compressed mime id byte + * @return the compressed mime id, between 0 and 127, or a negative id if the input is invalid + * @see #decodeMimeTypeFromMimeBuffer(ByteBuf) + */ + public static byte decodeMimeIdFromMimeBuffer(ByteBuf mimeBuffer) { + if (mimeBuffer.readableBytes() != 1) { + return WellKnownMimeType.UNPARSEABLE_MIME_TYPE.getIdentifier(); + } + return (byte) (mimeBuffer.readByte() & STREAM_METADATA_LENGTH_MASK); + } + + /** + * Decode a {@link CharSequence} custome mime type from a {@link ByteBuf}, assuming said buffer + * properly contains such a mime type. + * + *

    The buffer must at least have two readable bytes, which distinguishes it from the {@link + * #decodeMimeIdFromMimeBuffer(ByteBuf) compressed id} case. The first byte is a size and the + * remaining bytes must correspond to the {@link CharSequence}, encoded fully in US_ASCII. As a + * result, the first byte can simply be skipped, and the remaining of the buffer be decoded to the + * mime type. + * + *

    If the mime header buffer is less than 2 bytes long, returns {@code null}. + * + * @param flyweightMimeBuffer the mime header {@link ByteBuf} that contains length + custom mime + * type + * @return the decoded custom mime type, as a {@link CharSequence}, or null if the input is + * invalid + * @see #decodeMimeIdFromMimeBuffer(ByteBuf) + */ + @Nullable + public static CharSequence decodeMimeTypeFromMimeBuffer(ByteBuf flyweightMimeBuffer) { + if (flyweightMimeBuffer.readableBytes() < 2) { + throw new IllegalStateException("unable to decode explicit MIME type"); + } + // the encoded length is assumed to be kept at the start of the buffer + // but also assumed to be irrelevant because the rest of the slice length + // actually already matches _decoded_length + flyweightMimeBuffer.skipBytes(1); + int mimeStringLength = flyweightMimeBuffer.readableBytes(); + return flyweightMimeBuffer.readCharSequence(mimeStringLength, CharsetUtil.US_ASCII); + } + + /** + * Encode a new sub-metadata information into a composite metadata {@link CompositeByteBuf + * buffer}, without checking if the {@link String} can be matched with a well known compressable + * mime type. Prefer using this method and {@link #encodeAndAddMetadata(CompositeByteBuf, + * ByteBufAllocator, WellKnownMimeType, ByteBuf)} if you know in advance whether or not the mime + * is well known. Otherwise use {@link #encodeAndAddMetadataWithCompression(CompositeByteBuf, + * ByteBufAllocator, String, ByteBuf)} + * + * @param compositeMetaData the buffer that will hold all composite metadata information. + * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed. + * @param customMimeType the custom mime type to encode. + * @param metadata the metadata value to encode. + */ + // see #encodeMetadataHeader(ByteBufAllocator, String, int) + public static void encodeAndAddMetadata( + CompositeByteBuf compositeMetaData, + ByteBufAllocator allocator, + String customMimeType, + ByteBuf metadata) { + compositeMetaData.addComponents( + true, encodeMetadataHeader(allocator, customMimeType, metadata.readableBytes()), metadata); + } + + /** + * Encode a new sub-metadata information into a composite metadata {@link CompositeByteBuf + * buffer}. + * + * @param compositeMetaData the buffer that will hold all composite metadata information. + * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed. + * @param knownMimeType the {@link WellKnownMimeType} to encode. + * @param metadata the metadata value to encode. + */ + // see #encodeMetadataHeader(ByteBufAllocator, byte, int) + public static void encodeAndAddMetadata( + CompositeByteBuf compositeMetaData, + ByteBufAllocator allocator, + WellKnownMimeType knownMimeType, + ByteBuf metadata) { + compositeMetaData.addComponents( + true, + encodeMetadataHeader(allocator, knownMimeType.getIdentifier(), metadata.readableBytes()), + metadata); + } + + /** + * Encode a new sub-metadata information into a composite metadata {@link CompositeByteBuf + * buffer}, first verifying if the passed {@link String} matches a {@link WellKnownMimeType} (in + * which case it will be encoded in a compressed fashion using the mime id of that type). + * + *

    Prefer using {@link #encodeAndAddMetadata(CompositeByteBuf, ByteBufAllocator, String, + * ByteBuf)} if you already know that the mime type is not a {@link WellKnownMimeType}. + * + * @param compositeMetaData the buffer that will hold all composite metadata information. + * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed. + * @param mimeType the mime type to encode, as a {@link String}. well known mime types are + * compressed. + * @param metadata the metadata value to encode. + * @see #encodeAndAddMetadata(CompositeByteBuf, ByteBufAllocator, WellKnownMimeType, ByteBuf) + */ + // see #encodeMetadataHeader(ByteBufAllocator, String, int) + public static void encodeAndAddMetadataWithCompression( + CompositeByteBuf compositeMetaData, + ByteBufAllocator allocator, + String mimeType, + ByteBuf metadata) { + WellKnownMimeType wkn = WellKnownMimeType.fromString(mimeType); + if (wkn == WellKnownMimeType.UNPARSEABLE_MIME_TYPE) { + compositeMetaData.addComponents( + true, encodeMetadataHeader(allocator, mimeType, metadata.readableBytes()), metadata); + } else { + compositeMetaData.addComponents( + true, + encodeMetadataHeader(allocator, wkn.getIdentifier(), metadata.readableBytes()), + metadata); + } + } + + /** + * Returns whether there is another entry available at a given index + * + * @param compositeMetadata the buffer to inspect + * @param entryIndex the index to check at + * @return whether there is another entry available at a given index + */ + public static boolean hasEntry(ByteBuf compositeMetadata, int entryIndex) { + return compositeMetadata.writerIndex() - entryIndex > 0; + } + + /** + * Returns whether the header represents a well-known MIME type. + * + * @param header the header to inspect + * @return whether the header represents a well-known MIME type + */ + public static boolean isWellKnownMimeType(ByteBuf header) { + return header.readableBytes() == 1; + } + + /** + * Encode a new sub-metadata information into a composite metadata {@link CompositeByteBuf + * buffer}. + * + * @param compositeMetaData the buffer that will hold all composite metadata information. + * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed. + * @param unknownCompressedMimeType the id of the {@link + * WellKnownMimeType#UNKNOWN_RESERVED_MIME_TYPE} to encode. + * @param metadata the metadata value to encode. + */ + // see #encodeMetadataHeader(ByteBufAllocator, byte, int) + static void encodeAndAddMetadata( + CompositeByteBuf compositeMetaData, + ByteBufAllocator allocator, + byte unknownCompressedMimeType, + ByteBuf metadata) { + compositeMetaData.addComponents( + true, + encodeMetadataHeader(allocator, unknownCompressedMimeType, metadata.readableBytes()), + metadata); + } + + /** + * Encode a custom mime type and a metadata value length into a newly allocated {@link ByteBuf}. + * + *

    This larger representation encodes the mime type representation's length on a single byte, + * then the representation itself, then the unsigned metadata value length on 3 additional bytes. + * + * @param allocator the {@link ByteBufAllocator} to use to create the buffer. + * @param customMime a custom mime type to encode. + * @param metadataLength the metadata length to append to the buffer as an unsigned 24 bits + * integer. + * @return the encoded mime and metadata length information + */ + static ByteBuf encodeMetadataHeader( + ByteBufAllocator allocator, String customMime, int metadataLength) { + ByteBuf metadataHeader = allocator.buffer(4 + customMime.length()); + // reserve 1 byte for the customMime length + // /!\ careful not to read that first byte, which is random at this point + int writerIndexInitial = metadataHeader.writerIndex(); + metadataHeader.writerIndex(writerIndexInitial + 1); + + // write the custom mime in UTF8 but validate it is all ASCII-compatible + // (which produces the right result since ASCII chars are still encoded on 1 byte in UTF8) + int customMimeLength = ByteBufUtil.writeUtf8(metadataHeader, customMime); + if (!ByteBufUtil.isText( + metadataHeader, metadataHeader.readerIndex() + 1, customMimeLength, CharsetUtil.US_ASCII)) { + metadataHeader.release(); + throw new IllegalArgumentException("custom mime type must be US_ASCII characters only"); + } + if (customMimeLength < 1 || customMimeLength > 128) { + metadataHeader.release(); + throw new IllegalArgumentException( + "custom mime type must have a strictly positive length that fits on 7 unsigned bits, ie 1-128"); + } + metadataHeader.markWriterIndex(); + + // go back to beginning and write the length + // encoded length is one less than actual length, since 0 is never a valid length, which gives + // wider representation range + metadataHeader.writerIndex(writerIndexInitial); + metadataHeader.writeByte(customMimeLength - 1); + + // go back to post-mime type and write the metadata content length + metadataHeader.resetWriterIndex(); + NumberUtils.encodeUnsignedMedium(metadataHeader, metadataLength); + + return metadataHeader; + } + + /** + * Encode a {@link WellKnownMimeType well known mime type} and a metadata value length into a + * newly allocated {@link ByteBuf}. + * + *

    This compact representation encodes the mime type via its ID on a single byte, and the + * unsigned value length on 3 additional bytes. + * + * @param allocator the {@link ByteBufAllocator} to use to create the buffer. + * @param mimeType a byte identifier of a {@link WellKnownMimeType} to encode. + * @param metadataLength the metadata length to append to the buffer as an unsigned 24 bits + * integer. + * @return the encoded mime and metadata length information + */ + static ByteBuf encodeMetadataHeader( + ByteBufAllocator allocator, byte mimeType, int metadataLength) { + ByteBuf buffer = allocator.buffer(4, 4).writeByte(mimeType | STREAM_METADATA_KNOWN_MASK); + + NumberUtils.encodeUnsignedMedium(buffer, metadataLength); + + return buffer; + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/CompositeMetadataFlyweight.java b/rsocket-core/src/main/java/io/rsocket/metadata/CompositeMetadataFlyweight.java index 0520285c2..9916dfd3b 100644 --- a/rsocket-core/src/main/java/io/rsocket/metadata/CompositeMetadataFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/metadata/CompositeMetadataFlyweight.java @@ -18,31 +18,25 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.ByteBufUtil; import io.netty.buffer.CompositeByteBuf; -import io.netty.util.CharsetUtil; -import io.rsocket.util.NumberUtils; import reactor.util.annotation.Nullable; /** * A flyweight class that can be used to encode/decode composite metadata information to/from {@link * ByteBuf}. This is intended for low-level efficient manipulation of such buffers. See {@link * CompositeMetadata} for an Iterator-like approach to decoding entries. + * + * @deprecated in favor of {@link CompositeMetadataCodec} */ +@Deprecated public class CompositeMetadataFlyweight { - static final int STREAM_METADATA_KNOWN_MASK = 0x80; // 1000 0000 - - static final byte STREAM_METADATA_LENGTH_MASK = 0x7F; // 0111 1111 - private CompositeMetadataFlyweight() {} public static int computeNextEntryIndex( int currentEntryIndex, ByteBuf headerSlice, ByteBuf contentSlice) { - return currentEntryIndex - + headerSlice.readableBytes() // this includes the mime length byte - + 3 // 3 bytes of the content length, which are excluded from the slice - + contentSlice.readableBytes(); + return CompositeMetadataCodec.computeNextEntryIndex( + currentEntryIndex, headerSlice, contentSlice); } /** @@ -77,67 +71,8 @@ public static int computeNextEntryIndex( */ public static ByteBuf[] decodeMimeAndContentBuffersSlices( ByteBuf compositeMetadata, int entryIndex, boolean retainSlices) { - compositeMetadata.markReaderIndex(); - compositeMetadata.readerIndex(entryIndex); - - if (compositeMetadata.isReadable()) { - ByteBuf mime; - int ridx = compositeMetadata.readerIndex(); - byte mimeIdOrLength = compositeMetadata.readByte(); - if ((mimeIdOrLength & STREAM_METADATA_KNOWN_MASK) == STREAM_METADATA_KNOWN_MASK) { - mime = - retainSlices - ? compositeMetadata.retainedSlice(ridx, 1) - : compositeMetadata.slice(ridx, 1); - } else { - // M flag unset, remaining 7 bits are the length of the mime - int mimeLength = Byte.toUnsignedInt(mimeIdOrLength) + 1; - - if (compositeMetadata.isReadable( - mimeLength)) { // need to be able to read an extra mimeLength bytes - // here we need a way for the returned ByteBuf to differentiate between a - // 1-byte length mime type and a 1 byte encoded mime id, preferably without - // re-applying the byte mask. The easiest way is to include the initial byte - // and have further decoding ignore the first byte. 1 byte buffer == id, 2+ byte - // buffer == full mime string. - mime = - retainSlices - ? - // we accommodate that we don't read from current readerIndex, but - // readerIndex - 1 ("0"), for a total slice size of mimeLength + 1 - compositeMetadata.retainedSlice(ridx, mimeLength + 1) - : compositeMetadata.slice(ridx, mimeLength + 1); - // we thus need to skip the bytes we just sliced, but not the flag/length byte - // which was already skipped in initial read - compositeMetadata.skipBytes(mimeLength); - } else { - compositeMetadata.resetReaderIndex(); - throw new IllegalStateException("metadata is malformed"); - } - } - - if (compositeMetadata.isReadable(3)) { - // ensures the length medium can be read - final int metadataLength = compositeMetadata.readUnsignedMedium(); - if (compositeMetadata.isReadable(metadataLength)) { - ByteBuf metadata = - retainSlices - ? compositeMetadata.readRetainedSlice(metadataLength) - : compositeMetadata.readSlice(metadataLength); - compositeMetadata.resetReaderIndex(); - return new ByteBuf[] {mime, metadata}; - } else { - compositeMetadata.resetReaderIndex(); - throw new IllegalStateException("metadata is malformed"); - } - } else { - compositeMetadata.resetReaderIndex(); - throw new IllegalStateException("metadata is malformed"); - } - } - compositeMetadata.resetReaderIndex(); - throw new IllegalArgumentException( - String.format("entry index %d is larger than buffer size", entryIndex)); + return CompositeMetadataCodec.decodeMimeAndContentBuffersSlices( + compositeMetadata, entryIndex, retainSlices); } /** @@ -145,8 +80,8 @@ public static ByteBuf[] decodeMimeAndContentBuffersSlices( * contains such an id. * *

    The buffer must have exactly one readable byte, which is assumed to have been tested for - * mime id encoding via the {@link #STREAM_METADATA_KNOWN_MASK} mask ({@code firstByte & - * STREAM_METADATA_KNOWN_MASK) == STREAM_METADATA_KNOWN_MASK}). + * mime id encoding via the {@link CompositeMetadataCodec#STREAM_METADATA_KNOWN_MASK} mask ({@code + * firstByte & STREAM_METADATA_KNOWN_MASK) == STREAM_METADATA_KNOWN_MASK}). * *

    If there is no readable byte, the negative identifier of {@link * WellKnownMimeType#UNPARSEABLE_MIME_TYPE} is returned. @@ -156,10 +91,7 @@ public static ByteBuf[] decodeMimeAndContentBuffersSlices( * @see #decodeMimeTypeFromMimeBuffer(ByteBuf) */ public static byte decodeMimeIdFromMimeBuffer(ByteBuf mimeBuffer) { - if (mimeBuffer.readableBytes() != 1) { - return WellKnownMimeType.UNPARSEABLE_MIME_TYPE.getIdentifier(); - } - return (byte) (mimeBuffer.readByte() & STREAM_METADATA_LENGTH_MASK); + return CompositeMetadataCodec.decodeMimeIdFromMimeBuffer(mimeBuffer); } /** @@ -182,15 +114,7 @@ public static byte decodeMimeIdFromMimeBuffer(ByteBuf mimeBuffer) { */ @Nullable public static CharSequence decodeMimeTypeFromMimeBuffer(ByteBuf flyweightMimeBuffer) { - if (flyweightMimeBuffer.readableBytes() < 2) { - throw new IllegalStateException("unable to decode explicit MIME type"); - } - // the encoded length is assumed to be kept at the start of the buffer - // but also assumed to be irrelevant because the rest of the slice length - // actually already matches _decoded_length - flyweightMimeBuffer.skipBytes(1); - int mimeStringLength = flyweightMimeBuffer.readableBytes(); - return flyweightMimeBuffer.readCharSequence(mimeStringLength, CharsetUtil.US_ASCII); + return CompositeMetadataCodec.decodeMimeTypeFromMimeBuffer(flyweightMimeBuffer); } /** @@ -212,8 +136,8 @@ public static void encodeAndAddMetadata( ByteBufAllocator allocator, String customMimeType, ByteBuf metadata) { - compositeMetaData.addComponents( - true, encodeMetadataHeader(allocator, customMimeType, metadata.readableBytes()), metadata); + CompositeMetadataCodec.encodeAndAddMetadata( + compositeMetaData, allocator, customMimeType, metadata); } /** @@ -231,10 +155,8 @@ public static void encodeAndAddMetadata( ByteBufAllocator allocator, WellKnownMimeType knownMimeType, ByteBuf metadata) { - compositeMetaData.addComponents( - true, - encodeMetadataHeader(allocator, knownMimeType.getIdentifier(), metadata.readableBytes()), - metadata); + CompositeMetadataCodec.encodeAndAddMetadata( + compositeMetaData, allocator, knownMimeType, metadata); } /** @@ -258,16 +180,8 @@ public static void encodeAndAddMetadataWithCompression( ByteBufAllocator allocator, String mimeType, ByteBuf metadata) { - WellKnownMimeType wkn = WellKnownMimeType.fromString(mimeType); - if (wkn == WellKnownMimeType.UNPARSEABLE_MIME_TYPE) { - compositeMetaData.addComponents( - true, encodeMetadataHeader(allocator, mimeType, metadata.readableBytes()), metadata); - } else { - compositeMetaData.addComponents( - true, - encodeMetadataHeader(allocator, wkn.getIdentifier(), metadata.readableBytes()), - metadata); - } + CompositeMetadataCodec.encodeAndAddMetadataWithCompression( + compositeMetaData, allocator, mimeType, metadata); } /** @@ -278,7 +192,7 @@ public static void encodeAndAddMetadataWithCompression( * @return whether there is another entry available at a given index */ public static boolean hasEntry(ByteBuf compositeMetadata, int entryIndex) { - return compositeMetadata.writerIndex() - entryIndex > 0; + return CompositeMetadataCodec.hasEntry(compositeMetadata, entryIndex); } /** @@ -288,7 +202,7 @@ public static boolean hasEntry(ByteBuf compositeMetadata, int entryIndex) { * @return whether the header represents a well-known MIME type */ public static boolean isWellKnownMimeType(ByteBuf header) { - return header.readableBytes() == 1; + return CompositeMetadataCodec.isWellKnownMimeType(header); } /** @@ -307,10 +221,8 @@ static void encodeAndAddMetadata( ByteBufAllocator allocator, byte unknownCompressedMimeType, ByteBuf metadata) { - compositeMetaData.addComponents( - true, - encodeMetadataHeader(allocator, unknownCompressedMimeType, metadata.readableBytes()), - metadata); + CompositeMetadataCodec.encodeAndAddMetadata( + compositeMetaData, allocator, unknownCompressedMimeType, metadata); } /** @@ -327,38 +239,7 @@ static void encodeAndAddMetadata( */ static ByteBuf encodeMetadataHeader( ByteBufAllocator allocator, String customMime, int metadataLength) { - ByteBuf metadataHeader = allocator.buffer(4 + customMime.length()); - // reserve 1 byte for the customMime length - // /!\ careful not to read that first byte, which is random at this point - int writerIndexInitial = metadataHeader.writerIndex(); - metadataHeader.writerIndex(writerIndexInitial + 1); - - // write the custom mime in UTF8 but validate it is all ASCII-compatible - // (which produces the right result since ASCII chars are still encoded on 1 byte in UTF8) - int customMimeLength = ByteBufUtil.writeUtf8(metadataHeader, customMime); - if (!ByteBufUtil.isText( - metadataHeader, metadataHeader.readerIndex() + 1, customMimeLength, CharsetUtil.US_ASCII)) { - metadataHeader.release(); - throw new IllegalArgumentException("custom mime type must be US_ASCII characters only"); - } - if (customMimeLength < 1 || customMimeLength > 128) { - metadataHeader.release(); - throw new IllegalArgumentException( - "custom mime type must have a strictly positive length that fits on 7 unsigned bits, ie 1-128"); - } - metadataHeader.markWriterIndex(); - - // go back to beginning and write the length - // encoded length is one less than actual length, since 0 is never a valid length, which gives - // wider representation range - metadataHeader.writerIndex(writerIndexInitial); - metadataHeader.writeByte(customMimeLength - 1); - - // go back to post-mime type and write the metadata content length - metadataHeader.resetWriterIndex(); - NumberUtils.encodeUnsignedMedium(metadataHeader, metadataLength); - - return metadataHeader; + return CompositeMetadataCodec.encodeMetadataHeader(allocator, customMime, metadataLength); } /** @@ -376,10 +257,6 @@ static ByteBuf encodeMetadataHeader( */ static ByteBuf encodeMetadataHeader( ByteBufAllocator allocator, byte mimeType, int metadataLength) { - ByteBuf buffer = allocator.buffer(4, 4).writeByte(mimeType | STREAM_METADATA_KNOWN_MASK); - - NumberUtils.encodeUnsignedMedium(buffer, metadataLength); - - return buffer; + return CompositeMetadataCodec.encodeMetadataHeader(allocator, mimeType, metadataLength); } } diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/TaggingMetadataCodec.java b/rsocket-core/src/main/java/io/rsocket/metadata/TaggingMetadataCodec.java new file mode 100644 index 000000000..d766cf59f --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/metadata/TaggingMetadataCodec.java @@ -0,0 +1,76 @@ +package io.rsocket.metadata; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.CompositeByteBuf; +import java.nio.charset.StandardCharsets; +import java.util.Collection; + +/** + * A flyweight class that can be used to encode/decode tagging metadata information to/from {@link + * ByteBuf}. This is intended for low-level efficient manipulation of such buffers. See {@link + * TaggingMetadata} for an Iterator-like approach to decoding entries. + * + * @author linux_china + */ +public class TaggingMetadataCodec { + /** Tag max length in bytes */ + private static int TAG_LENGTH_MAX = 0xFF; + + /** + * create routing metadata + * + * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed. + * @param tags tag values + * @return routing metadata + */ + public static RoutingMetadata createRoutingMetadata( + ByteBufAllocator allocator, Collection tags) { + return new RoutingMetadata(createTaggingContent(allocator, tags)); + } + + /** + * create tagging metadata from composite metadata entry + * + * @param entry composite metadata entry + * @return tagging metadata + */ + public static TaggingMetadata createTaggingMetadata(CompositeMetadata.Entry entry) { + return new TaggingMetadata(entry.getMimeType(), entry.getContent()); + } + + /** + * create tagging metadata + * + * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed. + * @param knownMimeType the {@link WellKnownMimeType} to encode. + * @param tags tag values + * @return Tagging Metadata + */ + public static TaggingMetadata createTaggingMetadata( + ByteBufAllocator allocator, String knownMimeType, Collection tags) { + return new TaggingMetadata(knownMimeType, createTaggingContent(allocator, tags)); + } + + /** + * create tagging content + * + * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed. + * @param tags tag values + * @return tagging content + */ + public static ByteBuf createTaggingContent(ByteBufAllocator allocator, Collection tags) { + CompositeByteBuf taggingContent = allocator.compositeBuffer(); + for (String key : tags) { + int length = ByteBufUtil.utf8Bytes(key); + if (length == 0 || length > TAG_LENGTH_MAX) { + continue; + } + ByteBuf byteBuf = allocator.buffer().writeByte(length); + byteBuf.writeCharSequence(key, StandardCharsets.UTF_8); + taggingContent.addComponent(true, byteBuf); + } + return taggingContent; + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/TaggingMetadataFlyweight.java b/rsocket-core/src/main/java/io/rsocket/metadata/TaggingMetadataFlyweight.java index c7870bf0d..718528358 100644 --- a/rsocket-core/src/main/java/io/rsocket/metadata/TaggingMetadataFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/metadata/TaggingMetadataFlyweight.java @@ -2,9 +2,6 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.ByteBufUtil; -import io.netty.buffer.CompositeByteBuf; -import java.nio.charset.StandardCharsets; import java.util.Collection; /** @@ -12,12 +9,11 @@ * ByteBuf}. This is intended for low-level efficient manipulation of such buffers. See {@link * TaggingMetadata} for an Iterator-like approach to decoding entries. * + * @deprecated in favor of {@link TaggingMetadataCodec} * @author linux_china */ +@Deprecated public class TaggingMetadataFlyweight { - /** Tag max length in bytes */ - private static int TAG_LENGTH_MAX = 0xFF; - /** * create routing metadata * @@ -27,7 +23,7 @@ public class TaggingMetadataFlyweight { */ public static RoutingMetadata createRoutingMetadata( ByteBufAllocator allocator, Collection tags) { - return new RoutingMetadata(createTaggingContent(allocator, tags)); + return TaggingMetadataCodec.createRoutingMetadata(allocator, tags); } /** @@ -37,7 +33,7 @@ public static RoutingMetadata createRoutingMetadata( * @return tagging metadata */ public static TaggingMetadata createTaggingMetadata(CompositeMetadata.Entry entry) { - return new TaggingMetadata(entry.getMimeType(), entry.getContent()); + return TaggingMetadataCodec.createTaggingMetadata(entry); } /** @@ -50,7 +46,7 @@ public static TaggingMetadata createTaggingMetadata(CompositeMetadata.Entry entr */ public static TaggingMetadata createTaggingMetadata( ByteBufAllocator allocator, String knownMimeType, Collection tags) { - return new TaggingMetadata(knownMimeType, createTaggingContent(allocator, tags)); + return TaggingMetadataCodec.createTaggingMetadata(allocator, knownMimeType, tags); } /** @@ -61,16 +57,6 @@ public static TaggingMetadata createTaggingMetadata( * @return tagging content */ public static ByteBuf createTaggingContent(ByteBufAllocator allocator, Collection tags) { - CompositeByteBuf taggingContent = allocator.compositeBuffer(); - for (String key : tags) { - int length = ByteBufUtil.utf8Bytes(key); - if (length == 0 || length > TAG_LENGTH_MAX) { - continue; - } - ByteBuf byteBuf = allocator.buffer().writeByte(length); - byteBuf.writeCharSequence(key, StandardCharsets.UTF_8); - taggingContent.addComponent(true, byteBuf); - } - return taggingContent; + return TaggingMetadataCodec.createTaggingContent(allocator, tags); } } diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/WellKnownAuthType.java b/rsocket-core/src/main/java/io/rsocket/metadata/WellKnownAuthType.java new file mode 100644 index 000000000..66c98701c --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/metadata/WellKnownAuthType.java @@ -0,0 +1,121 @@ +/* + * Copyright 2015-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.rsocket.metadata; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Enumeration of Well Known Auth Types, as defined in the eponymous extension. Such auth types are + * used in composite metadata (which can include routing and/or tracing metadata). Per + * specification, identifiers are between 0 and 127 (inclusive). + */ +public enum WellKnownAuthType { + UNPARSEABLE_AUTH_TYPE("UNPARSEABLE_AUTH_TYPE_DO_NOT_USE", (byte) -2), + UNKNOWN_RESERVED_AUTH_TYPE("UNKNOWN_YET_RESERVED_DO_NOT_USE", (byte) -1), + + SIMPLE("simple", (byte) 0x00), + BEARER("bearer", (byte) 0x01); + // ... reserved for future use ... + + static final WellKnownAuthType[] TYPES_BY_AUTH_ID; + static final Map TYPES_BY_AUTH_STRING; + + static { + // precompute an array of all valid auth ids, filling the blanks with the RESERVED enum + TYPES_BY_AUTH_ID = new WellKnownAuthType[128]; // 0-127 inclusive + Arrays.fill(TYPES_BY_AUTH_ID, UNKNOWN_RESERVED_AUTH_TYPE); + // also prepare a Map of the types by auth string + TYPES_BY_AUTH_STRING = new LinkedHashMap<>(128); + + for (WellKnownAuthType value : values()) { + if (value.getIdentifier() >= 0) { + TYPES_BY_AUTH_ID[value.getIdentifier()] = value; + TYPES_BY_AUTH_STRING.put(value.getString(), value); + } + } + } + + private final byte identifier; + private final String str; + + WellKnownAuthType(String str, byte identifier) { + this.str = str; + this.identifier = identifier; + } + + /** + * Find the {@link WellKnownAuthType} for the given identifier (as an {@code int}). Valid + * identifiers are defined to be integers between 0 and 127, inclusive. Identifiers outside of + * this range will produce the {@link #UNPARSEABLE_AUTH_TYPE}. Additionally, some identifiers in + * that range are still only reserved and don't have a type associated yet: this method returns + * the {@link #UNKNOWN_RESERVED_AUTH_TYPE} when passing such an identifier, which lets call sites + * potentially detect this and keep the original representation when transmitting the associated + * metadata buffer. + * + * @param id the looked up identifier + * @return the {@link WellKnownAuthType}, or {@link #UNKNOWN_RESERVED_AUTH_TYPE} if the id is out + * of the specification's range, or {@link #UNKNOWN_RESERVED_AUTH_TYPE} if the id is one that + * is merely reserved but unknown to this implementation. + */ + public static WellKnownAuthType fromIdentifier(int id) { + if (id < 0x00 || id > 0x7F) { + return UNPARSEABLE_AUTH_TYPE; + } + return TYPES_BY_AUTH_ID[id]; + } + + /** + * Find the {@link WellKnownAuthType} for the given {@link String} representation. If the + * representation is {@code null} or doesn't match a {@link WellKnownAuthType}, the {@link + * #UNPARSEABLE_AUTH_TYPE} is returned. + * + * @param authType the looked up auth type + * @return the matching {@link WellKnownAuthType}, or {@link #UNPARSEABLE_AUTH_TYPE} if none + * matches + */ + public static WellKnownAuthType fromString(String authType) { + if (authType == null) throw new IllegalArgumentException("type must be non-null"); + + // force UNPARSEABLE if by chance UNKNOWN_RESERVED_AUTH_TYPE's text has been used + if (authType.equals(UNKNOWN_RESERVED_AUTH_TYPE.str)) { + return UNPARSEABLE_AUTH_TYPE; + } + + return TYPES_BY_AUTH_STRING.getOrDefault(authType, UNPARSEABLE_AUTH_TYPE); + } + + /** @return the byte identifier of the auth type, guaranteed to be positive or zero. */ + public byte getIdentifier() { + return identifier; + } + + /** + * @return the auth type represented as a {@link String}, which is made of US_ASCII compatible + * characters only + */ + public String getString() { + return str; + } + + /** @see #getString() */ + @Override + public String toString() { + return str; + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java index 27bf4d1da..fd990d273 100644 --- a/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java @@ -2,20 +2,14 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; -import io.netty.util.CharsetUtil; -import io.rsocket.util.CharByteBufUtil; +import io.rsocket.metadata.AuthMetadataCodec; +/** @deprecated in favor of {@link io.rsocket.metadata.AuthMetadataCodec} */ +@Deprecated public class AuthMetadataFlyweight { static final int STREAM_METADATA_KNOWN_MASK = 0x80; // 1000 0000 - static final byte STREAM_METADATA_LENGTH_MASK = 0x7F; // 0111 1111 - - static final int USERNAME_BYTES_LENGTH = 1; - static final int AUTH_TYPE_ID_LENGTH = 1; - - static final char[] EMPTY_CHARS_ARRAY = new char[0]; private AuthMetadataFlyweight() {} @@ -31,24 +25,7 @@ private AuthMetadataFlyweight() {} public static ByteBuf encodeMetadata( ByteBufAllocator allocator, String customAuthType, ByteBuf metadata) { - int actualASCIILength = ByteBufUtil.utf8Bytes(customAuthType); - if (actualASCIILength != customAuthType.length()) { - throw new IllegalArgumentException("custom auth type must be US_ASCII characters only"); - } - if (actualASCIILength < 1 || actualASCIILength > 128) { - throw new IllegalArgumentException( - "custom auth type must have a strictly positive length that fits on 7 unsigned bits, ie 1-128"); - } - - int capacity = 1 + actualASCIILength; - ByteBuf headerBuffer = allocator.buffer(capacity, capacity); - // encoded length is one less than actual length, since 0 is never a valid length, which gives - // wider representation range - headerBuffer.writeByte(actualASCIILength - 1); - - ByteBufUtil.reserveAndWriteUtf8(headerBuffer, customAuthType, actualASCIILength); - - return allocator.compositeBuffer(2).addComponents(true, headerBuffer, metadata); + return AuthMetadataCodec.encodeMetadata(allocator, customAuthType, metadata); } /** @@ -64,18 +41,7 @@ public static ByteBuf encodeMetadata( public static ByteBuf encodeMetadata( ByteBufAllocator allocator, WellKnownAuthType authType, ByteBuf metadata) { - if (authType == WellKnownAuthType.UNPARSEABLE_AUTH_TYPE - || authType == WellKnownAuthType.UNKNOWN_RESERVED_AUTH_TYPE) { - throw new IllegalArgumentException("only allowed AuthType should be used"); - } - - int capacity = AUTH_TYPE_ID_LENGTH; - ByteBuf headerBuffer = - allocator - .buffer(capacity, capacity) - .writeByte(authType.getIdentifier() | STREAM_METADATA_KNOWN_MASK); - - return allocator.compositeBuffer(2).addComponents(true, headerBuffer, metadata); + return AuthMetadataCodec.encodeMetadata(allocator, WellKnownAuthType.cast(authType), metadata); } /** @@ -88,25 +54,7 @@ public static ByteBuf encodeMetadata( */ public static ByteBuf encodeSimpleMetadata( ByteBufAllocator allocator, char[] username, char[] password) { - - int usernameLength = CharByteBufUtil.utf8Bytes(username); - if (usernameLength > 255) { - throw new IllegalArgumentException( - "Username should be shorter than or equal to 255 bytes length in UTF-8 encoding"); - } - - int passwordLength = CharByteBufUtil.utf8Bytes(password); - int capacity = AUTH_TYPE_ID_LENGTH + USERNAME_BYTES_LENGTH + usernameLength + passwordLength; - final ByteBuf buffer = - allocator - .buffer(capacity, capacity) - .writeByte(WellKnownAuthType.SIMPLE.getIdentifier() | STREAM_METADATA_KNOWN_MASK) - .writeByte(usernameLength); - - CharByteBufUtil.writeUtf8(buffer, username); - CharByteBufUtil.writeUtf8(buffer, password); - - return buffer; + return AuthMetadataCodec.encodeSimpleMetadata(allocator, username, password); } /** @@ -116,17 +64,7 @@ public static ByteBuf encodeSimpleMetadata( * @param token the char sequence which represents BEARER token. */ public static ByteBuf encodeBearerMetadata(ByteBufAllocator allocator, char[] token) { - - int tokenLength = CharByteBufUtil.utf8Bytes(token); - int capacity = AUTH_TYPE_ID_LENGTH + tokenLength; - final ByteBuf buffer = - allocator - .buffer(capacity, capacity) - .writeByte(WellKnownAuthType.BEARER.getIdentifier() | STREAM_METADATA_KNOWN_MASK); - - CharByteBufUtil.writeUtf8(buffer, token); - - return buffer; + return AuthMetadataCodec.encodeBearerMetadata(allocator, token); } /** @@ -146,12 +84,7 @@ public static ByteBuf encodeBearerMetadata(ByteBufAllocator allocator, char[] to */ public static ByteBuf encodeMetadataWithCompression( ByteBufAllocator allocator, String authType, ByteBuf metadata) { - WellKnownAuthType wkn = WellKnownAuthType.fromString(authType); - if (wkn == WellKnownAuthType.UNPARSEABLE_AUTH_TYPE) { - return AuthMetadataFlyweight.encodeMetadata(allocator, authType, metadata); - } else { - return AuthMetadataFlyweight.encodeMetadata(allocator, wkn, metadata); - } + return AuthMetadataCodec.encodeMetadataWithCompression(allocator, authType, metadata); } /** @@ -161,8 +94,7 @@ public static ByteBuf encodeMetadataWithCompression( * @param metadata byteBuf used to get information from */ public static boolean isWellKnownAuthType(ByteBuf metadata) { - byte lengthOrId = metadata.getByte(0); - return (lengthOrId & STREAM_METADATA_LENGTH_MASK) != lengthOrId; + return AuthMetadataCodec.isWellKnownAuthType(metadata); } /** @@ -175,18 +107,7 @@ public static boolean isWellKnownAuthType(ByteBuf metadata) { * @throws IllegalStateException if not enough readable bytes in the given {@link ByteBuf} */ public static WellKnownAuthType decodeWellKnownAuthType(ByteBuf metadata) { - if (metadata.readableBytes() < 1) { - throw new IllegalStateException( - "Unable to decode Well Know Auth type. Not enough readable bytes"); - } - byte lengthOrId = metadata.readByte(); - int normalizedId = (byte) (lengthOrId & STREAM_METADATA_LENGTH_MASK); - - if (normalizedId != lengthOrId) { - return WellKnownAuthType.fromIdentifier(normalizedId); - } - - return WellKnownAuthType.UNPARSEABLE_AUTH_TYPE; + return WellKnownAuthType.cast(AuthMetadataCodec.decodeWellKnownAuthType(metadata)); } /** @@ -196,25 +117,7 @@ public static WellKnownAuthType decodeWellKnownAuthType(ByteBuf metadata) { * @return */ public static CharSequence decodeCustomAuthType(ByteBuf metadata) { - if (metadata.readableBytes() < 2) { - throw new IllegalStateException( - "Unable to decode custom Auth type. Not enough readable bytes"); - } - - byte encodedLength = metadata.readByte(); - if (encodedLength < 0) { - throw new IllegalStateException( - "Unable to decode custom Auth type. Incorrect auth type length"); - } - - // encoded length is realLength - 1 in order to avoid intersection with 0x00 authtype - int realLength = encodedLength + 1; - if (metadata.readableBytes() < realLength) { - throw new IllegalArgumentException( - "Unable to decode custom Auth type. Malformed length or auth type string"); - } - - return metadata.readCharSequence(realLength, CharsetUtil.US_ASCII); + return AuthMetadataCodec.decodeCustomAuthType(metadata); } /** @@ -227,11 +130,7 @@ public static CharSequence decodeCustomAuthType(ByteBuf metadata) { * given one */ public static ByteBuf decodePayload(ByteBuf metadata) { - if (metadata.readableBytes() == 0) { - return Unpooled.EMPTY_BUFFER; - } - - return metadata.readSlice(metadata.readableBytes()); + return AuthMetadataCodec.decodePayload(metadata); } /** @@ -243,13 +142,7 @@ public static ByteBuf decodePayload(ByteBuf metadata) { * @return sliced {@link ByteBuf} or {@link Unpooled#EMPTY_BUFFER} if username length is zero */ public static ByteBuf decodeUsername(ByteBuf simpleAuthMetadata) { - short usernameLength = decodeUsernameLength(simpleAuthMetadata); - - if (usernameLength == 0) { - return Unpooled.EMPTY_BUFFER; - } - - return simpleAuthMetadata.readSlice(usernameLength); + return AuthMetadataCodec.decodeUsername(simpleAuthMetadata); } /** @@ -261,11 +154,7 @@ public static ByteBuf decodeUsername(ByteBuf simpleAuthMetadata) { * @return sliced {@link ByteBuf} or {@link Unpooled#EMPTY_BUFFER} if password length is zero */ public static ByteBuf decodePassword(ByteBuf simpleAuthMetadata) { - if (simpleAuthMetadata.readableBytes() == 0) { - return Unpooled.EMPTY_BUFFER; - } - - return simpleAuthMetadata.readSlice(simpleAuthMetadata.readableBytes()); + return AuthMetadataCodec.decodePassword(simpleAuthMetadata); } /** * Read up to 257 {@code bytes} from the given {@link ByteBuf} where the first byte is username @@ -276,13 +165,7 @@ public static ByteBuf decodePassword(ByteBuf simpleAuthMetadata) { * @return {@code char[]} which represents UTF-8 username */ public static char[] decodeUsernameAsCharArray(ByteBuf simpleAuthMetadata) { - short usernameLength = decodeUsernameLength(simpleAuthMetadata); - - if (usernameLength == 0) { - return EMPTY_CHARS_ARRAY; - } - - return CharByteBufUtil.readUtf8(simpleAuthMetadata, usernameLength); + return AuthMetadataCodec.decodeUsernameAsCharArray(simpleAuthMetadata); } /** @@ -294,11 +177,7 @@ public static char[] decodeUsernameAsCharArray(ByteBuf simpleAuthMetadata) { * @return {@code char[]} which represents UTF-8 password */ public static char[] decodePasswordAsCharArray(ByteBuf simpleAuthMetadata) { - if (simpleAuthMetadata.readableBytes() == 0) { - return EMPTY_CHARS_ARRAY; - } - - return CharByteBufUtil.readUtf8(simpleAuthMetadata, simpleAuthMetadata.readableBytes()); + return AuthMetadataCodec.decodePasswordAsCharArray(simpleAuthMetadata); } /** @@ -310,26 +189,6 @@ public static char[] decodePasswordAsCharArray(ByteBuf simpleAuthMetadata) { * @return {@code char[]} which represents UTF-8 password */ public static char[] decodeBearerTokenAsCharArray(ByteBuf bearerAuthMetadata) { - if (bearerAuthMetadata.readableBytes() == 0) { - return EMPTY_CHARS_ARRAY; - } - - return CharByteBufUtil.readUtf8(bearerAuthMetadata, bearerAuthMetadata.readableBytes()); - } - - private static short decodeUsernameLength(ByteBuf simpleAuthMetadata) { - if (simpleAuthMetadata.readableBytes() < 1) { - throw new IllegalStateException( - "Unable to decode custom username. Not enough readable bytes"); - } - - short usernameLength = simpleAuthMetadata.readUnsignedByte(); - - if (simpleAuthMetadata.readableBytes() < usernameLength) { - throw new IllegalArgumentException( - "Unable to decode username. Malformed username length or content"); - } - - return usernameLength; + return AuthMetadataCodec.decodeBearerTokenAsCharArray(bearerAuthMetadata); } } diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/security/WellKnownAuthType.java b/rsocket-core/src/main/java/io/rsocket/metadata/security/WellKnownAuthType.java index bd4b656b8..24e5ff0db 100644 --- a/rsocket-core/src/main/java/io/rsocket/metadata/security/WellKnownAuthType.java +++ b/rsocket-core/src/main/java/io/rsocket/metadata/security/WellKnownAuthType.java @@ -24,7 +24,10 @@ * Enumeration of Well Known Auth Types, as defined in the eponymous extension. Such auth types are * used in composite metadata (which can include routing and/or tracing metadata). Per * specification, identifiers are between 0 and 127 (inclusive). + * + * @deprecated in favor of {@link io.rsocket.metadata.WellKnownAuthType} */ +@Deprecated public enum WellKnownAuthType { UNPARSEABLE_AUTH_TYPE("UNPARSEABLE_AUTH_TYPE_DO_NOT_USE", (byte) -2), UNKNOWN_RESERVED_AUTH_TYPE("UNKNOWN_YET_RESERVED_DO_NOT_USE", (byte) -1), @@ -59,6 +62,29 @@ public enum WellKnownAuthType { this.identifier = identifier; } + static io.rsocket.metadata.WellKnownAuthType cast(WellKnownAuthType wellKnownAuthType) { + byte identifier = wellKnownAuthType.identifier; + if (identifier == io.rsocket.metadata.WellKnownAuthType.UNPARSEABLE_AUTH_TYPE.getIdentifier()) { + return io.rsocket.metadata.WellKnownAuthType.UNPARSEABLE_AUTH_TYPE; + } else if (identifier + == io.rsocket.metadata.WellKnownAuthType.UNKNOWN_RESERVED_AUTH_TYPE.getIdentifier()) { + return io.rsocket.metadata.WellKnownAuthType.UNKNOWN_RESERVED_AUTH_TYPE; + } else { + return io.rsocket.metadata.WellKnownAuthType.fromIdentifier(identifier); + } + } + + static WellKnownAuthType cast(io.rsocket.metadata.WellKnownAuthType wellKnownAuthType) { + byte identifier = wellKnownAuthType.getIdentifier(); + if (identifier == WellKnownAuthType.UNPARSEABLE_AUTH_TYPE.identifier) { + return WellKnownAuthType.UNPARSEABLE_AUTH_TYPE; + } else if (identifier == WellKnownAuthType.UNKNOWN_RESERVED_AUTH_TYPE.identifier) { + return WellKnownAuthType.UNKNOWN_RESERVED_AUTH_TYPE; + } else { + return TYPES_BY_AUTH_ID[identifier]; + } + } + /** * Find the {@link WellKnownAuthType} for the given identifier (as an {@code int}). Valid * identifiers are defined to be integers between 0 and 127, inclusive. Identifiers outside of diff --git a/rsocket-core/src/main/java/io/rsocket/resume/ClientRSocketSession.java b/rsocket-core/src/main/java/io/rsocket/resume/ClientRSocketSession.java index 01b6dfeae..ed9450357 100644 --- a/rsocket-core/src/main/java/io/rsocket/resume/ClientRSocketSession.java +++ b/rsocket-core/src/main/java/io/rsocket/resume/ClientRSocketSession.java @@ -20,9 +20,9 @@ import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; import io.rsocket.exceptions.ConnectionErrorException; -import io.rsocket.frame.ErrorFrameFlyweight; -import io.rsocket.frame.ResumeFrameFlyweight; -import io.rsocket.frame.ResumeOkFrameFlyweight; +import io.rsocket.frame.ErrorFrameCodec; +import io.rsocket.frame.ResumeFrameCodec; +import io.rsocket.frame.ResumeOkFrameCodec; import io.rsocket.internal.ClientServerInputMultiplexer; import java.time.Duration; import java.util.concurrent.atomic.AtomicBoolean; @@ -86,7 +86,7 @@ public ClientRSocketSession( position); /*Connection is established again: send RESUME frame to server, listen for RESUME_OK*/ sendFrame( - ResumeFrameFlyweight.encode( + ResumeFrameCodec.encode( allocator, /*retain so token is not released once sent as part of resume frame*/ resumeToken.retain(), @@ -123,7 +123,7 @@ public ClientRSocketSession resumeWith(ByteBuf resumeOkFrame) { .onErrorResume( err -> sendFrame( - ErrorFrameFlyweight.encode( + ErrorFrameCodec.encode( allocator, 0, errorFrameThrowable(remoteImpliedPos))) .then(Mono.fromRunnable(resumableConnection::dispose)) /*Resumption is impossible: no need to return control to ResumableConnection*/ @@ -157,7 +157,7 @@ private Mono sendFrame(ByteBuf frame) { } private static long remoteImpliedPos(ByteBuf resumeOkFrame) { - return ResumeOkFrameFlyweight.lastReceivedClientPos(resumeOkFrame); + return ResumeOkFrameCodec.lastReceivedClientPos(resumeOkFrame); } private static long remotePos(ByteBuf resumeOkFrame) { diff --git a/rsocket-core/src/main/java/io/rsocket/resume/ResumableDuplexConnection.java b/rsocket-core/src/main/java/io/rsocket/resume/ResumableDuplexConnection.java index 980de2de1..461d71228 100644 --- a/rsocket-core/src/main/java/io/rsocket/resume/ResumableDuplexConnection.java +++ b/rsocket-core/src/main/java/io/rsocket/resume/ResumableDuplexConnection.java @@ -20,7 +20,7 @@ import io.netty.buffer.ByteBufAllocator; import io.rsocket.Closeable; import io.rsocket.DuplexConnection; -import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.FrameHeaderCodec; import java.nio.channels.ClosedChannelException; import java.time.Duration; import java.util.Queue; @@ -373,7 +373,7 @@ private void releaseFramesToPosition(long remoteImpliedPos) { } static boolean isResumableFrame(ByteBuf frame) { - switch (FrameHeaderFlyweight.nativeFrameType(frame)) { + switch (FrameHeaderCodec.nativeFrameType(frame)) { case REQUEST_CHANNEL: case REQUEST_STREAM: case REQUEST_RESPONSE: diff --git a/rsocket-core/src/main/java/io/rsocket/resume/ServerRSocketSession.java b/rsocket-core/src/main/java/io/rsocket/resume/ServerRSocketSession.java index 5d55559cc..b54ce644f 100644 --- a/rsocket-core/src/main/java/io/rsocket/resume/ServerRSocketSession.java +++ b/rsocket-core/src/main/java/io/rsocket/resume/ServerRSocketSession.java @@ -20,9 +20,9 @@ import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; import io.rsocket.exceptions.RejectedResumeException; -import io.rsocket.frame.ErrorFrameFlyweight; -import io.rsocket.frame.ResumeFrameFlyweight; -import io.rsocket.frame.ResumeOkFrameFlyweight; +import io.rsocket.frame.ErrorFrameCodec; +import io.rsocket.frame.ResumeFrameCodec; +import io.rsocket.frame.ResumeOkFrameCodec; import java.time.Duration; import java.util.function.Function; import org.slf4j.Logger; @@ -103,12 +103,10 @@ public ServerRSocketSession resumeWith(ByteBuf resumeFrame) { remotePos, remoteImpliedPos, pos -> - pos.flatMap( - impliedPos -> sendFrame(ResumeOkFrameFlyweight.encode(allocator, impliedPos))) + pos.flatMap(impliedPos -> sendFrame(ResumeOkFrameCodec.encode(allocator, impliedPos))) .onErrorResume( err -> - sendFrame( - ErrorFrameFlyweight.encode(allocator, 0, errorFrameThrowable(err))) + sendFrame(ErrorFrameCodec.encode(allocator, 0, errorFrameThrowable(err))) .then(Mono.fromRunnable(resumableConnection::dispose)) /*Resumption is impossible: no need to return control to ResumableConnection*/ .then(Mono.never()))); @@ -136,11 +134,11 @@ private Mono sendFrame(ByteBuf frame) { } private static long remotePos(ByteBuf resumeFrame) { - return ResumeFrameFlyweight.firstAvailableClientPos(resumeFrame); + return ResumeFrameCodec.firstAvailableClientPos(resumeFrame); } private static long remoteImpliedPos(ByteBuf resumeFrame) { - return ResumeFrameFlyweight.lastReceivedServerPos(resumeFrame); + return ResumeFrameCodec.lastReceivedServerPos(resumeFrame); } private static RejectedResumeException errorFrameThrowable(Throwable err) { diff --git a/rsocket-core/src/test/java/io/rsocket/core/ConnectionSetupPayloadTest.java b/rsocket-core/src/test/java/io/rsocket/core/ConnectionSetupPayloadTest.java index ea3142d25..9d8b8354a 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/ConnectionSetupPayloadTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/ConnectionSetupPayloadTest.java @@ -8,7 +8,7 @@ import io.netty.buffer.Unpooled; import io.rsocket.ConnectionSetupPayload; import io.rsocket.Payload; -import io.rsocket.frame.SetupFrameFlyweight; +import io.rsocket.frame.SetupFrameCodec; import io.rsocket.util.DefaultPayload; import org.junit.jupiter.api.Test; @@ -31,8 +31,8 @@ void testSetupPayloadWithDataMetadata() { assertTrue(setupPayload.willClientHonorLease()); assertEquals(KEEP_ALIVE_INTERVAL, setupPayload.keepAliveInterval()); assertEquals(KEEP_ALIVE_MAX_LIFETIME, setupPayload.keepAliveMaxLifetime()); - assertEquals(METADATA_TYPE, SetupFrameFlyweight.metadataMimeType(frame)); - assertEquals(DATA_TYPE, SetupFrameFlyweight.dataMimeType(frame)); + assertEquals(METADATA_TYPE, SetupFrameCodec.metadataMimeType(frame)); + assertEquals(DATA_TYPE, SetupFrameCodec.dataMimeType(frame)); assertTrue(setupPayload.hasMetadata()); assertNotNull(setupPayload.metadata()); assertEquals(payload.metadata(), setupPayload.metadata()); @@ -77,7 +77,7 @@ void testSetupPayloadWithEmptyMetadata() { } private static ByteBuf encodeSetupFrame(boolean leaseEnabled, Payload setupPayload) { - return SetupFrameFlyweight.encode( + return SetupFrameCodec.encode( ByteBufAllocator.DEFAULT, leaseEnabled, KEEP_ALIVE_INTERVAL, diff --git a/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java b/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java index 7e465db08..b3ded08ec 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java @@ -26,9 +26,9 @@ import io.rsocket.TestScheduler; import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.exceptions.ConnectionErrorException; -import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.FrameHeaderCodec; import io.rsocket.frame.FrameType; -import io.rsocket.frame.KeepAliveFrameFlyweight; +import io.rsocket.frame.KeepAliveFrameCodec; import io.rsocket.lease.RequesterLeaseHandler; import io.rsocket.resume.InMemoryResumableFramesStore; import io.rsocket.resume.ResumableDuplexConnection; @@ -115,7 +115,7 @@ void rSocketNotDisposedOnPresentKeepAlives() { .subscribe( n -> connection.addToReceivedBuffer( - KeepAliveFrameFlyweight.encode( + KeepAliveFrameCodec.encode( ByteBufAllocator.DEFAULT, true, 0, Unpooled.EMPTY_BUFFER))); Mono.delay(Duration.ofMillis(2000)).block(); @@ -171,7 +171,7 @@ void requesterRespondsToKeepAlives() { .subscribe( l -> connection.addToReceivedBuffer( - KeepAliveFrameFlyweight.encode( + KeepAliveFrameCodec.encode( ByteBufAllocator.DEFAULT, true, 0, Unpooled.EMPTY_BUFFER))); StepVerifier.create(Flux.from(connection.getSentAsPublisher()).take(1)) @@ -235,15 +235,15 @@ void resumableRSocketsNotDisposedOnMissingKeepAlives() { } private boolean keepAliveFrame(ByteBuf frame) { - return FrameHeaderFlyweight.frameType(frame) == FrameType.KEEPALIVE; + return FrameHeaderCodec.frameType(frame) == FrameType.KEEPALIVE; } private boolean keepAliveFrameWithRespondFlag(ByteBuf frame) { - return keepAliveFrame(frame) && KeepAliveFrameFlyweight.respondFlag(frame); + return keepAliveFrame(frame) && KeepAliveFrameCodec.respondFlag(frame); } private boolean keepAliveFrameWithoutRespondFlag(ByteBuf frame) { - return keepAliveFrame(frame) && !KeepAliveFrameFlyweight.respondFlag(frame); + return keepAliveFrame(frame) && !KeepAliveFrameCodec.respondFlag(frame); } static class RSocketState { diff --git a/rsocket-core/src/test/java/io/rsocket/core/PayloadValidationUtilsTest.java b/rsocket-core/src/test/java/io/rsocket/core/PayloadValidationUtilsTest.java index e91fce848..ed9f1ec4a 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/PayloadValidationUtilsTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/PayloadValidationUtilsTest.java @@ -1,8 +1,8 @@ package io.rsocket.core; import io.rsocket.Payload; -import io.rsocket.frame.FrameHeaderFlyweight; -import io.rsocket.frame.FrameLengthFlyweight; +import io.rsocket.frame.FrameHeaderCodec; +import io.rsocket.frame.FrameLengthCodec; import io.rsocket.util.DefaultPayload; import java.util.concurrent.ThreadLocalRandom; import org.assertj.core.api.Assertions; @@ -14,9 +14,9 @@ class PayloadValidationUtilsTest { void shouldBeValidFrameWithNoFragmentation() { byte[] data = new byte - [FrameLengthFlyweight.FRAME_LENGTH_MASK - - FrameLengthFlyweight.FRAME_LENGTH_SIZE - - FrameHeaderFlyweight.size()]; + [FrameLengthCodec.FRAME_LENGTH_MASK + - FrameLengthCodec.FRAME_LENGTH_SIZE + - FrameHeaderCodec.size()]; ThreadLocalRandom.current().nextBytes(data); final Payload payload = DefaultPayload.create(data); @@ -27,9 +27,9 @@ void shouldBeValidFrameWithNoFragmentation() { void shouldBeInValidFrameWithNoFragmentation() { byte[] data = new byte - [FrameLengthFlyweight.FRAME_LENGTH_MASK - - FrameLengthFlyweight.FRAME_LENGTH_SIZE - - FrameHeaderFlyweight.size() + [FrameLengthCodec.FRAME_LENGTH_MASK + - FrameLengthCodec.FRAME_LENGTH_SIZE + - FrameHeaderCodec.size() + 1]; ThreadLocalRandom.current().nextBytes(data); final Payload payload = DefaultPayload.create(data); @@ -39,13 +39,13 @@ void shouldBeInValidFrameWithNoFragmentation() { @Test void shouldBeValidFrameWithNoFragmentation0() { - byte[] metadata = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK / 2]; + byte[] metadata = new byte[FrameLengthCodec.FRAME_LENGTH_MASK / 2]; byte[] data = new byte - [FrameLengthFlyweight.FRAME_LENGTH_MASK / 2 - - FrameLengthFlyweight.FRAME_LENGTH_SIZE - - FrameHeaderFlyweight.size() - - FrameHeaderFlyweight.size()]; + [FrameLengthCodec.FRAME_LENGTH_MASK / 2 + - FrameLengthCodec.FRAME_LENGTH_SIZE + - FrameHeaderCodec.size() + - FrameHeaderCodec.size()]; ThreadLocalRandom.current().nextBytes(data); ThreadLocalRandom.current().nextBytes(metadata); final Payload payload = DefaultPayload.create(data, metadata); @@ -55,8 +55,8 @@ void shouldBeValidFrameWithNoFragmentation0() { @Test void shouldBeInValidFrameWithNoFragmentation1() { - byte[] metadata = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; - byte[] data = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; + byte[] metadata = new byte[FrameLengthCodec.FRAME_LENGTH_MASK]; + byte[] data = new byte[FrameLengthCodec.FRAME_LENGTH_MASK]; ThreadLocalRandom.current().nextBytes(metadata); ThreadLocalRandom.current().nextBytes(data); final Payload payload = DefaultPayload.create(data, metadata); @@ -77,8 +77,8 @@ void shouldBeValidFrameWithNoFragmentation2() { @Test void shouldBeValidFrameWithNoFragmentation3() { - byte[] metadata = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; - byte[] data = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; + byte[] metadata = new byte[FrameLengthCodec.FRAME_LENGTH_MASK]; + byte[] data = new byte[FrameLengthCodec.FRAME_LENGTH_MASK]; ThreadLocalRandom.current().nextBytes(metadata); ThreadLocalRandom.current().nextBytes(data); final Payload payload = DefaultPayload.create(data, metadata); diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java index 04d5fe174..ddfbe4234 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java @@ -31,10 +31,10 @@ import io.rsocket.TestScheduler; import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.exceptions.Exceptions; -import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.FrameHeaderCodec; import io.rsocket.frame.FrameType; -import io.rsocket.frame.LeaseFrameFlyweight; -import io.rsocket.frame.SetupFrameFlyweight; +import io.rsocket.frame.LeaseFrameCodec; +import io.rsocket.frame.SetupFrameCodec; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.internal.ClientServerInputMultiplexer; import io.rsocket.lease.*; @@ -123,7 +123,7 @@ void setUp() { public void serverRSocketFactoryRejectsUnsupportedLease() { Payload payload = DefaultPayload.create(DefaultPayload.EMPTY_BUFFER); ByteBuf setupFrame = - SetupFrameFlyweight.encode( + SetupFrameCodec.encode( ByteBufAllocator.DEFAULT, true, 1000, @@ -141,7 +141,7 @@ public void serverRSocketFactoryRejectsUnsupportedLease() { Collection sent = connection.getSent(); Assertions.assertThat(sent).hasSize(1); ByteBuf error = sent.iterator().next(); - Assertions.assertThat(FrameHeaderFlyweight.frameType(error)).isEqualTo(ERROR); + Assertions.assertThat(FrameHeaderCodec.frameType(error)).isEqualTo(ERROR); Assertions.assertThat(Exceptions.from(0, error).getMessage()) .isEqualTo("lease is not supported"); } @@ -154,8 +154,8 @@ public void clientRSocketFactorySetsLeaseFlag() { Collection sent = clientTransport.testConnection().getSent(); Assertions.assertThat(sent).hasSize(1); ByteBuf setup = sent.iterator().next(); - Assertions.assertThat(FrameHeaderFlyweight.frameType(setup)).isEqualTo(SETUP); - Assertions.assertThat(SetupFrameFlyweight.honorLease(setup)).isTrue(); + Assertions.assertThat(FrameHeaderCodec.frameType(setup)).isEqualTo(SETUP); + Assertions.assertThat(SetupFrameCodec.honorLease(setup)).isTrue(); } @ParameterizedTest @@ -278,13 +278,13 @@ void sendLease() { connection .getSent() .stream() - .filter(f -> FrameHeaderFlyweight.frameType(f) == FrameType.LEASE) + .filter(f -> FrameHeaderCodec.frameType(f) == FrameType.LEASE) .findFirst() .orElseThrow(() -> new IllegalStateException("Lease frame not sent")); - Assertions.assertThat(LeaseFrameFlyweight.ttl(leaseFrame)).isEqualTo(ttl); - Assertions.assertThat(LeaseFrameFlyweight.numRequests(leaseFrame)).isEqualTo(numberOfRequests); - Assertions.assertThat(LeaseFrameFlyweight.metadata(leaseFrame).toString(utf8)) + Assertions.assertThat(LeaseFrameCodec.ttl(leaseFrame)).isEqualTo(ttl); + Assertions.assertThat(LeaseFrameCodec.numRequests(leaseFrame)).isEqualTo(numberOfRequests); + Assertions.assertThat(LeaseFrameCodec.metadata(leaseFrame).toString(utf8)) .isEqualTo(metadataContent); } @@ -312,7 +312,7 @@ void receiveLease() { } ByteBuf leaseFrame(int ttl, int requests, ByteBuf metadata) { - return LeaseFrameFlyweight.encode(byteBufAllocator, ttl, requests, metadata); + return LeaseFrameCodec.encode(byteBufAllocator, ttl, requests, metadata); } static Stream>> interactions() { diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java index 3e7479af3..6f1ecf98b 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java @@ -21,9 +21,9 @@ import io.rsocket.RSocket; import io.rsocket.TestScheduler; import io.rsocket.buffer.LeaksTrackingByteBufAllocator; -import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.FrameHeaderCodec; import io.rsocket.frame.FrameType; -import io.rsocket.frame.PayloadFrameFlyweight; +import io.rsocket.frame.PayloadFrameCodec; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.internal.subscriber.AssertSubscriber; import io.rsocket.lease.RequesterLeaseHandler; @@ -87,7 +87,7 @@ void singleSubscriber(Function> interaction) { response.subscribe(assertSubscriberA); response.subscribe(assertSubscriberB); - connection.addToReceivedBuffer(PayloadFrameFlyweight.encodeComplete(connection.alloc(), 1)); + connection.addToReceivedBuffer(PayloadFrameCodec.encodeComplete(connection.alloc(), 1)); assertSubscriberA.assertTerminated(); assertSubscriberB.assertTerminated(); @@ -106,7 +106,7 @@ void singleSubscriberInCaseOfRacing(Function> interaction) RaceTestUtils.race( () -> response.subscribe(assertSubscriberA), () -> response.subscribe(assertSubscriberB)); - connection.addToReceivedBuffer(PayloadFrameFlyweight.encodeComplete(connection.alloc(), i)); + connection.addToReceivedBuffer(PayloadFrameCodec.encodeComplete(connection.alloc(), i)); assertSubscriberA.assertTerminated(); assertSubscriberB.assertTerminated(); @@ -117,7 +117,7 @@ void singleSubscriberInCaseOfRacing(Function> interaction) Assertions.assertThat(connection.getSent()) .hasSize(1) .first() - .matches(bb -> REQUEST_TYPES.contains(FrameHeaderFlyweight.frameType(bb))); + .matches(bb -> REQUEST_TYPES.contains(FrameHeaderCodec.frameType(bb))); connection.clearSendReceiveBuffers(); } } @@ -133,7 +133,7 @@ void singleSubscriberInteractionsAreLazy(Function> interac static long requestFramesCount(Collection frames) { return frames .stream() - .filter(frame -> REQUEST_TYPES.contains(FrameHeaderFlyweight.frameType(frame))) + .filter(frame -> REQUEST_TYPES.contains(FrameHeaderCodec.frameType(frame))) .count(); } diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java index 56e6c9342..e4943e9b0 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java @@ -17,7 +17,7 @@ package io.rsocket.core; import static io.rsocket.core.PayloadValidationUtils.INVALID_PAYLOAD_ERROR_MESSAGE; -import static io.rsocket.frame.FrameHeaderFlyweight.frameType; +import static io.rsocket.frame.FrameHeaderCodec.frameType; import static io.rsocket.frame.FrameType.CANCEL; import static io.rsocket.frame.FrameType.REQUEST_CHANNEL; import static io.rsocket.frame.FrameType.REQUEST_FNF; @@ -46,17 +46,17 @@ import io.rsocket.exceptions.ApplicationErrorException; import io.rsocket.exceptions.CustomRSocketException; import io.rsocket.exceptions.RejectedSetupException; -import io.rsocket.frame.CancelFrameFlyweight; -import io.rsocket.frame.ErrorFrameFlyweight; -import io.rsocket.frame.FrameHeaderFlyweight; -import io.rsocket.frame.FrameLengthFlyweight; +import io.rsocket.frame.CancelFrameCodec; +import io.rsocket.frame.ErrorFrameCodec; +import io.rsocket.frame.FrameHeaderCodec; +import io.rsocket.frame.FrameLengthCodec; import io.rsocket.frame.FrameType; -import io.rsocket.frame.PayloadFrameFlyweight; -import io.rsocket.frame.RequestChannelFrameFlyweight; -import io.rsocket.frame.RequestFireAndForgetFrameFlyweight; -import io.rsocket.frame.RequestNFrameFlyweight; -import io.rsocket.frame.RequestResponseFrameFlyweight; -import io.rsocket.frame.RequestStreamFrameFlyweight; +import io.rsocket.frame.PayloadFrameCodec; +import io.rsocket.frame.RequestChannelFrameCodec; +import io.rsocket.frame.RequestFireAndForgetFrameCodec; +import io.rsocket.frame.RequestNFrameCodec; +import io.rsocket.frame.RequestResponseFrameCodec; +import io.rsocket.frame.RequestStreamFrameCodec; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.internal.subscriber.AssertSubscriber; import io.rsocket.lease.RequesterLeaseHandler; @@ -123,7 +123,7 @@ public void tearDown() { @Test @Timeout(2_000) public void testInvalidFrameOnStream0() { - rule.connection.addToReceivedBuffer(RequestNFrameFlyweight.encode(rule.alloc(), 0, 10)); + rule.connection.addToReceivedBuffer(RequestNFrameCodec.encode(rule.alloc(), 0, 10)); assertThat("Unexpected errors.", rule.errors, hasSize(1)); assertThat( "Unexpected error received.", @@ -157,7 +157,7 @@ protected void hookOnSubscribe(Subscription subscription) { ByteBuf f = sent.get(0); assertThat("initial frame", frameType(f), is(REQUEST_STREAM)); - assertThat("initial request n", RequestStreamFrameFlyweight.initialRequestN(f), is(5L)); + assertThat("initial request n", RequestStreamFrameCodec.initialRequestN(f), is(5L)); assertThat("should be released", f.release(), is(true)); rule.assertHasNoLeaks(); } @@ -166,7 +166,7 @@ protected void hookOnSubscribe(Subscription subscription) { @Timeout(2_000) public void testHandleSetupException() { rule.connection.addToReceivedBuffer( - ErrorFrameFlyweight.encode(rule.alloc(), 0, new RejectedSetupException("boom"))); + ErrorFrameCodec.encode(rule.alloc(), 0, new RejectedSetupException("boom"))); assertThat("Unexpected errors.", rule.errors, hasSize(1)); assertThat( "Unexpected error received.", @@ -185,7 +185,7 @@ public void testHandleApplicationException() { int streamId = rule.getStreamIdForRequestType(REQUEST_RESPONSE); rule.connection.addToReceivedBuffer( - ErrorFrameFlyweight.encode(rule.alloc(), streamId, new ApplicationErrorException("error"))); + ErrorFrameCodec.encode(rule.alloc(), streamId, new ApplicationErrorException("error"))); verify(responseSub).onError(any(ApplicationErrorException.class)); @@ -206,7 +206,7 @@ public void testHandleValidFrame() { int streamId = rule.getStreamIdForRequestType(REQUEST_RESPONSE); rule.connection.addToReceivedBuffer( - PayloadFrameFlyweight.encodeNextReleasingPayload( + PayloadFrameCodec.encodeNextReleasingPayload( rule.alloc(), streamId, EmptyPayload.INSTANCE)); verify(sub).onComplete(); @@ -288,9 +288,8 @@ public void testChannelRequestServerSideCancellation() { request.onNext(EmptyPayload.INSTANCE); rule.socket.requestChannel(request).subscribe(cancelled); int streamId = rule.getStreamIdForRequestType(REQUEST_CHANNEL); - rule.connection.addToReceivedBuffer(CancelFrameFlyweight.encode(rule.alloc(), streamId)); - rule.connection.addToReceivedBuffer( - PayloadFrameFlyweight.encodeComplete(rule.alloc(), streamId)); + rule.connection.addToReceivedBuffer(CancelFrameCodec.encode(rule.alloc(), streamId)); + rule.connection.addToReceivedBuffer(PayloadFrameCodec.encodeComplete(rule.alloc(), streamId)); Flux.first( cancelled, Flux.error(new IllegalStateException("Channel request not cancelled")) @@ -328,11 +327,10 @@ protected void hookOnSubscribe(Subscription subscription) {} ByteBuf initialFrame = iterator.next(); - Assertions.assertThat(FrameHeaderFlyweight.frameType(initialFrame)).isEqualTo(REQUEST_CHANNEL); - Assertions.assertThat(RequestChannelFrameFlyweight.initialRequestN(initialFrame)) + Assertions.assertThat(FrameHeaderCodec.frameType(initialFrame)).isEqualTo(REQUEST_CHANNEL); + Assertions.assertThat(RequestChannelFrameCodec.initialRequestN(initialFrame)) .isEqualTo(Long.MAX_VALUE); - Assertions.assertThat( - RequestChannelFrameFlyweight.data(initialFrame).toString(CharsetUtil.UTF_8)) + Assertions.assertThat(RequestChannelFrameCodec.data(initialFrame).toString(CharsetUtil.UTF_8)) .isEqualTo("0"); Assertions.assertThat(initialFrame.release()).isTrue(); @@ -345,8 +343,8 @@ public void shouldThrownExceptionIfGivenPayloadIsExitsSizeAllowanceWithNoFragmen prepareCalls() .forEach( generator -> { - byte[] metadata = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; - byte[] data = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; + byte[] metadata = new byte[FrameLengthCodec.FRAME_LENGTH_MASK]; + byte[] data = new byte[FrameLengthCodec.FRAME_LENGTH_MASK]; ThreadLocalRandom.current().nextBytes(metadata); ThreadLocalRandom.current().nextBytes(data); StepVerifier.create( @@ -374,8 +372,8 @@ static Stream>> prepareCalls() { @Test public void shouldThrownExceptionIfGivenPayloadIsExitsSizeAllowanceWithNoFragmentationForRequestChannelCase() { - byte[] metadata = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; - byte[] data = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; + byte[] metadata = new byte[FrameLengthCodec.FRAME_LENGTH_MASK]; + byte[] data = new byte[FrameLengthCodec.FRAME_LENGTH_MASK]; ThreadLocalRandom.current().nextBytes(metadata); ThreadLocalRandom.current().nextBytes(data); StepVerifier.create( @@ -385,7 +383,7 @@ static Stream>> prepareCalls() { .then( () -> rule.connection.addToReceivedBuffer( - RequestNFrameFlyweight.encode( + RequestNFrameCodec.encode( rule.alloc(), rule.getStreamIdForRequestType(REQUEST_CHANNEL), 2))) .expectErrorSatisfies( t -> @@ -454,7 +452,7 @@ private static Stream racingCases() { as.request(1); int streamId = rule.getStreamIdForRequestType(REQUEST_STREAM); ByteBuf frame = - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, streamId, false, false, true, metadata, data); RaceTestUtils.race(as::cancel, () -> rule.connection.addToReceivedBuffer(frame)); @@ -472,7 +470,7 @@ private static Stream racingCases() { as.request(1); int streamId = rule.getStreamIdForRequestType(REQUEST_CHANNEL); ByteBuf frame = - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, streamId, false, false, true, metadata, data); RaceTestUtils.race(as::cancel, () -> rule.connection.addToReceivedBuffer(frame)); @@ -582,7 +580,7 @@ private static Stream racingCases() { ByteBufAllocator allocator = rule.alloc(); as.request(1); int streamId = rule.getStreamIdForRequestType(REQUEST_CHANNEL); - ByteBuf frame = CancelFrameFlyweight.encode(allocator, streamId); + ByteBuf frame = CancelFrameCodec.encode(allocator, streamId); RaceTestUtils.race( () -> as.request(Long.MAX_VALUE), @@ -609,7 +607,7 @@ private static Stream racingCases() { as.request(1); int streamId = rule.getStreamIdForRequestType(REQUEST_CHANNEL); ByteBuf frame = - ErrorFrameFlyweight.encode(allocator, streamId, new RuntimeException("test")); + ErrorFrameCodec.encode(allocator, streamId, new RuntimeException("test")); RaceTestUtils.race( () -> as.request(Long.MAX_VALUE), @@ -628,7 +626,7 @@ private static Stream racingCases() { as.request(Long.MAX_VALUE); int streamId = rule.getStreamIdForRequestType(REQUEST_RESPONSE); ByteBuf frame = - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, streamId, false, false, true, metadata, data); RaceTestUtils.race(as::cancel, () -> rule.connection.addToReceivedBuffer(frame)); @@ -672,7 +670,7 @@ public void simpleOnDiscardRequestChannelTest2() { testPublisher.next(ByteBufPayload.create("d1", "m1"), ByteBufPayload.create("d2", "m2")); rule.connection.addToReceivedBuffer( - ErrorFrameFlyweight.encode( + ErrorFrameCodec.encode( allocator, streamId, new CustomRSocketException(0x00000404, "test"))); Assertions.assertThat(rule.connection.getSent()).allMatch(ByteBuf::release); @@ -716,7 +714,7 @@ public void verifiesThatFrameWithNoMetadataHasDecodedCorrectlyIntoPayload( if (responsesCnt > 0) { for (int i = 0; i < responsesCnt - 1; i++) { rule.connection.addToReceivedBuffer( - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, streamId, false, @@ -727,7 +725,7 @@ public void verifiesThatFrameWithNoMetadataHasDecodedCorrectlyIntoPayload( } rule.connection.addToReceivedBuffer( - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, streamId, false, @@ -739,7 +737,7 @@ public void verifiesThatFrameWithNoMetadataHasDecodedCorrectlyIntoPayload( if (framesCnt > 1) { rule.connection.addToReceivedBuffer( - RequestNFrameFlyweight.encode(allocator, streamId, framesCnt)); + RequestNFrameCodec.encode(allocator, streamId, framesCnt)); } for (int i = 1; i < framesCnt; i++) { @@ -750,7 +748,7 @@ public void verifiesThatFrameWithNoMetadataHasDecodedCorrectlyIntoPayload( .describedAs( "Interaction Type :[%s]. Expected to observe %s frames sent", frameType, framesCnt) .hasSize(framesCnt) - .allMatch(bb -> !FrameHeaderFlyweight.hasMetadata(bb)) + .allMatch(bb -> !FrameHeaderCodec.hasMetadata(bb)) .allMatch(ByteBuf::release); Assertions.assertThat(assertSubscriber.isTerminated()) @@ -818,12 +816,12 @@ public void ensuresThatNoOpsMustHappenUntilSubscriptionInCaseOfFnfCall() { .hasSize(1) .first() .matches(bb -> frameType(bb) == REQUEST_FNF) - .matches(bb -> FrameHeaderFlyweight.streamId(bb) == 1) + .matches(bb -> FrameHeaderCodec.streamId(bb) == 1) // ensures that this is fnf1 with abc2 data .matches( bb -> ByteBufUtil.equals( - RequestFireAndForgetFrameFlyweight.data(bb), + RequestFireAndForgetFrameCodec.data(bb), Unpooled.wrappedBuffer("abc2".getBytes()))) .matches(ReferenceCounted::release); @@ -836,12 +834,12 @@ public void ensuresThatNoOpsMustHappenUntilSubscriptionInCaseOfFnfCall() { .hasSize(1) .first() .matches(bb -> frameType(bb) == REQUEST_FNF) - .matches(bb -> FrameHeaderFlyweight.streamId(bb) == 3) + .matches(bb -> FrameHeaderCodec.streamId(bb) == 3) // ensures that this is fnf1 with abc1 data .matches( bb -> ByteBufUtil.equals( - RequestFireAndForgetFrameFlyweight.data(bb), + RequestFireAndForgetFrameCodec.data(bb), Unpooled.wrappedBuffer("abc1".getBytes()))) .matches(ReferenceCounted::release); } @@ -875,25 +873,23 @@ public void ensuresThatNoOpsMustHappenUntilFirstRequestN( .first() .matches(bb -> frameType(bb) == frameType) .matches( - bb -> FrameHeaderFlyweight.streamId(bb) == 1, + bb -> FrameHeaderCodec.streamId(bb) == 1, "Expected to have stream ID {1} but got {" - + FrameHeaderFlyweight.streamId(rule.connection.getSent().iterator().next()) + + FrameHeaderCodec.streamId(rule.connection.getSent().iterator().next()) + "}") .matches( bb -> { switch (frameType) { case REQUEST_RESPONSE: return ByteBufUtil.equals( - RequestResponseFrameFlyweight.data(bb), + RequestResponseFrameCodec.data(bb), Unpooled.wrappedBuffer("abc2".getBytes())); case REQUEST_STREAM: return ByteBufUtil.equals( - RequestStreamFrameFlyweight.data(bb), - Unpooled.wrappedBuffer("abc2".getBytes())); + RequestStreamFrameCodec.data(bb), Unpooled.wrappedBuffer("abc2".getBytes())); case REQUEST_CHANNEL: return ByteBufUtil.equals( - RequestChannelFrameFlyweight.data(bb), - Unpooled.wrappedBuffer("abc2".getBytes())); + RequestChannelFrameCodec.data(bb), Unpooled.wrappedBuffer("abc2".getBytes())); } return false; @@ -908,25 +904,23 @@ public void ensuresThatNoOpsMustHappenUntilFirstRequestN( .first() .matches(bb -> frameType(bb) == frameType) .matches( - bb -> FrameHeaderFlyweight.streamId(bb) == 3, + bb -> FrameHeaderCodec.streamId(bb) == 3, "Expected to have stream ID {1} but got {" - + FrameHeaderFlyweight.streamId(rule.connection.getSent().iterator().next()) + + FrameHeaderCodec.streamId(rule.connection.getSent().iterator().next()) + "}") .matches( bb -> { switch (frameType) { case REQUEST_RESPONSE: return ByteBufUtil.equals( - RequestResponseFrameFlyweight.data(bb), + RequestResponseFrameCodec.data(bb), Unpooled.wrappedBuffer("abc1".getBytes())); case REQUEST_STREAM: return ByteBufUtil.equals( - RequestStreamFrameFlyweight.data(bb), - Unpooled.wrappedBuffer("abc1".getBytes())); + RequestStreamFrameCodec.data(bb), Unpooled.wrappedBuffer("abc1".getBytes())); case REQUEST_CHANNEL: return ByteBufUtil.equals( - RequestChannelFrameFlyweight.data(bb), - Unpooled.wrappedBuffer("abc1".getBytes())); + RequestChannelFrameCodec.data(bb), Unpooled.wrappedBuffer("abc1".getBytes())); } return false; @@ -964,7 +958,7 @@ public void ensuresCorrectOrderOfStreamIdIssuingInCaseOfRacing( () -> publisher2.subscribe(AssertSubscriber.create())); Assertions.assertThat(rule.connection.getSent()) - .extracting(FrameHeaderFlyweight::streamId) + .extracting(FrameHeaderCodec::streamId) .containsExactly(i, i + 2); rule.connection.getSent().clear(); } @@ -999,7 +993,7 @@ public int sendRequestResponse(Publisher response) { response.subscribe(sub); int streamId = rule.getStreamIdForRequestType(REQUEST_RESPONSE); rule.connection.addToReceivedBuffer( - PayloadFrameFlyweight.encodeNextCompleteReleasingPayload( + PayloadFrameCodec.encodeNextCompleteReleasingPayload( rule.alloc(), streamId, EmptyPayload.INSTANCE)); verify(sub).onNext(any(Payload.class)); verify(sub).onComplete(); @@ -1028,7 +1022,7 @@ public int getStreamIdForRequestType(FrameType expectedFrameType) { for (ByteBuf frame : connection.getSent()) { FrameType frameType = frameType(frame); if (frameType == expectedFrameType) { - return FrameHeaderFlyweight.streamId(frame); + return FrameHeaderCodec.streamId(frame); } framesFound.add(frameType); } diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java index 9ec2a2df1..e34973848 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java @@ -17,7 +17,7 @@ package io.rsocket.core; import static io.rsocket.core.PayloadValidationUtils.INVALID_PAYLOAD_ERROR_MESSAGE; -import static io.rsocket.frame.FrameHeaderFlyweight.frameType; +import static io.rsocket.frame.FrameHeaderCodec.frameType; import static io.rsocket.frame.FrameType.ERROR; import static io.rsocket.frame.FrameType.REQUEST_CHANNEL; import static io.rsocket.frame.FrameType.REQUEST_FNF; @@ -38,18 +38,18 @@ import io.netty.util.ReferenceCounted; import io.rsocket.Payload; import io.rsocket.RSocket; -import io.rsocket.frame.CancelFrameFlyweight; -import io.rsocket.frame.ErrorFrameFlyweight; -import io.rsocket.frame.FrameHeaderFlyweight; -import io.rsocket.frame.FrameLengthFlyweight; +import io.rsocket.frame.CancelFrameCodec; +import io.rsocket.frame.ErrorFrameCodec; +import io.rsocket.frame.FrameHeaderCodec; +import io.rsocket.frame.FrameLengthCodec; import io.rsocket.frame.FrameType; -import io.rsocket.frame.KeepAliveFrameFlyweight; -import io.rsocket.frame.PayloadFrameFlyweight; -import io.rsocket.frame.RequestChannelFrameFlyweight; -import io.rsocket.frame.RequestFireAndForgetFrameFlyweight; -import io.rsocket.frame.RequestNFrameFlyweight; -import io.rsocket.frame.RequestResponseFrameFlyweight; -import io.rsocket.frame.RequestStreamFrameFlyweight; +import io.rsocket.frame.KeepAliveFrameCodec; +import io.rsocket.frame.PayloadFrameCodec; +import io.rsocket.frame.RequestChannelFrameCodec; +import io.rsocket.frame.RequestFireAndForgetFrameCodec; +import io.rsocket.frame.RequestNFrameCodec; +import io.rsocket.frame.RequestResponseFrameCodec; +import io.rsocket.frame.RequestStreamFrameCodec; import io.rsocket.frame.decoder.PayloadDecoder; import io.rsocket.internal.subscriber.AssertSubscriber; import io.rsocket.lease.ResponderLeaseHandler; @@ -117,13 +117,13 @@ public void tearDown() { @Disabled public void testHandleKeepAlive() throws Exception { rule.connection.addToReceivedBuffer( - KeepAliveFrameFlyweight.encode(rule.alloc(), true, 0, Unpooled.EMPTY_BUFFER)); + KeepAliveFrameCodec.encode(rule.alloc(), true, 0, Unpooled.EMPTY_BUFFER)); ByteBuf sent = rule.connection.awaitSend(); assertThat("Unexpected frame sent.", frameType(sent), is(FrameType.KEEPALIVE)); /*Keep alive ack must not have respond flag else, it will result in infinite ping-pong of keep alive frames.*/ assertThat( "Unexpected keep-alive frame respond flag.", - KeepAliveFrameFlyweight.respondFlag(sent), + KeepAliveFrameCodec.respondFlag(sent), is(false)); } @@ -176,7 +176,7 @@ public Mono requestResponse(Payload payload) { assertThat("Unexpected error.", rule.errors, is(empty())); assertThat("Unexpected frame sent.", rule.connection.getSent(), is(empty())); - rule.connection.addToReceivedBuffer(CancelFrameFlyweight.encode(allocator, streamId)); + rule.connection.addToReceivedBuffer(CancelFrameCodec.encode(allocator, streamId)); assertThat("Unexpected frame sent.", rule.connection.getSent(), is(empty())); assertThat("Subscription not cancelled.", cancelled.get(), is(true)); @@ -188,8 +188,8 @@ public Mono requestResponse(Payload payload) { public void shouldThrownExceptionIfGivenPayloadIsExitsSizeAllowanceWithNoFragmentation() { final int streamId = 4; final AtomicBoolean cancelled = new AtomicBoolean(); - byte[] metadata = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; - byte[] data = new byte[FrameLengthFlyweight.FRAME_LENGTH_MASK]; + byte[] metadata = new byte[FrameLengthCodec.FRAME_LENGTH_MASK]; + byte[] data = new byte[FrameLengthCodec.FRAME_LENGTH_MASK]; ThreadLocalRandom.current().nextBytes(metadata); ThreadLocalRandom.current().nextBytes(data); final Payload payload = DefaultPayload.create(data, metadata); @@ -239,8 +239,8 @@ protected void hookOnSubscribe(Subscription subscription) { Assertions.assertThat(rule.connection.getSent()) .hasSize(1) .first() - .matches(bb -> FrameHeaderFlyweight.frameType(bb) == FrameType.ERROR) - .matches(bb -> ErrorFrameFlyweight.dataUtf8(bb).contains(INVALID_PAYLOAD_ERROR_MESSAGE)) + .matches(bb -> FrameHeaderCodec.frameType(bb) == FrameType.ERROR) + .matches(bb -> ErrorFrameCodec.dataUtf8(bb).contains(INVALID_PAYLOAD_ERROR_MESSAGE)) .matches(ReferenceCounted::release); assertThat("Subscription not cancelled.", cancelled.get(), is(true)); @@ -272,21 +272,21 @@ public Flux requestChannel(Publisher payloads) { ByteBuf data1 = allocator.buffer(); data1.writeCharSequence("def1", CharsetUtil.UTF_8); ByteBuf nextFrame1 = - PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata1, data1); + PayloadFrameCodec.encode(allocator, 1, false, false, true, metadata1, data1); ByteBuf metadata2 = allocator.buffer(); metadata2.writeCharSequence("abc2", CharsetUtil.UTF_8); ByteBuf data2 = allocator.buffer(); data2.writeCharSequence("def2", CharsetUtil.UTF_8); ByteBuf nextFrame2 = - PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata2, data2); + PayloadFrameCodec.encode(allocator, 1, false, false, true, metadata2, data2); ByteBuf metadata3 = allocator.buffer(); metadata3.writeCharSequence("abc3", CharsetUtil.UTF_8); ByteBuf data3 = allocator.buffer(); data3.writeCharSequence("def3", CharsetUtil.UTF_8); ByteBuf nextFrame3 = - PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata3, data3); + PayloadFrameCodec.encode(allocator, 1, false, false, true, metadata3, data3); RaceTestUtils.race( () -> { @@ -325,7 +325,7 @@ public Flux requestChannel(Publisher payloads) { rule.sendRequest(1, REQUEST_CHANNEL); - ByteBuf cancelFrame = CancelFrameFlyweight.encode(allocator, 1); + ByteBuf cancelFrame = CancelFrameCodec.encode(allocator, 1); FluxSink sink = sinks[0]; RaceTestUtils.race( () -> rule.connection.addToReceivedBuffer(cancelFrame), @@ -365,8 +365,8 @@ public Flux requestChannel(Publisher payloads) { rule.sendRequest(1, REQUEST_CHANNEL); - ByteBuf cancelFrame = CancelFrameFlyweight.encode(allocator, 1); - ByteBuf requestNFrame = RequestNFrameFlyweight.encode(allocator, 1, Integer.MAX_VALUE); + ByteBuf cancelFrame = CancelFrameCodec.encode(allocator, 1); + ByteBuf requestNFrame = RequestNFrameCodec.encode(allocator, 1, Integer.MAX_VALUE); FluxSink sink = sinks[0]; RaceTestUtils.race( () -> @@ -418,23 +418,23 @@ public Flux requestChannel(Publisher payloads) { ByteBuf data1 = allocator.buffer(); data1.writeCharSequence("def1", CharsetUtil.UTF_8); ByteBuf nextFrame1 = - PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata1, data1); + PayloadFrameCodec.encode(allocator, 1, false, false, true, metadata1, data1); ByteBuf metadata2 = allocator.buffer(); metadata2.writeCharSequence("abc2", CharsetUtil.UTF_8); ByteBuf data2 = allocator.buffer(); data2.writeCharSequence("def2", CharsetUtil.UTF_8); ByteBuf nextFrame2 = - PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata2, data2); + PayloadFrameCodec.encode(allocator, 1, false, false, true, metadata2, data2); ByteBuf metadata3 = allocator.buffer(); metadata3.writeCharSequence("abc3", CharsetUtil.UTF_8); ByteBuf data3 = allocator.buffer(); data3.writeCharSequence("def3", CharsetUtil.UTF_8); ByteBuf nextFrame3 = - PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata3, data3); + PayloadFrameCodec.encode(allocator, 1, false, false, true, metadata3, data3); - ByteBuf requestNFrame = RequestNFrameFlyweight.encode(allocator, 1, Integer.MAX_VALUE); + ByteBuf requestNFrame = RequestNFrameCodec.encode(allocator, 1, Integer.MAX_VALUE); FluxSink sink = sinks[0]; RaceTestUtils.race( @@ -477,7 +477,7 @@ public Flux requestStream(Payload payload) { rule.sendRequest(1, REQUEST_STREAM); - ByteBuf cancelFrame = CancelFrameFlyweight.encode(allocator, 1); + ByteBuf cancelFrame = CancelFrameCodec.encode(allocator, 1); FluxSink sink = sinks[0]; RaceTestUtils.race( () -> rule.connection.addToReceivedBuffer(cancelFrame), @@ -520,7 +520,7 @@ public void subscribe(CoreSubscriber actual) { rule.sendRequest(1, REQUEST_RESPONSE); - ByteBuf cancelFrame = CancelFrameFlyweight.encode(allocator, 1); + ByteBuf cancelFrame = CancelFrameCodec.encode(allocator, 1); RaceTestUtils.race( () -> rule.connection.addToReceivedBuffer(cancelFrame), () -> { @@ -551,7 +551,7 @@ public Flux requestStream(Payload payload) { rule.sendRequest(1, REQUEST_STREAM); - ByteBuf cancelFrame = CancelFrameFlyweight.encode(allocator, 1); + ByteBuf cancelFrame = CancelFrameCodec.encode(allocator, 1); FluxSink sink = sinks[0]; sink.next(ByteBufPayload.create("d1", "m1")); @@ -579,28 +579,28 @@ public Flux requestChannel(Publisher payloads) { rule.sendRequest(1, REQUEST_STREAM); - ByteBuf cancelFrame = CancelFrameFlyweight.encode(allocator, 1); + ByteBuf cancelFrame = CancelFrameCodec.encode(allocator, 1); ByteBuf metadata1 = allocator.buffer(); metadata1.writeCharSequence("abc1", CharsetUtil.UTF_8); ByteBuf data1 = allocator.buffer(); data1.writeCharSequence("def1", CharsetUtil.UTF_8); ByteBuf nextFrame1 = - PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata1, data1); + PayloadFrameCodec.encode(allocator, 1, false, false, true, metadata1, data1); ByteBuf metadata2 = allocator.buffer(); metadata2.writeCharSequence("abc2", CharsetUtil.UTF_8); ByteBuf data2 = allocator.buffer(); data2.writeCharSequence("def2", CharsetUtil.UTF_8); ByteBuf nextFrame2 = - PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata2, data2); + PayloadFrameCodec.encode(allocator, 1, false, false, true, metadata2, data2); ByteBuf metadata3 = allocator.buffer(); metadata3.writeCharSequence("abc3", CharsetUtil.UTF_8); ByteBuf data3 = allocator.buffer(); data3.writeCharSequence("de3", CharsetUtil.UTF_8); ByteBuf nextFrame3 = - PayloadFrameFlyweight.encode(allocator, 1, false, false, true, metadata3, data3); + PayloadFrameCodec.encode(allocator, 1, false, false, true, metadata3, data3); rule.connection.addToReceivedBuffer(nextFrame1, nextFrame2, nextFrame3); rule.connection.addToReceivedBuffer(cancelFrame); @@ -651,7 +651,7 @@ public Flux requestChannel(Publisher payloads) { // if responses number is bigger than 1 we have to send one extra requestN if (responsesCnt > 1) { rule.connection.addToReceivedBuffer( - RequestNFrameFlyweight.encode(allocator, 1, responsesCnt - 1)); + RequestNFrameCodec.encode(allocator, 1, responsesCnt - 1)); } // respond with specific number of elements @@ -663,7 +663,7 @@ public Flux requestChannel(Publisher payloads) { if (framesCnt > 1) { for (int i = 1; i < responsesCnt; i++) { rule.connection.addToReceivedBuffer( - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, false, @@ -680,7 +680,7 @@ public Flux requestChannel(Publisher payloads) { .describedAs( "Interaction Type :[%s]. Expected to observe %s frames sent", frameType, responsesCnt) .hasSize(responsesCnt) - .allMatch(bb -> !FrameHeaderFlyweight.hasMetadata(bb)); + .allMatch(bb -> !FrameHeaderCodec.hasMetadata(bb)); } if (framesCnt > 1) { @@ -691,7 +691,7 @@ public Flux requestChannel(Publisher payloads) { frameType, framesCnt - 1) .hasSize(1) .first() - .matches(bb -> RequestNFrameFlyweight.requestN(bb) == (framesCnt - 1)); + .matches(bb -> RequestNFrameCodec.requestN(bb) == (framesCnt - 1)); } Assertions.assertThat(rule.connection.getSent()).allMatch(ReferenceCounted::release); @@ -813,22 +813,20 @@ private void sendRequest(int streamId, FrameType frameType, Payload payload) { switch (frameType) { case REQUEST_CHANNEL: request = - RequestChannelFrameFlyweight.encodeReleasingPayload( + RequestChannelFrameCodec.encodeReleasingPayload( allocator, streamId, false, prefetch, payload); break; case REQUEST_STREAM: request = - RequestStreamFrameFlyweight.encodeReleasingPayload( + RequestStreamFrameCodec.encodeReleasingPayload( allocator, streamId, prefetch, payload); break; case REQUEST_RESPONSE: - request = - RequestResponseFrameFlyweight.encodeReleasingPayload(allocator, streamId, payload); + request = RequestResponseFrameCodec.encodeReleasingPayload(allocator, streamId, payload); break; case REQUEST_FNF: request = - RequestFireAndForgetFrameFlyweight.encodeReleasingPayload( - allocator, streamId, payload); + RequestFireAndForgetFrameCodec.encodeReleasingPayload(allocator, streamId, payload); break; default: throw new IllegalArgumentException("unsupported type: " + frameType); diff --git a/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java b/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java index 388bfffeb..75a5e070e 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java @@ -9,10 +9,10 @@ import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.exceptions.Exceptions; import io.rsocket.exceptions.RejectedSetupException; -import io.rsocket.frame.ErrorFrameFlyweight; -import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.ErrorFrameCodec; +import io.rsocket.frame.FrameHeaderCodec; import io.rsocket.frame.FrameType; -import io.rsocket.frame.SetupFrameFlyweight; +import io.rsocket.frame.SetupFrameCodec; import io.rsocket.lease.RequesterLeaseHandler; import io.rsocket.test.util.TestDuplexConnection; import io.rsocket.transport.ServerTransport; @@ -39,7 +39,7 @@ void responderRejectSetup() { transport.connect(); ByteBuf sentFrame = transport.awaitSent(); - assertThat(FrameHeaderFlyweight.frameType(sentFrame)).isEqualTo(FrameType.ERROR); + assertThat(FrameHeaderCodec.frameType(sentFrame)).isEqualTo(FrameType.ERROR); RuntimeException error = Exceptions.from(0, sentFrame); assertThat(errorMsg).isEqualTo(error.getMessage()); assertThat(error).isInstanceOf(RejectedSetupException.class); @@ -75,7 +75,7 @@ void requesterStreamsTerminatedOnZeroErrorFrame() { .doOnRequest( ignored -> conn.addToReceivedBuffer( - ErrorFrameFlyweight.encode( + ErrorFrameCodec.encode( ByteBufAllocator.DEFAULT, 0, new RejectedSetupException(errorMsg))))) @@ -106,8 +106,7 @@ void requesterNewStreamsTerminatedAfterZeroErrorFrame() { TestScheduler.INSTANCE); conn.addToReceivedBuffer( - ErrorFrameFlyweight.encode( - ByteBufAllocator.DEFAULT, 0, new RejectedSetupException("error"))); + ErrorFrameCodec.encode(ByteBufAllocator.DEFAULT, 0, new RejectedSetupException("error"))); StepVerifier.create( rSocket @@ -158,8 +157,7 @@ public ByteBuf awaitSent() { public void connect() { Payload payload = DefaultPayload.create(DefaultPayload.EMPTY_BUFFER); - ByteBuf setup = - SetupFrameFlyweight.encode(allocator, false, 0, 42, "mdMime", "dMime", payload); + ByteBuf setup = SetupFrameCodec.encode(allocator, false, 0, 42, "mdMime", "dMime", payload); conn.addToReceivedBuffer(setup); } diff --git a/rsocket-core/src/test/java/io/rsocket/exceptions/ExceptionsTest.java b/rsocket-core/src/test/java/io/rsocket/exceptions/ExceptionsTest.java index e646080c7..b3f596a37 100644 --- a/rsocket-core/src/test/java/io/rsocket/exceptions/ExceptionsTest.java +++ b/rsocket-core/src/test/java/io/rsocket/exceptions/ExceptionsTest.java @@ -16,22 +16,22 @@ package io.rsocket.exceptions; -import static io.rsocket.frame.ErrorFrameFlyweight.APPLICATION_ERROR; -import static io.rsocket.frame.ErrorFrameFlyweight.CANCELED; -import static io.rsocket.frame.ErrorFrameFlyweight.CONNECTION_CLOSE; -import static io.rsocket.frame.ErrorFrameFlyweight.CONNECTION_ERROR; -import static io.rsocket.frame.ErrorFrameFlyweight.INVALID; -import static io.rsocket.frame.ErrorFrameFlyweight.INVALID_SETUP; -import static io.rsocket.frame.ErrorFrameFlyweight.REJECTED; -import static io.rsocket.frame.ErrorFrameFlyweight.REJECTED_RESUME; -import static io.rsocket.frame.ErrorFrameFlyweight.REJECTED_SETUP; -import static io.rsocket.frame.ErrorFrameFlyweight.UNSUPPORTED_SETUP; +import static io.rsocket.frame.ErrorFrameCodec.APPLICATION_ERROR; +import static io.rsocket.frame.ErrorFrameCodec.CANCELED; +import static io.rsocket.frame.ErrorFrameCodec.CONNECTION_CLOSE; +import static io.rsocket.frame.ErrorFrameCodec.CONNECTION_ERROR; +import static io.rsocket.frame.ErrorFrameCodec.INVALID; +import static io.rsocket.frame.ErrorFrameCodec.INVALID_SETUP; +import static io.rsocket.frame.ErrorFrameCodec.REJECTED; +import static io.rsocket.frame.ErrorFrameCodec.REJECTED_RESUME; +import static io.rsocket.frame.ErrorFrameCodec.REJECTED_SETUP; +import static io.rsocket.frame.ErrorFrameCodec.UNSUPPORTED_SETUP; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNullPointerException; import io.netty.buffer.ByteBuf; import io.netty.buffer.UnpooledByteBufAllocator; -import io.rsocket.frame.ErrorFrameFlyweight; +import io.rsocket.frame.ErrorFrameCodec; import java.util.concurrent.ThreadLocalRandom; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -205,9 +205,9 @@ void fromCustomRSocketException() { int randomCode = ThreadLocalRandom.current().nextBoolean() ? ThreadLocalRandom.current() - .nextInt(Integer.MIN_VALUE, ErrorFrameFlyweight.MAX_USER_ALLOWED_ERROR_CODE) + .nextInt(Integer.MIN_VALUE, ErrorFrameCodec.MAX_USER_ALLOWED_ERROR_CODE) : ThreadLocalRandom.current() - .nextInt(ErrorFrameFlyweight.MIN_USER_ALLOWED_ERROR_CODE, Integer.MAX_VALUE); + .nextInt(ErrorFrameCodec.MIN_USER_ALLOWED_ERROR_CODE, Integer.MAX_VALUE); ByteBuf byteBuf = createErrorFrame(0, randomCode, "test-message"); assertThat(Exceptions.from(1, byteBuf)) @@ -229,7 +229,7 @@ void fromWithNullFrame() { } private ByteBuf createErrorFrame(int streamId, int errorCode, String message) { - return ErrorFrameFlyweight.encode( + return ErrorFrameCodec.encode( UnpooledByteBufAllocator.DEFAULT, streamId, new TestRSocketException(errorCode, message)); } } diff --git a/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationDuplexConnectionTest.java b/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationDuplexConnectionTest.java index 9050eaa90..932df4283 100644 --- a/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationDuplexConnectionTest.java +++ b/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationDuplexConnectionTest.java @@ -87,7 +87,7 @@ void constructorNullDelegate() { @Test void sendData() { ByteBuf encode = - RequestResponseFrameFlyweight.encode( + RequestResponseFrameCodec.encode( allocator, 1, false, Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer(data)); when(delegate.onClose()).thenReturn(Mono.never()); @@ -101,8 +101,8 @@ void sendData() { .expectNextCount(17) .assertNext( byteBuf -> { - Assert.assertEquals(FrameType.NEXT, FrameHeaderFlyweight.frameType(byteBuf)); - Assert.assertFalse(FrameHeaderFlyweight.hasFollows(byteBuf)); + Assert.assertEquals(FrameType.NEXT, FrameHeaderCodec.frameType(byteBuf)); + Assert.assertFalse(FrameHeaderCodec.hasFollows(byteBuf)); }) .verifyComplete(); } diff --git a/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationIntegrationTest.java b/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationIntegrationTest.java index a8569ef3b..ff62b56f2 100644 --- a/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationIntegrationTest.java +++ b/rsocket-core/src/test/java/io/rsocket/fragmentation/FragmentationIntegrationTest.java @@ -2,9 +2,9 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; -import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.FrameHeaderCodec; import io.rsocket.frame.FrameUtil; -import io.rsocket.frame.PayloadFrameFlyweight; +import io.rsocket.frame.PayloadFrameCodec; import io.rsocket.util.DefaultPayload; import java.util.concurrent.ThreadLocalRandom; import org.junit.Assert; @@ -28,7 +28,7 @@ public class FragmentationIntegrationTest { @Test void fragmentAndReassembleData() { ByteBuf frame = - PayloadFrameFlyweight.encodeNextCompleteReleasingPayload( + PayloadFrameCodec.encodeNextCompleteReleasingPayload( allocator, 2, DefaultPayload.create(data)); System.out.println(FrameUtil.toString(frame)); @@ -36,7 +36,7 @@ void fragmentAndReassembleData() { Publisher fragments = FrameFragmenter.fragmentFrame( - allocator, 64, frame, FrameHeaderFlyweight.frameType(frame), false); + allocator, 64, frame, FrameHeaderCodec.frameType(frame), false); FrameReassembler reassembler = new FrameReassembler(allocator); @@ -50,9 +50,8 @@ void fragmentAndReassembleData() { String s = FrameUtil.toString(assembled); System.out.println(s); - Assert.assertEquals( - FrameHeaderFlyweight.frameType(frame), FrameHeaderFlyweight.frameType(assembled)); + Assert.assertEquals(FrameHeaderCodec.frameType(frame), FrameHeaderCodec.frameType(assembled)); Assert.assertEquals(frame.readableBytes(), assembled.readableBytes()); - Assert.assertEquals(PayloadFrameFlyweight.data(frame), PayloadFrameFlyweight.data(assembled)); + Assert.assertEquals(PayloadFrameCodec.data(frame), PayloadFrameCodec.data(assembled)); } } diff --git a/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameFragmenterTest.java b/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameFragmenterTest.java index c6b1735e6..60dbef74b 100644 --- a/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameFragmenterTest.java +++ b/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameFragmenterTest.java @@ -42,16 +42,14 @@ final class FrameFragmenterTest { @Test void testGettingData() { ByteBuf rr = - RequestResponseFrameFlyweight.encode( - allocator, 1, true, null, Unpooled.wrappedBuffer(data)); + RequestResponseFrameCodec.encode(allocator, 1, true, null, Unpooled.wrappedBuffer(data)); ByteBuf fnf = - RequestFireAndForgetFrameFlyweight.encode( + RequestFireAndForgetFrameCodec.encode( allocator, 1, true, null, Unpooled.wrappedBuffer(data)); ByteBuf rs = - RequestStreamFrameFlyweight.encode( - allocator, 1, true, 1, null, Unpooled.wrappedBuffer(data)); + RequestStreamFrameCodec.encode(allocator, 1, true, 1, null, Unpooled.wrappedBuffer(data)); ByteBuf rc = - RequestChannelFrameFlyweight.encode( + RequestChannelFrameCodec.encode( allocator, 1, true, false, 1, null, Unpooled.wrappedBuffer(data)); ByteBuf data = FrameFragmenter.getData(rr, FrameType.REQUEST_RESPONSE); @@ -74,16 +72,16 @@ void testGettingData() { @Test void testGettingMetadata() { ByteBuf rr = - RequestResponseFrameFlyweight.encode( + RequestResponseFrameCodec.encode( allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.wrappedBuffer(data)); ByteBuf fnf = - RequestFireAndForgetFrameFlyweight.encode( + RequestFireAndForgetFrameCodec.encode( allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.wrappedBuffer(data)); ByteBuf rs = - RequestStreamFrameFlyweight.encode( + RequestStreamFrameCodec.encode( allocator, 1, true, 1, Unpooled.wrappedBuffer(metadata), Unpooled.wrappedBuffer(data)); ByteBuf rc = - RequestChannelFrameFlyweight.encode( + RequestChannelFrameCodec.encode( allocator, 1, true, @@ -112,8 +110,7 @@ void testGettingMetadata() { @Test void returnEmptBufferWhenNoMetadataPresent() { ByteBuf rr = - RequestResponseFrameFlyweight.encode( - allocator, 1, true, null, Unpooled.wrappedBuffer(data)); + RequestResponseFrameCodec.encode(allocator, 1, true, null, Unpooled.wrappedBuffer(data)); ByteBuf data = FrameFragmenter.getMetadata(rr, FrameType.REQUEST_RESPONSE); Assert.assertEquals(data, Unpooled.EMPTY_BUFFER); @@ -124,8 +121,7 @@ void returnEmptBufferWhenNoMetadataPresent() { @Test void encodeFirstFrameWithData() { ByteBuf rr = - RequestResponseFrameFlyweight.encode( - allocator, 1, true, null, Unpooled.wrappedBuffer(data)); + RequestResponseFrameCodec.encode(allocator, 1, true, null, Unpooled.wrappedBuffer(data)); ByteBuf fragment = FrameFragmenter.encodeFirstFragment( @@ -138,22 +134,22 @@ void encodeFirstFrameWithData() { Unpooled.wrappedBuffer(data)); Assert.assertEquals(256, fragment.readableBytes()); - Assert.assertEquals(FrameType.REQUEST_RESPONSE, FrameHeaderFlyweight.frameType(fragment)); - Assert.assertEquals(1, FrameHeaderFlyweight.streamId(fragment)); - Assert.assertTrue(FrameHeaderFlyweight.hasFollows(fragment)); + Assert.assertEquals(FrameType.REQUEST_RESPONSE, FrameHeaderCodec.frameType(fragment)); + Assert.assertEquals(1, FrameHeaderCodec.streamId(fragment)); + Assert.assertTrue(FrameHeaderCodec.hasFollows(fragment)); - ByteBuf data = RequestResponseFrameFlyweight.data(fragment); + ByteBuf data = RequestResponseFrameCodec.data(fragment); ByteBuf byteBuf = Unpooled.wrappedBuffer(this.data).readSlice(data.readableBytes()); Assert.assertEquals(byteBuf, data); - Assert.assertFalse(FrameHeaderFlyweight.hasMetadata(fragment)); + Assert.assertFalse(FrameHeaderCodec.hasMetadata(fragment)); } @DisplayName("encode first channel frame") @Test void encodeFirstWithDataChannel() { ByteBuf rc = - RequestChannelFrameFlyweight.encode( + RequestChannelFrameCodec.encode( allocator, 1, true, false, 10, null, Unpooled.wrappedBuffer(data)); ByteBuf fragment = @@ -167,24 +163,23 @@ void encodeFirstWithDataChannel() { Unpooled.wrappedBuffer(data)); Assert.assertEquals(256, fragment.readableBytes()); - Assert.assertEquals(FrameType.REQUEST_CHANNEL, FrameHeaderFlyweight.frameType(fragment)); - Assert.assertEquals(1, FrameHeaderFlyweight.streamId(fragment)); - Assert.assertEquals(10, RequestChannelFrameFlyweight.initialRequestN(fragment)); - Assert.assertTrue(FrameHeaderFlyweight.hasFollows(fragment)); + Assert.assertEquals(FrameType.REQUEST_CHANNEL, FrameHeaderCodec.frameType(fragment)); + Assert.assertEquals(1, FrameHeaderCodec.streamId(fragment)); + Assert.assertEquals(10, RequestChannelFrameCodec.initialRequestN(fragment)); + Assert.assertTrue(FrameHeaderCodec.hasFollows(fragment)); - ByteBuf data = RequestChannelFrameFlyweight.data(fragment); + ByteBuf data = RequestChannelFrameCodec.data(fragment); ByteBuf byteBuf = Unpooled.wrappedBuffer(this.data).readSlice(data.readableBytes()); Assert.assertEquals(byteBuf, data); - Assert.assertFalse(FrameHeaderFlyweight.hasMetadata(fragment)); + Assert.assertFalse(FrameHeaderCodec.hasMetadata(fragment)); } @DisplayName("encode first stream frame") @Test void encodeFirstWithDataStream() { ByteBuf rc = - RequestStreamFrameFlyweight.encode( - allocator, 1, true, 50, null, Unpooled.wrappedBuffer(data)); + RequestStreamFrameCodec.encode(allocator, 1, true, 50, null, Unpooled.wrappedBuffer(data)); ByteBuf fragment = FrameFragmenter.encodeFirstFragment( @@ -197,23 +192,23 @@ void encodeFirstWithDataStream() { Unpooled.wrappedBuffer(data)); Assert.assertEquals(256, fragment.readableBytes()); - Assert.assertEquals(FrameType.REQUEST_STREAM, FrameHeaderFlyweight.frameType(fragment)); - Assert.assertEquals(1, FrameHeaderFlyweight.streamId(fragment)); - Assert.assertEquals(50, RequestStreamFrameFlyweight.initialRequestN(fragment)); - Assert.assertTrue(FrameHeaderFlyweight.hasFollows(fragment)); + Assert.assertEquals(FrameType.REQUEST_STREAM, FrameHeaderCodec.frameType(fragment)); + Assert.assertEquals(1, FrameHeaderCodec.streamId(fragment)); + Assert.assertEquals(50, RequestStreamFrameCodec.initialRequestN(fragment)); + Assert.assertTrue(FrameHeaderCodec.hasFollows(fragment)); - ByteBuf data = RequestStreamFrameFlyweight.data(fragment); + ByteBuf data = RequestStreamFrameCodec.data(fragment); ByteBuf byteBuf = Unpooled.wrappedBuffer(this.data).readSlice(data.readableBytes()); Assert.assertEquals(byteBuf, data); - Assert.assertFalse(FrameHeaderFlyweight.hasMetadata(fragment)); + Assert.assertFalse(FrameHeaderCodec.hasMetadata(fragment)); } @DisplayName("encode first frame with only metadata") @Test void encodeFirstFrameWithMetadata() { ByteBuf rr = - RequestResponseFrameFlyweight.encode( + RequestResponseFrameCodec.encode( allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER); ByteBuf fragment = @@ -227,21 +222,21 @@ void encodeFirstFrameWithMetadata() { Unpooled.EMPTY_BUFFER); Assert.assertEquals(256, fragment.readableBytes()); - Assert.assertEquals(FrameType.REQUEST_RESPONSE, FrameHeaderFlyweight.frameType(fragment)); - Assert.assertEquals(1, FrameHeaderFlyweight.streamId(fragment)); - Assert.assertTrue(FrameHeaderFlyweight.hasFollows(fragment)); + Assert.assertEquals(FrameType.REQUEST_RESPONSE, FrameHeaderCodec.frameType(fragment)); + Assert.assertEquals(1, FrameHeaderCodec.streamId(fragment)); + Assert.assertTrue(FrameHeaderCodec.hasFollows(fragment)); - ByteBuf data = RequestResponseFrameFlyweight.data(fragment); + ByteBuf data = RequestResponseFrameCodec.data(fragment); Assert.assertEquals(data, Unpooled.EMPTY_BUFFER); - Assert.assertTrue(FrameHeaderFlyweight.hasMetadata(fragment)); + Assert.assertTrue(FrameHeaderCodec.hasMetadata(fragment)); } @DisplayName("encode first stream frame with data and metadata") @Test void encodeFirstWithDataAndMetadataStream() { ByteBuf rc = - RequestStreamFrameFlyweight.encode( + RequestStreamFrameCodec.encode( allocator, 1, true, 50, Unpooled.wrappedBuffer(metadata), Unpooled.wrappedBuffer(data)); ByteBuf fragment = @@ -255,27 +250,26 @@ void encodeFirstWithDataAndMetadataStream() { Unpooled.wrappedBuffer(data)); Assert.assertEquals(256, fragment.readableBytes()); - Assert.assertEquals(FrameType.REQUEST_STREAM, FrameHeaderFlyweight.frameType(fragment)); - Assert.assertEquals(1, FrameHeaderFlyweight.streamId(fragment)); - Assert.assertEquals(50, RequestStreamFrameFlyweight.initialRequestN(fragment)); - Assert.assertTrue(FrameHeaderFlyweight.hasFollows(fragment)); + Assert.assertEquals(FrameType.REQUEST_STREAM, FrameHeaderCodec.frameType(fragment)); + Assert.assertEquals(1, FrameHeaderCodec.streamId(fragment)); + Assert.assertEquals(50, RequestStreamFrameCodec.initialRequestN(fragment)); + Assert.assertTrue(FrameHeaderCodec.hasFollows(fragment)); - ByteBuf data = RequestStreamFrameFlyweight.data(fragment); + ByteBuf data = RequestStreamFrameCodec.data(fragment); Assert.assertEquals(0, data.readableBytes()); - ByteBuf metadata = RequestStreamFrameFlyweight.metadata(fragment); + ByteBuf metadata = RequestStreamFrameCodec.metadata(fragment); ByteBuf byteBuf = Unpooled.wrappedBuffer(this.metadata).readSlice(metadata.readableBytes()); Assert.assertEquals(byteBuf, metadata); - Assert.assertTrue(FrameHeaderFlyweight.hasMetadata(fragment)); + Assert.assertTrue(FrameHeaderCodec.hasMetadata(fragment)); } @DisplayName("fragments frame with only data") @Test void fragmentData() { ByteBuf rr = - RequestResponseFrameFlyweight.encode( - allocator, 1, true, null, Unpooled.wrappedBuffer(data)); + RequestResponseFrameCodec.encode(allocator, 1, true, null, Unpooled.wrappedBuffer(data)); Publisher fragments = FrameFragmenter.fragmentFrame(allocator, 1024, rr, FrameType.REQUEST_RESPONSE, false); @@ -284,15 +278,15 @@ void fragmentData() { .expectNextCount(1) .assertNext( byteBuf -> { - Assert.assertEquals(FrameType.NEXT, FrameHeaderFlyweight.frameType(byteBuf)); - Assert.assertEquals(1, FrameHeaderFlyweight.streamId(byteBuf)); - Assert.assertTrue(FrameHeaderFlyweight.hasFollows(byteBuf)); + Assert.assertEquals(FrameType.NEXT, FrameHeaderCodec.frameType(byteBuf)); + Assert.assertEquals(1, FrameHeaderCodec.streamId(byteBuf)); + Assert.assertTrue(FrameHeaderCodec.hasFollows(byteBuf)); }) .expectNextCount(2) .assertNext( byteBuf -> { - Assert.assertEquals(FrameType.NEXT, FrameHeaderFlyweight.frameType(byteBuf)); - Assert.assertFalse(FrameHeaderFlyweight.hasFollows(byteBuf)); + Assert.assertEquals(FrameType.NEXT, FrameHeaderCodec.frameType(byteBuf)); + Assert.assertFalse(FrameHeaderCodec.hasFollows(byteBuf)); }) .verifyComplete(); } @@ -301,7 +295,7 @@ void fragmentData() { @Test void fragmentMetadata() { ByteBuf rr = - RequestStreamFrameFlyweight.encode( + RequestStreamFrameCodec.encode( allocator, 1, true, 10, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER); Publisher fragments = @@ -311,15 +305,15 @@ void fragmentMetadata() { .expectNextCount(1) .assertNext( byteBuf -> { - Assert.assertEquals(FrameType.NEXT, FrameHeaderFlyweight.frameType(byteBuf)); - Assert.assertEquals(1, FrameHeaderFlyweight.streamId(byteBuf)); - Assert.assertTrue(FrameHeaderFlyweight.hasFollows(byteBuf)); + Assert.assertEquals(FrameType.NEXT, FrameHeaderCodec.frameType(byteBuf)); + Assert.assertEquals(1, FrameHeaderCodec.streamId(byteBuf)); + Assert.assertTrue(FrameHeaderCodec.hasFollows(byteBuf)); }) .expectNextCount(2) .assertNext( byteBuf -> { - Assert.assertEquals(FrameType.NEXT, FrameHeaderFlyweight.frameType(byteBuf)); - Assert.assertFalse(FrameHeaderFlyweight.hasFollows(byteBuf)); + Assert.assertEquals(FrameType.NEXT, FrameHeaderCodec.frameType(byteBuf)); + Assert.assertFalse(FrameHeaderCodec.hasFollows(byteBuf)); }) .verifyComplete(); } @@ -328,7 +322,7 @@ void fragmentMetadata() { @Test void fragmentDataAndMetadata() { ByteBuf rr = - RequestResponseFrameFlyweight.encode( + RequestResponseFrameCodec.encode( allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.wrappedBuffer(data)); Publisher fragments = @@ -337,20 +331,19 @@ void fragmentDataAndMetadata() { StepVerifier.create(Flux.from(fragments).doOnError(Throwable::printStackTrace)) .assertNext( byteBuf -> { - Assert.assertEquals( - FrameType.REQUEST_RESPONSE, FrameHeaderFlyweight.frameType(byteBuf)); - Assert.assertTrue(FrameHeaderFlyweight.hasFollows(byteBuf)); + Assert.assertEquals(FrameType.REQUEST_RESPONSE, FrameHeaderCodec.frameType(byteBuf)); + Assert.assertTrue(FrameHeaderCodec.hasFollows(byteBuf)); }) .expectNextCount(6) .assertNext( byteBuf -> { - Assert.assertEquals(FrameType.NEXT, FrameHeaderFlyweight.frameType(byteBuf)); - Assert.assertTrue(FrameHeaderFlyweight.hasFollows(byteBuf)); + Assert.assertEquals(FrameType.NEXT, FrameHeaderCodec.frameType(byteBuf)); + Assert.assertTrue(FrameHeaderCodec.hasFollows(byteBuf)); }) .assertNext( byteBuf -> { - Assert.assertEquals(FrameType.NEXT, FrameHeaderFlyweight.frameType(byteBuf)); - Assert.assertFalse(FrameHeaderFlyweight.hasFollows(byteBuf)); + Assert.assertEquals(FrameType.NEXT, FrameHeaderCodec.frameType(byteBuf)); + Assert.assertFalse(FrameHeaderCodec.hasFollows(byteBuf)); }) .verifyComplete(); } diff --git a/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameReassemblerTest.java b/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameReassemblerTest.java index 13632165b..56f7fcf90 100644 --- a/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameReassemblerTest.java +++ b/rsocket-core/src/test/java/io/rsocket/fragmentation/FrameReassemblerTest.java @@ -47,15 +47,15 @@ final class FrameReassemblerTest { void reassembleData() { List byteBufs = Arrays.asList( - RequestResponseFrameFlyweight.encode( + RequestResponseFrameCodec.encode( allocator, 1, true, null, Unpooled.wrappedBuffer(data)), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, false, true, null, Unpooled.wrappedBuffer(data)), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, false, true, null, Unpooled.wrappedBuffer(data)), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, false, true, null, Unpooled.wrappedBuffer(data)), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, false, false, true, null, Unpooled.wrappedBuffer(data))); FrameReassembler reassembler = new FrameReassembler(allocator); @@ -76,7 +76,7 @@ void reassembleData() { StepVerifier.create(assembled) .assertNext( byteBuf -> { - Assert.assertEquals(data, RequestResponseFrameFlyweight.data(byteBuf)); + Assert.assertEquals(data, RequestResponseFrameCodec.data(byteBuf)); ReferenceCountUtil.safeRelease(byteBuf); }) .verifyComplete(); @@ -88,7 +88,7 @@ void reassembleData() { void passthrough() { List byteBufs = Arrays.asList( - RequestResponseFrameFlyweight.encode( + RequestResponseFrameCodec.encode( allocator, 1, false, null, Unpooled.wrappedBuffer(data))); FrameReassembler reassembler = new FrameReassembler(allocator); @@ -103,7 +103,7 @@ void passthrough() { StepVerifier.create(assembled) .assertNext( byteBuf -> { - Assert.assertEquals(data, RequestResponseFrameFlyweight.data(byteBuf)); + Assert.assertEquals(data, RequestResponseFrameCodec.data(byteBuf)); ReferenceCountUtil.safeRelease(byteBuf); }) .verifyComplete(); @@ -115,9 +115,9 @@ void passthrough() { void reassembleMetadata() { List byteBufs = Arrays.asList( - RequestResponseFrameFlyweight.encode( + RequestResponseFrameCodec.encode( allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -125,7 +125,7 @@ void reassembleMetadata() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -133,7 +133,7 @@ void reassembleMetadata() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -141,7 +141,7 @@ void reassembleMetadata() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, false, @@ -169,7 +169,7 @@ void reassembleMetadata() { .assertNext( byteBuf -> { System.out.println(byteBuf.readableBytes()); - ByteBuf m = RequestResponseFrameFlyweight.metadata(byteBuf); + ByteBuf m = RequestResponseFrameCodec.metadata(byteBuf); Assert.assertEquals(metadata, m); }) .verifyComplete(); @@ -180,7 +180,7 @@ void reassembleMetadata() { void reassembleMetadataChannel() { List byteBufs = Arrays.asList( - RequestChannelFrameFlyweight.encode( + RequestChannelFrameCodec.encode( allocator, 1, true, @@ -188,7 +188,7 @@ void reassembleMetadataChannel() { 100, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -196,7 +196,7 @@ void reassembleMetadataChannel() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -204,7 +204,7 @@ void reassembleMetadataChannel() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -212,7 +212,7 @@ void reassembleMetadataChannel() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, false, @@ -240,9 +240,9 @@ void reassembleMetadataChannel() { .assertNext( byteBuf -> { System.out.println(byteBuf.readableBytes()); - ByteBuf m = RequestChannelFrameFlyweight.metadata(byteBuf); + ByteBuf m = RequestChannelFrameCodec.metadata(byteBuf); Assert.assertEquals(metadata, m); - Assert.assertEquals(100, RequestChannelFrameFlyweight.initialRequestN(byteBuf)); + Assert.assertEquals(100, RequestChannelFrameCodec.initialRequestN(byteBuf)); ReferenceCountUtil.safeRelease(byteBuf); }) .verifyComplete(); @@ -255,9 +255,9 @@ void reassembleMetadataChannel() { void reassembleMetadataStream() { List byteBufs = Arrays.asList( - RequestStreamFrameFlyweight.encode( + RequestStreamFrameCodec.encode( allocator, 1, true, 250, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -265,7 +265,7 @@ void reassembleMetadataStream() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -273,7 +273,7 @@ void reassembleMetadataStream() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -281,7 +281,7 @@ void reassembleMetadataStream() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, false, @@ -309,9 +309,9 @@ void reassembleMetadataStream() { .assertNext( byteBuf -> { System.out.println(byteBuf.readableBytes()); - ByteBuf m = RequestStreamFrameFlyweight.metadata(byteBuf); + ByteBuf m = RequestStreamFrameCodec.metadata(byteBuf); Assert.assertEquals(metadata, m); - Assert.assertEquals(250, RequestChannelFrameFlyweight.initialRequestN(byteBuf)); + Assert.assertEquals(250, RequestChannelFrameCodec.initialRequestN(byteBuf)); ReferenceCountUtil.safeRelease(byteBuf); }) .verifyComplete(); @@ -325,9 +325,9 @@ void reassembleMetadataAndData() { List byteBufs = Arrays.asList( - RequestResponseFrameFlyweight.encode( + RequestResponseFrameCodec.encode( allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -335,7 +335,7 @@ void reassembleMetadataAndData() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -343,7 +343,7 @@ void reassembleMetadataAndData() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -351,7 +351,7 @@ void reassembleMetadataAndData() { true, Unpooled.wrappedBuffer(metadata), Unpooled.wrappedBuffer(data)), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, false, false, true, null, Unpooled.wrappedBuffer(data))); FrameReassembler reassembler = new FrameReassembler(allocator); @@ -379,8 +379,8 @@ void reassembleMetadataAndData() { StepVerifier.create(assembled) .assertNext( byteBuf -> { - Assert.assertEquals(data, RequestResponseFrameFlyweight.data(byteBuf)); - Assert.assertEquals(metadata, RequestResponseFrameFlyweight.metadata(byteBuf)); + Assert.assertEquals(data, RequestResponseFrameCodec.data(byteBuf)); + Assert.assertEquals(metadata, RequestResponseFrameCodec.metadata(byteBuf)); }) .verifyComplete(); ReferenceCountUtil.safeRelease(data); @@ -392,9 +392,9 @@ void reassembleMetadataAndData() { public void cancelBeforeAssembling() { List byteBufs = Arrays.asList( - RequestResponseFrameFlyweight.encode( + RequestResponseFrameCodec.encode( allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -402,7 +402,7 @@ public void cancelBeforeAssembling() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -410,7 +410,7 @@ public void cancelBeforeAssembling() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -426,7 +426,7 @@ public void cancelBeforeAssembling() { Assert.assertTrue(reassembler.metadata.containsKey(1)); Assert.assertTrue(reassembler.data.containsKey(1)); - Flux.just(CancelFrameFlyweight.encode(allocator, 1)) + Flux.just(CancelFrameCodec.encode(allocator, 1)) .handle(reassembler::reassembleFrame) .blockLast(); @@ -440,9 +440,9 @@ public void cancelBeforeAssembling() { public void dispose() { List byteBufs = Arrays.asList( - RequestResponseFrameFlyweight.encode( + RequestResponseFrameCodec.encode( allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -450,7 +450,7 @@ public void dispose() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -458,7 +458,7 @@ public void dispose() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, diff --git a/rsocket-core/src/test/java/io/rsocket/fragmentation/ReassembleDuplexConnectionTest.java b/rsocket-core/src/test/java/io/rsocket/fragmentation/ReassembleDuplexConnectionTest.java index 013e2ebc2..b083d6841 100644 --- a/rsocket-core/src/test/java/io/rsocket/fragmentation/ReassembleDuplexConnectionTest.java +++ b/rsocket-core/src/test/java/io/rsocket/fragmentation/ReassembleDuplexConnectionTest.java @@ -26,11 +26,11 @@ import io.netty.buffer.Unpooled; import io.rsocket.DuplexConnection; import io.rsocket.buffer.LeaksTrackingByteBufAllocator; -import io.rsocket.frame.CancelFrameFlyweight; -import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.CancelFrameCodec; +import io.rsocket.frame.FrameHeaderCodec; import io.rsocket.frame.FrameType; -import io.rsocket.frame.PayloadFrameFlyweight; -import io.rsocket.frame.RequestResponseFrameFlyweight; +import io.rsocket.frame.PayloadFrameCodec; +import io.rsocket.frame.RequestResponseFrameCodec; import java.util.Arrays; import java.util.List; import java.util.concurrent.ThreadLocalRandom; @@ -60,15 +60,15 @@ final class ReassembleDuplexConnectionTest { void reassembleData() { List byteBufs = Arrays.asList( - RequestResponseFrameFlyweight.encode( + RequestResponseFrameCodec.encode( allocator, 1, true, null, Unpooled.wrappedBuffer(data)), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, false, true, null, Unpooled.wrappedBuffer(data)), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, false, true, null, Unpooled.wrappedBuffer(data)), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, false, true, null, Unpooled.wrappedBuffer(data)), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, false, false, true, null, Unpooled.wrappedBuffer(data))); CompositeByteBuf data = @@ -91,7 +91,7 @@ void reassembleData() { .as(StepVerifier::create) .assertNext( byteBuf -> { - Assert.assertEquals(data, RequestResponseFrameFlyweight.data(byteBuf)); + Assert.assertEquals(data, RequestResponseFrameCodec.data(byteBuf)); }) .verifyComplete(); } @@ -101,9 +101,9 @@ void reassembleData() { void reassembleMetadata() { List byteBufs = Arrays.asList( - RequestResponseFrameFlyweight.encode( + RequestResponseFrameCodec.encode( allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -111,7 +111,7 @@ void reassembleMetadata() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -119,7 +119,7 @@ void reassembleMetadata() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -127,7 +127,7 @@ void reassembleMetadata() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, false, @@ -157,7 +157,7 @@ void reassembleMetadata() { .assertNext( byteBuf -> { System.out.println(byteBuf.readableBytes()); - ByteBuf m = RequestResponseFrameFlyweight.metadata(byteBuf); + ByteBuf m = RequestResponseFrameCodec.metadata(byteBuf); Assert.assertEquals(metadata, m); }) .verifyComplete(); @@ -168,9 +168,9 @@ void reassembleMetadata() { void reassembleMetadataAndData() { List byteBufs = Arrays.asList( - RequestResponseFrameFlyweight.encode( + RequestResponseFrameCodec.encode( allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -178,7 +178,7 @@ void reassembleMetadataAndData() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -186,7 +186,7 @@ void reassembleMetadataAndData() { true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, true, @@ -194,7 +194,7 @@ void reassembleMetadataAndData() { true, Unpooled.wrappedBuffer(metadata), Unpooled.wrappedBuffer(data)), - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 1, false, false, true, null, Unpooled.wrappedBuffer(data))); CompositeByteBuf data = @@ -224,8 +224,8 @@ void reassembleMetadataAndData() { .as(StepVerifier::create) .assertNext( byteBuf -> { - Assert.assertEquals(data, RequestResponseFrameFlyweight.data(byteBuf)); - Assert.assertEquals(metadata, RequestResponseFrameFlyweight.metadata(byteBuf)); + Assert.assertEquals(data, RequestResponseFrameCodec.data(byteBuf)); + Assert.assertEquals(metadata, RequestResponseFrameCodec.metadata(byteBuf)); }) .verifyComplete(); } @@ -234,8 +234,7 @@ void reassembleMetadataAndData() { @Test void reassembleNonFragment() { ByteBuf encode = - RequestResponseFrameFlyweight.encode( - allocator, 1, false, null, Unpooled.wrappedBuffer(data)); + RequestResponseFrameCodec.encode(allocator, 1, false, null, Unpooled.wrappedBuffer(data)); when(delegate.receive()).thenReturn(Flux.just(encode)); when(delegate.onClose()).thenReturn(Mono.never()); @@ -247,7 +246,7 @@ void reassembleNonFragment() { .assertNext( byteBuf -> { Assert.assertEquals( - Unpooled.wrappedBuffer(data), RequestResponseFrameFlyweight.data(byteBuf)); + Unpooled.wrappedBuffer(data), RequestResponseFrameCodec.data(byteBuf)); }) .verifyComplete(); } @@ -255,7 +254,7 @@ void reassembleNonFragment() { @DisplayName("does not reassemble non fragmentable frame") @Test void reassembleNonFragmentableFrame() { - ByteBuf encode = CancelFrameFlyweight.encode(allocator, 2); + ByteBuf encode = CancelFrameCodec.encode(allocator, 2); when(delegate.receive()).thenReturn(Flux.just(encode)); when(delegate.onClose()).thenReturn(Mono.never()); @@ -266,7 +265,7 @@ void reassembleNonFragmentableFrame() { .as(StepVerifier::create) .assertNext( byteBuf -> { - Assert.assertEquals(FrameType.CANCEL, FrameHeaderFlyweight.frameType(byteBuf)); + Assert.assertEquals(FrameType.CANCEL, FrameHeaderCodec.frameType(byteBuf)); }) .verifyComplete(); } diff --git a/rsocket-core/src/test/java/io/rsocket/frame/ErrorFrameFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/frame/ErrorFrameCodecTest.java similarity index 65% rename from rsocket-core/src/test/java/io/rsocket/frame/ErrorFrameFlyweightTest.java rename to rsocket-core/src/test/java/io/rsocket/frame/ErrorFrameCodecTest.java index fa663432c..dc04c1141 100644 --- a/rsocket-core/src/test/java/io/rsocket/frame/ErrorFrameFlyweightTest.java +++ b/rsocket-core/src/test/java/io/rsocket/frame/ErrorFrameCodecTest.java @@ -8,13 +8,13 @@ import io.rsocket.exceptions.ApplicationErrorException; import org.junit.jupiter.api.Test; -class ErrorFrameFlyweightTest { +class ErrorFrameCodecTest { @Test void testEncode() { ByteBuf frame = - ErrorFrameFlyweight.encode(ByteBufAllocator.DEFAULT, 1, new ApplicationErrorException("d")); + ErrorFrameCodec.encode(ByteBufAllocator.DEFAULT, 1, new ApplicationErrorException("d")); - frame = FrameLengthFlyweight.encode(ByteBufAllocator.DEFAULT, frame.readableBytes(), frame); + frame = FrameLengthCodec.encode(ByteBufAllocator.DEFAULT, frame.readableBytes(), frame); assertEquals("00000b000000012c000000020164", ByteBufUtil.hexDump(frame)); frame.release(); } diff --git a/rsocket-core/src/test/java/io/rsocket/frame/ExtensionFrameCodecTest.java b/rsocket-core/src/test/java/io/rsocket/frame/ExtensionFrameCodecTest.java new file mode 100644 index 000000000..28209393e --- /dev/null +++ b/rsocket-core/src/test/java/io/rsocket/frame/ExtensionFrameCodecTest.java @@ -0,0 +1,62 @@ +package io.rsocket.frame; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class ExtensionFrameCodecTest { + + @Test + void extensionDataMetadata() { + ByteBuf metadata = bytebuf("md"); + ByteBuf data = bytebuf("d"); + int extendedType = 1; + + ByteBuf extension = + ExtensionFrameCodec.encode(ByteBufAllocator.DEFAULT, 1, extendedType, metadata, data); + + Assertions.assertTrue(FrameHeaderCodec.hasMetadata(extension)); + Assertions.assertEquals(extendedType, ExtensionFrameCodec.extendedType(extension)); + Assertions.assertEquals(metadata, ExtensionFrameCodec.metadata(extension)); + Assertions.assertEquals(data, ExtensionFrameCodec.data(extension)); + extension.release(); + } + + @Test + void extensionData() { + ByteBuf data = bytebuf("d"); + int extendedType = 1; + + ByteBuf extension = + ExtensionFrameCodec.encode(ByteBufAllocator.DEFAULT, 1, extendedType, null, data); + + Assertions.assertFalse(FrameHeaderCodec.hasMetadata(extension)); + Assertions.assertEquals(extendedType, ExtensionFrameCodec.extendedType(extension)); + Assertions.assertNull(ExtensionFrameCodec.metadata(extension)); + Assertions.assertEquals(data, ExtensionFrameCodec.data(extension)); + extension.release(); + } + + @Test + void extensionMetadata() { + ByteBuf metadata = bytebuf("md"); + int extendedType = 1; + + ByteBuf extension = + ExtensionFrameCodec.encode( + ByteBufAllocator.DEFAULT, 1, extendedType, metadata, Unpooled.EMPTY_BUFFER); + + Assertions.assertTrue(FrameHeaderCodec.hasMetadata(extension)); + Assertions.assertEquals(extendedType, ExtensionFrameCodec.extendedType(extension)); + Assertions.assertEquals(metadata, ExtensionFrameCodec.metadata(extension)); + Assertions.assertEquals(0, ExtensionFrameCodec.data(extension).readableBytes()); + extension.release(); + } + + private static ByteBuf bytebuf(String str) { + return Unpooled.copiedBuffer(str, StandardCharsets.UTF_8); + } +} diff --git a/rsocket-core/src/test/java/io/rsocket/frame/ExtensionFrameFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/frame/ExtensionFrameFlyweightTest.java deleted file mode 100644 index eea72c03e..000000000 --- a/rsocket-core/src/test/java/io/rsocket/frame/ExtensionFrameFlyweightTest.java +++ /dev/null @@ -1,62 +0,0 @@ -package io.rsocket.frame; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.Unpooled; -import java.nio.charset.StandardCharsets; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class ExtensionFrameFlyweightTest { - - @Test - void extensionDataMetadata() { - ByteBuf metadata = bytebuf("md"); - ByteBuf data = bytebuf("d"); - int extendedType = 1; - - ByteBuf extension = - ExtensionFrameFlyweight.encode(ByteBufAllocator.DEFAULT, 1, extendedType, metadata, data); - - Assertions.assertTrue(FrameHeaderFlyweight.hasMetadata(extension)); - Assertions.assertEquals(extendedType, ExtensionFrameFlyweight.extendedType(extension)); - Assertions.assertEquals(metadata, ExtensionFrameFlyweight.metadata(extension)); - Assertions.assertEquals(data, ExtensionFrameFlyweight.data(extension)); - extension.release(); - } - - @Test - void extensionData() { - ByteBuf data = bytebuf("d"); - int extendedType = 1; - - ByteBuf extension = - ExtensionFrameFlyweight.encode(ByteBufAllocator.DEFAULT, 1, extendedType, null, data); - - Assertions.assertFalse(FrameHeaderFlyweight.hasMetadata(extension)); - Assertions.assertEquals(extendedType, ExtensionFrameFlyweight.extendedType(extension)); - Assertions.assertNull(ExtensionFrameFlyweight.metadata(extension)); - Assertions.assertEquals(data, ExtensionFrameFlyweight.data(extension)); - extension.release(); - } - - @Test - void extensionMetadata() { - ByteBuf metadata = bytebuf("md"); - int extendedType = 1; - - ByteBuf extension = - ExtensionFrameFlyweight.encode( - ByteBufAllocator.DEFAULT, 1, extendedType, metadata, Unpooled.EMPTY_BUFFER); - - Assertions.assertTrue(FrameHeaderFlyweight.hasMetadata(extension)); - Assertions.assertEquals(extendedType, ExtensionFrameFlyweight.extendedType(extension)); - Assertions.assertEquals(metadata, ExtensionFrameFlyweight.metadata(extension)); - Assertions.assertEquals(0, ExtensionFrameFlyweight.data(extension).readableBytes()); - extension.release(); - } - - private static ByteBuf bytebuf(String str) { - return Unpooled.copiedBuffer(str, StandardCharsets.UTF_8); - } -} diff --git a/rsocket-core/src/test/java/io/rsocket/frame/FrameHeaderFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/frame/FrameHeaderCodecTest.java similarity index 52% rename from rsocket-core/src/test/java/io/rsocket/frame/FrameHeaderFlyweightTest.java rename to rsocket-core/src/test/java/io/rsocket/frame/FrameHeaderCodecTest.java index a17fcc205..15788e631 100644 --- a/rsocket-core/src/test/java/io/rsocket/frame/FrameHeaderFlyweightTest.java +++ b/rsocket-core/src/test/java/io/rsocket/frame/FrameHeaderCodecTest.java @@ -7,7 +7,7 @@ import io.netty.buffer.ByteBufAllocator; import org.junit.jupiter.api.Test; -class FrameHeaderFlyweightTest { +class FrameHeaderCodecTest { // Taken from spec private static final int FRAME_MAX_SIZE = 16_777_215; @@ -15,10 +15,10 @@ class FrameHeaderFlyweightTest { void typeAndFlag() { FrameType frameType = FrameType.REQUEST_FNF; int flags = 0b1110110111; - ByteBuf header = FrameHeaderFlyweight.encode(ByteBufAllocator.DEFAULT, 0, frameType, flags); + ByteBuf header = FrameHeaderCodec.encode(ByteBufAllocator.DEFAULT, 0, frameType, flags); - assertEquals(flags, FrameHeaderFlyweight.flags(header)); - assertEquals(frameType, FrameHeaderFlyweight.frameType(header)); + assertEquals(flags, FrameHeaderCodec.flags(header)); + assertEquals(frameType, FrameHeaderCodec.frameType(header)); header.release(); } @@ -26,11 +26,11 @@ void typeAndFlag() { void typeAndFlagTruncated() { FrameType frameType = FrameType.SETUP; int flags = 0b11110110111; // 1 bit too many - ByteBuf header = FrameHeaderFlyweight.encode(ByteBufAllocator.DEFAULT, 0, frameType, flags); + ByteBuf header = FrameHeaderCodec.encode(ByteBufAllocator.DEFAULT, 0, frameType, flags); - assertNotEquals(flags, FrameHeaderFlyweight.flags(header)); - assertEquals(flags & 0b0000_0011_1111_1111, FrameHeaderFlyweight.flags(header)); - assertEquals(frameType, FrameHeaderFlyweight.frameType(header)); + assertNotEquals(flags, FrameHeaderCodec.flags(header)); + assertEquals(flags & 0b0000_0011_1111_1111, FrameHeaderCodec.flags(header)); + assertEquals(frameType, FrameHeaderCodec.frameType(header)); header.release(); } } diff --git a/rsocket-core/src/test/java/io/rsocket/frame/RequestFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/frame/GenericFrameCodecTest.java similarity index 65% rename from rsocket-core/src/test/java/io/rsocket/frame/RequestFlyweightTest.java rename to rsocket-core/src/test/java/io/rsocket/frame/GenericFrameCodecTest.java index c19d4e1f4..ac19dc754 100644 --- a/rsocket-core/src/test/java/io/rsocket/frame/RequestFlyweightTest.java +++ b/rsocket-core/src/test/java/io/rsocket/frame/GenericFrameCodecTest.java @@ -9,11 +9,11 @@ import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; -class RequestFlyweightTest { +class GenericFrameCodecTest { @Test void testEncoding() { ByteBuf frame = - RequestStreamFrameFlyweight.encode( + RequestStreamFrameCodec.encode( ByteBufAllocator.DEFAULT, 1, false, @@ -21,7 +21,7 @@ void testEncoding() { Unpooled.copiedBuffer("md", StandardCharsets.UTF_8), Unpooled.copiedBuffer("d", StandardCharsets.UTF_8)); - frame = FrameLengthFlyweight.encode(ByteBufAllocator.DEFAULT, frame.readableBytes(), frame); + frame = FrameLengthCodec.encode(ByteBufAllocator.DEFAULT, frame.readableBytes(), frame); // Encoded FrameLength⌍ ⌌ Encoded Headers // | | ⌌ Encoded Request(1) // | | | ⌌Encoded Metadata Length @@ -37,7 +37,7 @@ void testEncoding() { @Test void testEncodingWithEmptyMetadata() { ByteBuf frame = - RequestStreamFrameFlyweight.encode( + RequestStreamFrameCodec.encode( ByteBufAllocator.DEFAULT, 1, false, @@ -45,7 +45,7 @@ void testEncodingWithEmptyMetadata() { Unpooled.EMPTY_BUFFER, Unpooled.copiedBuffer("d", StandardCharsets.UTF_8)); - frame = FrameLengthFlyweight.encode(ByteBufAllocator.DEFAULT, frame.readableBytes(), frame); + frame = FrameLengthCodec.encode(ByteBufAllocator.DEFAULT, frame.readableBytes(), frame); // Encoded FrameLength⌍ ⌌ Encoded Headers // | | ⌌ Encoded Request(1) // | | | ⌌Encoded Metadata Length (0) @@ -60,7 +60,7 @@ void testEncodingWithEmptyMetadata() { @Test void testEncodingWithNullMetadata() { ByteBuf frame = - RequestStreamFrameFlyweight.encode( + RequestStreamFrameCodec.encode( ByteBufAllocator.DEFAULT, 1, false, @@ -68,7 +68,7 @@ void testEncodingWithNullMetadata() { null, Unpooled.copiedBuffer("d", StandardCharsets.UTF_8)); - frame = FrameLengthFlyweight.encode(ByteBufAllocator.DEFAULT, frame.readableBytes(), frame); + frame = FrameLengthCodec.encode(ByteBufAllocator.DEFAULT, frame.readableBytes(), frame); // Encoded FrameLength⌍ ⌌ Encoded Headers // | | ⌌ Encoded Request(1) @@ -83,18 +83,17 @@ void testEncodingWithNullMetadata() { @Test void requestResponseDataMetadata() { ByteBuf request = - RequestResponseFrameFlyweight.encode( + RequestResponseFrameCodec.encode( ByteBufAllocator.DEFAULT, 1, false, Unpooled.copiedBuffer("md", StandardCharsets.UTF_8), Unpooled.copiedBuffer("d", StandardCharsets.UTF_8)); - String data = RequestResponseFrameFlyweight.data(request).toString(StandardCharsets.UTF_8); - String metadata = - RequestResponseFrameFlyweight.metadata(request).toString(StandardCharsets.UTF_8); + String data = RequestResponseFrameCodec.data(request).toString(StandardCharsets.UTF_8); + String metadata = RequestResponseFrameCodec.metadata(request).toString(StandardCharsets.UTF_8); - assertTrue(FrameHeaderFlyweight.hasMetadata(request)); + assertTrue(FrameHeaderCodec.hasMetadata(request)); assertEquals("d", data); assertEquals("md", metadata); request.release(); @@ -103,17 +102,17 @@ void requestResponseDataMetadata() { @Test void requestResponseData() { ByteBuf request = - RequestResponseFrameFlyweight.encode( + RequestResponseFrameCodec.encode( ByteBufAllocator.DEFAULT, 1, false, null, Unpooled.copiedBuffer("d", StandardCharsets.UTF_8)); - String data = RequestResponseFrameFlyweight.data(request).toString(StandardCharsets.UTF_8); - ByteBuf metadata = RequestResponseFrameFlyweight.metadata(request); + String data = RequestResponseFrameCodec.data(request).toString(StandardCharsets.UTF_8); + ByteBuf metadata = RequestResponseFrameCodec.metadata(request); - assertFalse(FrameHeaderFlyweight.hasMetadata(request)); + assertFalse(FrameHeaderCodec.hasMetadata(request)); assertEquals("d", data); assertNull(metadata); request.release(); @@ -122,18 +121,17 @@ void requestResponseData() { @Test void requestResponseMetadata() { ByteBuf request = - RequestResponseFrameFlyweight.encode( + RequestResponseFrameCodec.encode( ByteBufAllocator.DEFAULT, 1, false, Unpooled.copiedBuffer("md", StandardCharsets.UTF_8), Unpooled.EMPTY_BUFFER); - ByteBuf data = RequestResponseFrameFlyweight.data(request); - String metadata = - RequestResponseFrameFlyweight.metadata(request).toString(StandardCharsets.UTF_8); + ByteBuf data = RequestResponseFrameCodec.data(request); + String metadata = RequestResponseFrameCodec.metadata(request).toString(StandardCharsets.UTF_8); - assertTrue(FrameHeaderFlyweight.hasMetadata(request)); + assertTrue(FrameHeaderCodec.hasMetadata(request)); assertTrue(data.readableBytes() == 0); assertEquals("md", metadata); request.release(); @@ -142,7 +140,7 @@ void requestResponseMetadata() { @Test void requestStreamDataMetadata() { ByteBuf request = - RequestStreamFrameFlyweight.encode( + RequestStreamFrameCodec.encode( ByteBufAllocator.DEFAULT, 1, false, @@ -150,12 +148,11 @@ void requestStreamDataMetadata() { Unpooled.copiedBuffer("md", StandardCharsets.UTF_8), Unpooled.copiedBuffer("d", StandardCharsets.UTF_8)); - long actualRequest = RequestStreamFrameFlyweight.initialRequestN(request); - String data = RequestStreamFrameFlyweight.data(request).toString(StandardCharsets.UTF_8); - String metadata = - RequestStreamFrameFlyweight.metadata(request).toString(StandardCharsets.UTF_8); + long actualRequest = RequestStreamFrameCodec.initialRequestN(request); + String data = RequestStreamFrameCodec.data(request).toString(StandardCharsets.UTF_8); + String metadata = RequestStreamFrameCodec.metadata(request).toString(StandardCharsets.UTF_8); - assertTrue(FrameHeaderFlyweight.hasMetadata(request)); + assertTrue(FrameHeaderCodec.hasMetadata(request)); assertEquals(Long.MAX_VALUE, actualRequest); assertEquals("md", metadata); assertEquals("d", data); @@ -165,7 +162,7 @@ void requestStreamDataMetadata() { @Test void requestStreamData() { ByteBuf request = - RequestStreamFrameFlyweight.encode( + RequestStreamFrameCodec.encode( ByteBufAllocator.DEFAULT, 1, false, @@ -173,11 +170,11 @@ void requestStreamData() { null, Unpooled.copiedBuffer("d", StandardCharsets.UTF_8)); - long actualRequest = RequestStreamFrameFlyweight.initialRequestN(request); - String data = RequestStreamFrameFlyweight.data(request).toString(StandardCharsets.UTF_8); - ByteBuf metadata = RequestStreamFrameFlyweight.metadata(request); + long actualRequest = RequestStreamFrameCodec.initialRequestN(request); + String data = RequestStreamFrameCodec.data(request).toString(StandardCharsets.UTF_8); + ByteBuf metadata = RequestStreamFrameCodec.metadata(request); - assertFalse(FrameHeaderFlyweight.hasMetadata(request)); + assertFalse(FrameHeaderCodec.hasMetadata(request)); assertEquals(42L, actualRequest); assertNull(metadata); assertEquals("d", data); @@ -187,7 +184,7 @@ void requestStreamData() { @Test void requestStreamMetadata() { ByteBuf request = - RequestStreamFrameFlyweight.encode( + RequestStreamFrameCodec.encode( ByteBufAllocator.DEFAULT, 1, false, @@ -195,12 +192,11 @@ void requestStreamMetadata() { Unpooled.copiedBuffer("md", StandardCharsets.UTF_8), Unpooled.EMPTY_BUFFER); - long actualRequest = RequestStreamFrameFlyweight.initialRequestN(request); - ByteBuf data = RequestStreamFrameFlyweight.data(request); - String metadata = - RequestStreamFrameFlyweight.metadata(request).toString(StandardCharsets.UTF_8); + long actualRequest = RequestStreamFrameCodec.initialRequestN(request); + ByteBuf data = RequestStreamFrameCodec.data(request); + String metadata = RequestStreamFrameCodec.metadata(request).toString(StandardCharsets.UTF_8); - assertTrue(FrameHeaderFlyweight.hasMetadata(request)); + assertTrue(FrameHeaderCodec.hasMetadata(request)); assertEquals(42L, actualRequest); assertTrue(data.readableBytes() == 0); assertEquals("md", metadata); @@ -210,18 +206,18 @@ void requestStreamMetadata() { @Test void requestFnfDataAndMetadata() { ByteBuf request = - RequestFireAndForgetFrameFlyweight.encode( + RequestFireAndForgetFrameCodec.encode( ByteBufAllocator.DEFAULT, 1, false, Unpooled.copiedBuffer("md", StandardCharsets.UTF_8), Unpooled.copiedBuffer("d", StandardCharsets.UTF_8)); - String data = RequestFireAndForgetFrameFlyweight.data(request).toString(StandardCharsets.UTF_8); + String data = RequestFireAndForgetFrameCodec.data(request).toString(StandardCharsets.UTF_8); String metadata = - RequestFireAndForgetFrameFlyweight.metadata(request).toString(StandardCharsets.UTF_8); + RequestFireAndForgetFrameCodec.metadata(request).toString(StandardCharsets.UTF_8); - assertTrue(FrameHeaderFlyweight.hasMetadata(request)); + assertTrue(FrameHeaderCodec.hasMetadata(request)); assertEquals("d", data); assertEquals("md", metadata); request.release(); @@ -230,17 +226,17 @@ void requestFnfDataAndMetadata() { @Test void requestFnfData() { ByteBuf request = - RequestFireAndForgetFrameFlyweight.encode( + RequestFireAndForgetFrameCodec.encode( ByteBufAllocator.DEFAULT, 1, false, null, Unpooled.copiedBuffer("d", StandardCharsets.UTF_8)); - String data = RequestFireAndForgetFrameFlyweight.data(request).toString(StandardCharsets.UTF_8); - ByteBuf metadata = RequestFireAndForgetFrameFlyweight.metadata(request); + String data = RequestFireAndForgetFrameCodec.data(request).toString(StandardCharsets.UTF_8); + ByteBuf metadata = RequestFireAndForgetFrameCodec.metadata(request); - assertFalse(FrameHeaderFlyweight.hasMetadata(request)); + assertFalse(FrameHeaderCodec.hasMetadata(request)); assertEquals("d", data); assertNull(metadata); request.release(); @@ -249,18 +245,18 @@ void requestFnfData() { @Test void requestFnfMetadata() { ByteBuf request = - RequestFireAndForgetFrameFlyweight.encode( + RequestFireAndForgetFrameCodec.encode( ByteBufAllocator.DEFAULT, 1, false, Unpooled.copiedBuffer("md", StandardCharsets.UTF_8), Unpooled.EMPTY_BUFFER); - ByteBuf data = RequestFireAndForgetFrameFlyweight.data(request); + ByteBuf data = RequestFireAndForgetFrameCodec.data(request); String metadata = - RequestFireAndForgetFrameFlyweight.metadata(request).toString(StandardCharsets.UTF_8); + RequestFireAndForgetFrameCodec.metadata(request).toString(StandardCharsets.UTF_8); - assertTrue(FrameHeaderFlyweight.hasMetadata(request)); + assertTrue(FrameHeaderCodec.hasMetadata(request)); assertEquals("md", metadata); assertTrue(data.readableBytes() == 0); request.release(); diff --git a/rsocket-core/src/test/java/io/rsocket/frame/KeepaliveFrameFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/frame/KeepaliveFrameFlyweightTest.java index eb55e89cd..bc013e024 100644 --- a/rsocket-core/src/test/java/io/rsocket/frame/KeepaliveFrameFlyweightTest.java +++ b/rsocket-core/src/test/java/io/rsocket/frame/KeepaliveFrameFlyweightTest.java @@ -14,18 +14,18 @@ class KeepaliveFrameFlyweightTest { @Test void canReadData() { ByteBuf data = Unpooled.wrappedBuffer(new byte[] {5, 4, 3}); - ByteBuf frame = KeepAliveFrameFlyweight.encode(ByteBufAllocator.DEFAULT, true, 0, data); - assertTrue(KeepAliveFrameFlyweight.respondFlag(frame)); - assertEquals(data, KeepAliveFrameFlyweight.data(frame)); + ByteBuf frame = KeepAliveFrameCodec.encode(ByteBufAllocator.DEFAULT, true, 0, data); + assertTrue(KeepAliveFrameCodec.respondFlag(frame)); + assertEquals(data, KeepAliveFrameCodec.data(frame)); frame.release(); } @Test void testEncoding() { ByteBuf frame = - KeepAliveFrameFlyweight.encode( + KeepAliveFrameCodec.encode( ByteBufAllocator.DEFAULT, true, 0, Unpooled.copiedBuffer("d", StandardCharsets.UTF_8)); - frame = FrameLengthFlyweight.encode(ByteBufAllocator.DEFAULT, frame.readableBytes(), frame); + frame = FrameLengthCodec.encode(ByteBufAllocator.DEFAULT, frame.readableBytes(), frame); assertEquals("00000f000000000c80000000000000000064", ByteBufUtil.hexDump(frame)); frame.release(); } diff --git a/rsocket-core/src/test/java/io/rsocket/frame/LeaseFrameCodecTest.java b/rsocket-core/src/test/java/io/rsocket/frame/LeaseFrameCodecTest.java new file mode 100644 index 000000000..448b5003b --- /dev/null +++ b/rsocket-core/src/test/java/io/rsocket/frame/LeaseFrameCodecTest.java @@ -0,0 +1,42 @@ +package io.rsocket.frame; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class LeaseFrameCodecTest { + + @Test + void leaseMetadata() { + ByteBuf metadata = bytebuf("md"); + int ttl = 1; + int numRequests = 42; + ByteBuf lease = LeaseFrameCodec.encode(ByteBufAllocator.DEFAULT, ttl, numRequests, metadata); + + Assertions.assertTrue(FrameHeaderCodec.hasMetadata(lease)); + Assertions.assertEquals(ttl, LeaseFrameCodec.ttl(lease)); + Assertions.assertEquals(numRequests, LeaseFrameCodec.numRequests(lease)); + Assertions.assertEquals(metadata, LeaseFrameCodec.metadata(lease)); + lease.release(); + } + + @Test + void leaseAbsentMetadata() { + int ttl = 1; + int numRequests = 42; + ByteBuf lease = LeaseFrameCodec.encode(ByteBufAllocator.DEFAULT, ttl, numRequests, null); + + Assertions.assertFalse(FrameHeaderCodec.hasMetadata(lease)); + Assertions.assertEquals(ttl, LeaseFrameCodec.ttl(lease)); + Assertions.assertEquals(numRequests, LeaseFrameCodec.numRequests(lease)); + Assertions.assertEquals(0, LeaseFrameCodec.metadata(lease).readableBytes()); + lease.release(); + } + + private static ByteBuf bytebuf(String str) { + return Unpooled.copiedBuffer(str, StandardCharsets.UTF_8); + } +} diff --git a/rsocket-core/src/test/java/io/rsocket/frame/LeaseFrameFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/frame/LeaseFrameFlyweightTest.java deleted file mode 100644 index 0fc0c112b..000000000 --- a/rsocket-core/src/test/java/io/rsocket/frame/LeaseFrameFlyweightTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package io.rsocket.frame; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.Unpooled; -import java.nio.charset.StandardCharsets; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class LeaseFrameFlyweightTest { - - @Test - void leaseMetadata() { - ByteBuf metadata = bytebuf("md"); - int ttl = 1; - int numRequests = 42; - ByteBuf lease = - LeaseFrameFlyweight.encode(ByteBufAllocator.DEFAULT, ttl, numRequests, metadata); - - Assertions.assertTrue(FrameHeaderFlyweight.hasMetadata(lease)); - Assertions.assertEquals(ttl, LeaseFrameFlyweight.ttl(lease)); - Assertions.assertEquals(numRequests, LeaseFrameFlyweight.numRequests(lease)); - Assertions.assertEquals(metadata, LeaseFrameFlyweight.metadata(lease)); - lease.release(); - } - - @Test - void leaseAbsentMetadata() { - int ttl = 1; - int numRequests = 42; - ByteBuf lease = LeaseFrameFlyweight.encode(ByteBufAllocator.DEFAULT, ttl, numRequests, null); - - Assertions.assertFalse(FrameHeaderFlyweight.hasMetadata(lease)); - Assertions.assertEquals(ttl, LeaseFrameFlyweight.ttl(lease)); - Assertions.assertEquals(numRequests, LeaseFrameFlyweight.numRequests(lease)); - Assertions.assertEquals(0, LeaseFrameFlyweight.metadata(lease).readableBytes()); - lease.release(); - } - - private static ByteBuf bytebuf(String str) { - return Unpooled.copiedBuffer(str, StandardCharsets.UTF_8); - } -} diff --git a/rsocket-core/src/test/java/io/rsocket/frame/PayloadFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/frame/PayloadFlyweightTest.java index 439d23c15..aecbb31ce 100644 --- a/rsocket-core/src/test/java/io/rsocket/frame/PayloadFlyweightTest.java +++ b/rsocket-core/src/test/java/io/rsocket/frame/PayloadFlyweightTest.java @@ -15,10 +15,9 @@ public class PayloadFlyweightTest { void nextCompleteDataMetadata() { Payload payload = DefaultPayload.create("d", "md"); ByteBuf nextComplete = - PayloadFrameFlyweight.encodeNextCompleteReleasingPayload( - ByteBufAllocator.DEFAULT, 1, payload); - String data = PayloadFrameFlyweight.data(nextComplete).toString(StandardCharsets.UTF_8); - String metadata = PayloadFrameFlyweight.metadata(nextComplete).toString(StandardCharsets.UTF_8); + PayloadFrameCodec.encodeNextCompleteReleasingPayload(ByteBufAllocator.DEFAULT, 1, payload); + String data = PayloadFrameCodec.data(nextComplete).toString(StandardCharsets.UTF_8); + String metadata = PayloadFrameCodec.metadata(nextComplete).toString(StandardCharsets.UTF_8); Assertions.assertEquals("d", data); Assertions.assertEquals("md", metadata); nextComplete.release(); @@ -28,10 +27,9 @@ void nextCompleteDataMetadata() { void nextCompleteData() { Payload payload = DefaultPayload.create("d"); ByteBuf nextComplete = - PayloadFrameFlyweight.encodeNextCompleteReleasingPayload( - ByteBufAllocator.DEFAULT, 1, payload); - String data = PayloadFrameFlyweight.data(nextComplete).toString(StandardCharsets.UTF_8); - ByteBuf metadata = PayloadFrameFlyweight.metadata(nextComplete); + PayloadFrameCodec.encodeNextCompleteReleasingPayload(ByteBufAllocator.DEFAULT, 1, payload); + String data = PayloadFrameCodec.data(nextComplete).toString(StandardCharsets.UTF_8); + ByteBuf metadata = PayloadFrameCodec.metadata(nextComplete); Assertions.assertEquals("d", data); Assertions.assertNull(metadata); nextComplete.release(); @@ -44,10 +42,9 @@ void nextCompleteMetaData() { Unpooled.EMPTY_BUFFER, Unpooled.wrappedBuffer("md".getBytes(StandardCharsets.UTF_8))); ByteBuf nextComplete = - PayloadFrameFlyweight.encodeNextCompleteReleasingPayload( - ByteBufAllocator.DEFAULT, 1, payload); - ByteBuf data = PayloadFrameFlyweight.data(nextComplete); - String metadata = PayloadFrameFlyweight.metadata(nextComplete).toString(StandardCharsets.UTF_8); + PayloadFrameCodec.encodeNextCompleteReleasingPayload(ByteBufAllocator.DEFAULT, 1, payload); + ByteBuf data = PayloadFrameCodec.data(nextComplete); + String metadata = PayloadFrameCodec.metadata(nextComplete).toString(StandardCharsets.UTF_8); Assertions.assertTrue(data.readableBytes() == 0); Assertions.assertEquals("md", metadata); nextComplete.release(); @@ -57,9 +54,9 @@ void nextCompleteMetaData() { void nextDataMetadata() { Payload payload = DefaultPayload.create("d", "md"); ByteBuf next = - PayloadFrameFlyweight.encodeNextReleasingPayload(ByteBufAllocator.DEFAULT, 1, payload); - String data = PayloadFrameFlyweight.data(next).toString(StandardCharsets.UTF_8); - String metadata = PayloadFrameFlyweight.metadata(next).toString(StandardCharsets.UTF_8); + PayloadFrameCodec.encodeNextReleasingPayload(ByteBufAllocator.DEFAULT, 1, payload); + String data = PayloadFrameCodec.data(next).toString(StandardCharsets.UTF_8); + String metadata = PayloadFrameCodec.metadata(next).toString(StandardCharsets.UTF_8); Assertions.assertEquals("d", data); Assertions.assertEquals("md", metadata); next.release(); @@ -69,9 +66,9 @@ void nextDataMetadata() { void nextData() { Payload payload = DefaultPayload.create("d"); ByteBuf next = - PayloadFrameFlyweight.encodeNextReleasingPayload(ByteBufAllocator.DEFAULT, 1, payload); - String data = PayloadFrameFlyweight.data(next).toString(StandardCharsets.UTF_8); - ByteBuf metadata = PayloadFrameFlyweight.metadata(next); + PayloadFrameCodec.encodeNextReleasingPayload(ByteBufAllocator.DEFAULT, 1, payload); + String data = PayloadFrameCodec.data(next).toString(StandardCharsets.UTF_8); + ByteBuf metadata = PayloadFrameCodec.metadata(next); Assertions.assertEquals("d", data); Assertions.assertNull(metadata); next.release(); @@ -81,9 +78,9 @@ void nextData() { void nextDataEmptyMetadata() { Payload payload = DefaultPayload.create("d".getBytes(), new byte[0]); ByteBuf next = - PayloadFrameFlyweight.encodeNextReleasingPayload(ByteBufAllocator.DEFAULT, 1, payload); - String data = PayloadFrameFlyweight.data(next).toString(StandardCharsets.UTF_8); - ByteBuf metadata = PayloadFrameFlyweight.metadata(next); + PayloadFrameCodec.encodeNextReleasingPayload(ByteBufAllocator.DEFAULT, 1, payload); + String data = PayloadFrameCodec.data(next).toString(StandardCharsets.UTF_8); + ByteBuf metadata = PayloadFrameCodec.metadata(next); Assertions.assertEquals("d", data); Assertions.assertEquals(metadata.readableBytes(), 0); next.release(); diff --git a/rsocket-core/src/test/java/io/rsocket/frame/RequestNFrameFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/frame/RequestNFrameCodecTest.java similarity index 63% rename from rsocket-core/src/test/java/io/rsocket/frame/RequestNFrameFlyweightTest.java rename to rsocket-core/src/test/java/io/rsocket/frame/RequestNFrameCodecTest.java index 4411b98c9..e38258040 100644 --- a/rsocket-core/src/test/java/io/rsocket/frame/RequestNFrameFlyweightTest.java +++ b/rsocket-core/src/test/java/io/rsocket/frame/RequestNFrameCodecTest.java @@ -7,12 +7,12 @@ import io.netty.buffer.ByteBufUtil; import org.junit.jupiter.api.Test; -class RequestNFrameFlyweightTest { +class RequestNFrameCodecTest { @Test void testEncoding() { - ByteBuf frame = RequestNFrameFlyweight.encode(ByteBufAllocator.DEFAULT, 1, 5); + ByteBuf frame = RequestNFrameCodec.encode(ByteBufAllocator.DEFAULT, 1, 5); - frame = FrameLengthFlyweight.encode(ByteBufAllocator.DEFAULT, frame.readableBytes(), frame); + frame = FrameLengthCodec.encode(ByteBufAllocator.DEFAULT, frame.readableBytes(), frame); assertEquals("00000a00000001200000000005", ByteBufUtil.hexDump(frame)); frame.release(); } diff --git a/rsocket-core/src/test/java/io/rsocket/frame/ResumeFrameFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/frame/ResumeFrameCodecTest.java similarity index 68% rename from rsocket-core/src/test/java/io/rsocket/frame/ResumeFrameFlyweightTest.java rename to rsocket-core/src/test/java/io/rsocket/frame/ResumeFrameCodecTest.java index f8b481f05..fe05335d2 100644 --- a/rsocket-core/src/test/java/io/rsocket/frame/ResumeFrameFlyweightTest.java +++ b/rsocket-core/src/test/java/io/rsocket/frame/ResumeFrameCodecTest.java @@ -23,19 +23,18 @@ import org.junit.Assert; import org.junit.jupiter.api.Test; -public class ResumeFrameFlyweightTest { +public class ResumeFrameCodecTest { @Test void testEncoding() { byte[] tokenBytes = new byte[65000]; Arrays.fill(tokenBytes, (byte) 1); ByteBuf token = Unpooled.wrappedBuffer(tokenBytes); - ByteBuf byteBuf = ResumeFrameFlyweight.encode(ByteBufAllocator.DEFAULT, token, 21, 12); - Assert.assertEquals( - ResumeFrameFlyweight.CURRENT_VERSION, ResumeFrameFlyweight.version(byteBuf)); - Assert.assertEquals(token, ResumeFrameFlyweight.token(byteBuf)); - Assert.assertEquals(21, ResumeFrameFlyweight.lastReceivedServerPos(byteBuf)); - Assert.assertEquals(12, ResumeFrameFlyweight.firstAvailableClientPos(byteBuf)); + ByteBuf byteBuf = ResumeFrameCodec.encode(ByteBufAllocator.DEFAULT, token, 21, 12); + Assert.assertEquals(ResumeFrameCodec.CURRENT_VERSION, ResumeFrameCodec.version(byteBuf)); + Assert.assertEquals(token, ResumeFrameCodec.token(byteBuf)); + Assert.assertEquals(21, ResumeFrameCodec.lastReceivedServerPos(byteBuf)); + Assert.assertEquals(12, ResumeFrameCodec.firstAvailableClientPos(byteBuf)); byteBuf.release(); } } diff --git a/rsocket-core/src/test/java/io/rsocket/frame/ResumeOkFrameFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/frame/ResumeOkFrameCodecTest.java similarity index 51% rename from rsocket-core/src/test/java/io/rsocket/frame/ResumeOkFrameFlyweightTest.java rename to rsocket-core/src/test/java/io/rsocket/frame/ResumeOkFrameCodecTest.java index c73409ffa..33dd8eb70 100644 --- a/rsocket-core/src/test/java/io/rsocket/frame/ResumeOkFrameFlyweightTest.java +++ b/rsocket-core/src/test/java/io/rsocket/frame/ResumeOkFrameCodecTest.java @@ -5,12 +5,12 @@ import org.junit.Assert; import org.junit.Test; -public class ResumeOkFrameFlyweightTest { +public class ResumeOkFrameCodecTest { @Test public void testEncoding() { - ByteBuf byteBuf = ResumeOkFrameFlyweight.encode(ByteBufAllocator.DEFAULT, 42); - Assert.assertEquals(42, ResumeOkFrameFlyweight.lastReceivedClientPos(byteBuf)); + ByteBuf byteBuf = ResumeOkFrameCodec.encode(ByteBufAllocator.DEFAULT, 42); + Assert.assertEquals(42, ResumeOkFrameCodec.lastReceivedClientPos(byteBuf)); byteBuf.release(); } } diff --git a/rsocket-core/src/test/java/io/rsocket/frame/SetupFrameCodecTest.java b/rsocket-core/src/test/java/io/rsocket/frame/SetupFrameCodecTest.java new file mode 100644 index 000000000..f7d649972 --- /dev/null +++ b/rsocket-core/src/test/java/io/rsocket/frame/SetupFrameCodecTest.java @@ -0,0 +1,57 @@ +package io.rsocket.frame; + +import static org.junit.jupiter.api.Assertions.*; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; +import io.rsocket.Payload; +import io.rsocket.util.DefaultPayload; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +class SetupFrameCodecTest { + @Test + void testEncodingNoResume() { + ByteBuf metadata = Unpooled.wrappedBuffer(new byte[] {1, 2, 3, 4}); + ByteBuf data = Unpooled.wrappedBuffer(new byte[] {5, 4, 3}); + Payload payload = DefaultPayload.create(data, metadata); + ByteBuf frame = + SetupFrameCodec.encode( + ByteBufAllocator.DEFAULT, false, 5, 500, "metadata_type", "data_type", payload); + + assertEquals(FrameType.SETUP, FrameHeaderCodec.frameType(frame)); + assertFalse(SetupFrameCodec.resumeEnabled(frame)); + assertNull(SetupFrameCodec.resumeToken(frame)); + assertEquals("metadata_type", SetupFrameCodec.metadataMimeType(frame)); + assertEquals("data_type", SetupFrameCodec.dataMimeType(frame)); + assertEquals(metadata, SetupFrameCodec.metadata(frame)); + assertEquals(data, SetupFrameCodec.data(frame)); + assertEquals(SetupFrameCodec.CURRENT_VERSION, SetupFrameCodec.version(frame)); + frame.release(); + } + + @Test + void testEncodingResume() { + byte[] tokenBytes = new byte[65000]; + Arrays.fill(tokenBytes, (byte) 1); + ByteBuf metadata = Unpooled.wrappedBuffer(new byte[] {1, 2, 3, 4}); + ByteBuf data = Unpooled.wrappedBuffer(new byte[] {5, 4, 3}); + Payload payload = DefaultPayload.create(data, metadata); + ByteBuf token = Unpooled.wrappedBuffer(tokenBytes); + ByteBuf frame = + SetupFrameCodec.encode( + ByteBufAllocator.DEFAULT, true, 5, 500, token, "metadata_type", "data_type", payload); + + assertEquals(FrameType.SETUP, FrameHeaderCodec.frameType(frame)); + assertTrue(SetupFrameCodec.honorLease(frame)); + assertTrue(SetupFrameCodec.resumeEnabled(frame)); + assertEquals(token, SetupFrameCodec.resumeToken(frame)); + assertEquals("metadata_type", SetupFrameCodec.metadataMimeType(frame)); + assertEquals("data_type", SetupFrameCodec.dataMimeType(frame)); + assertEquals(metadata, SetupFrameCodec.metadata(frame)); + assertEquals(data, SetupFrameCodec.data(frame)); + assertEquals(SetupFrameCodec.CURRENT_VERSION, SetupFrameCodec.version(frame)); + frame.release(); + } +} diff --git a/rsocket-core/src/test/java/io/rsocket/frame/SetupFrameFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/frame/SetupFrameFlyweightTest.java deleted file mode 100644 index 128b3ff84..000000000 --- a/rsocket-core/src/test/java/io/rsocket/frame/SetupFrameFlyweightTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package io.rsocket.frame; - -import static org.junit.jupiter.api.Assertions.*; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.Unpooled; -import io.rsocket.Payload; -import io.rsocket.util.DefaultPayload; -import java.util.Arrays; -import org.junit.jupiter.api.Test; - -class SetupFrameFlyweightTest { - @Test - void testEncodingNoResume() { - ByteBuf metadata = Unpooled.wrappedBuffer(new byte[] {1, 2, 3, 4}); - ByteBuf data = Unpooled.wrappedBuffer(new byte[] {5, 4, 3}); - Payload payload = DefaultPayload.create(data, metadata); - ByteBuf frame = - SetupFrameFlyweight.encode( - ByteBufAllocator.DEFAULT, false, 5, 500, "metadata_type", "data_type", payload); - - assertEquals(FrameType.SETUP, FrameHeaderFlyweight.frameType(frame)); - assertFalse(SetupFrameFlyweight.resumeEnabled(frame)); - assertNull(SetupFrameFlyweight.resumeToken(frame)); - assertEquals("metadata_type", SetupFrameFlyweight.metadataMimeType(frame)); - assertEquals("data_type", SetupFrameFlyweight.dataMimeType(frame)); - assertEquals(metadata, SetupFrameFlyweight.metadata(frame)); - assertEquals(data, SetupFrameFlyweight.data(frame)); - assertEquals(SetupFrameFlyweight.CURRENT_VERSION, SetupFrameFlyweight.version(frame)); - frame.release(); - } - - @Test - void testEncodingResume() { - byte[] tokenBytes = new byte[65000]; - Arrays.fill(tokenBytes, (byte) 1); - ByteBuf metadata = Unpooled.wrappedBuffer(new byte[] {1, 2, 3, 4}); - ByteBuf data = Unpooled.wrappedBuffer(new byte[] {5, 4, 3}); - Payload payload = DefaultPayload.create(data, metadata); - ByteBuf token = Unpooled.wrappedBuffer(tokenBytes); - ByteBuf frame = - SetupFrameFlyweight.encode( - ByteBufAllocator.DEFAULT, true, 5, 500, token, "metadata_type", "data_type", payload); - - assertEquals(FrameType.SETUP, FrameHeaderFlyweight.frameType(frame)); - assertTrue(SetupFrameFlyweight.honorLease(frame)); - assertTrue(SetupFrameFlyweight.resumeEnabled(frame)); - assertEquals(token, SetupFrameFlyweight.resumeToken(frame)); - assertEquals("metadata_type", SetupFrameFlyweight.metadataMimeType(frame)); - assertEquals("data_type", SetupFrameFlyweight.dataMimeType(frame)); - assertEquals(metadata, SetupFrameFlyweight.metadata(frame)); - assertEquals(data, SetupFrameFlyweight.data(frame)); - assertEquals(SetupFrameFlyweight.CURRENT_VERSION, SetupFrameFlyweight.version(frame)); - frame.release(); - } -} diff --git a/rsocket-core/src/test/java/io/rsocket/frame/VersionFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/frame/VersionCodecTest.java similarity index 58% rename from rsocket-core/src/test/java/io/rsocket/frame/VersionFlyweightTest.java rename to rsocket-core/src/test/java/io/rsocket/frame/VersionCodecTest.java index 3f311c7ef..be7fb837b 100644 --- a/rsocket-core/src/test/java/io/rsocket/frame/VersionFlyweightTest.java +++ b/rsocket-core/src/test/java/io/rsocket/frame/VersionCodecTest.java @@ -20,29 +20,29 @@ import org.junit.jupiter.api.Test; -public class VersionFlyweightTest { +public class VersionCodecTest { @Test public void simple() { - int version = VersionFlyweight.encode(1, 0); - assertEquals(1, VersionFlyweight.major(version)); - assertEquals(0, VersionFlyweight.minor(version)); + int version = VersionCodec.encode(1, 0); + assertEquals(1, VersionCodec.major(version)); + assertEquals(0, VersionCodec.minor(version)); assertEquals(0x00010000, version); - assertEquals("1.0", VersionFlyweight.toString(version)); + assertEquals("1.0", VersionCodec.toString(version)); } @Test public void complex() { - int version = VersionFlyweight.encode(0x1234, 0x5678); - assertEquals(0x1234, VersionFlyweight.major(version)); - assertEquals(0x5678, VersionFlyweight.minor(version)); + int version = VersionCodec.encode(0x1234, 0x5678); + assertEquals(0x1234, VersionCodec.major(version)); + assertEquals(0x5678, VersionCodec.minor(version)); assertEquals(0x12345678, version); - assertEquals("4660.22136", VersionFlyweight.toString(version)); + assertEquals("4660.22136", VersionCodec.toString(version)); } @Test public void noShortOverflow() { - int version = VersionFlyweight.encode(43210, 43211); - assertEquals(43210, VersionFlyweight.major(version)); - assertEquals(43211, VersionFlyweight.minor(version)); + int version = VersionCodec.encode(43210, 43211); + assertEquals(43210, VersionCodec.major(version)); + assertEquals(43211, VersionCodec.minor(version)); } } diff --git a/rsocket-core/src/test/java/io/rsocket/internal/ClientServerInputMultiplexerTest.java b/rsocket-core/src/test/java/io/rsocket/internal/ClientServerInputMultiplexerTest.java index efa962c48..63acc40aa 100644 --- a/rsocket-core/src/test/java/io/rsocket/internal/ClientServerInputMultiplexerTest.java +++ b/rsocket-core/src/test/java/io/rsocket/internal/ClientServerInputMultiplexerTest.java @@ -193,11 +193,11 @@ public void serverSplits() { } private ByteBuf resumeFrame() { - return ResumeFrameFlyweight.encode(allocator, Unpooled.EMPTY_BUFFER, 0, 0); + return ResumeFrameCodec.encode(allocator, Unpooled.EMPTY_BUFFER, 0, 0); } private ByteBuf setupFrame() { - return SetupFrameFlyweight.encode( + return SetupFrameCodec.encode( ByteBufAllocator.DEFAULT, false, 0, @@ -208,22 +208,22 @@ private ByteBuf setupFrame() { } private ByteBuf leaseFrame() { - return LeaseFrameFlyweight.encode(allocator, 1_000, 1, Unpooled.EMPTY_BUFFER); + return LeaseFrameCodec.encode(allocator, 1_000, 1, Unpooled.EMPTY_BUFFER); } private ByteBuf errorFrame(int i) { - return ErrorFrameFlyweight.encode(allocator, i, new Exception()); + return ErrorFrameCodec.encode(allocator, i, new Exception()); } private ByteBuf resumeOkFrame() { - return ResumeOkFrameFlyweight.encode(allocator, 0); + return ResumeOkFrameCodec.encode(allocator, 0); } private ByteBuf keepAliveFrame() { - return KeepAliveFrameFlyweight.encode(allocator, false, 0, Unpooled.EMPTY_BUFFER); + return KeepAliveFrameCodec.encode(allocator, false, 0, Unpooled.EMPTY_BUFFER); } private ByteBuf metadataPushFrame() { - return MetadataPushFrameFlyweight.encode(allocator, Unpooled.EMPTY_BUFFER); + return MetadataPushFrameCodec.encode(allocator, Unpooled.EMPTY_BUFFER); } } diff --git a/rsocket-micrometer/src/main/java/io/rsocket/micrometer/MicrometerDuplexConnection.java b/rsocket-micrometer/src/main/java/io/rsocket/micrometer/MicrometerDuplexConnection.java index 9904c2b24..c8b22382a 100644 --- a/rsocket-micrometer/src/main/java/io/rsocket/micrometer/MicrometerDuplexConnection.java +++ b/rsocket-micrometer/src/main/java/io/rsocket/micrometer/MicrometerDuplexConnection.java @@ -22,7 +22,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; -import io.rsocket.frame.FrameHeaderFlyweight; +import io.rsocket.frame.FrameHeaderCodec; import io.rsocket.frame.FrameType; import io.rsocket.plugins.DuplexConnectionInterceptor.Type; import java.util.Objects; @@ -191,7 +191,7 @@ private static Counter counter( @Override public void accept(ByteBuf frame) { - FrameType frameType = FrameHeaderFlyweight.frameType(frame); + FrameType frameType = FrameHeaderCodec.frameType(frame); switch (frameType) { case SETUP: diff --git a/rsocket-test/src/main/java/io/rsocket/test/TestFrames.java b/rsocket-test/src/main/java/io/rsocket/test/TestFrames.java index 60ff05124..1e66abc5e 100644 --- a/rsocket-test/src/main/java/io/rsocket/test/TestFrames.java +++ b/rsocket-test/src/main/java/io/rsocket/test/TestFrames.java @@ -33,71 +33,69 @@ private TestFrames() {} /** @return {@link ByteBuf} representing test instance of Cancel frame */ public static ByteBuf createTestCancelFrame() { - return CancelFrameFlyweight.encode(allocator, 1); + return CancelFrameCodec.encode(allocator, 1); } /** @return {@link ByteBuf} representing test instance of Error frame */ public static ByteBuf createTestErrorFrame() { - return ErrorFrameFlyweight.encode(allocator, 1, new RuntimeException()); + return ErrorFrameCodec.encode(allocator, 1, new RuntimeException()); } /** @return {@link ByteBuf} representing test instance of Extension frame */ public static ByteBuf createTestExtensionFrame() { - return ExtensionFrameFlyweight.encode( + return ExtensionFrameCodec.encode( allocator, 1, 1, Unpooled.EMPTY_BUFFER, Unpooled.EMPTY_BUFFER); } /** @return {@link ByteBuf} representing test instance of Keep-Alive frame */ public static ByteBuf createTestKeepaliveFrame() { - return KeepAliveFrameFlyweight.encode(allocator, false, 1, Unpooled.EMPTY_BUFFER); + return KeepAliveFrameCodec.encode(allocator, false, 1, Unpooled.EMPTY_BUFFER); } /** @return {@link ByteBuf} representing test instance of Lease frame */ public static ByteBuf createTestLeaseFrame() { - return LeaseFrameFlyweight.encode(allocator, 1, 1, null); + return LeaseFrameCodec.encode(allocator, 1, 1, null); } /** @return {@link ByteBuf} representing test instance of Metadata-Push frame */ public static ByteBuf createTestMetadataPushFrame() { - return MetadataPushFrameFlyweight.encode(allocator, Unpooled.EMPTY_BUFFER); + return MetadataPushFrameCodec.encode(allocator, Unpooled.EMPTY_BUFFER); } /** @return {@link ByteBuf} representing test instance of Payload frame */ public static ByteBuf createTestPayloadFrame() { - return PayloadFrameFlyweight.encode( - allocator, 1, false, true, false, null, Unpooled.EMPTY_BUFFER); + return PayloadFrameCodec.encode(allocator, 1, false, true, false, null, Unpooled.EMPTY_BUFFER); } /** @return {@link ByteBuf} representing test instance of Request-Channel frame */ public static ByteBuf createTestRequestChannelFrame() { - return RequestChannelFrameFlyweight.encode( + return RequestChannelFrameCodec.encode( allocator, 1, false, false, 1, null, Unpooled.EMPTY_BUFFER); } /** @return {@link ByteBuf} representing test instance of Fire-and-Forget frame */ public static ByteBuf createTestRequestFireAndForgetFrame() { - return RequestFireAndForgetFrameFlyweight.encode( - allocator, 1, false, null, Unpooled.EMPTY_BUFFER); + return RequestFireAndForgetFrameCodec.encode(allocator, 1, false, null, Unpooled.EMPTY_BUFFER); } /** @return {@link ByteBuf} representing test instance of Request-N frame */ public static ByteBuf createTestRequestNFrame() { - return RequestNFrameFlyweight.encode(allocator, 1, 1); + return RequestNFrameCodec.encode(allocator, 1, 1); } /** @return {@link ByteBuf} representing test instance of Request-Response frame */ public static ByteBuf createTestRequestResponseFrame() { - return RequestResponseFrameFlyweight.encodeReleasingPayload(allocator, 1, emptyPayload); + return RequestResponseFrameCodec.encodeReleasingPayload(allocator, 1, emptyPayload); } /** @return {@link ByteBuf} representing test instance of Request-Stream frame */ public static ByteBuf createTestRequestStreamFrame() { - return RequestStreamFrameFlyweight.encodeReleasingPayload(allocator, 1, 1L, emptyPayload); + return RequestStreamFrameCodec.encodeReleasingPayload(allocator, 1, 1L, emptyPayload); } /** @return {@link ByteBuf} representing test instance of Setup frame */ public static ByteBuf createTestSetupFrame() { - return SetupFrameFlyweight.encode( + return SetupFrameCodec.encode( allocator, false, 1, diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/RSocketLengthCodec.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/RSocketLengthCodec.java index 68d7ab175..e2c134653 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/RSocketLengthCodec.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/RSocketLengthCodec.java @@ -16,8 +16,8 @@ package io.rsocket.transport.netty; -import static io.rsocket.frame.FrameLengthFlyweight.FRAME_LENGTH_MASK; -import static io.rsocket.frame.FrameLengthFlyweight.FRAME_LENGTH_SIZE; +import static io.rsocket.frame.FrameLengthCodec.FRAME_LENGTH_MASK; +import static io.rsocket.frame.FrameLengthCodec.FRAME_LENGTH_SIZE; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/TcpDuplexConnection.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/TcpDuplexConnection.java index d71d6b356..b7081593c 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/TcpDuplexConnection.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/TcpDuplexConnection.java @@ -19,7 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.rsocket.DuplexConnection; -import io.rsocket.frame.FrameLengthFlyweight; +import io.rsocket.frame.FrameLengthCodec; import io.rsocket.internal.BaseDuplexConnection; import java.util.Objects; import org.reactivestreams.Publisher; @@ -88,7 +88,7 @@ public Mono send(Publisher frames) { private ByteBuf encode(ByteBuf frame) { if (encodeLength) { - return FrameLengthFlyweight.encode(alloc(), frame.readableBytes(), frame); + return FrameLengthCodec.encode(alloc(), frame.readableBytes(), frame); } else { return frame; } @@ -96,7 +96,7 @@ private ByteBuf encode(ByteBuf frame) { private ByteBuf decode(ByteBuf frame) { if (encodeLength) { - return FrameLengthFlyweight.frame(frame).retain(); + return FrameLengthCodec.frame(frame).retain(); } else { return frame; } diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java index b19621d46..6991728ca 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java @@ -16,7 +16,7 @@ package io.rsocket.transport.netty.client; -import static io.rsocket.frame.FrameLengthFlyweight.FRAME_LENGTH_MASK; +import static io.rsocket.frame.FrameLengthCodec.FRAME_LENGTH_MASK; import static io.rsocket.transport.netty.UriUtils.getPort; import static io.rsocket.transport.netty.UriUtils.isSecure; diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketRouteTransport.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketRouteTransport.java index 83cb010b7..bd19f18b0 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketRouteTransport.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketRouteTransport.java @@ -16,7 +16,7 @@ package io.rsocket.transport.netty.server; -import static io.rsocket.frame.FrameLengthFlyweight.FRAME_LENGTH_MASK; +import static io.rsocket.frame.FrameLengthCodec.FRAME_LENGTH_MASK; import io.rsocket.Closeable; import io.rsocket.DuplexConnection; diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketServerTransport.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketServerTransport.java index 4a0331c08..1a0b32cf0 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketServerTransport.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/WebsocketServerTransport.java @@ -16,7 +16,7 @@ package io.rsocket.transport.netty.server; -import static io.rsocket.frame.FrameLengthFlyweight.FRAME_LENGTH_MASK; +import static io.rsocket.frame.FrameLengthCodec.FRAME_LENGTH_MASK; import io.rsocket.DuplexConnection; import io.rsocket.fragmentation.FragmentationDuplexConnection; diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/client/WebsocketClientTransportTest.java b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/client/WebsocketClientTransportTest.java index 905f022f2..fc035c536 100644 --- a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/client/WebsocketClientTransportTest.java +++ b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/client/WebsocketClientTransportTest.java @@ -16,7 +16,7 @@ package io.rsocket.transport.netty.client; -import static io.rsocket.frame.FrameLengthFlyweight.FRAME_LENGTH_MASK; +import static io.rsocket.frame.FrameLengthCodec.FRAME_LENGTH_MASK; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNullPointerException; diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/server/WebsocketServerTransportTest.java b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/server/WebsocketServerTransportTest.java index 5a2986485..249a3e12a 100644 --- a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/server/WebsocketServerTransportTest.java +++ b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/server/WebsocketServerTransportTest.java @@ -16,7 +16,7 @@ package io.rsocket.transport.netty.server; -import static io.rsocket.frame.FrameLengthFlyweight.FRAME_LENGTH_MASK; +import static io.rsocket.frame.FrameLengthCodec.FRAME_LENGTH_MASK; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNullPointerException; From cec7a789c7a644c20b1c48815e5446c8099a8d13 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Fri, 8 May 2020 20:00:56 +0300 Subject: [PATCH 53/62] provides extra @NonNullApi annotation for all packages (#826) --- build.gradle | 1 - rsocket-core/build.gradle | 2 -- .../io/rsocket/ConnectionSetupPayload.java | 2 +- .../io/rsocket/core/RSocketConnector.java | 2 +- .../io/rsocket/core/RSocketRequester.java | 2 +- .../io/rsocket/core/RSocketResponder.java | 2 +- .../io/rsocket/core/StreamIdSupplier.java | 1 + .../exceptions/ApplicationErrorException.java | 2 +- .../rsocket/exceptions/CanceledException.java | 2 +- .../exceptions/ConnectionCloseException.java | 2 +- .../exceptions/ConnectionErrorException.java | 2 +- .../exceptions/CustomRSocketException.java | 2 +- .../rsocket/exceptions/InvalidException.java | 2 +- .../exceptions/InvalidSetupException.java | 2 +- .../rsocket/exceptions/RejectedException.java | 2 +- .../exceptions/RejectedResumeException.java | 2 +- .../exceptions/RejectedSetupException.java | 2 +- .../io/rsocket/exceptions/SetupException.java | 2 +- .../exceptions/UnsupportedSetupException.java | 2 +- .../FragmentationDuplexConnection.java | 2 +- .../fragmentation/FrameReassembler.java | 8 +++++-- .../io/rsocket/frame/ExtensionFrameCodec.java | 3 ++- .../java/io/rsocket/frame/FrameBodyCodec.java | 5 ++-- .../io/rsocket/frame/GenericFrameCodec.java | 6 +++-- .../io/rsocket/frame/LeaseFrameCodec.java | 6 ++--- .../rsocket/frame/MetadataPushFrameCodec.java | 4 ++++ .../io/rsocket/frame/PayloadFrameCodec.java | 6 +++-- .../frame/RequestChannelFrameCodec.java | 4 +++- .../frame/RequestFireAndForgetFrameCodec.java | 4 +++- .../frame/RequestResponseFrameCodec.java | 4 +++- .../frame/RequestStreamFrameCodec.java | 4 +++- .../io/rsocket/frame/SetupFrameCodec.java | 6 +++-- .../rsocket/frame/decoder/package-info.java | 24 +++++++++++++++++++ .../java/io/rsocket/frame/package-info.java | 3 +++ .../ClientServerInputMultiplexer.java | 1 + .../io/rsocket/internal/package-info.java | 4 +++- .../io/rsocket/keepalive/package-info.java | 4 +++- .../src/main/java/io/rsocket/lease/Lease.java | 4 +--- .../main/java/io/rsocket/lease/LeaseImpl.java | 4 +--- .../rsocket/lease/MissingLeaseException.java | 7 +++--- .../rsocket/lease/ResponderLeaseHandler.java | 2 +- .../java/io/rsocket/lease/package-info.java | 4 +++- .../io/rsocket/metadata/package-info.java | 3 +++ .../main/java/io/rsocket/package-info.java | 3 +++ .../java/io/rsocket/plugins/package-info.java | 3 +++ .../io/rsocket/resume/SessionManager.java | 2 +- .../java/io/rsocket/resume/package-info.java | 4 +++- .../io/rsocket/transport/package-info.java | 4 +++- .../java/io/rsocket/util/ByteBufPayload.java | 2 +- .../java/io/rsocket/util/DefaultPayload.java | 2 +- .../java/io/rsocket/util/package-info.java | 4 +++- .../core/ConnectionSetupPayloadTest.java | 2 +- .../core/RSocketRequesterSubscribersTest.java | 2 +- .../io/rsocket/frame/LeaseFrameCodecTest.java | 2 +- .../io/rsocket/frame/SetupFrameCodecTest.java | 2 +- .../rsocket/client/filter/package-info.java | 20 ++++++++++++++++ .../java/io/rsocket/client/package-info.java | 20 ++++++++++++++++ .../java/io/rsocket/stat/package-info.java | 20 ++++++++++++++++ rsocket-micrometer/build.gradle | 2 -- rsocket-test/build.gradle | 2 -- rsocket-transport-local/build.gradle | 2 -- rsocket-transport-netty/build.gradle | 2 -- .../netty/client/TcpClientTransport.java | 2 +- .../client/WebsocketClientTransport.java | 8 ++++--- .../netty/server/CloseableChannel.java | 2 +- .../netty/TcpSecureTransportTest.java | 4 ++-- .../netty/WebsocketSecureTransportTest.java | 4 ++-- 67 files changed, 199 insertions(+), 79 deletions(-) create mode 100644 rsocket-core/src/main/java/io/rsocket/frame/decoder/package-info.java create mode 100644 rsocket-load-balancer/src/main/java/io/rsocket/client/filter/package-info.java create mode 100644 rsocket-load-balancer/src/main/java/io/rsocket/client/package-info.java create mode 100644 rsocket-load-balancer/src/main/java/io/rsocket/stat/package-info.java diff --git a/build.gradle b/build.gradle index 2c7757e0f..f579b3ae0 100644 --- a/build.gradle +++ b/build.gradle @@ -62,7 +62,6 @@ subprojects { dependencies { dependency "ch.qos.logback:logback-classic:${ext['logback.version']}" - dependency "com.google.code.findbugs:jsr305:${ext['findbugs.version']}" dependency "io.netty:netty-tcnative-boringssl-static:${ext['netty-boringssl.version']}" dependency "io.micrometer:micrometer-core:${ext['micrometer.version']}" dependency "org.assertj:assertj-core:${ext['assertj.version']}" diff --git a/rsocket-core/build.gradle b/rsocket-core/build.gradle index ca2ae0e65..41adbd7a8 100644 --- a/rsocket-core/build.gradle +++ b/rsocket-core/build.gradle @@ -29,8 +29,6 @@ dependencies { implementation 'org.slf4j:slf4j-api' - compileOnly 'com.google.code.findbugs:jsr305' - testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.assertj:assertj-core' testImplementation 'org.junit.jupiter:junit-jupiter-api' diff --git a/rsocket-core/src/main/java/io/rsocket/ConnectionSetupPayload.java b/rsocket-core/src/main/java/io/rsocket/ConnectionSetupPayload.java index bd4582e2b..ece2aa9fa 100644 --- a/rsocket-core/src/main/java/io/rsocket/ConnectionSetupPayload.java +++ b/rsocket-core/src/main/java/io/rsocket/ConnectionSetupPayload.java @@ -19,7 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.util.AbstractReferenceCounted; import io.rsocket.core.DefaultConnectionSetupPayload; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; /** * Exposes information from the {@code SETUP} frame to a server, as well as to client responders. diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java index b69610f3f..38393c27d 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java @@ -41,10 +41,10 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Supplier; -import javax.annotation.Nullable; import reactor.core.Disposable; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; +import reactor.util.annotation.Nullable; import reactor.util.retry.Retry; /** diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index 846eaa922..068797d39 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -55,7 +55,6 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.Consumer; import java.util.function.Supplier; -import javax.annotation.Nullable; import org.reactivestreams.Processor; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; @@ -68,6 +67,7 @@ import reactor.core.publisher.SignalType; import reactor.core.publisher.UnicastProcessor; import reactor.core.scheduler.Scheduler; +import reactor.util.annotation.Nullable; import reactor.util.concurrent.Queues; /** diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java index b9d3ea794..dce182b49 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java @@ -39,7 +39,6 @@ import java.util.function.Consumer; import java.util.function.LongConsumer; import java.util.function.Supplier; -import javax.annotation.Nullable; import org.reactivestreams.Processor; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; @@ -47,6 +46,7 @@ import reactor.core.Disposable; import reactor.core.Exceptions; import reactor.core.publisher.*; +import reactor.util.annotation.Nullable; import reactor.util.concurrent.Queues; /** Responder side of RSocket. Receives {@link ByteBuf}s from a peer's {@link RSocketRequester} */ diff --git a/rsocket-core/src/main/java/io/rsocket/core/StreamIdSupplier.java b/rsocket-core/src/main/java/io/rsocket/core/StreamIdSupplier.java index 7f4d7b7b3..15d39c993 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/StreamIdSupplier.java +++ b/rsocket-core/src/main/java/io/rsocket/core/StreamIdSupplier.java @@ -17,6 +17,7 @@ import io.netty.util.collection.IntObjectMap; +/** This API is not thread-safe and must be strictly used in serialized fashion */ final class StreamIdSupplier { private static final int MASK = 0x7FFFFFFF; diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/ApplicationErrorException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/ApplicationErrorException.java index e617b82d8..cd0d46754 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/ApplicationErrorException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/ApplicationErrorException.java @@ -17,7 +17,7 @@ package io.rsocket.exceptions; import io.rsocket.frame.ErrorFrameCodec; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; /** * Application layer logic generating a Reactive Streams {@code onError} event. diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/CanceledException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/CanceledException.java index 3c5fc7420..d51ba0fb7 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/CanceledException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/CanceledException.java @@ -17,7 +17,7 @@ package io.rsocket.exceptions; import io.rsocket.frame.ErrorFrameCodec; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; /** * The Responder canceled the request but may have started processing it (similar to REJECTED but diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionCloseException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionCloseException.java index 5cff2c821..80324aa90 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionCloseException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionCloseException.java @@ -17,7 +17,7 @@ package io.rsocket.exceptions; import io.rsocket.frame.ErrorFrameCodec; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; /** * The connection is being terminated. Sender or Receiver of this frame MUST wait for outstanding diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionErrorException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionErrorException.java index 3fcb8f5de..b44714f7e 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionErrorException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/ConnectionErrorException.java @@ -17,7 +17,7 @@ package io.rsocket.exceptions; import io.rsocket.frame.ErrorFrameCodec; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; /** * The connection is being terminated. Sender or Receiver of this frame MAY close the connection diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/CustomRSocketException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/CustomRSocketException.java index 18f488ba0..079b561f9 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/CustomRSocketException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/CustomRSocketException.java @@ -17,7 +17,7 @@ package io.rsocket.exceptions; import io.rsocket.frame.ErrorFrameCodec; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; public class CustomRSocketException extends RSocketException { private static final long serialVersionUID = 7873267740343446585L; diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidException.java index 2428d1e7e..a1b77b8dd 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidException.java @@ -17,7 +17,7 @@ package io.rsocket.exceptions; import io.rsocket.frame.ErrorFrameCodec; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; /** * The request is invalid. diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidSetupException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidSetupException.java index 57da19bb6..b0889c5a6 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidSetupException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/InvalidSetupException.java @@ -17,7 +17,7 @@ package io.rsocket.exceptions; import io.rsocket.frame.ErrorFrameCodec; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; /** * The Setup frame is invalid for the server (it could be that the client is too recent for the old diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedException.java index c87a60243..baed84e1b 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedException.java @@ -17,7 +17,7 @@ package io.rsocket.exceptions; import io.rsocket.frame.ErrorFrameCodec; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; /** * Despite being a valid request, the Responder decided to reject it. The Responder guarantees that diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedResumeException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedResumeException.java index 8a6ea2244..8a99fcffb 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedResumeException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedResumeException.java @@ -17,7 +17,7 @@ package io.rsocket.exceptions; import io.rsocket.frame.ErrorFrameCodec; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; /** * The server rejected the resume, it can specify the reason in the payload. diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedSetupException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedSetupException.java index 972b430a7..c09a27e32 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedSetupException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/RejectedSetupException.java @@ -17,7 +17,7 @@ package io.rsocket.exceptions; import io.rsocket.frame.ErrorFrameCodec; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; /** * The server rejected the setup, it can specify the reason in the payload. diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/SetupException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/SetupException.java index 158e5410d..ed979c9e6 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/SetupException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/SetupException.java @@ -17,7 +17,7 @@ package io.rsocket.exceptions; import io.rsocket.frame.ErrorFrameCodec; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; /** The root of the setup exception hierarchy. */ public abstract class SetupException extends RSocketException { diff --git a/rsocket-core/src/main/java/io/rsocket/exceptions/UnsupportedSetupException.java b/rsocket-core/src/main/java/io/rsocket/exceptions/UnsupportedSetupException.java index 3282c9750..7429ccd98 100644 --- a/rsocket-core/src/main/java/io/rsocket/exceptions/UnsupportedSetupException.java +++ b/rsocket-core/src/main/java/io/rsocket/exceptions/UnsupportedSetupException.java @@ -17,7 +17,7 @@ package io.rsocket.exceptions; import io.rsocket.frame.ErrorFrameCodec; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; /** * Some (or all) of the parameters specified by the client are unsupported by the server. diff --git a/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java b/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java index 5192ffead..5d89bb9ad 100644 --- a/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java +++ b/rsocket-core/src/main/java/io/rsocket/fragmentation/FragmentationDuplexConnection.java @@ -25,12 +25,12 @@ import io.rsocket.frame.FrameLengthCodec; import io.rsocket.frame.FrameType; import java.util.Objects; -import javax.annotation.Nullable; import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.util.annotation.Nullable; /** * A {@link DuplexConnection} implementation that fragments and reassembles {@link ByteBuf}s. diff --git a/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameReassembler.java b/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameReassembler.java index 1e96bd1fc..52068e5de 100644 --- a/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameReassembler.java +++ b/rsocket-core/src/main/java/io/rsocket/fragmentation/FrameReassembler.java @@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory; import reactor.core.Disposable; import reactor.core.publisher.SynchronousSink; +import reactor.util.annotation.Nullable; /** * The implementation of the RSocket reassembly behavior. @@ -83,6 +84,7 @@ public boolean isDisposed() { return get(); } + @Nullable synchronized ByteBuf getHeader(int streamId) { return headers.get(streamId); } @@ -109,14 +111,17 @@ synchronized CompositeByteBuf getData(int streamId) { return byteBuf; } + @Nullable synchronized ByteBuf removeHeader(int streamId) { return headers.remove(streamId); } + @Nullable synchronized CompositeByteBuf removeMetadata(int streamId) { return metadata.remove(streamId); } + @Nullable synchronized CompositeByteBuf removeData(int streamId) { return data.remove(streamId); } @@ -236,7 +241,6 @@ void reassembleFrame(ByteBuf frame, SynchronousSink sink) { case CANCEL: case ERROR: cancelAssemble(streamId); - default: } if (!frameType.isFragmentable()) { @@ -270,7 +274,7 @@ private ByteBuf assembleFrameWithMetadata(ByteBuf frame, int streamId, ByteBuf h metadata = PayloadFrameCodec.metadata(frame).retain(); } } else { - metadata = cm != null ? cm : null; + metadata = cm; } ByteBuf data = assembleData(frame, streamId); diff --git a/rsocket-core/src/main/java/io/rsocket/frame/ExtensionFrameCodec.java b/rsocket-core/src/main/java/io/rsocket/frame/ExtensionFrameCodec.java index bf30b9556..418926596 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/ExtensionFrameCodec.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/ExtensionFrameCodec.java @@ -2,7 +2,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; public class ExtensionFrameCodec { private ExtensionFrameCodec() {} @@ -49,6 +49,7 @@ public static ByteBuf data(ByteBuf byteBuf) { return data; } + @Nullable public static ByteBuf metadata(ByteBuf byteBuf) { FrameHeaderCodec.ensureFrameType(FrameType.EXT, byteBuf); diff --git a/rsocket-core/src/main/java/io/rsocket/frame/FrameBodyCodec.java b/rsocket-core/src/main/java/io/rsocket/frame/FrameBodyCodec.java index 3256d4426..ea011e503 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/FrameBodyCodec.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/FrameBodyCodec.java @@ -3,6 +3,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; +import reactor.util.annotation.Nullable; class FrameBodyCodec { public static final int FRAME_LENGTH_MASK = 0xFFFFFF; @@ -33,9 +34,9 @@ private static int decodeLength(final ByteBuf byteBuf) { static ByteBuf encode( ByteBufAllocator allocator, final ByteBuf header, - ByteBuf metadata, + @Nullable ByteBuf metadata, boolean hasMetadata, - ByteBuf data) { + @Nullable ByteBuf data) { final boolean addData; if (data != null) { diff --git a/rsocket-core/src/main/java/io/rsocket/frame/GenericFrameCodec.java b/rsocket-core/src/main/java/io/rsocket/frame/GenericFrameCodec.java index 65e7eeeea..56a93d869 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/GenericFrameCodec.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/GenericFrameCodec.java @@ -4,7 +4,7 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.util.IllegalReferenceCountException; import io.rsocket.Payload; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; class GenericFrameCodec { @@ -75,7 +75,7 @@ static ByteBuf encode( boolean next, int requestN, @Nullable ByteBuf metadata, - ByteBuf data) { + @Nullable ByteBuf data) { final boolean hasMetadata = metadata != null; @@ -115,6 +115,7 @@ static ByteBuf data(ByteBuf byteBuf) { return data; } + @Nullable static ByteBuf metadata(ByteBuf byteBuf) { boolean hasMetadata = FrameHeaderCodec.hasMetadata(byteBuf); if (!hasMetadata) { @@ -136,6 +137,7 @@ static ByteBuf dataWithRequestN(ByteBuf byteBuf) { return data; } + @Nullable static ByteBuf metadataWithRequestN(ByteBuf byteBuf) { boolean hasMetadata = FrameHeaderCodec.hasMetadata(byteBuf); if (!hasMetadata) { diff --git a/rsocket-core/src/main/java/io/rsocket/frame/LeaseFrameCodec.java b/rsocket-core/src/main/java/io/rsocket/frame/LeaseFrameCodec.java index cafd80104..f20c25d3b 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/LeaseFrameCodec.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/LeaseFrameCodec.java @@ -2,8 +2,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.Unpooled; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; public class LeaseFrameCodec { @@ -67,6 +66,7 @@ public static int numRequests(final ByteBuf byteBuf) { return numRequests; } + @Nullable public static ByteBuf metadata(final ByteBuf byteBuf) { FrameHeaderCodec.ensureFrameType(FrameType.LEASE, byteBuf); if (FrameHeaderCodec.hasMetadata(byteBuf)) { @@ -77,7 +77,7 @@ public static ByteBuf metadata(final ByteBuf byteBuf) { byteBuf.resetReaderIndex(); return metadata; } else { - return Unpooled.EMPTY_BUFFER; + return null; } } } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/MetadataPushFrameCodec.java b/rsocket-core/src/main/java/io/rsocket/frame/MetadataPushFrameCodec.java index 62c8a17dc..d8ffe3eef 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/MetadataPushFrameCodec.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/MetadataPushFrameCodec.java @@ -8,6 +8,10 @@ public class MetadataPushFrameCodec { public static ByteBuf encodeReleasingPayload(ByteBufAllocator allocator, Payload payload) { + if (!payload.hasMetadata()) { + throw new IllegalStateException( + "Metadata push requires to have metadata present" + " in the given Payload"); + } final ByteBuf metadata = payload.metadata().retain(); // releasing payload safely since it can be already released wheres we have to release retained // data and metadata as well diff --git a/rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameCodec.java b/rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameCodec.java index 8a7e6427a..1ae9c6750 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameCodec.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/PayloadFrameCodec.java @@ -3,6 +3,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.rsocket.Payload; +import reactor.util.annotation.Nullable; public class PayloadFrameCodec { @@ -37,8 +38,8 @@ public static ByteBuf encode( boolean fragmentFollows, boolean complete, boolean next, - ByteBuf metadata, - ByteBuf data) { + @Nullable ByteBuf metadata, + @Nullable ByteBuf data) { return GenericFrameCodec.encode( allocator, FrameType.PAYLOAD, streamId, fragmentFollows, complete, next, 0, metadata, data); @@ -48,6 +49,7 @@ public static ByteBuf data(ByteBuf byteBuf) { return GenericFrameCodec.data(byteBuf); } + @Nullable public static ByteBuf metadata(ByteBuf byteBuf) { return GenericFrameCodec.metadata(byteBuf); } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameCodec.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameCodec.java index 2ff887043..60906083d 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameCodec.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/RequestChannelFrameCodec.java @@ -3,6 +3,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.rsocket.Payload; +import reactor.util.annotation.Nullable; public class RequestChannelFrameCodec { @@ -31,7 +32,7 @@ public static ByteBuf encode( boolean fragmentFollows, boolean complete, long initialRequestN, - ByteBuf metadata, + @Nullable ByteBuf metadata, ByteBuf data) { if (initialRequestN < 1) { @@ -56,6 +57,7 @@ public static ByteBuf data(ByteBuf byteBuf) { return GenericFrameCodec.dataWithRequestN(byteBuf); } + @Nullable public static ByteBuf metadata(ByteBuf byteBuf) { return GenericFrameCodec.metadataWithRequestN(byteBuf); } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameCodec.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameCodec.java index ddb5bc5d7..b91199179 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameCodec.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/RequestFireAndForgetFrameCodec.java @@ -3,6 +3,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.rsocket.Payload; +import reactor.util.annotation.Nullable; public class RequestFireAndForgetFrameCodec { @@ -19,7 +20,7 @@ public static ByteBuf encode( ByteBufAllocator allocator, int streamId, boolean fragmentFollows, - ByteBuf metadata, + @Nullable ByteBuf metadata, ByteBuf data) { return GenericFrameCodec.encode( @@ -30,6 +31,7 @@ public static ByteBuf data(ByteBuf byteBuf) { return GenericFrameCodec.data(byteBuf); } + @Nullable public static ByteBuf metadata(ByteBuf byteBuf) { return GenericFrameCodec.metadata(byteBuf); } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameCodec.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameCodec.java index 884a8293b..4a37acfd5 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameCodec.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/RequestResponseFrameCodec.java @@ -3,6 +3,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.rsocket.Payload; +import reactor.util.annotation.Nullable; public class RequestResponseFrameCodec { @@ -19,7 +20,7 @@ public static ByteBuf encode( ByteBufAllocator allocator, int streamId, boolean fragmentFollows, - ByteBuf metadata, + @Nullable ByteBuf metadata, ByteBuf data) { return GenericFrameCodec.encode( allocator, FrameType.REQUEST_RESPONSE, streamId, fragmentFollows, metadata, data); @@ -29,6 +30,7 @@ public static ByteBuf data(ByteBuf byteBuf) { return GenericFrameCodec.data(byteBuf); } + @Nullable public static ByteBuf metadata(ByteBuf byteBuf) { return GenericFrameCodec.metadata(byteBuf); } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameCodec.java b/rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameCodec.java index 9853a8b54..2f5dbf0d8 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameCodec.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/RequestStreamFrameCodec.java @@ -3,6 +3,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.rsocket.Payload; +import reactor.util.annotation.Nullable; public class RequestStreamFrameCodec { @@ -26,7 +27,7 @@ public static ByteBuf encode( int streamId, boolean fragmentFollows, long initialRequestN, - ByteBuf metadata, + @Nullable ByteBuf metadata, ByteBuf data) { if (initialRequestN < 1) { @@ -51,6 +52,7 @@ public static ByteBuf data(ByteBuf byteBuf) { return GenericFrameCodec.dataWithRequestN(byteBuf); } + @Nullable public static ByteBuf metadata(ByteBuf byteBuf) { return GenericFrameCodec.metadataWithRequestN(byteBuf); } diff --git a/rsocket-core/src/main/java/io/rsocket/frame/SetupFrameCodec.java b/rsocket-core/src/main/java/io/rsocket/frame/SetupFrameCodec.java index d6f7431e4..547e2436e 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/SetupFrameCodec.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/SetupFrameCodec.java @@ -6,6 +6,7 @@ import io.netty.buffer.Unpooled; import io.rsocket.Payload; import java.nio.charset.StandardCharsets; +import reactor.util.annotation.Nullable; public class SetupFrameCodec { /** @@ -61,7 +62,7 @@ public static ByteBuf encode( int flags = 0; - if (resumeToken != null && resumeToken.readableBytes() > 0) { + if (resumeToken.readableBytes() > 0) { flags |= FLAGS_RESUME_ENABLE; } @@ -163,7 +164,7 @@ public static ByteBuf resumeToken(ByteBuf byteBuf) { byteBuf.resetReaderIndex(); return resumeToken; } else { - return null; + return Unpooled.EMPTY_BUFFER; } } @@ -186,6 +187,7 @@ public static String dataMimeType(ByteBuf byteBuf) { return mimeType; } + @Nullable public static ByteBuf metadata(ByteBuf byteBuf) { boolean hasMetadata = FrameHeaderCodec.hasMetadata(byteBuf); if (!hasMetadata) { diff --git a/rsocket-core/src/main/java/io/rsocket/frame/decoder/package-info.java b/rsocket-core/src/main/java/io/rsocket/frame/decoder/package-info.java new file mode 100644 index 000000000..82e8acaf3 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/frame/decoder/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Support for encoding and decoding of RSocket frames to and from {@link io.rsocket.Payload + * Payload}. + */ +@NonNullApi +package io.rsocket.frame.decoder; + +import reactor.util.annotation.NonNullApi; diff --git a/rsocket-core/src/main/java/io/rsocket/frame/package-info.java b/rsocket-core/src/main/java/io/rsocket/frame/package-info.java index 1d02ebca0..69f6d6860 100644 --- a/rsocket-core/src/main/java/io/rsocket/frame/package-info.java +++ b/rsocket-core/src/main/java/io/rsocket/frame/package-info.java @@ -18,4 +18,7 @@ * Support for encoding and decoding of RSocket frames to and from {@link io.rsocket.Payload * Payload}. */ +@NonNullApi package io.rsocket.frame; + +import reactor.util.annotation.NonNullApi; diff --git a/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java b/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java index c294d6539..0d24c51d8 100644 --- a/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java +++ b/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java @@ -161,6 +161,7 @@ private static class InternalDuplexConnection implements DuplexConnection { private final MonoProcessor>[] processors; private final boolean debugEnabled; + @SafeVarargs public InternalDuplexConnection( DuplexConnection source, MonoProcessor>... processors) { this.source = source; diff --git a/rsocket-core/src/main/java/io/rsocket/internal/package-info.java b/rsocket-core/src/main/java/io/rsocket/internal/package-info.java index 09918f3d1..07ddfab41 100644 --- a/rsocket-core/src/main/java/io/rsocket/internal/package-info.java +++ b/rsocket-core/src/main/java/io/rsocket/internal/package-info.java @@ -18,5 +18,7 @@ * Internal package and must not be used outside this project. There are no guarantees for * API compatibility. */ -@javax.annotation.ParametersAreNonnullByDefault +@NonNullApi package io.rsocket.internal; + +import reactor.util.annotation.NonNullApi; diff --git a/rsocket-core/src/main/java/io/rsocket/keepalive/package-info.java b/rsocket-core/src/main/java/io/rsocket/keepalive/package-info.java index ce8a2f3fb..d94a93cad 100644 --- a/rsocket-core/src/main/java/io/rsocket/keepalive/package-info.java +++ b/rsocket-core/src/main/java/io/rsocket/keepalive/package-info.java @@ -15,5 +15,7 @@ */ /** Support classes for sending and keeping track of KEEPALIVE frames from the remote. */ -@javax.annotation.ParametersAreNonnullByDefault +@NonNullApi package io.rsocket.keepalive; + +import reactor.util.annotation.NonNullApi; diff --git a/rsocket-core/src/main/java/io/rsocket/lease/Lease.java b/rsocket-core/src/main/java/io/rsocket/lease/Lease.java index b9d99f88a..673b4a480 100644 --- a/rsocket-core/src/main/java/io/rsocket/lease/Lease.java +++ b/rsocket-core/src/main/java/io/rsocket/lease/Lease.java @@ -19,8 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.rsocket.Availability; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; /** A contract for RSocket lease, which is sent by a request acceptor and is time bound. */ public interface Lease extends Availability { @@ -78,7 +77,6 @@ default int getRemainingTimeToLiveMillis(long now) { * * @return Metadata for the lease. */ - @Nonnull ByteBuf getMetadata(); /** diff --git a/rsocket-core/src/main/java/io/rsocket/lease/LeaseImpl.java b/rsocket-core/src/main/java/io/rsocket/lease/LeaseImpl.java index 63b0433cb..7abb8aab9 100644 --- a/rsocket-core/src/main/java/io/rsocket/lease/LeaseImpl.java +++ b/rsocket-core/src/main/java/io/rsocket/lease/LeaseImpl.java @@ -19,8 +19,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import java.util.concurrent.atomic.AtomicInteger; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; public class LeaseImpl implements Lease { private final int timeToLiveMillis; @@ -60,7 +59,6 @@ public int getStartingAllowedRequests() { return startingAllowedRequests; } - @Nonnull @Override public ByteBuf getMetadata() { return metadata; diff --git a/rsocket-core/src/main/java/io/rsocket/lease/MissingLeaseException.java b/rsocket-core/src/main/java/io/rsocket/lease/MissingLeaseException.java index 734d16d07..3b6cec62c 100644 --- a/rsocket-core/src/main/java/io/rsocket/lease/MissingLeaseException.java +++ b/rsocket-core/src/main/java/io/rsocket/lease/MissingLeaseException.java @@ -17,17 +17,16 @@ import io.rsocket.exceptions.RejectedException; import java.util.Objects; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; public class MissingLeaseException extends RejectedException { private static final long serialVersionUID = -6169748673403858959L; - public MissingLeaseException(@Nonnull Lease lease, @Nonnull String tag) { + public MissingLeaseException(Lease lease, String tag) { super(leaseMessage(Objects.requireNonNull(lease), Objects.requireNonNull(tag))); } - public MissingLeaseException(@Nonnull String tag) { + public MissingLeaseException(String tag) { super(leaseMessage(null, Objects.requireNonNull(tag))); } diff --git a/rsocket-core/src/main/java/io/rsocket/lease/ResponderLeaseHandler.java b/rsocket-core/src/main/java/io/rsocket/lease/ResponderLeaseHandler.java index 5f000cb30..2035ade87 100644 --- a/rsocket-core/src/main/java/io/rsocket/lease/ResponderLeaseHandler.java +++ b/rsocket-core/src/main/java/io/rsocket/lease/ResponderLeaseHandler.java @@ -23,10 +23,10 @@ import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; -import javax.annotation.Nullable; import reactor.core.Disposable; import reactor.core.Disposables; import reactor.core.publisher.Flux; +import reactor.util.annotation.Nullable; public interface ResponderLeaseHandler extends Availability { diff --git a/rsocket-core/src/main/java/io/rsocket/lease/package-info.java b/rsocket-core/src/main/java/io/rsocket/lease/package-info.java index ce1956628..342ab27f7 100644 --- a/rsocket-core/src/main/java/io/rsocket/lease/package-info.java +++ b/rsocket-core/src/main/java/io/rsocket/lease/package-info.java @@ -21,5 +21,7 @@ * href="https://github.com/rsocket/rsocket/blob/master/Protocol.md#resuming-operation">Resuming * Operation */ -@javax.annotation.ParametersAreNonnullByDefault +@NonNullApi package io.rsocket.lease; + +import reactor.util.annotation.NonNullApi; diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/package-info.java b/rsocket-core/src/main/java/io/rsocket/metadata/package-info.java index b1bc45ff0..3fb9ae1d6 100644 --- a/rsocket-core/src/main/java/io/rsocket/metadata/package-info.java +++ b/rsocket-core/src/main/java/io/rsocket/metadata/package-info.java @@ -19,4 +19,7 @@ * href="https://github.com/rsocket/rsocket/tree/master/Extensions">protocol extensions related * to the use of metadata. */ +@NonNullApi package io.rsocket.metadata; + +import reactor.util.annotation.NonNullApi; diff --git a/rsocket-core/src/main/java/io/rsocket/package-info.java b/rsocket-core/src/main/java/io/rsocket/package-info.java index 878a56301..6fe74fb38 100644 --- a/rsocket-core/src/main/java/io/rsocket/package-info.java +++ b/rsocket-core/src/main/java/io/rsocket/package-info.java @@ -23,4 +23,7 @@ *

    To connect to or start a server see {@link io.rsocket.core.RSocketConnector RSocketConnector} * and {@link io.rsocket.core.RSocketServer RSocketServer} in {@link io.rsocket.core}. */ +@NonNullApi package io.rsocket; + +import reactor.util.annotation.NonNullApi; diff --git a/rsocket-core/src/main/java/io/rsocket/plugins/package-info.java b/rsocket-core/src/main/java/io/rsocket/plugins/package-info.java index 743e3a8a4..fd9e1f01a 100644 --- a/rsocket-core/src/main/java/io/rsocket/plugins/package-info.java +++ b/rsocket-core/src/main/java/io/rsocket/plugins/package-info.java @@ -15,4 +15,7 @@ */ /** Contracts for interception of transports, connections, and requests in in RSocket Java. */ +@NonNullApi package io.rsocket.plugins; + +import reactor.util.annotation.NonNullApi; diff --git a/rsocket-core/src/main/java/io/rsocket/resume/SessionManager.java b/rsocket-core/src/main/java/io/rsocket/resume/SessionManager.java index 3882103a0..1d5c23bd6 100644 --- a/rsocket-core/src/main/java/io/rsocket/resume/SessionManager.java +++ b/rsocket-core/src/main/java/io/rsocket/resume/SessionManager.java @@ -19,7 +19,7 @@ import io.netty.buffer.ByteBuf; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; public class SessionManager { private volatile boolean isDisposed; diff --git a/rsocket-core/src/main/java/io/rsocket/resume/package-info.java b/rsocket-core/src/main/java/io/rsocket/resume/package-info.java index aaaa3ee9f..98744386a 100644 --- a/rsocket-core/src/main/java/io/rsocket/resume/package-info.java +++ b/rsocket-core/src/main/java/io/rsocket/resume/package-info.java @@ -21,5 +21,7 @@ * href="https://github.com/rsocket/rsocket/blob/master/Protocol.md#resuming-operation">Resuming * Operation */ -@javax.annotation.ParametersAreNonnullByDefault +@NonNullApi package io.rsocket.resume; + +import reactor.util.annotation.NonNullApi; diff --git a/rsocket-core/src/main/java/io/rsocket/transport/package-info.java b/rsocket-core/src/main/java/io/rsocket/transport/package-info.java index 153676324..00536122a 100644 --- a/rsocket-core/src/main/java/io/rsocket/transport/package-info.java +++ b/rsocket-core/src/main/java/io/rsocket/transport/package-info.java @@ -15,5 +15,7 @@ */ /** Client and server transport contracts for pluggable transports. */ -@javax.annotation.ParametersAreNonnullByDefault +@NonNullApi package io.rsocket.transport; + +import reactor.util.annotation.NonNullApi; diff --git a/rsocket-core/src/main/java/io/rsocket/util/ByteBufPayload.java b/rsocket-core/src/main/java/io/rsocket/util/ByteBufPayload.java index f5d747f7f..4cf33fa86 100644 --- a/rsocket-core/src/main/java/io/rsocket/util/ByteBufPayload.java +++ b/rsocket-core/src/main/java/io/rsocket/util/ByteBufPayload.java @@ -28,7 +28,7 @@ import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; public final class ByteBufPayload extends AbstractReferenceCounted implements Payload { private static final Recycler RECYCLER = diff --git a/rsocket-core/src/main/java/io/rsocket/util/DefaultPayload.java b/rsocket-core/src/main/java/io/rsocket/util/DefaultPayload.java index ec73399f1..58f282110 100644 --- a/rsocket-core/src/main/java/io/rsocket/util/DefaultPayload.java +++ b/rsocket-core/src/main/java/io/rsocket/util/DefaultPayload.java @@ -23,7 +23,7 @@ import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import javax.annotation.Nullable; +import reactor.util.annotation.Nullable; /** * An implementation of {@link Payload}. This implementation is not thread-safe, and hence diff --git a/rsocket-core/src/main/java/io/rsocket/util/package-info.java b/rsocket-core/src/main/java/io/rsocket/util/package-info.java index e034672f1..2fac3327f 100644 --- a/rsocket-core/src/main/java/io/rsocket/util/package-info.java +++ b/rsocket-core/src/main/java/io/rsocket/util/package-info.java @@ -15,5 +15,7 @@ */ /** Shared utility classes and {@link io.rsocket.Payload} implementations. */ -@javax.annotation.ParametersAreNonnullByDefault +@NonNullApi package io.rsocket.util; + +import reactor.util.annotation.NonNullApi; diff --git a/rsocket-core/src/test/java/io/rsocket/core/ConnectionSetupPayloadTest.java b/rsocket-core/src/test/java/io/rsocket/core/ConnectionSetupPayloadTest.java index 9d8b8354a..8eb5dee09 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/ConnectionSetupPayloadTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/ConnectionSetupPayloadTest.java @@ -82,7 +82,7 @@ private static ByteBuf encodeSetupFrame(boolean leaseEnabled, Payload setupPaylo leaseEnabled, KEEP_ALIVE_INTERVAL, KEEP_ALIVE_MAX_LIFETIME, - null, + Unpooled.EMPTY_BUFFER, METADATA_TYPE, DATA_TYPE, setupPayload); diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java index 6f1ecf98b..00f74152a 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java @@ -143,6 +143,6 @@ static Stream>> allInteractions() { rSocket -> rSocket.requestResponse(DefaultPayload.create("test")), rSocket -> rSocket.requestStream(DefaultPayload.create("test")), // rSocket -> rSocket.requestChannel(Mono.just(DefaultPayload.create("test"))), - rSocket -> rSocket.metadataPush(DefaultPayload.create("test"))); + rSocket -> rSocket.metadataPush(DefaultPayload.create("", "test"))); } } diff --git a/rsocket-core/src/test/java/io/rsocket/frame/LeaseFrameCodecTest.java b/rsocket-core/src/test/java/io/rsocket/frame/LeaseFrameCodecTest.java index 448b5003b..73c3bde5e 100644 --- a/rsocket-core/src/test/java/io/rsocket/frame/LeaseFrameCodecTest.java +++ b/rsocket-core/src/test/java/io/rsocket/frame/LeaseFrameCodecTest.java @@ -32,7 +32,7 @@ void leaseAbsentMetadata() { Assertions.assertFalse(FrameHeaderCodec.hasMetadata(lease)); Assertions.assertEquals(ttl, LeaseFrameCodec.ttl(lease)); Assertions.assertEquals(numRequests, LeaseFrameCodec.numRequests(lease)); - Assertions.assertEquals(0, LeaseFrameCodec.metadata(lease).readableBytes()); + Assertions.assertNull(LeaseFrameCodec.metadata(lease)); lease.release(); } diff --git a/rsocket-core/src/test/java/io/rsocket/frame/SetupFrameCodecTest.java b/rsocket-core/src/test/java/io/rsocket/frame/SetupFrameCodecTest.java index f7d649972..9607ad327 100644 --- a/rsocket-core/src/test/java/io/rsocket/frame/SetupFrameCodecTest.java +++ b/rsocket-core/src/test/java/io/rsocket/frame/SetupFrameCodecTest.java @@ -22,7 +22,7 @@ void testEncodingNoResume() { assertEquals(FrameType.SETUP, FrameHeaderCodec.frameType(frame)); assertFalse(SetupFrameCodec.resumeEnabled(frame)); - assertNull(SetupFrameCodec.resumeToken(frame)); + assertEquals(0, SetupFrameCodec.resumeToken(frame).readableBytes()); assertEquals("metadata_type", SetupFrameCodec.metadataMimeType(frame)); assertEquals("data_type", SetupFrameCodec.dataMimeType(frame)); assertEquals(metadata, SetupFrameCodec.metadata(frame)); diff --git a/rsocket-load-balancer/src/main/java/io/rsocket/client/filter/package-info.java b/rsocket-load-balancer/src/main/java/io/rsocket/client/filter/package-info.java new file mode 100644 index 000000000..55ce5646c --- /dev/null +++ b/rsocket-load-balancer/src/main/java/io/rsocket/client/filter/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@NonNullApi +package io.rsocket.client.filter; + +import reactor.util.annotation.NonNullApi; diff --git a/rsocket-load-balancer/src/main/java/io/rsocket/client/package-info.java b/rsocket-load-balancer/src/main/java/io/rsocket/client/package-info.java new file mode 100644 index 000000000..ec21dee96 --- /dev/null +++ b/rsocket-load-balancer/src/main/java/io/rsocket/client/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@NonNullApi +package io.rsocket.client; + +import reactor.util.annotation.NonNullApi; diff --git a/rsocket-load-balancer/src/main/java/io/rsocket/stat/package-info.java b/rsocket-load-balancer/src/main/java/io/rsocket/stat/package-info.java new file mode 100644 index 000000000..cfb071175 --- /dev/null +++ b/rsocket-load-balancer/src/main/java/io/rsocket/stat/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@NonNullApi +package io.rsocket.stat; + +import reactor.util.annotation.NonNullApi; diff --git a/rsocket-micrometer/build.gradle b/rsocket-micrometer/build.gradle index 5f2aeb16f..4be616623 100644 --- a/rsocket-micrometer/build.gradle +++ b/rsocket-micrometer/build.gradle @@ -27,8 +27,6 @@ dependencies { implementation 'org.slf4j:slf4j-api' - compileOnly 'com.google.code.findbugs:jsr305' - testImplementation project(':rsocket-test') testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.assertj:assertj-core' diff --git a/rsocket-test/build.gradle b/rsocket-test/build.gradle index 3009b5135..5ec1a8061 100644 --- a/rsocket-test/build.gradle +++ b/rsocket-test/build.gradle @@ -26,8 +26,6 @@ dependencies { api 'org.hdrhistogram:HdrHistogram' api 'org.junit.jupiter:junit-jupiter-api' - compileOnly 'com.google.code.findbugs:jsr305' - implementation 'io.projectreactor:reactor-test' implementation 'org.assertj:assertj-core' implementation 'org.mockito:mockito-core' diff --git a/rsocket-transport-local/build.gradle b/rsocket-transport-local/build.gradle index 8c3226065..a5ba84d5c 100644 --- a/rsocket-transport-local/build.gradle +++ b/rsocket-transport-local/build.gradle @@ -24,8 +24,6 @@ plugins { dependencies { api project(':rsocket-core') - compileOnly 'com.google.code.findbugs:jsr305' - testImplementation project(':rsocket-test') testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.assertj:assertj-core' diff --git a/rsocket-transport-netty/build.gradle b/rsocket-transport-netty/build.gradle index 0aac12d5c..64e483c90 100644 --- a/rsocket-transport-netty/build.gradle +++ b/rsocket-transport-netty/build.gradle @@ -32,8 +32,6 @@ dependencies { api 'io.projectreactor.netty:reactor-netty' api 'org.slf4j:slf4j-api' - compileOnly 'com.google.code.findbugs:jsr305' - testImplementation project(':rsocket-test') testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.assertj:assertj-core' diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/TcpClientTransport.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/TcpClientTransport.java index 8be019f1c..22f139310 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/TcpClientTransport.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/TcpClientTransport.java @@ -75,7 +75,7 @@ public static TcpClientTransport create(String bindAddress, int port) { public static TcpClientTransport create(InetSocketAddress address) { Objects.requireNonNull(address, "address must not be null"); - TcpClient tcpClient = TcpClient.create().addressSupplier(() -> address); + TcpClient tcpClient = TcpClient.create().remoteAddress(() -> address); return create(tcpClient); } diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java index 6991728ca..747401210 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/client/WebsocketClientTransport.java @@ -35,6 +35,7 @@ import java.util.function.Supplier; import reactor.core.publisher.Mono; import reactor.netty.http.client.HttpClient; +import reactor.netty.http.client.WebsocketClientSpec; import reactor.netty.tcp.TcpClient; /** @@ -47,7 +48,7 @@ public final class WebsocketClientTransport implements ClientTransport, Transpor private final HttpClient client; - private String path; + private final String path; private Supplier> transportHeaders = Collections::emptyMap; @@ -92,7 +93,7 @@ public static WebsocketClientTransport create(String bindAddress, int port) { public static WebsocketClientTransport create(InetSocketAddress address) { Objects.requireNonNull(address, "address must not be null"); - TcpClient client = TcpClient.create().addressSupplier(() -> address); + TcpClient client = TcpClient.create().remoteAddress(() -> address); return create(client); } @@ -155,7 +156,8 @@ public Mono connect(int mtu) { ? isError : client .headers(headers -> transportHeaders.get().forEach(headers::set)) - .websocket(FRAME_LENGTH_MASK) + .websocket( + WebsocketClientSpec.builder().maxFramePayloadLength(FRAME_LENGTH_MASK).build()) .uri(path) .connect() .map( diff --git a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/CloseableChannel.java b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/CloseableChannel.java index f6e83bc36..c0340c7a2 100644 --- a/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/CloseableChannel.java +++ b/rsocket-transport-netty/src/main/java/io/rsocket/transport/netty/server/CloseableChannel.java @@ -28,7 +28,7 @@ */ public final class CloseableChannel implements Closeable { - private DisposableChannel channel; + private final DisposableChannel channel; /** * Creates a new instance diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpSecureTransportTest.java b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpSecureTransportTest.java index b77de6d4e..95bebd6aa 100644 --- a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpSecureTransportTest.java +++ b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/TcpSecureTransportTest.java @@ -20,7 +20,7 @@ public class TcpSecureTransportTest implements TransportTest { (address, server) -> TcpClientTransport.create( TcpClient.create() - .addressSupplier(server::address) + .remoteAddress(server::address) .secure( ssl -> ssl.sslContext( @@ -31,7 +31,7 @@ public class TcpSecureTransportTest implements TransportTest { SelfSignedCertificate ssc = new SelfSignedCertificate(); TcpServer server = TcpServer.create() - .addressSupplier(() -> address) + .bindAddress(() -> address) .secure( ssl -> ssl.sslContext( diff --git a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketSecureTransportTest.java b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketSecureTransportTest.java index c1d608979..ec33060b2 100644 --- a/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketSecureTransportTest.java +++ b/rsocket-transport-netty/src/test/java/io/rsocket/transport/netty/WebsocketSecureTransportTest.java @@ -38,7 +38,7 @@ final class WebsocketSecureTransportTest implements TransportTest { (address, server) -> WebsocketClientTransport.create( HttpClient.create() - .addressSupplier(server::address) + .remoteAddress(server::address) .secure( ssl -> ssl.sslContext( @@ -53,7 +53,7 @@ final class WebsocketSecureTransportTest implements TransportTest { HttpServer server = HttpServer.from( TcpServer.create() - .addressSupplier(() -> address) + .bindAddress(() -> address) .secure( ssl -> ssl.sslContext( From fdc9b1bc50cedbb49366c20cf639959e55c0c75a Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Sun, 10 May 2020 23:34:59 +0300 Subject: [PATCH 54/62] removes limit rate operator (#829) --- .../io/rsocket/core/RSocketRequester.java | 1 - .../io/rsocket/core/RSocketResponder.java | 6 +- .../plugins/LimitRateInterceptorExample.java | 168 ++++++++++++++++++ .../java/io/rsocket/test/TransportTest.java | 3 +- 4 files changed, 170 insertions(+), 8 deletions(-) create mode 100644 rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/plugins/LimitRateInterceptorExample.java diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index 068797d39..3de074953 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -491,7 +491,6 @@ void hookOnFirstRequest(long n) { receivers.put(streamId, receiver); inboundFlux - .limitRate(Queues.SMALL_BUFFER_SIZE) .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER) .subscribe(upstreamSubscriber); diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java index dce182b49..d5f9206d8 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java @@ -47,7 +47,6 @@ import reactor.core.Exceptions; import reactor.core.publisher.*; import reactor.util.annotation.Nullable; -import reactor.util.concurrent.Queues; /** Responder side of RSocket. Receives {@link ByteBuf}s from a peer's {@link RSocketRequester} */ class RSocketResponder implements RSocket { @@ -526,10 +525,7 @@ protected void hookFinally(SignalType type) { }; sendingSubscriptions.put(streamId, subscriber); - response - .limitRate(Queues.SMALL_BUFFER_SIZE) - .doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER) - .subscribe(subscriber); + response.doOnDiscard(ReferenceCounted.class, DROPPED_ELEMENTS_CONSUMER).subscribe(subscriber); } private void handleChannel(int streamId, Payload payload, long initialRequestN) { diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/plugins/LimitRateInterceptorExample.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/plugins/LimitRateInterceptorExample.java new file mode 100644 index 000000000..ac473d7b1 --- /dev/null +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/plugins/LimitRateInterceptorExample.java @@ -0,0 +1,168 @@ +package io.rsocket.examples.transport.tcp.plugins; + +import io.rsocket.Payload; +import io.rsocket.RSocket; +import io.rsocket.SocketAcceptor; +import io.rsocket.core.RSocketConnector; +import io.rsocket.core.RSocketServer; +import io.rsocket.examples.transport.tcp.stream.StreamingClient; +import io.rsocket.plugins.RSocketInterceptor; +import io.rsocket.transport.netty.client.TcpClientTransport; +import io.rsocket.transport.netty.server.TcpServerTransport; +import io.rsocket.util.DefaultPayload; +import io.rsocket.util.RSocketProxy; +import java.time.Duration; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import org.reactivestreams.Publisher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Flux; +import reactor.util.concurrent.Queues; + +public class LimitRateInterceptorExample { + + private static final Logger logger = LoggerFactory.getLogger(StreamingClient.class); + + public static void main(String[] args) { + BlockingQueue requests = new ArrayBlockingQueue<>(100); + RSocketServer.create( + SocketAcceptor.with( + new RSocket() { + @Override + public Flux requestStream(Payload payload) { + return Flux.interval(Duration.ofMillis(100)) + .doOnRequest(e -> requests.add("Responder requestN(" + e + ")")) + .map(aLong -> DefaultPayload.create("Interval: " + aLong)); + } + + @Override + public Flux requestChannel(Publisher payloads) { + return Flux.from(payloads) + .doOnRequest(e -> requests.add("Responder requestN(" + e + ")")); + } + })) + .interceptors( + ir -> + ir.forRequester(LimitRateInterceptor.forRequester()) + .forResponder(LimitRateInterceptor.forResponder())) + .bind(TcpServerTransport.create("localhost", 7000)) + .subscribe(); + + RSocket socket = + RSocketConnector.create() + .interceptors( + ir -> + ir.forRequester(LimitRateInterceptor.forRequester()) + .forResponder(LimitRateInterceptor.forResponder())) + .connect(TcpClientTransport.create("localhost", 7000)) + .block(); + + socket + .requestStream(DefaultPayload.create("Hello")) + .doOnRequest(e -> requests.add("Requester requestN(" + e + ")")) + .map(Payload::getDataUtf8) + .doOnNext(logger::debug) + .take(10) + .then() + .block(); + + requests.forEach(request -> logger.debug("Requested : {}", request)); + requests.clear(); + + logger.debug("-----------------------------------------------------------------"); + logger.debug("Does requestChannel"); + socket + .requestChannel( + Flux.generate( + () -> 1L, + (s, sink) -> { + sink.next(DefaultPayload.create("Next " + s)); + return ++s; + }) + .doOnRequest(e -> requests.add("Requester Upstream requestN(" + e + ")"))) + .doOnRequest(e -> requests.add("Requester Downstream requestN(" + e + ")")) + .map(Payload::getDataUtf8) + .doOnNext(logger::debug) + .take(10) + .then() + .doFinally(signalType -> socket.dispose()) + .then() + .block(); + + requests.forEach(request -> logger.debug("Requested : {}", request)); + } + + static class LimitRateInterceptor implements RSocketInterceptor { + + final boolean requesterSide; + final int highTide; + final int lowTide; + + LimitRateInterceptor(boolean requesterSide, int highTide, int lowTide) { + this.requesterSide = requesterSide; + this.highTide = highTide; + this.lowTide = lowTide; + } + + @Override + public RSocket apply(RSocket socket) { + return new LimitRateRSocket(socket, requesterSide, highTide, lowTide); + } + + public static LimitRateInterceptor forRequester() { + return forRequester(Queues.SMALL_BUFFER_SIZE); + } + + public static LimitRateInterceptor forRequester(int limit) { + return forRequester(limit, limit); + } + + public static LimitRateInterceptor forRequester(int highTide, int lowTide) { + return new LimitRateInterceptor(true, highTide, lowTide); + } + + public static LimitRateInterceptor forResponder() { + return forRequester(Queues.SMALL_BUFFER_SIZE); + } + + public static LimitRateInterceptor forResponder(int limit) { + return forRequester(limit, limit); + } + + public static LimitRateInterceptor forResponder(int highTide, int lowTide) { + return new LimitRateInterceptor(false, highTide, lowTide); + } + } + + static class LimitRateRSocket extends RSocketProxy { + + final boolean requesterSide; + final int highTide; + final int lowTide; + + public LimitRateRSocket(RSocket source, boolean requesterSide, int highTide, int lowTide) { + super(source); + this.requesterSide = requesterSide; + this.highTide = highTide; + this.lowTide = lowTide; + } + + @Override + public Flux requestStream(Payload payload) { + Flux flux = super.requestStream(payload); + if (requesterSide) { + return flux; + } + return flux.limitRate(highTide, lowTide); + } + + @Override + public Flux requestChannel(Publisher payloads) { + if (requesterSide) { + return super.requestChannel(Flux.from(payloads).limitRate(highTide, lowTide)); + } + return super.requestChannel(payloads).limitRate(highTide, lowTide); + } + } +} diff --git a/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java b/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java index 583f58634..fc059c7d1 100644 --- a/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java +++ b/rsocket-test/src/main/java/io/rsocket/test/TransportTest.java @@ -237,8 +237,7 @@ default void requestChannel3() { .expectComplete() .verify(getTimeout()); - Assertions.assertThat(requested.get()) - .isEqualTo(256L); // 256 because of eager behavior of limitRate + Assertions.assertThat(requested.get()).isEqualTo(3L); } @DisplayName("makes 1 requestChannel request with 512 payloads") From 69ca8185920d60ef4f20b41db0c19b330bae7330 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Sun, 10 May 2020 23:35:41 +0300 Subject: [PATCH 55/62] provides `bindNow` shortcut for the server (#830) --- .../src/main/java/io/rsocket/core/RSocketServer.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java index 66e2249b5..a0f7c810b 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java @@ -290,6 +290,14 @@ public Mono get() { }); } + /** + * Start the server on the given transport. Effectively is a shortcut for {@code + * .bind(ServerTransport).block()} + */ + public T bindNow(ServerTransport transport) { + return bind(transport).block(); + } + /** * An alternative to {@link #bind(ServerTransport)} that is useful for installing RSocket on a * server that is started independently. From 31c1509f9a31c006cb9635d78e54dd0924377614 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Mon, 11 May 2020 15:31:52 +0300 Subject: [PATCH 56/62] removes deprecated errorConsumer (#832) --- .../main/java/io/rsocket/RSocketFactory.java | 20 ++-- .../io/rsocket/core/RSocketConnector.java | 16 ---- .../io/rsocket/core/RSocketRequester.java | 38 ++++---- .../io/rsocket/core/RSocketResponder.java | 27 +++--- .../java/io/rsocket/core/RSocketServer.java | 20 +--- .../ClientServerInputMultiplexer.java | 5 +- .../rsocket/lease/ResponderLeaseHandler.java | 6 +- .../io/rsocket/core/AbstractSocketRule.java | 10 -- .../java/io/rsocket/core/KeepAliveTest.java | 55 ++--------- .../io/rsocket/core/RSocketLeaseTest.java | 4 +- .../core/RSocketRequesterSubscribersTest.java | 1 - .../io/rsocket/core/RSocketRequesterTest.java | 25 ++--- .../io/rsocket/core/RSocketResponderTest.java | 17 +--- .../java/io/rsocket/core/RSocketTest.java | 93 ++++--------------- .../io/rsocket/core/SetupRejectionTest.java | 6 -- 15 files changed, 90 insertions(+), 253 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java b/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java index 178cc4fa9..e23bcceb2 100644 --- a/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java +++ b/rsocket-core/src/main/java/io/rsocket/RSocketFactory.java @@ -111,7 +111,7 @@ public static class ClientRSocketFactory implements ClientTransportAcceptor { private Resume resume; public ClientRSocketFactory() { - this(RSocketConnector.create().errorConsumer(Throwable::printStackTrace)); + this(RSocketConnector.create()); } public ClientRSocketFactory(RSocketConnector connector) { @@ -393,9 +393,13 @@ public ClientRSocketFactory fragment(int mtu) { return this; } - /** @deprecated this is deprecated with no replacement. */ + /** + * @deprecated this handler is deliberately no-ops and is deprecated with no replacement. In + * order to observe errors, it is recommended to add error handler using {@code doOnError} + * on the specific logical stream. In order to observe connection, or RSocket terminal + * errors, it is recommended to hook on {@link Closeable#onClose()} handler. + */ public ClientRSocketFactory errorConsumer(Consumer errorConsumer) { - connector.errorConsumer(errorConsumer); return this; } @@ -417,7 +421,7 @@ public static class ServerRSocketFactory implements ServerTransportAcceptor { private Resume resume; public ServerRSocketFactory() { - this(RSocketServer.create().errorConsumer(Throwable::printStackTrace)); + this(RSocketServer.create()); } public ServerRSocketFactory(RSocketServer server) { @@ -497,9 +501,13 @@ public ServerRSocketFactory fragment(int mtu) { return this; } - /** @deprecated this is deprecated with no replacement. */ + /** + * @deprecated this handler is deliberately no-ops and is deprecated with no replacement. In + * order to observe errors, it is recommended to add error handler using {@code doOnError} + * on the specific logical stream. In order to observe connection, or RSocket terminal + * errors, it is recommended to hook on {@link Closeable#onClose()} handler. + */ public ServerRSocketFactory errorConsumer(Consumer errorConsumer) { - server.errorConsumer(errorConsumer); return this; } diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java index 38393c27d..d9622e0f0 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java @@ -91,8 +91,6 @@ public class RSocketConnector { private int mtu = 0; private PayloadDecoder payloadDecoder = PayloadDecoder.DEFAULT; - private Consumer errorConsumer = ex -> {}; - private RSocketConnector() {} /** @@ -436,17 +434,6 @@ public RSocketConnector payloadDecoder(PayloadDecoder decoder) { return this; } - /** - * @deprecated this is deprecated with no replacement and will be removed after {@link - * io.rsocket.RSocketFactory} is removed. - */ - @Deprecated - public RSocketConnector errorConsumer(Consumer errorConsumer) { - Objects.requireNonNull(errorConsumer); - this.errorConsumer = errorConsumer; - return this; - } - /** * The final step to connect with the transport to use as input and the resulting {@code * Mono} as output. Each subscriber to the returned {@code Mono} starts a new connection @@ -524,7 +511,6 @@ public Mono connect(Supplier transportSupplier) { new RSocketRequester( multiplexer.asClientConnection(), payloadDecoder, - errorConsumer, StreamIdSupplier.clientSupplier(), mtu, (int) keepAliveInterval.toMillis(), @@ -564,7 +550,6 @@ public Mono connect(Supplier transportSupplier) { CLIENT_TAG, wrappedConnection.alloc(), leases.sender(), - errorConsumer, leases.stats()) : ResponderLeaseHandler.None; @@ -573,7 +558,6 @@ public Mono connect(Supplier transportSupplier) { multiplexer.asServerConnection(), wrappedRSocketHandler, payloadDecoder, - errorConsumer, responderLeaseHandler, mtu); diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index 3de074953..cdfda0755 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -59,6 +59,8 @@ import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import reactor.core.publisher.BaseSubscriber; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -74,9 +76,8 @@ * Requester Side of a RSocket socket. Sends {@link ByteBuf}s to a {@link RSocketResponder} of peer */ class RSocketRequester implements RSocket { - private static final AtomicReferenceFieldUpdater TERMINATION_ERROR = - AtomicReferenceFieldUpdater.newUpdater( - RSocketRequester.class, Throwable.class, "terminationError"); + private static final Logger LOGGER = LoggerFactory.getLogger(RSocketRequester.class); + private static final Exception CLOSED_CHANNEL_EXCEPTION = new ClosedChannelException(); private static final Consumer DROPPED_ELEMENTS_CONSUMER = referenceCounted -> { @@ -93,9 +94,14 @@ class RSocketRequester implements RSocket { CLOSED_CHANNEL_EXCEPTION.setStackTrace(new StackTraceElement[0]); } + private volatile Throwable terminationError; + + private static final AtomicReferenceFieldUpdater TERMINATION_ERROR = + AtomicReferenceFieldUpdater.newUpdater( + RSocketRequester.class, Throwable.class, "terminationError"); + private final DuplexConnection connection; private final PayloadDecoder payloadDecoder; - private final Consumer errorConsumer; private final StreamIdSupplier streamIdSupplier; private final IntObjectMap senders; private final IntObjectMap> receivers; @@ -104,14 +110,12 @@ class RSocketRequester implements RSocket { private final RequesterLeaseHandler leaseHandler; private final ByteBufAllocator allocator; private final KeepAliveFramesAcceptor keepAliveFramesAcceptor; - private volatile Throwable terminationError; private final MonoProcessor onClose; private final Scheduler serialScheduler; RSocketRequester( DuplexConnection connection, PayloadDecoder payloadDecoder, - Consumer errorConsumer, StreamIdSupplier streamIdSupplier, int mtu, int keepAliveTickPeriod, @@ -122,7 +126,6 @@ class RSocketRequester implements RSocket { this.connection = connection; this.allocator = connection.alloc(); this.payloadDecoder = payloadDecoder; - this.errorConsumer = errorConsumer; this.streamIdSupplier = streamIdSupplier; this.mtu = mtu; this.leaseHandler = leaseHandler; @@ -140,7 +143,7 @@ class RSocketRequester implements RSocket { .subscribe(null, this::tryTerminateOnConnectionError, this::tryTerminateOnConnectionClose); connection.send(sendProcessor).subscribe(null, this::handleSendProcessorError); - connection.receive().subscribe(this::handleIncomingFrames, errorConsumer); + connection.receive().subscribe(this::handleIncomingFrames, e -> {}); if (keepAliveTickPeriod != 0 && keepAliveHandler != null) { KeepAliveSupport keepAliveSupport = @@ -396,7 +399,6 @@ private Flux handleChannel(Flux request) { payload.release(); final IllegalArgumentException t = new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE); - errorConsumer.accept(t); return Mono.error(t); } return handleChannel(payload, flux); @@ -446,7 +448,6 @@ protected void hookOnNext(Payload payload) { cancel(); final IllegalArgumentException t = new IllegalArgumentException(INVALID_PAYLOAD_ERROR_MESSAGE); - errorConsumer.accept(t); // no need to send any errors. sendProcessor.onNext(CancelFrameCodec.encode(allocator, streamId)); receiver.onError(t); @@ -609,9 +610,9 @@ private void handleStreamZero(FrameType type, ByteBuf frame) { break; default: // Ignore unknown frames. Throwing an error will close the socket. - errorConsumer.accept( - new IllegalStateException( - "Client received supported frame on stream 0: " + frame.toString())); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Requester received unsupported frame on stream 0: " + frame.toString()); + } } } @@ -668,7 +669,7 @@ private void handleFrame(int streamId, FrameType type, ByteBuf frame) { } default: throw new IllegalStateException( - "Client received supported frame on stream " + streamId + ": " + frame.toString()); + "Requester received unsupported frame on stream " + streamId + ": " + frame.toString()); } } @@ -736,7 +737,9 @@ private void terminate(Throwable e) { try { receiver.onError(e); } catch (Throwable t) { - errorConsumer.accept(t); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Dropped exception", t); + } } }); } @@ -748,14 +751,15 @@ private void terminate(Throwable e) { try { sender.cancel(); } catch (Throwable t) { - errorConsumer.accept(t); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Dropped exception", t); + } } }); } senders.clear(); receivers.clear(); sendProcessor.dispose(); - errorConsumer.accept(e); onClose.onError(e); } diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java index d5f9206d8..ca5e605c7 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java @@ -43,6 +43,8 @@ import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import reactor.core.Disposable; import reactor.core.Exceptions; import reactor.core.publisher.*; @@ -50,6 +52,8 @@ /** Responder side of RSocket. Receives {@link ByteBuf}s from a peer's {@link RSocketRequester} */ class RSocketResponder implements RSocket { + private static final Logger LOGGER = LoggerFactory.getLogger(RSocketResponder.class); + private static final Consumer DROPPED_ELEMENTS_CONSUMER = referenceCounted -> { if (referenceCounted.refCnt() > 0) { @@ -69,7 +73,6 @@ class RSocketResponder implements RSocket { private final io.rsocket.ResponderRSocket responderRSocket; private final PayloadDecoder payloadDecoder; - private final Consumer errorConsumer; private final ResponderLeaseHandler leaseHandler; private final Disposable leaseHandlerDisposable; private final MonoProcessor onClose; @@ -87,12 +90,10 @@ class RSocketResponder implements RSocket { private final UnboundedProcessor sendProcessor; private final ByteBufAllocator allocator; - @SuppressWarnings("deprecation") RSocketResponder( DuplexConnection connection, RSocket requestHandler, PayloadDecoder payloadDecoder, - Consumer errorConsumer, ResponderLeaseHandler leaseHandler, int mtu) { this.connection = connection; @@ -106,7 +107,6 @@ class RSocketResponder implements RSocket { : null; this.payloadDecoder = payloadDecoder; - this.errorConsumer = errorConsumer; this.leaseHandler = leaseHandler; this.sendingSubscriptions = new SynchronizedIntObjectHashMap<>(); this.channelProcessors = new SynchronizedIntObjectHashMap<>(); @@ -118,7 +118,7 @@ class RSocketResponder implements RSocket { connection.send(sendProcessor).subscribe(null, this::handleSendProcessorError); - connection.receive().subscribe(this::handleFrame, errorConsumer); + connection.receive().subscribe(this::handleFrame, e -> {}); leaseHandlerDisposable = leaseHandler.send(sendProcessor::onNextPrioritized); this.connection @@ -135,7 +135,9 @@ private void handleSendProcessorError(Throwable t) { try { subscription.cancel(); } catch (Throwable e) { - errorConsumer.accept(e); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Dropped exception", t); + } } }); @@ -146,7 +148,9 @@ private void handleSendProcessorError(Throwable t) { try { subscription.onError(t); } catch (Throwable e) { - errorConsumer.accept(e); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Dropped exception", t); + } } }); } @@ -375,9 +379,7 @@ protected void hookOnSubscribe(Subscription subscription) { } @Override - protected void hookOnError(Throwable throwable) { - errorConsumer.accept(throwable); - } + protected void hookOnError(Throwable throwable) {} @Override protected void hookFinally(SignalType type) { @@ -583,9 +585,7 @@ protected void hookOnSubscribe(Subscription subscription) { } @Override - protected void hookOnError(Throwable throwable) { - errorConsumer.accept(throwable); - } + protected void hookOnError(Throwable throwable) {} }); } @@ -599,7 +599,6 @@ private void handleCancelFrame(int streamId) { } private void handleError(int streamId, Throwable t) { - errorConsumer.accept(t); sendProcessor.onNext(ErrorFrameCodec.encode(allocator, streamId, t)); } diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java index a0f7c810b..6390d44dd 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java @@ -67,8 +67,6 @@ public final class RSocketServer { private int mtu = 0; private PayloadDecoder payloadDecoder = PayloadDecoder.DEFAULT; - private Consumer errorConsumer = ex -> {}; - private RSocketServer() {} /** Static factory method to create an {@code RSocketServer}. */ @@ -247,16 +245,6 @@ public RSocketServer payloadDecoder(PayloadDecoder decoder) { return this; } - /** - * @deprecated this is deprecated with no replacement and will be removed after {@link - * io.rsocket.RSocketFactory} is removed. - */ - @Deprecated - public RSocketServer errorConsumer(Consumer errorConsumer) { - this.errorConsumer = errorConsumer; - return this; - } - /** * Start the server on the given transport. * @@ -395,7 +383,6 @@ private Mono acceptSetup( new RSocketRequester( wrappedMultiplexer.asServerConnection(), payloadDecoder, - errorConsumer, StreamIdSupplier.serverSupplier(), mtu, setupPayload.keepAliveInterval(), @@ -422,11 +409,7 @@ private Mono acceptSetup( ResponderLeaseHandler responderLeaseHandler = leaseEnabled ? new ResponderLeaseHandler.Impl<>( - SERVER_TAG, - connection.alloc(), - leases.sender(), - errorConsumer, - leases.stats()) + SERVER_TAG, connection.alloc(), leases.sender(), leases.stats()) : ResponderLeaseHandler.None; RSocket rSocketResponder = @@ -434,7 +417,6 @@ private Mono acceptSetup( connection, wrappedRSocketHandler, payloadDecoder, - errorConsumer, responderLeaseHandler, mtu); }) diff --git a/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java b/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java index 0d24c51d8..038120efc 100644 --- a/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java +++ b/rsocket-core/src/main/java/io/rsocket/internal/ClientServerInputMultiplexer.java @@ -119,10 +119,7 @@ public ClientServerInputMultiplexer( break; } }, - t -> { - LOGGER.error("Error receiving frame:", t); - dispose(); - }); + t -> {}); } public DuplexConnection asClientServerConnection() { diff --git a/rsocket-core/src/main/java/io/rsocket/lease/ResponderLeaseHandler.java b/rsocket-core/src/main/java/io/rsocket/lease/ResponderLeaseHandler.java index 2035ade87..df8787cb7 100644 --- a/rsocket-core/src/main/java/io/rsocket/lease/ResponderLeaseHandler.java +++ b/rsocket-core/src/main/java/io/rsocket/lease/ResponderLeaseHandler.java @@ -41,7 +41,6 @@ final class Impl implements ResponderLeaseHandler { private final String tag; private final ByteBufAllocator allocator; private final Function, Flux> leaseSender; - private final Consumer errorConsumer; private final Optional leaseStatsOption; private final T leaseStats; @@ -49,12 +48,10 @@ public Impl( String tag, ByteBufAllocator allocator, Function, Flux> leaseSender, - Consumer errorConsumer, Optional leaseStatsOption) { this.tag = tag; this.allocator = allocator; this.leaseSender = leaseSender; - this.errorConsumer = errorConsumer; this.leaseStatsOption = leaseStatsOption; this.leaseStats = leaseStatsOption.orElse(null); } @@ -86,8 +83,7 @@ public Disposable send(Consumer leaseFrameSender) { lease -> { currentLease = create(lease); leaseFrameSender.accept(createLeaseFrame(lease)); - }, - errorConsumer); + }); } @Override diff --git a/rsocket-core/src/test/java/io/rsocket/core/AbstractSocketRule.java b/rsocket-core/src/test/java/io/rsocket/core/AbstractSocketRule.java index 20972a0d3..ac5832aaa 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/AbstractSocketRule.java +++ b/rsocket-core/src/test/java/io/rsocket/core/AbstractSocketRule.java @@ -21,8 +21,6 @@ import io.rsocket.buffer.LeaksTrackingByteBufAllocator; import io.rsocket.test.util.TestDuplexConnection; import io.rsocket.test.util.TestSubscriber; -import java.util.concurrent.ConcurrentLinkedQueue; -import org.junit.Assert; import org.junit.rules.ExternalResource; import org.junit.runner.Description; import org.junit.runners.model.Statement; @@ -33,7 +31,6 @@ public abstract class AbstractSocketRule extends ExternalReso protected TestDuplexConnection connection; protected Subscriber connectSub; protected T socket; - protected ConcurrentLinkedQueue errors; protected LeaksTrackingByteBufAllocator allocator; @Override @@ -44,7 +41,6 @@ public void evaluate() throws Throwable { allocator = LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); connection = new TestDuplexConnection(allocator); connectSub = TestSubscriber.create(); - errors = new ConcurrentLinkedQueue<>(); init(); base.evaluate(); } @@ -57,12 +53,6 @@ protected void init() { protected abstract T newRSocket(); - public void assertNoConnectionErrors() { - if (errors.size() > 1) { - Assert.fail("No connection errors expected: " + errors.peek().toString()); - } - } - public ByteBufAllocator alloc() { return allocator; } diff --git a/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java b/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java index b3ded08ec..d98f86113 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/KeepAliveTest.java @@ -35,9 +35,6 @@ import io.rsocket.test.util.TestDuplexConnection; import io.rsocket.util.DefaultPayload; import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -57,12 +54,10 @@ static RSocketState requester(int tickPeriod, int timeout) { LeaksTrackingByteBufAllocator allocator = LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); TestDuplexConnection connection = new TestDuplexConnection(allocator); - Errors errors = new Errors(); RSocketRequester rSocket = new RSocketRequester( connection, DefaultPayload::create, - errors, StreamIdSupplier.clientSupplier(), 0, tickPeriod, @@ -70,7 +65,7 @@ static RSocketState requester(int tickPeriod, int timeout) { new DefaultKeepAliveHandler(connection), RequesterLeaseHandler.None, TestScheduler.INSTANCE); - return new RSocketState(rSocket, errors, allocator, connection); + return new RSocketState(rSocket, allocator, connection); } static ResumableRSocketState resumableRequester(int tickPeriod, int timeout) { @@ -85,12 +80,10 @@ static ResumableRSocketState resumableRequester(int tickPeriod, int timeout) { Duration.ofSeconds(10), false); - Errors errors = new Errors(); RSocketRequester rSocket = new RSocketRequester( resumableConnection, DefaultPayload::create, - errors, StreamIdSupplier.clientSupplier(), 0, tickPeriod, @@ -98,7 +91,7 @@ static ResumableRSocketState resumableRequester(int tickPeriod, int timeout) { new ResumableKeepAliveHandler(resumableConnection), RequesterLeaseHandler.None, TestScheduler.INSTANCE); - return new ResumableRSocketState(rSocket, errors, connection, resumableConnection, allocator); + return new ResumableRSocketState(rSocket, connection, resumableConnection, allocator); } @BeforeEach @@ -121,10 +114,8 @@ void rSocketNotDisposedOnPresentKeepAlives() { Mono.delay(Duration.ofMillis(2000)).block(); RSocket rSocket = requesterState.rSocket(); - List errors = requesterState.errors().errors(); Assertions.assertThat(rSocket.isDisposed()).isFalse(); - Assertions.assertThat(errors).isEmpty(); } @Test @@ -143,11 +134,12 @@ void rSocketDisposedOnMissingKeepAlives() { Mono.delay(Duration.ofMillis(2000)).block(); - List errors = requesterState.errors().errors(); Assertions.assertThat(rSocket.isDisposed()).isTrue(); - Assertions.assertThat(errors).hasSize(1); - Throwable throwable = errors.get(0); - Assertions.assertThat(throwable).isInstanceOf(ConnectionErrorException.class); + rSocket + .onClose() + .as(StepVerifier::create) + .expectError(ConnectionErrorException.class) + .verify(Duration.ofMillis(100)); } @Test @@ -224,13 +216,11 @@ void resumableRequesterNoKeepAlivesAfterDispose() { @Test void resumableRSocketsNotDisposedOnMissingKeepAlives() { RSocket rSocket = resumableRequesterState.rSocket(); - List errors = resumableRequesterState.errors().errors(); TestDuplexConnection connection = resumableRequesterState.connection(); Mono.delay(Duration.ofMillis(500)).block(); Assertions.assertThat(rSocket.isDisposed()).isFalse(); - Assertions.assertThat(errors).hasSize(0); Assertions.assertThat(connection.isDisposed()).isTrue(); } @@ -248,17 +238,12 @@ private boolean keepAliveFrameWithoutRespondFlag(ByteBuf frame) { static class RSocketState { private final RSocket rSocket; - private final Errors errors; private final TestDuplexConnection connection; private final LeaksTrackingByteBufAllocator allocator; public RSocketState( - RSocket rSocket, - Errors errors, - LeaksTrackingByteBufAllocator allocator, - TestDuplexConnection connection) { + RSocket rSocket, LeaksTrackingByteBufAllocator allocator, TestDuplexConnection connection) { this.rSocket = rSocket; - this.errors = errors; this.connection = connection; this.allocator = allocator; } @@ -271,10 +256,6 @@ public RSocket rSocket() { return rSocket; } - public Errors errors() { - return errors; - } - public LeaksTrackingByteBufAllocator alloc() { return allocator; } @@ -282,19 +263,16 @@ public LeaksTrackingByteBufAllocator alloc() { static class ResumableRSocketState { private final RSocket rSocket; - private final Errors errors; private final TestDuplexConnection connection; private final ResumableDuplexConnection resumableDuplexConnection; private final LeaksTrackingByteBufAllocator allocator; public ResumableRSocketState( RSocket rSocket, - Errors errors, TestDuplexConnection connection, ResumableDuplexConnection resumableDuplexConnection, LeaksTrackingByteBufAllocator allocator) { this.rSocket = rSocket; - this.errors = errors; this.connection = connection; this.resumableDuplexConnection = resumableDuplexConnection; this.allocator = allocator; @@ -312,25 +290,8 @@ public RSocket rSocket() { return rSocket; } - public Errors errors() { - return errors; - } - public LeaksTrackingByteBufAllocator alloc() { return allocator; } } - - static class Errors implements Consumer { - private final List errors = new ArrayList<>(); - - @Override - public void accept(Throwable throwable) { - errors.add(throwable); - } - - public List errors() { - return new ArrayList<>(errors); - } - } } diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java index ddfbe4234..ab336b8cd 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketLeaseTest.java @@ -85,7 +85,7 @@ void setUp() { requesterLeaseHandler = new RequesterLeaseHandler.Impl(TAG, leases -> leaseReceiver = leases); responderLeaseHandler = new ResponderLeaseHandler.Impl<>( - TAG, byteBufAllocator, stats -> leaseSender, err -> {}, Optional.empty()); + TAG, byteBufAllocator, stats -> leaseSender, Optional.empty()); ClientServerInputMultiplexer multiplexer = new ClientServerInputMultiplexer(connection, new InitializingInterceptorRegistry(), true); @@ -93,7 +93,6 @@ void setUp() { new RSocketRequester( multiplexer.asClientConnection(), payloadDecoder, - err -> {}, StreamIdSupplier.clientSupplier(), 0, 0, @@ -114,7 +113,6 @@ void setUp() { multiplexer.asServerConnection(), mockRSocketHandler, payloadDecoder, - err -> {}, responderLeaseHandler, 0); } diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java index 00f74152a..4cd3a3a26 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterSubscribersTest.java @@ -66,7 +66,6 @@ void setUp() { new RSocketRequester( connection, PayloadDecoder.DEFAULT, - err -> {}, StreamIdSupplier.clientSupplier(), 0, 0, diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java index e4943e9b0..1ba75f75a 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketRequesterTest.java @@ -24,10 +24,8 @@ import static io.rsocket.frame.FrameType.REQUEST_RESPONSE; import static io.rsocket.frame.FrameType.REQUEST_STREAM; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; @@ -122,13 +120,9 @@ public void tearDown() { @Test @Timeout(2_000) - public void testInvalidFrameOnStream0() { + public void testInvalidFrameOnStream0ShouldNotTerminateRSocket() { rule.connection.addToReceivedBuffer(RequestNFrameCodec.encode(rule.alloc(), 0, 10)); - assertThat("Unexpected errors.", rule.errors, hasSize(1)); - assertThat( - "Unexpected error received.", - rule.errors, - contains(instanceOf(IllegalStateException.class))); + Assertions.assertThat(rule.socket.isDisposed()).isFalse(); rule.assertHasNoLeaks(); } @@ -167,11 +161,8 @@ protected void hookOnSubscribe(Subscription subscription) { public void testHandleSetupException() { rule.connection.addToReceivedBuffer( ErrorFrameCodec.encode(rule.alloc(), 0, new RejectedSetupException("boom"))); - assertThat("Unexpected errors.", rule.errors, hasSize(1)); - assertThat( - "Unexpected error received.", - rule.errors, - contains(instanceOf(RejectedSetupException.class))); + Assertions.assertThatThrownBy(() -> rule.socket.onClose().block()) + .isInstanceOf(RejectedSetupException.class); rule.assertHasNoLeaks(); } @@ -242,7 +233,12 @@ public void testRequestReplyErrorOnSend() { Subscriber responseSub = TestSubscriber.create(10); response.subscribe(responseSub); - this.rule.assertNoConnectionErrors(); + this.rule + .socket + .onClose() + .as(StepVerifier::create) + .expectComplete() + .verify(Duration.ofMillis(100)); verify(responseSub).onSubscribe(any(Subscription.class)); @@ -1006,7 +1002,6 @@ protected RSocketRequester newRSocket() { return new RSocketRequester( connection, PayloadDecoder.ZERO_COPY, - throwable -> errors.add(throwable), StreamIdSupplier.clientSupplier(), 0, 0, diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java index e34973848..036dc2eef 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketResponderTest.java @@ -59,7 +59,6 @@ import io.rsocket.util.DefaultPayload; import io.rsocket.util.EmptyPayload; import java.util.Collection; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; @@ -138,7 +137,6 @@ public void testHandleResponseFrameNoError() throws Exception { Collection> sendSubscribers = rule.connection.getSendSubscribers(); assertThat("Request not sent.", sendSubscribers, hasSize(1)); - assertThat("Unexpected error.", rule.errors, is(empty())); Subscriber sendSub = sendSubscribers.iterator().next(); assertThat( "Unexpected frame sent.", @@ -152,7 +150,6 @@ public void testHandleResponseFrameNoError() throws Exception { public void testHandlerEmitsError() throws Exception { final int streamId = 4; rule.sendRequest(streamId, FrameType.REQUEST_STREAM); - assertThat("Unexpected error.", rule.errors, is(empty())); assertThat( "Unexpected frame sent.", frameType(rule.connection.awaitSend()), is(FrameType.ERROR)); } @@ -173,7 +170,6 @@ public Mono requestResponse(Payload payload) { }); rule.sendRequest(streamId, FrameType.REQUEST_RESPONSE); - assertThat("Unexpected error.", rule.errors, is(empty())); assertThat("Unexpected frame sent.", rule.connection.getSent(), is(empty())); rule.connection.addToReceivedBuffer(CancelFrameCodec.encode(allocator, streamId)); @@ -232,10 +228,6 @@ protected void hookOnSubscribe(Subscription subscription) { for (Runnable runnable : runnables) { rule.connection.clearSendReceiveBuffers(); runnable.run(); - Assertions.assertThat(rule.errors) - .first() - .isInstanceOf(IllegalArgumentException.class) - .hasToString("java.lang.IllegalArgumentException: " + INVALID_PAYLOAD_ERROR_MESSAGE); Assertions.assertThat(rule.connection.getSent()) .hasSize(1) .first() @@ -778,7 +770,6 @@ public void setAcceptingSocket(RSocket acceptingSocket) { this.acceptingSocket = acceptingSocket; connection = new TestDuplexConnection(alloc()); connectSub = TestSubscriber.create(); - errors = new ConcurrentLinkedQueue<>(); this.prefetch = Integer.MAX_VALUE; super.init(); } @@ -787,7 +778,6 @@ public void setAcceptingSocket(RSocket acceptingSocket, int prefetch) { this.acceptingSocket = acceptingSocket; connection = new TestDuplexConnection(alloc()); connectSub = TestSubscriber.create(); - errors = new ConcurrentLinkedQueue<>(); this.prefetch = prefetch; super.init(); } @@ -795,12 +785,7 @@ public void setAcceptingSocket(RSocket acceptingSocket, int prefetch) { @Override protected RSocketResponder newRSocket() { return new RSocketResponder( - connection, - acceptingSocket, - PayloadDecoder.ZERO_COPY, - throwable -> errors.add(throwable), - ResponderLeaseHandler.None, - 0); + connection, acceptingSocket, PayloadDecoder.ZERO_COPY, ResponderLeaseHandler.None, 0); } private void sendRequest(int streamId, FrameType frameType) { diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java index 48ce150d6..f032942db 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java @@ -16,11 +16,6 @@ package io.rsocket.core; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.is; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.verify; - import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.rsocket.Payload; @@ -34,24 +29,18 @@ import io.rsocket.lease.RequesterLeaseHandler; import io.rsocket.lease.ResponderLeaseHandler; import io.rsocket.test.util.LocalDuplexConnection; -import io.rsocket.test.util.TestSubscriber; import io.rsocket.util.DefaultPayload; import io.rsocket.util.EmptyPayload; import java.time.Duration; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.assertj.core.api.Assertions; -import org.hamcrest.MatcherAssert; -import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExternalResource; import org.junit.runner.Description; import org.junit.runners.model.Statement; -import org.mockito.ArgumentCaptor; import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; import reactor.core.publisher.DirectProcessor; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -62,16 +51,6 @@ public class RSocketTest { @Rule public final SocketRule rule = new SocketRule(); - public static void assertError(String s, String mode, ArrayList errors) { - for (Throwable t : errors) { - if (t.toString().equals(s)) { - return; - } - } - - Assert.fail("Expected " + mode + " connection error: " + s + " other errors " + errors.size()); - } - @Test(timeout = 2_000) public void testRequestReplyNoError() { StepVerifier.create(rule.crs.requestResponse(DefaultPayload.create("hello"))) @@ -89,14 +68,15 @@ public Mono requestResponse(Payload payload) { return Mono.error(new NullPointerException("Deliberate exception.")); } }); - Subscriber subscriber = TestSubscriber.create(); - rule.crs.requestResponse(EmptyPayload.INSTANCE).subscribe(subscriber); - verify(subscriber).onError(any(ApplicationErrorException.class)); - - // Client sees error through normal API - rule.assertNoClientErrors(); - - rule.assertServerError("java.lang.NullPointerException: Deliberate exception."); + rule.crs + .requestResponse(EmptyPayload.INSTANCE) + .as(StepVerifier::create) + .expectErrorSatisfies( + t -> + Assertions.assertThat(t) + .isInstanceOf(ApplicationErrorException.class) + .hasMessage("Deliberate exception.")) + .verify(Duration.ofMillis(100)); } @Test(timeout = 2000) @@ -109,21 +89,16 @@ public Mono requestResponse(Payload payload) { new CustomRSocketException(0x00000501, "Deliberate Custom exception.")); } }); - Subscriber subscriber = TestSubscriber.create(); - rule.crs.requestResponse(EmptyPayload.INSTANCE).subscribe(subscriber); - ArgumentCaptor customRSocketExceptionArgumentCaptor = - ArgumentCaptor.forClass(CustomRSocketException.class); - verify(subscriber).onError(customRSocketExceptionArgumentCaptor.capture()); - - Assert.assertEquals( - "Deliberate Custom exception.", - customRSocketExceptionArgumentCaptor.getValue().getMessage()); - Assert.assertEquals(0x00000501, customRSocketExceptionArgumentCaptor.getValue().errorCode()); - - // Client sees error through normal API - rule.assertNoClientErrors(); - - rule.assertServerError("CustomRSocketException (0x501): Deliberate Custom exception."); + rule.crs + .requestResponse(EmptyPayload.INSTANCE) + .as(StepVerifier::create) + .expectErrorSatisfies( + t -> + Assertions.assertThat(t) + .isInstanceOf(CustomRSocketException.class) + .hasMessage("Deliberate Custom exception.") + .hasFieldOrPropertyWithValue("errorCode", 0x00000501)) + .verify(); } @Test(timeout = 2000) @@ -147,9 +122,6 @@ public Flux requestChannel(Publisher payloads) { .expectNextCount(3) .expectComplete() .verify(Duration.ofMillis(5000)); - - rule.assertNoClientErrors(); - rule.assertNoServerErrors(); } @Test(timeout = 2000) @@ -413,8 +385,6 @@ public static class SocketRule extends ExternalResource { private RSocketResponder srs; private RSocket requestAcceptor; - private ArrayList clientErrors = new ArrayList<>(); - private ArrayList serverErrors = new ArrayList<>(); private LeaksTrackingByteBufAllocator allocator; @@ -479,7 +449,6 @@ public Flux requestChannel(Publisher payloads) { serverConnection, requestAcceptor, PayloadDecoder.DEFAULT, - throwable -> serverErrors.add(throwable), ResponderLeaseHandler.None, 0); @@ -487,7 +456,6 @@ public Flux requestChannel(Publisher payloads) { new RSocketRequester( clientConnection, PayloadDecoder.DEFAULT, - throwable -> clientErrors.add(throwable), StreamIdSupplier.clientSupplier(), 0, 0, @@ -501,28 +469,5 @@ public void setRequestAcceptor(RSocket requestAcceptor) { this.requestAcceptor = requestAcceptor; init(); } - - public void assertNoErrors() { - assertNoClientErrors(); - assertNoServerErrors(); - } - - public void assertNoClientErrors() { - MatcherAssert.assertThat( - "Unexpected error on the client connection.", clientErrors, is(empty())); - } - - public void assertNoServerErrors() { - MatcherAssert.assertThat( - "Unexpected error on the server connection.", serverErrors, is(empty())); - } - - public void assertClientError(String s) { - assertError(s, "client", this.clientErrors); - } - - public void assertServerError(String s) { - assertError(s, "server", this.serverErrors); - } } } diff --git a/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java b/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java index 75a5e070e..2957a051e 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/SetupRejectionTest.java @@ -18,8 +18,6 @@ import io.rsocket.transport.ServerTransport; import io.rsocket.util.DefaultPayload; import java.time.Duration; -import java.util.ArrayList; -import java.util.List; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; @@ -53,12 +51,10 @@ void requesterStreamsTerminatedOnZeroErrorFrame() { LeaksTrackingByteBufAllocator allocator = LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); TestDuplexConnection conn = new TestDuplexConnection(allocator); - List errors = new ArrayList<>(); RSocketRequester rSocket = new RSocketRequester( conn, DefaultPayload::create, - errors::add, StreamIdSupplier.clientSupplier(), 0, 0, @@ -83,7 +79,6 @@ void requesterStreamsTerminatedOnZeroErrorFrame() { err -> err instanceof RejectedSetupException && errorMsg.equals(err.getMessage())) .verify(Duration.ofSeconds(5)); - assertThat(errors).hasSize(1); assertThat(rSocket.isDisposed()).isTrue(); } @@ -96,7 +91,6 @@ void requesterNewStreamsTerminatedAfterZeroErrorFrame() { new RSocketRequester( conn, DefaultPayload::create, - err -> {}, StreamIdSupplier.clientSupplier(), 0, 0, From d880f51862b71e8790a9883a05963991f779e4a8 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Mon, 11 May 2020 16:30:39 +0300 Subject: [PATCH 57/62] fixes onClose behaviour to not error on shutdown (#833) --- .../io/rsocket/core/RSocketRequester.java | 26 ++++++++------- .../io/rsocket/core/RSocketResponder.java | 7 ++-- .../java/io/rsocket/core/RSocketTest.java | 33 +++++++++++++++++++ 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java index cdfda0755..f7fb161fd 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketRequester.java @@ -50,7 +50,6 @@ import io.rsocket.keepalive.KeepAliveSupport; import io.rsocket.lease.RequesterLeaseHandler; import java.nio.channels.ClosedChannelException; -import java.util.concurrent.CancellationException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.Consumer; @@ -137,10 +136,7 @@ class RSocketRequester implements RSocket { // DO NOT Change the order here. The Send processor must be subscribed to before receiving this.sendProcessor = new UnboundedProcessor<>(); - connection - .onClose() - .or(onClose) - .subscribe(null, this::tryTerminateOnConnectionError, this::tryTerminateOnConnectionClose); + connection.onClose().subscribe(null, this::tryTerminateOnConnectionError, this::tryShutdown); connection.send(sendProcessor).subscribe(null, this::handleSendProcessorError); connection.receive().subscribe(this::handleIncomingFrames, e -> {}); @@ -188,7 +184,7 @@ public double availability() { @Override public void dispose() { - tryTerminate(() -> new CancellationException("Disposed")); + tryShutdown(); } @Override @@ -708,10 +704,6 @@ private void tryTerminateOnConnectionError(Throwable e) { tryTerminate(() -> e); } - private void tryTerminateOnConnectionClose() { - tryTerminate(() -> CLOSED_CHANNEL_EXCEPTION); - } - private void tryTerminateOnZeroError(ByteBuf errorFrame) { tryTerminate(() -> Exceptions.from(0, errorFrame)); } @@ -725,6 +717,14 @@ private void tryTerminate(Supplier errorSupplier) { } } + private void tryShutdown() { + if (terminationError == null) { + if (TERMINATION_ERROR.compareAndSet(this, null, CLOSED_CHANNEL_EXCEPTION)) { + terminate(CLOSED_CHANNEL_EXCEPTION); + } + } + } + private void terminate(Throwable e) { connection.dispose(); leaseHandler.dispose(); @@ -760,7 +760,11 @@ private void terminate(Throwable e) { senders.clear(); receivers.clear(); sendProcessor.dispose(); - onClose.onError(e); + if (e == CLOSED_CHANNEL_EXCEPTION) { + onClose.onComplete(); + } else { + onClose.onError(e); + } } private void handleSendProcessorError(Throwable t) { diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java index ca5e605c7..d3860e5f2 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketResponder.java @@ -75,7 +75,6 @@ class RSocketResponder implements RSocket { private final PayloadDecoder payloadDecoder; private final ResponderLeaseHandler leaseHandler; private final Disposable leaseHandlerDisposable; - private final MonoProcessor onClose; private volatile Throwable terminationError; private static final AtomicReferenceFieldUpdater TERMINATION_ERROR = @@ -110,7 +109,6 @@ class RSocketResponder implements RSocket { this.leaseHandler = leaseHandler; this.sendingSubscriptions = new SynchronizedIntObjectHashMap<>(); this.channelProcessors = new SynchronizedIntObjectHashMap<>(); - this.onClose = MonoProcessor.create(); // DO NOT Change the order here. The Send processor must be subscribed to before receiving // connections @@ -123,7 +121,6 @@ class RSocketResponder implements RSocket { this.connection .onClose() - .or(onClose) .subscribe(null, this::tryTerminateOnConnectionError, this::tryTerminateOnConnectionClose); } @@ -256,12 +253,12 @@ public void dispose() { @Override public boolean isDisposed() { - return onClose.isDisposed(); + return connection.isDisposed(); } @Override public Mono onClose() { - return onClose; + return connection.onClose(); } private void cleanup(Throwable e) { diff --git a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java index f032942db..692894fd6 100644 --- a/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java +++ b/rsocket-core/src/test/java/io/rsocket/core/RSocketTest.java @@ -41,6 +41,8 @@ import org.junit.runner.Description; import org.junit.runners.model.Statement; import org.reactivestreams.Publisher; +import reactor.core.Disposable; +import reactor.core.Disposables; import reactor.core.publisher.DirectProcessor; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -51,6 +53,34 @@ public class RSocketTest { @Rule public final SocketRule rule = new SocketRule(); + @Test + public void rsocketDisposalShouldEndupWithNoErrorsOnClose() { + RSocket requestHandlingRSocket = + new RSocket() { + final Disposable disposable = Disposables.single(); + + @Override + public void dispose() { + disposable.dispose(); + } + + @Override + public boolean isDisposed() { + return disposable.isDisposed(); + } + }; + rule.setRequestAcceptor(requestHandlingRSocket); + rule.crs + .onClose() + .as(StepVerifier::create) + .expectSubscription() + .then(rule.crs::dispose) + .expectComplete() + .verify(Duration.ofMillis(100)); + + Assertions.assertThat(requestHandlingRSocket.isDisposed()).isTrue(); + } + @Test(timeout = 2_000) public void testRequestReplyNoError() { StepVerifier.create(rule.crs.requestResponse(DefaultPayload.create("hello"))) @@ -413,6 +443,9 @@ protected void init() { LocalDuplexConnection clientConnection = new LocalDuplexConnection("client", allocator, serverProcessor, clientProcessor); + clientConnection.onClose().doFinally(__ -> serverConnection.dispose()).subscribe(); + serverConnection.onClose().doFinally(__ -> clientConnection.dispose()).subscribe(); + requestAcceptor = null != requestAcceptor ? requestAcceptor From a0e741c5af29a1ca16c8acaf316d6ae2067be776 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 11 May 2020 18:18:57 +0100 Subject: [PATCH 58/62] includes LimitRateInterceptor as a built-in interceptor (#834) --- .../io/rsocket/core/RSocketConnector.java | 1 + .../java/io/rsocket/core/RSocketServer.java | 1 + .../rsocket/plugins/LimitRateInterceptor.java | 133 ++++++++++++++++++ .../plugins/LimitRateInterceptorExample.java | 116 +++------------ 4 files changed, 151 insertions(+), 100 deletions(-) create mode 100644 rsocket-core/src/main/java/io/rsocket/plugins/LimitRateInterceptor.java diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java index d9622e0f0..02f1a51cc 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketConnector.java @@ -221,6 +221,7 @@ public RSocketConnector keepAlive(Duration interval, Duration maxLifeTime) { * * @param configurer a configurer to customize interception with. * @return the same instance for method chaining + * @see io.rsocket.plugins.LimitRateInterceptor */ public RSocketConnector interceptors(Consumer configurer) { configurer.accept(this.interceptors); diff --git a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java index 6390d44dd..c5734cecc 100644 --- a/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java +++ b/rsocket-core/src/main/java/io/rsocket/core/RSocketServer.java @@ -142,6 +142,7 @@ public RSocketServer acceptor(SocketAcceptor acceptor) { * * @param configurer a configurer to customize interception with. * @return the same instance for method chaining + * @see io.rsocket.plugins.LimitRateInterceptor */ public RSocketServer interceptors(Consumer configurer) { configurer.accept(this.interceptors); diff --git a/rsocket-core/src/main/java/io/rsocket/plugins/LimitRateInterceptor.java b/rsocket-core/src/main/java/io/rsocket/plugins/LimitRateInterceptor.java new file mode 100644 index 000000000..d7d9742d0 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/plugins/LimitRateInterceptor.java @@ -0,0 +1,133 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.rsocket.plugins; + +import io.rsocket.Payload; +import io.rsocket.RSocket; +import io.rsocket.util.RSocketProxy; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; + +/** + * Interceptor that adds {@link Flux#limitRate(int, int)} to publishers of outbound streams that + * breaks down or aggregates demand values from the remote end (i.e. {@code REQUEST_N} frames) into + * batches of a uniform size. For example the remote may request {@code Long.MAXVALUE} or it may + * start requesting one at a time, in both cases with the limit set to 64, the publisher will see a + * demand of 64 to start and subsequent batches of 48, i.e. continuing to prefetch and refill an + * internal queue when it falls to 75% full. The high and low tide marks are configurable. + * + *

    See static factory methods to create an instance for a requester or for a responder. + * + *

    Note: keep in mind that the {@code limitRate} operator always uses requests + * the same request values, even if the remote requests less than the limit. For example given a + * limit of 64, if the remote requests 4, 64 will be prefetched of which 4 will be sent and 60 will + * be cached. + * + * @since 1.0 + */ +public class LimitRateInterceptor implements RSocketInterceptor { + + private final int highTide; + private final int lowTide; + private final boolean requesterProxy; + + private LimitRateInterceptor(int highTide, int lowTide, boolean requesterProxy) { + this.highTide = highTide; + this.lowTide = lowTide; + this.requesterProxy = requesterProxy; + } + + @Override + public RSocket apply(RSocket socket) { + return requesterProxy ? new RequesterProxy(socket) : new ResponderProxy(socket); + } + + /** + * Create an interceptor for an {@code RSocket} that handles request-stream and/or request-channel + * interactions. + * + * @param prefetchRate the prefetch rate to pass to {@link Flux#limitRate(int)} + * @return the created interceptor + */ + public static LimitRateInterceptor forResponder(int prefetchRate) { + return forResponder(prefetchRate, prefetchRate); + } + + /** + * Create an interceptor for an {@code RSocket} that handles request-stream and/or request-channel + * interactions with more control over the overall prefetch rate and replenish threshold. + * + * @param highTide the high tide value to pass to {@link Flux#limitRate(int, int)} + * @param lowTide the low tide value to pass to {@link Flux#limitRate(int, int)} + * @return the created interceptor + */ + public static LimitRateInterceptor forResponder(int highTide, int lowTide) { + return new LimitRateInterceptor(highTide, lowTide, false); + } + + /** + * Create an interceptor for an {@code RSocket} that performs request-channel interactions. + * + * @param prefetchRate the prefetch rate to pass to {@link Flux#limitRate(int)} + * @return the created interceptor + */ + public static LimitRateInterceptor forRequester(int prefetchRate) { + return forRequester(prefetchRate, prefetchRate); + } + + /** + * Create an interceptor for an {@code RSocket} that performs request-channel interactions with + * more control over the overall prefetch rate and replenish threshold. + * + * @param highTide the high tide value to pass to {@link Flux#limitRate(int, int)} + * @param lowTide the low tide value to pass to {@link Flux#limitRate(int, int)} + * @return the created interceptor + */ + public static LimitRateInterceptor forRequester(int highTide, int lowTide) { + return new LimitRateInterceptor(highTide, lowTide, true); + } + + /** Responder side proxy, limits response streams. */ + private class ResponderProxy extends RSocketProxy { + + ResponderProxy(RSocket source) { + super(source); + } + + @Override + public Flux requestStream(Payload payload) { + return super.requestStream(payload).limitRate(highTide, lowTide); + } + + @Override + public Flux requestChannel(Publisher payloads) { + return super.requestChannel(payloads).limitRate(highTide, lowTide); + } + } + + /** Requester side proxy, limits channel request stream. */ + private class RequesterProxy extends RSocketProxy { + + RequesterProxy(RSocket source) { + super(source); + } + + @Override + public Flux requestChannel(Publisher payloads) { + return super.requestChannel(Flux.from(payloads).limitRate(highTide, lowTide)); + } + } +} diff --git a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/plugins/LimitRateInterceptorExample.java b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/plugins/LimitRateInterceptorExample.java index ac473d7b1..67a85b67f 100644 --- a/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/plugins/LimitRateInterceptorExample.java +++ b/rsocket-examples/src/main/java/io/rsocket/examples/transport/tcp/plugins/LimitRateInterceptorExample.java @@ -5,73 +5,64 @@ import io.rsocket.SocketAcceptor; import io.rsocket.core.RSocketConnector; import io.rsocket.core.RSocketServer; -import io.rsocket.examples.transport.tcp.stream.StreamingClient; -import io.rsocket.plugins.RSocketInterceptor; +import io.rsocket.plugins.LimitRateInterceptor; import io.rsocket.transport.netty.client.TcpClientTransport; import io.rsocket.transport.netty.server.TcpServerTransport; import io.rsocket.util.DefaultPayload; -import io.rsocket.util.RSocketProxy; import java.time.Duration; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; -import reactor.util.concurrent.Queues; public class LimitRateInterceptorExample { - private static final Logger logger = LoggerFactory.getLogger(StreamingClient.class); + private static final Logger logger = LoggerFactory.getLogger(LimitRateInterceptorExample.class); public static void main(String[] args) { - BlockingQueue requests = new ArrayBlockingQueue<>(100); RSocketServer.create( SocketAcceptor.with( new RSocket() { @Override public Flux requestStream(Payload payload) { return Flux.interval(Duration.ofMillis(100)) - .doOnRequest(e -> requests.add("Responder requestN(" + e + ")")) + .doOnRequest( + e -> logger.debug("Server publisher receives request for " + e)) .map(aLong -> DefaultPayload.create("Interval: " + aLong)); } @Override public Flux requestChannel(Publisher payloads) { return Flux.from(payloads) - .doOnRequest(e -> requests.add("Responder requestN(" + e + ")")); + .doOnRequest( + e -> logger.debug("Server publisher receives request for " + e)); } })) - .interceptors( - ir -> - ir.forRequester(LimitRateInterceptor.forRequester()) - .forResponder(LimitRateInterceptor.forResponder())) + .interceptors(registry -> registry.forResponder(LimitRateInterceptor.forResponder(64))) .bind(TcpServerTransport.create("localhost", 7000)) .subscribe(); RSocket socket = RSocketConnector.create() - .interceptors( - ir -> - ir.forRequester(LimitRateInterceptor.forRequester()) - .forResponder(LimitRateInterceptor.forResponder())) + .interceptors(registry -> registry.forRequester(LimitRateInterceptor.forRequester(64))) .connect(TcpClientTransport.create("localhost", 7000)) .block(); + logger.debug( + "\n\nStart of requestStream interaction\n" + "----------------------------------\n"); + socket .requestStream(DefaultPayload.create("Hello")) - .doOnRequest(e -> requests.add("Requester requestN(" + e + ")")) + .doOnRequest(e -> logger.debug("Client sends requestN(" + e + ")")) .map(Payload::getDataUtf8) .doOnNext(logger::debug) .take(10) .then() .block(); - requests.forEach(request -> logger.debug("Requested : {}", request)); - requests.clear(); + logger.debug( + "\n\nStart of requestChannel interaction\n" + "-----------------------------------\n"); - logger.debug("-----------------------------------------------------------------"); - logger.debug("Does requestChannel"); socket .requestChannel( Flux.generate( @@ -80,8 +71,8 @@ public Flux requestChannel(Publisher payloads) { sink.next(DefaultPayload.create("Next " + s)); return ++s; }) - .doOnRequest(e -> requests.add("Requester Upstream requestN(" + e + ")"))) - .doOnRequest(e -> requests.add("Requester Downstream requestN(" + e + ")")) + .doOnRequest(e -> logger.debug("Client publisher receives request for " + e))) + .doOnRequest(e -> logger.debug("Client sends requestN(" + e + ")")) .map(Payload::getDataUtf8) .doOnNext(logger::debug) .take(10) @@ -89,80 +80,5 @@ public Flux requestChannel(Publisher payloads) { .doFinally(signalType -> socket.dispose()) .then() .block(); - - requests.forEach(request -> logger.debug("Requested : {}", request)); - } - - static class LimitRateInterceptor implements RSocketInterceptor { - - final boolean requesterSide; - final int highTide; - final int lowTide; - - LimitRateInterceptor(boolean requesterSide, int highTide, int lowTide) { - this.requesterSide = requesterSide; - this.highTide = highTide; - this.lowTide = lowTide; - } - - @Override - public RSocket apply(RSocket socket) { - return new LimitRateRSocket(socket, requesterSide, highTide, lowTide); - } - - public static LimitRateInterceptor forRequester() { - return forRequester(Queues.SMALL_BUFFER_SIZE); - } - - public static LimitRateInterceptor forRequester(int limit) { - return forRequester(limit, limit); - } - - public static LimitRateInterceptor forRequester(int highTide, int lowTide) { - return new LimitRateInterceptor(true, highTide, lowTide); - } - - public static LimitRateInterceptor forResponder() { - return forRequester(Queues.SMALL_BUFFER_SIZE); - } - - public static LimitRateInterceptor forResponder(int limit) { - return forRequester(limit, limit); - } - - public static LimitRateInterceptor forResponder(int highTide, int lowTide) { - return new LimitRateInterceptor(false, highTide, lowTide); - } - } - - static class LimitRateRSocket extends RSocketProxy { - - final boolean requesterSide; - final int highTide; - final int lowTide; - - public LimitRateRSocket(RSocket source, boolean requesterSide, int highTide, int lowTide) { - super(source); - this.requesterSide = requesterSide; - this.highTide = highTide; - this.lowTide = lowTide; - } - - @Override - public Flux requestStream(Payload payload) { - Flux flux = super.requestStream(payload); - if (requesterSide) { - return flux; - } - return flux.limitRate(highTide, lowTide); - } - - @Override - public Flux requestChannel(Publisher payloads) { - if (requesterSide) { - return super.requestChannel(Flux.from(payloads).limitRate(highTide, lowTide)); - } - return super.requestChannel(payloads).limitRate(highTide, lowTide); - } } } From 08c2a78f25c0b5e78103e95cbf90144cd7ea118a Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Tue, 12 May 2020 02:02:43 +0300 Subject: [PATCH 59/62] provides support for TracingMetadata (#824) * provides support for TracingMetadata Signed-off-by: Oleh Dokuka * improves docs Signed-off-by: Oleh Dokuka --- .../io/rsocket/metadata/TracingMetadata.java | 110 +++++++++ .../metadata/TracingMetadataCodec.java | 172 ++++++++++++++ .../metadata/TracingMetadataCodecTest.java | 209 ++++++++++++++++++ 3 files changed, 491 insertions(+) create mode 100644 rsocket-core/src/main/java/io/rsocket/metadata/TracingMetadata.java create mode 100644 rsocket-core/src/main/java/io/rsocket/metadata/TracingMetadataCodec.java create mode 100644 rsocket-core/src/test/java/io/rsocket/metadata/TracingMetadataCodecTest.java diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/TracingMetadata.java b/rsocket-core/src/main/java/io/rsocket/metadata/TracingMetadata.java new file mode 100644 index 000000000..d276a9436 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/metadata/TracingMetadata.java @@ -0,0 +1,110 @@ +/* + * Copyright 2015-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.rsocket.metadata; + +/** + * Represents decoded tracing metadata which is fully compatible with Zipkin B3 propagation + * + * @since 1.0 + */ +public final class TracingMetadata { + + final long traceIdHigh; + final long traceId; + private final boolean hasParentId; + final long parentId; + final long spanId; + final boolean isEmpty; + final boolean isNotSampled; + final boolean isSampled; + final boolean isDebug; + + TracingMetadata( + long traceIdHigh, + long traceId, + long spanId, + boolean hasParentId, + long parentId, + boolean isEmpty, + boolean isNotSampled, + boolean isSampled, + boolean isDebug) { + this.traceIdHigh = traceIdHigh; + this.traceId = traceId; + this.spanId = spanId; + this.hasParentId = hasParentId; + this.parentId = parentId; + this.isEmpty = isEmpty; + this.isNotSampled = isNotSampled; + this.isSampled = isSampled; + this.isDebug = isDebug; + } + + /** When non-zero, the trace containing this span uses 128-bit trace identifiers. */ + public long traceIdHigh() { + return traceIdHigh; + } + + /** Unique 8-byte identifier for a trace, set on all spans within it. */ + public long traceId() { + return traceId; + } + + /** Indicates if the parent's {@link #spanId} or if this the root span in a trace. */ + public final boolean hasParent() { + return hasParentId; + } + + /** Returns the parent's {@link #spanId} where zero implies absent. */ + public long parentId() { + return parentId; + } + + /** + * Unique 8-byte identifier of this span within a trace. + * + *

    A span is uniquely identified in storage by ({@linkplain #traceId}, {@linkplain #spanId}). + */ + public long spanId() { + return spanId; + } + + /** Indicates that trace IDs should be accepted for tracing. */ + public boolean isSampled() { + return isSampled; + } + + /** Indicates that trace IDs should be force traced. */ + public boolean isDebug() { + return isDebug; + } + + /** Includes that there is sampling information and no trace IDs. */ + public boolean isEmpty() { + return isEmpty; + } + + /** + * Indicated that sampling decision is present. If {@code false} means that decision is unknown + * and says explicitly that {@link #isDebug()} and {@link #isSampled()} also returns {@code + * false}. + */ + public boolean isDecided() { + return isNotSampled || isDebug || isSampled; + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/TracingMetadataCodec.java b/rsocket-core/src/main/java/io/rsocket/metadata/TracingMetadataCodec.java new file mode 100644 index 000000000..eb44956f6 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/metadata/TracingMetadataCodec.java @@ -0,0 +1,172 @@ +package io.rsocket.metadata; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; + +/** + * Represents codes for tracing metadata which is fully compatible with Zipkin B3 propagation + * + * @since 1.0 + */ +public class TracingMetadataCodec { + + static final int FLAG_EXTENDED_TRACE_ID_SIZE = 0b0000_1000; + static final int FLAG_INCLUDE_PARENT_ID = 0b0000_0100; + static final int FLAG_NOT_SAMPLED = 0b0001_0000; + static final int FLAG_SAMPLED = 0b0010_0000; + static final int FLAG_DEBUG = 0b0100_0000; + static final int FLAG_IDS_SET = 0b1000_0000; + + public static ByteBuf encodeEmpty(ByteBufAllocator allocator, Flags flag) { + + return encode(allocator, true, 0, 0, false, 0, 0, false, flag); + } + + public static ByteBuf encode128( + ByteBufAllocator allocator, + long traceIdHigh, + long traceId, + long spanId, + long parentId, + Flags flag) { + + return encode(allocator, false, traceIdHigh, traceId, true, spanId, parentId, true, flag); + } + + public static ByteBuf encode128( + ByteBufAllocator allocator, long traceIdHigh, long traceId, long spanId, Flags flag) { + + return encode(allocator, false, traceIdHigh, traceId, true, spanId, 0, false, flag); + } + + public static ByteBuf encode64( + ByteBufAllocator allocator, long traceId, long spanId, long parentId, Flags flag) { + + return encode(allocator, false, 0, traceId, false, spanId, parentId, true, flag); + } + + public static ByteBuf encode64( + ByteBufAllocator allocator, long traceId, long spanId, Flags flag) { + return encode(allocator, false, 0, traceId, false, spanId, 0, false, flag); + } + + static ByteBuf encode( + ByteBufAllocator allocator, + boolean isEmpty, + long traceIdHigh, + long traceId, + boolean extendedTraceId, + long spanId, + long parentId, + boolean includesParent, + Flags flag) { + int size = + 1 + + (isEmpty + ? 0 + : (Long.BYTES + + Long.BYTES + + (extendedTraceId ? Long.BYTES : 0) + + (includesParent ? Long.BYTES : 0))); + final ByteBuf buffer = allocator.buffer(size); + + int byteFlags = 0; + switch (flag) { + case NOT_SAMPLE: + byteFlags |= FLAG_NOT_SAMPLED; + break; + case SAMPLE: + byteFlags |= FLAG_SAMPLED; + break; + case DEBUG: + byteFlags |= FLAG_DEBUG; + break; + } + + if (isEmpty) { + return buffer.writeByte(byteFlags); + } + + byteFlags |= FLAG_IDS_SET; + + if (extendedTraceId) { + byteFlags |= FLAG_EXTENDED_TRACE_ID_SIZE; + } + + if (includesParent) { + byteFlags |= FLAG_INCLUDE_PARENT_ID; + } + + buffer.writeByte(byteFlags); + + if (extendedTraceId) { + buffer.writeLong(traceIdHigh); + } + + buffer.writeLong(traceId).writeLong(spanId); + + if (includesParent) { + buffer.writeLong(parentId); + } + + return buffer; + } + + public static TracingMetadata decode(ByteBuf byteBuf) { + byteBuf.markReaderIndex(); + try { + byte flags = byteBuf.readByte(); + boolean isNotSampled = (flags & FLAG_NOT_SAMPLED) == FLAG_NOT_SAMPLED; + boolean isSampled = (flags & FLAG_SAMPLED) == FLAG_SAMPLED; + boolean isDebug = (flags & FLAG_DEBUG) == FLAG_DEBUG; + boolean isIDSet = (flags & FLAG_IDS_SET) == FLAG_IDS_SET; + + if (!isIDSet) { + return new TracingMetadata(0, 0, 0, false, 0, true, isNotSampled, isSampled, isDebug); + } + + boolean extendedTraceId = + (flags & FLAG_EXTENDED_TRACE_ID_SIZE) == FLAG_EXTENDED_TRACE_ID_SIZE; + + long traceIdHigh; + if (extendedTraceId) { + traceIdHigh = byteBuf.readLong(); + } else { + traceIdHigh = 0; + } + + long traceId = byteBuf.readLong(); + long spanId = byteBuf.readLong(); + + boolean includesParent = (flags & FLAG_INCLUDE_PARENT_ID) == FLAG_INCLUDE_PARENT_ID; + + long parentId; + if (includesParent) { + parentId = byteBuf.readLong(); + } else { + parentId = 0; + } + + return new TracingMetadata( + traceIdHigh, + traceId, + spanId, + includesParent, + parentId, + false, + isNotSampled, + isSampled, + isDebug); + } finally { + byteBuf.resetReaderIndex(); + } + } + + public enum Flags { + UNDECIDED, + NOT_SAMPLE, + SAMPLE, + DEBUG + } +} diff --git a/rsocket-core/src/test/java/io/rsocket/metadata/TracingMetadataCodecTest.java b/rsocket-core/src/test/java/io/rsocket/metadata/TracingMetadataCodecTest.java new file mode 100644 index 000000000..cb8478c13 --- /dev/null +++ b/rsocket-core/src/test/java/io/rsocket/metadata/TracingMetadataCodecTest.java @@ -0,0 +1,209 @@ +package io.rsocket.metadata; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.util.ReferenceCounted; +import io.rsocket.buffer.LeaksTrackingByteBufAllocator; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Stream; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class TracingMetadataCodecTest { + + private static Stream flags() { + return Stream.of(TracingMetadataCodec.Flags.values()); + } + + @ParameterizedTest + @MethodSource("flags") + public void shouldEncodeEmptyTrace(TracingMetadataCodec.Flags expectedFlag) { + LeaksTrackingByteBufAllocator allocator = + LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); + ByteBuf byteBuf = TracingMetadataCodec.encodeEmpty(allocator, expectedFlag); + + TracingMetadata tracingMetadata = TracingMetadataCodec.decode(byteBuf); + + Assertions.assertThat(tracingMetadata) + .matches(TracingMetadata::isEmpty) + .matches( + tm -> { + switch (expectedFlag) { + case UNDECIDED: + return !tm.isDecided(); + case NOT_SAMPLE: + return tm.isDecided() && !tm.isSampled(); + case SAMPLE: + return tm.isDecided() && tm.isSampled(); + case DEBUG: + return tm.isDecided() && tm.isDebug(); + } + return false; + }); + Assertions.assertThat(byteBuf).matches(ReferenceCounted::release); + allocator.assertHasNoLeaks(); + } + + @ParameterizedTest + @MethodSource("flags") + public void shouldEncodeTrace64WithParent(TracingMetadataCodec.Flags expectedFlag) { + long traceId = ThreadLocalRandom.current().nextLong(); + long spanId = ThreadLocalRandom.current().nextLong(); + long parentId = ThreadLocalRandom.current().nextLong(); + LeaksTrackingByteBufAllocator allocator = + LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); + ByteBuf byteBuf = + TracingMetadataCodec.encode64(allocator, traceId, spanId, parentId, expectedFlag); + + TracingMetadata tracingMetadata = TracingMetadataCodec.decode(byteBuf); + + Assertions.assertThat(tracingMetadata) + .matches(metadata -> !metadata.isEmpty()) + .matches(tm -> tm.traceIdHigh() == 0) + .matches(tm -> tm.traceId() == traceId) + .matches(tm -> tm.spanId() == spanId) + .matches(tm -> tm.hasParent()) + .matches(tm -> tm.parentId() == parentId) + .matches( + tm -> { + switch (expectedFlag) { + case UNDECIDED: + return !tm.isDecided(); + case NOT_SAMPLE: + return tm.isDecided() && !tm.isSampled(); + case SAMPLE: + return tm.isDecided() && tm.isSampled(); + case DEBUG: + return tm.isDecided() && tm.isDebug(); + } + return false; + }); + Assertions.assertThat(byteBuf).matches(ReferenceCounted::release); + allocator.assertHasNoLeaks(); + } + + @ParameterizedTest + @MethodSource("flags") + public void shouldEncodeTrace64(TracingMetadataCodec.Flags expectedFlag) { + long traceId = ThreadLocalRandom.current().nextLong(); + long spanId = ThreadLocalRandom.current().nextLong(); + LeaksTrackingByteBufAllocator allocator = + LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); + ByteBuf byteBuf = TracingMetadataCodec.encode64(allocator, traceId, spanId, expectedFlag); + + TracingMetadata tracingMetadata = TracingMetadataCodec.decode(byteBuf); + + Assertions.assertThat(tracingMetadata) + .matches(metadata -> !metadata.isEmpty()) + .matches(tm -> tm.traceIdHigh() == 0) + .matches(tm -> tm.traceId() == traceId) + .matches(tm -> tm.spanId() == spanId) + .matches(tm -> !tm.hasParent()) + .matches(tm -> tm.parentId() == 0) + .matches( + tm -> { + switch (expectedFlag) { + case UNDECIDED: + return !tm.isDecided(); + case NOT_SAMPLE: + return tm.isDecided() && !tm.isSampled(); + case SAMPLE: + return tm.isDecided() && tm.isSampled(); + case DEBUG: + return tm.isDecided() && tm.isDebug(); + } + return false; + }); + Assertions.assertThat(byteBuf).matches(ReferenceCounted::release); + allocator.assertHasNoLeaks(); + } + + @ParameterizedTest + @MethodSource("flags") + public void shouldEncodeTrace128WithParent(TracingMetadataCodec.Flags expectedFlag) { + long traceIdHighLocal; + do { + traceIdHighLocal = ThreadLocalRandom.current().nextLong(); + + } while (traceIdHighLocal == 0); + long traceIdHigh = traceIdHighLocal; + long traceId = ThreadLocalRandom.current().nextLong(); + long spanId = ThreadLocalRandom.current().nextLong(); + long parentId = ThreadLocalRandom.current().nextLong(); + LeaksTrackingByteBufAllocator allocator = + LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); + ByteBuf byteBuf = + TracingMetadataCodec.encode128( + allocator, traceIdHigh, traceId, spanId, parentId, expectedFlag); + + TracingMetadata tracingMetadata = TracingMetadataCodec.decode(byteBuf); + + Assertions.assertThat(tracingMetadata) + .matches(metadata -> !metadata.isEmpty()) + .matches(tm -> tm.traceIdHigh() == traceIdHigh) + .matches(tm -> tm.traceId() == traceId) + .matches(tm -> tm.spanId() == spanId) + .matches(tm -> tm.hasParent()) + .matches(tm -> tm.parentId() == parentId) + .matches( + tm -> { + switch (expectedFlag) { + case UNDECIDED: + return !tm.isDecided(); + case NOT_SAMPLE: + return tm.isDecided() && !tm.isSampled(); + case SAMPLE: + return tm.isDecided() && tm.isSampled(); + case DEBUG: + return tm.isDecided() && tm.isDebug(); + } + return false; + }); + Assertions.assertThat(byteBuf).matches(ReferenceCounted::release); + allocator.assertHasNoLeaks(); + } + + @ParameterizedTest + @MethodSource("flags") + public void shouldEncodeTrace128(TracingMetadataCodec.Flags expectedFlag) { + long traceIdHighLocal; + do { + traceIdHighLocal = ThreadLocalRandom.current().nextLong(); + + } while (traceIdHighLocal == 0); + long traceIdHigh = traceIdHighLocal; + long traceId = ThreadLocalRandom.current().nextLong(); + long spanId = ThreadLocalRandom.current().nextLong(); + LeaksTrackingByteBufAllocator allocator = + LeaksTrackingByteBufAllocator.instrument(ByteBufAllocator.DEFAULT); + ByteBuf byteBuf = + TracingMetadataCodec.encode128(allocator, traceIdHigh, traceId, spanId, expectedFlag); + + TracingMetadata tracingMetadata = TracingMetadataCodec.decode(byteBuf); + + Assertions.assertThat(tracingMetadata) + .matches(metadata -> !metadata.isEmpty()) + .matches(tm -> tm.traceIdHigh() == traceIdHigh) + .matches(tm -> tm.traceId() == traceId) + .matches(tm -> tm.spanId() == spanId) + .matches(tm -> !tm.hasParent()) + .matches(tm -> tm.parentId() == 0) + .matches( + tm -> { + switch (expectedFlag) { + case UNDECIDED: + return !tm.isDecided(); + case NOT_SAMPLE: + return tm.isDecided() && !tm.isSampled(); + case SAMPLE: + return tm.isDecided() && tm.isSampled(); + case DEBUG: + return tm.isDecided() && tm.isDebug(); + } + return false; + }); + Assertions.assertThat(byteBuf).matches(ReferenceCounted::release); + allocator.assertHasNoLeaks(); + } +} From a9e2954c168e6923836166da326abdc6119eb5c3 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Tue, 12 May 2020 09:46:23 +0300 Subject: [PATCH 60/62] bumps version to 1.0.0 Signed-off-by: Oleh Dokuka --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 2e429e05a..c5c4fab15 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,5 +11,5 @@ # See the License for the specific language governing permissions and # limitations under the License. # -version=1.0.0-RC8 +version=1.0.0 perfBaselineVersion=1.0.0-RC7 From 8cb9e747e9b307a3c15312e460c25a8cbdb57ce5 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Tue, 12 May 2020 09:54:56 +0300 Subject: [PATCH 61/62] fixes AuthMetadataCodec methods names Signed-off-by: Oleh Dokuka --- .../rsocket/metadata/AuthMetadataCodec.java | 22 +++++++++---------- .../security/AuthMetadataFlyweight.java | 16 +++++++------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/AuthMetadataCodec.java b/rsocket-core/src/main/java/io/rsocket/metadata/AuthMetadataCodec.java index 41dafb33d..d908abb3c 100644 --- a/rsocket-core/src/main/java/io/rsocket/metadata/AuthMetadataCodec.java +++ b/rsocket-core/src/main/java/io/rsocket/metadata/AuthMetadataCodec.java @@ -174,7 +174,7 @@ public static boolean isWellKnownAuthType(ByteBuf metadata) { * field's value is length or unknown auth type * @throws IllegalStateException if not enough readable bytes in the given {@link ByteBuf} */ - public static WellKnownAuthType decodeWellKnownAuthType(ByteBuf metadata) { + public static WellKnownAuthType readWellKnownAuthType(ByteBuf metadata) { if (metadata.readableBytes() < 1) { throw new IllegalStateException( "Unable to decode Well Know Auth type. Not enough readable bytes"); @@ -195,7 +195,7 @@ public static WellKnownAuthType decodeWellKnownAuthType(ByteBuf metadata) { * @param metadata * @return */ - public static CharSequence decodeCustomAuthType(ByteBuf metadata) { + public static CharSequence readCustomAuthType(ByteBuf metadata) { if (metadata.readableBytes() < 2) { throw new IllegalStateException( "Unable to decode custom Auth type. Not enough readable bytes"); @@ -226,7 +226,7 @@ public static CharSequence decodeCustomAuthType(ByteBuf metadata) { * @return sliced {@link ByteBuf} or {@link Unpooled#EMPTY_BUFFER} if no bytes readable in the * given one */ - public static ByteBuf decodePayload(ByteBuf metadata) { + public static ByteBuf readPayload(ByteBuf metadata) { if (metadata.readableBytes() == 0) { return Unpooled.EMPTY_BUFFER; } @@ -242,8 +242,8 @@ public static ByteBuf decodePayload(ByteBuf metadata) { * simpleAuthMetadata#readIndex} should be set to the username length byte * @return sliced {@link ByteBuf} or {@link Unpooled#EMPTY_BUFFER} if username length is zero */ - public static ByteBuf decodeUsername(ByteBuf simpleAuthMetadata) { - short usernameLength = decodeUsernameLength(simpleAuthMetadata); + public static ByteBuf readUsername(ByteBuf simpleAuthMetadata) { + short usernameLength = readUsernameLength(simpleAuthMetadata); if (usernameLength == 0) { return Unpooled.EMPTY_BUFFER; @@ -260,7 +260,7 @@ public static ByteBuf decodeUsername(ByteBuf simpleAuthMetadata) { * simpleAuthMetadata#readIndex} should be set to the beginning of the password bytes * @return sliced {@link ByteBuf} or {@link Unpooled#EMPTY_BUFFER} if password length is zero */ - public static ByteBuf decodePassword(ByteBuf simpleAuthMetadata) { + public static ByteBuf readPassword(ByteBuf simpleAuthMetadata) { if (simpleAuthMetadata.readableBytes() == 0) { return Unpooled.EMPTY_BUFFER; } @@ -275,8 +275,8 @@ public static ByteBuf decodePassword(ByteBuf simpleAuthMetadata) { * simpleAuthMetadata#readIndex} should be set to the username length byte * @return {@code char[]} which represents UTF-8 username */ - public static char[] decodeUsernameAsCharArray(ByteBuf simpleAuthMetadata) { - short usernameLength = decodeUsernameLength(simpleAuthMetadata); + public static char[] readUsernameAsCharArray(ByteBuf simpleAuthMetadata) { + short usernameLength = readUsernameLength(simpleAuthMetadata); if (usernameLength == 0) { return EMPTY_CHARS_ARRAY; @@ -293,7 +293,7 @@ public static char[] decodeUsernameAsCharArray(ByteBuf simpleAuthMetadata) { * simpleAuthMetadata#readIndex} should be set to the beginning of the password bytes * @return {@code char[]} which represents UTF-8 password */ - public static char[] decodePasswordAsCharArray(ByteBuf simpleAuthMetadata) { + public static char[] readPasswordAsCharArray(ByteBuf simpleAuthMetadata) { if (simpleAuthMetadata.readableBytes() == 0) { return EMPTY_CHARS_ARRAY; } @@ -309,7 +309,7 @@ public static char[] decodePasswordAsCharArray(ByteBuf simpleAuthMetadata) { * simpleAuthMetadata#readIndex} should be set to the beginning of the password bytes * @return {@code char[]} which represents UTF-8 password */ - public static char[] decodeBearerTokenAsCharArray(ByteBuf bearerAuthMetadata) { + public static char[] readBearerTokenAsCharArray(ByteBuf bearerAuthMetadata) { if (bearerAuthMetadata.readableBytes() == 0) { return EMPTY_CHARS_ARRAY; } @@ -317,7 +317,7 @@ public static char[] decodeBearerTokenAsCharArray(ByteBuf bearerAuthMetadata) { return CharByteBufUtil.readUtf8(bearerAuthMetadata, bearerAuthMetadata.readableBytes()); } - private static short decodeUsernameLength(ByteBuf simpleAuthMetadata) { + private static short readUsernameLength(ByteBuf simpleAuthMetadata) { if (simpleAuthMetadata.readableBytes() < 1) { throw new IllegalStateException( "Unable to decode custom username. Not enough readable bytes"); diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java index fd990d273..e1a8ba449 100644 --- a/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java @@ -107,7 +107,7 @@ public static boolean isWellKnownAuthType(ByteBuf metadata) { * @throws IllegalStateException if not enough readable bytes in the given {@link ByteBuf} */ public static WellKnownAuthType decodeWellKnownAuthType(ByteBuf metadata) { - return WellKnownAuthType.cast(AuthMetadataCodec.decodeWellKnownAuthType(metadata)); + return WellKnownAuthType.cast(AuthMetadataCodec.readWellKnownAuthType(metadata)); } /** @@ -117,7 +117,7 @@ public static WellKnownAuthType decodeWellKnownAuthType(ByteBuf metadata) { * @return */ public static CharSequence decodeCustomAuthType(ByteBuf metadata) { - return AuthMetadataCodec.decodeCustomAuthType(metadata); + return AuthMetadataCodec.readCustomAuthType(metadata); } /** @@ -130,7 +130,7 @@ public static CharSequence decodeCustomAuthType(ByteBuf metadata) { * given one */ public static ByteBuf decodePayload(ByteBuf metadata) { - return AuthMetadataCodec.decodePayload(metadata); + return AuthMetadataCodec.readPayload(metadata); } /** @@ -142,7 +142,7 @@ public static ByteBuf decodePayload(ByteBuf metadata) { * @return sliced {@link ByteBuf} or {@link Unpooled#EMPTY_BUFFER} if username length is zero */ public static ByteBuf decodeUsername(ByteBuf simpleAuthMetadata) { - return AuthMetadataCodec.decodeUsername(simpleAuthMetadata); + return AuthMetadataCodec.readUsername(simpleAuthMetadata); } /** @@ -154,7 +154,7 @@ public static ByteBuf decodeUsername(ByteBuf simpleAuthMetadata) { * @return sliced {@link ByteBuf} or {@link Unpooled#EMPTY_BUFFER} if password length is zero */ public static ByteBuf decodePassword(ByteBuf simpleAuthMetadata) { - return AuthMetadataCodec.decodePassword(simpleAuthMetadata); + return AuthMetadataCodec.readPassword(simpleAuthMetadata); } /** * Read up to 257 {@code bytes} from the given {@link ByteBuf} where the first byte is username @@ -165,7 +165,7 @@ public static ByteBuf decodePassword(ByteBuf simpleAuthMetadata) { * @return {@code char[]} which represents UTF-8 username */ public static char[] decodeUsernameAsCharArray(ByteBuf simpleAuthMetadata) { - return AuthMetadataCodec.decodeUsernameAsCharArray(simpleAuthMetadata); + return AuthMetadataCodec.readUsernameAsCharArray(simpleAuthMetadata); } /** @@ -177,7 +177,7 @@ public static char[] decodeUsernameAsCharArray(ByteBuf simpleAuthMetadata) { * @return {@code char[]} which represents UTF-8 password */ public static char[] decodePasswordAsCharArray(ByteBuf simpleAuthMetadata) { - return AuthMetadataCodec.decodePasswordAsCharArray(simpleAuthMetadata); + return AuthMetadataCodec.readPasswordAsCharArray(simpleAuthMetadata); } /** @@ -189,6 +189,6 @@ public static char[] decodePasswordAsCharArray(ByteBuf simpleAuthMetadata) { * @return {@code char[]} which represents UTF-8 password */ public static char[] decodeBearerTokenAsCharArray(ByteBuf bearerAuthMetadata) { - return AuthMetadataCodec.decodeBearerTokenAsCharArray(bearerAuthMetadata); + return AuthMetadataCodec.readBearerTokenAsCharArray(bearerAuthMetadata); } } From d903e9635a159285b6943ea93156c31aa406ba5d Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Tue, 12 May 2020 16:14:30 +0300 Subject: [PATCH 62/62] bamps versions Signed-off-by: Oleh Dokuka --- README.md | 8 ++-- .../frame/FrameHeaderFlyweightPerf.java | 10 ++-- .../rsocket/frame/PayloadFlyweightPerf.java | 8 ++-- .../UnicastVsDefaultMonoProcessorPerf.java | 46 ------------------- gradle.properties | 4 +- 5 files changed, 15 insertions(+), 61 deletions(-) delete mode 100644 benchmarks/src/main/java/io/rsocket/internal/UnicastVsDefaultMonoProcessorPerf.java diff --git a/README.md b/README.md index 8a2ba991c..f8110a31e 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ repositories { mavenCentral() } dependencies { - implementation 'io.rsocket:rsocket-core:1.0.0-RC7' - implementation 'io.rsocket:rsocket-transport-netty:1.0.0-RC7' + implementation 'io.rsocket:rsocket-core:1.0.0' + implementation 'io.rsocket:rsocket-transport-netty:1.0.0' } ``` @@ -40,8 +40,8 @@ repositories { maven { url 'https://oss.jfrog.org/oss-snapshot-local' } } dependencies { - implementation 'io.rsocket:rsocket-core:1.0.0-RC8-SNAPSHOT' - implementation 'io.rsocket:rsocket-transport-netty:1.0.0-RC8-SNAPSHOT' + implementation 'io.rsocket:rsocket-core:1.0.1-SNAPSHOT' + implementation 'io.rsocket:rsocket-transport-netty:1.0.1-SNAPSHOT' } ``` diff --git a/benchmarks/src/main/java/io/rsocket/frame/FrameHeaderFlyweightPerf.java b/benchmarks/src/main/java/io/rsocket/frame/FrameHeaderFlyweightPerf.java index 139114466..b4ac808d0 100644 --- a/benchmarks/src/main/java/io/rsocket/frame/FrameHeaderFlyweightPerf.java +++ b/benchmarks/src/main/java/io/rsocket/frame/FrameHeaderFlyweightPerf.java @@ -16,7 +16,7 @@ public class FrameHeaderFlyweightPerf { @Benchmark public void encode(Input input) { - ByteBuf byteBuf = FrameHeaderFlyweight.encodeStreamZero(input.allocator, FrameType.SETUP, 0); + ByteBuf byteBuf = FrameHeaderCodec.encodeStreamZero(input.allocator, FrameType.SETUP, 0); boolean release = byteBuf.release(); input.bh.consume(release); } @@ -24,9 +24,9 @@ public void encode(Input input) { @Benchmark public void decode(Input input) { ByteBuf frame = input.frame; - FrameType frameType = FrameHeaderFlyweight.frameType(frame); - int streamId = FrameHeaderFlyweight.streamId(frame); - int flags = FrameHeaderFlyweight.flags(frame); + FrameType frameType = FrameHeaderCodec.frameType(frame); + int streamId = FrameHeaderCodec.streamId(frame); + int flags = FrameHeaderCodec.flags(frame); input.bh.consume(streamId); input.bh.consume(flags); input.bh.consume(frameType); @@ -44,7 +44,7 @@ public void setup(Blackhole bh) { this.bh = bh; this.frameType = FrameType.REQUEST_RESPONSE; allocator = ByteBufAllocator.DEFAULT; - frame = FrameHeaderFlyweight.encode(allocator, 123, FrameType.SETUP, 0); + frame = FrameHeaderCodec.encode(allocator, 123, FrameType.SETUP, 0); } @TearDown diff --git a/benchmarks/src/main/java/io/rsocket/frame/PayloadFlyweightPerf.java b/benchmarks/src/main/java/io/rsocket/frame/PayloadFlyweightPerf.java index 89e50a6e9..01d82a08f 100644 --- a/benchmarks/src/main/java/io/rsocket/frame/PayloadFlyweightPerf.java +++ b/benchmarks/src/main/java/io/rsocket/frame/PayloadFlyweightPerf.java @@ -18,7 +18,7 @@ public class PayloadFlyweightPerf { @Benchmark public void encode(Input input) { ByteBuf encode = - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( input.allocator, 100, false, @@ -33,8 +33,8 @@ public void encode(Input input) { @Benchmark public void decode(Input input) { ByteBuf frame = input.payload; - ByteBuf data = PayloadFrameFlyweight.data(frame); - ByteBuf metadata = PayloadFrameFlyweight.metadata(frame); + ByteBuf data = PayloadFrameCodec.data(frame); + ByteBuf metadata = PayloadFrameCodec.metadata(frame); input.bh.consume(data); input.bh.consume(metadata); } @@ -57,7 +57,7 @@ public void setup(Blackhole bh) { // Encode a payload and then copy it a single bytebuf payload = allocator.buffer(); ByteBuf encode = - PayloadFrameFlyweight.encode( + PayloadFrameCodec.encode( allocator, 100, false, diff --git a/benchmarks/src/main/java/io/rsocket/internal/UnicastVsDefaultMonoProcessorPerf.java b/benchmarks/src/main/java/io/rsocket/internal/UnicastVsDefaultMonoProcessorPerf.java deleted file mode 100644 index 963067ef0..000000000 --- a/benchmarks/src/main/java/io/rsocket/internal/UnicastVsDefaultMonoProcessorPerf.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.rsocket.internal; - -import io.rsocket.MaxPerfSubscriber; -import java.util.concurrent.TimeUnit; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.Warmup; -import org.openjdk.jmh.infra.Blackhole; -import reactor.core.publisher.MonoProcessor; - -@BenchmarkMode({Mode.Throughput, Mode.SampleTime}) -@Fork(1) -@Warmup(iterations = 10) -@Measurement(iterations = 10, time = 20) -@State(Scope.Benchmark) -@OutputTimeUnit(TimeUnit.MICROSECONDS) -public class UnicastVsDefaultMonoProcessorPerf { - - @Benchmark - public void monoProcessorPerf(Blackhole bh) { - MaxPerfSubscriber subscriber = new MaxPerfSubscriber<>(bh); - MonoProcessor monoProcessor = MonoProcessor.create(); - monoProcessor.onNext(1); - monoProcessor.subscribe(subscriber); - - bh.consume(monoProcessor); - bh.consume(subscriber); - } - - @Benchmark - public void unicastMonoProcessorPerf(Blackhole bh) { - MaxPerfSubscriber subscriber = new MaxPerfSubscriber<>(bh); - UnicastMonoProcessor monoProcessor = UnicastMonoProcessor.create(); - monoProcessor.onNext(1); - monoProcessor.subscribe(subscriber); - - bh.consume(monoProcessor); - bh.consume(subscriber); - } -} diff --git a/gradle.properties b/gradle.properties index c5c4fab15..b0b107ec4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,5 +11,5 @@ # See the License for the specific language governing permissions and # limitations under the License. # -version=1.0.0 -perfBaselineVersion=1.0.0-RC7 +version=1.0.1 +perfBaselineVersion=1.0.0